Skip to content

Release: develop -> main - #186

Open
github-actions[bot] wants to merge 13 commits into
mainfrom
develop
Open

Release: develop -> main#186
github-actions[bot] wants to merge 13 commits into
mainfrom
develop

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automatic Release PR

Commits: 4 new commit(s)

  • Review all changes
  • Verify CI passes
  • Merge when ready for production

Rewrite the README into a project overview, a system-wide repo map, and
an app-specific guide. Add the end-to-end layer/repo table, a "What
zkCoins is" intro, and consolidate the app section (stack, trust model,
thin-client architecture, develop/build, configuration, layout, branch
flow). Replace the stale server-repo reference with zk-coins/node and
neutralise the hosted-operator wording.
…182)

* feat: multi-asset wallet UI — create-coin flow, per-asset portfolio, i18n

Mirrors the neutral multi-asset contract (node#220 / sdk#29). No native
asset; per-(owner, asset_id) accounts.

- Create-coin flow (replaces the testnet faucet): name/decimals/amount
  form -> two-phase creator-signed mint (src/app/create).
- Per-asset portfolio on home (GET /api/balance/:address) with empty
  state; usePortfolio hook; formatAssetAmount/shortAssetId.
- Asset picker in Send; chosen asset_id flows into the send; amount
  scaled by the asset's decimals.
- Per-asset detail screen (src/app/asset/[id]) with the owner-history
  note (history is per-wallet, not per-asset).
- i18n de (default) + en via next-intl; all user-facing strings +
  error messages moved into messages/{de,en}.json.
- API layer (src/lib/api/client.ts) implements the multi-asset wire
  bits locally (buildMintMessage byte-for-byte vs the SDK source,
  per-asset balance + ownerBalances, creator-signed createCoin, send
  with asset_id), reusing the existing @zkcoins/wasm crypto path; the
  installed @zkcoins/sdk predates multi-asset, so the wire bits live in
  the app until the SDK (sdk#29) is published.
- e2e harness funds via the create-coin flow.

Verified: next lint clean, prettier (CI scope src/**) clean, next build
green, vitest 535 passed (stmts 90.5 / branches 84.5 / funcs 90.7 /
lines 91.1; strict src/lib + src/hooks 100%), tsc clean, live-node
smoke green (node accepted the app's mint-message signature).

* fix(crypto+e2e): derive wallet address as node-compatible Poseidon; multi-asset goldens prep

CRITICAL: the app's address derivation (app/rust/shared hash()) used
SHA-256, but node#220 keys accounts by owner = Poseidon(creator_pubkey)
(hash_bytes). So the app's address never matched the node owner — the
live portfolio (GET /api/balance/:address) would never show minted
assets. Aligned the app's shared crate to the node's Poseidon-Goldilocks
hash + digest_to_bytes (byte-for-byte) and rebuilt @zkcoins/wasm.
Verified: new address_hex == digest_to_bytes(Poseidon(pubkey0)) == node
owner; a completed create-coin mint then shows under that address.

- e2e/_helpers/wasm.ts loads the rebuilt wasm in Node; _helpers/api.ts +
  _global-setup.ts derive the polled address via the wasm Poseidon path
  (was the SDK's sha256), so fixture funding is detected (3 Dockerized
  globalSetup runs funded Alice).
- New screenshot specs 18-create-coin, 19-portfolio, 20-send-asset,
  21-asset-detail (desktop+mobile, applicable states); 06/17 repaired
  for the per-asset UI; registered in playwright.config.
- e2e build locale override NEXT_PUBLIC_E2E_LOCALE (app default stays de;
  e2e bakes en for stable snapshots).
- a11y: bump asset-id text contrast to WCAG-AA (text-ink3).
- scripts/e2e-local.sh: publish --update-snapshots baselines back from
  the throwaway container to the worktree.

Baselines themselves land in a follow-up commit once generated against
the running node (lint/build/unit are green; tsc clean).

* test(e2e): green multi-asset screenshot-golden suite + regenerated baselines

Finish the e2e visual-regression suite for the multi-asset wallet so a
no-update `scripts/e2e-local.sh` run passes end to end against a local
node (leg 1: 106 passed; leg 2 real send: 1 passed).

Spec fixes for the single-asset -> multi-asset migration:
- 07-send: rewrite for the per-asset Send flow (asset picker +
  per-asset Available readout + amount in asset units), consistent with
  20-send-asset. Keeps every branch (default/no-funds/recipient/amount/
  set-max/invalid/insufficient/confirm/cancel/real send-success). The
  no-funds banner is driven via a route-mocked zero-balance asset (the
  live fixtures never produce a held-but-empty asset). Mask the volatile
  asset name on the success screen via a new `send-success-amount`
  testid.
- 13-send-server-errors: assert the English `errors.*` catalog strings
  (the e2e build bakes NEXT_PUBLIC_E2E_LOCALE=en) instead of German; fix
  the flow to the per-asset form with a valid asset-unit amount.
- 17-tx-detail: the local node lacks `GET /api/history/{id}` (404), so
  drive the detail-body goldens via a `page.route()` mock of that route
  returning a deterministic mint TxDetail; the live history list (and the
  clickable row) is untouched. The not-found case stays unmocked.

Tooling + docs:
- scripts/e2e-local.sh: strip a leading `--` arg separator on the host
  entrypoint so `e2e-local.sh -- --update-snapshots` forwards the flag to
  Playwright instead of passing a bare `--` (which made Playwright treat
  it as a test-file filter -> "No tests found").
- public/handbook: document the new screens (specs 18-21) and drop the
  removed balance-hidden entry, so the `prebuild` handbook<->baseline
  coverage gate passes.

Regenerated all affected `*-chromium-linux.png` baselines and added the
new spec 18-21 baselines (Docker Playwright, locale=en, against the live
node). Removed the orphan 06-balance-hidden baseline.

* ci: pin app/rust to nightly (plonky2 in shared needs it) + resync package-lock

- rust/rust-toolchain.toml = nightly so the Rust Crypto Tests job (and
  the wasm build) compile the Poseidon-Goldilocks address path (plonky2
  requires nightly), mirroring the node workspace.
- Regenerate package-lock.json so npm ci is in sync (next-intl
  transitive deps incl. @swc/helpers).

* build: pin @swc/helpers 0.5.23 via overrides so npm ci resolves consistently

CI's npm resolves next@15.5's transitive @swc/helpers to 0.5.23 while a
newer local npm pinned 0.5.15, leaving the lock file out of sync (npm ci
EUSAGE 'Missing @swc/helpers@0.5.23'). An explicit override fixes the
version across the tree; lock file regenerated, npm ci --dry-run clean.

* test(e2e): cover create/asset-detail routes in audits, drop stale balance ghosts

The multi-asset redesign added two route screens (`/create`, `/asset/[id]`)
and replaced the single balance hero with a per-asset portfolio, leaving the
two CI audits red:

- golden-coverage: register the `/create` and `/asset/[id]` route screens in
  `e2e/_audit/screens.ts` with their `18-create-empty-*` / `21-asset-detail-*`
  baselines (already produced by specs 18 / 21).
- button-inventory:
  - cover the two uncovered testids with real assertions —
    `getByTestId('create-phase')` in the create-success flow (the lifecycle
    phase label renders during the live mint) and
    `getByTestId('asset-row-decimals')` in the asset-detail happy path.
  - clear the three balance-hero ghost testids (`balance-amount-usd`,
    `balance-amount-btc`, `balance-value`) that no longer exist in `src/`:
    the default screenshot mask now targets the portfolio's volatile
    `asset-row-balance` cell, and the stale references in fixtures/README
    are updated to the per-asset model.

The mask swap is pixel-neutral: `asset-row-balance` is already masked
per-spec wherever it renders (06/19/21) and matches nothing elsewhere, while
the removed `balance-amount-*` mask/CSS selectors matched nothing — so no
baseline regeneration is required.

* fix(build): pin @swc/helpers to 0.5.15 (next 15.5.18's exact dep) so standalone boots

The 0.5.23 override satisfied @swc/core's optional >=0.5.17 peer but
violated next@15.5.18's exact @swc/helpers 0.5.15 requirement. next's
standalone require-hook is compiled against 0.5.15's file layout
(esm/_interop_require_default.js), which 0.5.23 does not ship — so
`node .next/standalone/server.js` crashed with MODULE_NOT_FOUND and the
E2E job's app never came up. 0.5.15 satisfies next exactly; @swc/core's
peer is optional and build-time only, unaffected at runtime.

* feat(app): make the wallet a capability-adaptive multi-asset client

The branch made the app multi-asset-only, which broke the E2E CI job: it
serves the PR bundle against the single-asset DEV node (info-proxy forces
capabilities.multi_asset=false), so the create-coin globalSetup, the
portfolio-only WalletScreen, and the per-asset send all failed.

Render the pre-branch single-asset surface when the node reports
multi_asset:false, and the multi-asset surface only when true — mirroring
how the repo already gates username_claim / address_list / lnurl.

Gating:
- src/lib/features.ts: add MULTI_ASSET as a build-time route gate
  (NEXT_PUBLIC_ENABLE_MULTI_ASSET, dead-strips /create + /asset) AND a
  runtime capability on useFeatures() (drives the shared screens).
- WalletScreen: multi_asset=false → single-asset balance hero (balance-value
  + eye toggle + faucet via api.mint/api.walletBalance); true → per-asset
  portfolio + create-coin entry.
- send/page.tsx: split into single-asset (api.walletSend, no asset_id) and
  multi-asset (asset picker, api.send) surfaces selected at runtime.
- /create + /asset/[id]: build-time notFound() + runtime redirect home when
  multi_asset is off.
- api client: add walletBalance (single-arg) + faucet mint + walletSend
  (no asset_id) alongside the multi-asset balance/ownerBalances/send.

E2E:
- _global-setup.ts: branch on the node's real /api/info — faucet-fund on
  false, create-coin on true. Keeps the WASM-Poseidon address derivation.
- specs 18-21: skip when multi_asset:false via a shared capability probe
  (_helpers/capabilities.ts); their committed baselines are dropped (gated,
  no CI baseline) but the specs run + pass against a true node.
- screens.ts: /create + /asset are MULTI_ASSET-gated (no baseline);
  gates.mjs adds MULTI_ASSET to ENV_GATED_FEATURES.
- regenerate the false-surface 0x baselines (06/07/13/17 + the wallet shots
  in 02-05/08-10/15) to the single-asset rendering; restore the
  balance-amount-{usd,btc} masks + balance-value testid + handbook refs.

Tests: features.test covers MULTI_ASSET; client.test covers
walletBalance/mint/walletSend; new single-asset WalletScreen + Send specs;
existing multi-asset unit tests pin the runtime capability ON.

* fix(e2e+wallet): CI-rendered send baselines + gate portfolio poll, dedupe recipient resolve

- Replace the 8 send-surface visual baselines (07-send + 15-scan-filled)
  with the ubuntu-latest Chromium renders the E2E job itself produces.
  The dual-mode rework's baselines were generated in the local Docker
  Playwright image, whose font metrics differ from the CI runner's
  npx-installed Chromium; text reflowed ~2% on the send chrome and tripped
  the threshold (text-light screens like 06 stayed just under). Harvesting
  the CI actuals makes the send baselines consistent with the rest of the
  repo's CI-rendered *-chromium-linux.png set.
- WalletScreen: gate usePortfolio on MULTI_ASSET so a single-asset node is
  not polled on GET /api/balance/:address (404 there) every 5 s.
- send/page.tsx: extract the byte-identical recipient-resolution block from
  both surfaces into a shared resolveRecipient() helper.

* test(e2e): assert German send-error copy (de is the build/default locale)

CI builds the app with the default `de` locale (the committed baselines
render German — e.g. 'Bitcoin senden'), but the dual-mode rework's spec
assertions were anglicised to 'Invalid amount' / 'Server error …'. Restore
the German strings (matching messages/de.json + the pre-branch specs) so
07-send amount validation and 13 server-error toasts match the rendered
text. The send-error tests fail on the toHaveText before their snap(), so
this also lets those screenshots run.

* test(e2e): CI-rendered baselines for the send error-state screens

The amount-invalid/insufficient (07) and server-error toast (13) screens
were unblocked once their assertions matched the de locale; their
Docker-generated baselines then tripped the same font-metric drift vs the
CI runner's Chromium. Replace them with the E2E job's own ubuntu-latest
renders, consistent with the rest of the *-chromium-linux.png set.
…ract (#185)

* fix(e2e): seed single-asset fixtures via the creator-signed mint contract

The DEV node's neutral permissionless minting (zk-coins/node#220/#222)
removed the server-mediated faucet: POST /api/jobs/mint now requires the
creator-bound signed contract and 422s the old {account_address, amount}
body, GET /api/balance?address= requires asset_id (no native asset), and
sends without asset_id are rejected. The E2E globalSetup single-asset leg
still posted the old mint body, failing every CI run at seeding.

- e2e/_global-setup.ts: the single-asset leg now seeds Alice through the
  same creator-signed createCoin flow as the multi-asset leg, with a
  deterministic E2E-FIXTURE asset name, and asserts the wasm-derived mint
  owner matches the wallet UI address.
- e2e/_helpers/api.ts: drop the dead old-contract mint helper; document
  that the parameterless walletBalance read only works through the proxy
  translation.
- scripts/e2e-info-proxy.mjs: translate the single-asset surface against
  the multi-asset upstream — aggregate GET /api/balance?address= from the
  per-owner portfolio (num_sends summed wallet-globally) and inject the
  sender's sole asset_id into asset_id-less sends (the send signature
  does not cover asset_id). Capabilities normalisation is unchanged.
- src/lib/api/client.ts + WalletScreen: rewrite the faucet as a
  creator-signed self-mint (delegates to createCoin with a unique
  FAUCET-<ts> asset name); no caller posts the old contract any more.
- .gitattributes: mark packages/zkcoins-wasm/src/pkg/* linguist-generated
  so the generated wasm-bindgen glue stops attracting style findings.
- unit tests updated/extended for the new faucet shape and both proxy
  translations (100% proxy coverage gate kept).

* fix(e2e): make single-asset seeding retry idempotent

The node rejects a re-mint into an existing (owner, asset_id) account,
so with the deterministic E2E-FIXTURE name a createCoinWithRetry retry
failed deterministically whenever an earlier attempt had landed
server-side but the client's completed-poll blipped — even though Alice
was actually funded. Treat the node's re-mint rejection on attempt >= 2
(deterministic names only) as already-seeded and defer to the
independent balance poll for verification; the predicate lives in a
dependency-free helper with unit tests.

Also: widen the .gitattributes pkg glob to ** so future wasm-pack
snippets/ subdirs stay linguist-generated, prettier-format e2e/README.md
(pre-existing check failure), and document that E2E_FAUCET_CALLS > 1
produces a multi-asset portfolio in the proxied single-asset env after
which send injection 422s.
Promote: staging -> develop
@TaprootFreak
TaprootFreak marked this pull request as ready for review June 11, 2026 21:17
@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

[OK] Button-Inventory-Audit — all clear

Checked 137 testid(s) in src/ against 124 reference(s) in e2e/. Nothing to do.

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

[OK] Golden-Coverage-Audit — all clear

Every active, ungated screen has its golden: 12 active screen(s) across 10 route(s), 2 env-gated route(s) exempt.

Screen Reach Golden(s) Status
onboarding-welcome / · welcome (no stored wallet) 01-welcome-desktop, 01-welcome-mobile
onboarding-create-seed / · create-seed reveal 02-seed-reveal-shown
onboarding-restore-seed / · restore-seed entry 03-restore-entry-empty
unlock / · hasStoredWallet && isLocked 04-unlock-empty
wallet / · account && !isLocked 06-balance-funded-desktop, 06-balance-funded-mobile
send /send 07-send-default
tx-detail /tx/[id] 17-tx-detail-desktop, 17-tx-detail-mobile
receive /receive 08-receive-default-desktop, 08-receive-default-mobile
settings /settings 05-settings-desktop, 05-settings-mobile
network /network 14-network-activity-desktop, 14-network-activity-mobile
create /create 18-create-empty-desktop, 18-create-empty-mobile
asset-detail /asset/[id] 21-asset-detail-desktop, 21-asset-detail-mobile
(gated) /apps ⚪ exempt (APPS_DIRECTORY)
(gated) /reset ⚪ exempt (DEV_ROUTES)

…189)

* test: close the src/app unit-coverage gap to 100% function coverage

Cover the untested route-component handlers so src/app/** reaches 100%
function coverage (was 75%):
- send/page.tsx: single-asset scan/Set-max/cancel/Done/phase handlers
  and no-funds banner; multi-asset select onChange, ?asset= deep-link
  default, and success Done button
- page.tsx: the unlock-screen handleReset escape hatch
- create/page.tsx: the createCoin onPhase callback
- receive/page.tsx: the clipboard-write rejection leg
- settings/page.tsx: extract the disabled-only Toggle handler and
  c8-ignore it (all shipped settings toggles are "Planned"/disabled)

Recalibrate the global functions threshold 86 -> 93 and add a per-glob
src/app/** functions:100 gate so the route surface cannot silently
regress again.

Closes #188

* test: query home-reset stubs by role/label to satisfy the button-inventory audit

The mocked Home child components used data-testids, which the
button-inventory audit (scans src/ incl. __tests__) flagged as orphans;
switch to role/aria-label queries like the QrScanModal stubs.
Coverage scope is now one deterministic criterion: code not behind a NEXT_PUBLIC_ENABLE_* build flag is default-active and must reach 100% (unit + e2e); env-gated code is exempt. No more triage / decorative / no-logic exemptions.

- vitest.config: encode the rule; split excludes into permanent (env-gated) vs DEBT (not env-gated, must be covered — this list only shrinks); point at ENV_GATED_FEATURES as the single source of truth.
- Remove the "MVP" term repo-wide (code comments, audit scripts, e2e/README.md) in favour of default-active / env-gated / always-on.
- Rename MVP_EXEMPT_* -> EXEMPT_* in the button-coverage audit.
- Fix mislabels: faucet (off-mainnet) and username resolve are always-on, not env-gated; the /network chart is in scope.

No behaviour change: coverage thresholds, the test set, and both audits are unchanged.
Bring src/lib/simulate-network.ts and src/lib/api/explorer.ts to 100% coverage and drop them from the vitest DEBT exclude list, so the strict src/lib/** 100% gate again spans all of src/lib. Per the coverage-scope rule (#190): not env-gated -> coverage-required.

- simulate-network: buildHistory (no-arg defaults + explicit args), nextSample (injected rng + Math.random default), plus deterministic-seed and timestamp-spacing assertions.
- explorer: getNetworkActivity across every branch — no URL configured, ok + non-empty data, empty array, non-array samples, non-ok response, and fetch rejection.
Bring src/app/network/page.tsx and src/components/NetworkActivity.tsx to 100% coverage and drop them from the vitest DEBT exclude list. Per the coverage-scope rule (#190): not env-gated -> coverage-required.

- network/page: drives the poll state machine — placeholder->loaded swap, the simulated local tick, the explorer re-fetch (samples present vs empty), and the failed-initial-fetch path that still marks loaded and starts no interval (fake timers).
- NetworkActivity: data->geometry branches — populated vs empty window, live readouts, x-axis labels, path building.
- Two genuinely-unreachable defensive guards (catmullRomToBezier length, interval cleanup ref) are c8-ignored with a justification, matching the existing convention.
…burn-down (#194)

Cover the last non-env-gated files and remove them from the vitest exclude list, which now holds only env-gated files. Ratchet the global thresholds up to lock in the completed burn-down.

- PixelIcon: render every icon in the map (sprite -> 1x1 rects) plus the size/color/className/rest-prop surface.
- Logo / PixelLogo: default + explicit size, prop forwarding.
- RootLayout: metadata/viewport exports and the html/body shell (next/font/google mocked).
- vitest.config: exclude list is now env-gated-only; global floors raised to lines 92 / statements 92 / functions 94 / branches 85 (just under the live 92.9 / 92.3 / 94.3 / 85.8).
The hosted DEV (dev-api.zkcoins.app) + PRD (api.zkcoins.app) nodes were
retired and now return 502, so the required 'E2E Tests' check had no
reachable upstream and every PR was blocked.

Repoint the node URL to ci.zkcoins.app — a dedicated Mutinynet node under
the project's own domain — across:
  - ci.yaml            E2E_NODE_URL (the required E2E gate)
  - api-contract.yml   live contract probe default + dispatch default
  - regenerate-visual-baselines.yml  E2E_API_URL

Same upstream contract as before (real ZK proof gen + real Mutinynet
broadcast); only the hostname changes. No DFX-internal infra names are
referenced from this public repo.
Staging mirror of #196 (which landed on develop). In the
feature -> staging -> develop -> main flow, staging is upstream of
develop, so the E2E_NODE_URL repoint from #196 does not reach
staging-based feature branches. Apply the identical change here so
staging (and feature PRs based on it, e.g. #195) target the live
ci.zkcoins.app node instead of the retired dev-api.zkcoins.app (502).

Reconciles trivially on the next staging->develop promotion (develop
already carries the identical value).
…I_ASSET gate) (#195)

* feat: make multi-asset default-active (remove NEXT_PUBLIC_ENABLE_MULTI_ASSET gate)

The multi-asset routes (/create, /asset/[id]) and the per-asset surface no longer sit behind the NEXT_PUBLIC_ENABLE_MULTI_ASSET build flag — they ship unconditionally. Whether the per-asset UI renders is driven purely by the runtime node capability (caps.multi_asset from /api/info), exactly like username_claim.

- features.ts: drop the MULTI_ASSET build flag; the runtime useFeatures().MULTI_ASSET (= caps.multi_asset) stays.
- create/page.tsx, asset/[id]/page.tsx: remove the `!FEATURES.MULTI_ASSET) notFound()` build guard (the runtime redirect-home guard stays).
- gates.mjs: MULTI_ASSET is no longer an env gate.
- screens.ts: /create + /asset/[id] are now default-active screens; register their page-level goldens.
- features.test.ts + page tests: drop the build-flag assertions / stale guard comments.

Golden baselines for the two routes must be generated against a multi_asset:true node via the regenerate-visual-baselines workflow before the Golden-Coverage-Audit goes green.

* test(e2e): add the multi-asset leg + generate the /create + /asset goldens

Adds the multi_asset:true leg the multi-asset-default surface needs, and the page-level baselines registered for /create + /asset[id] (so the Golden-Coverage-Audit passes).

- e2e-info-proxy: `E2E_INFO_MULTI_ASSET=true` mode reports multi_asset:true and passes the per-asset balance/send through 1:1 (the multi-asset app issues native requests); proxy unit suite extended to keep its strict 100% gate.
- e2e-local: thread `E2E_MULTI_ASSET` to the proxy; fix two latent regen bugs (the baseline sync-back missed `--update-snapshots=all` and was skipped by `set -e` when any spec failed).
- e2e/18..21 baselines: captured on m5me (noble container) against the local multi_asset:true node — specs 18-21 ran green (19 passed). These are golden-audit existence targets; CI's single-asset leg skips them.

* docs(handbook): document the multi-asset screens (create, portfolio, send-asset, asset-detail)

Adds spec-18..21 sections (EN + DE) for the multi-asset surface, referencing all 19 new baselines so the handbook <-> baseline consistency gate (sync-handbook-baselines, run in prebuild) passes. Each section follows the existing per-spec layout with the mobile/desktop viewport toggle.
* test: close the src/app unit-coverage gap to 100% function coverage (#189)

* test: close the src/app unit-coverage gap to 100% function coverage

Cover the untested route-component handlers so src/app/** reaches 100%
function coverage (was 75%):
- send/page.tsx: single-asset scan/Set-max/cancel/Done/phase handlers
  and no-funds banner; multi-asset select onChange, ?asset= deep-link
  default, and success Done button
- page.tsx: the unlock-screen handleReset escape hatch
- create/page.tsx: the createCoin onPhase callback
- receive/page.tsx: the clipboard-write rejection leg
- settings/page.tsx: extract the disabled-only Toggle handler and
  c8-ignore it (all shipped settings toggles are "Planned"/disabled)

Recalibrate the global functions threshold 86 -> 93 and add a per-glob
src/app/** functions:100 gate so the route surface cannot silently
regress again.

Closes #188

* test: query home-reset stubs by role/label to satisfy the button-inventory audit

The mocked Home child components used data-testids, which the
button-inventory audit (scans src/ incl. __tests__) flagged as orphans;
switch to role/aria-label queries like the QrScanModal stubs.

* test: define coverage-scope rule and remove the "MVP" concept (#190)

Coverage scope is now one deterministic criterion: code not behind a NEXT_PUBLIC_ENABLE_* build flag is default-active and must reach 100% (unit + e2e); env-gated code is exempt. No more triage / decorative / no-logic exemptions.

- vitest.config: encode the rule; split excludes into permanent (env-gated) vs DEBT (not env-gated, must be covered — this list only shrinks); point at ENV_GATED_FEATURES as the single source of truth.
- Remove the "MVP" term repo-wide (code comments, audit scripts, e2e/README.md) in favour of default-active / env-gated / always-on.
- Rename MVP_EXEMPT_* -> EXEMPT_* in the button-coverage audit.
- Fix mislabels: faucet (off-mainnet) and username resolve are always-on, not env-gated; the /network chart is in scope.

No behaviour change: coverage thresholds, the test set, and both audits are unchanged.

* test: cover the network-activity lib helpers to 100% (#192)

Bring src/lib/simulate-network.ts and src/lib/api/explorer.ts to 100% coverage and drop them from the vitest DEBT exclude list, so the strict src/lib/** 100% gate again spans all of src/lib. Per the coverage-scope rule (#190): not env-gated -> coverage-required.

- simulate-network: buildHistory (no-arg defaults + explicit args), nextSample (injected rng + Math.random default), plus deterministic-seed and timestamp-spacing assertions.
- explorer: getNetworkActivity across every branch — no URL configured, ok + non-empty data, empty array, non-array samples, non-ok response, and fetch rejection.

* test: cover the network-activity chart UI to 100% (#193)

Bring src/app/network/page.tsx and src/components/NetworkActivity.tsx to 100% coverage and drop them from the vitest DEBT exclude list. Per the coverage-scope rule (#190): not env-gated -> coverage-required.

- network/page: drives the poll state machine — placeholder->loaded swap, the simulated local tick, the explorer re-fetch (samples present vs empty), and the failed-initial-fetch path that still marks loaded and starts no interval (fake timers).
- NetworkActivity: data->geometry branches — populated vs empty window, live readouts, x-axis labels, path building.
- Two genuinely-unreachable defensive guards (catmullRomToBezier length, interval cleanup ref) are c8-ignored with a justification, matching the existing convention.

* test: cover decorative icons + root layout, finish the coverage DEBT burn-down (#194)

Cover the last non-env-gated files and remove them from the vitest exclude list, which now holds only env-gated files. Ratchet the global thresholds up to lock in the completed burn-down.

- PixelIcon: render every icon in the map (sprite -> 1x1 rects) plus the size/color/className/rest-prop surface.
- Logo / PixelLogo: default + explicit size, prop forwarding.
- RootLayout: metadata/viewport exports and the html/body shell (next/font/google mocked).
- vitest.config: exclude list is now env-gated-only; global floors raised to lines 92 / statements 92 / functions 94 / branches 85 (just under the live 92.9 / 92.3 / 94.3 / 85.8).

* ci: point E2E upstream node at ci.zkcoins.app (staging) (#197)

Staging mirror of #196 (which landed on develop). In the
feature -> staging -> develop -> main flow, staging is upstream of
develop, so the E2E_NODE_URL repoint from #196 does not reach
staging-based feature branches. Apply the identical change here so
staging (and feature PRs based on it, e.g. #195) target the live
ci.zkcoins.app node instead of the retired dev-api.zkcoins.app (502).

Reconciles trivially on the next staging->develop promotion (develop
already carries the identical value).

* feat: make multi-asset default-active (remove NEXT_PUBLIC_ENABLE_MULTI_ASSET gate) (#195)

* feat: make multi-asset default-active (remove NEXT_PUBLIC_ENABLE_MULTI_ASSET gate)

The multi-asset routes (/create, /asset/[id]) and the per-asset surface no longer sit behind the NEXT_PUBLIC_ENABLE_MULTI_ASSET build flag — they ship unconditionally. Whether the per-asset UI renders is driven purely by the runtime node capability (caps.multi_asset from /api/info), exactly like username_claim.

- features.ts: drop the MULTI_ASSET build flag; the runtime useFeatures().MULTI_ASSET (= caps.multi_asset) stays.
- create/page.tsx, asset/[id]/page.tsx: remove the `!FEATURES.MULTI_ASSET) notFound()` build guard (the runtime redirect-home guard stays).
- gates.mjs: MULTI_ASSET is no longer an env gate.
- screens.ts: /create + /asset/[id] are now default-active screens; register their page-level goldens.
- features.test.ts + page tests: drop the build-flag assertions / stale guard comments.

Golden baselines for the two routes must be generated against a multi_asset:true node via the regenerate-visual-baselines workflow before the Golden-Coverage-Audit goes green.

* test(e2e): add the multi-asset leg + generate the /create + /asset goldens

Adds the multi_asset:true leg the multi-asset-default surface needs, and the page-level baselines registered for /create + /asset[id] (so the Golden-Coverage-Audit passes).

- e2e-info-proxy: `E2E_INFO_MULTI_ASSET=true` mode reports multi_asset:true and passes the per-asset balance/send through 1:1 (the multi-asset app issues native requests); proxy unit suite extended to keep its strict 100% gate.
- e2e-local: thread `E2E_MULTI_ASSET` to the proxy; fix two latent regen bugs (the baseline sync-back missed `--update-snapshots=all` and was skipped by `set -e` when any spec failed).
- e2e/18..21 baselines: captured on m5me (noble container) against the local multi_asset:true node — specs 18-21 ran green (19 passed). These are golden-audit existence targets; CI's single-asset leg skips them.

* docs(handbook): document the multi-asset screens (create, portfolio, send-asset, asset-detail)

Adds spec-18..21 sections (EN + DE) for the multi-asset surface, referencing all 19 new baselines so the handbook <-> baseline consistency gate (sync-handbook-baselines, run in prebuild) passes. Each section follows the existing per-spec layout with the mobile/desktop viewport toggle.

---------

Co-authored-by: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant