Commit graph

111 commits

Author SHA1 Message Date
Horizontal42
439eec2795 add tokenizer tests 2026-07-29 14:14:48 +03:00
Horizontal42
6292bbf103 add formatter tests for formatNumber/formatValue 2026-07-29 14:14:48 +03:00
Horizontal42
adea6870fd add parser tests for parseLine 2026-07-29 14:14:48 +03:00
Horizontal42
e70c69df52 add lexer tests for lexLine 2026-07-29 14:14:48 +03:00
Horizontal42
6a88767edf extract evalAgg into an AGG_FUNCS lookup table 2026-07-29 14:14:48 +03:00
Horizontal42
f5b1fc038b sync Cargo.lock with 1.2.2 2026-07-16 03:41:10 +03:00
Horizontal42
ca19042af0 update tauri.conf version to 1.2.2 2026-07-16 02:52:03 +03:00
Horizontal42
539fb6eff5 bump cargo version to 1.2.2 2026-07-16 02:49:05 +03:00
Horizontal42
c7c87257bd bump version to 1.2.2 2026-07-16 02:44:47 +03:00
Horizontal42
68d7492dba fix modal backdrop-click guard, re-entrancy, and monotonic clock
- Track window focus via performance.now() instead of suppressing all backdrop
  clicks for 300ms after the modal appears. This fixes over-suppression (Ctrl+W
  close-sheet now dismisses on immediate click) while keeping protection for
  genuine stray refocus-clicks (native dialog closing, tray window refocus).
- Serialize concurrent askModal calls via a promise chain so a second call
  queues instead of clobbering the first's handlers (was: hang on concurrent
  modal show).
- Use monotonic performance.now() throughout to avoid clock-jump edge cases
  with Date.now().
