Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
d448e51
Adopt shared actions, switches, avatars and icons
mahanti Sep 15, 2026
1b8708c
Preserve human and agent avatar shape variants
mahanti Sep 15, 2026
942cc3b
Use the Block UI typography ramp throughout shared tokens
mahanti Sep 15, 2026
fc09005
Map xsmall semantics to the shared 12px size
mahanti Sep 15, 2026
fc1d326
Describe the current design system and remove adoption comparison
mahanti Sep 15, 2026
9134602
Preserve decorative icon semantics and picker clear visibility
mahanti Sep 15, 2026
f9eeaa7
Assert shared picker icon semantics across GIF tabs
mahanti Sep 15, 2026
3c8aa79
Keep the final message fully visible after fractional reflow
mahanti Sep 15, 2026
294516d
Round Virtua end offsets before native scroll writes
mahanti Sep 15, 2026
866484a
Keep notification settings readable at enlarged text sizes
mahanti Sep 15, 2026
0e4b4d8
Avoid flex growth overflow in notification actions
mahanti Sep 15, 2026
f3c9be6
Define semantic colors for Block UI adoption
mahanti Sep 17, 2026
28d4676
Build semantic Base UI controls and states
mahanti Sep 17, 2026
efae409
Compose Base UI dialogs tooltips and tab panels
mahanti Sep 17, 2026
8b7cac6
Establish signer order in shared cooldown regression
mahanti Sep 17, 2026
6b05215
Establish signer order in shared cooldown regression
mahanti Sep 17, 2026
a03b75b
Preserve shared control labels, sizing and keyboard traversal
mahanti Sep 17, 2026
8461e91
Adopt shared semantic controls across the Buzz app
mahanti Sep 17, 2026
e0f1a30
Carry shared control fixes into compositions
mahanti Sep 17, 2026
ed6bc24
Carry validated control fixes through the app stack
mahanti Sep 17, 2026
ab0837b
Keep avatar geometry independent of action sizes
mahanti Sep 17, 2026
b31a9a8
Prepare signed capacity fixtures before behavior checks
mahanti Sep 17, 2026
27867d4
Carry avatar and fixture fixes into shared compositions
mahanti Sep 17, 2026
338bbb5
Expose modal state to shortcut guards and stabilize channel targeting
mahanti Sep 17, 2026
d0cfd1e
Preserve modal shortcuts and responsive shared-control layouts
mahanti Sep 17, 2026
6e57737
Carry final shared component fixes through app adoption
mahanti Sep 17, 2026
bd5cf3f
Align Home navigation assertion with shared row label
mahanti Sep 17, 2026
99b12ed
Merge main into shared-control adoption and preserve current UI behavior
mahanti Sep 21, 2026
622cd0b
Preserve avatar checks and contain large emoji after adoption
mahanti Sep 21, 2026
aa3ed56
Preserve tooltip descriptions and document pill navigation
mahanti Sep 21, 2026
394eaa1
Finish shared design-system baseline ownership
mahanti Sep 21, 2026
7dec23e
Refresh semantic foundations and address contrast review
mahanti Sep 21, 2026
ebe3808
Allow rich unread labels in shared navigation rows
mahanti Sep 21, 2026
2b309cf
Align viewer checks with semantic roles and public source
mahanti Sep 21, 2026
b99ddc9
Merge refreshed foundations and fix shared control review findings
mahanti Sep 21, 2026
bbdaf9d
Merge final foundation viewer checks
mahanti Sep 21, 2026
45f606e
Merge refreshed shared controls into component compositions
mahanti Sep 21, 2026
27dd5d0
Wait for channel menu lifecycle before testing focus return
mahanti Sep 21, 2026
5aad8b7
Merge refreshed semantic foundations with menu lifecycle coverage
mahanti Sep 21, 2026
a6fb286
Merge refreshed controls with menu lifecycle coverage
mahanti Sep 21, 2026
21b1fdd
Merge refreshed shared components and preserve current app features
mahanti Sep 21, 2026
bbc4c4a
Merge commit 'a6fb286f5d60e3af0d22030e57ce01914198722f' into codex/pr…
mahanti Sep 21, 2026
53218e5
Keep native reset aligned with controlled and deferred choices
mahanti Sep 21, 2026
b91c8fb
Preserve activity row layout with shared button labels
mahanti Sep 21, 2026
6fcfa9f
Preserve visible controls and production avatar masks during adoption
mahanti Sep 21, 2026
7c51d7d
Merge reviewed controls reset and activity layout fixes
mahanti Sep 21, 2026
d7d9216
Keep media recovery actions readable on inverse stages
mahanti Sep 21, 2026
580fb3f
Merge final shared controls and preserve app navigation layout
mahanti Sep 21, 2026
1207d7c
Keep hints and unread affordances usable with shared controls
mahanti Sep 21, 2026
6a70513
Fix adoption guard dependencies in hook fixtures
mahanti Sep 21, 2026
81b15a4
Keep unchanged channel rows stable during navigation
mahanti Sep 21, 2026
959aae7
Merge main and preserve shared notification controls
mahanti Sep 21, 2026
a8deea4
Merge refreshed design-system base with presence and notification set…
mahanti Sep 21, 2026
afc4de9
Merge current foundation base with presence and Dock updates
mahanti Sep 21, 2026
1b5a4fc
Merge updated controls base with presence and Dock updates
mahanti Sep 21, 2026
d35cab4
Merge refreshed compositions and current main into app adoption
mahanti Sep 21, 2026
7d01c09
Use shared buttons for Dock badge settings
mahanti Sep 21, 2026
622990a
Wait for thread trigger input readiness after timeline scroll
mahanti Sep 21, 2026
3b27679
Merge main into shared compositions without changing their contract
Sep 22, 2026
890af02
Merge refreshed compositions and main into app adoption
Sep 22, 2026
a5669f2
Carry agent editor colors into the shared semantic contract
Sep 22, 2026
6915669
Merge main after shared compositions squash
Sep 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions docs/design-system-adoption.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Shared design system adoption

