Commit graph

33 commits

Author SHA1 Message Date
383533da36 feat(email): email the trip report to the finance department
build_report_email (pure, tested) attaches the report PDF as
application/pdf in a multipart message to a comma-separated finance
recipient list. spawn_email_trip_report fetches the inbox, extracts trip
receipts, builds the report, and emails it via the signed-in SMTP account;
the proxy backend reports 'attachments unsupported' honestly rather than
failing silently. The Finance card gains a recipients field and an
'Email report to finance' button.

An end-to-end test sends the attached PDF through the SMTP sink and asserts
the recipient, application/pdf type and filename land in DATA. Domain tests
234 -> 237.
2026-08-18 10:07:54 +00:00
074066a382 feat(email): export the trip report from the inbox
spawn_export_trip_report fetches the inbox (shared fetch_inbox_messages
helper), extracts trip receipts, builds the report, and writes
trip-expense-report.pdf into app-data; it posts TripReportExported with the
path and a summary. The More page gains a Finance card with an
'Export trip report (PDF)' button and a status line.
2026-08-17 12:08:19 +00:00
595ad6ad24 feat(email): pull-to-refresh (C7) and a real OS keystore (C1f)
C7: pull-to-refresh on the inbox, mirroring the SMS/M-Pesa transaction
lists (scrolled + scroll_position over a threshold, throttled to 1.2s and
guarded by the in-flight flag). The Refresh button remains for platforms
without a gesture.

C1f: a real KeyringCredentialStore behind the keystore feature -- the OS
credential vault (Linux Secret Service, Windows Credential Manager, macOS
Keychain) via the keyring crate, so IMAP credentials can survive a restart.
Native only; without the feature active_store() stays fail-closed. The
runtime vault is not host-verified (no secret service in CI), which is the
same honest caveat as the IMAP transport.
2026-08-17 04:29:30 +00:00
765e178737 feat(email): paced bulk send — batch and pace large recipient lists (C6)
The Bulk tab was capped at MAX_RECIPIENTS (100): a 500-recipient list was
refused with TooManyRecipients, not paced. That is a capped single send,
not bulk.

email_bulk.rs: bulk_send_plan splits a list into provider-sized batches
with a pacing schedule (pure, tested), and run_bulk_send executes the plan
— gap between batches, rate-limiter backstop, abandon check between every
step, per-batch progress. Tested against a mock send (batching, delays,
failed-batch counting, abandon).

email_send.rs: validate_bulk_message accepts a list over the cap (the
caller batches it) while still enforcing subject/body limits.

The Bulk page now sends <=100 recipients as one message and anything over
as paced batches, posting BulkSendProgress after each batch and at the end.
Domain tests 195 -> 206.
2026-08-17 04:29:30 +00:00
32b8decc1e feat(email): real inbox, compose and More pages (C4b/C5/C7, D1/D2/D3)
C4b: the inbox fetches real mail via spawn_fetch_inbox (backend-agnostic)
and renders loading / error / empty states; sample_thread() is test-only.
C5: Compose is a real form -- to/subject/body, build_without_config
validation, two-tap confirm, spawn_send_message (branches on backend).
C7: a Refresh button re-fetches (worker -> InboxFetched -> drained on the
UI thread).

D1: the last placeholder, more.rs, is now the account page (status,
backend, sign-out) -- there is no duplicated scaffold left to extract.
D2: the lib.rs compatibility shims are deleted; imports go straight to
nigig_core/nigig_uikit and NavigationBarAction lives in a real module.
D3: the CachedWidget decision is documented in email.rs.
2026-08-16 22:22:55 +00:00
47b2f72af0 feat(email): backend chooser + two setup forms (C1c)
SetupDraft ties account identity to the backend choice and validates
them together (the direct backend must also have a usable SMTP server;
the proxy backend leaves SMTP fields unset). The setup form grows a
chooser -- Direct (IMAP + SMTP) vs the Nigig mail service -- with an
honest per-backend summary, and two forms swapped by the chooser. The
inbox branches: proxy accounts verify against the mail service via
spawn_proxy_verify instead of an SMTP handshake.