2026-07-16 02:42:14 +03:00
Horizontal42
087ee44f12 ignore backdrop click within 300ms of showing a modal
a click that focuses the window lands on the modal backdrop too, so
the update-available prompt after an unattended auto-check could get
dismissed by the very click that brought the window to front, before
it was ever seen
2026-07-16 01:14:52 +03:00
Horizontal42
6f6c3b439a bump version to 1.2.1 2026-07-08 15:58:14 +03:00
Horizontal42
c1a7254fdf fix arbitrary file write via write_text_file/write_image_file
Both Tauri commands took a raw path string straight from the frontend
and wrote to it with no validation — any code able to call invoke()
(a compromised or malicious extension script, which run with full page
privileges) could write to any path the OS user has access to, not just
one chosen through the save dialog. Move the save-file dialog into the
Rust command itself so the only path ever used is one the native dialog
returned; the frontend now only sends the content to save.
2026-07-08 15:50:13 +03:00
Horizontal42
f2deaa1b94 split lexLine's number/date matching into named helpers
lexLine had grown into one long function trying dates, hex/bin/oct,
grouped numbers, decimals and xrefs inline. Extracted each into a
tryLex* helper with the same fallback order, so the driving loop
reads as a priority list instead of nested ifs.
2026-07-08 15:48:40 +03:00
Horizontal42
f634b1858e fetch market data symbols concurrently instead of sequentially
fetch_market_data hit Yahoo's v8 chart endpoint once per symbol in a
sequential loop — with 9 symbols that's 9 round trips end to end.
join_all fires them together.
2026-07-08 15:47:31 +03:00
Horizontal42
a674c66d63 split boot() into initSettings/initDataAndEngine/initUI
Boot had grown into one long function covering settings load, engine
and workspace setup, and all the UI wiring. Split along those natural
phase boundaries; boot() itself is now just the three awaits in order.
2026-07-08 15:46:22 +03:00
Horizontal42
216a18821c split bindSettingsUI into one function per settings tab
The settings panel grew into tabs (General/Format/Data/Updates) but
the binding code was still one 170-line function. Split it to match:
bindFormattingSettings, bindHotkeySettings, bindBehaviorSettings,
bindDataDirSettings, bindSettingsNavigation.
2026-07-08 15:45:32 +03:00
Horizontal42
eab3dbae2f add tests for storage.ts
Covers isTauri, and loadSettings/saveSettings across both the Tauri
invoke path and the localStorage fallback, including corrupt-data
and thrown-error recovery.
2026-07-08 15:44:38 +03:00
Horizontal42
afe2693acf add tests for updater.ts
Covers the three checkForUpdate outcomes (null/error/available) and
the install() flow (downloadAndInstall then relaunch).
2026-07-08 15:43:52 +03:00
Horizontal42
34fa14ffc6 add tests for extensions.ts
Covers makeApi's delegation to the engine and runExtensions' per-script
error isolation (one bad extension shouldn't stop the rest from loading).
2026-07-08 15:43:03 +03:00
Horizontal42
984275711a gitignore AI-agent scratch files and the local cargo config dir
.jules/ and test_perf.ts kept showing up in Jules/Bolt-generated PRs
(agent notes and a throwaway benchmark script) — ignore them so they
can't land on main by accident. Also picked up the pending
src-tauri/.cargo/ ignore that was sitting uncommitted.
2026-07-08 14:19:12 +03:00
Horizontal42
47beea4652 avoid rebuilding decoration fields on every editor transaction
highlightField and varHighlightField rebuilt from scratch on every
CodeMirror transaction, including ones that touch neither the doc nor
the selection (e.g. the setResults effect firing after evaluation).
varHighlightField now only rebuilds when the doc or selection actually
changed; highlightField remaps existing decorations through doc edits
and only does a full rebuild when new results arrive.
2026-07-08 14:15:04 +03:00
Horizontal42
00bd1248e1 use a Set for the cross-sheet resolving stack
resolving was an array checked with .includes() on every xref lookup
during resolution — an O(n) membership test where only membership
ever mattered, not order. Set makes it O(1).
2026-07-08 14:12:38 +03:00
Horizontal42
80bb94674d cache referencedTitles per sheet in invalidate()'s fixed-point loop
invalidate() re-scans every non-dirty sheet's text with a regex on
every pass of its while loop, even though the text hasn't changed
between passes — cache the parsed refs per sheet id, keyed by text
identity, so repeated passes reuse the same result.
2026-07-08 14:10:36 +03:00
Horizontal42
e774546818 batch doc-list DOM inserts through a DocumentFragment
renderDocList appended each sheet's element straight to the live list,
forcing a reflow per item; build them in a fragment and append once.
2026-07-08 14:09:26 +03:00
Horizontal42
fc3153a7eb add tests for i18n
Covers setLang/t language switching and fallback behavior, and
detectLang's navigator.language parsing — previously untested.
2026-07-08 14:06:47 +03:00
Horizontal42
40b5b94102 fetch historical rates for multiple dates concurrently
fetchNeededHistoricalRates awaited each date's fetch sequentially even
though they're independent requests — a sheet referencing several
historical dates paid for that latency serially instead of in parallel.
2026-07-08 14:06:00 +03:00
Horizontal42
697f1f0e3d add aria-labels to icon-only buttons and inputs
Toolbar buttons (☰ 🔍 ⚙ ✕ ─ ⤓ +) and the untitled doc-title/search
inputs had no accessible name beyond a title tooltip, which screen
readers don't reliably announce.
2026-07-08 14:03:32 +03:00
Horizontal42
d71ad92422 add tests for datetime.ts
Covers detectDateOrder, resolveZone, addToDate and isCalendarUnit —
none of it had direct unit coverage before, only indirectly through
the engine's date-arithmetic tests.
2026-07-08 14:01:34 +03:00
Horizontal42
a0cda64b95 drop unused export on ExtensionApi
Not imported anywhere outside extensions.ts.
2026-07-08 14:00:47 +03:00
Horizontal42
b68869701e drop unused export on AvailableUpdate
Not imported anywhere outside updater.ts.
2026-07-08 14:00:10 +03:00
Horizontal42
1b61ec35cc release: publish the release instead of leaving it a draft
releaseDraft: true (needed so build assets land on the same release
object across the two matrix jobs) was never followed by a step that
un-drafted it — the notes job set the changelog but left the release
sitting invisible on GitHub, which also means the auto-updater's
"latest release" lookup never found it (GitHub excludes drafts).
Confirmed against the v1.2.0 run: build + notes both succeeded, but
the release stayed in draft with no publish date. Publish it in the
same step that writes the notes.
2026-07-03 13:47:14 +03:00
Horizontal42
b179f64fb4 bump version to 1.2.0 2026-07-03 13:26:32 +03:00
Horizontal42
156ce447af fix: a boot-time hotkey registration failure was completely silent
The manual rebind flow in Settings already toasts hotkeyFailed when a
combo is taken by another app, but the initial registerHotkey() call
at boot discarded its success/failure result — if the saved hotkey
couldn't be registered (already bound elsewhere, etc.), there was no
error and no indication the shortcut just wasn't active.
2026-07-03 13:15:04 +03:00
Horizontal42
bb6b08f153 fix: results-width slider still showed the native OS control
appearance:none on the <input> alone didn't strip the native chrome
in every Chromium build — some keep ::-webkit-slider-runnable-track
styled natively unless that pseudo-element gets its own
appearance:none. Made the track's styling explicit there instead of
relying on it inheriting from the input.
2026-07-03 13:12:50 +03:00
Horizontal42
ad8e1960d7 fix: sheet drag-and-drop showed a blocked cursor and never dropped
Tauri's window-level file-drop hook (needed for dragging a .numi file
in from Explorer) intercepts WebView2's own drag-and-drop session, so
native HTML5 draggable="true" elements never get a valid drop target
inside the webview — the OS always shows "not allowed", confirmed
with an actual mouse-drag simulation before this fix.