Buzz keeps Base UI for interaction behavior and uses its own public palette,
Inter and JetBrains Mono. No private fonts, packages, artwork or business examples
are required. The visual direction follows Block UI: clear semantic color roles,
pill actions, consistent fields and shared states.

## From the audit to the app

| Audit area | Shared owner | App adoption |
| --- | --- | --- |
| Colors and type | Semantic surface, text, border and affordance roles | Host aliases forward to shared roles; feature CSS uses role names; inline links have paired text and hover roles. |
| Action sizes and states | Button and IconButton | Retry, refresh, delete, recovery, composer send and picker triggers. Buttons use 32/40/52px minimum sizes and allow labels to wrap. |
| Forms and choices | Field, Input, Textarea, RadioGroup, Checkbox | Profile, community setup, appearance, plugin import and workflow editing. |
| Search | SearchField | Channels, pages, members and GIFs preserve their query, ref and keyboard handlers. |
| Navigation | NavigationItem | Settings, channel rows, shell destinations, Home and community choices. Route destinations remain buttons, not tabs. |
| Tabs | Tabs | Emoji/GIF uses associated panels and Base UI keyboard activation. Workflow mode retains its existing externally owned editor view. |
| Modals | Dialog and AlertDialog | Page search, community chooser/setup and workflow confirmations. Pending work prevents dismissal; focus returns to the opener. |
| Panels and headers | Panel and PanelHeader | Settings, channels and companion cards use shared paint. Grids, scrolling, docks and subscriptions stay with the feature. |
| Hints | Tooltip | Control titles and agent activity use keyboard-accessible, dismissible hints. Accessible names stay on the controls. |
| Sessions and activity | NavigationItem, Button, IconButton, Panel and PanelHeader | Session history, agent choice, child-channel navigation and activity actions retain unread, admission, draft and focus behavior. Base UI owns their menus. |
| Media stages | surface-inverse with text-inverse | Preserve existing stage values and measure their text pairing explicitly; images and video pixels stay renderer-owned. |

## Deliberate local ownership

- The rich message editor keeps its caret, IME, selection and completion logic.
Completion rows retain `aria-activedescendant` while using shared colors and type.
- GIF and image tiles retain native media-selection buttons and image geometry.
Search, retry, playback and zoom actions use shared controls. The image zoom
range retains native range behavior and reads semantic colors; there is no
separate shared Slider. Media modal focus, drag regions, playback and timecode
ownership stay with the renderer.
- Emoji Mart keeps its shadow-root adapter and compact search geometry. It reads
shared semantic colors, type and the host’s keyboard-focus mode. It does not own
another appearance preference.
- Native disclosures remain for persisted channel groups and diagnostic content.
They are disclosures, not application menus; their content and state remain local.
- Avatars, previews, links, mentions, thread summaries and recipient removal retain
their identity and navigation behavior. Shared appearance does not move their data.
- Panel marks its surface separately from interactive components. Native product
and plugin content inside it can still receive host defaults.
- Legacy utility names remain available through the host bridge for existing
callers and plugins. They are aliases, not another palette. Use the semantic
names and shared components for new work.

## Checking a migration

Check pointer and keyboard behavior, loading and failures, both color modes,
narrow layouts and enlarged text. A rendered app check matters: a component can
look right in the viewer while its stylesheet is missing from the host.

Browser journeys retain community joining, workflow save/recovery, draft and
sidebar persistence, media insertion and focus checks. Visual assertions should
track the shared treatment. The media journey now checks tab/panel associations,
keyboard activation and search clearing instead of the retired picker-specific
stretch animation. No browser journey is removed by this migration.

Before review/integration, run the contribution workflow’s full batch checks.
Draft PRs and a running preview are not claims of native or full-suite validation.
68 changes: 45 additions & 23 deletions docs/design-system.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Design system and appearance

> **The design system going forward:** the imported system is documented in
> [the handoff README](../src/shared/design-system/README.md) and displayed at
> `/tests/fixtures/design-system.html`. New UI and existing surfaces moving off the
> current styles should use it. This initial port does not migrate existing surfaces,
> so the host styling described below still governs those callers until they move;
> the host remains the single owner of appearance throughout the transition.
The shared components live in `src/shared/design-system` and appear at
`/tests/fixtures/design-system.html`. Buzz follows Block UI’s approach to semantic
roles, controls and states, using Base UI for behavior and public fonts and assets.
The app imports the same form and overlay styles as the component viewer.
See [the adoption map](design-system-adoption.md) for ownership and retained adapters.

The **host** owns appearance, including startup and recovery. A plugin must not be
required to render the shell correctly. Pages still own their layout and behavior;
Expand Down Expand Up @@ -89,15 +88,11 @@ picker. Browser regressions cover host Settings changes through an open widget,
opening in Dark, and no updates after disposal. Mutation probes exercise the startup
script, Settings writes/retry, host lifetime and widget initial/update/disposal paths.

The initial integration batch passed the theme/picker journeys in both browsers,
Node/Vitest/plugin-manager tests, formatting/types, build and Clippy. The broad browser
run was **89/90**, not green: WebKit's `initial-position.spec.mjs` reload case reported
a localhost access-control console warning. The identical failure reproduced on
pre-theme `742a770` (one failure, two passes); its cause is not diagnosed or suppressed.
Native Rust test targets compile but contain zero tests. Human light/dark visual
approval and independent source review do not replace attended packaged-app
chrome/relaunch acceptance. Browser evidence also does not cover third-party plugins
that hard-code their own colors.
Run the design guards, unit tests and relevant browser journeys for a changed
component. Native window chrome and relaunch still need an attended packaged-app
check; browser checks do not establish native acceptance or third-party plugin
styling. Keep full batch validation separate from an interactive preview.



## Text size and shortcuts
Expand Down Expand Up @@ -135,14 +130,11 @@ palette, typography roles, materials and component styles with one Tailwind rese
It does not import the viewer's global entry, preference owner, docking vendor CSS
or workspace experiments. Panel styling remains defined only by the shared system.

Existing screens retain their palette, canvas, type sizes and native-control
recipes. Compatibility names are temporary, not the vocabulary for new work.
`bg-primary` retains the old action fill via an explicit compatibility utility;
`text-primary` and `border-primary` belong to the system. The old control radius
is explicitly named `--radius-legacy-control` to avoid overriding shared controls.
Legacy monospace utilities and native code retain their system font stack; migrated
boundaries select the shared mono face. No old paint role is aliased merely because it sounds similar: the current
palettes differ, and aliasing them would silently recolor unmigrated screens.
Bundled screens use semantic colors and shared controls. Compatibility names
forward to the same shared roles; they are not the vocabulary for new work.
`bg-primary` forwards to the prominent action role, while `text-primary` and
`border-primary` keep their text and border meanings. Native plugin fallbacks
remain available outside shared-control boundaries.

Shared primitives carry `data-buzz-ui`, including portal popup roots. Legacy
native-element selectors exclude that boundary and its descendants (without native CSS scope); shared typography starts there.
Expand All @@ -167,3 +159,33 @@ and the temporary font/color compatibility contracts. Remove compatibility check
as their legacy consumers disappear; no separate legacy viewer or test suite is
needed. Browser checks do not establish native or packaged acceptance. Broad scan
remains an agreed integration-batch gate.

## Baseline ownership and exceptions

Bundled UI uses semantic roles, including renderer adapters. Change color values
in the shared token layer and visual control recipes in the shared components.
Feature CSS owns layout, not a second Button/Input recipe. `design:check` rejects
direct palette consumption and feature selectors that override control paint,
padding or typography; `design:census` includes the app and renderer string reads.
These are static guardrails, not a substitute for browser checks.

Anchored emoji, mention, completion, account and diagnostics surfaces use
`popover-surface` for their border, fill, elevation and layer. Their placement,
scrolling and specialized keyboard/editor interactions remain feature-owned.
Popup selection uses the shared hover affordance so it stays visible on the
raised dark surface. Compact completion/emoji layouts may select shared radius
tokens to fit their inner geometry. Shared Button/IconButton `title` props render
a shared Tooltip; content titles (full names, timestamps and media descriptions) remain native.

Explicit exceptions: GIF and image tiles use native media buttons, image zoom
uses a native range with semantic colors; rendered Markdown task
checkboxes and inline links keep their content semantics; the rich editor uses
native selection colors; terminal ANSI colors and decorative artwork remain
renderer-owned. The design viewer's layout experiments are not bundled app UI.
Plugin examples consume public CSS roles and host fallbacks. External plugins
cannot be guaranteed to follow this system; no new plugin API is introduced here.

