Commit graph

312 commits

Author SHA1 Message Date
8fdff3ff55 Update makepad fork to a79f0dc (remove duplicate dependencies)
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
repo hygiene / hygiene (push) Has been cancelled
nigig-map / test (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
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
Commit a79f0dc fixes the duplicate dependency declarations that were
causing TOML parsing errors. This is the correct commit to use after
the parallel fixes in 5eda8056 and 11375214.

All 34 Cargo.toml files updated to reference the correct commit.
2026-07-31 19:43:08 +00:00
Arena Agent
a0dbd192c1 ci: add an unfiltered repo-hygiene workflow
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
repo hygiene / hygiene (push) Has been cancelled
Every workflow in .forgejo/workflows is scoped with `paths:`. That is
right for build and test jobs -- no reason to compile the CAD module
because a payment file moved -- but it leaves a structural hole: a commit
touching only unfiltered paths runs no checks at all.

Commit 8c9ccb9 fell straight through it, pushing 30 unresolved
conflict-marker lines across five files. The worst of them was
.forgejo/workflows/nigig-build.yml itself: with markers in it the file is
not valid YAML, so all seven CAD gates silently stopped running. A
workflow that does not parse does not fail -- it does not run, which is
strictly worse than a red build because nothing announces it.

`git diff --check` would have caught this and was already a step. It was
in the file the same commit broke, and only ran for that workflow's
filtered paths.

So this workflow has no path filter, no toolchain dependency and no
network dependency -- it cannot be knocked out by an unrelated build
break -- and it validates the CI configuration itself:

1. No conflict markers anywhere in the tree.
2. Every workflow file parses as YAML and has a top-level `jobs:`.
3. Whitespace errors.

Verified against the real failure, not a synthetic one: checked out
8c9ccb9 and confirmed check 1 reports all 30 marker lines and check 2
names both unparseable workflows. Also probed for false positives --
`>>`/`<<` shift operators, `// =======` comments and a "=======" string
literal do not trip it, because the patterns are anchored to column 0 and
the closing marker requires the trailing space git writes.

Also fixes the fifth file from that commit, which I missed while
repairing the others because I only grepped .rs and .yml: ARCHITECTURE.md
still had two live conflicts on main. Both were stale-vs-current
versions of my own lines; kept the current text. While there, refreshed
every LOC figure in the file table from the actual files -- 14 of them
were stale, workspace.rs by 64 lines.
2026-07-31 19:41:10 +00:00
Arena Agent
80425bbfb8 fix: repair the makepad fork and unblock the build
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
The repository has been uncompilable since the d6d1f99c fork bump. Root
cause was in gitdab.com/andodeki/makepad, not here, so the fix went there
first: commit 5eda8056 on portallist_flow_adaptive_view.

TWO defects, both introduced by the "Update fork to upstream dev 5d4483f"
merge, both pure losses rather than intentional changes:

1. widgets/Cargo.toml: the makepad-gltf / makepad-csg / makepad-test
   dependency lines were relocated from [dependencies] to below
   [features]. Cargo then parses each as a feature whose value should be
   an array, giving "invalid type: map, expected a sequence", and the
   gltf/csg/test/maps features cease to exist.

2. widgets/src/lib.rs: the feature-gated re-export block for those same
   crates (plus makepad_fast_inflate and makepad_mbtile_reader) was
   deleted outright. Fixing only the manifest surfaced this as
   "no `makepad_csg` in the root".

Both restored verbatim from 2c5cd97, the last rev that resolved. Neither
is a judgement call: the moved lines are byte-identical and the deleted
block is copied back unchanged.

This repo is then repinned from d6d1f99c to the fixed rev, full 40-char
SHA per the pinning convention CI enforces.

Verified end to end after removing the local git redirect used during
development, so this resolves against the real remote:
  cargo metadata            resolves
  nigig-build --lib         685 passed
  cad_integration           154 passed
  spreadsheet-engine        225 passed
  doc-engine                 53 passed
  nigig-map (maps feature)  compiles
  Cargo.lock                unchanged, --locked passes

Also resolved committed conflict markers in two workflow files, which
had made nigig-build.yml invalid YAML -- the CI config could not be
parsed at all:

- nigig-build.yml: kept --include='*.rs' on the by-value-getter gate.
  Without it the gate scans ARCHITECTURE.md and fails on its own
  documentation, which is the bug fixed in 4f32b1c.
- pdf.yml: kept upstream's side. Enumerating targets via
  `cargo fuzz list` and failing when the list is empty is strictly
  better than a hardcoded target list that silently passes vacuously if
  a target is renamed.

That makes four files in three commits now carrying committed conflict
markers from this merge. Worth checking how they are reaching main --
`git diff --check` catches exactly this and is already a step in the
nigig-build workflow, but it only runs on paths under that workflow's
filter.
2026-07-31 19:32:08 +00:00
6fd4182313 docs(spreadsheet-ui): update workbook model ownership comments
Some checks failed
nigig-build.yml / docs(spreadsheet-ui): update workbook model ownership comments (push) Failing after 0s
pdf.yml / docs(spreadsheet-ui): update workbook model ownership comments (push) Failing after 0s
2026-07-31 19:25:21 +00:00
4eebae14f2 Update makepad fork to 11375214 (Cargo.toml fix)
Some checks failed
nigig-build.yml / Update makepad fork to 11375214 (Cargo.toml fix) (push) Failing after 0s
pdf.yml / Update makepad fork to 11375214 (Cargo.toml fix) (push) Failing after 0s
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
- Fixed TOML parsing error where fork-specific dependencies were in wrong section
- Dependencies now correctly placed in [dependencies] before [features]
- Maps feature should now be properly recognized
2026-07-31 19:24:22 +00:00
d131e310df refactor(spreadsheet-ui): make WorkspaceModel the state owner
Some checks failed
nigig-build.yml / refactor(spreadsheet-ui): make WorkspaceModel the state owner (push) Failing after 0s
pdf.yml / refactor(spreadsheet-ui): make WorkspaceModel the state owner (push) Failing after 0s
2026-07-31 19:16:41 +00:00
32370d84dc fix: resolve runtime DSL errors in cost_estimator and cad modules
Some checks failed
nigig-build.yml / fix: resolve runtime DSL errors in cost_estimator and cad modules (push) Failing after 0s
pdf.yml / fix: resolve runtime DSL errors in cost_estimator and cad modules (push) Failing after 0s
- Remove invalid property overrides on CostEstimatorMetric child widgets
- Remove unsupported 'visible' property from Svg widget
- Fix 'align' type mismatch by using Align{} constructor

These were runtime errors that didn't prevent compilation but caused
warnings in the Makepad script system.
2026-07-31 19:08:34 +00:00
Arena Agent
b4f22d33e1 fix: restore CadWorkspace and resolve committed conflict markers
Some checks failed
nigig-build.yml / fix: restore CadWorkspace and resolve committed conflict markers (push) Failing after 0s
pdf.yml / fix: restore CadWorkspace and resolve committed conflict markers (push) Failing after 0s
origin/main (8c9ccb9) shipped an unfinished merge. Two problems, both in
files it merged from my recent commits.

1. COMMITTED CONFLICT MARKERS. Eleven `<<<<<<< / ======= / >>>>>>>`
   lines were committed in workspace.rs and formula2.rs, so neither file
   parses. The markers are literally in the pushed tree, not a local
   artifact -- `git show origin/main:<file>` contains them.

2. ~3,000 LINES OF CadWorkspace DELETED. workspace.rs went from 3,908
   lines to 883. Every method of `impl CadWorkspace` is gone --
   handle_actions, draw_walk, all five export entry points, bake_parts,
   with_viewport, export_scene_source. The impl block opens at line 185
   and never closes, which is the "unclosed delimiter" the compiler
   reports.

Resolution:

- workspace.rs restored from 36c9c0b, my last verified commit. Checked
  first that upstream's truncated version added nothing: the set of
  function names in it is a strict subset of mine, so nothing of theirs
  is lost by restoring. Both helpers from the conflicting merge --
  save_status_message and ExportBlocked/export_blocked_message -- are
  present and were what the markers were fighting over. Both are kept.

- formula2.rs conflicts were resolved by hand. Two were whitespace-only.
  The other two were the same test data formatted two ways; I kept
  upstream's rustfmt output since they had just run a formatter over the
  crate.

Deliberately NOT changed: the makepad rev stays at d6d1f99c and
Cargo.lock is untouched. The repo still does not resolve, for a separate
reason in the fork (see below), and quietly pinning back to 2c5cd97
would hide their bump rather than fix it. I verified this commit by
temporarily repinning locally, then reverted the manifests -- 685 lib
tests and 225 spreadsheet-engine tests pass against the restored source.

STILL BLOCKED, and it needs a fix in gitdab.com/andodeki/makepad, not
here: at rev d6d1f99c, widgets/Cargo.toml has three dependency lines

    makepad-gltf  = { path = "../libs/gltf",        optional = true }
    makepad-csg   = { path = "../libs/csg/csg",     optional = true }
    makepad-test  = { path = "../libs/makepad_test", optional = true }

sitting BELOW the `[features]` header instead of inside `[dependencies]`.
Cargo parses them as feature definitions, hence "invalid type: map,
expected a sequence", and the gltf/csg/test/maps features then do not
exist. The block was relocated by the "Update fork to upstream dev
5d4483f" merge; at 2c5cd97 the same three lines are inside
[dependencies].

Confirmed the one-block move is the whole fix: with those lines returned
to [dependencies] in a local clone, `cargo metadata --features
maps,csg,gltf,test` on makepad-widgets resolves cleanly.

A [patch] override is not a workaround here -- cargo rejects patching a
source with itself, and pointing at a corrected path clone makes the
patched and unpatched copies coexist, producing E0659 ambiguity across
makepad-ai and makepad-xr.
2026-07-31 19:02:14 +00:00
d8d29c226d feat(pdf): transparency, and four operator-parsing bugs it exposed
Some checks failed
nigig-build.yml / feat(pdf): transparency, and four operator-parsing bugs it exposed (push) Failing after 0s
pdf.yml / feat(pdf): transparency, and four operator-parsing bugs it exposed (push) Failing after 0s
Phase 8 #5 of REVIEWS/DART_PDF_VS_MAKEPAD_PDF_GAP_ANALYSIS.md
("Advanced transparency"). Design and merge criteria in
REVIEWS/adr/0009-pdf-transparency.md.

The headline defect was not that transparency was missing. `gs` was
MIS-PARSED as CloseStroke:

  /GS0 gs 1 0 0 rg 100 100 200 200 re f
    -> [CloseStroke, RgbFill, Rectangle, FillWinding]

so every page setting a graphics state gained a stroked path the
document never asked for, and lost its alpha, blend mode and soft mask
silently. Downstream everything was dead: StrokeExtGState/FillExtGState
were never constructed, set_fill_opacity was never called and emitted no
command when it was, and PdfPage::ext_gstate was read by no code at all.

New pdf-graphics/src/transparency.rs: full /ExtGState (ca, CA, BM,
SMask, LW, LC, LJ, ML, D, AIS, TK), all sixteen blend modes including
the four non-separable ones, soft masks with /S, /G, /BC and a /TR
evaluated through ADR 0006's PdfFunction, and /Group parsing. Wired end
to end: parser -> PdfOp::SetExtGState -> device -> RenderCommand ->
Makepad renderer.

Compositing is NOT claimed. Backdrop blending needs render-to-texture,
which an engine-neutral crate has no framebuffer for. Constant alpha is
applied because it needs no backdrop; blend modes and soft masks are
reported through TransparencyError::Unsupported rather than dropped,
because a silently ignored /Multiply looks exactly like a correct
/Normal.

The ADR required a test enumerating every multi-character operator, on
the grounds that fixing the third instance of a shadowing bug (after cm
and rg) without preventing the fourth is not a fix. It immediately found
three more live bugs, none of them transparency-related:

- `b` and `b*` dropped their close-path, mapping to FillStroke* instead
  of CloseFillStroke*, so every closed-and-stroked path drew with a gap.
- Text operators within three bytes of the end of a content stream were
  mis-parsed: the b'T' arm guarded `*i + 3 < len` while reading only two
  bytes, so a stream ending in `/F1 12 Tf` parsed as FillWinding.

And the transparency-group fixture found a fourth:

- PdfPage::xobjects was empty for every page of every document.
  extract_xobjects called doc.resolve(), which follows the reference,
  then asked the resolved object for as_ref() - always None - and only
  accepted a bare dict when every XObject is a stream. No `Do` operator
  could be resolved through the page model. Same defect as the one that
  once destroyed annotation object references; it now has its own
  regression test.

T* and BMC are genuinely unimplemented and are deliberately excluded
from the guard's table rather than papered over.

7 corpus fixtures, 12 acceptance tests, 32 unit tests asserting the
§11.3.5 formulas (not our own output), and a parse_ext_gstate fuzz
target.

TEST_TARGET=pdf 451 -> 497, TEST_TARGET=pdf-ui 495 -> 541.
rustfmt and clippy -D warnings clean.
2026-07-31 18:58:31 +00:00
8c9ccb92cc Update makepad fork to latest dev branch (d6d1f99c)
Some checks failed
nigig-build.yml / Update makepad fork to latest dev branch (d6d1f99c) (push) Failing after 0s
pdf.yml / Update makepad fork to latest dev branch (d6d1f99c) (push) Failing after 0s
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
- Sync with upstream commit 5d4483f (latest map improvements + platform updates)
- Include location API, audio echo cancellation, bridge-dz overlay
- Add new libraries: geodata, map_nav, i_float, i_shape, i_tree, converse, llama vision
- Preserve all fork-specific re-exports (gltf, csg, test)
- All 102+ map improvements now available: 2D/3D toggle, shadows, labels, overlays, pattern fills
2026-07-31 18:47:37 +00:00
7deb5fe9f6 chore(spreadsheet): remove unused test imports
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
2026-07-31 18:44:58 +00:00
Arena Agent
36c9c0b0d1 fix(cad): both Save buttons reported success when the write failed
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
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.
2026-07-31 18:42:27 +00:00
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.
2026-07-31 18:39:39 +00:00
Arena Agent
fc2e555cf6 refactor(cad): collapse the four duplicated export prologues
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
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.
2026-07-31 18:35:59 +00:00
00f1dfbc12 fix(pdf): fuzz every target, and close two ADR 0004 gaps
Some checks failed
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
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.
2026-07-31 18:34:25 +00:00
49c8fd2e16 style(spreadsheet): format updated formula engine
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
2026-07-31 18:32:25 +00:00
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.
2026-07-31 18:25:10 +00:00
arena-agent
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.
2026-07-29 10:43:07 +00:00
3213603e21 fix(pay): put a build marker in the tracker-only message
Some checks failed
Payment domain, storage, platform and UI / payment-ui-tests (push) Has been cancelled
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
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 3e77bf5 and `git log -S` shows it in no source file today,
so a binary built from current main cannot print it. The reports are
coming from a binary built before that commit — most likely an installed
APK, which `git pull` does not update.

The real gap is that there was no way to tell those two builds apart from
the screen. A stale APK and a current one showed a message of the same
shape, so each report restarted the same investigation.

The message now ends with `[tracker-2]`. If the screen shows that marker,
the binary contains every fix to date and the message is the intended
product boundary. If it does not, the binary is stale and needs a rebuild
and reinstall.

Cheap to read aloud in a bug report, and it settles the question in one
round trip instead of four.
2026-07-29 10:06:04 +00:00
arena-agent
652105dff6 feat(doc): touch cell-range selection via long-press and drag
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
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.
2026-07-29 09:00:00 +00:00
e12a3bb5c4 fix(pay): reword the bulk gate message too
Some checks failed
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
Third report of "Payment dispatch is unavailable in this build".

That exact string is gone from source as of 3e77bf5, so a build from
current main cannot print it. But the bulk path still carried the same
phrasing ("Bulk payment dispatch is unavailable in this build"), which is
the same defect: it describes the binary rather than the product and gives
the user no next step.

Both gated paths now say what the app does and what to do instead. A CI
guard rejects `set_status(...unavailable in this build...)` so the phrasing
cannot come back; it checks displayed strings only, so the explanatory
comments remain legal. Verified to fail against the reintroduced wording.
2026-07-29 08:56:53 +00:00
3e77bf52b0 fix(pay): make the demo gate reachable and its message honest
Some checks failed
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
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.
2026-07-29 08:54:22 +00:00
arena-agent
1f44da2b61 feat(doc): mobile Edit/Done interaction mode for 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
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.
2026-07-29 07:14:50 +00:00
arena-agent
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.
2026-07-29 05:29:05 +00:00
arena-agent
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.
2026-07-29 05:06:16 +00:00
arena-agent
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.
2026-07-29 05:06:16 +00:00
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)
2026-07-29 04:51:09 +00:00
arena-agent
0397f25c75 test(doc): CrdtDocEditor runtime integration tests on a real Cx
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
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.
2026-07-29 04:36:26 +00:00
Arena Bot
83f4bae485 fix(ui): remove inline ids assignments since ids inside prototype override instances are unsupported
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
2026-07-29 04:24:16 +00:00
arena-agent
496bc1c29e feat(doc): wire application navigation to CrdtDocWorkspace
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
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).
2026-07-29 04:21:13 +00:00
arena-agent
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.
2026-07-29 04:21:13 +00:00
Arena Bot
a09f989fbe fix(ui): close brackets correctly for RoomTypePicker definition
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
2026-07-29 04:09:14 +00:00
User
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.
2026-07-29 04:07:02 +00:00
arena-agent
a7d422a0ac feat(doc): switch workspace DSL to CrdtDocWorkspace with toolbar parity
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
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.
2026-07-29 04:06:36 +00:00
cbafa92269 fix(pay): close the fail-open biometric the coordinator migration would open
Some checks failed
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
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.
2026-07-29 03:57:07 +00:00
Arena Bot
06d38056b6 fix(ui): use correct nested override syntax for 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
2026-07-29 03:50:52 +00:00
Arena Bot
0b44064bac fix(ui): access inherited id paths on metrics to avoid scope errors
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
2026-07-29 03:01:15 +00:00
arena-agent
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.
2026-07-29 03:00:55 +00:00
b4ad9348d3 fix(pay): make batch progress bounded and derived, not counted
Some checks failed
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
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.
2026-07-29 02:55:49 +00:00
Arena Bot
8e06b7ed30 fix(ui): define CategoryDropdown before AddRoomModal in CostEstimateScreen
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
2026-07-29 02:45:46 +00:00
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.
2026-07-29 02:45:32 +00:00
arena-agent
06afa336b2 feat(doc): CRDT-native mobile long-press and selection handles
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
- 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
2026-07-29 02:33:29 +00:00
750d856668 feat(pay): Phase 6 truthful payment states, and run UI tests in CI
Some checks failed
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
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.
2026-07-29 02:27:44 +00:00
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)
2026-07-29 02:19:42 +00:00
arena-agent
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)
2026-07-29 02:03:37 +00:00
Arena Bot
5ec5270015 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
2026-07-29 01:58:05 +00:00
Arena Bot
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
2026-07-29 01:56:41 +00:00
arena-agent
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
2026-07-28 21:01:31 +00:00
7d21532ebf feat(pdf): real colour spaces, ICC profiles and PDF functions
Some checks failed
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
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.
2026-07-28 20:48:55 +00:00
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
2026-07-28 20:41:02 +00:00