docs: update README and ARCHITECTURE for v2.0 features

This commit is contained in:
Horizontal42 2026-07-01 22:32:03 +03:00
parent fbeca06f2e
commit b509a65fc5
4 changed files with 146 additions and 36 deletions

View file

@ -29,10 +29,12 @@ 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) with cache, backups,
data-folder migration, file drops, plugin registration
(autostart, global-shortcut, single-instance, opener,
dialog, updater, process)
(open.er-api.com + CoinGecko + Yahoo Finance) with
cache, historical rates (frankfurter.app, permanent
per-date cache), backups, data-folder migration,
file drops, plugin registration (autostart,
global-shortcut, single-instance, opener, dialog,
updater, process)
```
## How a line becomes a result
@ -89,9 +91,25 @@ snapshot price) plus the mirror list in `src-tauri/src/main.rs`.
because an "attosecond" symbol would shadow the word `as`.
- `and`/`or` are dual-purpose: `and` adds (`5 and 3` = 8), `or` is bitwise.
`of/off/on` only act on a percent left-hand side; otherwise they're prose.
The same `on` token doubles as the historical-rate date separator (`in EUR on
2024-01-01`) — `parseSeq` checks for `pctop(on)` + `datelit` right after a
currency conv target and consumes both when present.
- Currency rates are "units per USD"; a currency Unit's ratio is computed at
tokenization time from the current rate map, so re-setting rates re-prices
everything on the next evaluation.
evaluation time from the current rate map, so re-setting rates re-prices
everything on the next evaluation. Historical rates live in a separate
`historicalRates: Map<date, Map<code, number>>` on `SumEngine` and are
passed through `EvalCtx` to `evalConv`.
- Live market data (stocks, commodities via Yahoo Finance) is merged into the
same rate map as exchange rates via `applyAllRates()` in `main.ts` to avoid
one `setRates()` call overwriting the other.
- Goal seek (`? * 1.2 = 1000`) tokenizes `?` as `{ t: "unknown" }`, parsed
into `{ k: "unknown" }` nodes. `parseLine` detects the presence of `unknown`
+ `assign` and builds a `{ k: "goalseek", lhs, rhs }` node. The solver
tries a linear probe (f(0), f(1) → exact root for affine expressions) and
falls back to bisection for nonlinear ones.
- `ChartValue` (`kind: "chart"`) is returned by `evalAgg("chart", …)` and
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.
@ -131,9 +149,24 @@ snapshot price) plus the mirror list in `src-tauri/src/main.rs`.
(write-only — there is no durable copy in the repo). Losing that key means
already-installed copies can never trust a future signed release again.
## Storage (cache files)
All files below live in `%APPDATA%/app.summarum.calc` (or the user-chosen folder):
| File | Contents | TTL |
|------|----------|-----|
| `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 |
| `backups/documents-YYYY-MM-DD.json` | daily snapshots | 14 days |
| `backups/deleted/*.numi` | soft-deleted sheets | configurable |
## Tests
`npm test` runs ~96 vitest cases over the engine (`src/engine/*.test.ts`):
`npm test` runs 113 vitest cases over the engine (`src/engine/*.test.ts`):
every expression class, both languages, deterministic injected rates,
plus a regression suite covering all known-fixed bugs.
UI is exercised manually; the engine is where the complexity lives.
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.

View file