The browser adoption regression changes semantic fill, type and spacing values
and checks the actual Settings button, inline chips and production CSS inside
message-history containers and anchored popups. It exists because DOM emulation
cannot establish CSS layer ownership.
5 changes: 4 additions & 1 deletion examples/plugins/counter/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ export function apply(ctx) {
const [count, setCount] = React.useState(0);
return React.createElement(
"section",
{ style: { padding: 24, background: "white", borderRadius: 24 } },
{
className: "ui-card",
style: { padding: "var(--space-panel-inset)" },
},
React.createElement("h1", null, "Counter playground"),
React.createElement("p", null, "Your local plugin is running. Narf!"),
React.createElement(
Expand Down
11 changes: 9 additions & 2 deletions examples/plugins/notes/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ export function apply(ctx) {
const [note, setNote] = React.useState("");
return React.createElement(
"section",
{ style: { padding: 24, background: "white", borderRadius: 24 } },
{
className: "ui-card",
style: { padding: "var(--space-panel-inset)" },
},
React.createElement("h1", null, "Notes playground"),
React.createElement(
"p",
Expand All @@ -24,7 +27,11 @@ export function apply(ctx) {
React.createElement("textarea", {
value: note,
rows: 5,
style: { display: "block", width: "100%", marginTop: 8 },
style: {
display: "block",
width: "100%",
marginTop: "var(--space-2)",
},
onChange: (event) => setNote(event.target.value),
}),
),
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"design:build": "pnpm design:typecheck && vite build --config vite.design.config.ts",
"design:preview": "vite preview --config vite.design.config.ts",
"design:typecheck": "tsc -p tsconfig.design.json",
"design:check": "node scripts/design-system/check-type.mjs && node scripts/design-system/check-color.mjs && node scripts/design-system/check-contrast.mjs && node scripts/design-system/check-app-foundations.mjs && node scripts/design-system/check-icons.mjs",
"design:check": "node scripts/design-system/check-type.mjs && node scripts/design-system/check-color.mjs && node scripts/design-system/check-contrast.mjs && node scripts/design-system/check-app-foundations.mjs && node scripts/design-system/check-icons.mjs && node scripts/design-system/check-adoption.mjs",
"design:census": "node scripts/design-system/token-consumers.mjs",
"design:test": "vitest run --config vitest.design.config.ts",
"design:test:browser": "pnpm build && pnpm design:build && playwright test --config tests/fixtures/design-system/playwright.config.ts"
Expand Down
112 changes: 112 additions & 0 deletions scripts/design-system/check-adoption.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/** Production UI chooses semantic roles; shared controls own their appearance. */
import { readdirSync, readFileSync } from "node:fs";
import { join, relative } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import postcss from "postcss";

// These styles are only used by the design viewer's layout experiments.
const viewerStyles = new Set([
"bento.css",
"flex-workspace.css",
"multi-panel-swap.css",
"swap-workspace.css",
"globals.css",
]);
const nativeRecipes = new Map([
[
"src/bundled/emoji/Emoji.module.css",
new Set([".gifGrid button", ".gifGrid button:hover"]),
],
[
"src/features/messages/Messages.module.css",
new Set(['.text input[type="checkbox"]']),
],
["src/shared/InlineReference.module.css", new Set(["button.link"])],
]);
const visualProperty =
/^(?:background(?:-.+)?|color|border(?:-.+)?|box-shadow|font(?:-.+)?|line-height|letter-spacing|padding(?:-.+)?|outline(?:-.+)?)$/;
const controlSelector =
/(?:^|[\s>+~])(?:button|input|textarea|select)(?:[.#][\w-]+|\[[^\]]*\]|:[\w-]+)*$|\.buzz-(?:button|input|textarea|radio|checkbox)\b/;
const palette =
/--(?:neutral|purple|red|green|amber|blue|cyan|orange)-(?:\d+|action-pressed)\b|\b(?:bg|text|border|outline|ring|fill|stroke|shadow|from|via|to)-(?:neutral|purple|red|green|amber|blue|cyan|orange)-\d+\b/g;

export function adoptionFindings(file, source) {
const code = source
.replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, " "))
.replace(/^\s*\/\/.*$/gm, (s) => " ".repeat(s.length));
const findings = [];
for (const match of code.matchAll(palette)) {
findings.push({
line: code.slice(0, match.index).split("\n").length,
reason: `direct palette ${match[0]}; use a semantic role`,
});
}
if (file.endsWith(".module.css")) {
postcss.parse(code).walkRules((rule) => {
// Host fallback explicitly excludes shared controls. GIF tiles, rendered
// Markdown checkboxes and inline links have their own native contracts.
if (rule.selector.includes(":not(:where([data-buzz-ui]")) return;
if (
!controlSelector.test(rule.selector) ||
nativeRecipes.get(file)?.has(rule.selector)
)
return;
rule.walkDecls((decl) => {
if (visualProperty.test(decl.prop))
findings.push({
line: decl.source.start.line,
reason: `${rule.selector} overrides control ${decl.prop}; use the shared recipe`,
});
});
});
}
return findings;
}

export function isAdoptionSource(file) {
if (
!/\.(?:css|tsx?|js|mjs)$/.test(file) ||
/(?:\.test\.|\.spec\.|fixture)/.test(file)
)
return false;
if (
file === "src/shared/design-system/styles/tokens.css" ||
file === "src/shared/design-system/tokens/registry.ts"
)
return false;
if (
file.startsWith("src/shared/design-system/styles/") &&
viewerStyles.has(file.split("/").at(-1))
)
return false;
return true;
}

if (
process.argv[1] &&
import.meta.url === pathToFileURL(process.argv[1]).href
) {
const root = fileURLToPath(new URL("../../", import.meta.url));
const walk = (dir) =>
readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const path = join(dir, entry.name);
return entry.isDirectory() ? walk(path) : [path];
});
const failures = [];
for (const path of [
...walk(join(root, "src")),
...walk(join(root, "examples/plugins")),
]) {
const file = relative(root, path);
if (!isAdoptionSource(file)) continue;
for (const finding of adoptionFindings(file, readFileSync(path, "utf8")))
failures.push(`${file}:${finding.line}: ${finding.reason}`);
}
if (failures.length) {
console.error(failures.join("\n"));
process.exitCode = 1;
} else
console.log(
"✓ Adoption: production colors use roles; feature CSS preserves shared controls",
);
}
26 changes: 3 additions & 23 deletions scripts/design-system/check-color.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -106,29 +106,9 @@ const PALETTE_HUES = [
"orange",
];

/**
* **Palette steps are public now. Only glass is private.**
*
* This rule used to reject `var(--neutral-4)` in a component, on the reasoning
* that a screen should say what a thing *is* rather than which colour it takes.
* That reasoning was imported from Tailwind, where `neutral-800` is a single
* literal and naming it really does break dark mode.
*
* It does not hold here. **Every step in this palette is authored per mode**, so
* `neutral-4` is one value in light and another in dark and a component naming it
* behaves correctly in both. Once that is true, a role whose light and dark
* values are the same step is a name in front of a number — and a name in front
* of a number hides the decision rather than recording it. Nineteen roles were
* exactly that.
*
* Glass stays private for a different reason, which has nothing to do with
* naming: a glass fill without its blur, rim, and lift is not glass. It is
* reachable only through the `glass-primary` / `glass-secondary` utilities, which
* carry the whole material. That is enforced below.
*
* Hues are enumerated rather than matched as `[a-z]+` because a palette step and
* a non-colour token are the same shape: `--neutral-4` and `--space-4`.
*/
/** Glass is consumed as a complete material, never as a bare fill. Direct
* palette consumption in production is checked by check-adoption.mjs; viewer
* swatches may inspect the palette. */
const PRIVATE_TOKEN = /var\(\s*--glass-\d+\s*\)/g;

/**
Expand Down
3 changes: 3 additions & 0 deletions scripts/design-system/check-contrast.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const TEXT_ROLES = [
"--text-warning",
"--text-success",
"--text-accent",
"--text-link",
"--text-primary",
"--text-secondary",
"--text-tertiary",
Expand All @@ -108,6 +109,8 @@ const TEXT_ROLES = [
* every fill it can actually sit on, and hover is one of them.
*/
const PAIRS = [
["--text-inverse", "--surface-inverse"],
["--text-link", "--affordance-link-hover"],
...["subtle", "subtle-hover", "subtle-pressed"].map((state) => [
"--text-standard",
`--affordance-${state}`,
Expand Down
Loading
Loading