Also pins the Proton Bridge provider default (local bridge, not a
remote imap.protonmail.ch).
2026-08-16 21:49:12 +00:00
nigig-ci
901cddc716 fix(email): abandon_send shipped as dead code; wire it and gate it (B6)
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (push) Has been cancelled
Auditing Phase B against the tree rather than against my own notes found
that abandon_send() existed in nigig-core and NOTHING called it. The user
had no way to stop waiting on a hung send. I had marked B6 "partial" for
the right reason -- lettre cannot cancel mid-transaction -- and missed
that the part I did implement was unreachable.

A control the user cannot reach is not a control. It is dead code wearing
a safety label, which is worse than an acknowledged gap because it reads
as done.

Now wired: while a send is in flight the Send button becomes "Stop
waiting". The label is deliberately not "Cancel" -- this does not stop
delivery, because once DATA is accepted the message is sent whether we
wait for the reply or not. It frees the UI and suppresses a result the
user has stopped caring about. The 20s timeout from A6 bounds the window.

New gate: abandon_send() must exist in nigig-core AND be called from the
UI. The wiring is the thing checked, not the function.

That gate was ALSO broken when first written -- it grepped for
`abandon_send()` across src/, and the comment block explaining why the
control exists mentions it by name, so unwiring the call left the gate
green. Same flaw as the B5 gate in the previous commit, found the same
way: delete the fix, watch the gate. Now excludes comment lines.

Twice in two commits I have written a gate that its own explanatory text
satisfied. Worth stating rather than quietly fixing: a gate is only
evidence if you have watched it fail.

Phase B verified closed: B1-B6 all done, 11 gates pass, 99 domain tests,
check --all-targets clean on both crates, fmt clean.
2026-08-16 20:04:41 +00:00
nigig-ci
d889cbecd4 ci(email): gate multi-recipient send, and a gate that did not work
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (push) Has been cancelled
Two new gates, and one of them was broken when I first wrote it.

B1 gate: the send path must call email_send::parse_recipients, must NOT
contain a single-Mailbox parse of the whole To field, and must add every
accepted recipient. Three checks rather than one, because each failure
mode is separately reachable.

B5 gate: spawn_send_email must keep the SEND_IN_FLIGHT swap.

THE B5 GATE DID NOT WORK AS FIRST WRITTEN. It grepped the whole file for
`SEND_IN_FLIGHT.swap(true`, and the unit TESTS for the guard contain that
same string -- so deleting the guard from production code left the gate
green. I found it by negative-testing, which is the only reason I know.
Now scoped to the text before `#[cfg(test)]`.

That is worth recording rather than quietly fixing: a gate whose own test
fixtures satisfy it is indistinguishable from a gate that works, and the
only way to tell them apart is to break the thing on purpose.

Negative tests, all confirmed firing:
  remove the list parse                     -> fires
  reintroduce `let to_mbox: Mailbox = ..`   -> fires
  delete the in-flight guard                -> fires (after the fix)
and all 10 gates pass on the clean tree.

Test floor 60 -> 95 (actual 99).

Bulk page: builds through EmailSendRequest, so a partly-invalid list
reports what was dropped instead of refusing everything, and requires a
second tap before sending. The prompt quotes the recipient count and any
duplicates or rejections, so the user knows what they are confirming.
Editing the message after arming re-prompts rather than sending the old
confirmation.
2026-08-16 19:51:21 +00:00
nigig-ci
3786e7c1cf docs(email): threat model, and mark Phase A complete (A6)
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
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (push) Has been cancelled
New crates/apps/nigig-email/THREAT_MODEL.md. Separate from the root
THREAT_MODEL.md, which is Nigig-Pay's and shares no assets with this
feature.

Nine threats with the control named for each, so a reader can check the
claim rather than take it on trust. The two that matter:

  T-E2 (password leaked by our own code) was LIVE, not hypothetical --
  SmtpConfig derived Serialize over a plaintext String and the whole
  struct was POSTed on wasm.

  T-E7 (DoS via a hostile message) is the SMS A3 bug class. One inbound
  message containing emoji took down the SMS list on every frame until it
  was deleted; email bodies are more hostile, not less.

Sections that exist specifically to avoid overclaiming:

  * "Residual" notes on every mitigation. Secret does not zero on drop.
    We trust the platform root store; no certificate pinning. Header
    injection is handled by lettre, NOT by us -- which means the C1d proxy
    backend, which does not go through lettre, must sanitise or T-E4
    becomes unmitigated.
  * "What has not been tested": no live SMTP server has been contacted,
    the wasm path has never been built, and no IMAP code exists, so
    T-E1/T-E2 cover the SMTP direction only.
  * Four open risks ranked, each tied to a plan item, including two
    (proxy auth, keystore storage) that MUST land with C1d/C1f rather
    than after -- the proxy is only safer than on-device IMAP if its token
    is revocable and scoped.

Marks A1-A6 done in the plan. Phase A is complete.
2026-08-16 19:22:40 +00:00
nigig-ci
b3d562f4e2 ci(email): gate the Phase A security properties, and surface the warning
Three new gates in email.yml, each negative-tested by reverting the fix
and confirming the gate fires:

  1. SmtpConfig.password must be a Secret, AND SmtpConfig must not derive
     Serialize/Deserialize. Two separate checks, because either alone
     re-opens S2: a Secret that gets serialised is still exposed, and a
     String that never gets serialised still Debug-prints.

  2. set_email_api_url must call email_api_url_is_safe. Checks both that
     the validator exists and that the setter uses it -- a validator
     nobody calls is decoration.

  3. tls_mode_for_port must exist, and Tls::None / Tls::Opportunistic must
     not appear. Opportunistic is the dangerous one: it silently accepts a
     downgrade, which is exactly the attack starttls_relay's Tls::Required
     prevents.

Negative tests, all confirmed firing:
  password: Secret -> String              gate fires
  re-add #[derive(.., Serialize)]         gate fires
  remove the validator call               gate fires
  introduce Tls::None                     gate fires
and all 8 gates pass on the clean tree.

Domain test floor raised 38 -> 60 (actual: 68) and the filter widened to
include the secret:: module, so the new tests are actually covered by the
floor rather than sitting outside it.

Also surfaces config_warning() in the setup flow, so a from/username
mismatch is shown while the user can still fix it, rather than becoming a
silent provider rejection later.

One YAML trap worth recording: the test filter ends in `secret::`, and a
bare trailing colon makes YAML parse the line as a mapping. The run string
has to be quoted. Caught by validating the workflow before committing,
which is the only reason this is not a broken pipeline.
2026-08-16 19:22:40 +00:00
nigig-ci
7751e96c54 ci(email): give nigig-email a CI workflow, and fix two bugs it caught
(Phase 0.3, 0.6)

nigig-email had no CI of any kind. That is how a binary with unbalanced
braces reached main and stayed there -- `cargo check -p nigig-email`
failed while `--lib` passed, so the library was fine and the BINARY had
never compiled once. It is also how four unused dependencies survived.

Four jobs:

  gates          4 source scans, no toolchain, fail fast
  email-domain   the 38 pure tests in nigig-core + a floor
  nigig-email    check --all-targets, test, fmt, clippy ratchet
  supply-chain   unused deps, lockfile, whitespace

`--all-targets` is deliberate in the check step: `--lib` alone passed for
the entire time main.rs was syntactically invalid, which is precisely the
failure this job exists to prevent.

Phase 0.6: fmt is a HARD gate here, not report-only. The crate already
formats clean so there is no pre-existing drift to grandfather in --
unlike sms.yml and nigig-map.yml, which inherited hundreds of diffs and
had to settle for reporting.

WRITING THE GATES FOUND TWO REAL BUGS, both in bulk.rs:

  B3 -- `port_t.parse().unwrap_or(587)` was still live. A typo'd port like
  "465x" silently became 587, and because the port selects the transport
  (465 implicit TLS vs 587 STARTTLS) that silently changed the security
  posture with no message. Now routed through AccountDraft::validate,
  which is unit tested in nigig-core and returns
  AccountError::PortInvalid.

  B2 -- the handler read five TextInputs and built an SmtpConfig on EVERY
  action event: ten heap allocations per keystroke, per scroll, per timer
  tick from any widget in the app, for a struct only read on click. It
  also captured whatever the fields happened to hold when an unrelated
  action fired. Now read on click.

I also got a baseline wrong and corrected it. I set the clippy ratchet to
2, having seen two `unexpected_cfgs` warnings for native_activity from
the app_main! macro. Measuring with the same dedupe the script uses gives
0 -- those two attribute to the bin target and are filtered by the
package_id check. A baseline above the real count is not a harmless
margin: the script fails when n < BASELINE precisely so slack cannot hide
a regression.

Every gate negative-tested:
  password field on EmailAccount        -> fails
  unwrap_or(587) in non-comment code    -> fails
  a new clippy warning                  -> fails (0 -> 2)
  test floor raised above actual        -> fails (38 < 99)
  and all pass on the clean tree.

Two of my own regexes were too strict on the first run and are fixed
here: the port gate matched the comments that document the old behaviour,
and the sample-data gate counted the `use` import as a call site. A gate
that trips on its own rationale is a gate nobody keeps.

Verified: check --all-targets clean; 41 tests pass; fmt clean;
clippy 0 at baseline 0.
2026-08-16 18:35:39 +00:00
nigig-ci
964fd5d4ef build(email): drop three unused dependencies, and gate the platform one
(Phase 0.5)

nigig-email declared four dependencies its source never mentions:

    serde            0 references in src/
    serde_json       0
    robius-location  0
    chrono           1  <- KEPT, see below

robius-location is the same defect SMS Phase B removed from nigig-build,
nigig-core and nigig-uikit: it drags polkit/gio/glib into the dependency
graph, which is where RUSTSEC-2024-0370, RUSTSEC-2024-0429 and an
LGPL-2.1 distribution question come from -- for code that is never
called.

