Commit Graph
804 Commits
Author SHA1 Message Date
Pranav 1cd45cc909 Merge branch 'main' into pr/macros-ui 2026-10-25 23:54:09 -04:00
Dylan M. Taylor 42bbf6baa2 refactor: split complex functions for Sonar S3776 (#2612)
Extract group_bracket from outline layout and move_marks from data
outline_settings. No behaviour change.
2026-10-25 21:39:17 -04:00
Dylan M. Taylor 52cf825395 refactor: split complex functions for Sonar S3776 (#2609)
Extract helpers from auto_sum (selection and single-cell guess), update_cursor
(drag, hover, freeze, and header-edge cursors) and read_styles (dxf reader).
No behaviour change.
2026-10-25 21:37:09 -04:00
3b65b9af58 Sonar maintainability: explicit imports, method paths, smaller functions (#2412)
* Sonar: explicit imports, fn paths for closures, extract helpers for cognitive complexity

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Fix imports

* Fix serde imports

* Fix engine imports

* Fix clippy gate and a type error from the explicit-import change

Remove the unused imports the review flagged (cf.rs, pivot.rs, tables.rs, cmd/sheet.rs) so clippy -D warnings passes, and point the backstage parent-folder lookup at Path::to_str, which the fn-path rewrite had broken.

Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>

* Apply rustfmt to the explicit-import cleanup so the fmt gate passes

* Share the per-directory font path expansion in theme.rs

The candidate font lists repeated the same loop over the Linux font
directories in every role. A single helper expands them in the same order,
so the lookup order is unchanged and the duplicated blocks go away.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>

* Read the Windows fonts folder in one place in theme.rs

Every script-font list repeated the same WINDIR lookup. A single helper
returns the same path, so the duplicated lines go away without changing
which font files are tried.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>

* Build the light and dark UI palettes from one table in theme.rs

Tokens::light() and Tokens::dark() listed the same 40 colour fields in the
same order, so every change had to be made twice. A single palette
constructor takes the mode and picks each colour's value, so each field is
written once and both modes keep their exact colours.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>

* Share the Noto CJK font list between the CJK script lists in theme.rs

The Japanese, Simplified and Korean lists each repeated the same loop over
the Linux CJK directories with a different locale file name. One helper
builds that loop, and each list keeps its own face index, so the lookup
order is unchanged.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>

* Share Linux Noto directory list across Thai and Hebrew font lookups

Removes the duplicated directory arrays flagged by SonarCloud new-code duplication.

* Reuse in_dirs for Thai and Hebrew Noto font candidates

Removes the remaining duplicated per-directory loops flagged by SonarCloud.

* Share one font-list builder for the Thai and Hebrew faces

The two per-script functions repeated the same Windows, macOS, Linux and
variable-font setup. A single script_fonts helper now builds both lists,
keeping the candidate order unchanged, which clears the Sonar duplication gate.

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Codebuff <noreply@codebuff.com>
2026-10-25 12:32:02 -04:00
Brandon Thomas 4a44dec500 Merge pull request #2515 from Fubeo:fub/edit-note-keeps-thread 2026-10-25 09:36:36 -04:00
Brandon Thomas 97ec7653aa Merge pull request #2512 from Fubeo:fub/open-saved-without-recalc 2026-10-25 09:36:34 -04:00
Brandon Thomas 27e96a9a58 Merge pull request #2561 from arcanii:autosum-across-a-row 2026-10-25 09:36:34 -04:00
Brandon Thomas 2c81adf94f Merge pull request #2557 from Silvertree2010:fix/2517-a-new-font-derived-from-the-them 2026-10-25 09:36:33 -04:00
Bryan MarkandClaude Opus 5.5 802c782360 AutoSum: a selected row gets its total to the right, as in Excel
AutoSum over any selection of more than one cell wrote a total under each column, so a row
selected (B2:E2) got four one-cell totals in row 3 instead of =SUM(B2:E2) in F2.

As Excel does, totals now go in the selection's empty last row or column, and otherwise past
it:
- a single row: its total in its last cell when that is empty, else just to its right;
- a block whose last column is empty: each row's total there; whose last row is empty: each
  column's; both: both, and the block's total in the corner;
- otherwise totals under each column, as before.

A whole-row selection still stops at the data's last column, and all totals are one undo step.
Tests: autosum_over_a_row_totals_it_to_the_right,
autosum_over_a_whole_row_totals_it_after_the_data and
autosum_fills_a_selections_empty_last_column_and_row; each fails without the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 20:34:30 +08:00
NetterSilverandClaude Opus 5.5 85b6f564f9 Keep <scheme val="minor"/> on every body-font font on save
Fixes #2517

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 13:20:17 +01:00
prxnvt 8fd6ec124b Engine: a macro shortcut is one letter, alone or after Ctrl+Shift
`parse_shortcut` took the last letter of any spelling, so `"Ctrl+Alt+M"` was saved as
Ctrl+Shift+M. It now takes a bare letter or one after Ctrl+Shift, Cmd+Shift or ⌘⇧, and refuses
other modifiers.

- Test: `shortcuts_parse_to_one_letter_and_avoid_reserved_and_taken_ones`.
2026-10-25 06:48:57 -04:00
prxnvt ffacf7405f Docs: Record Macro's shortcut example uses a letter a macro may take
The example was K, which GridCraft uses (Group), so a caller who copied it was refused; it is M
now, in the command's description and on the macro page.
2026-10-25 06:48:57 -04:00
prxnvt c442da9221 Engine: stopping a recording says when it left the chosen shortcut off
Record Macro checks the Ctrl+Shift letter when recording starts, but
another macro can take it before recording stops; the save then leaves
the shortcut off, and nothing said so. The result now names it in
shortcutDropped, the toast after Stop says the macro was saved without
it, and the macro page describes it.
2026-10-25 06:48:57 -04:00
prxnvt 7c66aaa2d3 Engine: Record Macro takes a name, shortcut and description
macro.record checks name, shortcut and description when recording
starts and keeps them for the save, so a name that is taken, too long
or holds line breaks, or a Ctrl+Shift letter that GridCraft or another
macro uses, is refused before anything is recorded. With empty
parameters it records under the next MacroN, as before. A name or
description given when stopping still wins, and a shortcut that another
macro took while recording is dropped.

The shortcut rules (macros::parse_shortcut, shortcut_text,
shortcut_conflict, RESERVED_SHORTCUTS and check_new_name) live in the
engine, so the Record Macro dialog, Macro Options and agents agree.
2026-10-25 06:48:57 -04:00
prxnvt 299beb5085 Merge #2145 (bf6a8520) into #2152
# Conflicts:
#	crates/engine/src/record.rs
2026-10-25 06:48:57 -04:00
prxnvt bf6a8520b8 Merge the footprint pull request (65634c2b, with #2144 and #2151) into #2145 2026-10-25 06:48:03 -04:00
prxnvt 65634c2bb7 Merge #2144 (b9aa4a6a) into the footprint pull request 2026-10-25 06:48:03 -04:00
prxnvt b9aa4a6a9c Merge #2151 (0533480c) into #2144 2026-10-25 06:48:03 -04:00
prxnvt 0533480c97 Engine: recorded steps carry a number, so a group's rollback finds its own after others
A group remembered the recording's length when it began, which another workbook's rollback (or a
recording started again inside the group) made point at the wrong steps. Each kept step now has
a number, and each recording one of its own; a group remembers both, and its rollback takes back
the steps of its workbook numbered from its mark, or all of them when the recording started
inside the group.

- Test: `rollbacks_take_back_their_own_recorded_steps_in_any_order` (groups on two workbooks
  rolled back in turn, and a recording started again inside a group).
2026-10-25 06:48:03 -04:00
prxnvt ca2be34e8a Engine: a rolled-back group takes what its script runs ran out of the recording too
Taking back the steps a rolled-back group recorded left what a taken-back script or macro run
ran (`Recorder::ran`) under its old index, where a later run could pick it up, and left it out of
the size count. `cut_back` now drops it with its step, moves a kept run's entry to the step's new
index and counts both again.

- Test: `a_rolled_back_script_run_takes_what_it_ran_out_of_the_recording`.
2026-10-25 06:29:23 -04:00
prxnvt 9a22c4b2f2 Merge the footprint pull request (24f0c3df, with #2144 and #2151) into #2145 2026-10-25 06:28:06 -04:00
prxnvt 24f0c3df92 Merge #2144 (1a00ce19) into the footprint pull request 2026-10-25 06:28:06 -04:00
prxnvt 1a00ce19fb Merge #2151 (0a929eaf) into #2144 2026-10-25 06:28:06 -04:00
prxnvt 0a9305f784 Merge #2145 (9a22c4b2) into #2152 2026-10-25 06:28:06 -04:00
prxnvt 0a929eaf68 Engine: a rolled-back group takes back only the steps recorded in its workbook
A rollback took back every step recorded since its group began, so an edit recorded in another
workbook while the group was open was lost from the recording although the edit stayed. It now
takes back only the steps recorded in the group's own workbook.

- Test: the other workbook's edit in `stop_action_runs_only_on_its_own_and_is_not_recorded`.
2026-10-25 06:27:53 -04:00
prxnvt 2e8284c191 Engine: a panic during a dry run puts the journal pause and the touch log back at any depth
The pause of a dry run was cleared only by a panic at the top level. A command that started a
dry run, nested in another, and panicked left journaling paused for the rest of the session (and
the touch log open) once the outer command finished. Each command now keeps the pause count it
started with, and its panic guard puts it back, closing the touch logs, whatever its depth.

- Test: `a_dry_run_cut_short_by_a_panic_leaves_journaling_on`.
2026-10-25 06:26:51 -04:00
prxnvt f0b667aa02 Merge the footprint pull request (361fff98, with #2144 and #2151) into #2145 2026-10-25 06:02:18 -04:00
prxnvt ec58ec5f4b Merge #2145 (f0b667aa) into #2152 2026-10-25 06:02:18 -04:00
prxnvt 361fff982a Engine: a dry run journals nothing, so the journal and Record Actions' start stay as they were
Every nested command a dry run executed was journaled. Past the journal's limits that evicted
the person's own oldest entries, and the restore could only cut back what was left, so one
`macro.check` of a long macro could empty the journal and move a Record Actions recording's
start. While `Saved` holds the session's state, commands are not journaled
(`Session::journal_paused`), and the restore puts the journal and the recording start back
exactly; the count of dropped entries it relied on is gone. The fallible calls of a dry run come
before the pause, and a panic at the top level turns journaling back on.

- Test: `a_dry_run_leaves_the_journal_and_the_recording_start_as_they_were` (a full journal, a
  Record Actions start, and a check of 12,000 nested steps; replaces the test that accepted the
  loss).
2026-10-25 06:02:18 -04:00
prxnvt fe14c0d86e Merge #2151 (0aae1ffe) into #2144 2026-10-25 06:00:19 -04:00
prxnvt 891986bf66 Merge #2144 (fe14c0d8) into the footprint pull request 2026-10-25 06:00:19 -04:00
prxnvt 0aae1ffeea Engine: an xlsx save refuses a macro that skipped the check, as a JSON save does
`with_names` checked only the size of each item, so a macro built in code past the model's
limits (a name with a line break) was written to the file and dropped the next time it opened.
The xlsx save now runs `check` on every macro first.

- Test: the macro past the limits in `a_macro_built_in_code_goes_through_the_size_check_too`.
2026-10-25 06:00:13 -04:00
prxnvt bfcf50aad3 Engine: rolling back an undo group takes back the steps it recorded
Stop Action (or any rollback) put a group's edits back but left the steps recorded while the
group was open in the recording, so playing the recording back made edits the workbook no longer
had. A group remembers where the recording was when it began, and its rollback cuts the
recording back to there.

- Test: `stop_action_runs_only_on_its_own_and_is_not_recorded`, which kept the rolled-back edit
  before.
2026-10-25 06:00:13 -04:00
NetterSilverandClaude Haiku 5.5 80e2041159 Remove sparklines when their row or column is deleted
Found while testing crates/engine/src/cmd/sheet.rs:~199 (shift_sheet_features, sparklines loop)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 09:39:12 +00:00
prxnvt f225cd1026 Engine: Check Macro waits while a macro is being debugged
`macro.check` is a query, so the debugger's lock let it through, but its dry run runs the macro's
steps, which the lock refuses: the check reported the lock as the failure of a step. It now waits
with "Finish or stop the macro first", as other commands that change the workbook do.

- Test: `between_steps_only_looking_around_runs_and_the_run_continues_where_it_was`.
2026-10-25 05:16:37 -04:00
prxnvt 9fe25835d4 Merge #2145 (f225cd10) into #2152 2026-10-25 05:16:37 -04:00
prxnvt f6f4585c93 Engine: a dry run puts the document's revision back
The dry run's rollback counts as a change of the document and moves its revision on, which front
ends key their caches on, so a `macro.check` that changed nothing (and a shift it tried and took
back) looked like a change. `Saved` keeps the revision and puts it back with the rest.

- Test: the revision in `shifting_makes_room_and_is_checked_again`.
2026-10-25 04:14:12 -04:00
prxnvt 91bb5eded8 Merge the footprint pull request (f6f4585c, with #2144) into #2145 2026-10-25 04:14:12 -04:00
prxnvt 5aa10132aa Merge #2145 (91bb5ede, with the footprint pull request and #2144) into #2152 2026-10-25 04:14:12 -04:00
prxnvt 90a46da496 Engine: macro.run's description lists only what it returns here
`shifted` comes with the footprint in the pull request above; here a shift rule stops the run as
`fail` does, which the description now says.
2026-10-25 04:13:05 -04:00
prxnvt fdd26876fb Merge #2145 (227078f5, with the footprint pull request and #2144) into #2152
# Conflicts:
#	docs/macros.md
2026-10-25 03:21:39 -04:00
prxnvt 227078f56a Merge the footprint pull request (dadffdb3, with #2144) into #2145
# Conflicts:
#	docs/macros.md
2026-10-25 03:21:31 -04:00
prxnvt dadffdb33f Engine: macro.check shifts and checks again, as the run does, before it says ok
Under `shiftDown` or `shiftRight` with conflicts, `macro.check` looked only for the refusals made
before a shift, so it said `ok` for a run the second footprint stops (an anchor that moves with
the shifted data). It now shifts the data inside an undo group, takes the footprint again, rolls
both back and puts the session back, and reports what still stands in the way.

- Test: the `Named` case and a shift that makes room in `shifting_makes_room_and_is_checked_again`.
2026-10-25 03:21:15 -04:00
prxnvt e9effe5dbd Merge #2144 (198fe482) into the footprint pull request
# Conflicts:
#	docs/macros.md
2026-10-25 03:20:11 -04:00
prxnvt 198fe482a1 Engine: the run's check of each step finds the inputs on their sheet after a sheet moved
- The check for writes over data compared the inputs' sheet with the sheet's index when the run
  started, while the inputs follow their cells: after a step inserted a sheet in front, a step
  writing into its own input on the moved sheet was stopped as writing over data. It now uses the
  sheet's index now. Test: the input case in
  `inputs_and_the_output_follow_their_cells_when_a_step_moves_them`.
- Docs: the page says `macro.run` checks the macro, its inputs and parameters and the policy
  before it runs, and that a formula whose references a move rewrites counts as changed too.
2026-10-25 03:19:59 -04:00
prxnvt 460a72d3bb Engine: a recording keeps what ran and plays back as it was recorded
- A script step that only opened a dialog (a menu-style call that left the workbook unchanged)
  is left out of the script run's steps, by the recorder hook's rule; a script run whose steps
  the recording couldn't keep is left out and listed in `skipped`, not read from the workbook
  as the script stands when recording stops (a_script_step_that_only_opened_a_dialog_is_left_out_of_the_recording).
- A script's toggles keep the state they set (`"on": true`), as a top-level Bold does
  (a_script_run_records_the_state_its_toggles_set).
- A recorded macro.run keeps the steps it ran, as a script run does, so saving or deleting the
  macro before the recording stops changes nothing (a_recorded_macro_run_keeps_the_steps_that_ran).
- The example input's shape and column types are inferred when recording starts, not from the
  data the recording changed (the_recorded_input_is_inferred_as_it_was_when_recording_started).
- Recorded text that playback would read as a token stays text: a reference parameter that reads
  as one becomes {"$text": …}, and `{{` becomes `{{{{`
  (recorded_text_that_reads_as_a_token_plays_back_as_text). The resolver (below #2152) gains
  `{"$text": …}`, text passed as it is (text_marked_as_text_is_never_a_token).
- A recording over the macro size limit is cut at the limit and the steps left out are counted
  in `dropped` (a_recording_past_the_macro_size_is_cut_and_counted).
- A macro recorded in a workbook opened from a file is approved for the session, since the
  person recorded it (a_macro_recorded_in_a_workbook_from_a_file_runs_without_asking).
- macro.record refuses parameters it would ignore: name or description when starting, relative
  when stopping, stop: false while recording, others beside cancel: true, and wrong kinds
  (record_refuses_parameters_it_would_ignore).
- Undo while recording keeps the undone step, as in Excel; documented
  (undo_while_recording_keeps_the_undone_step_as_excel_does).
- Docs: macros.md Recording and Tokens; module comments no longer say scripts are read.
2026-10-25 03:19:35 -04:00
prxnvt 8b018d89cd Model: the undo budget counts each macro's whole JSON, parameters included
- Macros::estimated_bytes counted names, descriptions and step counts but not step parameters,
  so each save, rename or delete (which copies the macro list an undo step shares) could keep a
  4 M-character parameter per step unseen by the 128 MB budget. It now counts each macro's JSON
  as it is written, without building it.
- This changes the macro storage layer below #2152, the smallest change that makes the budget
  hold (answers CodeRabbit thread 4366060037).
- Test: re_saving_a_large_macro_keeps_the_undo_history_within_its_budget.
2026-10-25 03:19:03 -04:00
prxnvt bc0d3035fb Engine: kept macros count toward the macro limit and keep their names; saving over a macro needs replace: true; renaming checks the size
- macro.save and a stopped recording count the macros kept in a newer format with the readable
  ones, so a save never holds more than the next open keeps; the message names the kept count
  (kept_macros_count_toward_the_limit_and_keep_their_names, which saves and reopens as xlsx and
  JSON).
- The names of kept macros are taken: macro.save and macro.rename refuse them, unique_name and
  next_name step around them (same test).
- macro.save no longer replaces by default: `replace` defaults to false, the error names the
  existing macro and says to pass `replace: true`, and a `replace` that is not a boolean is
  refused (saving_over_a_macro_needs_replace).
- macro.rename runs the whole macro check, size included, so a longer name can't take a macro
  past what an xlsx file holds (renaming_checks_the_macro_size).
- Docs: macros.md, the management commands and kept macros.
2026-10-25 03:18:26 -04:00
prxnvt 7dad14e77e Engine: the macro debugger locks its workbook to looking around, ends with Stop Action and checks its parameters
- `macro.debug` refuses to run as a step of a script or macro, and is never recorded (the
  recording buffer and Record Actions' journal both leave it out); a debugger whose undo group
  something else closed locks nothing, and its next action says the run had ended instead of
  reporting a keep or a rollback. Tests: `the_debugger_runs_on_its_own_and_is_never_recorded`,
  `a_debugger_whose_group_was_closed_elsewhere_locks_nothing`.
- Stop Action is no longer refused under the debugger: it rolls the run back, ends the debugger
  and names the macro. Test: `stop_action_stops_a_macro_being_debugged` (was
  `stop_action_waits_while_a_macro_is_being_debugged`).
- Between steps the debugged workbook runs only `macro.debug`, queries and an allow-list of
  commands that look around (selection, sheet changes, Go To, Find, Copy, zoom, Switch Window,
  Close, Stop Action); everything else is refused, saving and view modes included. The run's
  active sheet and selection are put back before its next step. Test:
  `between_steps_only_looking_around_runs_and_the_run_continues_where_it_was`.
- Only the top-level `macro.debug` call rolls the run back after a panic; a panic nested in a step
  is that step's error. Test: `a_panic_inside_a_step_is_that_steps_error_and_the_run_goes_on`.
- `keep` and `breakpointsOnly` must be true or false, breakpoints must be step paths of the macro
  (at most 10,000), and the action is checked before breakpoints change. Test:
  `debugger_parameters_are_checked_before_anything_changes`.
- Answers: `executed` counts the run's own steps (not the footprint's), `shifted` and `wroteFiles`
  are reported, a rollback's message names the files that stay, `changed` lists deleted and
  renamed sheets and at most 500 entries with `changedCells`, and a Repeat over whole rows or
  columns shows the part in use. Tests: `the_debuggers_answers_count_its_own_steps_and_say_what_stays`,
  `the_debugger_lists_deleted_and_renamed_sheets_and_caps_the_list`,
  `the_debugger_shows_a_repeat_over_whole_columns_as_the_run_goes_over_them`.
2026-10-25 03:17:42 -04:00
prxnvt d309c85c31 Engine: Stop Action waits while a macro is being debugged
`automate.stopAction` rolls back the undo groups open on the workbook. While a macro was being
debugged it rolled back the debugger's own group and left the debugger set, pointing at a group
that was gone. It is now refused under the debugger's lock with "Finish or stop the macro first",
as edits are; `macro.debug {action: "stop"}` ends the run.

- Test: `stop_action_waits_while_a_macro_is_being_debugged`.
2026-10-25 02:52:43 -04:00