Replaced with plain pointer tracking (mousedown/mousemove/mouseup),
the same pattern already used for the results-column divider — full
control over the cursor and the drag-over indicator, no dependency on
the webview's native DnD stack, and it doesn't touch the unrelated
OS file-drop feature at all.
2026-07-03 13:12:44 +03:00
Horizontal42
7b94f6a5b0 sheets: reorder by dragging instead of up/down buttons
Move-up/move-down felt disconnected from the result — clicking a
button and having two rows silently swap position doesn't read as
motion. Dropped both buttons for native HTML5 drag-and-drop: drag a
sheet, an accent line shows where it'll land, drop to reorder. Still
respects the pinned/unpinned grouping — dragging across that boundary
is a no-op, same as the old up/down buttons.
2026-07-03 12:57:14 +03:00
Horizontal42
5205c9fb63 design: market status matches rates styling, shows a loading pulse
#market-info had no CSS at all — different font size and color from
#rates-info right next to it, and its own hardcoded English "Market"
text ignored the language setting. Merged its styling into the same
rule as #rates-info and gave it the same .spin pulse while a fetch
is in flight, so a market refresh is as visible as a rates refresh.
2026-07-03 12:55:32 +03:00
Horizontal42
88a60c4e73 design: style the results-width slider to match the theme
It was still the bare OS-native slider — flat gray track everywhere
else in Settings is themed. Now a thin themed track with an accent
thumb that grows slightly while dragging.
2026-07-03 12:53:47 +03:00
Horizontal42
708206d245 fix: overlays only animated opening, closing snapped instantly
Settings, export menu, modal, and search all drove their entrance
with a @keyframes animation gated on :not(.hidden), triggered by a
display:none toggle — animations don't run when a property changes
away from display:none, only when the element (re)enters the render
tree. Closing just flipped straight to display:none with nothing to
watch. Replaced with opacity/transform transitions plus a delayed
visibility flip (visibility switches instantly on open, only after
the fade completes on close) — same visual effect, correct both ways.
2026-07-03 12:53:08 +03:00
Horizontal42
fd97484c4a design: pulsing indicator while rates refresh
The status bar just dimmed the "Rates" text to 0.5 opacity and left
it static for the whole fetch — easy to miss as feedback that
anything is happening. A slow pulse reads as "working" instead of
just "disabled".
2026-07-03 12:35:22 +03:00
Horizontal42
4302f7025c design: keyboard focus ring on form fields
Doc title, search input, and settings inputs/selects either zeroed
out the default outline or never set one — Tab-only navigation had
no visible indicator of what's focused. :focus-visible keeps mouse
clicks silent and only rings for keyboard focus.
2026-07-03 12:34:27 +03:00
Horizontal42
ec8b2f6448 design: thin scrollbars matching the theme
Default Windows scrollbars were chunky and out of place next to the
minimal chrome elsewhere. WebView2 is Chromium under the hood, so a
plain ::-webkit-scrollbar override works everywhere without JS.
2026-07-03 12:33:51 +03:00
Horizontal42
92dd4e3125 extend subtle animations from settings to the rest of the app
Sidebar collapse now transitions width instead of snapping via
display:none. Export menu, the modal, and the search overlay all get
a short pop-in (scale + fade) matching the settings panel's motion.
Buttons, doc-list items, and status-bar text ease their hover/color
changes instead of snapping.

