mirror of
https://github.com/Horizontal42/summarum.git
synced 2026-11-11 02:12:00 +03:00
docs: tabbed settings, auto-update toggle and diagnostics
This commit is contained in:
parent
1e06281f5e
commit
6f190c7245
4 changed files with 220 additions and 46 deletions
109
ARCHITECTURE.md
109
ARCHITECTURE.md
|
|
@ -21,6 +21,8 @@ src/
|
|||
index.ts SumEngine facade + extension API + sheet totals
|
||||
ui/
|
||||
editor.ts CodeMirror 6 wiring: highlight, results overlay, copy
|
||||
search.ts cross-sheet search overlay (text + result-value queries)
|
||||
export.ts renders the sheet + results to a PNG (clipboard/file)
|
||||
app.css themes (light/dark via CSS variables)
|
||||
main.ts app bootstrap: documents, settings, status bar, dnd
|
||||
storage.ts Tauri commands / localStorage fallback
|
||||
|
|
@ -29,8 +31,8 @@ src/
|
|||
updater.ts checks tauri-plugin-updater, installs + relaunches
|
||||
src-tauri/
|
||||
src/main.rs tray, hide-to-tray, storage commands, rates fetching
|
||||
(open.er-api.com + CoinGecko + Yahoo Finance) with
|
||||
cache, historical rates (frankfurter.app, permanent
|
||||
(open.er-api.com + CoinGecko + Yahoo Finance v8 chart)
|
||||
with cache, historical rates (frankfurter.dev, permanent
|
||||
per-date cache), backups, data-folder migration,
|
||||
file drops, plugin registration (autostart,
|
||||
global-shortcut, single-instance, opener, dialog,
|
||||
|
|
@ -111,7 +113,49 @@ snapshot price) plus the mirror list in `src-tauri/src/main.rs`.
|
|||
bypasses `formatValue` — the results overlay renders an SVG sparkline
|
||||
instead of a text string.
|
||||
- The registry is rebuilt never; extensions mutate it (add phrases/functions)
|
||||
at startup.
|
||||
at startup. `buildRegistry()` (`registry.ts`) is a thin orchestrator over
|
||||
`registerCoreVocab` / `registerUnits` / `registerAreaVolume` /
|
||||
`registerCurrencies` / `registerExtraUnits` / `registerCrypto` /
|
||||
`registerCompletions` — **call order matters**, since `Registry.addPhrase`
|
||||
keeps the *first* registration of an identical phrase and drops later ones
|
||||
(core vocab must win over unit/currency phrases that happen to collide).
|
||||
- Multiplying two same-dimension quantities (`2 kg * 500 g`) converts the
|
||||
right operand into the left operand's unit before multiplying, so the
|
||||
result keeps a sane unit (`1 kg`, not `1000` in a `kg` label). Cross-dimension
|
||||
multiplication (`$12 * 3`, `5 hours * 2`) is unaffected — the right side is
|
||||
treated as a plain scale factor there.
|
||||
- A `//` only starts a comment at the start of a line or after whitespace
|
||||
(`findCommentStart` in `index.ts`), so a URL like `see https://example.com`
|
||||
isn't truncated at the double slash.
|
||||
- **Date literals and formats.** Besides ISO (`2024-01-01`), the lexer accepts
|
||||
`D.M.YYYY` / `M/D/YYYY` (`31.12.2024`, `12/31/2024`) with the same separator
|
||||
on both sides and a 4-digit year. Day/month order is disambiguated per
|
||||
literal: a component `>12` fixes which side is the day; if both are `≤12`,
|
||||
the engine's `dateFormat` setting decides. All three literal forms reject
|
||||
out-of-range components via a round-trip check (`validDateMs` in `lexer.ts`
|
||||
— `new Date(y, mo-1, d)` silently *rolls over* invalid dates like
|
||||
`2026-13-45` instead of erroring, so the constructed date's year/month/day
|
||||
are compared back against the input). `SumEngine` resolves `dateFormat:
|
||||
"system"` to an actual day/month order once via `detectDateOrder()`
|
||||
(`datetime.ts`, probes `Intl.DateTimeFormat` on a `2000-12-31` fixture) and
|
||||
passes it into `tokenize()` → `lexLine()` on every call; `"dmy"`/`"mdy"`
|
||||
settings skip detection. Display works the same way in reverse: `formatter.ts`
|
||||
either uses the OS-locale `Intl` formatting (`"system"`) or builds a fixed
|
||||
`YYYY-MM-DD` / `DD.MM.YYYY` / `MM/DD/YYYY` string via
|
||||
`Intl.DateTimeFormat(...).formatToParts()` (needed to respect the value's
|
||||
own IANA timezone, which a hand-rolled `ms`-based formatter can't do).
|
||||
`Intl.DateTimeFormat` instances are cached by options key in `formatter.ts`
|
||||
(`dtf()`) since constructing one is comparatively expensive and every dated
|
||||
line rebuilds its format on every render.
|
||||
- Timezone name parsing (`time in Europe/Berlin`) has two independent traps.
|
||||
First, `parseLine`'s noise-filter only keeps word tokens that immediately
|
||||
follow a `conv` token (`in`/`as`/...); any other token type resets that
|
||||
state — a bare `/` between two timezone words would normally drop the
|
||||
second word before the parser ever sees it, so the filter special-cases a
|
||||
`/` that appears while collecting post-conv words. Second, `parseTarget()`'s
|
||||
word-collecting loop mirrors that same allowance so it keeps consuming past
|
||||
the `/` instead of stopping at the first word. Both places need the
|
||||
exception — fixing only one still drops `Europe/Berlin` down to `Europe`.
|
||||
|
||||
## Cross-sheet references
|
||||
|
||||
|
|
@ -147,10 +191,19 @@ recursing forever.
|
|||
scroll/geometry changes. Click-to-copy reads `data-value`.
|
||||
- Engine evaluation is synchronous on every keystroke — a full sheet parse
|
||||
is well under a millisecond, so there's no debounce or worker.
|
||||
- Search-all-sheets (`Ctrl+F`) is a plain line scan over `data.contents` in
|
||||
`main.ts`, not a CodeMirror search extension — it has to cross documents,
|
||||
so it never touches the active editor until a result is clicked
|
||||
(`editor.goToLine`).
|
||||
- Search-all-sheets (`Ctrl+F`) is a plain line scan over every open sheet's
|
||||
text, not a CodeMirror search extension — it has to cross documents, so it
|
||||
never touches the active editor until a result is clicked
|
||||
(`editor.goToLine`). It lives in `src/ui/search.ts` (`initSearch()`), which
|
||||
owns the `#search-overlay` DOM and its own 150 ms input debounce — a
|
||||
result-value query (`>1000`) re-evaluates every open sheet, so debouncing
|
||||
matters once there are more than a couple of sheets open.
|
||||
- The settings popover is split into four tabs (General / Format / Data /
|
||||
Updates) in `index.html`, each a `.settings-tabpanel` toggled by a
|
||||
`#settings-tabs .tab` button in `bindSettingsUI()` (`main.ts`) — plain
|
||||
`classList.toggle`, no router. Tab styling reuses the sidebar's
|
||||
`doc-item.active` idiom (`background: var(--sel)` for the active pill)
|
||||
rather than introducing a new visual pattern.
|
||||
|
||||
## Storage
|
||||
|
||||
|
|
@ -165,7 +218,28 @@ recursing forever.
|
|||
|
||||
- `updater.ts` calls `tauri-plugin-updater`'s `check()` on boot; if a signed
|
||||
update is available the user is prompted, then `downloadAndInstall()` +
|
||||
`tauri-plugin-process`'s `relaunch()`.
|
||||
`tauri-plugin-process`'s `relaunch()`. `checkForUpdate()` returns
|
||||
`AvailableUpdate | null | "error"` — `null` means the check succeeded and
|
||||
found nothing newer, `"error"` means the check itself failed (offline,
|
||||
signature mismatch, etc.); the distinction only matters for the manual path
|
||||
below. Every outcome is now visible in devtools: failures via
|
||||
`console.warn`, and a successful "no update" result logs the running
|
||||
version alongside "that's the latest published release" via `console.info`
|
||||
— the most common false alarm is simply that the installed build already
|
||||
*is* the newest release (e.g. right after installing it), which used to be
|
||||
indistinguishable from a silently broken check.
|
||||
- Closing the window hides it to the tray instead of exiting the process (see
|
||||
`on_window_event` in `main.rs`), so a boot-time-only check can go unnoticed
|
||||
for a long time on a machine that isn't restarted often. `main.ts` also
|
||||
reruns `checkUpdate()` on a `setInterval` (every 6 hours) so a long-lived
|
||||
tray process still notices new releases, and Settings → Updates has a
|
||||
manual "Check for updates" button (`checkUpdate(true)`) that reports
|
||||
"up to date" / "check failed" via toast — those toasts are suppressed on
|
||||
the automatic background checks so the app doesn't nag every few hours.
|
||||
A `SettingsData.autoUpdateEnabled` toggle turns off both the boot check and
|
||||
the periodic recheck (re-read from `settings` on every interval tick, so
|
||||
flipping it while the app is running takes effect immediately); the manual
|
||||
button ignores the toggle and always works.
|
||||
- The update endpoint is `latest.json` published alongside each GitHub
|
||||
Release. `release.yml` signs both architectures with a minisign keypair:
|
||||
the public half is embedded in `tauri.conf.json`; the private half + its
|
||||
|
|
@ -181,16 +255,19 @@ All files below live in `%APPDATA%/app.summarum.calc` (or the user-chosen folder
|
|||
|------|----------|-----|
|
||||
| `settings.json` | app settings | persisted |
|
||||
| `documents.json` | all sheets | persisted (400 ms debounce) |
|
||||
| `rates.json` | live exchange rates | 1 hour |
|
||||
| `market.json` | stock/commodity prices | 15 minutes |
|
||||
| `rates-YYYY-MM-DD.json` | historical ECB rates for one date | permanent |
|
||||
| `rates.json` | live exchange rates (open.er-api.com + CoinGecko) | 1 hour |
|
||||
| `market.json` | stock/commodity prices (Yahoo Finance v8 chart, one request per symbol) | 15 minutes |
|
||||
| `rates-YYYY-MM-DD.json` | historical ECB rates for one date (frankfurter.dev) | permanent |
|
||||
| `backups/documents-YYYY-MM-DD.json` | daily snapshots | 14 days |
|
||||
| `backups/deleted/*.numi` | soft-deleted sheets | configurable |
|
||||
|
||||
## Tests
|
||||
|
||||
`npm test` runs 113 vitest cases over the engine (`src/engine/*.test.ts`):
|
||||
every expression class, both languages, deterministic injected rates,
|
||||
goal seek, historical rates (injected), plus a regression suite covering
|
||||
all known-fixed bugs. UI is exercised manually; the engine is where the
|
||||
complexity lives.
|
||||
`npm test` runs 152 vitest cases: `src/engine/*.test.ts` covers every
|
||||
expression class, both languages, deterministic injected rates, goal seek,
|
||||
historical rates (injected), date-format literals/display (dedicated engines
|
||||
with an explicit `dateFormat`, since the default "system" format depends on
|
||||
the CI machine's locale), plus a regression suite covering all known-fixed
|
||||
bugs; `src/workspace.test.ts` covers cross-sheet references, caching and
|
||||
cycle detection. UI is exercised manually; the engine is where the complexity
|
||||
lives.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ src/
|
|||
index.ts фасад SumEngine + API расширений + итоги листа
|
||||
ui/
|
||||
editor.ts обвязка CodeMirror 6: подсветка, оверлей результатов, копирование
|
||||
search.ts оверлей поиска по всем листам (текст + запросы по значению)
|
||||
export.ts рендерит лист + результаты в PNG (буфер обмена/файл)
|
||||
app.css темы (светлая/тёмная через CSS-переменные)
|
||||
main.ts запуск приложения: документы, настройки, строка статуса, dnd
|
||||
storage.ts команды Tauri / запасной вариант на localStorage
|
||||
|
|
@ -30,10 +32,10 @@ src/
|
|||
updater.ts проверка tauri-plugin-updater, установка + перезапуск
|
||||
src-tauri/
|
||||
src/main.rs трей, скрытие в трей, команды хранилища, загрузка курсов
|
||||
(open.er-api.com + CoinGecko + Yahoo Finance) с кэшем,
|
||||
исторические курсы (frankfurter.app, постоянный кэш
|
||||
на каждую дату), резервные копии, миграция папки данных,
|
||||
открытие файлов, регистрация плагинов (autostart,
|
||||
(open.er-api.com + CoinGecko + Yahoo Finance v8 chart)
|
||||
с кэшем, исторические курсы (frankfurter.dev, постоянный
|
||||
кэш на каждую дату), резервные копии, миграция папки
|
||||
данных, открытие файлов, регистрация плагинов (autostart,
|
||||
global-shortcut, single-instance, opener, dialog,
|
||||
updater, process)
|
||||
```
|
||||
|
|
@ -109,7 +111,53 @@ src-tauri/
|
|||
- `ChartValue` (`kind: "chart"`) возвращается из `evalAgg("chart", …)` и
|
||||
минует `formatValue` — оверлей результатов рисует SVG-спарклайн вместо текста.
|
||||
- Реестр не пересобирается никогда; расширения мутируют его (добавляют фразы/функции)
|
||||
при запуске.
|
||||
при запуске. `buildRegistry()` (`registry.ts`) — тонкий оркестратор над
|
||||
`registerCoreVocab` / `registerUnits` / `registerAreaVolume` /
|
||||
`registerCurrencies` / `registerExtraUnits` / `registerCrypto` /
|
||||
`registerCompletions` — **порядок вызовов важен**: `Registry.addPhrase`
|
||||
оставляет *первую* регистрацию одинаковой фразы и отбрасывает последующие
|
||||
(базовый словарь обязан побеждать фразы единиц/валют, если они случайно
|
||||
совпадают).
|
||||
- Умножение двух величин одного измерения (`2 kg * 500 g`) конвертирует
|
||||
правый операнд в единицу левого перед умножением, чтобы результат имел
|
||||
осмысленную единицу (`1 kg`, а не `1000` в подписи `kg`). Умножение разных
|
||||
измерений (`$12 * 3`, `5 hours * 2`) не затронуто — там правая часть
|
||||
трактуется как простой числовой множитель.
|
||||
- `//` начинает комментарий только в начале строки или после пробела
|
||||
(`findCommentStart` в `index.ts`), поэтому URL вида `see https://example.com`
|
||||
не обрезается на двойном слэше.
|
||||
- **Литералы и форматы дат.** Помимо ISO (`2024-01-01`), лексер понимает
|
||||
`D.M.YYYY` / `M/D/YYYY` (`31.12.2024`, `12/31/2024`) с одинаковым
|
||||
разделителем с обеих сторон и 4-значным годом. Порядок день/месяц
|
||||
разрешается для каждого литерала отдельно: компонент `>12` однозначно
|
||||
определяет, где день; если оба `≤12`, решает настройка движка `dateFormat`.
|
||||
Все три формы литералов отклоняют компоненты вне диапазона через
|
||||
round-trip проверку (`validDateMs` в `lexer.ts` — `new Date(y, mo-1, d)`
|
||||
молча *перекатывает* невалидные даты вроде `2026-13-45` вместо ошибки,
|
||||
поэтому построенные год/месяц/день сравниваются обратно со входом).
|
||||
`SumEngine` разрешает `dateFormat: "system"` в реальный порядок день/месяц
|
||||
один раз через `detectDateOrder()` (`datetime.ts`, пробует
|
||||
`Intl.DateTimeFormat` на фикстуре `2000-12-31`) и передаёт его в
|
||||
`tokenize()` → `lexLine()` при каждом вызове; настройки `"dmy"`/`"mdy"`
|
||||
пропускают детекцию. Отображение работает так же в обратную сторону:
|
||||
`formatter.ts` либо использует форматирование `Intl` по локали ОС
|
||||
(`"system"`), либо строит фиксированную строку `YYYY-MM-DD` / `DD.MM.YYYY` /
|
||||
`MM/DD/YYYY` через `Intl.DateTimeFormat(...).formatToParts()` (это нужно,
|
||||
чтобы уважать собственный IANA-часовой пояс значения, чего форматтер на
|
||||
основе голого `ms` сделать не может). Экземпляры `Intl.DateTimeFormat`
|
||||
кэшируются по ключу опций в `formatter.ts` (`dtf()`), так как их создание
|
||||
сравнительно дорого, а каждая строка с датой пересобирает формат при
|
||||
каждой отрисовке.
|
||||
- У разбора названий часовых поясов (`time in Europe/Berlin`) есть две
|
||||
независимые ловушки. Во-первых, шумовой фильтр `parseLine` оставляет
|
||||
word-токены только сразу после `conv`-токена (`in`/`as`/...); любой другой
|
||||
тип токена сбрасывает это состояние — голый `/` между двумя словами
|
||||
часового пояса обычно отбросил бы второе слово раньше, чем его увидит
|
||||
парсер, поэтому фильтр отдельно разрешает `/`, встреченный при сборе
|
||||
post-conv слов. Во-вторых, цикл сбора слов в `parseTarget()` зеркалит то же
|
||||
исключение, чтобы продолжать читать после `/`, а не останавливаться на
|
||||
первом слове. Нужны оба места — исправление только одного всё равно
|
||||
урезает `Europe/Berlin` до `Europe`.
|
||||
|
||||
## Ссылки между листами
|
||||
|
||||
|
|
@ -144,10 +192,20 @@ src-tauri/
|
|||
прокрутке/изменении геометрии. Клик для копирования читает `data-value`.
|
||||
- Оценка движка синхронная на каждое нажатие клавиши — полный разбор листа
|
||||
занимает менее миллисекунды, поэтому нет дебаунса и воркеров.
|
||||
- Поиск по всем листам (`Ctrl+F`) — простое построчное сканирование
|
||||
`data.contents` в `main.ts`, а не расширение поиска CodeMirror — ему нужно
|
||||
- Поиск по всем листам (`Ctrl+F`) — простое построчное сканирование текста
|
||||
каждого открытого листа, а не расширение поиска CodeMirror — ему нужно
|
||||
пересекать документы, поэтому он не трогает активный редактор, пока не
|
||||
кликнут результат (`editor.goToLine`).
|
||||
кликнут результат (`editor.goToLine`). Живёт в `src/ui/search.ts`
|
||||
(`initSearch()`), который владеет DOM `#search-overlay` и собственным
|
||||
дебаунсом ввода на 150 мс — запрос по значению (`>1000`) пересчитывает
|
||||
каждый открытый лист, так что дебаунс важен, как только листов становится
|
||||
больше пары.
|
||||
- Поповер настроек разбит на четыре вкладки (Общие / Формат / Данные /
|
||||
Обновления) в `index.html`, каждая — `.settings-tabpanel`, переключаемая
|
||||
кнопкой `#settings-tabs .tab` в `bindSettingsUI()` (`main.ts`) — обычный
|
||||
`classList.toggle`, без роутера. Стиль вкладок повторяет идиому сайдбара
|
||||
`doc-item.active` (`background: var(--sel)` для активной таблетки) вместо
|
||||
нового визуального паттерна.
|
||||
|
||||
## Хранилище
|
||||
|
||||
|
|
@ -163,6 +221,28 @@ src-tauri/
|
|||
- `updater.ts` вызывает `check()` из `tauri-plugin-updater` при запуске; если
|
||||
подписанное обновление доступно, пользователю предлагают установить, затем
|
||||
`downloadAndInstall()` + `relaunch()` из `tauri-plugin-process`.
|
||||
`checkForUpdate()` возвращает `AvailableUpdate | null | "error"` — `null`
|
||||
значит проверка прошла успешно и ничего новее нет, `"error"` значит сама
|
||||
проверка не удалась (офлайн, рассинхрон подписи и т.п.); различие важно
|
||||
только для ручного пути ниже. Теперь виден любой исход: сбои через
|
||||
`console.warn`, а успешный результат «нет обновления» логирует текущую
|
||||
версию рядом с «это последний опубликованный релиз» через `console.info` —
|
||||
самый частый ложный сигнал тревоги это когда установленная сборка И ЕСТЬ
|
||||
последний релиз (например, сразу после его установки), что раньше было
|
||||
неотличимо от молча сломанной проверки.
|
||||
- Закрытие окна прячет его в трей вместо завершения процесса (см.
|
||||
`on_window_event` в `main.rs`), поэтому проверка только при старте может
|
||||
долго оставаться незамеченной на машине, которую редко перезагружают.
|
||||
`main.ts` также перезапускает `checkUpdate()` через `setInterval` (каждые
|
||||
6 часов), чтобы долгоживущий трей-процесс всё равно замечал новые релизы,
|
||||
а в настройках (вкладка «Обновления») есть кнопка «Проверить обновления»
|
||||
(`checkUpdate(true)`), которая сообщает «последняя версия» / «не удалось
|
||||
проверить» через toast — эти уведомления подавлены на автоматических
|
||||
фоновых проверках, чтобы приложение не напоминало о себе каждые несколько
|
||||
часов. Тумблер `SettingsData.autoUpdateEnabled` отключает и проверку при
|
||||
запуске, и периодическую (значение перечитывается из `settings` на каждом
|
||||
тике интервала, так что переключение прямо во время работы срабатывает
|
||||
сразу); ручная кнопка тумблер игнорирует и работает всегда.
|
||||
- Эндпоинт обновлений — `latest.json`, публикуемый вместе с каждым GitHub
|
||||
Release. `release.yml` подписывает обе архитектуры парой ключей minisign:
|
||||
публичная половина зашита в `tauri.conf.json`; приватная половина + пароль
|
||||
|
|
@ -179,16 +259,19 @@ src-tauri/
|
|||
|------|-----------|-----|
|
||||
| `settings.json` | настройки | постоянно |
|
||||
| `documents.json` | все листы | постоянно (дебаунс 400 мс) |
|
||||
| `rates.json` | живые курсы валют | 1 час |
|
||||
| `market.json` | котировки акций/сырья | 15 минут |
|
||||
| `rates-YYYY-MM-DD.json` | исторические курсы ЕЦБ за одну дату | навсегда |
|
||||
| `rates.json` | живые курсы валют (open.er-api.com + CoinGecko) | 1 час |
|
||||
| `market.json` | котировки акций/сырья (Yahoo Finance v8 chart, по запросу на символ) | 15 минут |
|
||||
| `rates-YYYY-MM-DD.json` | исторические курсы ЕЦБ за одну дату (frankfurter.dev) | навсегда |
|
||||
| `backups/documents-YYYY-MM-DD.json` | ежедневные снимки | 14 дней |
|
||||
| `backups/deleted/*.numi` | мягко удалённые листы | настраивается |
|
||||
|
||||
## Тесты
|
||||
|
||||
`npm test` запускает 113 vitest-кейсов по движку (`src/engine/*.test.ts`):
|
||||
все классы выражений, оба языка, детерминированные инжектированные курсы,
|
||||
поиск неизвестного, исторические курсы (инжектированные), плюс набор регрессий
|
||||
по всем известным исправленным багам. UI проверяется вручную; движок — там
|
||||
где живёт вся сложность.
|
||||
`npm test` запускает 152 vitest-кейса: `src/engine/*.test.ts` покрывает все
|
||||
классы выражений, оба языка, детерминированные инжектированные курсы, поиск
|
||||
неизвестного, исторические курсы (инжектированные), литералы и отображение
|
||||
форматов дат (отдельные движки с явным `dateFormat` — формат «system» по
|
||||
умолчанию зависит от локали машины CI), плюс набор регрессий по всем известным
|
||||
исправленным багам; `src/workspace.test.ts` покрывает межлистовые ссылки,
|
||||
кэширование и обнаружение циклов. UI проверяется вручную; движок — там где
|
||||
живёт вся сложность.
|
||||
|
|
|
|||
21
README.md
21
README.md
|
|
@ -67,13 +67,16 @@ the math is ignored, so `spent 20 on pizza and 5 on coffee` just gives 25.
|
|||
`100 AAPL in USD`, `2 GC=F in USD` (gold futures), `MSFT + GOOGL`.
|
||||
|
||||
**Historical exchange rates** — `1000 USD in EUR on 2024-01-01` looks up the
|
||||
ECB rate for that exact date (via frankfurter.app) and caches it permanently.
|
||||
ECB rate for that exact date (via frankfurter.dev) and caches it permanently.
|
||||
|
||||
**Percents the human way** — `20% of 80`, `20% off 1500`, `5 as a % of 25`,
|
||||
`20% of what is 30`.
|
||||
|
||||
**Dates and time** — `today + 2 weeks`, `tomorrow - today`, `time in New York`,
|
||||
`now in unix`, `1750000000 as date`. ISO date literals: `2024-01-01 + 30 days`,
|
||||
**Dates and time** — `today + 2 weeks`, `tomorrow - today`, `time in New York`
|
||||
(also works with a raw IANA id: `time in Europe/Berlin`), `now in unix`,
|
||||
`1750000000 as date`. Date literals: ISO (`2024-01-01 + 30 days`), or
|
||||
dot/slash with a 4-digit year — `31.12.2024`, `12/31/2024` — where the
|
||||
day/month order for ambiguous cases follows the Date format setting.
|
||||
`days until 2027-01-01`, `days since 2020-03-15`.
|
||||
|
||||
**Goal seek** — put `?` where the unknown is and an `=` with the target:
|
||||
|
|
@ -117,9 +120,13 @@ auto-derived one) so the reference doesn't break when its first line changes.
|
|||
- **Data folder is configurable** — point it at OneDrive/Dropbox and your
|
||||
sheets follow you between machines.
|
||||
- **Always on top** (Settings) pins the window above everything else.
|
||||
- The app checks for updates on launch and offers to install them — no
|
||||
manual download-and-reinstall.
|
||||
- Light/dark theme, en/ru interface, configurable precision and separators.
|
||||
- The app checks for updates on launch and every few hours after that, and
|
||||
offers to install them — no manual download-and-reinstall. Settings →
|
||||
Updates → "Check for updates" runs one on demand, or turn automatic
|
||||
checking off entirely with the toggle next to it.
|
||||
- Light/dark theme, en/ru interface, configurable precision, separators, and
|
||||
date format (system locale, `31.12.2024`, `12/31/2024`, or `2024-12-31`).
|
||||
Settings is split into General / Format / Data / Updates tabs.
|
||||
|
||||
## Extensions
|
||||
|
||||
|
|
@ -149,7 +156,7 @@ see [ARCHITECTURE.md](ARCHITECTURE.md).
|
|||
|
||||
```bash
|
||||
npm install
|
||||
npm test # engine tests (113 cases)
|
||||
npm test # engine tests (152 cases)
|
||||
npm run tauri dev # run the app
|
||||
npm run tauri build # build the installer
|
||||
```
|
||||
|
|
|
|||
21
README.ru.md
21
README.ru.md
|
|
@ -63,13 +63,16 @@ AAPL в USD → $213.55
|
|||
`100 AAPL в USD`, `2 GC=F в USD` (фьючерс на золото), `MSFT + GOOGL`.
|
||||
|
||||
**Исторические курсы** — `1000 USD в EUR на 2024-01-01` запрашивает курс ЕЦБ
|
||||
за этот день (через frankfurter.app) и кэширует его навсегда.
|
||||
за этот день (через frankfurter.dev) и кэширует его навсегда.
|
||||
|
||||
**Проценты по-человечески** — `20% от 80`, `скидка 20% от 1500`, `5 это сколько % от 25`,
|
||||
`20% от какого числа равно 30`.
|
||||
|
||||
**Даты и время** — `сегодня + 2 недели`, `завтра - сегодня`, `время в Нью-Йорке`,
|
||||
`сейчас в unix`, `1750000000 как дата`. ISO-даты: `2024-01-01 + 30 дней`,
|
||||
**Даты и время** — `сегодня + 2 недели`, `завтра - сегодня`, `время в Нью-Йорке`
|
||||
(работает и с прямым IANA-id: `время в Europe/Berlin`), `сейчас в unix`,
|
||||
`1750000000 как дата`. Форматы дат: ISO (`2024-01-01 + 30 дней`), или через
|
||||
точку/слэш с 4-значным годом — `31.12.2024`, `12/31/2024` — порядок день/месяц
|
||||
для неоднозначных случаев берётся из настройки «Формат даты».
|
||||
`дней до 2027-01-01`, `дней с 2020-03-15`.
|
||||
|
||||
**Поиск неизвестного** — поставь `?` на место неизвестного и `=` с нужным результатом:
|
||||
|
|
@ -112,9 +115,13 @@ AAPL в USD → $213.55
|
|||
- **Папка данных настраивается** — укажи OneDrive/Dropbox и листы синхронизируются
|
||||
между машинами.
|
||||
- **Поверх всех окон** (в настройках) закрепляет окно над остальными.
|
||||
- Приложение само проверяет обновления при запуске и предлагает установить —
|
||||
вручную скачивать и переустанавливать не нужно.
|
||||
- Светлая/тёмная тема, интерфейс en/ru, настраиваемая точность и разделители.
|
||||
- Приложение проверяет обновления при запуске и потом каждые несколько
|
||||
часов, предлагая установить — вручную скачивать и переустанавливать не
|
||||
нужно. Настройки → Обновления → «Проверить обновления» запускает проверку
|
||||
вручную, либо отключи автопроверку целиком тумблером рядом.
|
||||
- Светлая/тёмная тема, интерфейс en/ru, настраиваемая точность, разделители
|
||||
и формат даты (системный, `31.12.2024`, `12/31/2024` или `2024-12-31`).
|
||||
Настройки разбиты на вкладки: Общие / Формат / Данные / Обновления.
|
||||
|
||||
## Расширения
|
||||
|
||||
|
|
@ -144,7 +151,7 @@ numi.addFunction({ id: "гипот", phrases: "гипот" },
|
|||
|
||||
```bash
|
||||
npm install
|
||||
npm test # тесты движка (113 кейсов)
|
||||
npm test # тесты движка (152 кейса)
|
||||
npm run tauri dev # запустить приложение
|
||||
npm run tauri build # собрать установщик
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue