feat: add txs information on dashboard#3
Merged
Merged
Conversation
Spec at docs/superpowers/specs/2026-05-07-m3-dashboard-design.md. Plan at docs/superpowers/plans/2026-05-07-m3-dashboard-implementation.md. v2 architecture: tx3-lift tracker runs as an external sidecar writing SQLite; the dashboard is a TanStack Start app whose Nitro server functions read that SQLite directly via Kysely + better-sqlite3. No Rust backend on the dashboard side. MVP scope: matches list + match detail with parties.
… directly Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous value was the mainnet ticket_policy (1d9c0b541adc300c19ddc6b9fb63c0bfe32b1508305ba65b8762dc7b). With the tracker pointed at preview.utxorpc-v0.demeter.run, that filter would never match anything. The correct preview policy comes from the TII's preview profile: 4672f28ce36e492e722392359f71e9a9442646f81d856f92d7e163f1. Plan Task 2 template updated to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds createDb in src/lib/db.ts: typed Kysely<DashboardDatabase> that wraps an existing better-sqlite3 instance (used by tests with :memory:), or opens tracker.db readonly+fileMustExist with WAL journaling. Path resolves from opts.path, then TRACKER_DB_PATH env, then ./tracker.db. Schema types cover matches, cursor, and _schema_versions rows. Vitest test seeds an in-memory schema and runs a typed select to prove the type wiring.
- Mark all row-interface fields readonly to match the read-only connection contract. - Seed _schema_versions in the test SCHEMA_SQL so future tests against that table don't fail at runtime. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a dialect-agnostic adapter for the subset of `matches.lifted` the MVP renders. `bytesToHex` re-encodes the tracker's `[u8]`-as-int-array fields (addresses, hashes) into lowercase hex, `truncateHex` shortens them with a U+2026 ellipsis, and `parseLifted` shapes the JSON into the typed `Lifted` record consumed by the matches list and detail views. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… test - RawLifted.parties is Record<string, unknown>; the inner narrow takes care of each value (was a forced cast). - Add 9th test for truncateHex when hex.length === edge * 2 exactly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements AP-1 (listMatches, newest-first, limit clamped to [1,200]) and AP-2 (getMatch by hex tx_hash, throws on invalid hex) as typed Kysely queries returning a consumer-friendly MatchRow shape. Both reuse parseLifted to populate parties + rawLifted and convert matched_at (unix seconds) to Date. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t8Array branch
- Replace local RawMatch with Pick<Selectable<MatchesRow>, …> so column types stay tied to the canonical schema in db.ts.
- better-sqlite3 always returns Buffer for BLOB columns; drop the defensive Uint8Array branch and call blob.toString('hex') directly.
- Add a getMatch test covering empty / non-hex / odd-length input.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bare aria-hidden compiles to aria-hidden={true} which React serializes
correctly, but the explicit string form is what a11y linters and the
WAI-ARIA spec examples use. Self-documents intent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the M1 placeholder at `/` with the AP-1 matches list. A `createServerFn` opens a fresh Kysely DB, runs `listMatches(db, 50)`, and destroys the connection in a `finally` block. The route's loader invokes the server fn; the component renders a header, an empty-state card when there are no rows, and otherwise a table of Tx, Hash (linked to `/txs/$hash`), Slot, Parties, and When.
…JSON tx_name is denormalized into both the column and the lifted payload by the tracker. The column is the indexed/authoritative source; the JSON is opaque text. If parseLifted ever fails to extract tx_name (malformed or unexpected shape), the row would render an empty TxNamePill. Reading from the column eliminates that latent failure mode. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implement AP-2 detail at /txs/$hash: - Server fn (.inputValidator + .handler) opens createDb, runs getMatch, destroys in finally; returns MatchRow | null. - Loader throws notFound() on null so TanStack Router shows the 404 page. - Component renders TxNamePill + protocol/profile/slot caption, full hex, matched_at (UTC sliced to seconds), back link, parties section (chip grid or "No parties annotated" copy), and a <details> accordion with pretty-printed raw lifted JSON. Delete the placeholder /txs index route — the matches list now lives at /. RouteTree.gen.ts regenerates accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The DB invariant says rawLifted is valid JSON (parseLifted has already parsed it once on the server), but if the wire transport ever truncates or corrupts it, an unguarded JSON.parse in the render body would crash the page rather than show the raw text. Tiny try/catch keeps the pretty-printer for the happy path and falls back to the raw string. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the Overview/Transactions nav (the /txs route was deleted in Task 9, leaving a dead link). The header now shows brand link, a "Matches view" caption, and ThemeToggle pushed right via ml-auto. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the M1 root README (Rust backend + Axum + utxorpc ingestion) with the M3 v2 architecture: TanStack Start + external tx3-lift tracker + SQLite as the integration boundary. Add three docs files under docs/ covering the two-process topology with C4 diagrams, the AP-1/AP-2 access patterns, and the operator-facing run guide. Slim frontend/README.md from the TanStack Start template to a 3-line pointer. Tracker commit hash in docs/running.md "Tested with" left as a placeholder for Task 12 (smoke checklist) to fill. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- The L1 System node now reads 'Tracker (sidecar) + SSR app …' so the gRPC relation in the L1 diagram is no longer attached to a node a reader would interpret as just the SSR web app. - 'Production build' section now opens with 'From the repo root:' and uses 'cd dashboard/frontend', matching the first-run section's working directory assumption. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI:
- Run pnpm test --run (was missing from frontend-ci, so unit tests were never gated).
- Add pnpm rebuild better-sqlite3 step so the native binding actually builds on a fresh runner.
Docs:
- docs/running.md empty-state troubleshooting: replace the bogus 'cursor slot' wording with the actual UI message ('No matches yet — confirm the tracker is running.').
- docs/architecture.md: 'reused across requests' was wrong; we open a fresh Kysely per request and destroy in finally. Document that.
- docs/running.md 'Tested with' tracker commit: pin to 04d0b90 (the head of tx3-lang/tx3-lift main at testing time).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With multi-source tracking, the same tx_name can come from different protocols (e.g. swap_a_to_b in vyfi vs strike-staking has overlapping generic verbs). Adding the protocol column at column-1 makes the row self-explanatory: 'protocol → operation' reads top-to-bottom for the operator. Styled as a muted pill so it doesn't compete with the primary-colored TxNamePill. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ewer
Replace the plain <pre> in 'Raw lifted JSON (debug)' with
react-json-view-lite (~5 KB). Each object/array is clickable to
collapse/expand; primitives get distinct colors. Top-level expanded by
default; nested nodes start collapsed via shouldExpandNode={level < 1}
so the operator gets an overview and drills down where they care.
Falls back to a plain <pre> when JSON.parse fails or yields a non-object,
preserving the resilience of the previous version.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The lib's darkStyles ships with a teal-tinted background that clashes with the site's deep-dark + pink-accent palette. Replace it with a custom theme that uses Tailwind palette tokens: - transparent container (lets the parent details bg-muted/20 show) - foreground for keys, emerald-400 for strings, amber-400 for numbers/booleans - muted-foreground for null/undefined/punctuation - primary (pink) for the expand/collapse chevrons The custom theme is derived as Partial<typeof darkStyles> rather than importing the lib's StyleProps interface, which is internal-only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…viewer Dropping the lib's CSS in the previous commit also dropped the ::before pseudo-element rules that supplied the ▾/▸ glyphs. The icon spans were rendered with reserved width but no content, so expandable nodes looked indistinguishable from primitives. Tailwind 4 generates the content via before:content-['…']; same effect, no lib CSS dependency. Also adds an ellipsis hint to the collapsed-content state so collapsed objects show '…' instead of an empty span. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…bold w-3/text-xs (inherited) was tight. Goes to w-4 + text-base + font-bold + leading-none so the ▾/▸ glyphs render visibly larger without breaking the row's vertical rhythm. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… detail header The dashboard is going back to single-protocol focus (Indigo for now; multi-protocol moves to a tabbed UX later, not a mixed list), so the protocol column adds noise without value. Reverts to the M3 spec's five-column shape (Tx · Hash · Slot · Parties · When). Detail header gets a 'View on cexplorer ↗' link in the meta row, next to '← back to list'. The URL is derived from the match's profile_name so mainnet / preview / preprod each go to the right subdomain. The hash itself stays as plain selectable text — better for copy/paste than turning it into a link. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps each <td>'s content with a TanStack Router <Link>, so any click in the row navigates to /txs/$hash. The padding moves from <td> to <Link className='block …'> so the link fills the cell — no dead space. Why per-cell instead of <tr onClick>: preserves middle-click (open in new tab), Cmd+click, right-click context menu, and keyboard navigation (Tab + Enter), all of which an onClick handler would silently break. Adds 'group cursor-pointer hover:bg-muted/30' on <tr> for row-level hover feedback, and 'group-hover:underline' on the hash text so the key affordance still gets emphasis on row hover. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.