237 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 06a3de6126 |
feat(spreadsheet): dropdown cells and button cells (#9 remainder, complete)
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Finish the widget-in-cell catalog with the two deferred controls, each a plain value plus a render flag (same convention as checkbox/slider/Markdown). - Dropdown: CellStyle.choices (a serialized, pipe-escaped list) makes a cell cycle through its choices on click. The toolbar "Drop" button parses the selected cell's value as a "Low|Med|High" list into choices (undoable SetChoices) and selects the first entry; a dropdown renders its value with a trailing ▾ affordance. dropdown.rs holds next_choice / parse_choice_list. - Button: CellStyle.button makes a cell a button whose value is a TARGET[+N] action spec (A1 ref + optional signed step, default +1). Clicking increments the target cell's numeric value through set_cell (undoable, recalculates dependents) — the spreadsheet-native counterpart of the reference's "+10" boost. button.rs holds parse_button_spec / button_step / format_step_value; the cell renders as a raised box with the spec centred. Toolbar "Btn" toggles the flag. Both flags ride in the render cache so cached cells stay styled, and both commands route through WorkbookCommand (apply + apply_command + dirty marking). Choices serialize after the markdown/slider/button flags; older files default to off/empty. Engine: 491 lib tests (+4) + integration. UI controllers: 142 tests (+8, button 5 + dropdown 4). Coverage: engine 96.59%, ui-controllers 99.45% (floors 96); button.rs and dropdown.rs at 100%. |
|||
| 328690dfbf |
feat(spreadsheet): 3-state header sort (asc/desc/off) with a timed report
Close the last sort gap: the spreadsheet grid's header sort now cycles
ascending → descending → off like the datagrid reference, and reports how
long each sort took.
Engine: SpreadsheetData::sort_rows remembers a RowSort { ascending,
key_col, restore } where restore[view_row] is the row the data originally
lived on, composed across sort chains. New unsort_rows applies that map to
restore the pre-sort order (no-op without a sort, clears undo, recalculates
moved formulas); sort_state() reports the active (column, direction).
Workbook gains unsort_active_sheet and active_sort_state. The sort order is
transient — reset by deserialize, never serialized.
UI: sort_state::next_sort_state replaces the 2-state next_sort_direction
with the asc→desc→off cycle, and the workspace HeaderClicked handler times
the sort with std::time::Instant, drives unsort_active_sheet on "off", and
appends the report ("sorted 1000 rows by B descending in N ms") to the
status bar via a cached sort_status field.
Engine: 487 lib tests (+6) + integration. UI controllers: 134 tests.
Coverage: engine 96.66%, ui-controllers 99.42% (floors 96).
|
|||
| 4d681dfdbe |
feat(spreadsheet): slider cells and inline-Markdown cells (#9 remainder)
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Complete the widget-in-cell gap the same value/style-convention way as the
checkbox: two more cell kinds, both plain data with a render flag.
Engine:
- CellStyle gains `markdown` and `slider` bool flags, serialized as two
trailing columns on the CELL line (older files default them off), plus
WorkbookCommand::{SetMarkdown, SetSlider} routed through apply and
apply_command via mutate_cell — undoable like every style mutation.
UI:
- markdown.rs (measured): a flat inline parser splitting a cell value into
Regular/Bold/Italic/Code runs (`**bold**`, `*italic*`, `` `code` ``);
unclosed markers and empty spans stay literal/dropped. The grid draws
each run with the bold or regular resource (code tinted like formulas).
- slider.rs (measured): 0-100 fraction/value mapping (rounded to whole
steps) and track/fill/handle geometry. A slider cell draws the control
instead of text, and a press/drag sets the value through set_cell — one
undo step per drag (reverse-order ChangeSet application restores the
pre-drag value).
- Toolbar "Md" and "Slider" buttons toggle the flags on the selection;
the render cache carries the two flags so cached cells stay styled.
Dropdown and button cells are intentionally not included: a dropdown
needs a per-cell choices list and a button needs an action semantic that
a spreadsheet does not have — both would require a CellKind in the data
model rather than a render flag.
Engine: 481 lib tests (+2) + integration. UI controllers: 133 tests (+13,
markdown 10 + slider 3). Coverage: engine 96.62%, ui-controllers 99.42%
(floors 96); markdown.rs and slider.rs at 100%.
|
|||
| 8c1a4ad446 |
feat(spreadsheet-ui): Big Data virtual tab — 1B cells, procedural, timed sort
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Close the UI half of the virtualization gap. A new VirtualGrid widget renders one billion virtual cells (1,000,000 rows × 1,000 columns) from the engine's VirtualSheet: only the visible range is drawn, values derive from a hashed (row, col), and a column-header click sorts the full million rows by permuting an index — with the elapsed time reported in a status strip, as the reference does. - virtual_grid.rs (DSL widget, excluded from coverage like grid.rs): zebra-striped cells, row/column headers with the sort glyph, gridlines, drag-to-pan and wheel scroll, and a 3-state header sort (asc → desc → off) delegated to VirtualSheet::sort_rows/reset_sort. All placement and hit-testing reuses the measured GridMetrics; text widths reuse TextMeasureCache. - workspace.rs: a "BigData" toolbar button overlays the virtual grid over the spreadsheet grid via two child Views toggled with View::set_visible, so the existing spreadsheet path is untouched. Engine: 479 lib tests + integration (grid data-provider commit included). UI controllers: 119 tests. Coverage: engine 96.67% (data_source.rs 100%), ui-controllers 99.35% (floors 96). |
|||
| f14823a6da |
feat(spreadsheet): grid data-provider abstraction and a virtual 1B-cell source
Close the engine half of the virtualization gap (report section A). Add a GridDataSource trait — row/col counts, per-cell display text, sort, and a column label — implemented by SpreadsheetData (the real store, delegating to get_display_value/sort_rows) and by a new VirtualSheet. VirtualSheet derives every cell procedurally from a hashed (row, col), so 1,000,000 rows × 1,000 columns (one billion cells) cost one struct and no stored data. Its rendered text and numeric sort key come from the same hash, so sorting by the key sorts what the user sees; sort_rows permutes a Vec<u32> row index (O(n log n) over the index, not the data) with a deterministic tie-break. Column labels mirror the reference (# / Name / City / Balance / Score / Active, then ·N repeats). Engine: 478 lib tests (+12) + integration. Coverage: data_source.rs 100%, engine total 96.67% (floor 96). |
|||
| 7946aa889f |
feat(spreadsheet-ui): OS-clipboard TSV copy and toolbar zoom (#13, #7)
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Close the two remaining small UI gaps from the datagrid analysis: - TSV copy to the OS clipboard (#13): clipboard.rs gains a tested tsv_escape/rows_to_tsv pair (tab/newline/quote escaping, Excel-style), and the grid answers Hit::TextCopy with the selection as TSV while Ctrl+C also writes it straight to the OS clipboard via Cx::copy_to_clipboard. The in-app clipboard still feeds Ctrl+V paste. - Zoom (#7): a new measured zoom module holds the step factor (×1.15), 50-400% clamps and the minimum/maximum cell-size clamp. The grid's apply_zoom/reset_zoom rescale the default cell size deterministically from a captured base (no floating-point drift), and the cell text's font_scale follows the zoom — with text widths scaled at draw time so right/centre alignment stays true. The toolbar gains - / 100% / + buttons. Engine untouched. UI controllers: 119 tests (+7). Coverage: ui-controllers 99.35% (floor 96), clipboard.rs and zoom.rs at 100%. |
|||
| 2adec957e9 |
feat(spreadsheet-ui): TrendChart line + candlesticks wired to the selected row
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Close gap #11 (chart integration) from the datagrid gap analysis, split as usual into testable headless logic plus a thin DSL widget: - chart.rs (measured): candle bucketing (OHLC from a price series), series bounds, line-point mapping with the reference's 8% padding, the vertical strips that trace a polyline with axis-aligned quads, candle body/wick geometry with up/down classification, and the 1/2/5×10^k axis tick step. - market.rs (measured): a deterministic live market — the reference's splitmix-style `mix64`, a HISTORY-capped random walk per symbol, lazy per-row symbol growth (so any selected row charts), tick() with roll-off, and the derived stats (last/change/pct_change/day_range/candles). - trend_chart.rs: a `TrendChart` widget (excluded from coverage like grid.rs) that colours and draws chart.rs output — gridlines, a polyline for a series, or candle bodies + wicks — via `set_series`/`set_candles`. - workspace.rs glue: a 220px chart panel under the grid (line + candlesticks side by side) fed from a 0.25s `Timer` ticker; selecting a grid row switches the charted symbol and updates the title label. Engine untouched. UI controllers: 112 tests (+16 chart/market). Coverage: ui-controllers 99.32% (floor 96), chart.rs 100%, market.rs 99.36%. |
|||
| 1a36ca5538 |
feat(spreadsheet-ui): interactive checkbox cells (widget-in-cell)
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Close gap #9 (cells hosting widgets) the spreadsheet-native way. The datagrid reference hosts a live CheckBox widget per visible cell and recycles instances from a per-template pool; our grid instead draws the control from batched quads, so there is no per-cell widget object to instantiate or recycle — the render cache and reused draw buffers already fill the role the pool does. - A plain value cell holding TRUE/FALSE now renders as a checkbox: a centred square box with a checkmark when TRUE, and its TRUE/FALSE label to the right. A single click toggles the value (through the normal set_cell path, so it is undoable and recalculates dependents), selects the cell, and does not open the editor — a checkbox is a button, not a text surface. Formula cells are never checkboxes, so a click can't clobber a formula. - The predicate (is_checkbox), the value flip (toggled) and the box geometry (checkbox_layout) live in a new measured checkbox module with unit tests; grid.rs stays thin glue (draw the box/tick/label, reposition the label, and the click handler's toggle). UI controllers: 96 tests (+6). Coverage: ui-controllers 99.20% (floor 96), checkbox.rs at 100%. No engine changes. |
|||
| 1a4b479013 |
feat(spreadsheet): SPARKLINE(range) formula rendered as in-cell sparkline bars
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Close gap #10 (sparkline cells) from the datagrid gap analysis, split as usual into testable headless logic plus thin grid glue: - Engine: a `SPARKLINE(range)` function resolves its single range argument to a flat numeric series and returns a new `Value::Sparkline(Vec<f64>)`. The variant is not a scalar — arithmetic on it is a `#VALUE:` error, and the aggregation/lookup helpers skip it — so no code path invents a number from a chart. `SpreadsheetData::apply_formula_result` stores the series on `CellData.sparkline` (derived state, like spills: never serialized, re-derived on recalculation) and clears it when the formula stops returning a sparkline. Dependency tracking is inherited from the range reference, so editing a cell in the source range re-derives the bars. - UI: a new measured `sparkline` module computes the bar rectangles (bars rise from the series minimum, tinted up/down by last-vs-first trend, gap-shrunk for narrow cells) — the same geometry as the reference `Sparkline` widget, but testable headlessly. The grid draws the bars with a dedicated `draw_spark` resource (depth 0.4) when a cell carries a sparkline and skips the text path; the series is cached in `CellRenderState` alongside the display text. The in-cell editor keeps its text on top by suppressing the bars while editing. Engine: 467 lib tests + integration (483 total, +6 sparkline tests). UI controllers: 90 tests (+5). Coverage: engine 96.60%, ui-controllers 99.15% (floors 96); sparkline.rs at 100%. |
|||
| e13aa03dca |
feat(spreadsheet-ui): header sort, column reorder and row/col/all selection
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Wire up the three missing header interactions from the datagrid gap analysis, split as usual into testable headless logic plus thin grid glue: - Selection kinds: SelectionController gains GridSelectKind (Cells/Rows/Cols/All). Header clicks select a whole column / row, the corner (or Ctrl+A) selects the sheet, and bounds() expands the row/column/all kinds to the sheet's full extent so the fill, copy, delete, format and autofill paths handle every kind without branching. - Header-click sort: clicking a column header toggles the sort direction (same column flips asc/desc, a new column starts ascending) and re-sorts the active sheet; the sorted header shows an ▲/▼ glyph. The toggle lives in the new measured sort_state module; the grid holds the (col, asc) state and the workspace applies Workbook::sort_active_sheet. - Column reorder: dragging a header past the tap threshold moves the column, with a drop-indicator line at the insertion index (computed by the new, tested GridMetrics::col_insert_at). Engine addition SpreadsheetData::move_column(from, to) remaps cells and column-width overrides, rebuilds the dependency graph, recalculates, and clears undo history; Workbook::move_active_column propagates to cross-sheet readers via the new WorkbookCommand::MoveColumn. Selection overlay drawing is clipped to the cell area so a full row/column/sheet selection border no longer paints over the headers and the surrounding workspace. Engine: 461 lib tests + integration (477 total). UI controllers: 85 tests. Coverage: engine 96.66% and ui-controllers 99.11% (floors 96). |
|||
| 61d666699b |
feat(spreadsheet): sort rows — the engine half of the datagrid sort
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Tranche 3 of the gap analysis: the datagrid example sorts a million rows on a header click. This is the engine-side sort that makes that possible (the UI header wiring and the procedural data provider are later tranches). `SpreadsheetData::sort_rows(ascending, key_col)`: - Sorts every row by the values in one column. Numbers sort before text (case-insensitively), and empty cells always sort last — in both directions, unlike a naive `reverse` which would float blanks to the top on a descending sort. - Stable: equal keys keep their original order via a row-index tie-break. - Formulas move with their rows and recalculate against the sorted positions, matching Excel's reference-by-position semantics. - Undo history is cleared (a sort is a destructive bulk reorder); row heights, column widths and named ranges stay positional, as in Excel. `Workbook::sort_active_sheet(ascending, key_col)`: - Sorts the active sheet and recalculates cross-sheet dependents so readers on other sheets see the sorted values. Tests: 6 (ascending/descending, numbers-vs-text-vs-empty ordering, stable ties, formula recalculation, non-undoability, workbook-level sort with cross-sheet propagation). Engine unit tests 447 -> 453. Engine coverage 96.61% (floor 96). |
|||
| 481a0e133c |
feat(spreadsheet-ui): red error cells and a visible-cell status bar
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Tranche 2 of the datagrid gap analysis — the small UI polish. (Zebra stripes were already present as `cell_alt_bg_color` alternating rows.) Error cells render red: - `render_cache::is_error_text` matches a display value against the engine's own `FormulaError::from_display` surface (plus `#SPILL!`), so real errors are red while a user value like `#hashtag` stays plain text. - The grid's text-colour precedence now checks the error case after an explicit user text colour and before bold/formula/default, using a new `error_text_color` (default red). Visible-cell status bar: - `GridMetrics::visible_cell_count(viewport_w, viewport_h)` estimates the visible columns/rows from the viewport and default cell sizes, clamped to the grid extent — testable in geometry.rs. - `SpreadsheetGrid::visible_cell_counts` hands the live viewport to it, and the workspace status label now shows "Ready | C × R visible = N cells", cached so it only re-lays-out when the numbers change (scroll or resize). Tests: 2 new (error detection against real/plain values; visible-count estimation + clamping). UI lib tests 73 -> 75; ui-controllers coverage 99.02% (floor 96). |
|||
| 0326da8dfe |
feat(spreadsheet): ^ power operator and General thousands separators
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Tranche 1 of the gap analysis against the Makepad `work`-branch datagrid example: the two formula-engine features it has that we lacked. `^` exponentiation operator: - New `Caret` token, `BinOp::Pow` (precedence 5), and a right-associative `parse_pow` between multiplicative and unary in the parser. - Unary minus binds tighter than `^`, so `-3^2` is `(-3)^2 = 9` — Excel's precedence, and the exact assertion the datagrid reference pins. - `2*3^2` = 18 (power over multiply) and `2^3^2` = 512 (right-assoc). - Evaluated in `apply_binop`, so it broadcasts element-wise over arrays like every other operator. General-format thousands separators: - `format_number` stays comma-free — computed values and criteria must round-trip through `parse_cell_computed_value` and `parse::<f64>`. - New `format_number_display` (plus a `group_thousands` helper) groups the integer part at the display boundary only: `apply_number_format`'s General arm now shows `1,000,000` while the raw/edit value stays `1000000`. Non-numeric text passes through untouched. Tests: `^` precedence/associativity/evaluation, display grouping, and an end-to-end check that comma display does not break the recalc fast path. Engine unit tests 441 -> 447; UI lib tests still pass. Engine coverage 96.58% (floor 96). |
|||
| 555c7daaa1 |
feat(spreadsheet): postfix LAMBDA application and LET
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Completes the first-class-function story started with LAMBDA-as-an-
argument.
Postfix application:
- A new `Expr::Apply { func, args }` node lets a callable expression be
invoked directly: `LAMBDA(x, x*2)(3)` and curried `LAMBDA(x, LAMBDA(y,
x+y))(1)(2)`. The parser gained a `parse_postfix` loop, so `f(...)(...)`
chains bind tightest, after the primary expression.
- Arguments are bound with `resolve_bound_arg`: a range or array binds as
an array (so `LAMBDA(x, SUM(x))(A1:A4)` aggregates), a scalar binds as
a scalar.
- Applying a non-callable is `#VALUE!`; an argument-count mismatch is
reported. Postfix arguments still register their cell dependencies.
LET:
- `LET(name1, value1, [name2, value2, ...], body)` binds names to values
sequentially — a later value may reference an earlier name — and
evaluates the body with the names in scope, reusing the LAMBDA
substitution machinery. A range value binds as an array, so
`LET(s, A1:A4, SUM(s))` aggregates the whole range.
- Duplicate names are rejected in both LET and LAMBDA, matching Excel;
an unbound name in the body is `#NAME?`.
Tests: 6 unit tests (postfix application, currying, LET binding/range/
error shapes, duplicate-parameter rejection, dependency tracking) + an
end-to-end test proving LET and postfix application recalculate through
the dependency graph. Engine unit tests 434 -> 441. Engine coverage
96.55% (floor 96).
|
|||
| 47f645ab58 |
feat(spreadsheet): resolve the four dynamic-array limits
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Closes the four limits documented when the spill model landed. LAMBDA (was: GROUPBY/PIVOTBY only took an aggregate name): - `LAMBDA(params..., body)` builds a `Value::Callable` without evaluating its body. A new `Expr::BoundValue` node splices a bound argument into the body AST when the callable is applied, so the ordinary evaluator runs the body. - `GROUPBY` and `PIVOTBY` accept either a named aggregate (`SUM`, ...) or a `LAMBDA`, applied per value column / per pivot bucket. This unlocks arbitrary aggregations (`LAMBDA(x, MAX(x)-MIN(x))`). FILTER include + full arithmetic broadcasting: - `FILTER` now accepts a same-shape include: matching cells are kept and non-matching positions become `#N/A`, element-wise, like Excel. - Unary operators broadcast over an array (`-A1#`, `-FILTER(...)`), completing the operator-level arithmetic alongside the existing binary broadcast. Spill formatting: - `SpillRange` records the anchor's `NumberFormat`, and derived cells are formatted at display time through a shared `apply_number_format` (extracted from `write_display_value`). Raw values stay numeric, so `SUM(A1#)` still evaluates correctly. #SPILL! blocking: - A spill that would overwrite an existing cell — or another spill, flowing or blocked — reports `#SPILL!` in the anchor instead of clobbering data. The would-be range is remembered in `blocked_spills`, and clearing the blocking cell retries the spill automatically. Tests: 7 new unit tests (LAMBDA in GROUPBY/PIVOTBY, lambda errors, same-shape FILTER, unary broadcast) + 2 end-to-end tests (NumberFormat inheritance and #SPILL! block-then-retry). Engine unit tests 427 -> 434; UI lib tests still pass. Engine coverage 96.41% (floor 96). |
|||
| c9474e2c9f |
feat(spreadsheet): dynamic-array spill model — FILTER, GROUPBY, PIVOTBY, A1#
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
The engine stored every formula result as one display string per cell. This adds the dynamic-array architecture: a formula can return a grid, which "spills" into the cells below/right of its anchor. Value model: - `Value::Array(Vec<Vec<Value>>)` — a grid result. Scalars coerce to one-cell grids where the shape matters; `to_f64`/`to_bool` refuse an array, `to_display_string` shows the top-left, and `resolve_arg` flattens an array argument so `SUM(FILTER(...))` aggregates its cells. - Element-wise broadcasting: `resolve_array2d` maps a binary operator over a grid when one side is a scalar (or both grids share a shape), so `FILTER(A1:A4, C1:C4 > 15)` builds the boolean include the way Excel does. The binary-op logic was factored into `apply_binop`. Parser and AST: - `#` is now the spill operator: `A1#` and `Sheet2!A1#` parse as `SpillRef` / `SheetSpillRef`, evaluate to the anchored array (so `=A1#` re-spills), flatten in aggregates, and track their anchor in the dependency graph (intra- and cross-sheet). Functions (dynamic arrays): - FILTER(array, include, [if_empty]) — keep rows (column include) or columns (row include); `#N/A` on no match unless `if_empty`. - GROUPBY(row_fields, values, function, [field_headers]) — group rows by field tuples and aggregate each value column (SUM/AVERAGE/COUNT/ MAX/MIN/MEDIAN by name, ETA-reduced-LAMBDA form). - PIVOTBY(row_fields, col_fields, values, function) — a 2D pivot with the aggregate name in the top-left corner. Spill storage (data.rs): - `SpillRange` + `spills` map on `SpreadsheetData`: derived cells read back through `get_display_value`/`get_raw`/`get_edit_value`, are not blank, and are read-only — `set_cell`/`put_cell`/`remove_cell`/ `mutate_cell` refuse to touch them (the UI blocks via `is_spilled`). - Recalc builds the spill from the `Array` result (`apply_formula_result`) and drops stale spills when a formula becomes scalar, is removed, or cycles. Spills are derived state, never serialized — the anchor formula persists and re-derives on load. - Cross-sheet spills read through `get_sheet_spill_values`. Tests: 21 new units (FILTER/GROUPBY/PIVOTBY shapes, broadcasting, Value::Array methods, spill parsing) + 5 end-to-end tests (spill display, read-only cells, `A1#` aggregation and re-spill, stale-spill clearing, cross-sheet spill). Engine unit tests 405 -> 427; UI lib tests still pass. Engine coverage 96.66% (floor 96). |
|||
| 45efc74106 |
feat(spreadsheet): pivot and chart aggregates — IFS family, statistics, SUMPRODUCT, LARGE/SMALL/RANK
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Full GROUPBY/PIVOTBY need a dynamic-array "spill" model (a formula returning a grid), which this single-cell engine deliberately does not have. These are the single-cell building blocks that do the same work. Multi-criteria conditional aggregates (the "filter then aggregate" pivot core), resolved positionally so criteria ranges stay aligned: - SUMIFS(sum_range, criteria_range1, criteria1, ...) - AVERAGEIFS(avg_range, criteria_range1, criteria1, ...) (#DIV/0! on no match) - COUNTIFS(criteria_range1, criteria1, ...) - MAXIFS / MINIFS (0 on no match, like Excel) Mismatched range sizes are #VALUE!, not a silent misalignment. Chart statistics (over the flattened numeric arguments): - MEDIAN, MODE (ties keep the smallest value), and the sample/population STDEV / STDEVP / VAR / VARP. Sample forms divide by n-1 (#DIV/0! for a single value), population by n. Pivot/ranking helpers: - SUMPRODUCT(array1, [array2], ...) — the element-wise dot product; text counts as zero, errors propagate, mismatched sizes are #VALUE!. - LARGE / SMALL(array, k) — k-th largest/smallest; k out of range is the new #NUM! error. - RANK(value, array, [order]) — descending by default, ascending on any nonzero order, tied values share a rank (RANK.EQ). `FormulaError` gains `NumError` (`#NUM!`) for out-of-domain numeric arguments, rounding out the error surface after `Na` in the lookup tranche; it round-trips through display/parse and propagates from cached values. Tests: 10 unit tests (multi-criteria aggregation, statistics, dot products, top-N/ranking, argument/size errors) + an end-to-end test proving the dependency graph tracks every range and recalculates the pivot formulas when a source cell is edited. Engine unit tests 395 -> 405. Engine coverage 97.34% (floor 96). |
|||
| 780e323674 |
refactor(spreadsheet-ui): headless formula-bar state machine, wired to the workspace
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
The formula bar's state — formula text, target cell, dirty flag, and the "ignore the next change" latch — lived as five scattered fields plus inline logic inside workspace.rs, a `script_mod!` DSL file that no unit test can construct. That made the formula bar the one piece of the UI whose behaviour was untestable headlessly. Extract a `formula_bar` controller module (the same pattern as the earlier geometry.rs extraction) and wire the workspace to it: - `FormulaBar` owns text/target/dirty/latch with `sync_to_cell`, `on_user_input`, `insert_reference`, `take_commit`, and `sync_after_commit`. The workspace now only bridges it to the Makepad text input; the change/return/focus-loss/undo-redo handlers route through the controller unchanged in behaviour. - Point-and-click formula building (new, Excel-style): while the formula bar is focused, selecting a cell appends its reference instead of replacing the formula being edited. `reference_for(sheet, row, col)` and `quote_sheet_name` build `A1`, `Sheet2!A1`, or `'My Sheet'!A1` with Excel's quoting rules (bare identifier unquoted; spaces, punctuation or a leading digit quoted; embedded quotes doubled) — the machinery that makes the cross-sheet reference syntax buildable from the UI. - The coverage script now measures formula_bar.rs alongside the other controller modules. Tests: 11 new headless unit tests (quoting, reference construction, sync/latch/commit round trip, and insertion into empty/value/formula bars). UI lib tests 62 -> 73, all pass; ui-controllers coverage 98.74% (floor 96) with formula_bar.rs at ~98%. |
|||
| d0e7ede0c1 |
feat(spreadsheet): cross-sheet named ranges (Data!Total)
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
Completes the cross-sheet reference feature with `Sheet!Name`, the one
shape the previous tranche explicitly left out.
Parser and AST:
- `Sheet2!Total` and `'My Sheet'!Sales` parse as a new
`Expr::SheetNamedRange { sheet, name }` node. In
`parse_sheet_qualified_ref`, an identifier after `!` that is not a
valid cell reference is treated as a named range; a cell reference
wins when both readings are possible (`Sheet2!A1`), matching Excel.
Evaluation:
- `EvalContext` gains `get_sheet_named_range` (default `None`).
`evaluate` resolves a `SheetNamedRange` to its range and returns the
first cell in expression context, while `resolve_arg` expands it to
every cell for aggregates — so `Data!Total` reads one cell and
`SUM(Data!Total)` sums the whole range. An unknown name on a real
sheet is `#NAME?`, and `DataEvalContext` looks the range up on the
sibling sheet case-insensitively (or on the sheet itself, through
the intra-sheet path).
Dependencies:
- A named-range reference is a coarse sheet-level dependency, because
its bounds live on the target sheet. `SpreadsheetData` tracks
`cross_sheet_named_refs` (cell -> sheet names), rebuilt by
`rebuild_dependency_graphs` / `update_dependency_graph` alongside the
cell-level `cross_sheet_refs`, and the workbook folds both into its
sheet-index dependency map. Editing a cell inside the named range
therefore recalculates the reading sheet.
Tests: parser/evaluator units (incl. quoted names, cell-vs-named
precedence, dependency extraction) and workbook end-to-end (SUM over a
cross-sheet named range with propagation on edit, quoted names, and the
#NAME? case). Engine unit tests 390 -> 395; UI lib tests still pass.
Engine coverage 97.56% (floor 96).
|
|||
| 593cd8fee8 |
feat(spreadsheet): cross-sheet references (Sheet2!A1)
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
The engine's dependency graph is per-sheet by design, so this was the
structural change: a formula can now read a cell on another sheet, and
edits to the source sheet recalculate the readers.
Parser and AST:
- New tokens: `Bang` (`!`) and `SheetName` (`'My Sheet'`, with `''` as
an escaped quote). `!=` still tokenizes as not-equal.
- New AST nodes: `SheetCellRef { sheet, cell }` and
`SheetRange { sheet, range }`, parsed from `Sheet2!A1`,
`Sheet2!A1:B2`, `'My Sheet'!B3`, before the cell-ref fallback (a bare
`Sheet2` would otherwise parse as a bogus cell reference).
- `evaluate` and `resolve_arg` resolve them through two new `EvalContext`
hooks, `get_sheet_cell_value` / `get_sheet_range_values`, which default
to `#REF!` in contexts without sibling sheets.
Evaluation:
- `DataEvalContext` gains a sibling-sheet view plus a shared cross-sheet
cycle guard keyed by `(sheet, row, col)`. Reading a sibling routes to a
child context pointed at that sibling; re-entering the same cell on the
same sheet mid-evaluation reports `#CYCLE!`, and a reference back to the
sheet being recalculated is caught through the guard.
- `recalculate_all_with` / `evaluate_formula_with` accept the sibling
view; the plain per-sheet entry points are unchanged.
Recalculation and dependencies:
- `SpreadsheetData` tracks `cross_sheet_refs` per cell (rebuilt by
`rebuild_dependency_graphs` / `update_dependency_graph`), and the
workbook flattens it into sheet-index edges. Editing a sheet
recalculates — with sibling access — the transitive closure of sheets
that read it, plus the sheet itself (its own formulas may read other
sheets). Single-sheet edits with no cross-sheet references keep the
fast incremental path.
- `evaluate_all` clears computed values across sheets first, then runs
one extra pass per sheet, so acyclic chains propagate and mutual
cross-sheet cycles terminate with `#CYCLE!`.
- Cross-sheet formulas survive save/load: deserialization runs a
sibling-aware pass, and a missing sheet renders `#REF!`.
Tests: parser/evaluator units (incl. quoted names, dependency
extraction) and workbook end-to-end (read + propagation, a three-sheet
chain, quoted names, missing sheet, save/load round trip, cycle
termination). Engine unit tests 382 -> 390; UI lib tests still pass.
Engine coverage 97.55% (floor 96).
|
|||
| 2d7b48b72c |
feat(spreadsheet): lookup functions — MATCH, INDEX, VLOOKUP, HLOOKUP, XLOOKUP
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
The formula engine could compute over ranges but could not look a value up in one. This adds the Excel lookup family, plus the #N/A error they need to report "not found". New `FormulaError::Na`: - Renders as `#N/A`, parses back in `from_display`, and — a bonus fix — the recalc fast path now propagates a stored `#N/A` as an error instead of turning it into text. Functions (5): - MATCH(lookup_value, lookup_array, [match_type]) — 1-based position. Type 0 exact (case-insensitive text), 1 largest ≤ lookup, -1 smallest ≥ lookup. - INDEX(array, row_num, [col_num]) — cell at a 1-based position; a single index walks the array flat in row-major order. Out of range is #REF!. - VLOOKUP / HLOOKUP — exact or approximate (approximate takes the largest first-column/first-row value ≤ lookup, the sorted-table convention), col/row index out of range is #REF!. - XLOOKUP(lookup_value, lookup_array, return_array, [if_not_found], [match_mode], [search_mode]) — match modes 0 exact, -1 next-smaller, 1 next-larger, 2 wildcard (`*`/`?`, case-insensitive); search modes 1 first-to-last and -1 last-to-first; the `if_not_found` fallback is evaluated lazily, only when nothing matches. Binary search modes are rejected with a clear error rather than silently mishandled. Lookups index ranges by position, so they keep their arguments as AST nodes (a new `range_from_arg` resolves Range and NamedRange expressions) instead of flattening through the aggregate path. Tests: 9 new unit tests (exact/approximate/wildcard/search-direction/ error arms) + an end-to-end test proving the dependency graph tracks the lookup table and recalculates dependents when a table cell is edited. Engine unit tests 373 -> 382. |
|||
| 26f6d431fb |
feat(spreadsheet): date and time functions, with a date display format
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
The formula engine had no notion of dates. This adds an Excel-style date/time model and the functions to work with it. Date model (new `dates` module): - A date/time is a single f64: the integer part is a day serial, the fraction is the time of day. Serial 25569 = 1970-01-01, so every modern date agrees with Excel exactly. The calendar is the proleptic Gregorian (Howard Hinnant's days_from_civil/civil_from_days), so Excel's phantom 1900-02-29 (serial 60) reads back as 1900-02-28 and serial 61 = 1900-03-01 — documented rather than reproduced. - Serial <-> calendar conversion, day-of-week, leap-year and days-in-month helpers, date/time string parsing, EDATE/EOMONTH, DATEDIF(Y/M/D), and ISO formatting. Functions (17): - DATE, DATEVALUE, YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, TIME, TIMEVALUE, WEEKDAY (return types 1/2/3), DAYS, EDATE, EOMONTH, DATEDIF — with Excel's month/day rollover in DATE, day clamping in EDATE, and #VALUE! for malformed strings and unknown units. - TODAY and NOW read a wall clock. The engine stays deterministic: `EvalContext` gains a default-none `now_serial` hook, and `SpreadsheetData` carries an optional `now_serial` (never serialized). Without a clock they report "requires a wall clock"; `SpreadsheetData::set_system_now` / `Workbook::set_system_now` supply the system clock, and the UI workspace model wires it on creation. Display: - `NumberFormat::Date` and `NumberFormat::DateTime` render a serial as `YYYY-MM-DD` / `YYYY-MM-DD HH:MM:SS` through write_display_value, with codes that round-trip through the existing serialization. Tests: 18 new unit tests (calendar round-trips across centuries, known serials, leap-year rules, parsing, weekday schemes, EDATE/EOMONTH clamping, DATEDIF, ISO formatting) + 6 evaluator tests + an end-to-end test covering format rendering and recalc through the dependency graph. Engine unit tests 352 -> 373. UI lib tests still 62 pass with the new clock wiring. |
|||
| b5ff5dcf40 |
feat(spreadsheet): text manipulation and IS* info functions
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
The formula engine could count, sum, compare and test conditions, but had almost no way to work with text (only CONCAT/LEN/UPPER/LOWER) and no way to ask about a value's type. This adds both. Text functions (operate on the display form of their arguments): - TRIM — collapse internal space runs and drop leading/trailing spaces - LEFT / RIGHT — first/last n characters (default 1) - MID — n characters from a 1-based start - SUBSTITUTE — replace all occurrences, or just the nth instance - FIND / SEARCH — 1-based position, case-sensitive vs case-insensitive, #VALUE! when not found or start is out of range - REPT — repeat n times, capped at Excel's 32767-character result - PROPER — title-case each word Info functions (never propagate their argument's error, like Excel — ISERROR reports it, the others treat it as FALSE): - ISNUMBER / ISTEXT / ISNONTEXT / ISLOGICAL / ISERROR - ISBLANK — TRUE only for an absent cell. This needs the distinction between "missing" and "holds 0", so EvalContext gains a `cell_is_blank` method; DataEvalContext overrides it with a direct cells lookup (an absent cell reads as Number(0.0) through get_cell_value, but is blank, whereas a real 0 is not). Dependencies flow through the existing AST walk, so a TRIM/LEFT/etc. reference is tracked and recalculates when its source cell is edited — pinned by an end-to-end test through SpreadsheetData. Engine unit tests 345 -> 352. |
|||
| b93dc485b9 |
test(spreadsheet): cover public workbook serialization round trip
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
|
|||
| b7eacb2a94 |
test(spreadsheet): cover public multi-sheet evaluation
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
|
|||
| 4deefabd0a |
test(spreadsheet): cover public style commands
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
|
|||
| a099ab9980 |
test(spreadsheet): cover public workbook lifecycle API
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
|
|||
| 2824b49f0e |
test(spreadsheet): cover public workbook deserialization API
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
|
|||
| 1629994de7 |
test(spreadsheet): add public persistence integration test
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
|
|||
| 118fbefe9a |
test(spreadsheet): move format detection test to integration suite
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
|
|||
| 676b47087a |
refactor(spreadsheet-ui): centralize dirty region state
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
|
|||
| f75c1cc966 |
feat(spreadsheet-ui): model dirty render regions
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
|
|||
| ee81d292ed |
test(spreadsheet): cover literal and comparison formula edges
Some checks failed
email.yml / test(spreadsheet): cover literal and comparison formula edges (push) Failing after 0s
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
|
|||
| 0fcb2d3fae |
test(spreadsheet): cover sum boolean text error branches
Some checks failed
email.yml / test(spreadsheet): cover sum boolean text error branches (push) Failing after 0s
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
|
|||
| b7eb271a0d |
feat(spreadsheet): logical and conditional formula functions
Some checks failed
email.yml / feat(spreadsheet): logical and conditional formula functions (push) Failing after 0s
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
The formula engine could sum, average and do single-cell math, but had no way to combine conditions or aggregate selectively — no `=IF(AND(...))`, no `=SUMIF(...)`. This closes that gap with seven Excel-compatible functions. Logical (evaluated lazily, like IF, so they short-circuit): - `AND(a, b, ...)` — TRUE iff every argument coerces to TRUE; ranges flatten to their cells. Stops at the first FALSE, so `AND(FALSE, 1/0)` is FALSE rather than `#DIV/0!`. - `OR(a, b, ...)` — TRUE iff any argument is TRUE, short-circuiting on the first TRUE. - `NOT(x)` — logical negation of the single argument. - `IFERROR(value, fallback)` — `value` unless it errors, in which case the fallback is evaluated and returned (lazily). Conditional aggregates (criteria-matched by position): - `COUNTIF(range, criteria)` — count of matching cells. - `SUMIF(range, criteria[, sum_range])` — sum of `sum_range` (or `range`) cells whose position matches; text in the summed region is skipped, errors propagate. - `AVERAGEIF(range, criteria[, average_range])` — mean of the matched cells, `#DIV/0!` when nothing matches. Criteria accept numbers, comparison operators (`>5`, `>=5`, `<5`, `<=5`, `<>5`, `=5`), case-insensitive text (`"apple"`, `=apple`, `<>apple`), and cell references holding any of those. Wildcards are not supported. Dependencies flow through the existing AST walk, so a SUMIF's range and criteria cell are tracked by the dependency graph and the formula recalculates when an input is edited — pinned by an end-to-end test through SpreadsheetData. Engine unit tests 331 -> 343. |
|||
| 8ca5070b26 |
test(spreadsheet): cover the remaining serialization and border branches
Some checks failed
email.yml / test(spreadsheet): cover the remaining serialization and border branches (push) Failing after 0s
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
The last reachable lines in data.rs, all in the persistence layer and one style command: - serialize/deserialize round-trip for named ranges (the NAME block line), text colour, and an unknown block tag (ignored for forward compatibility), plus a malformed NAME line inserting nothing. - The legacy CSV deserializer's `|B` bold and `|F` formula metadata segments. - apply_command with BorderTarget::None clearing all four edges (the "remove borders" action), the complement of the per-edge arm the existing test covers. - A large-range self-reference reporting CycleDetected (pins the fix in the previous commit). data.rs 97.91% -> 99.49%; engine total 98.50% -> 99.13%. Unit tests 325 -> 331. Deliberately uncovered, as documented: the B17 invariant's debug_assert/#ERROR! patch (only reachable on a dep-graph bug), the topological-sort underflow guard, the let-else continue after a cell disappears mid-iteration, and the `}` attribution regions after unconditional returns. |
|||
| e1dcbefda3 |
fix(spreadsheet): large-range cycle detection must report, not zero
The large-range fast path in DataEvalContext::get_range_values (used for ranges over 64 cells) handled a re-entrant formula cell — a reference cycle — by silently pushing 0.0. The small-range path in get_cell_value reports FormulaError::CycleDetected for the same situation. So a cyclic formula inside a large range contributed zero to the sum instead of surfacing #CYCLE!. The inconsistency is invisible in normal recalculation: the topological sort in recalculate_incremental detects cycles before any evaluation, so the branch only fires through the legacy recursive get_cell_value API. The fix makes the two range paths agree. |
|||
| 691b868264 |
fix(spreadsheet): make an editing cell readable — no doubled border, cell's own ink and fill
Some checks failed
email.yml / fix(spreadsheet): make an editing cell readable — no doubled border, cell's own ink and fill (push) Failing after 0s
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
The same three symptoms were reported twice. The first fix went to `makepad_table`, which is not in the APK — `pageflipnav` does not depend on it. The editor actually being clicked is `SpreadsheetGrid::draw_edit_overlay`, reached through `nigig-build -> spreadsheet-ui`, and it is a custom-drawn overlay rather than a `TextInput`. This fixes that one. 1. **No border of its own.** `draw_walk` calls `draw_selection_overlay` immediately before `draw_edit_overlay`, and the selection rectangle is already stroked around this exact cell in `selected_border_color`. The overlay then drew four more 2px rects inside it, which is the doubled frame: an outer selection edge and an inner editor edge a pixel apart. The four strokes are gone. Checked before removing them that this cannot leave a cell unframed. All three paths into `request_begin_edit` are on an already-selected cell: the keystroke path uses `self.selected_cell()` by definition, and both double-tap paths record `InputIntent::Select` for the same cell on the first tap. The caret remains the signal that the cell is in edit mode. 2. **The cell's own ink, not a forced one.** The overlay did `self.draw_text.color = self.text_color;` with no branching, so a cell with a user text colour, a bold cell, or a formula all changed colour the moment the caret landed in them. It now resolves the same way the resting draw path does: per-cell colour, then formula green, then bold, then the default. 3. **The surface it rests on.** The fill was always `edit_bg_color`, which equals `cell_bg_color` — so an odd row, which rests on `cell_alt_bg_color`, visibly changed shade when editing began, and a cell the user had given a background lost it entirely. That last case is the "background goes a different colour and the text disappears" report: the fill came from `edit_bg_color` while the ink came from the cell's own style, and nothing kept the two in agreement. It also covers the selection fill. A cell being edited is by definition selected, so `draw_cell_bg` had already painted `selected_bg_color` (#x2d4a63, a blue-grey) underneath — text was sitting on a wash it was never coloured for. That is the grey. `grid.rs` had no tests and is excluded from the coverage report, because the widget needs a live `Cx`. The background choice does not, so it moved into `editing_bg_source` and is tested there; the border and ink changes are pinned by reading this file, which is blunt but is the only thing short of a GPU that can catch "compiles, runs, wrong on screen". 6 tests, verified against all three defects: restoring the border stroke fails 1, forcing `text_color` again fails 1, and ignoring row striping and cell backgrounds fails 3. The 12 clippy warnings in this crate tree are pre-existing and unchanged — checked by running the same command on a stashed tree. None are in the hunks here. |
|||
| 587a43864f |
test(spreadsheet-ui): cover the remaining controller branches
Some checks failed
email.yml / test(spreadsheet-ui): cover the remaining controller branches (push) Failing after 0s
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
The UI controller modules are the one part of the spreadsheet stack the coverage report could not reach before the memory-conscious build (-j1); measured now, their last reachable gaps are closed. geometry.rs 98.77% -> 99.51%: - row_resize_at: a point just inside a row's bottom edge resizes that row, and a point in the middle of a row is no resize target. The exact boundary between two rows resolves to the lower one, so the bottom-edge arm had no other path to it. render_cache.rs 98.21% -> 98.68%: - get_or_insert_with on a missing cell runs the builder (the entry API's insert arm, the complement of the hit arm), and a later read sees the cached state without rebuilding. model.rs 90.99% -> 98.40%: - load_saved() and save() were only exercised by a #[ignore]d test. The test now runs by default: it serialises on a module-local lock and restores whatever was in the shared generated/ directory before it ran, the same save/restore convention the engine's own persistence tests use. ui-controllers total 96.99% -> 99.11% (floor 96). UI lib tests 52 -> 55, all pass, none ignored. Deliberately uncovered, as before: the panic-arm canaries in event_router's positive tests, the frozen-pane `return None` guards in col_at_x/row_at_y (the frozen loop spans exactly the frozen interval, so they cannot fire), the existing unreachable-builder closure in render_cache's hit test, and the environment-dependent save/restore cleanup branch in model's disk test. |
|||
| 6b04b07e14 |
test(spreadsheet): cover the last reachable engine branches
Some checks failed
email.yml / test(spreadsheet): cover the last reachable engine branches (push) Failing after 0s
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
Three files, each the weakest reachable logic left after the workbook_api
tranche.
autofill.rs 92.71% -> 96.26%:
- A single chrono value is still a series (delta defaults to 1).
- A chrono sequence whose delta changes mid-run is not a series.
- A repeated chrono value has delta 0 and is rejected rather than
producing an infinite fill.
- A backward chrono sequence wraps the delta modulo the list length
(Feb, Jan -> delta 11).
- get_series_value on Series::None returns None.
- match_case with an empty value returns an empty string.
undo.rs 98.34% -> 100%:
- Redo of a deletion (a Change::SetCell whose `new` is None, the shape
recorded by set_cell("") and remove_cell) removes the cell and its
dependency-graph edges; undoing it restores both. This is the mirror
of the existing create/undo test and was the one uncovered arm in
Change::apply_redo.
workbook.rs 99.35% -> 99.68%:
- detect_workbook_version on a legacy `#MP_SHEET_V2` payload reports
version 1.
Engine total 98.24% -> 98.50% (floor 96). Unit tests 318 -> 325.
Deliberately uncovered, as before: panic-arm canaries in positive
tests, the `}` after an unconditional return in detect_series, and the
test-harness save/restore cleanup branches in persistence.rs (their
inverse runs depending on pre-existing state — the CI-normal path).
|
|||
| 89437838b7 |
test(spreadsheet): cover workbook_api.rs and util.rs remaining branches
Some checks failed
email.yml / test(spreadsheet): cover workbook_api.rs and util.rs remaining branches (push) Failing after 0s
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
The two weakest files left after the formula2 tranche. workbook_api.rs 88.80% -> 99.58%: - with_sheets_active with an empty sheet list falls back to a fresh single-sheet workbook. - set_active_sheet with an out-of-range index is a no-op. - remove_sheet: removing a sheet before the active one shifts the index down; removing the active one (or one after it) clamps to the new end. - apply(): the style arms the earlier tests did not touch — italic, underline, number format, background — and both SetBorders shapes (per-edge target writes each requested edge, BorderTarget::None clears all four). - Workbook::default() is a fresh single-sheet workbook. - save()/load() round-trip through the default path, a legacy payload still loads as a one-sheet workbook, and a non-workbook payload yields None. The disk test takes a new crate::test_disk lock so it cannot race the persistence module's disk test under parallel `cargo test`; that pre-existing test now takes the same lock. util.rs 90.91% -> 100%: - write_col_letters matches col_letters across one-, two- and three- letter columns and reuses the caller's buffer without stale tails. - adjust_formula_refs: a digit-bearing function name (LOG10) is not a cell ref; an 8-letter column overflowing u32 and a 30-digit row overflowing i64 are copied verbatim through the overflow fallbacks. Engine total 96.54% -> 98.24% (floor 96). Unit tests 308 -> 318; 9 integration tests unchanged. Deliberately uncovered: the mutex-poison recovery closure in the test_disk lock (unreachable unless a test panics while holding it), and the disk test's restore-to-clean branch (runs only when no save file pre-exists — the CI-normal path; its inverse is the branch that runs otherwise). Both match the existing persistence test's save/restore convention. |
|||
| 4a6409f37b |
chore(spreadsheet): drop Workbook::load's dead legacy-migrate branch
`Workbook::load()` carried a fallback — if the saved payload was not a V2 workbook but "looked legacy", migrate it — plus the two private helpers behind it, `is_legacy_workbook_payload` and `migrate_legacy_workbook`. The branch is unreachable. `deserialize_workbook` already treats both legacy V1 formats (single-sheet `#MP_SHEET_V2` and the older CSV) as `WorkbookFormat::V1Legacy` and returns a migrated one-sheet workbook, so by the time `load()` sees a legacy payload the `if let` above the fallback has already returned `Some`. The two conditions are exact complements — any string `deserialize_workbook` rejects is also rejected by `is_legacy_workbook_payload` — so the migrate branch and both helpers are dead code, confirmed by direct probe against `detect_workbook_format`. Behavior is unchanged: the legacy migration still happens, inside `deserialize_workbook`, which the existing `legacy_v1_migration_clears_undo_history` test already pins. |
|||
| d7fcd4c73d |
refactor(spreadsheet-ui): extract grid geometry so it can be measured
The UI coverage exclusion was hiding real logic, and the exclusion note said it was not. `grid.rs`, `ui.rs` and `workspace.rs` are excluded from coverage on the grounds that they carry the `script_mod!` DSL and cannot be constructed without a `ScriptVm`. That is true of the files. It was not true of most of their contents: `grid.rs` is 2,702 lines of which roughly the last 30 are DSL, and of its 59 functions **36 take no `cx`, no `Event` and no `Scope`**. Hit testing, cell rectangles, frozen-pane placement, scroll offsets, resize borders, autofill handle bounds — all arithmetic over plain numbers, none of it reachable by the report, and it carried **zero tests**. Confirmed rather than assumed: a probe test constructing `SpreadsheetGrid::default()` fails to compile, because the `Script` derive provides `script_default(vm)` and not `Default`. So the file genuinely cannot be unit-tested — which is exactly why the logic had to leave it rather than stay behind the exclusion. `geometry.rs` holds that arithmetic now as `GridMetrics`, a plain struct with no Makepad dependency. `grid.rs` keeps no second copy: `metrics()` snapshots the widget's live fields and `col_at_x`, `row_at_y`, `cell_abs_rect`, `range_abs_rect` and `handle_rect` all delegate. A parallel implementation would drift from its own tests, which is the failure this is meant to end, not repeat. Behaviour is unchanged and the semantics were read out of the original before being moved — including the ones that look like bugs and are not: a point left of the row header returns `None` rather than column 0, the frozen pane is searched before the scrolling area, and a fractional scroll offsets by a fraction of the *default* width rather than the overridden one, matching the scrollbar's model. Two review items are addressed on the way. SPREADSHEET REVIEW item 7 names `col_at_x`/`row_at_y`/`cell_abs_rect` as O(N) scans run per frame and per pointer event; item 10 names the geometry tangled through `handle_event`. The maths is now in one place with a stated coordinate convention, which is the precondition for replacing the scans with prefix sums — that is a separate change, deliberately, because this one must not alter a single pixel. 29 tests. They assert relationships rather than constants where the relationship is the contract: every cell origin hit-tests back to its own cell over an 8x6 grid, cell boundaries are half-open so there is no dead pixel between columns, frozen cells stay put under a scroll, and a reversed selection drag normalises instead of producing a negative-sized rect. The fixture grid uses non-uniform sizes on purpose — with every column 100 wide, an off-by-one column index and a 100-pixel offset error are indistinguishable, and so are a width and a height. Verified by mutation, six injected defects, each confirmed red: frozen columns scroll with the grid 1 fail range_rect stops normalising corners 1 fail cell boundary becomes inclusive 1 fail fractional scroll ignored 1 fail handle touch-target floor removed 1 fail resize ignores the header-strip check 1 fail UI controllers 95.70% -> 97.00%, floor 95 -> 96; geometry.rs at 98.78%. UI tests 23 -> 52. The gain is not the percentage — it is 409 lines of logic that were previously invisible to it. The exclusion note now says to audit the list before widening it. An exclusion that quietly grows to cover real logic is worse than no exclusion, because the number stays green while the coverage goes away. |
|||
| c301c7a48f |
test(spreadsheet): cover formula2.rs tokenizer/evaluator branches
Some checks failed
email.yml / test(spreadsheet): cover formula2.rs tokenizer/evaluator branches (push) Failing after 0s
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
The branches the corrected report named, now that every test binary is measured: - Tokenizer: a lone `!` is a parse error; string escapes (`\n`, `\t`, `\\`); a double-dot number (`1.2.3`) is rejected after the tokenizer breaks on the second dot. - FormulaError: `InvalidRef` and `RuntimeError` display strings; the `Display` delegation; `from_display` round-trip for `#REF!...`. - BinOp precedence table, pinned so a shared precedence cannot pass silently. - Value conversions: `to_f64` / `to_bool` / `to_display_string` / `is_error` propagate and render `Value::Error`. - Evaluator: boolean literals; unary `+` (built directly — the parser collapses `+x` to `x`, so the `UnaryOp::Pos` arm only runs on a hand-built AST); single-cell named range; single- and multi-cell range expressions; `SUM(Undefined)` -> UnknownName through `resolve_arg`; `values_equal` text (case-insensitive), boolean, error and mixed-type arms. - evaluate_call: `SQRT(-4)` -> `#DIV/0!`; `LOG10`, `SIN`, `COS`, `TAN`. - evaluate_if: wrong argument count -> `#VALUE:`. formula2.rs 90.11% -> 99.05% lines; engine total 94.38% -> 96.54%. Unit tests 289 -> 308; 9 integration tests unchanged. Deliberately left uncovered: the `invalid number` map_err closure in the tokenizer (an f64 parse of a digit/dot string cannot fail) and the `_ => panic!(...)` arms of existing positive match tests. |
|||
| 8776a961ee |
chore(spreadsheet): remove dead CellRef::parse_inner
`parse_inner` is a byte-for-byte duplicate of the earlier iteration of `CellRef::parse` that carried `#[allow(dead_code)]` since a refactor, and no caller in the repository references it. It never ran, so its 60 lines could only ever drag the coverage report down without guarding anything. `parse` remains the single entry point for cell-reference grammar. |
|||
| 8325805e22 |
test(spreadsheet): measure every test binary, and cover what that exposed
The coverage report was reading one object file. Cargo builds each integration test into its own executable, so measuring only the lib-test binary discarded everything `tests/` exercised. That is not a rounding error. `persistence.rs` reported 41.77% with 14 of its 17 functions apparently never called, while `tests/sync_flow.rs` was calling `save_spreadsheet_state` and `load_saved_spreadsheet_state` on every run and passing. The functions were covered; the report was reading the wrong object. Fixing it alone moved persistence.rs to 72.15% and the engine total 90.09% -> 90.57% without a single new test. This is the third defect of its kind in this script — the ignore regex that excluded the sources being measured, the awk matcher that never fired, and now the single-object report. All three had the same signature: a confident number that was measuring less than it claimed. `--all-targets` for the UI crate too, so a future `tests/` file is measured the day it is added rather than silently skipped. Doing that immediately surfaced `spreadsheet-ui/tests/ui.rs`, which had **never compiled**: the crate did not enable `makepad-widgets`' `test` feature, so `makepad_widgets::makepad_test` did not resolve. `cargo test --lib` never built it and nothing reported the breakage. The manifest now enables the feature, matching `pdf-makepad`, and the two tests are `#[ignore]`d with the same documented reason as `pdf-makepad`'s — the fork has no headless Linux backend. Compiled on every run, so they cannot rot further while appearing to be coverage. Then the branches the corrected report named: - `undo.rs` 86.11% -> 98.34%. Resize undo/redo, both directions. The `None` arms are the substance: a column with no width override must have its key *removed* on undo, not have a default written into it. Writing a default looks identical until the default changes, at which point every previously-resized-then-undone column stops following it. - `persistence.rs` -> 93.70%. The legacy `current.sheet.csv` fallback, including that a whitespace-only current file must not shadow a real legacy one; `save_spreadsheet_state_as` writing where it says it does; and a rejected filename writing nothing at all. - `style.rs` 92.19% -> 100%. Format and alignment codes round-trip, and the codes are distinct — a shared code passes a round-trip test while making two formats indistinguishable on disk. - `model.rs` 84.87% -> 90.99%. Undo/redo intents, from_parts/into_parts, the active-sheet accessors agreeing with each other, and the disk round trip (`#[ignore]`d: it writes the shared generated/ file). Verified by mutation, seven injected defects, each confirmed red: undo None-arm writes a default 6 fail two number formats share a code 1 fail save_as ignores its validation 1 fail legacy fallback removed 2 fail Undo intent wired to redo() 1 fail from_parts drops the active index 1 fail active_sheet_data_mut hits sheet 0 1 fail Two of those changed the tests rather than merely passing: - `save_as ignores its validation` really does write `../escape.tsv` into the crate root, and the file survives the failing run — so every later run failed on the previous run's debris rather than on the current code. The test now removes any leftover before asserting. - `undo_and_redo_intents_reach_the_workbook` failed on first run because `apply()` does not call `begin_recording` and `apply_batch()` does, so there was nothing to undo. That asymmetry is the trap pinned by the engine's `only_set_cell_records_its_own_undo_step`; it now has a test on the model side too, since a caller reaching for `apply` and then offering an undo button gets a button that does nothing. Also fixed a pre-existing clippy **error** in `util.rs` — `approx_constant` on a literal `3.14` in a test that has nothing to do with PI. Confirmed pre-existing by reproducing on a stashed tree. It denies the whole crate, so no clippy gate could be added while it stood. Engine 269 -> 280 tests, 90.09% -> 91.58%; floor 90 -> 91. UI 17 -> 23 tests, 94.55% -> 95.70%; floor 94 -> 95. |
|||
| a859053bc6 |
test(spreadsheet): cover remaining data.rs dependency-graph branches
Some checks failed
email.yml / test(spreadsheet): cover remaining data.rs dependency-graph branches (push) Failing after 0s
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
Targets the uncovered branches the coverage report named, around the
formula dependency graph and incremental recalculation:
- Formula replacement/removal edge cases: set_cell("") removes the
cell and its edges, formula->value and formula->formula rewiring
drop stale dependency edges.
- No-op removal paths: set_cell("") on a missing cell records nothing.
- Dependency-graph cleanup after formula deletion: remove_cell on a
formula cell, and update_dependency_graph's defensive path when a
dependent has no dependents entry.
- Affected-cell recalculation error paths: non-formula cells inside a
cycle-affected set keep their raw value; the ="" empty-result
regression; the recursive eval slow path (cached AST, parse
fallback, and CycleDetected); parse_cell_computed_value arms; parse
errors propagating through a dependent and through a large range.
Also covers named-range unary expansion (=-Total), the >64-cell range
fast path, non-numeric number-format fallbacks, and the demo_q3 /
demo_roi constructors.
Engine line coverage: data.rs 91.17% -> 97.90%; engine total
90.09% -> 93.24%. Unit tests 260 -> 279 (19 new); 9 integration tests
unchanged.
|
|||
| a88fb68eab |
fix(spreadsheet): B17 recalc invariant must not panic on empty-string results
`recalculate_incremental` treated "empty computed_value on a changed formula cell" as proof that the topological sort dropped the cell, and debug_asserted on it. But a formula may legitimately evaluate to the empty string (`=""`, `=IF(FALSE, "x", "")`), leaving computed_value empty through no fault of the dep-graph walk. In a debug build that edit panicked the engine. The invariant now checks what it actually meant to check: whether the topological sort *visited* every changed formula cell, using the `visited` set built from `sorted`. Emptiness is no longer used as the error signal, so legitimate empty-string results flow through (the display falls back to the raw formula text, as already documented for empty computed values). |
|||
| 5cb1bfe9f3 |
test(spreadsheet): cover the branches the report named
Some checks failed
email.yml / test(spreadsheet): cover the branches the report named (push) Failing after 0s
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
Payment domain, storage, platform and UI / payment-ui-tests (push) Has been cancelled
Nine tests against `workbook_api.rs`, the weakest file in the engine at 77.80%. Now 88.80%; engine total 88.97% -> 90.09%, tests 259 -> 260. One of them pins a trap rather than a bug. `set_cell` calls `begin_recording()` before `apply()`; the other eight public mutators (`remove_cell`, `put_cell`, `set_col_width`, `set_row_height`, `toggle_bold`, `set_number_format`, `set_alignment`, `set_bg_color`) do not. Calling one of those directly and then `undo()` reverts the *previous* recorded edit, not the one just made. Nothing ships broken: every `spreadsheet-ui` call site takes its own `data.snapshot()` first, checked one by one in `grid.rs`. But the asymmetry is invisible at the call site and the next caller will not know to snapshot. `only_set_cell_records_its_own_undo_step` states the current contract so a change to it is a deliberate decision rather than an accident. |
|||
| 83839ea0a3 |
test(spreadsheet): coverage for the UI controllers, and fix a 0% report
The coverage script measured the engine only, and it cherry-picked four
source files to report on, which flattered the number: 91.15% against a
hand-picked subset versus 88.97% for the whole of `src/`.
Rewritten to cover both crates honestly, with per-crate floors and a
listing of uncovered lines. Two bugs in the script itself:
- The ignore regex contained the work-directory name, so it excluded the
very sources being measured and reported a confident 0%. The work dir
also cannot live inside the repo, or Cargo treats the copied crates as
workspace members and refuses to build them.
- `llvm-cov show` filename headers carry no trailing colon, so the awk
matcher never fired and the uncovered-line listing was always empty.
`spreadsheet-ui/src/{grid,ui,workspace}.rs` and `src/bin/` are excluded:
the first three are `script_mod!` generated DSL and the last is desktop
startup, neither of which a unit test can reach.
UI controllers now measure 94.55%: `event_router.rs` 70.59% -> 97.96%,
`selection.rs` 80.65% -> 100%. UI tests 9 -> 17.
|