Skip to content

Web UI: dual-theme design system, Carbon Slate palette, and the V1 demo fix - #241

Merged
zheli merged 14 commits into
mainfrom
ui-console-polish
Jul 10, 2026
Merged

Web UI: dual-theme design system, Carbon Slate palette, and the V1 demo fix#241
zheli merged 14 commits into
mainfrom
ui-console-polish

Conversation

@srikanth-bitdynamics

Copy link
Copy Markdown
Collaborator

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

  • The token layer resolves through CSS variables with a dark default and a light override, so a single data-theme flip re-themes every screen (theme.ts, persisted, applied before first paint — no flash).
  • New shell chrome: a topbar with the page title, an instance-switcher pill, a Commands button that opens the ⌘K palette, a health pill, a light/dark toggle, and a Docs link; an icon sidebar under a LocalNet group label with a pinned footer.

2. Carbon Slate palette

  • Replaces the previous cobalt-on-navy, where the accent and the neutrals shared one blue hue family and read flat. Carbon Slate is a near-neutral graphite ramp with a single indigo-violet accent that sits well off the neutral hue, so action separates cleanly from chrome in both themes.
  • Every text/background pair verified WCAG 2.1 AA in both themes (body ~15:1, secondary ~8:1, status ~5:1, faint ~3.5:1, white-on-indigo button 5.7:1). Light-mode green/red/amber were darkened to clear 4.5, and the focus ring moved to the indigo family.

3. Console design polish (theme-agnostic)

  • One status renderer (StatusBadge): Title-Case label + a redundant dot, so status is never conveyed by colour alone.
  • 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 confirm dialog replaces native confirm() for destructive actions, showing the real dpm command inline (aligned with feat(cli): rename localnet clean to remove, keep clean as alias #227's cleanremove rename).
  • Layout-matched skeletons replace bare "Loading…" text so tables arrive in place instead of popping in.
  • Flat selection replaces the coloured left-border row idiom across the ACS, filter chips, and DAR/token/skill rows, with constant padding so rows no longer shift on click.
  • One depth technique per surface; radii normalized to 2/4/8; decorative gradients and hover-lifts removed from data views; transition:all scoped; microcopy tightened; error/empty states given cause + action + retry.

4. token demo works on standard (V1) instances

  • Previously the one-click demo required a token-standard-v2 instance — it always created a new on-ledger V2 instrument (needing the splice-test-token-v2 DAR a standard release doesn't publish), so on a normal LocalNet it failed.
  • RunDemo now 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

  • tsc clean, 218/218 frontend tests, full Go suite, make lint 0 issues.
  • Both themes reviewed live against a running LocalNet across Overview, Explorer, DAR, and Tokens; the V1 demo verified end-to-end (CLI + UI) funding a holder with Amulet.

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.
srikanth-bitdynamics and others added 11 commits July 10, 2026 21:34
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.
@zheli
zheli merged commit d4a35a9 into main Jul 10, 2026
6 checks passed
@zheli
zheli deleted the ui-console-polish branch July 10, 2026 19:57
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.

2 participants