Skip to content

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

Merged
schronck merged 62 commits into
mainfrom
feat/ui/bitsafe-design
Sep 2, 2026
Merged

schronck merged 62 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.

@schronck
schronck force-pushed the feat/ui/bitsafe-design branch from e04c85d to 36948e2 Compare August 18, 2026 09:15
@schronck
schronck force-pushed the feat/ui/bitsafe-design branch from 36948e2 to ceb90b6 Compare August 31, 2026 11:35
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.
Every card row now lifts on hover, not only the clickable ones. The lists are
read across, and the tint is what tracks the eye along a row; the external
parties list had no hover at all, because it passes no `onActivate`.

A row also takes an optional `detail`, revealed below it behind a chevron in a
slot the legend above the rows shares, so the two cannot drift apart. A row is
an expander or a control, never both — one click cannot mean two things.
The list said how many participants host a party but not which ones, and the
count alone doesn't answer the question an operator has — who is holding this
party up. The `PartyToParticipant` mapping the list already reads carries the
participants, so `ExternalPartyInfo` now carries them too, each with the
permission the mapping grants it.

Expanding a row reveals them. The permission chip is uncoloured: Confirmation
is the norm for a hosting mapping, so tinting it would paint nearly every row
and leave nothing for the exceptions to stand out against.

Mapping the proto discriminant to the wire `Permission` stays in the handler,
where the other topology reads do it, so the workflow layer keeps its proto
types to itself.
The theme insets a table's leading and trailing cell to `--content-pad` but
leaves the row rules and zebra fills full-bleed, so on a wide screen every
rule ran several hundred pixels past the column it belonged to. The inset now
belongs to a wrapper around the table and the cells give it back, which puts
the rule's ends where the content's are. Applied to all four tables in the
view, and the audit trail's mode toggle and the holdings controls move onto the
same inset — they sat against the sidebar while their rows started a column in.

Section titles take the weight and the full text contrast that the generic
`subtitle2` eyebrow doesn't, and their band keeps its fill whether open or
shut: expanded, the band is what separates one section's table from the next
section's title. Its fill becomes a named constant in the style module, where
the palette carried an unread `surface2` before — MUI has no palette slot for
that step, and a constant beside a component would break fast refresh.
`--content-pad` grows with the window, so past 1560px the package name drifted
right with it — half a screen of empty gutter before the first column, with the
row rules running under it. A package list is read down its first column, so it
now sits at the left of the pane at any width with a fixed gutter, the way a
Finder list view does. The search bar above moves onto the same gutter.

Replaces the comparison table's hand-trimmed trailing pad, which was solving
the same problem from the other end.
Confirm and Accept were accent-filled, which is the same red the destructive
buttons use, so approving and undoing read alike. Both are affirmative, so both
go green, and Revoke joins Cancel in error red — taking a confirmation back and
cancelling a proposal are the same kind of act. Execute drops to outlined so the
pair reads as one primary step and one follow-on rather than two equal calls to
action.

The title's value was accent too, which on a proposal whose value is a contract
id painted a whole line red. The mono face already sets it apart from the action
name, so the colour was doing nothing the type wasn't.
Quorum met, and the card still showed Confirm beside Execute plus a "Ready to
execute" pill. The pill said what the Execute button already says, and Confirm
invited a vote the action no longer needs. Both go, on the governance and the
domain cards alike, and Execute takes the filled treatment back now that it no
longer shares a row with Confirm.

Revoke stays where you have already confirmed: withdrawing your own vote is
still open to you, and it is the only way to take one back before someone
executes. Orphaned keeps its pill — that is a problem, not a step.
"Your vote needed" sat beside a Confirm button, "Respond" beside Decline and
Accept, and "Running" above the pipeline, the peer count and the current step.
In each case the pill named the state the rest of the card was already showing,
so a row carried the same fact twice.

What is left is the states nothing else accounts for: "Awaiting others", where
you have confirmed and the card offers no way forward; "Queued", which has no
pipeline yet; "Orphaned"; and the terminal pills on the collapsed rows in
Completed, where the pill is the only signal there is.
The Completed footer sat inside the capped card column, so its top rule stopped
where the cards did instead of closing off the list across the view. And the
column's bottom padding sat under a bar pinned with `bottom: 0`, so the bar
came to rest 24px short of the bottom with an empty strip below it.

Both are what the parties lists already solved: the bar renders outside the
column, and the region is a full-height flex column so the list takes the
leftover and the bar lands flush. Its visibility moves out of the section loop
with it, so the other filters do not grow a stray bar.
88px sat above the bar: 32 from the shared Container, which for this tab holds
nothing but portal-rendered dialogs and so was an empty strip above the content
(tabs 0 and 2 already opt out of it), 32 from the tab's own top padding, and 24
from the column's. The bar is sticky at `top: 0`, so all of it was space the
first scroll threw away — and the bar jumped as it went. Gone: the bar now
rests where it sticks.

Below it, the rule was the last thing the bar painted, so a card scrolling
under it arrived flush against the rule. The `mb` that looked like the gap
belonged to the content and scrolled away with it. The sticky element is now an
outer box whose fill runs 12px past the rule, and the card disappears behind
that strip instead of touching.
Seating the filter bar at the top dropped the column's `py: 3`, and the bottom
half of it went with the top half — so the last card ended flush against the
bottom of the page. The padding comes back on the bottom only. The Completed
footer bar is a sibling of the column, not a child, so this is the gutter above
it and the bar itself still sits flush.
The column read only `build_version` — the display string CI stamps from the
image tag. A peer that predates that field reports just its compatibility
`version`, and the column drew "—" for it even though its semver was right
there in the same response. It now falls back to `version` before giving up, on
the self row and the peer rows alike.

The mock fixture carried only `version` too, so the whole column read "—" in
mock mode and the preview said nothing about what the view does. Its reachable
peers now report a build version, and "—" is left to the peers that genuinely
report none — the unreachable and handshake-failed ones.
@schronck
schronck force-pushed the feat/ui/bitsafe-design branch from fc47290 to c319ed3 Compare September 1, 2026 15:14
Declining an invitation turns the coordinator's workflow down — the same kind of
act as Cancel and Revoke, so it takes the same red and the same outline instead
of reading as a muted afterthought beside Accept.
The approvals fixtures only ever reached Confirm and Execute. No confirmation
was ever attributed to the member party, so Revoke could not appear; no proposal
carried a `proposer`, so Cancel proposal could not either; and the orphaned card
had an empty confirmations list, so neither Revoke nor Dismiss showed on the one
card that exists to demonstrate them. Three of the four destructive buttons and
the "Awaiting others" pill were unreachable in mock mode.

Adds an action confirmed by the member and short of quorum (Awaiting others +
Revoke), one confirmed by the member and over it (Revoke + Execute), a proposal
the member proposed (Confirm + Cancel proposal), one both proposed and confirmed
(Revoke + Cancel proposal + Execute), and confirmations on the orphan (Revoke +
Dismiss).

Also reconciles `can_execute` with the confirmation counts. Six cards claimed
"quorum met" — the ring follows the count — while offering Confirm instead of
Execute, because the buttons follow `can_execute` and the fixtures disagreed
with themselves. Four of those predate this branch.
#393 removed the legacy Vault project and renamed the fixture strings it
touched, but the rows this branch had added kept the old flavour — so the mock
preview still advertised a product that no longer exists. Renamed on #393's own
precedent: `bitsafe-vault-*` to `cbtc-governance-*`, and the two remaining
`vault-` party prefixes to `treasury-`. No "vault" text is left in any view.
Five edge-to-edge grey bands stacked down the page, each opening onto a dense
mono table that ran the full width, with the party's figures reduced to pills
wedged between three accent-outlined buttons. Nothing in it had a rank, so
there was nowhere for the eye to land.

The sections become cards. A shut one is a single quiet strip; an open one gets
a filled header its table sits under, and the card's own edge bounds the table,
which is a better boundary than the wrapper inset that used to fake one — so
`insetTableSx` gives way to `cardTableSx` and the holdings and audit sections
move onto the card's gutter with it.

The figures become tiles worth reading, at 22px with the label tracked out above
them, and Threshold says "2 of 3" now that it has the room. Only the governance
parameters go up there: auth and the participant count both appear a few pixels
below as their own section's badge, so a tile for either says it twice.

The identity block leads with the party's own name, drops the full id and the
owner key beneath it at a quieter weight, and gathers the actions on the right
in a neutral outline rather than three accent buttons competing with the data.
@schronck
schronck merged commit be75319 into main Sep 2, 2026
10 checks passed
@schronck
schronck deleted the feat/ui/bitsafe-design branch September 2, 2026 10:06
hubagaspar91 added a commit that referenced this pull request Sep 2, 2026
Main advanced twice while the first merge ran its e2e suite. It landed the
UI modernization (#212) and the 1.7.0 version bump (#404).

One file conflicted: crates/decman/src/server/types.rs. Main added
ExternalPartyHost to the common::api re-export list. This branch had
reflowed that same list, because the PR moved the Instrument* and
PartyCredentialRequirement types into decman-lib. The resolution keeps this
branch's shorter list and adds ExternalPartyHost.

The UI PR is almost all frontend TypeScript, which this branch does not
touch, so it merged clean. Its Rust delta is additive: an ExternalPartyHost
DTO on the external-parties read.

The frontend needs a fresh install. The UI PR adds @fontsource/roboto-mono,
so a stale node_modules fails the build. `npm ci` from the merged lock file
fixes it and leaves the lock file untouched.

cargo fmt, cargo clippy --all-targets --all-features -D warnings and cargo
test all pass. The frontend builds, tsc -b type-checks the new components
against the regenerated wire types, and its six tests pass.

The localnet e2e suite passes end to end again: governance_workflows_e2e,
1 passed, 0 failed, 1391s. Its external_party_tenant phase covers the new
ExternalPartyHost field on the external-parties read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

3 participants