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%.
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).
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%.
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).
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%.
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%.
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.
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%.
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).
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).
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%.
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.
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.
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.
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.
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.
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.
The repo has a CI gate requiring full-length revs, added deliberately in
5e71457 with a comment explaining that an abbreviated rev resolves only
while no other object shares its prefix -- a property of the repository's
current object count, not a guarantee. Git's abbreviation length grows as
a repo grows, so a short pin silently becomes ambiguous, and an attacker
able to push to the fork can try to manufacture a colliding prefix.
That gate has been failing. 42 declarations across 34 crates used
abbreviated revs:
41x rev = "ecf5a572" (the current makepad pin)
1x rev = "5efe6e24c" (map/tests/makepad_test_app, left behind
by the ce0eaae bump)
Resolved both against the remote and rewrote them:
ecf5a572 -> ecf5a572ab62a1c1598909971f602f99083671cc
5efe6e24c -> 5efe6e24c9f732e9f11b783757f196f4f1c402b2
Verified this changes the LABEL and not the dependency: Cargo.lock holds
exactly one makepad commit id and zero references to the old one, so
nothing was silently upgraded. The stray makepad_test_app pin did move to
the current rev, which is the intent -- it pointed at a stale branch head.
Cargo.lock also picks up unrelated churn (brotli et al in,
makepad-android-state/jni-sys out). That staleness is PRE-EXISTING, not
caused by this change: confirmed by stashing every edit and running
`cargo metadata` on a pristine tree, which produces the identical diff.
Gate now passes:
$ grep -rn 'rev = ' --include=Cargo.toml . | grep -vE 'rev = "[0-9a-f]{40}"'
(no output)
86c9595 synced the fork to upstream/dev at abd70f4, which dropped three
fork-local optional dependencies from widgets/Cargo.toml and their
re-exports from lib.rs. They were fork additions, so the merge lost them.
Every Makepad UI target then failed to resolve:
package `nigig-pdf-makepad` depends on `makepad-widgets` with feature
`test` but `makepad-widgets` does not have that feature.
help: available features: default, serde
failed to select a version for `makepad-widgets`
The "available features" list is misleading: with no `test` feature on
widgets 2.0.0, cargo falls back to the stale old/widgets copy, which is
1.0.0 and offers only default and serde. Same fallback that produced the
bogus makepad-fonts-chinese-bold error in an earlier sync.
libs/makepad_test was never removed - only the manifest entries and the
re-export. The fork's ecf5a572 restores both. This bumps all 34 crates.
Verified against the real fork, not a local copy:
TEST_TARGET=pdf-ui 682 passing (was: failed to resolve)
TEST_TARGET=pdf 637 passing
Pin bump only: every hunk changes the rev and nothing else.
Updated makepad fork to include all latest APIs needed by map widget:
- pack_vector_vertices and VECTOR_PACKED_FLOATS_PER_VERTEX
- TileArchiveReader for MKMap archive support
- get_tile_decoded method on MbtilesReader
- set_trust_fill_winding and fill_fringe_into on Tessellator
- retain_queued method on TagThreadPool
- set_camera_delta method on DrawRotatedText
This resolves all compilation errors in the map widget code.
All 35 Cargo.toml pins move from d82756a to 5efe6e24c on the gitdab fork
(portallist base + makepad_test Android adb / standalone terminal wiring).
Lockfile regenerated; pdf crates compile against the new rev.