Left alone on purpose: the results overlay (re-rendered on every
keystroke — animating it would feel laggy, not smooth), the
autocomplete tooltip (CodeMirror's own timing), and the column-resize
drag handle (wants instant feedback while dragging).
2026-07-03 12:25:53 +03:00
Horizontal42
702670e08d settings: subtle open and tab-switch animations
Settings panel pops in (scale + fade, 0.15s) instead of appearing
instantly; switching tabs fades the new panel in (0.12s); the tab
buttons themselves transition their background/color on hover and
selection instead of snapping. CSS-only — @keyframes animations run
even from display:none, so no JS timing/exit-animation complexity.
2026-07-03 12:14:26 +03:00
Horizontal42
e5ef1164cc settings: show the running app version
Updates tab, below the check-update button. Reads from
tauri.conf.json via getVersion() in a real build (the source of
truth for what's actually installed); falls back to package.json's
version under vite dev, where there's no Tauri runtime to ask.
2026-07-03 12:13:01 +03:00
Horizontal42
6f190c7245 docs: tabbed settings, auto-update toggle and diagnostics 2026-07-03 12:03:58 +03:00
Horizontal42
1e06281f5e settings: tabbed panel, date format, update controls
The flat settings list had grown to 14 fields plus footer buttons —
split it into General / Format / Data / Updates tabs (same pill style
as the pinned-doc sidebar, active tab gets the selection color).

Wired the date-format setting into the engine, and reworked update
checks per an actual bug report: a boot-only check is easy to miss on
a tray app that can run for weeks without a cold restart, and every
failure was swallowed silently with no logging at all. Now: failures
are logged (console.warn) and successful "no update" checks say why
(console.info, current version vs. latest published release) so the
next report has something to go on; checks also rerun every 6 hours;
Settings has both a manual "Check for updates" button (reports back
via toast) and a toggle to turn automatic checking off entirely —
manual checks keep working either way.
2026-07-03 12:00:39 +03:00
Horizontal42
5af7c66c3c refactor: pull cross-sheet search and PNG export out of main.ts
Both were self-contained enough to live in their own modules —
src/ui/search.ts (Ctrl+F overlay, text and result-value queries) and
src/ui/export.ts (render the sheet + results to a PNG, clipboard or
file). Trims main.ts by about 150 lines.

Also fixes a real bug while it's in there: the PNG export's clipboard
fallback did btoa(String.fromCharCode(...bytes)), which blows the
call stack on large sheets (spreading a big array as call arguments).
Chunks the conversion in 0x8000-byte pieces instead.
2026-07-03 11:57:33 +03:00
Horizontal42
8b4e6fc0fe market data: switch to Yahoo v8 chart, drop delisted tickers
v7/finance/quote returns 401 now — Yahoo stopped issuing anonymous
clients the cookie+crumb pair it requires. v8 chart still answers
without auth, one symbol per request instead of one bulk call.
Also dropped SBER/GAZP from the tracked symbols; Yahoo delisted
Russian tickers in 2022, so those two always missed anyway.
2026-07-03 11:51:40 +03:00