mirror of
https://github.com/Horizontal42/summarum.git
synced 2026-11-11 01:02:01 +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
|
index.ts SumEngine facade + extension API + sheet totals
|
||||||
ui/
|
ui/
|
||||||
editor.ts CodeMirror 6 wiring: highlight, results overlay, copy
|
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)
|
app.css themes (light/dark via CSS variables)
|
||||||
main.ts app bootstrap: documents, settings, status bar, dnd
|
main.ts app bootstrap: documents, settings, status bar, dnd
|
||||||
storage.ts Tauri commands / localStorage fallback
|
storage.ts Tauri commands / localStorage fallback
|
||||||
|
|
@ -29,8 +31,8 @@ src/
|
||||||
updater.ts checks tauri-plugin-updater, installs + relaunches
|
updater.ts checks tauri-plugin-updater, installs + relaunches
|
||||||
src-tauri/
|
src-tauri/
|
||||||
src/main.rs tray, hide-to-tray, storage commands, rates fetching
|
src/main.rs tray, hide-to-tray, storage commands, rates fetching
|
||||||
(open.er-api.com + CoinGecko + Yahoo Finance) with
|
(open.er-api.com + CoinGecko + Yahoo Finance v8 chart)
|
||||||
cache, historical rates (frankfurter.app, permanent
|
with cache, historical rates (frankfurter.dev, permanent
|
||||||
per-date cache), backups, data-folder migration,
|
per-date cache), backups, data-folder migration,
|
||||||
file drops, plugin registration (autostart,
|
file drops, plugin registration (autostart,
|
||||||
global-shortcut, single-instance, opener, dialog,
|
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
|
bypasses `formatValue` — the results overlay renders an SVG sparkline
|
||||||
instead of a text string.
|
instead of a text string.
|
||||||
- The registry is rebuilt never; extensions mutate it (add phrases/functions)
|
- 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
|
## Cross-sheet references
|
||||||
|
|
||||||
|
|
@ -147,10 +191,19 @@ recursing forever.
|
||||||
scroll/geometry changes. Click-to-copy reads `data-value`.
|
scroll/geometry changes. Click-to-copy reads `data-value`.
|
||||||
- Engine evaluation is synchronous on every keystroke — a full sheet parse
|
- Engine evaluation is synchronous on every keystroke — a full sheet parse
|
||||||
is well under a millisecond, so there's no debounce or worker.
|
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
|
- Search-all-sheets (`Ctrl+F`) is a plain line scan over every open sheet's
|
||||||
`main.ts`, not a CodeMirror search extension — it has to cross documents,
|
text, not a CodeMirror search extension — it has to cross documents, so it
|
||||||
so it never touches the active editor until a result is clicked
|
never touches the active editor until a result is clicked
|
||||||
(`editor.goToLine`).
|
(`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
|
## Storage
|
||||||
|
|
||||||
|
|
@ -165,7 +218,28 @@ recursing forever.
|
||||||
|
|
||||||
- `updater.ts` calls `tauri-plugin-updater`'s `check()` on boot; if a signed
|
- `updater.ts` calls `tauri-plugin-updater`'s `check()` on boot; if a signed
|
||||||
update is available the user is prompted, then `downloadAndInstall()` +
|
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
|
- The update endpoint is `latest.json` published alongside each GitHub
|
||||||
Release. `release.yml` signs both architectures with a minisign keypair:
|
Release. `release.yml` signs both architectures with a minisign keypair:
|
||||||
the public half is embedded in `tauri.conf.json`; the private half + its
|
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 |
|
| `settings.json` | app settings | persisted |
|
||||||
| `documents.json` | all sheets | persisted (400 ms debounce) |
|
| `documents.json` | all sheets | persisted (400 ms debounce) |
|
||||||
| `rates.json` | live exchange rates | 1 hour |
|
| `rates.json` | live exchange rates (open.er-api.com + CoinGecko) | 1 hour |
|
||||||
| `market.json` | stock/commodity prices | 15 minutes |
|
| `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 | permanent |
|
| `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/documents-YYYY-MM-DD.json` | daily snapshots | 14 days |
|
||||||
| `backups/deleted/*.numi` | soft-deleted sheets | configurable |
|
| `backups/deleted/*.numi` | soft-deleted sheets | configurable |
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
`npm test` runs 113 vitest cases over the engine (`src/engine/*.test.ts`):
|
`npm test` runs 152 vitest cases: `src/engine/*.test.ts` covers every
|
||||||
every expression class, both languages, deterministic injected rates,
|
expression class, both languages, deterministic injected rates, goal seek,
|
||||||
goal seek, historical rates (injected), plus a regression suite covering
|
historical rates (injected), date-format literals/display (dedicated engines
|
||||||
all known-fixed bugs. UI is exercised manually; the engine is where the
|
with an explicit `dateFormat`, since the default "system" format depends on
|
||||||
complexity lives.
|
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 расширений + итоги листа
|
index.ts фасад SumEngine + API расширений + итоги листа
|
||||||
ui/
|
ui/
|
||||||
editor.ts обвязка CodeMirror 6: подсветка, оверлей результатов, копирование
|
editor.ts обвязка CodeMirror 6: подсветка, оверлей результатов, копирование
|
||||||
|
search.ts оверлей поиска по всем листам (текст + запросы по значению)
|
||||||
|
export.ts рендерит лист + результаты в PNG (буфер обмена/файл)
|
||||||
app.css темы (светлая/тёмная через CSS-переменные)
|
app.css темы (светлая/тёмная через CSS-переменные)
|
||||||
main.ts запуск приложения: документы, настройки, строка статуса, dnd
|
main.ts запуск приложения: документы, настройки, строка статуса, dnd
|
||||||
storage.ts команды Tauri / запасной вариант на localStorage
|
storage.ts команды Tauri / запасной вариант на localStorage
|
||||||
|
|
@ -30,10 +32,10 @@ src/
|
||||||
updater.ts проверка tauri-plugin-updater, установка + перезапуск
|
updater.ts проверка tauri-plugin-updater, установка + перезапуск
|
||||||
src-tauri/
|
src-tauri/
|
||||||
src/main.rs трей, скрытие в трей, команды хранилища, загрузка курсов
|
src/main.rs трей, скрытие в трей, команды хранилища, загрузка курсов
|
||||||
(open.er-api.com + CoinGecko + Yahoo Finance) с кэшем,
|
(open.er-api.com + CoinGecko + Yahoo Finance v8 chart)
|
||||||
исторические курсы (frankfurter.app, постоянный кэш
|
с кэшем, исторические курсы (frankfurter.dev, постоянный
|
||||||
на каждую дату), резервные копии, миграция папки данных,
|
кэш на каждую дату), резервные копии, миграция папки
|
||||||
открытие файлов, регистрация плагинов (autostart,
|
данных, открытие файлов, регистрация плагинов (autostart,
|
||||||
global-shortcut, single-instance, opener, dialog,
|
global-shortcut, single-instance, opener, dialog,
|
||||||
updater, process)
|
updater, process)
|
||||||
```
|
```
|
||||||
|
|
@ -109,7 +111,53 @@ src-tauri/
|
||||||
- `ChartValue` (`kind: "chart"`) возвращается из `evalAgg("chart", …)` и
|
- `ChartValue` (`kind: "chart"`) возвращается из `evalAgg("chart", …)` и
|
||||||
минует `formatValue` — оверлей результатов рисует SVG-спарклайн вместо текста.
|
минует `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`.
|
прокрутке/изменении геометрии. Клик для копирования читает `data-value`.
|
||||||
- Оценка движка синхронная на каждое нажатие клавиши — полный разбор листа
|
- Оценка движка синхронная на каждое нажатие клавиши — полный разбор листа
|
||||||
занимает менее миллисекунды, поэтому нет дебаунса и воркеров.
|
занимает менее миллисекунды, поэтому нет дебаунса и воркеров.
|
||||||
- Поиск по всем листам (`Ctrl+F`) — простое построчное сканирование
|
- Поиск по всем листам (`Ctrl+F`) — простое построчное сканирование текста
|
||||||
`data.contents` в `main.ts`, а не расширение поиска CodeMirror — ему нужно
|
каждого открытого листа, а не расширение поиска 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` при запуске; если
|
- `updater.ts` вызывает `check()` из `tauri-plugin-updater` при запуске; если
|
||||||
подписанное обновление доступно, пользователю предлагают установить, затем
|
подписанное обновление доступно, пользователю предлагают установить, затем
|
||||||
`downloadAndInstall()` + `relaunch()` из `tauri-plugin-process`.
|
`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
|
- Эндпоинт обновлений — `latest.json`, публикуемый вместе с каждым GitHub
|
||||||
Release. `release.yml` подписывает обе архитектуры парой ключей minisign:
|
Release. `release.yml` подписывает обе архитектуры парой ключей minisign:
|
||||||
публичная половина зашита в `tauri.conf.json`; приватная половина + пароль
|
публичная половина зашита в `tauri.conf.json`; приватная половина + пароль
|
||||||
|
|
@ -179,16 +259,19 @@ src-tauri/
|
||||||
|------|-----------|-----|
|
|------|-----------|-----|
|
||||||
| `settings.json` | настройки | постоянно |
|
| `settings.json` | настройки | постоянно |
|
||||||
| `documents.json` | все листы | постоянно (дебаунс 400 мс) |
|
| `documents.json` | все листы | постоянно (дебаунс 400 мс) |
|
||||||
| `rates.json` | живые курсы валют | 1 час |
|
| `rates.json` | живые курсы валют (open.er-api.com + CoinGecko) | 1 час |
|
||||||
| `market.json` | котировки акций/сырья | 15 минут |
|
| `market.json` | котировки акций/сырья (Yahoo Finance v8 chart, по запросу на символ) | 15 минут |
|
||||||
| `rates-YYYY-MM-DD.json` | исторические курсы ЕЦБ за одну дату | навсегда |
|
| `rates-YYYY-MM-DD.json` | исторические курсы ЕЦБ за одну дату (frankfurter.dev) | навсегда |
|
||||||
| `backups/documents-YYYY-MM-DD.json` | ежедневные снимки | 14 дней |
|
| `backups/documents-YYYY-MM-DD.json` | ежедневные снимки | 14 дней |
|
||||||
| `backups/deleted/*.numi` | мягко удалённые листы | настраивается |
|
| `backups/deleted/*.numi` | мягко удалённые листы | настраивается |
|
||||||
|
|
||||||
## Тесты
|
## Тесты
|
||||||
|
|
||||||
`npm test` запускает 113 vitest-кейсов по движку (`src/engine/*.test.ts`):
|
`npm test` запускает 152 vitest-кейса: `src/engine/*.test.ts` покрывает все
|
||||||
все классы выражений, оба языка, детерминированные инжектированные курсы,
|
классы выражений, оба языка, детерминированные инжектированные курсы, поиск
|
||||||
поиск неизвестного, исторические курсы (инжектированные), плюс набор регрессий
|
неизвестного, исторические курсы (инжектированные), литералы и отображение
|
||||||
по всем известным исправленным багам. UI проверяется вручную; движок — там
|
форматов дат (отдельные движки с явным `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`.
|
`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
|
**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`,
|
**Percents the human way** — `20% of 80`, `20% off 1500`, `5 as a % of 25`,
|
||||||
`20% of what is 30`.
|
`20% of what is 30`.
|
||||||
|
|
||||||
**Dates and time** — `today + 2 weeks`, `tomorrow - today`, `time in New York`,
|
**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`,
|
(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`.
|
`days until 2027-01-01`, `days since 2020-03-15`.
|
||||||
|
|
||||||
**Goal seek** — put `?` where the unknown is and an `=` with the target:
|
**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
|
- **Data folder is configurable** — point it at OneDrive/Dropbox and your
|
||||||
sheets follow you between machines.
|
sheets follow you between machines.
|
||||||
- **Always on top** (Settings) pins the window above everything else.
|
- **Always on top** (Settings) pins the window above everything else.
|
||||||
- The app checks for updates on launch and offers to install them — no
|
- The app checks for updates on launch and every few hours after that, and
|
||||||
manual download-and-reinstall.
|
offers to install them — no manual download-and-reinstall. Settings →
|
||||||
- Light/dark theme, en/ru interface, configurable precision and separators.
|
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
|
## Extensions
|
||||||
|
|
||||||
|
|
@ -149,7 +156,7 @@ see [ARCHITECTURE.md](ARCHITECTURE.md).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
npm test # engine tests (113 cases)
|
npm test # engine tests (152 cases)
|
||||||
npm run tauri dev # run the app
|
npm run tauri dev # run the app
|
||||||
npm run tauri build # build the installer
|
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`.
|
`100 AAPL в USD`, `2 GC=F в USD` (фьючерс на золото), `MSFT + GOOGL`.
|
||||||
|
|
||||||
**Исторические курсы** — `1000 USD в EUR на 2024-01-01` запрашивает курс ЕЦБ
|
**Исторические курсы** — `1000 USD в EUR на 2024-01-01` запрашивает курс ЕЦБ
|
||||||
за этот день (через frankfurter.app) и кэширует его навсегда.
|
за этот день (через frankfurter.dev) и кэширует его навсегда.
|
||||||
|
|
||||||
**Проценты по-человечески** — `20% от 80`, `скидка 20% от 1500`, `5 это сколько % от 25`,
|
**Проценты по-человечески** — `20% от 80`, `скидка 20% от 1500`, `5 это сколько % от 25`,
|
||||||
`20% от какого числа равно 30`.
|
`20% от какого числа равно 30`.
|
||||||
|
|
||||||
**Даты и время** — `сегодня + 2 недели`, `завтра - сегодня`, `время в Нью-Йорке`,
|
**Даты и время** — `сегодня + 2 недели`, `завтра - сегодня`, `время в Нью-Йорке`
|
||||||
`сейчас в unix`, `1750000000 как дата`. ISO-даты: `2024-01-01 + 30 дней`,
|
(работает и с прямым IANA-id: `время в Europe/Berlin`), `сейчас в unix`,
|
||||||
|
`1750000000 как дата`. Форматы дат: ISO (`2024-01-01 + 30 дней`), или через
|
||||||
|
точку/слэш с 4-значным годом — `31.12.2024`, `12/31/2024` — порядок день/месяц
|
||||||
|
для неоднозначных случаев берётся из настройки «Формат даты».
|
||||||
`дней до 2027-01-01`, `дней с 2020-03-15`.
|
`дней до 2027-01-01`, `дней с 2020-03-15`.
|
||||||
|
|
||||||
**Поиск неизвестного** — поставь `?` на место неизвестного и `=` с нужным результатом:
|
**Поиск неизвестного** — поставь `?` на место неизвестного и `=` с нужным результатом:
|
||||||
|
|
@ -112,9 +115,13 @@ AAPL в USD → $213.55
|
||||||
- **Папка данных настраивается** — укажи OneDrive/Dropbox и листы синхронизируются
|
- **Папка данных настраивается** — укажи OneDrive/Dropbox и листы синхронизируются
|
||||||
между машинами.
|
между машинами.
|
||||||
- **Поверх всех окон** (в настройках) закрепляет окно над остальными.
|
- **Поверх всех окон** (в настройках) закрепляет окно над остальными.
|
||||||
- Приложение само проверяет обновления при запуске и предлагает установить —
|
- Приложение проверяет обновления при запуске и потом каждые несколько
|
||||||
вручную скачивать и переустанавливать не нужно.
|
часов, предлагая установить — вручную скачивать и переустанавливать не
|
||||||
- Светлая/тёмная тема, интерфейс en/ru, настраиваемая точность и разделители.
|
нужно. Настройки → Обновления → «Проверить обновления» запускает проверку
|
||||||
|
вручную, либо отключи автопроверку целиком тумблером рядом.
|
||||||
|
- Светлая/тёмная тема, интерфейс en/ru, настраиваемая точность, разделители
|
||||||
|
и формат даты (системный, `31.12.2024`, `12/31/2024` или `2024-12-31`).
|
||||||
|
Настройки разбиты на вкладки: Общие / Формат / Данные / Обновления.
|
||||||
|
|
||||||
## Расширения
|
## Расширения
|
||||||
|
|
||||||
|
|
@ -144,7 +151,7 @@ numi.addFunction({ id: "гипот", phrases: "гипот" },
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
npm test # тесты движка (113 кейсов)
|
npm test # тесты движка (152 кейса)
|
||||||
npm run tauri dev # запустить приложение
|
npm run tauri dev # запустить приложение
|
||||||
npm run tauri build # собрать установщик
|
npm run tauri build # собрать установщик
|
||||||
```
|
```
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue