Skip to content

feat(ui): UI modernization — sidebar/rows/build, dark-first design system, command center - #212

Open
schronck wants to merge 47 commits into
mainfrom
feat/ui/bitsafe-design
Open

feat(ui): UI modernization — sidebar/rows/build, dark-first design system, command center#212
schronck wants to merge 47 commits into
mainfrom
feat/ui/bitsafe-design

Conversation

@schronck

@schronck schronck commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #211 (base fix/ui/bugs) — review/merge after it. Developed against mock mode.

Summary

Applies the bitsafe-design system across the whole app — dark by default, faithful to the skill's tokens. Layouts are unchanged; this is a design-system pass, not a restructure.

Theme (the bulk)

  • Warm-neutral substrate #0F0E0D / #1E1A17 / #2A2420 (not flat gray).
  • #D63A0F action accent — the AA-safe tone, not the decorative #FF6633.
  • Space Grotesk + Roboto Mono on every number / address / ID; tracked-out mono eyebrow headers.
  • Card anatomy: 1px border, no rest shadow. 6px buttons (sentence-case), 4px chips, accent focus, linear hover (no scale transforms). Deep warm dialog/menu shadows.
  • Dark-first default (light still available via the toggle).

Component polish

  • Pulsing status dot for peers — connected (green) / unreachable (red) / handshake-failed (amber) / local (accent); only a live/connected peer pulses a soft halo (the system's one blessed motion), prefers-reduced-motion honored.
  • Login: dark gradient hero with the restrained node-cluster motif + mono eyebrow.
  • Fix: the selected sidebar nav item's count badge flips to white so it no longer blends into the accent fill.

Notes

  • Adds @fontsource/roboto-mono. Keeps the unified 48px rows + large-screen table inset.
  • Build green, lint unchanged (51 pre-existing), no console errors across all tabs/dialogs.

@socket-security

socket-security Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​fontsource/​roboto-mono@​5.2.9100100868690

View full report

@schronck schronck changed the title feat(ui): BitSafe design-system modernization (dark-first) feat(ui): UI modernization (mock mode + dark-first design system + command center) Jun 22, 2026
@schronck
schronck changed the base branch from fix/ui/bugs to main June 22, 2026 15:00
@schronck schronck changed the title feat(ui): UI modernization (mock mode + dark-first design system + command center) feat(ui): UI modernization — sidebar/rows/build, dark-first design system, command center Jun 22, 2026
@schronck
schronck changed the base branch from main to feat/ui/mock-mode June 22, 2026 15:01
@schronck
schronck force-pushed the feat/ui/bitsafe-design branch 2 times, most recently from 39d78cb to 9a1ff9a Compare June 22, 2026 17:17
Base automatically changed from feat/ui/mock-mode to main June 23, 2026 12:54
@schronck schronck self-assigned this Jul 3, 2026
@schronck
schronck marked this pull request as draft August 12, 2026 09:50
@schronck
schronck requested a lite review from Copilot August 14, 2026 09:39
@schronck
schronck marked this pull request as ready for review August 14, 2026 09:39
@schronck
schronck requested review from a team, scolear and sosaucily August 14, 2026 09:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR modernizes the frontend UI by applying the BitSafe design system across the app (dark-first theme, updated typography/accent tokens), while also improving local development ergonomics with mock-mode API/auth support and richer UI primitives (sidebar collapse, status indicators, approval/workflow visualization).

Changes:

  • Reworked MUI theme tokens and global CSS to implement the new dark-first BitSafe design system (colors, typography, table row sizing/insets, component chrome).
  • Added build/version metadata injection and surfaced it in the UI; introduced a collapsible sidebar with persisted state.
  • Added dev-only mock API server + mock auth flow and expanded JSON fixtures to enable realistic offline UI development.

Reviewed changes

Copilot reviewed 68 out of 70 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
frontend/vite.config.ts Adds Cargo/version parsing, mock API dev plugin, and build-time defines (__APP_VERSION__, __MOCK__).
frontend/src/vite-env.d.ts Declares build-time injected globals for TypeScript.
frontend/src/styles.ts Updates zebra row styling to match the new dark substrate and accent hover behavior.
frontend/src/main.tsx Loads Roboto Mono font weights for numeric/ID typography.
frontend/src/index.css Introduces new CSS tokens (--font-mono, --accent, --content-pad) and updates light/dark backgrounds.
frontend/src/contexts/ThemeContext.tsx Major design-token overhaul: palettes, typography, component overrides; defaults new users to dark mode.
frontend/src/contexts/AuthContext.tsx Adds dev-only mock auth provider gated by __MOCK__.
frontend/src/components/viz/ApprovalViz.tsx New approval/workflow visualization components (pills, ring, pipeline, card chrome).
frontend/src/components/ThemeSwitcher.tsx Adds orientation support for horizontal/vertical layout.
frontend/src/components/StatusDot.tsx New status dot with optional pulse and reduced-motion handling.
frontend/src/components/Sidebar.tsx Adds collapsed rail mode, build/network footer, updated nav labels/badges.
frontend/src/components/PartyList.tsx Adjusts party table columns/layout and improves wide-screen ID display behavior.
frontend/src/components/PartyDetail.tsx Aligns page sections with shared --content-pad inset token.
frontend/src/components/PackagesPanel.tsx Aligns header padding with --content-pad; tweaks table layout and row padding.
frontend/src/components/OnboardingDialog.tsx Significant UX refresh: peer filtering/selection UI, modal layout, and updated copy.
frontend/src/components/NetworkConfigAccordion.tsx Replaces legacy status icon logic with new StatusDot; updates mono font usage.
frontend/src/components/Logo.tsx Removes build-date/redirect easter egg behavior and simplifies logo text behavior.
frontend/src/components/LoginPage.tsx Redesigns login screen with dark gradient hero and a background motif.
frontend/src/components/KickDialog.tsx Updates copy to match “Approvals” naming.
frontend/src/components/HoldingsSection.tsx Switches numeric typography to Roboto Mono token.
frontend/src/components/GovernanceSection.tsx Switches mono typography usage to Roboto Mono token.
frontend/src/components/GovernanceAuditTrail.tsx Aligns row padding and ensures collapsible detail rows opt out of fixed-height styling.
frontend/src/components/CopyableText.tsx Adds expandOnWide option and switches to Roboto Mono token.
frontend/src/components/ContractsDialog.tsx Switches mono typography usage to Roboto Mono token.
frontend/src/App.tsx Persists sidebar collapsed state; updates layout offsets/transitions; renames “Pending approvals” → “Approvals”.
frontend/package.json Adds dev:mock script and @fontsource/roboto-mono dependency.
frontend/package-lock.json Locks @fontsource/roboto-mono dependency.
frontend/mocks/workflows.json Adds mock workflows fixture for mock-mode UI rendering.
frontend/mocks/vaults.json Adds mock vaults fixture.
frontend/mocks/transfer-preapprovals.json Adds mock transfer-preapprovals fixture.
frontend/mocks/transfer-factories.json Adds mock transfer-factories fixture.
frontend/mocks/token-standard-contracts.json Adds mock token-standard-contracts fixture.
frontend/mocks/services_user.json Adds mock services (user) fixture.
frontend/mocks/services_provider.json Adds mock services (provider) fixture.
frontend/mocks/party-config.json Adds mock party-config fixture.
frontend/mocks/participants-status.json Adds mock participants-status fixture (including workflow status fields).
frontend/mocks/packages.json Adds mock packages fixture.
frontend/mocks/packages_vetted.json Adds mock vetted-packages fixture.
frontend/mocks/packages_compare-peers.json Adds mock peer package-compare fixture.
frontend/mocks/operator-info.json Adds mock operator-info fixture.
frontend/mocks/node-config.json Adds mock node-config fixture (incl. canton network field).
frontend/mocks/network-info.json Adds mock network-info fixture.
frontend/mocks/network-config.json Adds mock network-config fixture (peers list).
frontend/mocks/keys_status.json Adds mock keys status fixture.
frontend/mocks/invitations.json Adds mock invitations fixture.
frontend/mocks/instruments.json Adds mock instruments fixture.
frontend/mocks/holdings.json Adds mock holdings fixture.
frontend/mocks/governance_transfer-instructions.json Adds mock governance transfer-instructions fixture.
frontend/mocks/governance_state.json Adds mock governance state fixture (empty/default).
frontend/mocks/governance_state__ibtc-admin.json Adds party-scoped governance state fixture.
frontend/mocks/governance_state__governance-core.json Adds party-scoped governance state fixture.
frontend/mocks/governance_state__cbtc-network.json Adds party-scoped governance state fixture.
frontend/mocks/governance_state__cbtc-mint-authority.json Adds party-scoped governance state fixture.
frontend/mocks/governance_mint-requests.json Adds mock governance mint-requests fixture.
frontend/mocks/governance_known-members.json Adds mock governance known-members fixture.
frontend/mocks/governance_confirmations.json Adds mock governance confirmations fixture (empty/default).
frontend/mocks/governance_confirmations__ibtc-admin.json Adds party-scoped governance confirmations fixture.
frontend/mocks/governance_confirmations__governance-core.json Adds party-scoped governance confirmations fixture.
frontend/mocks/governance_confirmations__cbtc-network.json Adds party-scoped governance confirmations fixture.
frontend/mocks/governance_chain-audit.json Adds mock governance chain audit fixture.
frontend/mocks/governance_burn-requests.json Adds mock governance burn-requests fixture.
frontend/mocks/credential-offers.json Adds mock credential-offers fixture.
frontend/mocks/contracts_query.json Adds mock contracts query fixture.
frontend/mocks/auth-config.json Adds mock auth-config fixture.
frontend/mocks/auth_test.json Adds mock auth test fixture.
frontend/mocks/auth_status.json Adds mock auth status fixture.
frontend/index.html Updates pre-theme bootstrap script for dark-first default and new dark substrate color.
build.rs Passes crate version into the frontend build via APP_VERSION.
Files not reviewed (1)
  • frontend/package-lock.json: Generated file
Suppressed comments (1)

frontend/src/components/viz/ApprovalViz.tsx:255

  • var(--font-sans) is not defined in CSS, so this title font will not reliably match the app typography. Consider using the theme’s configured font family.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread frontend/src/components/Sidebar.tsx Outdated
Comment thread crates/decman/frontend/src/components/viz/ApprovalViz.tsx
Comment thread crates/decman/frontend/src/components/OnboardingDialog.tsx
@schronck

Copy link
Copy Markdown
Collaborator Author

Copilot review loop — disposition summary

Round Commits Findings Fixed Fixed w/ mods Won't-fix Filed issue
1 7dd0e12…2639717 3 2 1 0 0
Total 3 2 1 0 0

By category

Category Findings Fixed Fixed w/ mods Won't-fix Filed
Security 0 0 0 0 0
Functionality 1 1 0 0 0
Cosmetic 2 1 1 0 0
Total 3 2 1 0 0

All three were valid and are fixed, each as its own commit, with the threads replied to and resolved:

  • eb9a333 — build stamp tested its date instead of catching it. new Date returns an Invalid Date rather than throwing, so the try/catch could never fire and a bad value rendered as NaN-NaN-NaN NaN:NaN. Worth noting the stamp prefers buildTime from the backend, so the input is off the wire rather than compile-time — this was the one finding with real behavioural impact.
  • e8651e6 — declared the --font-sans token the approval cards were already asking for (fixed with modifications): rather than substitute a literal at the two call sites, the token is declared beside --font-mono and both the root rule and the theme's FONT_SANS now read it, so the sans stack is written once instead of duplicated as a literal in the stylesheet and the theme.
  • 2639717 — plain quotes in the party-prefix copy, in the helper text and the error it mirrors, matching the validator's ASCII - and _.

Verified after the fixes: tsc -b clean, vite build clean, ESLint unchanged at 60 (main's baseline is 62), and the typography checked in a running build — Space Grotesk for UI text, Roboto Mono for ids.

Outcome: all round-1 findings addressed; no open items.

Note on round 2: re-requesting copilot-pull-request-reviewer[bot] returns 201 Created but GitHub drops the bot from requested_reviewers, so a second pass could not be triggered from the API. Round 1 also reviewed the pre-rebase head (9a1ff9a); the branch has since been rebased onto 6f2949c. A re-request from the UI would review the current head if another pass is wanted.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 39 out of 41 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • crates/decman/frontend/package-lock.json: Generated file
Suppressed comments (1)

crates/decman/frontend/src/components/Sidebar.tsx:145

  • In collapsed mode the BitSafe logo is a clickable <img> with only onClick, which is not keyboard-accessible and doesn’t expose button semantics to assistive tech. Wrap it in an IconButton (or add role/tabIndex + key handlers) so the reload action is reachable via keyboard.

Comment thread crates/decman/frontend/vite.config.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 39 out of 41 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • crates/decman/frontend/package-lock.json: Generated file
Suppressed comments (1)

crates/decman/frontend/src/components/OnboardingDialog.tsx:168

  • toggleAllVisible decides whether to clear/select based on allVisibleSelected from the render, not the prev set inside the state updater. If the handler fires multiple times before a re-render (e.g. double-click / key repeat), the branch decision can be stale and the toggle won’t actually toggle. Compute the “all selected” check from prev inside the updater instead.
  const toggleAllVisible = () => {
    setSelectedPeerIds((prev) => {
      const next = new Set(prev);
      if (allVisibleSelected) {
        visiblePeers.forEach((p) => next.delete(p.participant_id));

@schronck

Copy link
Copy Markdown
Collaborator Author

Copilot review loop — final disposition summary

Supersedes the round-1 table above. Converged: round 3 reviewed the current head and generated no new comments.

Round Head Commits Findings Fixed Fixed w/ mods Won't-fix Filed
1 9a1ff9a (pre-rebase) 7dd0e12…2639717 3 2 1 0 0
2 2639717 2639717…e2068f5 2 (1 inline + 1 suppressed) 2 0 0 0
3 e2068f5 e2068f5…e04c85d 1 (suppressed) 1 0 0 0
Total 6 5 1 0 0

By category

Category Findings Fixed Fixed w/ mods Won't-fix Filed
Security 1 1 0 0 0
Functionality 3 3 0 0 0
Cosmetic 2 1 1 0 0
Total 6 5 1 0 0

Round 2 — the one that mattered

00e4489__MOCK__ came straight off process.env.MOCK with nothing tying it to the dev server, and that flag is what swaps the app onto a fake login session. build.rs shells out to npm run build, so a build environment carrying MOCK=true shipped a bundle that skipped real authentication.

Not theoretical — I built the pre-fix config to check:

Config Command Mock session token in dist
before MOCK=true npm run build 2 occurrences
before npm run build 0
after MOCK=true npm run build 0

(Probed by string literal, not identifier — a production build minifies names, so grepping for MockAuthProvider would have shown 0 either way and hidden the bug.) The flag is now command === "serve" && process.env.MOCK === "true", feeding the plugin and the define from one place. npm run dev:mock still serves fixtures.

e2068f5 — collapsed rail's logo was a click-handling <img>, unreachable by keyboard; now an IconButton with the action named on it. (From round 2's suppressed comment.)

Round 3 — suppressed, taken anyway

e04c85dtoggleAllVisible branched on allVisibleSelected (computed during render) from inside the state updater. Fired twice before a re-render, both calls read the same stale flag and the second repeated the first instead of toggling back. The updater now derives it from the prev set it was handed; the render-time value stays on the button's label and disabled state.

Verified on each round: tsc -b clean, vite build clean, ESLint steady at 60 (main's baseline is 62), and the affected UI checked in a running build.

Outcome: converged, no open items, nothing declined.

Correction to my earlier note: I reported that re-requesting the bot couldn't trigger another round. That was wrong — the request does work; the bot simply never appears in requested_reviewers, and rounds 2 and 3 each landed roughly 5–6 minutes after the re-fire, later than I'd waited.

Sidebar collapses to a 56px icon rail (state persisted). Build version + date always shown at the sidebar bottom — version injected from the crate version (build.rs APP_VERSION) with a Cargo.toml fallback for dev/mock — and the build-date/swagger click easter egg removed.
Every list row is a uniform 48px (theme MuiTableCell height) regardless of whether it carries a copy button/chip or just text, fixing the shorter Config-peers and DAR-compare rows. Normalized outlier per-cell paddings; collapsible audit-detail rows opt out via height:auto.
The grouped/filtered approvals feed replaced the flat paginated list that
main had meanwhile gained, which left `PaginationControls` referencing
state the new body no longer defined, and dropped the `data-*` attributes
main put on the invitation and workflow-run cards.

Pagination now applies to the Completed section only: "Needs your action"
and "In progress" are bounded by how much work is live, while Completed
grows without limit. The hook runs above the loading/empty early-returns so
its order is stable across renders.

ApprovalCard takes a `dataAttrs` bag so the redesigned cards keep carrying
the selectors, and the party ids main added use the mono token.
…entity

The always-on stamp in the sidebar footer and the build-info reveal main
added to the logo were two displays of the same thing off two different
sources. The stamp was compile-time only (crate version + bundle date via
vite `define`), which names the bundle rather than the image that is
actually running.

It now prefers the identity the backend reports (CI image tag / short SHA
and the time CI stamped it) and falls back to the injected values, which is
all that exists before login, under `npm run dev` and in mock mode. The
logo's reveal is left alone: on small screens there is no sidebar, so it
stays the only way to read the build there.
Surfaces that landed on main after this branch forked still carried the old
`ui-monospace, SFMono-Regular, Menlo` stack instead of the design system's
`--font-mono` (Roboto Mono), so ids, amounts and page numbers rendered in a
different face than the rows around them.

The external-parties table was modelled on the pre-redesign parties list and
kept its `py: 1.5` cells, which made its rows shorter than every other list;
normalized to the uniform 48px row, with the host/confirmation counts and the
created timestamp on the mono token.
The hide/unhide column sat under a blank header, leaving the eye icon
unexplained. The rigid 56px width would have squeezed the tracked uppercase
header, so the cell pair now uses the same shrink-to-fit idiom as Party ID
and Auth.
Both parties lists were tables, which spent a row's width on chrome and read
as a grid of cells rather than one party per line. They are now card rows in
the same shape as the approvals feed's Completed section.

The party id splits at `::`: the readable prefix carries the weight and the
namespace fingerprint recedes behind it, ellipsizing to fit. Cutting the tail
is safe here — every namespace opens with the same multicodec bytes and
diverges after — so the row adapts to the viewport without breakpoints.

Extracted the shell the Completed section had inline into RowCard and used it
in all three, so the shapes cannot drift apart. Since a card list has no
header row, the auth and visibility columns are labelled by a legend sharing
the cards' slot widths.

Dropped the external list's separate Fingerprint column: it is documented as
the `{fingerprint}` half of the party id, so the table was showing the same
value twice — the card now shows the whole id, with the full value copyable.
The endpoint was missing from the mock plugin's prefix list and had no
fixture, so the External Parties view could not be previewed offline at all.
The bar spanned the full width but pushed the range and the page pills to
opposite edges, so the controls sat in the corner rather than under the rows
they page through.

The bar still fills its container — its rule closes off the rows above — but
the range and the controls now travel together, centered on it.
Clearing the Create-Party FAB by padding the container's right side made its
horizontal inset lopsided. That is invisible while `--content-pad` is wide, but
once it bottoms out at 24px on a small screen the whole list reads as sitting
off-center.

The clearance moves inside the row instead, as trailing dead space standing in
for the spacer cell the table used to carry, so the visibility toggle stays out
from under the FAB while the list's own inset stays symmetric.
The Decentralized/External tab row kept a flat 16px inset while the lists below
it use `--content-pad`, so on a wide screen the tabs sat ~140px left of the
cards they switch between and the column read as off-center. The row arrived
with the external-parties view, after the inset was introduced, so it was never
brought across.

Its labels now line up with the PARTY ID header and the party ids beneath.
The rows belong in the shared inset column, but the footer bar that closes them
off should reach both edges — the tables these lists replaced behaved that way,
with the footer rule running edge to edge under content that stopped short of it.

The footer cancels the inset with a negative margin so its rule and fill reach
the edges while its contents stay centered. Moved the inset and the shared legend
style into styles.ts on the way past; both lists carried their own copy of the
latter.
The skeleton still drew a six-column zebra-striped table — the shape the list had
two redesigns ago, before it was trimmed to id + auth and again before it became
cards. It flashed a completely different layout than the one that replaced it.

Built from the same row shell and column slots as the real list, so the rows
don't resize or shift when the data lands.
Both fixtures sat under PAGE_SIZE (18 and 4 against 25), and the footer hides
itself when everything fits on one page — so mock mode could never show the
paginator, and checking it meant hand-editing the page size.

32 decentralized and 28 external parties: two pages each, the last one partial.
Namespaces are generated from a fixed seed, so the fixtures stay stable.
The parties rows carry 30px icon buttons while the external-parties rows are
text only, so the two lists sat at visibly different heights side by side.

RowCard now sets the uniform 48px row the tables already used, so a row is the
same height whatever it happens to carry.
Rows stretched to whatever `--content-pad` left over — 1300px on a wide
monitor — which is far past a comfortable measure for a single line of text.

They now sit in the same 1040px centered column the approvals feed already
used, shared as a token so the two views cannot drift. The footer stays a
sibling outside the column, so its rule still runs the full width of the view.
Legend, skeleton and the tab row follow the column.

Long party ids ellipsize at this measure; the full value stays in the row's
tooltip and its copy button.
Only 2 of the 8 pagination instances had enough fixture rows to render their
footer, so most of them could not be checked in mock mode at all.

Grown past PAGE_SIZE: vetted packages, the peer comparison, holdings, the
first party's contracts, and the terminal workflow runs the approvals feed
pages. The chain audit now returns `next_before_offset`, which is what makes
its cursor control appear.
The id took only as much width as its text, so each row's copy button sat
wherever that row's id happened to end — the buttons zigzagged down the list and
the gap before the Auth column swung between roughly 50 and 120px.

The id now holds the whole column and the button sits at its end, so the buttons
form a column and every row breaks at the same place.
The footer is `position: sticky`, which only pins it while something scrolls —
on a last page holding a handful of rows it sat right under them with the rest
of the view empty below.

The parties region is now a column at least a screen tall with the list flexing
into the leftover, so the footer lands at the bottom either way. `columnSx` sets
its width explicitly for the same reason: inside a flex column the `auto` side
margins suppress stretching, which had shrunk the tab row to its content and
pulled it off the column's left edge.
The footer carried a 4px top margin, which left a strip of page background
between the last row and the rule that is supposed to close the list off — most
obvious on the packages table, where the rows run right up to it.
Two things held them ~106px off the card's edge: they were centered in slots
sized by their legend labels, and every row carried 40px of trailing space to
keep the visibility toggle clear of the Create-Party FAB.

The columns now right-align — matching how the external-parties list already
aligns its own — and the FAB spacer drops out once the column is capped and
centered, since the toggle clears the corner on its own by then. That puts the
icons 38px from the edge on a wide screen, and the spacer still holds them clear
on the narrow widths where the column reaches the corner.

Moved the shared slot widths into styles.ts on the way past: exporting them from
a component file cost a react-refresh lint error, and they were always layout
tokens rather than anything of PartyList's.
Each peer column sized itself to its own header, so a wide grid of identical
check/cross cells arrived in a ragged set of widths, and the table only carried a
flat 650px floor: adding peers squeezed every column narrower rather than
overflowing, since an auto-layout table shrinks to fit before it scrolls.

The comparison table now states each column's width under a fixed layout — as
the vetted-packages table beside it already did — and its floor grows with the
peer count, so peers stay one width, the package name keeps a readable column,
and a long peer list overflows into the scroller that was already there.

Peer names ellipsize with the full participant id on hover, so a peer without a
short name can no longer stretch its column.
The theme pads a table's leading and trailing cells out to `--content-pad` so a
full-bleed table lines its edge content up with the rest of the UI. The peer
comparison isn't full-bleed — it's a fixed grid inside a scroller — so on a wide
screen that rule put 126px of padding inside its last peer cell: the column
painted its full width while its tick sat against the left edge and its label
clipped to one letter, which read as the columns being unequal.

The trailing inset is dropped for this table, and its columns are declared in a
colgroup so the widths come from one place rather than being inferred from the
cells. The leading inset stays — it lines the package name up with the panel
header above it.

Measured across every peer column: equal widths, and each tick centred on its
own column.
`new Date` yields an Invalid Date rather than throwing, so the try/catch around
the build stamp could never fire and a malformed value formatted as
`NaN-NaN-NaN NaN:NaN`. It matters because the stamp prefers `buildTime` from the
backend, so the input is off the wire rather than compile-time.

Checks `getTime()` and falls back to the raw string.
The approval cards asked for `var(--font-sans)`, which was never declared — only
`--font-mono` was — so those two labels silently fell back to whatever they
inherited instead of the UI face.

Declared it next to `--font-mono` and pointed both the root rule and the theme's
FONT_SANS at it, so the sans stack is written once rather than repeated as a
literal in the stylesheet and the theme.
The prefix rules named the two allowed punctuation characters inside
typographic quotes, which reads as though the quotes are part of what you may
type. The validator accepts ASCII '-' and '_' only, so the copy now quotes them
plainly — in the helper text and the error it mirrors.
`__MOCK__` came straight off `process.env.MOCK` with nothing tying it to the dev
server, and it is what swaps the app onto a fake login session. `build.rs` shells
out to `npm run build`, so a build environment carrying `MOCK=true` produced a
shipped bundle that skipped real authentication — confirmed by building the old
config that way and finding the mock session's token string in dist twice, where
a build without the variable had none.

The flag is now `command === "serve" && process.env.MOCK === "true"`, feeding both
the plugin and the define from one place. Rebuilt with `MOCK=true`: the token
string is gone from the bundle, and `npm run dev:mock` still serves fixtures.
In the collapsed rail the logo carried the reload action on a bare <img> with an
onClick, so it was unreachable by keyboard and announced itself as an image
rather than a control. It's an IconButton now, with the mark as its decorative
child and the action named on the button.
`toggleAllVisible` branched on `allVisibleSelected`, computed during render, from
inside the state updater. Two invocations before a re-render — key repeat on the
button — both read the same stale flag, so the second repeated the first instead
of toggling back.

The updater now derives "all selected" from the `prev` set it was handed. The
render-time value stays where it belongs: the button's label and disabled state.
@schronck
schronck force-pushed the feat/ui/bitsafe-design branch from e04c85d to 36948e2 Compare August 18, 2026 09:15
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