PR G: portal /transactions page + 12-state machine view #11

Merged
nsatoshi merged 1 commits from devin/1776876388-portal-transactions into main 2026-04-22 17:18:53 +00:00
Owner

Implements step 8 from the architecture gap-analysis — the first portal page backed by the CurrenciCombo orchestrator (PR A–F). Stacks on the live Solace portal branch (PR #2), not on the orchestrator branches, because this is a frontend-only change.

What lands

Routes

  • GET /transactions — list of recent plans. Columns: Plan ID, State (chip), Instrument hint, Owner, Updated, .
  • GET /transactions/:planId — detail view with three cards:
    1. 12-state machine grid from architecture §8. Current state glows green; visited states in indigo; non-reached states dimmed. Terminal states (COMMITTED / ABORTED / CLOSED) visually distinct.
    2. Audit trailfrom_state → to_state, actor, actor_role (SoD chip), reason, timestamp. Straight from the state_transitions table introduced by PR A.
    3. Signed event stream — id, type, signature, prev_hash, timestamp. Straight from the events table introduced by PR D. Demonstrates the hash chain visually.

Backend wiring — src/services/orchestrator.ts

  • listPlans()GET /api/plans
  • getPlanState(id)GET /api/plans/:id/state
  • getPlanEvents(id)GET /api/plans/:id/events
  • probeOrchestrator()GET /health

When VITE_ORCHESTRATOR_URL is unset (orchestrator not yet deployed), every call returns deterministic demo data so the 12-state view and audit trail still render. Matches how proxmox / dbisCore already degrade. Each card gets a LIVE / DEGRADED / DEMO badge so it is obvious which is which.

UI chrome

  • New Transactions nav entry (GitBranch icon) between Transaction Builder and Accounts.
  • Orchestrator registered in backendCatalog so the existing Dashboard BackendStatusBar picks it up automatically (LIVE when VITE_ORCHESTRATOR_URL is set, MOCKED otherwise).

CSS

All new styles appended to src/index.css under a clearly-labelled /transactions page (PR G) block — no existing rules changed. Covers state-pill grid, source badges, state chips, role chips, portal table rows.

Out of scope

  • SSE live-stream UI. The endpoint exists on the orchestrator (PR D) but the detail page polls /events once on mount. Adding an EventSource subscription is a follow-up.
  • Pagination. listPlans() returns whatever the orchestrator returns — no page-size negotiation yet.
  • Write actions. No approve / release / validate buttons in this PR. Those need the full SoD identity layer (architecture §4.6) wired, which is a separate effort.

Verification

$ npx tsc --noEmit     # clean
$ npm run build         # built in 605ms

UI verification of the three cards + demo-data fallback will be performed in test mode after merge (or by a reviewer locally).

Series order

A → B → C → D → E → F → G → H.

Base: devin/1776532671-solace-bank-portal (the live-portal branch, PR #2). Diff here is G-only.

Implements **step 8** from the architecture gap-analysis — the first portal page backed by the CurrenciCombo orchestrator (PR A–F). Stacks on the live Solace portal branch (PR #2), not on the orchestrator branches, because this is a frontend-only change. ## What lands ### Routes - `GET /transactions` — list of recent plans. Columns: Plan ID, State (chip), Instrument hint, Owner, Updated, `›`. - `GET /transactions/:planId` — detail view with three cards: 1. **12-state machine grid** from architecture §8. Current state glows green; visited states in indigo; non-reached states dimmed. Terminal states (COMMITTED / ABORTED / CLOSED) visually distinct. 2. **Audit trail** — `from_state → to_state`, actor, actor_role (SoD chip), reason, timestamp. Straight from the `state_transitions` table introduced by PR A. 3. **Signed event stream** — id, type, signature, prev_hash, timestamp. Straight from the `events` table introduced by PR D. Demonstrates the hash chain visually. ### Backend wiring — `src/services/orchestrator.ts` - `listPlans()` → `GET /api/plans` - `getPlanState(id)` → `GET /api/plans/:id/state` - `getPlanEvents(id)` → `GET /api/plans/:id/events` - `probeOrchestrator()` → `GET /health` When `VITE_ORCHESTRATOR_URL` is unset (orchestrator not yet deployed), every call returns **deterministic demo data** so the 12-state view and audit trail still render. Matches how `proxmox` / `dbisCore` already degrade. Each card gets a `LIVE` / `DEGRADED` / `DEMO` badge so it is obvious which is which. ### UI chrome - New **Transactions** nav entry (GitBranch icon) between Transaction Builder and Accounts. - Orchestrator registered in `backendCatalog` so the existing Dashboard `BackendStatusBar` picks it up automatically (`LIVE` when `VITE_ORCHESTRATOR_URL` is set, `MOCKED` otherwise). ### CSS All new styles appended to `src/index.css` under a clearly-labelled `/transactions page (PR G)` block — no existing rules changed. Covers state-pill grid, source badges, state chips, role chips, portal table rows. ## Out of scope - **SSE live-stream UI.** The endpoint exists on the orchestrator (PR D) but the detail page polls `/events` once on mount. Adding an `EventSource` subscription is a follow-up. - **Pagination.** `listPlans()` returns whatever the orchestrator returns — no page-size negotiation yet. - **Write actions.** No approve / release / validate buttons in this PR. Those need the full SoD identity layer (architecture §4.6) wired, which is a separate effort. ## Verification ``` $ npx tsc --noEmit # clean $ npm run build # built in 605ms ``` UI verification of the three cards + demo-data fallback will be performed in test mode after merge (or by a reviewer locally). ## Series order A → B → C → D → E → F → **G** → H. Base: `devin/1776532671-solace-bank-portal` (the live-portal branch, PR #2). Diff here is G-only.
Author
Owner

Test report — /transactions page

Ran live against a local Vite build at http://127.0.0.1:5173 with orchestrator intentionally unset (the DEMO fallback — only exercisable path until orchestrator ships). 2 tests, 14 sub-assertions, all passed.

  • Test 1 — /transactions list view — passed
  • Test 2 — /transactions/demo-pay-014 detail (12-state grid + audit trail + signed event stream) — passed
Test 1 — list (DEMO badge, 4 rows) Test 2 — detail (COMMITTED, 8 audit rows, 8 events)
list detail
Adversarial DOM-assertion check (click to expand)

Headline adversarial check: the PARTIALLY_EXECUTED pill must be pending and not visited (demo transitions never entered that state). A broken visited set would fail here. It passed, along with all other 11 pill-class checks.

=== PILL CLASSES ===
OK DRAFT                  {current:false,visited:true, pending:false,terminal:false}
OK INITIATED              {current:false,visited:true, pending:false,terminal:false}
OK PRECONDITIONS_PENDING  {current:false,visited:true, pending:false,terminal:false}
OK READY_FOR_PREPARE      {current:false,visited:true, pending:false,terminal:false}
OK PREPARED               {current:false,visited:true, pending:false,terminal:false}
OK EXECUTING              {current:false,visited:true, pending:false,terminal:false}
OK PARTIALLY_EXECUTED     {current:false,visited:false,pending:true, terminal:false}
OK VALIDATING             {current:false,visited:true, pending:false,terminal:false}
OK COMMITTED              {current:true, visited:false,pending:false,terminal:true }
OK ABORTED                {current:false,visited:false,pending:true, terminal:true }
OK UNWIND_PENDING         {current:false,visited:false,pending:true, terminal:false}
OK CLOSED                 {current:false,visited:false,pending:true, terminal:true }
=== COUNTS === {pillCount:12, auditRows:8, eventRows:8}
=== ROLE CHIPS (distinct) === submitter, coordinator, approver, releaser, validator

Audit row spot-checks (all OK):

  • Row 1: ∅ → DRAFT / ops.bob / submitter / plan created
  • Row 5: READY_FOR_PREPARE → PREPARED / ops.chen / approver / approve — prepare (SoD)
  • Row 8: VALIDATING → COMMITTED / ops.eve / validator / reconciled + committed (SoD)

Event stream spot-checks (all OK):

  • Row 1 signature demo-sig-0000, prev_hash
  • Row 8 type state.committed

Caveats:

  • Only the DEMO path is exercisable today (orchestrator not deployed). LIVE / DEGRADED branches are wired identically in code but not verified end-to-end.
  • MetaMask-connect flow was not re-verified here — covered by PR #2.

Session: https://app.devin.ai/sessions/a33f9ebe303d413aab86e04252a1b4a1

## Test report — `/transactions` page Ran live against a local Vite build at `http://127.0.0.1:5173` with orchestrator intentionally unset (the `DEMO` fallback — only exercisable path until orchestrator ships). 2 tests, 14 sub-assertions, all passed. - Test 1 — `/transactions` list view — **passed** - Test 2 — `/transactions/demo-pay-014` detail (12-state grid + audit trail + signed event stream) — **passed** | Test 1 — list (DEMO badge, 4 rows) | Test 2 — detail (COMMITTED, 8 audit rows, 8 events) | |---|---| | ![list](https://app.devin.ai/attachments/7ddf7e80-2535-438c-a83d-26c937d50701/pr11-step1-list.png) | ![detail](https://app.devin.ai/attachments/d1748136-cacc-4f45-a378-940ce5d0ac2c/pr11-step2-detail.png) | <details> <summary>Adversarial DOM-assertion check (click to expand)</summary> Headline adversarial check: the `PARTIALLY_EXECUTED` pill must be `pending` and **not** `visited` (demo transitions never entered that state). A broken `visited` set would fail here. It passed, along with all other 11 pill-class checks. ``` === PILL CLASSES === OK DRAFT {current:false,visited:true, pending:false,terminal:false} OK INITIATED {current:false,visited:true, pending:false,terminal:false} OK PRECONDITIONS_PENDING {current:false,visited:true, pending:false,terminal:false} OK READY_FOR_PREPARE {current:false,visited:true, pending:false,terminal:false} OK PREPARED {current:false,visited:true, pending:false,terminal:false} OK EXECUTING {current:false,visited:true, pending:false,terminal:false} OK PARTIALLY_EXECUTED {current:false,visited:false,pending:true, terminal:false} OK VALIDATING {current:false,visited:true, pending:false,terminal:false} OK COMMITTED {current:true, visited:false,pending:false,terminal:true } OK ABORTED {current:false,visited:false,pending:true, terminal:true } OK UNWIND_PENDING {current:false,visited:false,pending:true, terminal:false} OK CLOSED {current:false,visited:false,pending:true, terminal:true } === COUNTS === {pillCount:12, auditRows:8, eventRows:8} === ROLE CHIPS (distinct) === submitter, coordinator, approver, releaser, validator ``` Audit row spot-checks (all OK): - Row 1: `∅ → DRAFT` / `ops.bob` / `submitter` / `plan created` - Row 5: `READY_FOR_PREPARE → PREPARED` / `ops.chen` / `approver` / `approve — prepare (SoD)` - Row 8: `VALIDATING → COMMITTED` / `ops.eve` / `validator` / `reconciled + committed (SoD)` Event stream spot-checks (all OK): - Row 1 signature `demo-sig-0000`, prev_hash `∅` - Row 8 type `state.committed` </details> **Caveats:** - Only the `DEMO` path is exercisable today (orchestrator not deployed). `LIVE` / `DEGRADED` branches are wired identically in code but not verified end-to-end. - MetaMask-connect flow was not re-verified here — covered by PR #2. Session: https://app.devin.ai/sessions/a33f9ebe303d413aab86e04252a1b4a1
nsatoshi changed target branch from devin/1776532671-solace-bank-portal to main 2026-04-22 17:15:44 +00:00
nsatoshi added 1 commit 2026-04-22 17:15:44 +00:00
Adds the first portal page backed by the CurrenciCombo orchestrator
(PR A-F). Renders:

- /transactions           \u2014 list of recent plans with state chip,
                            instrument hint, owner, last-updated.
- /transactions/:planId   \u2014 detail view with three cards:
    1. 12-state machine grid highlighting current + visited states.
    2. Audit trail table: from\u2192to, actor, actor_role (SoD),
       reason, timestamp \u2014 straight from state_transitions.
    3. Signed event stream: id, type, signature, prev_hash, at \u2014
       straight from events (PR D).

Backend wiring (src/services/orchestrator.ts):
  - listPlans()        GET /api/plans
  - getPlanState(id)   GET /api/plans/:id/state
  - getPlanEvents(id)  GET /api/plans/:id/events
  - probeOrchestrator() GET /health

If VITE_ORCHESTRATOR_URL is unset (orchestrator not deployed yet),
every call returns deterministic demo data so the 12-state view and
audit trail still render. Mirrors how proxmox/dbisCore fall back.
Badge on every card shows LIVE / DEGRADED / DEMO.

Also registers the orchestrator in backendCatalog so the existing
BackendStatusBar on the Dashboard picks it up automatically.

PortalLayout gets a new 'Transactions' nav item (GitBranch icon).

Portal code from PR #2 is untouched except for the new route + nav
entry.
nsatoshi force-pushed devin/1776876388-portal-transactions from 07d47e726e to 0bda0b76f1 2026-04-22 17:18:45 +00:00 Compare
nsatoshi merged commit b66ec0a78f into main 2026-04-22 17:18:53 +00:00
nsatoshi deleted branch devin/1776876388-portal-transactions 2026-04-22 17:18:56 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: d-bis/CurrenciCombo#11