Web UI: dual-theme design system, Carbon Slate palette, and the V1 demo fix - #241
Merged
Conversation
Restyle the Web UI to the provided design and add a light theme
alongside the existing dark one, without changing the app's flow or
information architecture.
Theming
- index.css now defines the full token ramp as CSS variables with a
dark default (:root) and a light override (:root[data-theme]). The
W.* tokens in tokens.ts resolve through those variables, so every
screen that already used W.* themes for both palettes with no
per-screen color edit — one architecture change instead of touching
19 screens by hand.
- theme.ts owns the active theme: written to data-theme on <html>,
persisted to localStorage, applied before first paint (no flash).
useTheme() drives the topbar toggle.
- The 55 `${W.x}NN` hex-alpha concatenations that would become invalid
once W.x is a variable (var(--accent)1A is not a color) are replaced
with a tint() helper backed by color-mix. The handful of hardcoded
hexes that actually break in light mode — dark text meant to sit on
the cobalt fill, dark switch-track backgrounds — move to W.onAccent /
W.borderHi so they flip; the dataviz mid-tones (teal/amber/green
series colors) stay fixed since they read on either background.
Shell chrome
- Topbar: page title, an instance-switcher pill with a status dot and
wide-caps label, a Commands button that opens the ⌘K palette, a
Connected health pill, the light/dark toggle, and a Docs link.
- Sidebar: a LocalNet group label, icon + label nav (one stroke glyph
per route), and a pinned footer showing loopback-only and the live
schema version.
- CommandPalette gains an openPalette() event hook so the topbar
button can open it while the component keeps ownership of its state.
Verified: tsc clean, 218/218 frontend tests, Go UI suite, make lint
0 issues, and both themes reviewed live against a running LocalNet
(Overview, Explorer, DAR, Agent Skills) — the toggle flips the entire
app.
`token demo` (and the Web UI "Launch demo token" button) required a token-standard-v2 instance: it always created a new on-ledger V2 instrument, which needs the splice-test-token-v2 DAR that a standard release doesn't publish — so on a normal LocalNet the demo failed with "the test-token DAR isn't published for this instance's Splice version". RunDemo now adapts to the instance instead of assuming V2: - token-standard-v2 instance → unchanged: allocate an issuer, create a V2 instrument, mint the supply, seed a holder. - standard instance (V1) → there is no create/mint (Amulet is the only instrument), so allocate a holder and fund it with Amulet moved from the role's network-funded party (app-user) via the faucet — a transferable balance in one click, no alpha DAR required. The path is chosen by the instance's catalogue channel (SupportedVersions[version].IsAlpha()), not the alpha_protocol_enabled flag — that flag is true on both standard and V2 0.6.x instances and so can't discriminate. Unknown/uncurated versions default to the V1 path, which works on any running instance. Both surfaces call RunDemo, so the CLI and Web UI are fixed by one change; the CLI help text is updated to describe both paths. Adds a V1-path regression test and threads a routing seam through the existing V2 tests so they still exercise the V2 flow.
Two related passes to make the Web UI read as crafted and considered, both theme-aware (dark + light). Palette (Carbon Slate) - Replace the cobalt-on-navy palette, which shared one blue hue family between accent and neutrals and read flat. Carbon Slate uses a near-neutral graphite ramp with a single indigo-violet accent that sits well off the neutral hue, so action separates from chrome. - Every text/background pair verified WCAG AA in both themes (body 15:1, secondary ~8:1, status ~5:1, faint ~3.5:1, white-on-indigo button 5.7:1); the light-mode greens/reds/amber were darkened to clear 4.5, and the focus ring moved to the indigo family. Design polish (theme-agnostic) - One status renderer (StatusBadge): Title-Case label + redundant dot, so status is never color-only. - One ledger-id renderer (MonoId): middle-truncation with click-to-copy and tabular figures, replacing tail-only truncation that hid the discriminating suffix. - In-app ConfirmDialog replaces native confirm() for destructive actions, with the real dpm command shown inline. - Layout-matched Skeleton loaders replace bare "Loading…" text so tables don't pop in and shift. - Replace the coloured left-border selection idiom across the ACS, filter chips, and DAR/token/skill rows with a flat active fill and constant padding, so rows no longer shift on click. - One depth technique per surface (hairline or shadow, not both); radii normalized to 2/4/8; decorative gradients and per-element hover-lifts removed from data views; transition:all scoped; microcopy tightened; error/empty states given cause + action + retry and left-aligned. Verified: tsc clean, 218/218 frontend tests, both themes reviewed live against a running LocalNet across Overview, Explorer, DAR, and Tokens.
Two panels queried metrics Splice 0.6.4 does not provide, so they never populated: - ACS lookup buffer used daml_participant_api_index_db_active_contract_lookup_batch_buffer_length, which is no longer emitted. Point it at the live daml_participant_api_index_active_contracts_buffer_size gauge. - The latency panels used histogram_quantile on the sequencing-duration histogram, which 0.6.4 exports with only the +Inf bucket, so quantiles are NaN regardless of load. Show the computable average (sum/count) instead, labelled as an average, and hide the p50/p95/p99 strip when the backend can't compute those (it returns on versions whose histograms carry finite buckets).
Comment-only pass over the console. The component and shell files had
grown header blocks that narrated redesign history and editorialised
about the audience ("the discipline an auditor relies on", "reads as
unfinished", "one depth technique") rather than documenting the code.
Trim those to the load-bearing "why" and drop the asides; the metric,
race-condition, and accessibility comments are left intact.
Also fix palette naming left over from before the Carbon Slate swap:
the accent is no longer cobalt, so the design-token, CSS, and Button
docs that still called it that were inaccurate. The chart ramp stays
labelled cobalt — those hexes really are cobalt-blue.
One small non-comment change: MonoId's clipboard copy collapses the
redundant Promise temporary into a single optional-chained call.
The Web UI Metrics screen shows the mean sequencing latency because Splice 0.6.4 exports the submission-duration histogram with only the +Inf bucket, so histogram_quantile (p50/p95/p99) is NaN there while the mean (sum/count) is exact. The CLI `dpm localnet metrics` and the /metrics/summary handler still led with those NaN percentiles. Add a MediatorAvg query to the shared metricsq map so both surfaces pick it up automatically. The CLI text now leads with `avg` and prints p50/p95/p99 only when the histogram carries finite buckets (rather than a row of dashes); the JSON and the handler expose `avg_ms` alongside the percentiles. Percentiles remain for Splice versions whose histograms are bucketed, so no data is lost where it exists.
Cut the narration-track comments across the Web UI and the token-demo backend — redesign-history headers, step-by-step narration, section banners, and prose that merely restated the code — down to load-bearing why. Kept and tightened the genuine rationale: concurrency/ordering constraints, accessibility reasons, the iframe-sandbox security note, and the Splice-0.6.x substitute-metric notes the accuracy bar depends on. Comments only (plus one empty catch collapsed to a single line). The frontend type-checks and the full Go suite passes.
New sessions (no stored preference) now open in light. The JS default and the useSyncExternalStore server snapshot return "light", and the root <html> carries data-theme="light" so the first paint is light too, with a matching theme-color. A stored preference and the toggle still win.
The AreaChart built its gradient id from series.label, so a label with
spaces ("ACS lookup buffer") produced url(#area-ACS lookup buffer) — an
invalid reference, which falls back to the initial fill value, black. It
was invisible on the dark background and became a solid black block once
light became the default. Derive the id from useId() instead, so it is
always url-safe and unique.
Placeholder renders only for the path="*" wildcard, but its comment and
copy ("Route stub for screens whose backend hasn't landed yet", "Not
implemented yet in this build") implied unfinished features exist. Reword
to a plain not-found message.
Third surface of the same fix already applied to the CLI and Web UI. The bundled canton-localnet dashboard led with histogram_quantile p95/p50 latency panels, which are NaN on stock Splice 0.6.4 (its sequencing histogram exports only the +Inf bucket), and an ACS panel querying daml_participant_api_index_db_active_contract_lookup_batch_buffer_length, a metric 0.6.4 no longer emits. - Both latency panels now show the mean (sum/count) in seconds, labelled as an average, with a note that percentiles return on versions whose histograms carry finite buckets. - The ACS panel points at the live daml_participant_api_index_active_ contracts_buffer_size gauge. - docs/dashboard-customization.md updated to match, and its claim that the p95 panel was "audited on stock Splice 0.6.4" (the NaN case) is removed.
The committed index.html referenced hashed Vite assets (assets/index-*.js/.css) that are git-ignored and thus never committed, so a fresh checkout pointed at nonexistent files. Restore the tracked DEVKIT_FRONTEND_PLACEHOLDER version and document, in the file and .gitignore, that a real build must not be committed over it. Add TestFrontend_TrackedDistIsPlaceholder, which asserts the git-tracked (HEAD) dist/index.html still carries the placeholder sentinel. Existing tests only checked the working-tree file (which make frontend legitimately overwrites) and accepted either a placeholder or a real build, so neither caught this regression class.
Commit 8180a80 renamed Grafana panel id=14 from "ACS Lookup Buffer Length" to "ACS Lookup Buffer" (and switched its metric query to the live 0.6.4 signal) but did not update the pinning test, so TestDashboardHasACSAndThroughputPanels failed in CI. Align the expected title with the shipped dashboard JSON.
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.
The full Web UI design pass plus the token-demo fix, in one branch. Supersedes #237 (which carried the dual-theme work on its own and was never merged).
1. Dual-theme design system + shell chrome
data-themeflip re-themes every screen (theme.ts, persisted, applied before first paint — no flash).LocalNetgroup label with a pinned footer.2. Carbon Slate palette
3. Console design polish (theme-agnostic)
StatusBadge): Title-Case label + a redundant dot, so status is never conveyed by colour alone.MonoId): middle-truncation with click-to-copy and tabular figures, replacing tail-only truncation that hid the discriminating suffix.confirm()for destructive actions, showing the realdpmcommand inline (aligned with feat(cli): rename localnet clean to remove, keep clean as alias #227'sclean→removerename).transition:allscoped; microcopy tightened; error/empty states given cause + action + retry.4.
token demoworks on standard (V1) instancessplice-test-token-v2DAR a standard release doesn't publish), so on a normal LocalNet it failed.RunDemonow adapts to the instance: token-standard-v2 → create/mint/seed as before; standard (V1) → allocate a holder and fund it with Amulet moved from the role's network-funded party, giving a transferable balance in one click. Routed by the instance's catalogue channel. Both the CLI and the Web UI's "Launch demo token" button are fixed by the one change.Verification
tscclean, 218/218 frontend tests, full Go suite,make lint0 issues.