From 68ee4050642813b5db78266798dc12e00403a25a Mon Sep 17 00:00:00 2001
From: morgmart <98432065+morgmart@users.noreply.github.com>
Date: Tue, 8 Sep 2026 10:52:05 -0700
Subject: [PATCH] feat(desktop-new): shape product UI and system docs
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
---
desktop-new/AGENTS.md | 3 +-
desktop-new/MAINTAINING_DESIGN_SYSTEM.md | 75 ++++++
.../src/app/routes/design/agents-guide.tsx | 6 +
.../src/app/routes/design/composer.tsx | 7 -
.../src/app/routes/design/design-guide.tsx | 6 +
desktop-new/src/app/routes/design/growth.tsx | 7 -
.../src/app/routes/design/maintaining.tsx | 6 +
.../src/app/routes/design/vocabulary.tsx | 7 -
.../agent-activity/ui/AgentActivityRail.tsx | 5 +-
.../src/features/composer/ui/EmojiPicker.tsx | 3 +-
.../features/composer/ui/MessageComposer.tsx | 54 +++-
.../ui/ConversationComposerDock.tsx | 37 +--
.../design-system/ui/ComponentsPage.tsx | 3 +-
.../design-system/ui/ComposerPage.tsx | 79 ------
.../design-system/ui/DesignSystemLayout.tsx | 138 +++++-----
.../features/design-system/ui/GrowthPage.tsx | 89 -------
.../design-system/ui/MarkdownPage.tsx | 90 +++++++
.../design-system/ui/OverviewPage.tsx | 15 +-
.../design-system/ui/SystemDocumentPage.tsx | 18 ++
.../design-system/ui/VocabularyPage.tsx | 93 -------
.../design-system/ui/componentSpecimens.tsx | 72 ++++++
.../design-system/ui/composerSpecimens.tsx | 63 +++++
desktop-new/src/features/navigation/types.ts | 2 +-
.../navigation/ui/MessagesNavigator.tsx | 86 +-----
.../src/features/navigation/useNavigation.ts | 2 +-
desktop-new/src/features/sessions/types.ts | 5 +-
.../src/features/sessions/ui/AppShell.tsx | 42 ++-
.../features/sessions/ui/NewSessionView.tsx | 2 +-
.../sessions/ui/ParticipantDialog.tsx | 6 +-
.../src/features/sessions/ui/SessionView.tsx | 66 ++---
desktop-new/src/shared/runtime/client.ts | 36 +++
desktop-new/src/shared/styles/components.css | 114 +++++++-
.../src/shared/styles/design-components.css | 244 ++++++++++++------
desktop-new/src/shared/styles/product.css | 121 +++++++--
.../src/shared/ui/ConversationHeader.tsx | 54 ++++
desktop-new/src/shared/ui/IconButton.tsx | 8 +-
desktop-new/src/shared/ui/SearchField.tsx | 5 +-
desktop-new/src/shared/ui/ShimmerText.tsx | 21 ++
desktop-new/src/shared/ui/registry.test.ts | 16 +-
desktop-new/src/shared/ui/registry.ts | 70 ++++-
desktop-new/tests/sessions.spec.ts | 4 +-
41 files changed, 1129 insertions(+), 651 deletions(-)
create mode 100644 desktop-new/MAINTAINING_DESIGN_SYSTEM.md
create mode 100644 desktop-new/src/app/routes/design/agents-guide.tsx
delete mode 100644 desktop-new/src/app/routes/design/composer.tsx
create mode 100644 desktop-new/src/app/routes/design/design-guide.tsx
delete mode 100644 desktop-new/src/app/routes/design/growth.tsx
create mode 100644 desktop-new/src/app/routes/design/maintaining.tsx
delete mode 100644 desktop-new/src/app/routes/design/vocabulary.tsx
delete mode 100644 desktop-new/src/features/design-system/ui/ComposerPage.tsx
delete mode 100644 desktop-new/src/features/design-system/ui/GrowthPage.tsx
create mode 100644 desktop-new/src/features/design-system/ui/MarkdownPage.tsx
create mode 100644 desktop-new/src/features/design-system/ui/SystemDocumentPage.tsx
delete mode 100644 desktop-new/src/features/design-system/ui/VocabularyPage.tsx
create mode 100644 desktop-new/src/features/design-system/ui/composerSpecimens.tsx
create mode 100644 desktop-new/src/shared/ui/ConversationHeader.tsx
create mode 100644 desktop-new/src/shared/ui/ShimmerText.tsx
diff --git a/desktop-new/AGENTS.md b/desktop-new/AGENTS.md
index e041514d20..24f9c5af20 100644
--- a/desktop-new/AGENTS.md
+++ b/desktop-new/AGENTS.md
@@ -201,8 +201,7 @@ Deliberately, so nobody assumes it was forgotten:
## Architecture
-This client is built on **composable capabilities** — see the plan in Morgan's
-vault. A capability owns a durable product truth: it may be behavior-only,
+This client is built on **composable capabilities**. A capability owns a durable product truth: it may be behavior-only,
UI-bearing, or both. A feature is an organizational boundary, a surface/view
owns spatial arrangement, and shared UI owns generic visual and interaction
primitives.
diff --git a/desktop-new/MAINTAINING_DESIGN_SYSTEM.md b/desktop-new/MAINTAINING_DESIGN_SYSTEM.md
new file mode 100644
index 0000000000..22e4e70481
--- /dev/null
+++ b/desktop-new/MAINTAINING_DESIGN_SYSTEM.md
@@ -0,0 +1,75 @@
+# Maintaining the design system
+
+## The point
+
+The system helps Buzz stay clear and coherent as it grows. It should make the common choice easy, while leaving room for a design to be specific, surprising, or new. It is a shared memory of decisions that have been made—not a gate a designer needs to pass through.
+
+When the system cannot express the right design, change the system. Do not distort the design to satisfy an old rule.
+
+## Start with the work in front of you
+
+Build the screen or interaction you are trying to make. Use the existing colors, type, surfaces, and components when they fit. Look at the result in context, not just in a token table.
+
+A repeated need is evidence. One-off work is evidence too: it may reveal that a ramp step is wrong, a component needs another supported state, or a rule no longer reflects the product.
+
+## Color: use the ramps first
+
+Each color ramp has a version for light mode and a version for dark mode. A step has the same job in both: a quiet neutral surface, a selected row, a strong accent, or readable colored text. That lets a screen use a normal ramp step such as `neutral-2` and still work in both modes.
+
+A semantic color name is earned when a plain ramp step cannot tell the truth:
+
+- Light and dark need different steps for the same job. A panel, for example, needs a different relationship to its surroundings in each mode.
+- A name protects a real rule. The three text levels and the single shared border weight are examples.
+- A pattern has appeared enough times that giving it one name makes the product easier to understand and keep consistent.
+
+Do not create a semantic name just to avoid choosing a ramp step. If a name points to the same step in both modes, it is usually hiding a decision rather than recording one.
+
+## Refine the ramp when the product teaches us something
+
+The ramps are not sacred. They began as a useful starting point and should change when real product work gives us better evidence.
+
+The lighter search-field neutral is a good example. A real prototype showed that the old value was too heavy, so we corrected the neutral ramp instead of adding a search-only color or a fractional stop. That improvement now benefits every quiet compact surface that uses the same step.
+
+Before adding a new step or a new role, ask:
+
+1. Is an existing step already the right answer?
+2. If not, is the ramp itself missing the right relationship?
+3. Does light mode need one step and dark mode another? If so, give that relationship a semantic name.
+4. Will people see this pattern again, or is it one moment in one screen?
+
+## Components grow from real repetition
+
+A shared component is a promise: the same interaction and visual language will work the same way wherever it appears.
+
+Build the first version where it is needed. Once another real use appears, decide what is truly shared:
+
+- a generic building block belongs in the design system;
+- Buzz-specific behavior belongs with the product capability that owns it;
+- a complete arrangement should only become reusable when another surface needs that exact arrangement.
+
+Do not build a catalogue in advance. Small, proven components are more flexible than a large component with a long list of switches.
+
+## Keep the important states visible
+
+Every interactive piece should have a clear default, hover, selected state where it applies, and disabled state where it matters. Selection is a lasting statement, not just a stronger hover. Keyboard focus should be visible, and the same action should work with a pointer or keyboard.
+
+Check narrow, medium, and wide layouts. Check light and dark mode together. A decision that works only in a component specimen is not finished.
+
+## What the guards are for
+
+The automated checks protect decisions that are easy to accidentally undo: readable contrast, the shared type scale, and the color system’s light/dark structure.
+
+They are guardrails, not judges. Each one has a named exception list with room to explain why a specific design needs to differ. Adding an exception is normal when it reflects a deliberate design choice. If the exceptions start pointing in one direction, improve or remove the rule instead of accumulating workarounds.
+
+A passing check means the system is internally consistent. Looking at the actual interface tells us whether the system is good. We need both.
+
+## A good change leaves a trace
+
+When you change the system, make the decision easy to find:
+
+- update the relevant token, component, or guidance;
+- show the result on the design-system site where useful;
+- use the new thing in real product work, not only in documentation;
+- note the reason in plain language close to the decision.
+
+The goal is not to freeze Buzz into a style. It is to give every future designer and builder a clear starting point—and the confidence to improve it when the work asks for more.
diff --git a/desktop-new/src/app/routes/design/agents-guide.tsx b/desktop-new/src/app/routes/design/agents-guide.tsx
new file mode 100644
index 0000000000..490a3ed173
--- /dev/null
+++ b/desktop-new/src/app/routes/design/agents-guide.tsx
@@ -0,0 +1,6 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { SystemDocumentPage } from "@/features/design-system/ui/SystemDocumentPage";
+
+export const Route = createFileRoute("/design/agents-guide")({
+ component: () => ,
+});
diff --git a/desktop-new/src/app/routes/design/composer.tsx b/desktop-new/src/app/routes/design/composer.tsx
deleted file mode 100644
index 8200f42dfa..0000000000
--- a/desktop-new/src/app/routes/design/composer.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-
-import { ComposerPage } from "@/features/design-system/ui/ComposerPage";
-
-export const Route = createFileRoute("/design/composer")({
- component: ComposerPage,
-});
diff --git a/desktop-new/src/app/routes/design/design-guide.tsx b/desktop-new/src/app/routes/design/design-guide.tsx
new file mode 100644
index 0000000000..f2e62fb58e
--- /dev/null
+++ b/desktop-new/src/app/routes/design/design-guide.tsx
@@ -0,0 +1,6 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { SystemDocumentPage } from "@/features/design-system/ui/SystemDocumentPage";
+
+export const Route = createFileRoute("/design/design-guide")({
+ component: () => ,
+});
diff --git a/desktop-new/src/app/routes/design/growth.tsx b/desktop-new/src/app/routes/design/growth.tsx
deleted file mode 100644
index 732be720fc..0000000000
--- a/desktop-new/src/app/routes/design/growth.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-
-import { GrowthPage } from "@/features/design-system/ui/GrowthPage";
-
-export const Route = createFileRoute("/design/growth")({
- component: GrowthPage,
-});
diff --git a/desktop-new/src/app/routes/design/maintaining.tsx b/desktop-new/src/app/routes/design/maintaining.tsx
new file mode 100644
index 0000000000..1c2c4c9b5a
--- /dev/null
+++ b/desktop-new/src/app/routes/design/maintaining.tsx
@@ -0,0 +1,6 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { SystemDocumentPage } from "@/features/design-system/ui/SystemDocumentPage";
+
+export const Route = createFileRoute("/design/maintaining")({
+ component: () => ,
+});
diff --git a/desktop-new/src/app/routes/design/vocabulary.tsx b/desktop-new/src/app/routes/design/vocabulary.tsx
deleted file mode 100644
index 102554e6fe..0000000000
--- a/desktop-new/src/app/routes/design/vocabulary.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-
-import { VocabularyPage } from "@/features/design-system/ui/VocabularyPage";
-
-export const Route = createFileRoute("/design/vocabulary")({
- component: VocabularyPage,
-});
diff --git a/desktop-new/src/features/agent-activity/ui/AgentActivityRail.tsx b/desktop-new/src/features/agent-activity/ui/AgentActivityRail.tsx
index cc53963661..334685ec0f 100644
--- a/desktop-new/src/features/agent-activity/ui/AgentActivityRail.tsx
+++ b/desktop-new/src/features/agent-activity/ui/AgentActivityRail.tsx
@@ -1,3 +1,5 @@
+import { ShimmerText } from "@/shared/ui/ShimmerText";
+
import { latestActivityItem } from "../activityProjection";
import type { AgentTurn } from "../types";
@@ -21,9 +23,8 @@ export function AgentActivityRail({ turns }: { turns: AgentTurn[] }) {
- {label}
+ {label}
);
})}
diff --git a/desktop-new/src/features/composer/ui/EmojiPicker.tsx b/desktop-new/src/features/composer/ui/EmojiPicker.tsx
index 71bf573fd2..68d2766460 100644
--- a/desktop-new/src/features/composer/ui/EmojiPicker.tsx
+++ b/desktop-new/src/features/composer/ui/EmojiPicker.tsx
@@ -53,7 +53,8 @@ export function EmojiPicker({
}
+ icon={}
+ size="toolbar"
variant="ghost"
onMouseDown={(event) => event.preventDefault()}
/>
diff --git a/desktop-new/src/features/composer/ui/MessageComposer.tsx b/desktop-new/src/features/composer/ui/MessageComposer.tsx
index cdffca27fc..3828e89f45 100644
--- a/desktop-new/src/features/composer/ui/MessageComposer.tsx
+++ b/desktop-new/src/features/composer/ui/MessageComposer.tsx
@@ -1,8 +1,9 @@
import {
IconArrowUp,
- IconPaperclip,
- IconTypography,
+ IconLetterCase,
IconMicrophone,
+ IconPaperclip,
+ IconPlayerStopFilled,
} from "@tabler/icons-react";
import { type FormEvent, useEffect, useRef, useState } from "react";
@@ -24,12 +25,17 @@ export function MessageComposer({
onDraftChange,
onSend,
placeholder = "Message the session",
+ responseControl,
}: {
disabled?: boolean;
draft: string;
onDraftChange: (value: string) => void;
onSend: (content: string) => Promise;
placeholder?: string;
+ responseControl?: {
+ state: "responding" | "stopping";
+ onStop?: () => void;
+ };
}) {
const [sending, setSending] = useState(false);
const [error, setError] = useState(null);
@@ -99,7 +105,7 @@ export function MessageComposer({