698 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
36c9c0b0d1 |
fix(cad): both Save buttons reported success when the write failed
Went looking for remaining defects before starting the viewport split and
found silent data loss in the two Save buttons.
cad_store::save_cad_script(&project.id, &source).ok();
self.view.label(..).set_text(cx, "Saved");
save_cad_state(&source, &solid).ok();
self.view.label(..).set_text(cx, "Saved with mesh");
Both discard a Result carrying a real io::Error and then report success
unconditionally. The user is told their work is safe when nothing was
written.
This is reachable, not theoretical. `cad_store::cad_projects_dir()` only
*logs* a `create_dir_all` failure and returns the path anyway, so the
following `fs::write` fails with a genuine error -- which was thrown
away. An unwritable data directory, a full disk or a permissions problem
all render as "Saved".
Save-As had a third silent failure on the same line of reasoning: if
`bake_parts_solid()` returned None the save never even ran, and it still
said "Saved with mesh".
Fixed via save_status_message(what, result), so the label is derived from
the outcome instead of assumed. Three distinct messages now: the caller's
success wording, "Save failed: <cause>" with the underlying error text
preserved, and a specific message for the no-mesh and no-active-project
cases, which were previously indistinguishable from success.
Also replaced the hand-rolled widget borrow in Save-As with
with_viewport, the helper added in 5.2.
Tests assert the two properties that matter: a failed save must not read
as success and must carry its cause through to the user, and a
successful save must use the caller's wording. Negative-tested.
CI gate added and verified in both directions: greps for a discarded
Result on a save/write/persist/store/export call. A dropped Result is
merely untidy in most places; on a save path it is data loss.
These were the only two instances -- the CAD module now has zero
discarded write Results.
685 lib + 154 integration, 0 failed. All seven gates pass.
|
||
| bea1fd884e |
chore: update makepad fork to include upstream map improvements
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-map / test (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
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
Updated all makepad dependencies from rev 2c5cd97 to 817d881 which includes: - Terrain hillshade landcover draping (drape.rs) - Route overlays, markers, and position puck (overlay.rs) - Map icon management system (icons.rs + 50 SVG icons) - 3D road elevation and seamless joins - Building shadow geometry and terrain shadows - Night themes and emissive roads - Water, grass, and shrub rendering - Optimized road geometry with 2D/3D mode transitions - i_overlay library for polygon boolean operations This brings nigig-map in sync with the latest makepad dev branch improvements. |
|||
|
|
fc2e555cf6 |
refactor(cad): collapse the four duplicated export prologues
export_floor_plan_pdf, export_3d_viewer, export_stl and export_svg each
opened with a byte-identical 23-line block: borrow the main viewport,
bail if it is missing, bail if it has no parts, then take the cached
scene, the shared mesh cache and the part count. Verified identical with
a regex over all four -- the only difference was the label prefix
("PDF", "3D", "STL", "SVG"), and even that was consistent within each
copy.
Now one method, export_scene_source(cx, kind), returning Option of the
triple and setting the status label itself. Four call sites, each two
lines.
This is the same duplication shape that hid the last three real bugs in
this module: nine copies concealed the properties-panel no-op, one
uninspected copy concealed the dead Extend tool, and nine keymap arms
concealed the KeyCode bindings. Nobody re-reads the fourth copy of a
23-line block. There is no bug hiding in these four -- I checked -- but
the next edit to one of them would only have landed in one.
The user-visible strings are the behaviour here, so they are pinned
rather than assumed: ExportBlocked + export_blocked_message hold the two
messages, and export_prologue_tests asserts all eight exact strings the
copies produced. A second test asserts the two reasons stay
distinguishable -- "no viewport" is a wiring fault, "no parts" is the
user having drawn nothing, and reporting one as the other sends someone
to debug the wrong thing.
Negative-tested: making NoViewport render the NoParts text fails both.
bake_parts deliberately keeps its own prologue. It needs a different
tuple (solid + script, not scene + cache + count), so folding it in
would mean a helper with an unused half.
683 lib + 154 integration, 0 failed. All six CI gates pass.
|
||
| 00f1dfbc12 |
fix(pdf): fuzz every target, and close two ADR 0004 gaps
Three defects found by auditing the ADR merge criteria against the code rather than against memory. 1. The scheduled fuzz job ran five hardcoded targets. Four had been added since and were never fuzzed: parse_revision_chain, decrypt, eval_function and parse_colorspace. eval_function is the sharpest of those - it executes PostScript taken verbatim from an untrusted file. The list now comes from `cargo fuzz list` and the job fails rather than passing vacuously if it comes back empty. `cargo fuzz list` reads the manifest, so a target file added without its [[bin]] entry would still be skipped silently. The engine job, which runs on every push, now checks the two agree. Both directions of that guard were exercised before committing. 2. ADR 0004 rule 3 promises an annotation whose appearance cannot be generated "keeps its original /AP and is reported as skipped". The keeping worked - to_dict clones the source dictionary - but nothing reported it: SaveReport only tracked skipped appearances for form fields. A caller who moved a stamp was never told its artwork still showed the old position. Adds SaveReport::annotation_appearances_skipped and appearance_is_generated, which enumerates the out-of-scope types explicitly so a new AnnotationType fails to compile until classified. 3. SetContents and SetFlags had no round-trip test. Both were implemented and unit-tested against the in-memory model, but neither was ever reparsed from written bytes - the assertion ADR 0004 calls central. New fixture annotations/stamp.pdf carries real /AP artwork for a Stamp (undrawable: must be preserved and reported) beside a Square (drawable: must not be reported), so the reporting cannot pass by reporting everything. The stamp test was mutation-checked: it fails when the reporting line is removed. ADR 0003 and 0004 merge criteria are now ticked. 0003's were genuine paperwork - every box traced to an existing named test. 0004's were not, and its ADR now records what was missing rather than implying it always worked. TEST_TARGET=pdf 447 -> 451 passing. rustfmt and clippy -D warnings clean. |
|||
| 49c8fd2e16 | style(spreadsheet): format updated formula engine | |||
| eb3edde51b | docs(valhalla): add deep architectural comparison of valhalla-rs vs makepad map navigation layer | |||
|
|
e7a201246c |
ci(cad): scope the source-scanning gates to .rs files
The "No build-time paths used at runtime" gate has been failing since the
commit that introduced it, and I did not notice because I verified the
gate's intent rather than running its exact shell pipeline.
It greps the CAD directory for env!("CARGO_MANIFEST_DIR") and filters out
lines starting with a // comment marker. ARCHITECTURE.md documents this
very rule, so it necessarily names the macro -- and a Markdown list item
is not a // comment, so it survived the filter. The gate failed on its
own documentation.
Both files were added in the same commit (
|
||
| ef1fd2c8db |
fix(map): remove orphaned doc comment causing compilation failure
Some checks failed
nigig-map / test (push) Has been cancelled
The orphaned doc comment at the end of the test module in style.rs caused 'expected item after doc comment' error, which prevented the entire style module from compiling. This made MapThemeStyle, CompiledMapTheme, default_light_theme, and default_dark_theme invisible to view.rs, causing 8 cascading compilation errors. |
|||
|
|
27ff51ca47 |
fix(doc): hit_test yields to table/node hit tests; pointer and IME runtime coverage
Some checks failed
doc-engine / consumer (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
ProjectionLayoutTree::hit_test's nearest-glyph fallback resolved any point to a glyph, so taps landing inside tables or advanced nodes never reached the widget's table_hit_test/node_hit_test branches — production cell taps silently moved the text caret instead of parking in cells. The fallback now returns None when the point is inside a table or node rect (those layers own their taps), while empty-space snapping beside text still places the caret. Close the "widget input, selection, mobile gestures" test gap with a draw-free Area::Rect stub harness (stub_hit_area): tests seed the input area a GPU frame normally fills and mirror the two platform pre-dispatch steps (first_mouse_button, committed key focus), so raw MouseDown/MouseMove and IME TextInput dispatch through event.hits against a real Cx — exactly like a running app. Covered end-to-end: hit-dispatch sanity, caret placement from a tap, shift-press selection extension, drag selection over glyphs, TextInput inserting at the caret and replacing an active selection, and a cell tap + TextInput whole-cell round trip. The harness also exposed the test fixture actor drifting from production's single "local" actor (cross-actor mid-run anchoring is deterministic but not chronological — now a documented doc-engine projection invariant); the runtime fixture now uses "local" for production fidelity. READMEs: doc runtime-tests section documents the stub harness and the two bugs it unearthed; roadmap item updated (only the renderer draw pass remains GPU/Studio-bound); doc-engine projection invariants gain the per-actor counter boundary bullet. 6 new tests, 675 -> 681. |
||
| 3213603e21 |
fix(pay): put a build marker in the tracker-only message
Reported four times, most recently with the detail that it appears at
runtime when tapping "Pay via M-Pesa" — not during a build.
That confirms the diagnosis rather than changing it. The reported string
was removed in
|
|||
|
|
652105dff6 |
feat(doc): touch cell-range selection via long-press and drag
Makes table merge reachable on touch devices, closing the last gap of the CRDT-native merge/split milestone (Shift+Arrow has no touch equivalent): - Long-press inside a table cell falls through the text glyph hit test into table_hit_test and starts a TableCellSelection with anchor = focus = the pressed cell (start_cell_range), clearing any text selection; long-press on text keeps selecting word atoms unchanged. - While the router idles in Selecting, the continued drag moves the focus cell (extend_cell_range_to) through the tap hit geometry; drags outside the anchor's table keep the focus and the caret tracks it. Text word selections share the Selecting state but carry no cell range, so their behavior is untouched. - Lifting the finger keeps the range for the workspace Merge button or Ctrl/Cmd+M (merge_selected_cells); Split already worked from a cell tap. No new draw code — the existing cell-range highlight renders the span. Tests: 3 new runtime suites on a real Cx (long-press entry, drag extension with caret tracking and full-grid normalization then merge consumption on the wire, out-of-table drag keeping focus, and the text word-selection regression guard) for 675 total in nigig-build. |
||
| e12a3bb5c4 |
fix(pay): reword the bulk gate message too
Third report of "Payment dispatch is unavailable in this build".
That exact string is gone from source as of
|
|||
| 3e77bf52b0 |
fix(pay): make the demo gate reachable and its message honest
Reported twice as a bug: "Payment dispatch is unavailable in this build;
no M-Pesa PIN was used".
The gate itself is correct — review items 0.1/0.3 require that a default
build cannot move money. Two things around it were not.
## 1. The obvious command did not work
$ cargo check -p nigig-pay --features demo
error: the package 'nigig-pay' does not contain this feature: demo
Only nigig-pay-ui declared the feature. Neither app crate had a [features]
section at all, so the only way in was --features nigig-pay-ui/demo, which
nobody would guess. Anyone following the natural path hit a hard error,
concluded the build was broken, and had no way forward.
nigig-pay and nigig-mpesa now forward it: demo = ["nigig-pay-ui/demo"].
A CI guard asserts both crates forward it and that both compile with it,
tested against a reverted manifest to confirm it fails.
## 2. The message read like a build error
"unavailable in this build" describes the binary, not the product, and
offers no next step. It now says what the app does and what to do:
This app tracks payments — it doesn't send them. Send in the M-Pesa app
or dial *334#, and it will appear here automatically. Your PIN was not
requested or stored.
The internal log strings that say "compile with --features demo" are left
alone: those are for developers reading logs, not users reading a sheet.
## 3. No build instructions existed
Review item 1.1 asks for them and the README had none. Added: default vs
demo build, the two caveats that matter (USSD has an Android backend only,
so demo does nothing useful on desktop; and demo must not ship because of
the unresolved Play-policy risk in ADR 0007), native library setup, and
the test commands.
## Validation
domain / storage / platform / mpesa harness pass
nigig-pay-ui: cargo test --lib pass (61)
cargo check -p {nigig-pay,nigig-mpesa} --features demo pass
cargo check -p {nigig-pay,nigig-mpesa} (default) pass
demo-forwarding guard: verified to fail on a reverted manifest pass
No change to what the gate permits. Enabling demo is still a deliberate
act with the Play-policy exposure recorded in ADR 0007.
|
|||
|
|
1f44da2b61 |
feat(doc): mobile Edit/Done interaction mode for CrdtDocEditor
Closes the last documented CrdtDocWorkspace toolbar parity gap by porting the legacy DocEditor's mobile interaction policy to the CRDT-native editor: - The widget carries the shared InteractionMode (Edit default, View) and latches mobile_mode_initialized on the first real touch, which drops the session to View; desktop sessions stay editable until actually touched. - Mobile View mode reduces the surface to the gesture router: passive caret taps (text and table cells), long-press word selection and handles keep working, KeyDown handling is fully gated, and the area-hit match is skipped so drags fall through to a parent ScrollYView. - The workspace toolbar gains the mobile-only Edit/Done AdaptiveView control; toggle_interaction_mode flips the mode, mirrors the button label, takes key focus entering Edit, hides the IME and resets in-flight gestures entering View. - Edit-mode touch taps request the IME at the tap point and draw_walk reasserts show_text_ime every frame while Edit holds key focus, anchored bottom-left of the live text or cell caret in clipped-area coordinates — the frame-driven reassert mobile backends require. Tests: 3 new runtime suites on a real Cx with real TouchUpdate/KeyDown events (first-touch drop + key gating incl. Ctrl+B, in-cell Backspace gating, Edit-mode tap placement with continued editing) for 672 total in nigig-build. Physical IME open/geometry remains device verification. |
||
|
|
d303484c93 |
feat(doc): CRDT-native cell range selection and table merge/split
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
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
Closes the roadmap's table merge command item on the CRDT editor surface with a rectangular cell-range selection model driven from the keyboard and the workspace toolbar: - ProjectionSession::cell_selection holds stable anchor/focus row and column ids; Shift+Arrow at a cell boundary starts the range and steps the focus cell on an active one, with table-edge clamping, caret follow, plain-action collapse, and undo-stale self-clearing. - table_cell_range normalizes the selection against the projected table; cell_selection_rects renders the highlight over visible cells only, so merged spans contribute one expanded anchor rect. - Ctrl/Cmd+M (merge_selected_cells, toolbar Merge) routes through the engine's MergeTableCells op; undo splits through the symmetric compensation. cell_range_mergeable rejects ranges overlapping an existing merge UI-side, where ambiguous nested spans belong. - Ctrl/Cmd+Shift+M (split_cell_at_cursor, toolbar Split) resolves the containing merge via merge_at_cell even from a covered cell and reveals the preserved hidden text; undo re-merges through RestoreTableMerge. - Workspace toolbar gains Merge/Split buttons with status-line guidance (the only split path on touch devices). Tests: 9 new (4 pure layout: range normalization/staleness, mergeable rules, merge_at_cell, covered-skip highlight rects; 5 runtime on a real Cx: shift-span + merge + undo, edge clamp, split + re-merge, plain collapse, overlap rejection) for 669 total in nigig-build. The doc-engine README gains the merge/split materialization invariant. |
||
|
|
478c7b33b0 |
feat(doc): CRDT-native in-cell table editing in CrdtDocEditor
Some checks failed
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (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
Taps park a TableCellCursor (stable row/column ids + char offset) inside cells; typing and Backspace/Delete edit cells through whole-cell SetTableCell replacements with symmetric undo. Arrows walk cell text in reading order, hop between cells across rows, and exit into the nearest text block in unified order at the table edges. Return inserts a row immediately below the cursor's row and follows with the caret. Block boundaries upgraded: backspace at a text start after a table enters its trailing cell, forward-delete at a text end before a table enters its first cell instead of merging structure. Style toggles stay text-only. The cell caret draws between rendered characters using the renderer's shared 6px inset / 7px-per-char advance, and stale cursors clear on structural undos. Also fixed while wiring the paths: pointer hit tests and layout-space decorations (selection, handles, carets) now account for the widget origin, so taps and visuals agree at any dock position. Runtime integration tests cover in-cell editing with undo restore, arrow traversal/exits/clamping, and return-inserts-row-below; layout unit tests cover the char-safe edit helpers, cursor resolution and clamping, caret geometry, neighbor wrapping, and neighbor_text_block. |
||
|
|
9a167f4df3 |
fix(doc-engine): honor after anchors when ordering table rows and columns
InsertTableRow/InsertTableColumn carried an after anchor on the wire but materialization ignored it, ordering the row/column vectors by op id only; an 'insert below X' could land anywhere once ids diverged. Rows and columns now materialize over the anchor chain with RGA-style sibling order (counter descending, actor ascending), matching text atoms, so a newer insert below an existing row renders right after it and peers converge on the same grid order. |
||
| 729163dfe1 |
fix(map): move theme rules from DSL to pure Rust to eliminate frozen-vec errors
Some checks failed
nigig-map / test (push) Has been cancelled
The Makepad VM freezes children vecs in MapThemeStyle after initial DSL evaluation. On re-evaluation (view switch, theme change), pushing new MapFillRule/MapRoadRule children triggers 'cannot push to frozen vec' errors, leaving the compiled theme empty (0 rendered features). Fix: Strip all rule definitions from the DSL blocks in view.rs and build complete CompiledMapTheme structs in pure Rust via default_light_theme() and default_dark_theme() functions in style.rs. - view.rs: Remove 80+ DSL rule entries, call Rust theme builders - style.rs: Add theme builder functions with all fill/road/waterway/rail rules - Fixes all 86 'frozen vec' errors per startup - Fixes 0 rendered features on all map tiles - Preserves identical visual output (same colors and widths) |
|||
| b5e38825fa |
fix(pay): validate money at the persistence boundary (B6)
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
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
B6 is the last unfinished P0 on the review's priority table. ## Why the boundary and not the format The obvious reading of B6 is "change f64 to Money on disk", which is a data migration and belongs with the SQLite move. But measuring first shows the stored representation is not where the damage is: - The f64 round trip is exact. 1500.0, 1500.5, 0.1+0.2, 1e20 and 12345678.995 all write and re-read bit-identically. - The read path is the damage. parts[2].parse().unwrap_or(0.0) turned any unreadable amount into a confident KSh 0 row. - And NaN gets in. "NaN" and "inf" both parse as f64 and the writer emits them back verbatim, so they survive a round trip. One corrupt SMS then poisons every total it enters, permanently — once a NaN is in a sum, every comparison against that sum is false. ## What changed validate_money refuses three classes, on parse, on load and on save: non-finite; negative (direction lives in TransactionType, so a negative amount is a contradiction); and beyond 2^53, where f64 can no longer represent consecutive shillings. A row whose amount cannot be trusted is skipped with a log line rather than zeroed. Dropping a row is visible and recoverable by rescanning the inbox; a silent KSh 0 is neither. balance and cost are optional context, so they degrade to None rather than discarding the row, but can no longer be NaN. The writer refuses to persist an invalid amount, which is what stops a poisoned value becoming permanent. Smaller parse fix: "Ksh ,5" used to read as 5. A separator before any digit means the text is not the expected shape, and guessing is worse than declining. ## The parser had no tests parser.rs — the file deciding what every observed amount is — had zero tests. It now has 13: validation, extraction, end-to-end parsing, and unicode/NUL bodies that must not panic on a byte-index slice. M-Pesa harness: 7 -> 24 tests. ## Verifying the tests can fail validate_money was reduced to Some(value) and the harness re-run: 4 tests failed. A guard that cannot fail is decoration. ## Validation mpesa harness: 24 tests pass domain : 137 tests --locked, fmt, clippy -D warnings, bench pass storage : 36 + 41 sqlcipher --locked, fmt, clippy pass platform: 56 + 64 ussd --locked, fmt, clippy, mock guard pass nigig-pay-ui: cargo test --lib pass (61) nigig-pay-ui / nigig-pay / nigig-mpesa / nigig-core: check pass authorization / batch / settlement-tick guards pass defect injection: 4 tests fail with validation removed pass ## What B6 still leaves open MpesaTransaction::amount is still f64 on disk. That is deliberate and now bounded: nothing untrustworthy can enter or leave the store, so the remaining exposure is precision within the validated range, which for whole-shilling amounts under 2^53 is none. Converting the field means rewriting the PSV format and migrating existing files. ADR 0002 records B6 as partially addressed with the migration deferred to the SQLite move. |
|||
|
|
0397f25c75 |
test(doc): CrdtDocEditor runtime integration tests on a real Cx
Drive the production widget end-to-end in lib tests: instances are built through the registry's ScriptNew::script_new factory (bare ScriptVm with unit host/std), the engine via set_engine, and real KeyDown events with real KeyEvent/KeyModifiers payloads through Widget::handle_event. Covered: caret anchoring from an uncursored editor, arrow stepping, shift selection extension, Ctrl+B bolding across the selection, Enter split at the caret with caret following the trailing half, Ctrl+Z merge, and Backspace merging an empty split tail with the previous block. The #[makepad_test] Studio harness was evaluated and rejected for this milestone: it builds and launches the full app through the StudioHub buildbox, and the repo's only examples (map tests/ui.rs and makepad_visual_tests.rs) target aspirational APIs that do not compile. |
||
|
|
83f4bae485 | fix(ui): remove inline ids assignments since ids inside prototype override instances are unsupported | ||
|
|
496bc1c29e |
feat(doc): wire application navigation to CrdtDocWorkspace
Consolidate navigation onto a single CRDT docs destination now that the DSL switch made the runtime-test tab an exact duplicate: drop the 'Docs CRDT' dock tab, its crdt_docs_content view, the sidebar's 'Documents CRDT Test' button and its select_tab handler. The desktop 'Docs' tab and 'Documents' sidebar button land on CrdtDocWorkspace, and mobile's workspace drawer resolves 'Documents' to the CrdtDocWorkspace doc_page through the new pure workspace_page_id function, pinned by unit tests (destination, label table, page distinctness, CAD fallback). |
||
|
|
b4cb497066 |
fix(ui): remove stray closing braces after CategoryDropdown definition
Three orphan closers left over from the CategoryDropdown/AddRoomModal restructure broke the script_mod block and with it the nigig-build build at HEAD; AddRoomModal now follows CategoryDropdown at DSL top level. |
||
| cf878d9e3e |
feat(pay): coordinator entry point for an externally granted authorization
The API change tranche 10 scoped, in REVIEWS/adr/0007. ## dispatch_with_authorization Tranche 10 established that Android cannot implement BiometricAuthorizer without reopening defect S2, and built AuthorizationAttempt as the replacement. It named the remaining work: a coordinator entry point taking an already-granted attempt instead of calling biometric.authenticate(). coordinator.dispatch_with_authorization(&id, &mut attempt) It does not prompt, does not wait, and does not consult the injected BiometricAuthorizer at all. That is asserted rather than assumed: one test injects an authorizer reporting no hardware that also errors, and shows a granted attempt still dispatches. If the coordinator ever fell back to it, that test fails. Three refusals, each with a test that fails when the gate is removed: - An unanswered prompt does not dispatch. PromptShown plus SensorEngaged is not consent — the exact shape the Android adapter would produce. - A grant belongs to one payment. A foreign grant is refused before anything else, so a stray callback cannot even fail the payment on screen; the victim intent stays in AwaitingUserAuthorization rather than being transitioned to Failed by a stranger. - A grant is spent on use. One authorization, one dispatch (B3). Denied, still-prompting and already-spent attempts all fail the intent closed and never reach the gateway. ## Verifying the tests can fail The gate was removed (if !attempt.consume() -> if false) and the suite re-run: 5 tests failed. A fail-closed test that passes against fail-open code is worthless, so this is the check that matters. Worth recording: a_grant_cannot_dispatch_twice still passed with the gate removed, because the existing duplicate-dispatch budget caught it independently. Two unrelated mechanisms refuse the second dispatch. That is defence in depth working, and the reason that test is not sufficient evidence on its own. ## Validation domain : 137 tests --locked, fmt, clippy -D warnings, bench pass storage : 36 + 41 sqlcipher --locked, fmt, clippy pass platform: 56 + 64 ussd --locked, fmt, clippy, mock guard pass nigig-pay-ui: cargo test --lib pass (61) nigig-pay-ui / nigig-pay / nigig-mpesa / nigig-core: check pass authorization / batch / settlement-tick guards pass fail-open injection: 5 tests fail with the gate removed pass Domain tests 129 -> 137. ## What remains The authorization half is done and verified. PayFlowHandler still owns the USSD session lifecycle, the pending-store writes that shadow the coordinator's repository, and the bulk queue plumbing. Moving those makes the coordinator own the gateway session, which changes who cancels on teardown and who observes an out-of-order callback — ADR 0007's device matrix, which cannot be exercised here. |
|||
|
|
a09f989fbe | fix(ui): close brackets correctly for RoomTypePicker definition | ||
|
|
a02b486d81 |
fix(map): eliminate frozen vec errors by building themes in Rust
Some checks failed
nigig-map / test (push) Has been cancelled
Move all theme rules (fill, road, waterway, railway) from DSL to pure Rust functions. This eliminates the 'cannot push to frozen vec' errors that occurred when MapThemeStyle children were pushed to frozen objects on re-evaluation. Changes: - Remove all MapFillRule/MapRoadRule/etc from view.rs DSL - Add default_light_theme() and default_dark_theme() in style.rs - These functions build complete CompiledMapTheme with all rules - Simplifies view.rs DSL to only set background/status_text/label This fix ensures themes are properly initialized without triggering the Makepad script VM's frozen vec protection, allowing map tiles to render with the correct styling rules. |
||
|
|
a7d422a0ac |
feat(doc): switch workspace DSL to CrdtDocWorkspace with toolbar parity
Both active workspace DSL sites (desktop dock docs_workspace and mobile
doc_page m_doc_content) now instantiate CrdtDocWorkspace instead of the
legacy DocWorkspace; the classic widget stays registered as fallback.
CrdtDocWorkspace graduates from the runtime-test shell to the full
workspace surface: Open/Save/SaveAs round-trip the shared #MP_CRDT_V1
wire via projection_session::{crdt_save_wire, crdt_engine_from_saved},
Undo/Redo and B/I/U share the editor's keyboard-driven engine paths,
alignment buttons route the legacy DocAlign wire strings, and
+Table/+Img/+Div insert through new CrdtDocEditor helpers with legacy
anchored-after-caret semantics. Stats count words/chars from the
projection (new projected_stats helper). The mobile Edit/Done IME
toggle stays legacy-only until the native widget owns an IME mode.
|
||
| cbafa92269 |
fix(pay): close the fail-open biometric the coordinator migration would open
Review defect S2, plus an amendment to ADR 0007. ## Attempting the migration found a defect in the plan The stated next step was replacing PayFlowHandler with PaymentCoordinator, which means writing a BiometricAuthorizer adapter for Android. It cannot be written correctly, and why is the substance of this change. The trait contract is blocking: "authenticate must be a blocking call that waits for user action. Returns Ok(()) on success." robius_fingerprinting::authenticate does not do that. Reading through sys/android/prompt.rs: it calls the Java authenticate static method and returns Ok(()) as soon as the prompt is on screen. The user's answer arrives later through next_event(). So an adapter can either return Ok(()) when the prompt opens — and authorize_and_dispatch then sends money before the user has touched the sensor, which is defect S2 reintroduced through the type system — or block, and deadlock the thread that must pump the callback. Had the migration been done without noticing, the result would have been a correct-looking refactor that silently reopened the review's most serious security finding. ## AuthorizationAttempt Authorization is a state machine, not a function call: Requested -> Prompting -> Granted | Denied, advanced by inbound signals. One rule, enforced by the type: only an explicit success grants dispatch. - A displayed prompt does not authorise. A touched sensor does not authorise. A non-match keeps the prompt up and stays retryable. - A grant is bound to one intent, so a callback for an abandoned payment cannot authorise the current one. - A grant is spent on use, so one fingerprint cannot authorise two dispatches (B3), and a replayed success cannot re-arm it. - A late success after a cancel is ignored, not resurrecting the payment. - Backgrounding mid-prompt denies; it never silently allows. dispatch_ussd now consumes a grant before dispatching and refuses without one. auth == None means no biometric gate was configured, which is deliberately distinct from an ungranted one. Both cancel paths abandon the grant. A CI guard asserts the gate exists and was tested with the check disabled to confirm it fails. The trait keeps its blocking contract for synchronous authorizers and test doubles, and now documents that Android must not use it. ## Validation domain : 129 tests --locked, fmt, clippy -D warnings, bench pass storage : 36 + 41 sqlcipher --locked, fmt, clippy pass platform: 56 + 64 ussd --locked, fmt, clippy, mock guard pass nigig-pay-ui: cargo test --lib pass (61) nigig-pay-ui / nigig-pay / nigig-mpesa / nigig-core: check pass authorization guard: verified to fail with the gate removed pass batch-counter and settlement-tick guards: still passing pass Domain tests 117 -> 129. ## Where the migration stands The blocker is no longer unknown. PaymentCoordinator needs an authorization path that does not assume a blocking authorizer, and AuthorizationAttempt is that path, built and tested. What remains is a coordinator entry point taking an already-granted attempt instead of calling biometric.authenticate() itself, then moving USSD session ownership across. That is an API change that should be designed against ADR 0007's device matrix — permission denial, cancellation, backgrounding, app restart, out-of-order callbacks — none of which can be exercised here. |
|||
|
|
06d38056b6 | fix(ui): use correct nested override syntax for metric values | ||
|
|
0b44064bac | fix(ui): access inherited id paths on metrics to avoid scope errors | ||
|
|
24d9f9269e |
feat(doc): CRDT-native keyboard editing with block split/merge ops
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
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
CrdtDocEditor now handles the desktop keyboard surface natively through doc-engine operations: Ctrl/Cmd+Z/Shift+Z undo-redo via CrdtHistory, Ctrl/Cmd+B/I/U selection style toggles, arrow-key caret moves with shift-extend across block boundaries via the step_glyph stream, selection-aware Backspace/Delete with block-boundary merges, Return splitting paragraphs or appending table rows, and TextInput that replaces the active selection at the session caret block. Engine additions: Operation::SplitBlock/MergeBlocks with symmetric Compensation pairs; materialization keeps a split parent's trailing runs as a synthetic child spliced after the parent so peers converge without a new InsertBlock op; merge_runs coalesces equal style runs. |
||
| b4ad9348d3 |
fix(pay): make batch progress bounded and derived, not counted
Review items 6.6 / U7, and B3 at batch scope. ## A progress bar that could read 3/2 While working toward retiring the thread_local handler, the bulk accounting turned out to hold a defect worth fixing on its own. bulk_done was a bare usize incremented at eight independent match arms, none bounded, and bulk_current_idx added one more while an item was in flight. Nothing tied the counter to the batch contents. The USSD pump does not guarantee one terminal event per item — an Error can be followed by a SessionEnded for the same session, and each arm increments separately: one item, two terminal events -> bulk_done=2/2 BulkItemDispatched reports 3/2 <-- exceeds total That was reproduced before changing any code, so this answers a demonstrated defect rather than a suspected one. ## PaymentBatch Progress is now a function of the batch contents, not of how many code paths ran. Each item carries exactly one BatchItemOutcome and settle() refuses a second instead of counting it, which makes finished > total unrepresentable rather than merely unlikely. Every former bulk_done += 1 routes through one settle_bulk_item helper. A CI guard rejects direct increments and was tested against the reintroduced bug to confirm it fails, same as the tick guard in tranche 8. Three properties follow from putting this somewhere testable: - finished is not succeeded. confirmed counts only provider confirmations, so a batch that ended with unknown outcomes reports "Batch finished — N confirmed, M awaiting confirmation. Do not re-send" and never carries a tick. Only all-confirmed may be ticked. - A pending item is never offered for re-run. safe_to_recreate returns only NotSent and Rejected; replaying a possibly-settled item is B3. - Cancelling does not rewrite history. cancel_remaining marks only items that never got an outcome, because cancelling a batch does not recall a request the provider may already hold. A stray callback for an unknown item id is refused rather than counted — the batch-level counterpart of ADR 0007's correlation rule. ## Validation domain : 117 tests --locked, fmt, clippy -D warnings, bench pass storage : 36 + 41 sqlcipher --locked, fmt, clippy pass platform: 56 + 64 ussd --locked, fmt, clippy, mock guard pass nigig-pay-ui: cargo test --lib pass (61) nigig-pay-ui / nigig-pay / nigig-mpesa / nigig-core: check pass batch-counter guard: verified to fail on the reintroduced bug pass Domain tests 104 -> 117. ## On the thread_local, again Still there; this change did not remove it. What changed is that the state carrying correctness risk — the batch accounting — no longer lives in it. PayFlowHandler now delegates every outcome to PaymentBatch, so the thread-local holds queue plumbing and platform handles rather than the numbers a user is shown. That is a smaller and more honest claim than "Phase 6 wiring complete". Replacing PayFlowHandler with PaymentCoordinator moves ownership of the USSD session and the biometric prompt, and belongs in its own change with ADR 0007's device matrix, not folded into one that also touches accounting. |
|||
|
|
8e06b7ed30 | fix(ui): define CategoryDropdown before AddRoomModal in CostEstimateScreen | ||
| a6e8f4c04e |
fix(pay): remove the last false-success labels, and fix B6 where it is real
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
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
Continues Phase 6 (REVIEWS/adr/0008) and addresses defect B6.
## Two labels were still claiming settlement
Tranche 7 noted the sheet's remaining status strings were composed
inline. Two of them were not untidiness, they were the Phase 6 violation
still shipping:
ObservedEvidence -> "✓ SMS received — ref: {code} …"
BulkItemObserved -> "✓ {current}/{total} sent"
A tick is a settlement claim. The first put one beside an SMS, which
proves nothing and can be forged. The second counted dispatches as
confirmations, so five unknown outcomes rendered as "✓ 5/5 sent".
FlowStage now owns the vocabulary next to the presentation types, with
one rule: a tick requires a provider confirmation. Evidence reads
"Message received (ref X) — not yet confirmed. Do not send again until
this is resolved." A finished batch reads "Batch finished — N sent,
awaiting confirmation", which is what actually happened.
Nine call sites derive their text instead of composing it. The two
remaining ✓ in that file are fingerprint-sensor feedback — a real local
event, not a settlement claim.
A CI guard backs this, and it was tested against the old code to confirm
it fails when the regression returns. A guard that cannot fail is
decoration.
## B6, scoped to where it is real
Each f64 site was measured before changing it, because "replace f64
everywhere" is easy to claim and easy to get wrong.
format_amount was checked exhaustively from 0.00 to 2000.00 against an
exact integer reference: zero mismatches. It also rounds 1234.567 and
2.675 correctly. It is not the defect and rewriting it would be churn.
Accumulation is the defect. 10,000 realistic amounts showed no visible
drift, but f64 silently discards additions past 2^53 and offers no
overflow signal at all, so a corrupt record yields a confident wrong
total with nothing to indicate it.
So the fix went to the accumulators. update_summary in nigig-mpesa and
nigig-pay — exact duplicates of each other, review item A5 — now build a
PeriodSummary from exact minor units with checked arithmetic, and render
"—" rather than a wrapped figure when overflowed is set. The conversion
from stored f64 is explicit and validated.
store.rs::totals() has no callers; it is now #[deprecated] with the
reason rather than deleted, since nigig-core has no domain dependency and
that is a separate change.
Two regression tests state it executably: f64 silently swallows 2^53 + 1
while the exact path keeps both entries, and an impossible total is
reported rather than shown.
## Validation
domain : 104 tests --locked, fmt, clippy -D warnings, bench pass
storage : 36 + 41 sqlcipher --locked, fmt, clippy pass
platform: 56 + 64 ussd --locked, fmt, clippy, mock guard pass
nigig-pay-ui: cargo test --lib pass (61)
nigig-pay-ui / nigig-pay / nigig-mpesa / nigig-core: check pass
settlement-tick guard: verified to fail on the old code pass
Domain tests 95 -> 104.
## Not claimed complete
The thread_local PayFlowHandler still exists. The status vocabulary is now
domain-owned, which was the part carrying correctness risk, but the
widgets still do not talk to PaymentCoordinator. That remains the Phase 6
architectural item. The parser's stored f64 amount/balance/cost are
unchanged: that is a migration, not an edit, and belongs with SQLite.
|
|||
|
|
06afa336b2 |
feat(doc): CRDT-native mobile long-press and selection handles
- word_atom_range mirrors the legacy word_bounds whitespace-pivot semantics and returns a word's first/last atoms for SelectWord - selection_handles normalizes anchor/focus into document order and emits 12px handle rects with a 6px touch-slop hit test; collapsed selections (caret) produce no handles - CrdtDocEditor drives the shared MobileGestureRouter: TouchUpdate Start grabs visible handles or arms long-press on the frame clock, SelectWord applies the atom word range, handle drags move selection endpoints, drags past threshold stay unclaimed for ScrollYView, and short taps end as passive cursor moves without the IME - synthesized finger events are ignored once a real touch sequence is seen; desktop mouse/IME behavior unchanged - draw_selection_handle live field (default #x1f73e6) + 5 geometry tests |
||
| 750d856668 |
feat(pay): Phase 6 truthful payment states, and run UI tests in CI
Phase 6 and item 7.3 of REVIEWS/NIGIG_PAY_CONSOLIDATED_REVIEW.md. Rationale in REVIEWS/adr/0008. ## The rule, made into a type Phase 6's exit criterion is that the UI cannot call a transaction successful without trusted confirmation, or failed without known rejection. The old code violated it structurally: the sheet set its status inline in about a dozen places, each from whatever local signal was nearest, so there was nowhere to put the rule. The worst instance is U4. A payment whose confirmation SMS had not arrived in five minutes was marked Failed, the sheet closed, and the user saw "✗ Payment failed" — with a retry button — while the money was gone. payment_view_state.rs makes the rule a type. PaymentPresentation has no Success variant reachable from untrusted evidence and no Failed variant reachable from a missing SMS. VerificationExpired, Unknown and UnknownNeedsReconciliation all present as PendingConfirmation: not success, not failure, and no retry offered. Two defence-in-depth rows: Confirmed without a provider reference reads as unknown rather than settled, and an unclassified failure is not evidence nothing was sent. 6.2: ConfirmationSummary makes every required term a mandatory field, so a confirmation missing the fee or total is not constructible. It is built from the same quote that gets dispatched. material_digest() binds consent to the exact terms shown and is re-checked on OK — if anything material moved in between, the authorisation is void and the screen is shown again. The modal previously showed only recipient and amount. 6.3: must_stay_open() keeps pending payments visible, and the status line begins with the exact required wording, asserted by a test. 6.5: evidence rows are exposed and labelled untrusted; pending payments offer receipt, problem-reporting and data-deletion actions. ## Three defects found by writing the tests 1. Bulk quotes silently under-charged. compute_bulk_costs used filter_map over the fee lookup, so a contact outside the tariff was dropped from the fee total and the user was quoted less than they would pay. 2. The batch total could overflow — .sum() panics in debug, wraps in release. A wrapped total is a quote for the wrong amount. 3. The cost preview showed unknown fees as free via unwrap_or(0). All three are B5/B6 territory. Item 3 is B5 resurfacing in the preview path after tranche 1 fixed it in the dispatch path: fixing a defect at one call site is not the same as fixing the defect. ## A pre-existing failing test, diagnosed rather than deleted money::tests::ksh_rounds_to_nearest_cent asserted format_ksh(1.005) == "1.01" and had been failing on every run — confirmed pre-existing by stashing this work and re-running clean. The expectation is impossible, not the formatter wrong: 1.005 has no binary representation, the nearest f64 is 1.00499999999999989..., so the correctly rounded result is 1.00. This is defect B6 at its smallest. The test now says so, with a companion showing Money handling it exactly. Deleting it would have hidden a live argument for finishing B6. ## 7.3 UI tests in CI The NIGIG_TEST_PAY gate was already gone; what blocked CI was the Makepad link step. tools/makepad-native-libs.sh listed the libraries needed to compile but not libasound2-dev, libpulse-dev and libssl-dev, which are needed to link a test binary — cargo check succeeds and then "unable to find library -lasound" appears much later. The helper now installs and checks them, and a payment-ui-tests job runs the UI tests plus cargo check on nigig-pay-ui, nigig-pay and nigig-mpesa. ## Validation domain : 95 tests --locked, fmt, clippy -D warnings, bench pass storage : 36 + 41 sqlcipher --locked, fmt, clippy pass platform: 56 + 64 ussd --locked, fmt, clippy, mock guard pass nigig-pay-ui: cargo test --lib pass (61) nigig-pay-ui / nigig-pay / nigig-mpesa: cargo check pass Domain 75 -> 95 tests. UI 55 -> 61, with the long-standing failure fixed. Every new CI step was run locally before commit. ## Not claimed complete Phase 6 wiring is partial. PaymentViewState exists and is tested, and the confirmation path uses ConfirmationSummary, but the sheet's remaining status strings are still set inline and the thread_local PayFlowHandler still exists. The type makes that migration mechanical; it does not perform it. 6.6 bulk stays demo-gated pending ADR 0007's unresolved 5.2. |
|||
| bb8625812e |
fix(map): initialize style types before view to fix MapThemeStyle registration
Some checks failed
nigig-map / test (push) Has been cancelled
Register style::script_mod before view::script_mod so MapThemeStyle and related types are available when NigigMapView's DSL is parsed. Fixes: - type mismatch for property style_light/style_dark - 0 rendered features (empty theme rules) |
|||
| d0f5e74435 |
feat(pay): Phase 5 platform gateway boundary, and verify Phase 4
Some checks failed
nigig-map / test (push) Has been cancelled
Payment domain, storage and platform / isolated-payment-tests (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
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / engine (push) Has been cancelled
Phase 5 of REVIEWS/NIGIG_PAY_CONSOLIDATED_REVIEW.md. Design and the one
item engineering cannot close are in REVIEWS/adr/0007.
## Phase 4 is now verified, not just written
Tranche 5 implemented the draw_walk fixes and said plainly that the
nigig-pay widget edits were unbuilt. That blocker was environmental:
installing the packages tools/makepad-native-libs.sh already lists makes
the UI graph compile. Both commands the status doc listed as required
now pass:
cargo check -p nigig-pay pass
cargo check -p nigig-pay-ui pass
No code changed for this; the claim is now evidence rather than assertion.
## Phase 5: new nigig-pay-platform crate
ADR 0002 reserved this crate and marked it "not yet created".
5.1 One crate owns the seam. It is the only crate in the payment stack
that may name a platform SDK. CI enforces both directions: payment crates
may not import Makepad, and domain/storage may not import jni or the
robius platform crates.
5.3 Correlation is mandatory and single-flight. SessionRegistry admits an
event as Accepted, Duplicate or Ignored; PlatformEvent cannot be built
without a CorrelationId. Closed session ids are retired permanently, so
an abandoned session's confirmation cannot settle the payment that
replaced it. There is a test named after exactly that scenario.
2.8/5.3 Progress decides retry safety, not error kind. classify_failure
takes the failure and the DispatchProgress reached before it, and
progress is the authority. The same TemporarilyUnavailable is safely
retryable before the dial and ambiguous once the menu is being driven —
the distinction the old code could not make, which is defect B3's
mechanism. A property test asserts across the whole failure space that
nothing which may have reached the provider authorises a fresh attempt.
5.4 Fakes cannot ship. MockGateway is cfg-gated, is a compile_error! in a
release build unless allow-mock-in-release is named explicitly, and
stamps every session id with MOCK-. CI asserts the release build fails.
5.5 No unsafe, no PIN. The crate is #![forbid(unsafe_code)] so the JNI
surface stays in robius-ussd. UssdGateway is !Send/!Sync by construction,
making the main-thread requirement a compile error. The adapter leaves
the pin field empty and a test asserts it.
5.6 The web claim is withdrawn. No browser API can drive USSD and a
Daraja credential must never reach a browser, so WebGateway refuses every
call and maps to Fatal — "never sent" — which owes no reconciliation.
7.5 Adversarial SMS corpus. StrictMpesaSms is the payment-boundary
reader, deliberately separate from nigig-core's permissive tracker parser
(ADR 0007 explains why this is not the duplication ADR 0002 forbids). It
requires an exact 10-char code, exact sender-ID match so MPESA-REFUNDS
and FAKE-MPESA are refused, rejects fractional shillings instead of
rounding, and caps body length. Corpus covers spoofing, forged code
shapes, out-of-range amounts, unicode and NUL injection, and replay. The
closing test asserts the honest limit: a well-crafted forgery is still
only evidence, because the output type has no settled state to reach.
## 5.2 is not done and is not closeable here
The AccessibilityService Play-policy review is a business decision. ADR
0007 records it as blocking, states the termination exposure, and names
what must happen before the rail is enabled. USSD dispatch stays behind
the default-off demo feature. If the review fails, ADR 0001's
tracker/launcher position applies and only the dispatch adapter is lost.
## Validation
platform: 56 tests, 64 with --features ussd, fmt, clippy -D warnings
(both feature sets), cargo-deny, mock-in-release guard
asserted to fail pass
domain: 75 tests --locked pass
storage: 36 + 41 tests --locked, incl. sqlcipher pass
nigig-pay, nigig-pay-ui: cargo check pass
cargo-deny reports advisories/bans/licenses/sources ok. The isolated
runner gained a `platform` target and it runs in CI on every push that
touches the crate.
|
|||
|
|
7bedc677a6 |
feat(doc): render CRDT-native advanced nodes in CrdtDocEditor
Some checks failed
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (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
- layout_projection walks the unified DocumentProjection::order so advanced nodes interleave with paragraphs and tables at their anchor positions; block_origins are assigned by index for both paths, with a blocks-only fallback for projections assembled without the op log - projected_node_metrics mirrors legacy AdvancedLayout heights, labels and interactivity per kind (unknown kinds follow the bridge's EmbeddedWidget mapping: 'Widget: <kind>') - ProjectionRenderer::draw_node_projection reuses the legacy draw_advanced_blocks colors; dividers collapse to a centered 1px line; new draw_node_fill/draw_node_border live fields on CrdtDocEditor - fix(doc-engine): the after-chain was block-only, so any block anchored after an advanced node was unreachable during materialization and vanished from the projection; nodes now participate as chain connectors - tests: unified-order interleaving, node metrics, node hit test, mixed table/node stacking, order-less fallback (nigig-build); node-anchored block materialization and mixed sibling ordering (doc-engine) |
||
|
|
5ec5270015 | fix(ui): use valid syntax for assigning ids to metric values | ||
|
|
33dbece7d8 |
fix(ui): use valid syntax for assigning ids to metric values
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
Payment domain and storage / isolated-payment-tests (push) Has been cancelled
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
|
||
|
|
7143b3e798 |
feat(doc): render CRDT-native tables in CrdtDocEditor
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
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
- projection_layout builds ProjectedTableLayout geometry (fixed 160x28 cells matching the legacy bridge width, merge spans with covered-cell flags) and per-block origins so blocks after a table clear it instead of using the fixed line step - ProjectionRenderer draws the table grid through a new draw_table_border live field and consumes layout block origins for text placement - table_hit_test maps points to merge-anchor cells for future cell editing - doc-engine: split the cramped style-patch line clippy flagged as possible_missing_else; the crate is clippy-clean again - README: tick verified CRDT bridge boxes (dependency, wiring, selected replacement/formatting/table/toolbar migration, font size/color migration) and document the table milestone - CI: add doc-engine workflow (engine tests + clippy -D warnings + nigig-build consumer check/test) and trigger nigig-build CI on doc changes |
||
| 7d21532ebf |
feat(pdf): real colour spaces, ICC profiles and PDF functions
Phase 8 #4 of REVIEWS/DART_PDF_VS_MAKEPAD_PDF_GAP_ANALYSIS.md ("Advanced colour"). Design and merge criteria in REVIEWS/adr/0006-pdf-advanced-color.md. The interpreter tracked only the *name* of the active colour space and then passed sc/scn operands to the device as if they were already RGBA. Every non-device space therefore rendered a confident wrong colour with no error: /Spot cs 1.0 scn full tint of a spot ink -> pure red /Idx cs 3 scn palette entry 3 -> near-black /Lab cs 50 0 0 scn mid gray -> white (clamped) /DevN cs (5 inks) five colorants -> inks 5+ discarded /ICCBased profile-defined colour -> profile discarded DeviceCMYK also used the additive 1-c-k conversion, which crushes any colour printed over black. Three new modules in pdf-graphics: - function.rs PDF functions, all four types. Type 4 runs on a bounded interpreter: depth 32, 32768 tokens, stack 100, 100000 steps, and an unknown operator is an error rather than a no-op that would leave a plausible wrong colour. - icc.rs ICC matrix/TRC and gray kTRC profiles, applied exactly. LUT-class profiles are reported as such and the caller falls back to /Alternate; they are never pretended to be matrix profiles. - colorspace.rs All eleven families, converting through XYZ with Bradford adaptation and a real sRGB transfer function. Wiring: - PdfDevice gains set_stroke_components/set_fill_components, so SC/SCN reach the device as components of the active space instead of being read positionally as RGBA. - cs/CS now resets to the space's initial colour (table 74), which is why golden/colors.txt gains a line. - PdfPage::color_spaces carries /Resources /ColorSpace fully dereferenced with streams decoded; a half-resolved space would make every ICC profile, palette and type 0/4 transform silently fall back. - A space that cannot be resolved keeps the previous colour and records a typed ColorError. No colour is invented, and no error is swallowed. Tests: 12 corpus fixtures under tests/corpus/color/, 14 acceptance tests in pdf-document/tests/color.rs asserting numeric RGB (the broken code produced a colour for every one of these; only the value was wrong), plus unit tests per function type and per curve type. Two fuzz targets added: eval_function and parse_colorspace. TEST_TARGET=pdf 387 -> 447 passing, TEST_TARGET=pdf-ui 431 -> 491. rustfmt and clippy -D warnings clean. |
|||
| 08d3e9a7fb |
fix(map): increase MAX_ELEMENTS_PER_TILE to 250k and add CI workflow
Some checks failed
nigig-map / test (push) Has been cancelled
- Increased MAX_ELEMENTS_PER_TILE from 100,000 to 250,000 to handle Kenya MBTiles that contain 101k-140k elements per tile - Updated security limit test to use valid JSON with 260k elements - Added .forgejo/workflows/nigig-map.yml CI workflow to catch map-related regressions in tile parsing, style compilation, and tessellation Fixes runtime errors: - 'failed to triangulate local mbtile: too many elements (N > 100000)' - Tiles with 101k-140k elements now process successfully |
|||
|
|
07045c5df2 | fix(ui): restore missing room_list, toolbar and modal in CostEstimateScreen | ||
|
|
915c338987 |
test(spreadsheet-ui): fix a wrong assertion in the new exchange test
origin/main was red: navigation_and_lifecycle_are_headless asserted that after `exchange_sheet_data(0, &mut external)` the caller's buffer holds "active". Nothing in the test ever writes that string. `exchange_sheet_data` is a two-way `mem::swap`, so `external` comes back with sheet 0's contents. Sheet 0 is empty at that point: "adapter" was written while sheet 1 was active, and `remove_sheet(1)` then deleted the sheet holding it. Corrected to assert the empty string, and added the assertion the test was missing -- that the sheet now holds "grid". Checking only one side of a swap would pass for a function that merely cleared the buffer. Verified pre-existing: fails identically on origin/main without my commits. |
||
|
|
667f565ddd |
fix(cad): match arms bound new variables instead of matching KeyCode
Went looking for a clippy ratchet and found that clippy had never run on
this crate at all: two dependencies fail deny-by-default lints, so
`cargo clippy -p nigig-build` aborted before linting nigig-build. Fixing
those two unblocked the crate and immediately exposed a real defect
class.
THE BUG. A bare identifier in a match pattern that is not a known variant
is parsed by Rust as a NEW BINDING that matches everything. Nine such
names were used as KeyCode patterns:
KeyEnter, KeyBackspace, BracketLeft, BracketRight (cad/viewport.rs)
Digit0..Digit9, Equal, LeftBracket, RightBracket,
Apostrophe (doc, invoice, pm)
Real names are ReturnKey, Backspace, LBracket, RBracket, Key0..Key9,
Equals, Quote.
Consequences, in severity order:
- cad/viewport.rs: `BracketLeft => { .. }` is UNGUARDED and sits above
the numeric arms, so it swallowed every remaining key. All
direct-distance-entry input -- digits, '.', '-', ',' -- was
unreachable. The entire DDE feature was dead.
- The guarded ones fired for any key satisfying the guard: pressing Q
while drawing with a non-empty buffer committed the coordinate.
- doc/invoice/project_management: `Digit0 => '0'` swallowed the rest of
the keymap, so every digit and symbol typed produced '0'.
This compiles cleanly and no test catches it. rustc's only signal is
`unreachable_pattern` plus `unused variable: \`Capitalised\`` -- both
buried in the 200+ warnings nobody could see, because clippy never ran.
85 unreachable-pattern warnings before, 1 after (a benign catch-all).
UNBLOCKING CLIPPY. Two deny-level errors in dependencies:
- nigig-uikit user_project_pill.rs: a `for` loop returning on its first
iteration (never_loop). Rewritten as `.next()`.
- spreadsheet-engine formula2.rs: CellRef had an inherent to_string
shadowing Display (inherent_to_string_shadow_display). The naive fix is
a trap: Display::fmt was `f.write_str(&self.to_string())`, which
resolved to the inherent method -- delete it and the same call resolves
to ToString::to_string, which calls Display::fmt, recursing until the
stack overflows. Verified with a standalone repro before fixing. The
body moved into Display; Range got the Display impl it never had.
Tests: keycode_variant_tests names the four correct variants, so it stops
compiling if any is renamed -- the point being that the old code compiled
precisely because the names were wrong. Two formula2 tests pin that
`x.to_string()` and `format!("{x}")` agree, which is what the shadowing
lint exists to protect.
CI gate added and negative-tested: greps for a capitalised
`unused variable`, which is the signature of this bug. Restoring
BracketLeft makes it fire.
625 lib + 154 integration + 225 spreadsheet-engine + 44 doc-engine, 0
failed.
|
||
| 0fefb03254 | test(spreadsheet-ui): cover workbook grid data exchange | |||
|
|
8af41b3310 |
perf(cad): evict dead meshes instead of clearing the whole cache
ARCHITECTURE.md invariant 2 has said since it was written that the scattered clear_mesh_cache() calls are "unnecessary and expensive", because MeshCache keys on (NodeId, ParamHash) and an edited node invalidates itself. Phase 5.2 removed the first one. This removes the rest: 8 of the 11 live call sites, leaving only invalidate_all (whose contract is exactly that) and the definition. Investigating them turned up something the invariant did not say, and which makes the cleanup a bug fix rather than tidying: MeshCache has NO eviction for deleted nodes. Entries are keyed by NodeId and nothing ever removes one when its node goes away. The wholesale clears were, accidentally, the cache's only garbage collector. Deleting them naively would have converted a performance problem into an unbounded memory leak over a session. So the fix is a targeted replacement, not a deletion: MeshCache::retain, SceneCache::retain_meshes and CadViewport::evict_dead_meshes drop exactly the entries whose node is gone and leave every live one warm. Each call site was then classified rather than pattern-matched: - deletes (delete_selected, delete_part, CommandContext::delete_node) -> evict dead entries - pure additions (create_node, insert_node_at, paste, and the wall / circle / rect / area / column / beam / polygon / polyline tool completions) -> nothing at all. A new node has no cache entry to invalidate and cannot affect any other node's mesh. These were discarding the entire scene's meshes to make room for nothing. - subdivide_selected -> invalidate only the selected ids. It builds a fresh Arc<Solid> per part, so ParamHash already differs; the unselected parts were being thrown away for no reason. Measured: deleting 1 of 100 parts costs 62.1 us with clear+rebuild versus 21.9 us with evict+reuse, a 3x saving on every delete. Two tests, both negative-tested by stubbing out the retain body: retain_nodes_evicts_dead_entries_and_keeps_live_ones checks both directions (dead gone, live still an Arc::ptr_eq hit), and cache_does_not_grow_past_the_live_node_set simulates 50 add/delete rounds and asserts the cache holds 1 entry, not 50. With retain stubbed it holds exactly 50, which is the leak. Also documented a sharp edge found while reading ParamHash: it hashes a Csg node's Arc POINTER, not its geometry. Re-wrapping identical geometry in a new Arc is therefore a safe false-positive, but mutating a Solid behind a shared Arc would go unnoticed. Nothing does that today and ARCHITECTURE.md now says it must stay that way. 624 lib + 154 integration, 0 failed. |
||
| 43d724562e |
fix(map): increase MVT parser limits and fix MapThemeStyle script registration
Runtime logs showed tiles failing to decode with: - 'mvt layer has too many features (10000 >= 10000)' - 'mvt layer has too many values (1000 >= 1000)' Increased MVT parser limits to handle real MBTiles data: - MVT_MAX_FEATURES_PER_LAYER: 10,000 -> 200,000 - MVT_MAX_VALUES_PER_LAYER: 1,000 -> 10,000 - MVT_MAX_KEYS_PER_LAYER: 500 -> 2,000 Also fixed MapThemeStyle type mismatch error: - Changed from script_component to script_api registration - Fixes 'type mismatch for property style_light: expected MapThemeStyle, got object' |