A CI gate already exists to stop that regressing ("The removed platform
deps must not come back"), but its manifest list covered only three
crates and nigig-email was not one of them. Added it, so this cannot come
back the way it did here.

Correction to the assessment: it listed chrono as unused. That was true
when written and is no longer -- inbox.rs::format_thread_time uses it for
list-row timestamps. Kept, with a comment saying why, so the next person
auditing this file does not delete it and break the build.

Gate negative-tested: appending robius-location back to the manifest
produces
  ERROR: crates/apps/nigig-email/Cargo.toml declares robius-location
         but never uses it
and removing it passes again.

Verified: cargo check -p nigig-email --all-targets -> 0 errors;
41 tests still pass (38 nigig-core email_*, 3 nigig-email).
2026-08-16 18:35:39 +00:00
nigig-ci
34fecf1924 build: pin every git dependency to a full 40-character SHA (Phase 0.2)
The repo has a CI gate requiring full-length revs, added deliberately in
5e71457 with a comment explaining that an abbreviated rev resolves only
while no other object shares its prefix -- a property of the repository's
current object count, not a guarantee. Git's abbreviation length grows as
a repo grows, so a short pin silently becomes ambiguous, and an attacker
able to push to the fork can try to manufacture a colliding prefix.

That gate has been failing. 42 declarations across 34 crates used
abbreviated revs:

    41x  rev = "ecf5a572"    (the current makepad pin)
     1x  rev = "5efe6e24c"   (map/tests/makepad_test_app, left behind
                              by the ce0eaae bump)

Resolved both against the remote and rewrote them:

    ecf5a572  -> ecf5a572ab62a1c1598909971f602f99083671cc
    5efe6e24c -> 5efe6e24c9f732e9f11b783757f196f4f1c402b2

Verified this changes the LABEL and not the dependency: Cargo.lock holds
exactly one makepad commit id and zero references to the old one, so
nothing was silently upgraded. The stray makepad_test_app pin did move to
the current rev, which is the intent -- it pointed at a stale branch head.

Cargo.lock also picks up unrelated churn (brotli et al in,
makepad-android-state/jni-sys out). That staleness is PRE-EXISTING, not
caused by this change: confirmed by stashing every edit and running
`cargo metadata` on a pristine tree, which produces the identical diff.

Gate now passes:
  $ grep -rn 'rev = ' --include=Cargo.toml . | grep -vE 'rev = "[0-9a-f]{40}"'
  (no output)
2026-08-16 18:35:39 +00:00
ce0eaae935 fix(build): bump makepad pin to ecf5a572, restoring the test feature
Some checks failed
repo hygiene / hygiene (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
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
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
86c9595 synced the fork to upstream/dev at abd70f4, which dropped three
fork-local optional dependencies from widgets/Cargo.toml and their
re-exports from lib.rs. They were fork additions, so the merge lost them.

Every Makepad UI target then failed to resolve:

  package `nigig-pdf-makepad` depends on `makepad-widgets` with feature
  `test` but `makepad-widgets` does not have that feature.
  help: available features: default, serde
  failed to select a version for `makepad-widgets`

The "available features" list is misleading: with no `test` feature on
widgets 2.0.0, cargo falls back to the stale old/widgets copy, which is
1.0.0 and offers only default and serde. Same fallback that produced the
bogus makepad-fonts-chinese-bold error in an earlier sync.

libs/makepad_test was never removed - only the manifest entries and the
re-export. The fork's ecf5a572 restores both. This bumps all 34 crates.

Verified against the real fork, not a local copy:

  TEST_TARGET=pdf-ui  682 passing (was: failed to resolve)
  TEST_TARGET=pdf     637 passing

Pin bump only: every hunk changes the rev and nothing else.
2026-08-16 17:39:48 +00:00
nigig-ci
18bbb7badb feat(email): account-gated inbox with sender list and thread reader
The Inbox tab rendered "Top app bar page. Tap below to open a stack
screen." -- a placeholder with no path to any mail -- while the SMTP
credentials form sat on a tab called "Bulk". So the app had a login form
and no inbox, on separate tabs, with no connection between them.

Now the Inbox is gated on account state:

  SignedOut -> EmailAccountSetup, the connection form
  SignedIn  -> a PortalList of senders, newest thread first

and tapping a sender pushes a thread screen, matching how SMS opens a
conversation:

  row tap
    -> SharedConversationPreviewAction::Clicked
    -> RobrixStackNavigationView pushed with the timeline
    -> built-in back arrow pops to the list
    -> ContextNavAction::Hide/ShowBottomNav around the transition

This reuses nigig_uikit::shared::conversation rather than reimplementing
it. That module already exists for this purpose -- its types.rs has a
SharedConversationKind::Email variant and its row widget, message
bubbles and date dividers are all generic. Reusing it means the email
list and the SMS list behave identically, which matters because users
move between the two features.

Account setup (new page, moved off the Bulk tab):

  - autofills SMTP server and port from the address for known providers,
    so a Gmail user fills one field; only fills a blank field or one it
    filled itself, so a hand-typed server is never overwritten
  - reports every validation error at once
  - on failure, repopulates from SessionState::Failed so the user fixes
    one field instead of retyping six
  - the password is held in memory for the session only and cleared the
    moment a connection is known to have failed

Connection check reuses the existing spawn_smtp_test. A successful SMTP
handshake with AUTH is the only credential check available without an
IMAP client, and it is the honest one: it proves the account can send,
which is what this app can currently do with it.

Reading a thread marks its messages read and updates the unread badge.

Also in this commit:
  - deleted drafts.rs (finding A5): 157 lines, never declared in
    pages/mod.rs, so it was never compiled and could not be known to
    build. It was an unspecialised copy of the same scaffold.
  - dropped two unused imports in action_bars.rs.

3 unit tests on the pure display helpers -- row text composition and
format_thread_time against i64::MIN/MAX, since that runs inside
draw_walk for every visible row and a panic there takes down the frame.

NOT verified: no live SMTP server was contacted, and the list is
populated from email_store::sample_thread() because no receive path
exists yet. The list/thread transition is real and exercised by that
data; what it displays is not yet your actual mail. That is Phase C and
it needs the IMAP-vs-server-proxy decision first.
2026-08-16 17:27:35 +00:00
nigig-ci
b91f97b8db fix(email): repair main.rs so the binary compiles at all
nigig-email did not build. `cargo check -p nigig-email` failed with
"unexpected closing delimiter" at main.rs:24, while `--lib` was clean --
so the library was fine and the BINARY had never compiled. Nobody has
ever run this crate as a standalone app; it only ever loaded as a
library through pageflipnav.

The cause: StandaloneFeatureShell was closed immediately after
root_screen, so standalone_bottom_nav became a sibling at the wrong
depth and the brace count never reconciled. The self-inconsistent
indentation around it is the visible symptom of a hand-edit that was
never compiled.

nigig-sms/src/main.rs has the correct shape and the difference is one
missing wrapper: the body needs StandaloneFeatureBody around
root_screen, with standalone_bottom_nav as its sibling inside the shell.

This is finding 1 of the assessment and blocked everything else --
there is no point discussing tests or CI for a crate that cannot start.
2026-08-16 17:27:35 +00:00
86c9595729 chore: update makepad fork to latest upstream/dev (abd70f4)
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
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (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
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
Updated makepad fork to include all latest APIs needed by map widget:
- pack_vector_vertices and VECTOR_PACKED_FLOATS_PER_VERTEX
- TileArchiveReader for MKMap archive support
- get_tile_decoded method on MbtilesReader
- set_trust_fill_winding and fill_fringe_into on Tessellator
- retain_queued method on TagThreadPool
- set_camera_delta method on DrawRotatedText

This resolves all compilation errors in the map widget code.
2026-08-16 17:18:31 +00:00
9d647cec8c build(deps): bump makepad fork rev to 5efe6e24c (makepad-test enabled)
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
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (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
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
All 35 Cargo.toml pins move from d82756a to 5efe6e24c on the gitdab fork
(portallist base + makepad_test Android adb / standalone terminal wiring).
Lockfile regenerated; pdf crates compile against the new rev.
2026-08-16 08:38:18 +03:00
54ac36c0f7 refactor(map): use makepad-widgets map feature instead of custom copy
Some checks failed
nigig-map / test (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
sms / supply-chain (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 / fuzz (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
Updated makepad fork to d82756a which includes latest map improvements:
- Baked fills/faces support
- Enhanced 3D building rendering
- Improved road geometry and elevation
- Better theme matching and styling

Removed tile_makepad.rs (12k+ lines) and reverted to using makepad-widgets
map functionality directly. This avoids maintaining a separate copy and
ensures we get all upstream improvements automatically.

Changes:
- Updated all Cargo.toml files to use makepad fork d82756a
- Removed crates/apps/map/src/tile_makepad.rs
- Removed tile_makepad module from lib.rs
- Reverted tile_disk.rs to use mbtiles_tile_to_overpass_response
2026-08-04 11:22:05 +00:00
nigig-ci
cbe47a5f6c perf(sms): close the outstanding Phase B and D items
Some checks failed
doc-engine / engine (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
nigig-map / test (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
An audit of Phase 0 through D against the tree found three tasks marked
done in prose but absent from the code. This closes them.

D5 -- bulk send still blocked the UI thread.

  The send loop ran synchronously in handle_send_bulk.
  SmsManager.sendTextMessage queues to the radio and rate-limits, so a
  200-recipient batch was a multi-minute ANR with no progress and no
  way to tell whether anything was happening. Phase E8's throttle
  bounded the worst case at 30 sends, but bounded blocking is still
  blocking -- and I said as much when deferring it.

  Now uses the worker pattern D1 established: spawn, publish progress
  through a mutex-guarded slot, SignalToUI, drain on the UI thread. The
  status line counts up ("Sending 12/200…") instead of freezing.
  BULK_SEND_IN_FLIGHT prevents two overlapping batches.

D2 -- the ContentObserver, the half I left open.

  Phase D removed the 5-second poll that re-armed itself via redraw()
  and stopped the app ever idling. That fixed the busy loop but left a
  gap I documented rather than closed: a message arriving while the app
  was open did not surface until the next Resume or manual pull.

  Adds SmsInboxObserver.java -- a ContentObserver on content://sms,
  registered with a main-Looper Handler, idempotent so onResume can call
  it freely -- compiled and dexed by the existing build.rs pipeline and
  loaded through the same in-memory dex loader as the receivers.

  onChange calls into Rust, which does two cheap things: set an atomic,
  and invoke a registered waker. The waker matters. robius-sms has no UI
  dependency and cannot call SignalToUI itself, so without it the flag
  would only be observed on the next event-loop turn that happened for
  some other reason -- which, with the poll gone, might be never while
  the app sits idle. The app registers SignalToUI::set_ui_signal, so
  this is a genuine push.

  Native binding is dynamic, not #[no_mangle], for the same reason as
  E6: the class comes from an in-memory dex and is not on the JVM's
  search path.

B0 (wider) -- 23 crates declared robius-sms and never called it.

  Phase B removed the three declarations that put RUSTSEC advisories on
  nigig-build and explicitly flagged the rest as "the same latent
  problem, sweep separately". This is that sweep: every crate with zero
  references to robius_sms in its sources loses the dependency.

  nigig-mpesa, nigig-pay and nigig-sms keep it -- they are the only real
  users. nigig-system-prefs only mentions robius-sms in its package
  description, so its manifest is untouched.

  Cargo.lock loses another 21 lines.

Verified: 13/13 CI checks. clippy -D warnings clean on host and
aarch64-linux-android; the Android build compiles, javac-builds and
dexes the new observer class. cargo deny still "advisories ok, bans ok,
licenses ok, sources ok". clippy ratchet holds at 49. Sampled four of
the 23 stripped crates plus all five I edited; all build.

Pre-existing and unrelated: nigig-map fails to compile on pristine
origin/main (12 errors in view.rs, a Script/Widget derive problem), so
pageflipnav and anything else reaching it cannot be checked here. I
touched no files under crates/apps/map.

NOT verified on a device. The observer's registration, the onChange
callback and the waker all need an emulator or handset with a live SMS
provider; this sandbox has neither, and there is still no CI runner.
2026-08-02 07:28:01 +00:00
Arena Agent
5e714577fb ci: require full 40-character SHAs for git dependency revs
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
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-map / test (push) Has been cancelled
Upstream adopted the makepad fork fix (8fdff3f) but pinned it as
`rev = "a79f0dc"` -- a 7-character abbreviation. The pinning gate passed,
because it only checked that `rev = ` was present at all. Its own error
message has said "Add rev = \"<full-40-char-sha>\"" since it was written,
without ever enforcing it.

An abbreviated rev resolves only while no other object in the repository
shares its prefix. That is a property of the current object count, not a
guarantee -- it is why git's own auto-abbreviation length grows with a
repo. A short pin therefore degrades on its own over time, and someone
who can push to the fork can attempt to manufacture a colliding prefix.
For a dependency that executes at build time, that is a supply-chain
weakness rather than a style preference.

Checked before assuming: a79f0dc currently resolves uniquely in the fork
(exactly one matching object), so nothing is broken today. This closes it
while it is still cheap.

- All 34 manifests expanded to the full SHA
  a79f0dce4d477e2232344facca0798d3f25043ec. Cargo.lock is unchanged by
  the expansion, confirming it is the same commit and purely notational.
- The gate now also rejects any rev that is not exactly 40 hex chars.
  Negative-tested: restoring the 7-char form makes it fire.

685 lib tests pass; all nine gates pass.
2026-07-31 19:53:49 +00:00
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
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
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
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
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
60db0f21db build: update Nigig to Makepad dev reexport fork
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
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
2026-07-28 17:14:18 +00:00
Arena Agent
b5471e32e3 fix(cad): make the crate buildable, testable and safe to ship
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
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
Phases 0-2 of CAD_ASSESSMENT_AND_PLAN.md. The crate did not compile and no
test had ever run; it now builds clean with a green suite.

Build and CI (Phase 0)
- Pin all 33 git dependency manifests to an explicit rev. A branch
  dependency re-resolves on every build and is a code-execution path into
  CI if force-pushed.
- Commit Cargo.lock (540 packages). Producing it required fixing three
  resolution failures the workspace had always had: a non-existent
  makepad-widgets feature, two rusqlite versions both linking sqlite3, and
  four missed CellId call sites in spreadsheet-ui.
- Add .forgejo/workflows/nigig-build.yml.
- Replace five stale CAD docs that contradicted the code with one
  ARCHITECTURE.md; add PHASE0/1/2_STATUS.md and TEST_BASELINE.md.

Correctness (Phase 1)
- Rotation units: transform_point bound sin_cos() backwards, transposed X
  and Z, and applied axes in reverse order, so every exported STL was wrong
  even at zero rotation. It now shares the renderer's matrix helpers.
- GLB quaternions had norm 0.125 (half-angle applied to cos/sin, degrees
  read as radians) - invalid per the glTF spec.
- PDF wall/door/window yaw fed degrees to cos/sin.
- Fix a TOCTOU unwrap in touch picking; viewport.rs now has no unwrap().
- CommandContext gains update_node/insert_node_at/node_index: resize and
  modify were delete+create, silently moving nodes to the end of the scene.
- Wire MAX_UNDO_LEVELS (defined, exported, never read) and switch the undo
  stack to VecDeque; this also made the existing drag-merge logic reachable.
- CadNode::size() returned a fake 1x1x1 for CSG and extruded solids, making
  them unpickable outside a 1x1x1 box at their origin.
- Reject non-finite script input; makepad_csg clamps NaN rather than
  propagating it, so bad input produced silently wrong geometry.

Test baseline: 0 -> 722 passing, 0 failing
- 17 pre-existing failures fixed: 10 real defects (dependency-cycle
  detection, over-allocation of unassigned tasks, quote/backslash
  corruption on save, default rooms lost for all but the first region,
  RGA text ordering) and 7 tests that were themselves wrong, each checked
  against its production caller first.

Security (Phase 2)
- env!("CARGO_MANIFEST_DIR") was used as a runtime path in three places,
  including as the AI agent's working directory. All runtime data now goes
  under app_data_dir().
- Remove the hardcoded LAN LLM endpoint. It is now opt-in via
  NIGIG_CAD_LOCAL_OPENAI_URL/_MODEL and refuses plaintext HTTP to anything
  but loopback.
- Bound and content-sniff AI image attachments (8 MB cap, magic bytes);
  the MIME type came from the filename extension.
- Escape SVG/HTML output, and add SRI to the exported viewer's script tag.
  The pinned model-viewer@3.5.1 does not exist, so every exported viewer
  was silently broken; now 4.0.0 with a verified hash.
- Stop embedding $USER in exported PDFs and logging document content in
  release builds.
- CI now rejects reintroducing the runtime-path and hardcoded-endpoint
  classes; both gates were verified to fail on a reintroduced defect.

Add system_prompt.md and embed it with include_str!. The file was missing
from the repository, so the agent silently used a one-line fallback.
2026-07-28 16:49:30 +00:00
62dada264e build: consume Makepad sibling APIs through widgets 2026-07-27 04:22:42 +00:00
e73def6d1b build: align Makepad dependencies with Robrix fork 2026-07-27 03:32:08 +00:00
5af8a20d04 build: use Robrix upstream Robius dependencies 2026-07-27 03:26:49 +00:00
f6750c8259 feat(pay): add domain coordinator and sqlite storage foundation 2026-07-26 18:42:02 +00:00
cc05abdc71 Initial commit 2026-07-26 19:38:26 +03:00