Skip to content

feat(desktop-new): prototype channel-backed Sessions client - #7366

Draft
morgmart wants to merge 12 commits into
mainfrom
morgan/desktop-new
Draft

feat(desktop-new): prototype channel-backed Sessions client#7366
morgmart wants to merge 12 commits into
mainfrom
morgan/desktop-new

Conversation

@morgmart

@morgmart morgmart commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Introduces desktop-new, a from-scratch Buzz desktop client experiment focused on channel-backed Sessions and a design-system-first interface.

  • adds a real relay-backed Channels and Sessions vertical slice with recoverable first-send creation
  • adds durable parent-channel Session links and inherited human access across query, count, live delivery, and writes
  • adds a minimal managed-agent runtime flow and owner-visible inline activity
  • brings in Dockview as the workspace geometry layer
  • renames the experimental client and branch from desktop-next to desktop-new
  • adds the semantic colour/type foundations plus forming spacing, radius, and motion tokens
  • adds Base UI-backed shared components with Astryx-style live specimen pages at /design/components
  • uses Tabler Icons for product UI

Status

Draft. The backend/runtime vertical slice works, but the current product shell is not the intended final visual direction. The next step is to rebuild the populated Messages surface from the shared components, using Cynthia’s cc-experiment prototype as the executable visual reference and Figma node 185:9217 as the parity target.

Composer work is intentionally deferred until its inline-token/chip editing contract is designed.

Validation

  • repository pre-push suite: all lanes passed
  • Rust tests and desktop Tauri checks passed
  • desktop-new TypeScript, Biome, type-system, and APCA contrast checks passed
  • desktop-new unit and Playwright flows passed
  • production frontend build passed
  • file-size gate passed

Known follow-ups

  • replace the current generic product shell with the component-led Cynthia/Figma composition
  • design the composer and inline chips before implementation
  • add stronger production relay fault-path coverage for Session recovery and authorization
  • paginate Session discovery beyond the current bounded first page
  • split the large frontend bundle

…design system route

Starts a second desktop client rather than reworking the existing one, so the
design system can be built from scratch without unpicking 476 raw colour usages
and 72 hover values first. The existing desktop/ client is untouched.

What lands:

- The colour system in three layers: private ramps hold values, public roles
  hold meanings, components only ever use roles. Tailwind's default palette is
  deleted with --color-*: initial, so a raw palette class is a build error.
- A machine-readable token registry, so the design system documentation renders
  from the system itself and cannot drift from it.
- /design — foundations, vocabulary, and the growth procedure, with a live
  light/dark toggle. Typography, spacing, radius, and motion are stubs that
  state what is still to decide rather than pretending to a system.
- DESIGN.md for the judgement tokens cannot express, and AGENTS.md for the rules
  an agent needs before writing any UI.

Accent is a slot rather than a colour: nothing above the ramp knows the hue, so
it can change, become a preference, or vary per theme without touching a
component. Every dark value is authored rather than observed, because the design
exploration it derives from is light-only.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
The design system site rendered black borders and dividers while the tokens
said `#d4d4d4`. The border roles were declared in layer 2 and never registered
with Tailwind, so `border-primary` silently resolved to `--color-primary` — the
*text* colour — and every hairline drew at text contrast. Tailwind's `--color-*`
is a single namespace and text/borders share the emphasis names while holding
different values, so borders now register as `--border-color-*`.

Add typography as the second foundation, same three layers as colour: a nine-step
size ramp plus leading, tracking, and weight ramps in layer 1; ten public roles
in layer 2, each carrying its whole setting via Tailwind v4's
`--text-<name>--<property>` convention, so `text-body` alone produces correctly
set text and two supposedly identical labels cannot drift apart. Inter Variable
and JetBrains Mono, both already shipped in every current Buzz client. Every
size derives from one virtual rem, so keyboard zoom and the font-size preference
work by construction.

Soften the documentation surface to match where the product is going: regions
separated by soft fills rather than outlines, hairlines only for genuine
boundaries and never above `border-secondary`, no page-wide gradient behind
reading columns, and no all-caps labels anywhere.

The `/design/typography` page renders from the registry, so it cannot drift from
the system. Add `scripts/check-type.mjs` enforcing the four rules a token cannot
express — no arbitrary sizes (px or rem), no `uppercase`, no hand-applied
tracking, no size role paired with a weight or leading utility.

Also collapse the nav to a wrapped row below `lg`; the fixed 256px column
overflowed the viewport under 420px.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
A swatch on a grey fill is being judged against a surface the product will never
reproduce, and the dividers between swatches were redundant — each colour already
separates itself. Same for type specimens: a box around a size sample changes the
contrast you are evaluating it in.

Roles and specimens now sit directly on the page, separated by space alone. The
distinction is between two list shapes rather than a preference: `Rows` keeps
dividers for uniform rows where the eye needs a line to track along (the size and
tracking ramps, the audit table); `Specimens` has neither container nor divider
for entries that carry their own visible difference.

The swatch keeps its hairline. `bg-panel` is white on a white page, so without it
the most-used role in the system renders as nothing — that is a genuine boundary,
not decoration, and it is what makes the no-container treatment safe in both modes.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
* origin/main:
  chore(release): release Buzz Desktop version 0.5.21 (#7301)
  fix(scripts): copy global-agent-config.json in buzz-adopt-prod-agents (#7303)
  feat(relay): add early startup lifecycle logs (#7258)
  docs(nip-fi): add Blossom kind-24242 media possession-proof exception (#7278)
  fix(desktop): wrap message tables within the available pane (#7279)
  🤖 fix(desktop): harden smoke E2E tests against Bestie overlay and toast timing (#7270)
  Show status and huddle indicators beside names (#7112)
  Add mobile voice notes (#7121)
  perf(desktop): publish mention sends before waking agents (#7154)

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
…d-text roles

The colour page explains the system but never lists the tokens themselves, so
there is nowhere to answer "what does bg-inset actually paint". Add
/design/color/table: every token in one table — the name you type, the base
token it resolves through, and the value it paints.

The values are read from the live CSS rather than typed into the registry. A hex
column written by hand is a second source of truth for the same fact: edit a ramp
and the documentation is silently wrong. This needs two different reads, because
neither answers both questions — getComputedStyle returns the resolved value
(#ffffff) and has already lost the indirection, while walking document.styleSheets
finds the declared value (var(--neutral-1)), which is the only place the chain
still exists. So CSSOM for what a token points at, computed style for what colour
that is. It re-resolves on scheme change, which is visible in dark mode where
bg-panel points at neutral 3 rather than neutral 1.

The table immediately found a real defect. The registry has documented
text-on-danger, text-on-success, text-on-warning, and text-on-info since the
status groups were generated, but tokens.css never declared them — all four
resolved to nothing, so text on a danger button fell back to inherited near-black
on red. Declare them and register them with Tailwind. text-on-warning is
neutral-12, not white: the warning fill is amber (#eab308) and white on it fails
contrast.

Also spell it "Color" in the nav, per the product's US English. The route is now
/design/color, with the table nested under it. The file is color_.table.tsx —
the trailing underscore keeps the URL while opting out of nesting, because
otherwise /design/color becomes a layout route whose component must render an
Outlet, and ColourPage does not, so the parent page rendered in place of the
table. Component and type names still say Colour; renaming those is a separate
change from what the interface says.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
The value column had been holding a single unbroken gradient literal, which under
the default `auto` layout claimed most of the table's width and pushed the other
two columns into a scroll. Switch to `table-fixed` with an explicit colgroup so
the thirds hold regardless of content, and let cells wrap instead of scrolling —
the horizontal scroll container is gone.

The value uses `break-all` rather than `break-words`: a gradient is one token with
no spaces to break at, so word-boundary wrapping still overflows. The swatch stays
pinned to the first line while the value wraps beside it, so the kind label moves
under the value rather than competing with it for the same line.

Verified equal columns and zero overflowing cells at 380, 480, 700, 1024, and
1280px.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
…gible

The default accent was #3b82f6, and white label text on it measures WCAG 2
3.68:1 — below the 4.5:1 AA minimum for normal text. That applied to every
primary button and active sidebar row in a fresh install, not to a setting
someone had opted into.

Move it to #2563eb, one step deeper: WCAG 5.17:1 and APCA Lc 80, so it now
passes both standards. Radix, Bootstrap, and Tailwind's blue-600 all sit in this
range, which is where a blue has to be to carry white text.

Darkening the fill is the fix rather than switching the label to black. Black on
the old hue scores WCAG 5.71 and would "pass", but measures APCA Lc 40 —
perceptually unreadable, and visibly worse. This is the documented WCAG 2 flaw
on saturated blue: the formula underweights the blue channel and ignores
polarity. Apple ships white on #0088ff–#3daefc in Messages at WCAG 2.4–3.5 for
the same reason. DESIGN.md now records the position (judge with APCA, report
WCAG, constrain the fill rather than degrade the text) so it is a decision with
evidence rather than a preference.

Retiring a hex is not only cosmetic: it leaves the old value in people's
storage, where it is no longer in ACCENT_COLORS and therefore fails the
community-theme allowlist — parseCommunityThemePreference would return null and
discard the user's syntax theme and follow-system flag along with the accent.
Both storage paths now migrate the retired value instead: the community
preference maps it on parse, and the local accent upgrades on read (all three
read sites go through one helper). A regression test covers the migration and
asserts an arbitrary hex is still rejected, so the migration cannot become a
general escape hatch; removing the mapping fails that test.

The communityThemeSync fixture moves to a current accent — it covers coordinate
logic, and a retired hex there would be rewritten mid-assertion for reasons
unrelated to what it tests.

Verified: 6112 desktop tests pass, tsc clean, biome clean.
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Three fixes, and a guard so they cannot regress silently.

`text-on-warning` pointed at `var(--neutral-12)`, which is near-black in light
mode and near-white in dark. The warning fill is the same amber in both, so dark
mode put #f5f5f5 on #eab308: APCA Lc 34, WCAG 1.76:1. It is a literal now — a
paired-text token follows its fill, never the mode.

The dark neutral text steps were too dim. `text-secondary` (#8f8f8f) measured
Lc 38–41 and `text-tertiary` (#6e6e6e) Lc 23–26 against an Lc 60 body target,
while passing WCAG AA at 4.4–5.6:1 — legal, and visibly murky. Raise dark
neutral-9 to #a6a6a6 and neutral-10 to #c1c1c1, sized against bg-float
(#2a2a2a), the lightest dark surface and so the binding case. Light neutral-9
moves #a3a3a3 -> #909090 for the same reason at the meta target. Both ramps stay
monotonic, and bg-inverse-hover (the other consumer of neutral-10) still carries
its paired text at Lc 70.

This is APCA's polarity asymmetry doing real work: light-on-dark needs more
separation than the same WCAG ratio implies, so the dark ramp's text steps sit
above where mirroring the light ramp would put them.

`text-disabled` stays below target deliberately — low contrast is the signal
that a control is unavailable, and WCAG exempts inactive controls for the same
reason. Raising it would make disabled read as enabled. Recorded as the one
exception, with that reason.

The guard is the durable part. `scripts/check-contrast.mjs` measures every text
role against every surface it can sit on, in both modes, and fails the build
below target. It parses `tokens.css` and resolves each `var()` chain rather than
holding a copied list of values, so it cannot drift from the system it audits.
`scripts/apca.mjs` is the algorithm, asserted against the published reference
values on every run so a bad edit to the maths fails loudly rather than quietly
shifting every verdict. Verified by falsification: reverting either fix fails
the check, and perturbing one APCA exponent trips the reference assertion.

Also corrects an earlier misreading: the two `.dark` blocks are not duplicate
declarations. The first holds the dark ramp, the second overrides surface roles
so the panel lifts off pure black. That is the intended layering.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
…en the audit

A second pass over the colour system, checking the surfaces the first audit did
not cover.

The gap was tint hovers. DESIGN.md says a tint carries meaning and takes coloured
text, but the audit only measured text against the four neutral surfaces — so
`--bg-*-tint-hover`, the hardest surface in each coloured family, was never
checked. Four pairings were below target: light danger (Lc 56.6) and warning
(59.2), and dark accent and info (58.4 each). All four passed WCAG AA, which is
why nothing else caught them.

Size step 11 against the tint hover rather than the neutral panel, the same rule
already applied to the neutral ramp: light danger-11 #b91c1c -> #a80008, light
warning-11 #a16207 -> #9c5e00, dark accent-11 and info-11 #8fbaff -> #95c0ff.
Minimum moves in OKLCH holding hue and chroma, so the hues do not shift.

The audit now covers every tint and tint hover in all five families. Verified by
falsification: reverting any one of the four fails the check.

Two findings that are deliberately not changes:

Hairline dividers stay at 1.2-1.8:1. WCAG's 3:1 non-text rule covers boundaries
needed to identify a control or its state, not grouping lines; Radix ships 1.29
and Apple 1.26 for the same job. Raising them would draw the box the fill already
implies. Recorded in DESIGN.md, along with the note that a control's own outline
is a different question when the input layer lands.

`--ring-focus` is an accent against accent: legible on every neutral surface at
3.0-3.5:1, but 1.1:1 on `bg-accent`, where it vanishes. Not yet reachable — the
token is unregistered and unused — so the constraint is documented at the token
instead of being solved speculatively: the component layer that introduces focus
owns a surface-coloured offset, and there is no second ring token.

Glass surfaces were checked by compositing each translucent value over the app
gradient's own stops; all clear the target in both modes, which the relit
neutral ramp is what earns.

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
…essions

* origin/main:
  Add generic information-flow control core (#7293)
  feat(buzz-acp): update base prompt; add buzz context and skills to Pi agents (#7335)
  fix(desktop): restore mention chip identity icons (#7338)
  Persist video playback speed preference (#7336)
  Verify ACP relay events before prompt routing (#7010)
  fix(buzz-acp): bound busy-owner hold to prevent cross-channel starvation (#7337)
  feat(desktop): invite owned agents from standalone forums (#7125)
  fix(desktop): authorize remote mentions at publication (#7124)
  fix(acp): rename system tag to agent-instructions (#7332)
  fix(desktop): bind duplicate mention selections to exact recipients (#7133)
  refactor(relay): extract NIP-29 membership authorization (#7285)
  chore(release): release Buzz Desktop version 0.5.22 (#7308)
  feat(desktop): preserve mentions across copy and paste (#7228)
  test(desktop): await Bestie drag and profile hover endpoints (#7294)
  Collapse contiguous join messages (#7262)

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is f038cbbb0d4092a72ffd93f17916f84d2b39bb43...ef1b407e025ee65f2d9e4ac0e6e2290d68826f7b.
A new review must complete for this exact range. When manual authorization
is required, a Block organization member must comment exactly
@buzz-security-review ef1b407e025ee65f2d9e4ac0e6e2290d68826f7b to authorize a new review.
Any previous review applies only to its recorded range.

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.

1 participant