@ -30,10 +30,12 @@ src/
updater.ts проверка tauri-plugin-updater, установка + перезапуск
src-tauri/
src/main.rs трей, скрытие в трей, команды хранилища, загрузка курсов
(open.er-api.com + CoinGecko) с кэшем, резервные копии,
миграция папки данных, открытие файлов, регистрация
плагинов (autostart, global-shortcut, single-instance,
opener, dialog, updater, process)
(open.er-api.com + CoinGecko + Yahoo Finance) с кэшем,
исторические курсы (frankfurter.app, постоянный кэш
на каждую дату), резервные копии, миграция папки данных,
открытие файлов, регистрация плагинов (autostart,
global-shortcut, single-instance, opener, dialog,
updater, process)
```
## Как строка превращается в результат
@ -89,9 +91,23 @@ src-tauri/
только ms/µs/ns/ps, потому что символ «аттосекунды» затенил бы слово `as`.
- `и`/`или` двойного назначения: `и` складывает (`5 и 3` = 8), `или` — побитовое.
`от/без/на` работают только с процентом в левой части; иначе это просто текст.
Тот же токен `на` используется как разделитель даты для исторических курсов
(`в EUR на 2024-01-01`) — `parseSeq` проверяет `pctop(on)` + `datelit`
сразу после цели конвертации валюты и поглощает оба при совпадении.
- Курсы валют — «единиц за 1 USD»; соотношение единицы валюты вычисляется при
токенизации из текущей карты курсов, поэтому обновление курсов пересчитывает
всё при следующей оценке.
вычислении из текущей карты курсов, поэтому обновление курсов пересчитывает
всё при следующей оценке. Исторические курсы живут отдельно в
`historicalRates: Map<date, Map<code, number>>` на `SumEngine` и передаются
через `EvalCtx` в `evalConv`.
- Живые рыночные данные (акции, сырьё через Yahoo Finance) объединяются в ту же
карту курсов через `applyAllRates()` в `main.ts`, чтобы один вызов `setRates()`
не перезаписывал другой.
- Поиск неизвестного (`? * 1.2 = 1000`): `?` токенизируется как `{ t: "unknown" }`,
разбирается в узел `{ k: "unknown" }`. `parseLine` обнаруживает `unknown` + `assign`
и строит `{ k: "goalseek", lhs, rhs }`. Решатель пробует линейный метод (f(0),
f(1) → точный корень для аффинных выражений) и переходит к бисекции для нелинейных.
- `ChartValue` (`kind: "chart"`) возвращается из `evalAgg("chart", …)` и
минует `formatValue` — оверлей результатов рисует SVG-спарклайн вместо текста.
- Реестр не пересобирается никогда; расширения мутируют его (добавляют фразы/функции)
при запуске.
@ -132,9 +148,24 @@ src-tauri/
означает, что уже установленные копии никогда больше не смогут доверять
будущему подписанному релизу.
## Хранилище (кэш-файлы)
Все файлы ниже живут в `%APPDATA%/app.summarum.calc` (или в папке данных пользователя):
| Файл | Содержимое | TTL |
|------|-----------|-----|
| `settings.json` | настройки | постоянно |
| `documents.json` | все листы | постоянно (дебаунс 400 мс) |
| `rates.json` | живые курсы валют | 1 час |
| `market.json` | котировки акций/сырья | 15 минут |
| `rates-YYYY-MM-DD.json` | исторические курсы ЕЦБ за одну дату | навсегда |
| `backups/documents-YYYY-MM-DD.json` | ежедневные снимки | 14 дней |
| `backups/deleted/*.numi` | мягко удалённые листы | настраивается |
## Тесты
`npm test` запускает ~96 vitest-кейсов по движку (`src/engine/*.test.ts`):
`npm test` запускает 113 vitest-кейсов по движку (`src/engine/*.test.ts`):
все классы выражений, оба языка, детерминированные инжектированные курсы,
плюс набор регрессий по всем известным исправленным багам.
UI проверяется вручную; движок — там где живёт вся сложность.
поиск неизвестного, исторические курсы (инжектированные), плюс набор регрессий
по всем известным исправленным багам. UI проверяется вручную; движок — там
где живёт вся сложность.

View file

@ -17,7 +17,11 @@ total → $1,066.97
$2000 in rubles → 144,060 ₽
20% off 1500 → 1,200
today + 6 weeks → 22 July 2026
time in Tokyo → 14:25
1000 USD in EUR on 2024-01-01 → € 917.30
? * 1.2 = 1000 → 833.33
AAPL in USD → $213.55
days until 2027-01-01 → 183 day
chart → ▂▄▇█▆
```
Works in **English and Russian** at the same time: `5 метров в см`, `20$ в евро`,
@ -35,13 +39,14 @@ Requirements:
- **[WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)** —
already part of Windows 11 and most updated Windows 10 systems; if it's
missing, the installer downloads it automatically.
- Internet connection is only needed for live currency rates — everything
- Internet connection is only needed for live rates and market data — everything
else works offline (rates fall back to a cached/bundled snapshot).
## What it understands
**Plain math** — `2 + 2`, `8 / (45 - 20%)`, parentheses, `^`, `sqrt`, `sin`,
factorials. Decimal-exact: `0.1 + 0.2` is `0.3`, not `0.30000000000000004`.
factorials, `random()`, `random(1; 6)`. Decimal-exact: `0.1 + 0.2` is `0.3`,
not `0.30000000000000004`.
**Words as operators** — `12 plus 4`, `20 divided by 4`, `5 и 3`. Text around
the math is ignored, so `spent 20 on pizza and 5 on coffee` just gives 25.
@ -58,30 +63,48 @@ the math is ignored, so `spent 20 on pizza and 5 on coffee` just gives 25.
(refreshed hourly, cached for offline). `$100 + 20%`, `5 eth in USD`,
`9800 рублей в долларах`.
**Live market data** — stock and commodity prices refreshed every 15 minutes:
`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.
**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`.
`now in unix`, `1750000000 as date`. ISO date literals: `2024-01-01 + 30 days`,
`days until 2027-01-01`, `days since 2020-03-15`.
**Goal seek** — put `?` where the unknown is and an `=` with the target:
`? * 1.2 = 1000` → `833.33`, `100 - ? = 30` → `70`. Works for any expression
solvable by linear probe or bisection.
**Programmer stuff** — `0xFF & 0x0F`, `1 << 4`, `255 in hex`, `in binary`,
plus `in fraction` (0.75 → 3/4) and `in roman` (2026 → MMXXVI).
**Sheets that calculate** — variables (`rent = $500`), running totals
(`sum`, `avg`, `prev`), `#` headers, `//` comments. The status bar always shows
the sheet total; select a few lines to see just their sum.
(`sum`, `avg`, `prev`, `count`, `min`, `max`, `product`), `#` headers,
`//` comments. The status bar always shows the sheet total; select a few lines
to see just their sum. Add `chart` at the end of a block to draw a sparkline.
## Handy things
- **Ctrl+Alt+N** (configurable — the settings field records keys you press)
shows/hides the window from anywhere. Closing hides to tray.
- **Ctrl+F** (or the 🔍 button) searches every sheet at once, not just the
one you're in — click a hit to jump straight to that line.
- **Ctrl+W** closes the current sheet (asks first if it has content).
- **Ctrl+F** (or the 🔍 button) searches every sheet at once — by text or by
result value: `>1000`, `<50`, `=100`, `~100` (±1%). Click a hit to jump there.
- **Click a result** to copy it. Copying a line copies it *with* the answer
(`rent * 3 = $1,500`). **Ctrl+Shift+C** copies the whole sheet with answers.
- **Export as image** — the ⤓ menu includes "Copy as image": renders the sheet
to a PNG and puts it on the clipboard (falls back to file save if the browser
blocks clipboard write).
- **Drag the divider** to resize the results column.
- **Drop a `.numi`, `.sum`, `.txt` or `.md` file** into the window to open it
as a sheet.
- **Pin and reorder sheets** — click the pin icon on any sheet in the sidebar
to keep it at the top; use ↑↓ to rearrange.
- **Backups are automatic**: a daily snapshot of all sheets (last 14 kept), and
deleted sheets sit in a bin for 14 days (configurable) — restore one by
dragging the file back in. Settings → Backups opens the folder.
@ -120,7 +143,7 @@ see [ARCHITECTURE.md](ARCHITECTURE.md).
```bash
npm install
npm test # engine tests
npm test # engine tests (113 cases)
npm run tauri dev # run the app
npm run tauri build # build the installer
```

View file

@ -16,7 +16,11 @@
$2000 в рублях → 144,060 ₽
скидка 20% от 1500 → 1,200
сегодня + 6 недель → 22 Июля 2026
время в Токио → 14:25
1000 USD в EUR на 2024-01-01 → € 917.30
? * 1.2 = 1000 → 833.33
AAPL в USD → $213.55
дней до 2027-01-01 → 183 day
график → ▂▄▇█▆
```
Работает на **английском и русском одновременно**: `5 метров в см`, `20$ в евро`, `сумма` — переключать язык не нужно.
@ -31,13 +35,14 @@ $2000 в рублях → 144,060 ₽
- **[WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)** —
уже встроен в Windows 11 и большинство обновлённых Windows 10; если отсутствует,
установщик скачает его автоматически.
- Интернет нужен только для живых курсов валют — всё остальное работает офлайн
- Интернет нужен только для живых курсов и рыночных данных — всё остальное работает офлайн
(курсы берутся из кэша или встроенного снимка).
## Что понимает
**Обычная математика** — `2 + 2`, `8 / (45 - 20%)`, скобки, `^`, `sqrt`, `sin`,
факториалы. Точная десятичная арифметика: `0.1 + 0.2` это `0.3`, а не `0.30000000000000004`.
факториалы, `случайное()`, `случайное(1; 6)`. Точная десятичная арифметика:
`0.1 + 0.2` это `0.3`, а не `0.30000000000000004`.
**Слова как операторы** — `12 плюс 4`, `20 делить на 4`, `5 и 3`. Текст вокруг
математики игнорируется: `потратил 20 на пиццу и 5 на кофе` даст 25.
@ -54,29 +59,47 @@ $2000 в рублях → 144,060 ₽
(обновляются раз в час, кэшируются для офлайна). `$100 + 20%`, `5 eth в USD`,
`9800 рублей в долларах`.
**Живые рыночные данные** — котировки акций и сырья обновляются каждые 15 минут:
`100 AAPL в USD`, `2 GC=F в USD` (фьючерс на золото), `MSFT + GOOGL`.
**Исторические курсы** — `1000 USD в EUR на 2024-01-01` запрашивает курс ЕЦБ
за этот день (через frankfurter.app) и кэширует его навсегда.
**Проценты по-человечески** — `20% от 80`, `скидка 20% от 1500`, `5 это сколько % от 25`,
`20% от какого числа равно 30`.
**Даты и время** — `сегодня + 2 недели`, `завтра - сегодня`, `время в Нью-Йорке`,
`сейчас в unix`, `1750000000 как дата`.
`сейчас в unix`, `1750000000 как дата`. ISO-даты: `2024-01-01 + 30 дней`,
`дней до 2027-01-01`, `дней с 2020-03-15`.
**Поиск неизвестного** — поставь `?` на место неизвестного и `=` с нужным результатом:
`? * 1.2 = 1000` → `833.33`, `100 - ? = 30` → `70`. Работает для любых выражений,
решаемых линейным методом или бисекцией.
**Для программистов** — `0xFF & 0x0F`, `1 << 4`, `255 в hex`, `в binary`,
плюс `в дроби` (0.75 → 3/4) и `в roman` (2026 → MMXXVI).
**Листы с переменными** — переменные (`аренда = $500`), нарастающие итоги
(`сумма`, `avg`, `prev`), заголовки `#`, комментарии `//`. Строка статуса всегда
показывает итог листа; выдели несколько строк — увидишь их сумму.
(`сумма`, `avg`, `prev`, `количество`, `минимум`, `максимум`, `произведение`),
заголовки `#`, комментарии `//`. Строка статуса всегда показывает итог листа;
выдели несколько строк — увидишь их сумму. Добавь `график` в конце блока чтобы
нарисовать спарклайн.
## Удобные мелочи
- **Ctrl+Alt+N** (настраивается — поле в настройках записывает нажатые клавиши)
показывает/скрывает окно из любого места. Закрытие прячет в трей.
- **Ctrl+F** (или кнопка 🔍) ищет сразу по всем листам, а не только по текущему —
клик по результату переносит прямо на нужную строку.
- **Ctrl+W** закрывает текущий лист (спрашивает подтверждение если есть содержимое).
- **Ctrl+F** (или кнопка 🔍) ищет сразу по всем листам — по тексту или по значению
результата: `>1000`, `<50`, `=100`, `~100` (±1%). Клик по результату — переходишь туда.
- **Клик по результату** копирует его. Копирование строки копирует её *вместе с ответом*
(`аренда * 3 = $1,500`). **Ctrl+Shift+C** копирует весь лист с ответами.
- **Экспорт картинкой** — меню ⤓ содержит «Скопировать картинкой»: рендерит лист
в PNG и кладёт в буфер обмена (при ошибке предлагает сохранить файл).
- **Потяни разделитель** чтобы изменить ширину колонки результатов.
- **Брось файл `.numi`, `.sum`, `.txt` или `.md`** в окно — откроется как лист.
- **Закрепление и порядок листов** — нажми значок булавки на любом листе в сайдбаре
чтобы закрепить его вверху; кнопки ↑↓ меняют порядок.
- **Резервные копии автоматические**: ежедневный снимок всех листов (хранятся 14 дней),
удалённые листы лежат в корзине 14 дней (настраивается) — верни перетащив обратно.
Настройки → Backups откроет папку.
@ -115,7 +138,7 @@ numi.addFunction({ id: "гипот", phrases: "гипот" },
```bash
npm install
npm test # тесты движка
npm test # тесты движка (113 кейсов)
npm run tauri dev # запустить приложение
npm run tauri build # собрать установщик
```