Skip to content

feat(desktop): developer mode display style — prompt-first UI with pinned/recent channel list, palette, and split-pane side chats - #3459

Draft
joahg wants to merge 115 commits into
block:mainfrom
joahg:joah/dev-mode-display-style
Draft

feat(desktop): developer mode display style — prompt-first UI with pinned/recent channel list, palette, and split-pane side chats#3459
joahg wants to merge 115 commits into
block:mainfrom
joahg:joah/dev-mode-display-style

Conversation

@joahg

@joahg joahg commented Jul 29, 2026

Copy link
Copy Markdown
Member

Adds a developer mode display style to the Buzz desktop app (AGNTOPS-354): a prompt-first, keyboard-driven, Amp/Claude-Code-style surface layered on the existing channel structure.

Core flow

  • One monospace composer. Tab / Shift+Tab cycles the target between plain human chat and every available agent (managed + shared relay agents). The default target is the first managed (local) agent — chat mode is only the default when no agents exist.
  • Enter on a fresh prompt spawns a new channel, attaches/tags the selected agent, and sends the prompt there. Agents are not limited to a single channel.
  • Agent-directed prompts carry an @Agent prefix on the wire (plus the mention tag) so the standard UI and other clients see who a prompt is for — but dev mode lifts it out of the message body and renders it as a small “to Agent” line (in the agent's chat color) under the author name. The composer shows its Tab-cycled target the same way: a “to Agent” line at the top of the chat box (no more bordered pill), with “chat” / “working…” for the other states.
  • Prompts render as cards in the channel (borderless until ↑/↓ selects one); agent replies land in the card's thread on the same indent as the prompt. ↑/↓ (empty composer) walk cards with zero layout shift; Enter on a card opens a split-screen side chat scoped to that prompt's thread.
  • Kind-7 reactions render as chips on prompts and replies — agents react while working, so the chips double as a working/loading indicator without any architecture change.
  • Every prompt card shows the leading run of agent replies inline in the channel — everything the agent said before a human responded, no matter how old the prompt; from the first human reply onward, messages collapse into a "… N more replies" affordance that opens the thread pane (replaces the old newest-three auto-expand model).

Channel list: pinned chats, recency order, unread indicators

  • The channel list is always visible on the left; it dims while a channel is focused. The composer covers only the channel pane — full width when creating a new session or navigating the channel list.
  • Per-channel pin toggle (hover a row → pin/unpin): pinned chats render in their own pinned section at the top; all other chats sit beneath. Pins are device-local (localStorage) — the channel protocol has no pin field.
  • Both sections auto-order by last activity, newest first; new channels start unpinned near the top of the lower section.
  • Unread indicators: unread channels render with a brighter, bold name plus a dot. Dev mode tracks unread channel-level posts only — a new top-level prompt lights the dot, unopened thread replies don't. Opening a channel marks its top-level timeline read (same passive NIP-RS path the standard channel screen uses); standard UI unread behavior is unchanged.
  • Draggable channel-list width: a vertical divider on the list's right edge (pointer + keyboard, clamped 200–480px, persisted per device); the top bar's columns track the width live while dragging.
  • from the fresh composer highlights/previews channels (transcript previews behind), Enter opens, Escape backs out; Escape inside a channel returns to the list. Clicking a channel row opens it immediately on the first click — keyboard navigation stays preview-first.

Command palette

  • ⌘K anywhere (or / on an empty composer) opens an Amp-style palette: channel search across all sessions, agent configuration, appearance/profile/notifications settings, a keyboard shortcuts pane, switch to standard UI, and a name-color picker.
  • With a channel focused or previewed, the palette offers add someone to # channel (searches people & agents; local managed agents are attached with a running harness, others added with the right role), leave # channel (archives instead when you're the last human member, since the relay refuses to remove the last owner), archive # channel, and pin/unpin. While typing, an action verb typed literally ("archive", "leave", "pin"…) ranks first, then matching channels, then incidental action matches.
  • Archiving or leaving a chat lands on the most recently active non-pinned chat (fresh composer only when none remain).

Window chrome & focus

  • Developer mode replaces the shared top chrome: its gray surface stretches to the top of the window, with fixed-px clearance so the title never sits under the macOS traffic lights (~32px residual clearance when the 56px community rail absorbs most of their footprint).
  • The top bar is two columns aligned with the panes below: "buzz · developer mode" sits over the channel list and the active/previewed channel name + member summary sit left-aligned over the transcript — tracking the navigator divider live while it's dragged. The transcript itself carries no duplicate channel name/topic header.
  • No key hints in the UI: the composer hint footer, navigator footer, and top-bar shortcut labels are gone. Typing ? in an empty composer (or the palette's "keyboard shortcuts" entry) opens a shortcuts pane listing every dev-mode binding.
  • Refocusing the window restores the last-focused input; clicks on dead space never blur the active composer.
  • Prompt-card selection is keyboard-only (↑/↓ + Enter); clicking a card or message never selects it or steals focus — message text stays drag-selectable, and a click that ends without a selection returns focus to the composer. While a card is selected the caret leaves the message box entirely (typing is disabled); the shell drives ↑/↓/Enter/Escape, and clicking the box clears the selection to resume typing.

Split-pane side chat

  • Channel and side-chat composers sit side by side; draggable divider (pointer + keyboard, clamped 25–75%, persisted per device).
  • Both composers auto-grow upward with their content (wrapped lines included), capped at 40% of the window before scrolling internally; the top border doubles as a drag handle (pointer + keyboard) that sets a persisted minimum height.
  • Inactive pane dims; ←/→ on an empty composer switches panes.
  • ⌥↑/⌥↓ steps through the visible channel list (pinned then recent) directly from the composer — the caret never leaves the textbox, works mid-draft, and from the fresh composer ⌥↑ opens the nearest channel.
  • ⌘N jumps to the fresh composer to start a new channel from anywhere in dev mode. ⌘T opens a draft side chat in the open channel: the pane opens before any thread exists; the first send posts a root message to the channel (exactly like the main composer) and the pane attaches to that new thread for follow-ups.

Channel naming

  • Channel names are generated by an agent: a new buzz-acp complete subcommand runs a bounded one-shot ACP prompt through the tagged agent's own harness (same command/model/env resolution as agent spawn), bridged by a generate_agent_completion Tauri command. The exchange is a private subprocess — it never appears in any channel.
  • Channels are created instantly under a neutral unique new-session placeholder and renamed asynchronously when the title arrives; a lingering placeholder makes naming failures visible instead of masquerading as generated titles.
  • Falls back to the mesh LLM node's OpenAI-compatible endpoint when no managed agent completion is available.

Color

  • Deterministic per-author name colors hashed from pubkey into a curated palette, with a device-local hex override settable from the palette (a synced profile color field would need schema/event changes).
  • The composer's “to Agent” line and caret wear the selected agent's chat color.
  • Conservative keyword highlighting rendered as React spans: inline code, @mentions. Mentions matching one of the message's p-tag members render as plain colored text in that author's chat color (no box); multi-word agent names like amp (local) are matched longest-first. On human-authored messages, a leading mention is treated as direction, not prose — stripped from the body (side-chat header preview included) and shown as the “to Agent” line. Agent-authored messages keep their mentions inline in the body as normal colored text.
  • #channel references: typing # in either composer autocompletes known channel names (↑/↓ select, Tab/Enter insert, Esc dismiss); accepted references serialize as plain #channel-name text (the same format the standard UI links), and known references in the transcript render as clickable links that open that channel without stealing composer focus. Multi-word names match longest-first, same as mentions.
  • URLs render as clickable Slack-style links (link icon + fetched page title when available, else the scheme-stripped URL truncated at 64 chars) opening in the system browser; a new generic fetch_page_title Tauri command (https-only, 4s timeout, 256KB cap) resolves og:/twitter:/<title> titles.

Layout containment

  • Chat content can no longer push the layout past the right edge of the window: the dev-mode shell (a flex item) now has min-w-0 so nowrap content (truncated link labels, thread-header previews) can't inflate its intrinsic width, and the transcript/side-chat scroll viewports clip residual horizontal overflow. Long unbroken words and link labels wrap inside their pane.
  • New Playwright regression test (dev-mode-overflow.spec.ts, smoke project) sends a long unbroken message, opens the side chat, and asserts nothing extends past the window.

Markdown rendering

  • Agent markdown renders as rich formatting in dev-mode transcripts: a block-level layer (fenced code blocks, headings, bullet/numbered lists with hanging indents, blockquotes, horizontal rules) over the existing span-based inline highlighter, which now also handles bold, italic, strikethrough, and [label](url) links (labeled links render via the Slack-style link component using the written label, skipping the title fetch). Emphasis delimiters are strict so snake_case and arithmetic stay plain prose; everything renders as React nodes — never HTML — and plain human chat renders exactly as typed.

Channel members & join/leave narration

  • The top bar shows who is in the chat at any scale: small channels (≤4 members) list every name in its author color; larger ones collapse to a compact N members count read straight off the channel record — a 1000-member channel never fetches its roster just to render the header. Both refresh live off the same membership invalidation the standard UI uses.
  • Clicking the member summary (or the palette's view members action) opens a searchable member browser in the command palette: colored names with role/agent details, filtered as you type, rendering capped at 40 rows with a "type to narrow" overflow hint so huge rosters stay responsive.
  • Member joins/leaves render in the channel: kind:40099 membership system messages (already fetched in the channel timeline) now render as muted narration rows (→ alice joined, ← bob left, removed by …) interleaved chronologically with the prompt cards. Card ↑/↓ navigation skips them.
  • Departed members no longer appear in the member list, so their names resolve through a batch profile lookup fallback in useMemberNameResolver.
  • New Playwright regression test (dev-mode-members.spec.ts, smoke project) plus unit tests for the 40099 payload parser.

@user mentions

  • Typing @ in either dev-mode composer autocompletes users: current channel members rank first, then relay-wide search_users results (self excluded). ↑/↓ select, Tab/Enter insert @Display Name , Esc dismisses — same keyboard contract as #channel autocomplete.
  • Accepted mentions stay as plain text in the message; at send time any accepted @Name still present emits a ["p", pubkey] tag (multi-word names match longest-first; editing a mention away drops its tag; user@example.com never triggers).
  • Mentioning a non-member best-effort adds them to the channel (mirroring how the targeted agent is auto-attached); a failed add never blocks the send — the p tag still goes out.
  • New Playwright regression test (dev-mode-mentions.spec.ts, smoke project) plus unit tests for mention extraction.

Notes

  • Monospace text renders in JetBrains Mono (bundled via @fontsource-variable/jetbrains-mono, wired into Tailwind's font-mono stack with the system stack as fallback).
  • Display style persisted in localStorage (⌘⇧D toggles); standard UI untouched.
  • Validation: pnpm typecheck, pnpm check, pnpm test (3826 pass), cargo test -p buzz-acp (639 pass), pnpm build all green.

Linear: AGNTOPS-354

Parent & sub-channels (agent sessions)

  • Channels can now have sub-channels, identified purely by naming convention — parent-slug--sub-slug, one nesting level — so the relay is completely untouched and existing clients keep working.
  • Only main channels appear in the left navigator; a parent row aggregates its whole family's latest activity and unread state. Opening a channel shows tabs across the top (main, then each sub's suffix, unread dots, + sub) — horizontally scrollable and built off a single-pass parent/child index, so parents with hundreds of subs stay cheap.
  • + sub (tab strip or palette "new sub-channel" action) drafts a prompt; Enter creates the child (inheriting the parent's visibility/type), sends the prompt there, posts a spawn announcement in the parent, and records the relationship + report-back contract in both channels' canvases. Children get LLM-generated names via the same naming path as new channels (renaming only the suffix); sanitizeChannelName now guarantees generated names can never contain --.
  • Membership invariant: every sub-channel member must belong to the parent (parent members need not join every child). Enforced client-side — the palette's add-member search inside a child only offers parent members, the selected agent is added to the parent first, and mentioned non-parent users are skipped from auto-add.
  • Renaming a parent cascades to all children (suffixes preserved) with bounded concurrency; partial failures warn without rolling back the parent rename.
  • CLI/agents: buzz channels create --parent <channel> mirrors the same contract — parent resolution by id or name, caller-membership check, slug sanitization, no nested children, inherited type/visibility, spawn announcement + canvas updates. The ACP base prompt documents how agents spawn sub-channels and report completion back to the parent.
  • New Playwright suite (dev-mode-sub-channels.spec.ts): children hidden from the navigator + exposed as tabs, + sub creation with parent announcement, palette action.

Tabs UX: keyboard cycling & terminology

  • User-facing "sub-channel" copy is now "tab" — the tab strip button reads + tab (test id dev-mode-new-tab), and the palette action is "new tab in # channel" with a ⌘⇧T hint. Internal naming (parent--sub slug convention, canvas metadata, CLI --parent) is unchanged for relay compatibility.
  • ⌘⇧T starts a new-tab draft; ⌘[ / ⌘] cycle backward/forward through a channel's tabs (main + subs), wrapping at the ends.
  • New-tab drafts get an obvious visual treatment: a highlighted banner above the composer ("new tab in # channel · esc cancels") plus a tinted composer background, instead of only a placeholder change.
  • Window-level dev-mode shortcuts (⌘K, ⌘N, ⌘T, ⌘⇧T, ⌘[/⌘]) are consolidated into a new useDevModeShortcuts hook.
  • E2E coverage extended: draft banner + Escape cancel, palette "new tab" search, ⌘⇧T, and ⌘[/⌘] wrap-around cycling.
  • Palette rename: "rename # channel" switches the palette into a rename mode — type the new name, Enter applies. Names are sanitized to kebab-case and de-duped. Renaming a tab edits only its suffix (the parent-- prefix is the parent link); renaming a main cascades to its tabs via the existing useUpdateChannelMutation plan, with an active channel-list refetch so the family stays intact in the mounted dev-mode UI.

Contextual unread indicators that bubble up

  • Unread state bubbles up the hierarchy: a main channel's navigator row lights up when any of its tabs has an unread post or any thread in the family has unread replies — not just for top-level posts in the main channel. Channel-level and thread-level read state stay distinct (topLevelUnreadChannelIds vs. full unread set).
  • Contextual indicators at every level: tabs keep their unread dot while a nested thread inside them is unread (even the active tab); prompt cards show a thread-unread dot; the collapsed "… N more replies" affordance gets unread styling when hidden replies are unread.
  • Attention routing: opening an unread channel from the navigator (click or Enter) routes directly to the unread tab. The newest unread thread's side chat auto-opens only when its unread replies are collapsed (a human already responded in the thread); unread replies that render inline in the main chat view — the leading agent run — are marked read just by viewing the channel, so no side chat is forced open for them. Direct destinations stay exact — tab clicks, palette opens, ⌘[/⌘], and ⌥↑/⌥↓ never reroute.
  • Precise read advancement: rendering a card's inline replies advances thread read state only through the visible run (shared selectInlineVisibleCount helper) — collapsed replies stay unread until the side chat is actually opened. Channel previews from the navigator never clear read state.
  • New projection helpers (unreadThreads.ts: selectUnreadThreadRoots / findUnreadFamilyTarget) with unit tests, a useUnreadRouting hook resolving families via the existing single-pass SubChannelIndex, and a new Playwright suite (dev-mode-unread.spec.ts) covering bubbling, routing, and read-state distinctions. Shell focus/refocus logic extracted to useShellFocusGuards to keep DevModeShell under the file-size limit (no behavior change).

Included from PR #3335

Media attachments

  • Paste an image or video into either composer (main channel or side chat) to attach it: pending attachments render above the composer with image previews / video filename labels, per-file upload progress, error state, and remove buttons. Enter is gated while uploads are in flight; failed sends restore both text and attachments.
  • Sends use the existing pipeline — useMediaUpload + buildOutgoingMessage with NIP-92 imeta tags plus standalone markdown media lines — so attachments are fully compatible with the standard UI and other clients.
  • In both transcript panes, imeta-tagged media and standalone ![alt](url) lines render through the shared Markdown renderer: images open in the standard lightbox, videos get native controls, and everything stays constrained to pane width. Media-only sends work inside an existing channel (a brand-new channel still needs prompt text for naming).

Palette: join channels you haven't joined

  • Palette search now includes open channels the user hasn't joined (already returned by get_channels): they surface only while typing (a relay can have hundreds), labeled "not joined · enter to join", ranked below joined channels. Enter joins and opens in one step. Foreign parent--sub tabs stay hidden unless you're a member of the parent (membership invariant). The left navigator still shows joined channels only.

Developer-mode community rail

  • The community picker (multi-community rail) gets a developer variant when dev mode is active: flat squared monospace tiles with border-based active/hover states, squared unread badge/dot, dashed-border add button, and the dev shell's gray background — no Discord-style rounding or color pop. Standard mode is untouched, and all behavior (switching, drag-reorder, context menu, unread indicators) is shared between variants.

Authenticated link labels (Slack, Google) + copyable link URLs

  • Slack permalinks and private Google files sit behind auth walls, so the generic title fetch showed "Sign in to Block". A new fetch_agent_link_label Tauri command resolves labels through the locally installed sq agent-tools CLI first: Slack channel messages label as author in #channel, DM messages as author with partner, bare channel links as #channel, and Google Docs/Drive/Sheets/Slides show their real file title. Everything is best-effort — missing sq, CLI errors, and timeouts fall back to the generic page-title path, and CLI errors never surface in the UI.
  • The generic HTML title extractor now rejects auth-wall titles ("Sign in…", "Log in…", "Login…") so an unauthenticated fallback renders the bare URL instead of a login page title.
  • Copying a selection containing a transformed link now keeps the URL: dev-mode links carry a data-dev-link marker, and a shell-level copy handler rewrites text/plain so each link serializes as its href (surrounding text preserved) while text/html keeps the pretty labels for rich paste targets. Previously copying a link title could paste as nothing.
  • Native text selection includes links: the link anchor previously rendered as an atomic inline-flex box with a truncating span, so double-click-drag / triple-click selection skipped it entirely. It now flows as plain inline text (labels wrap with the message instead of truncating), so system selection gestures pick up the link — and the copy handler puts its URL on the clipboard.

Per-channel composer drafts

  • Message drafts are scoped per channel instead of following you across channels (AGNTOPS-396, requested in #buzz-dev-mode--per-channel-message-drafts): an in-memory draft map keyed by channel ID stashes the composer text when you switch away and restores that channel's own draft when you return. The fresh/new-session composer keeps its own separate draft slot.
  • Sending consumes the origin channel's draft; a failed send restores the prompt to the channel it was sent from, even if you've navigated elsewhere meanwhile. Switching channels mid-e edit preserves the pre-edit channel draft rather than leaking the abandoned edit buffer.
  • Drafts survive display-style toggles (module-level store, not component state) but intentionally do not persist across app restarts.

joahg and others added 2 commits July 28, 2026 22:34
Prompt-first, terminal-style UI toggled in Settings > Appearance or
Cmd/Ctrl+Shift+D. Tab cycles the composer between chat and agent modes;
Enter on a fresh prompt spawns a stream channel named from the prompt,
attaches the selected agent, and mentions it so it starts working there.
Arrow keys navigate recent session channels.

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Each prompt in a session channel renders as a card containing the
agent's threaded response. With the input empty, Up/Down selects cards;
Enter opens the selected card's thread in a 50/50 split side chat with
its own composer, so follow-up questions carry that thread's context.
Escape unwinds side chat, card selection, then channel.

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
@joahg joahg changed the title feat(desktop): add developer mode display style feat(desktop): developer mode display style with prompt cards and split-screen thread side chat Jul 29, 2026
…e polish (AGNTOPS-354)

- Amp-style command palette (Ctrl+O or / on empty composer): channel search
  across all sessions, settings/agent-config entries, standard-UI switch,
  and a name-color picker
- Explicit channel navigation: up from the fresh composer slides a channel
  navigator out from the left with live preview; enter opens, escape backs
  out; escape inside a channel returns to the navigator
- LLM channel naming via the mesh LLM node's OpenAI-compatible endpoint
  when running, with async rename and slug fallback
- Split-pane side chat: draggable (and keyboard-resizable) divider with
  persisted split, inactive-pane dimming, left/right arrows switch panes
- Deterministic per-author name colors with device-local hex overrides,
  conservative keyword highlighting (inline code, URLs, mentions)
- Square corners on prompt cards, pills, and channel rows

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
@joahg joahg changed the title feat(desktop): developer mode display style with prompt cards and split-screen thread side chat feat(desktop): developer mode display style — prompt-first UI with command palette, channel navigator, and split-pane side chats Jul 29, 2026
joahg and others added 2 commits July 28, 2026 23:26
…split view

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
…rome polish in dev mode (AGNTOPS-354)

- Channel list is always visible, dimmed while a channel is focused;
  composer covers only the channel pane, full width only for new sessions
- Device-local channel categories with per-row assignment menu;
  uncategorized/new channels always render below all categories
- Remove the shared top chrome bar in developer mode; dev-mode gray
  reaches the window top with macOS traffic-light clearance
- Window refocus restores the last-focused input; dead-space clicks
  no longer blur the composer
- Kind-7 reactions render as chips on prompts and replies (doubles as
  the agent working indicator)
- Agent replies align with the prompt's indent; selection hint is
  absolutely positioned so up/down navigation causes no layout shift
- Composer mode pill and caret match the agent's chat name color
- Agent-directed prompts carry a visible @agent prefix

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
@joahg joahg changed the title feat(desktop): developer mode display style — prompt-first UI with command palette, channel navigator, and split-pane side chats feat(desktop): developer mode display style — prompt-first UI with categorized channel workspace, palette, and split-pane side chats Jul 29, 2026
joahg and others added 12 commits July 29, 2026 00:42
Channel names now come from the tagged agent's own harness instead of a
prompt slug. buzz-acp gains a 'complete' subcommand (bounded one-shot
ACP prompt returning JSON text); the desktop bridges it through a new
generate_agent_completion Tauri command that resolves the managed
agent's effective harness command/model/env exactly like spawn. Channels
are created under a neutral unique 'new-session' placeholder and renamed
asynchronously when the title arrives, so naming failures stay visible
instead of masquerading as generated titles. Mesh inference remains the
fallback when no managed agent completion is available.

AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
…-style

* origin/main:
  fix(acp): per-runtime env defaults at spawn — isolate Hermes from configured MCP startup (block#3420)

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Both the channel and side-chat composers now size to their content —
wrapped lines included, not just hard newlines — growing upward to 40%
of the window before scrolling internally. The top border doubles as a
drag handle (pointer + keyboard) that sets a persisted minimum height.

AGNTOPS-354

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Transcript URLs were blue-underlined spans you couldn't click. They now
render as links — link icon plus the page's og:/twitter:/<title> title
when a new generic fetch_page_title command can fetch it (https-only,
4s timeout, 256KB cap), falling back to the URL without its scheme,
truncated at 64 chars. Clicks open the system browser via the opener
plugin. Trailing sentence punctuation stays in the prose instead of
breaking the href.

AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
…ocus

AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
…t search

AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Pinned chats render in their own section at the top of the channel list;
all other chats sit beneath. Both sections order by last activity, newest
first, with unread dots + bold names driven by the shared useUnreadChannels
state from AppShell.

AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
@joahg joahg changed the title feat(desktop): developer mode display style — prompt-first UI with categorized channel workspace, palette, and split-pane side chats feat(desktop): developer mode display style — prompt-first UI with pinned/recent channel list, palette, and split-pane side chats Jul 29, 2026
joahg and others added 9 commits July 29, 2026 07:11
Message @mentions whose name matches one of the event's p-tag members
(e.g. the @agent prompt prefix) render as a bordered pill in that
author's chat color — visually matching the composer's mode pill.
Multi-word agent names like "amp (local)" are matched longest-first;
unknown mentions keep the generic highlight.

AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
The channel name already renders in the app top bar; the in-transcript
duplicate wasted space and repeated the first prompt's text as topic.

AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
…elected

Selecting a card with ↑ now blurs the message box (readOnly while
inactive) so typing can't land in it; the shell owns ↑/↓/Enter/Escape
through a window listener until the selection clears. Clicking the box
clears the selection and resumes typing. Window-refocus and
click-on-message focus restoration skip the composer during card
selection so they can't silently re-arm it.

AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
⌘N jumps to the fresh composer to start a new channel. ⌘T opens a draft
side chat in the open channel: the pane opens with no thread yet, the
first send posts a root message to the channel exactly like the main
composer, and the pane attaches to that new thread for follow-ups.

AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
The gutter layout (time · name · content on one line) produced ragged
content columns. Every message now renders a header line — author name
in its chat color, timestamp, reaction chips — with the content on its
own line at the left edge, Slack-style.

AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
…-style

* origin/main:
  feat(agent): fix Anthropic prompt caching with Databricks (+ MCP proxy/TLS passthrough) (block#3463)
  Fix mobile attachment and gallery polish (block#3370)

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Typing # in either composer suggests known channels; Tab/Enter inserts
a plain-text #channel-name reference (the same wire format the standard
UI renders as channel links). Known references in the transcript render
as clickable links that open the channel without stealing composer
focus.

AGNTOPS-354

Amp-Thread-ID: https://ampcode.com/threads/T-019fabca-7cdc-777d-9ccd-ed829c1e60f0
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
joahg and others added 25 commits July 30, 2026 12:45
Tab used to cycle the composer target through chat plus every agent,
which gets slow as the agent list grows. Now Tab is a two-way toggle
between plain chat and the last agent the composer targeted, and
⌃Tab / ⌘Tab (+⇧ reverses) cycles through the agents only. From chat,
the agent cycle resumes at the remembered last agent (persisted in
localStorage alongside the last mode) instead of restarting at the top
of the list. ⌘Tab is accepted but macOS reserves it for the app
switcher, so ⌃Tab is the binding that actually lands there.

Selection logic moved out of DevModeShell into a
useComposerModeSelection hook (also keeps the shell under the
file-size ratchet).

Validation: desktop pnpm test (3923 pass), typecheck, biome check +
ratchets, and the dev-mode e2e specs (40 pass) including new coverage
for the toggle, agent-only cycling, and reload persistence.

Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019fb418-369b-7651-9a16-33e36a2c21be
…atest.json)

Amp-Thread-ID: https://ampcode.com/threads/T-019fb4a5-ccf3-75f9-b4d6-d5620bf99f54
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
…-style

* origin/main:
  feat(desktop): improve agent activity header ui (block#3321)
  perf(presence): reduce heartbeat frequency (block#3783)
  Tighten continuation message rows (block#3724)
  Fix video reviews in thread replies (block#3719)
  feat(release): make desktop releases immutable (block#3568)
  Make relay reconnect backoff authoritative (block#3774)
  feat(desktop): add password-protected backups in settings (block#3701)
  fix(desktop): reuse profiles when joining communities (block#2155)

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
…-style

* origin/main: (22 commits)
  feat(desktop): import local Pocket voices (block#3259)
  fix(desktop): open profiles from avatars (block#3751)
  refactor(voice): extract reusable Pocket primitives + Pocket voice settings (relands block#2467 + block#3208) (block#3910)
  docs: add VISION_REMOTE_AGENTS.md (block#3924)
  feat(desktop): auto-enable huddle transcription for agents (block#3180)
  feat(agent): optional reply guard reminds a silent turn to publish (block#3763)
  feat(desktop): upgrade Pocket TTS model (block#3266)
  feat(desktop): delete a message by clearing its edit to empty (block#3813)
  feat(relay): raise hosted community limit to five (block#3829)
  feat(desktop): locally stored NIP-49 encrypted key backup (block#2937)
  fix(catalog): update Amp tagline (block#3806)
  fix(desktop): channel topic and membership metadata cleanup (block#3642)
  fix(desktop): align data deletion labels (block#2230)
  fix(relay): align NIP-11 max_limit with REQ ceiling (block#3635)
  fix(desktop): allow linux-only media items as dead code off-linux (block#3811)
  fix(desktop): report authenticated relay recovery (block#3812)
  fix(desktop): don't gate hover affordances on the hover media query (block#3657)
  feat(relay): gate kind 30178 team-catalog reads behind the shared tag (block#3358)
  test(desktop): click visible thread collapse guide (block#3800)
  feat(desktop): raise the install ceiling and make installs observable (block#3368)
  ...

Amp-Thread-ID: https://ampcode.com/threads/T-019fb8e1-6ece-72a7-8808-9b12e0f7e833
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>

# Conflicts:
#	desktop/src-tauri/src/linux_media.rs
#	desktop/src/app/AppShell.tsx
Cmd+[/] alone no longer switches tabs; the shortcut is now
shift+cmd+[/] to match browser conventions. Matches on event.code so
shifted bracket keys resolve consistently across layouts.

Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019fb8ed-ef8a-733b-8810-5ca398cf89f3
Dev-mode reaction chips now render community/custom emoji images
(NIP-30 emoji tags, with community-palette fallback for CLI reactions),
sit inline on the author/timestamp header line, and are clickable:
clicking a chip toggles your own reaction optimistically, and a hover +
affordance opens the emoji picker. Deleted reactions (kind 5/9005) are
dropped and duplicate deliveries collapse.

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019fb8ed-7474-778a-81ef-60e0407b3938
Co-authored-by: Amp <amp@ampcode.com>
extract_p_tags returns {pubkey, role} objects, so comparing the whole
value against the caller hex string never matched and every
'channels create --parent' call was rejected as a non-member.

Amp-Thread-ID: https://ampcode.com/threads/T-019fb8fe-b2fb-7462-aa34-1ab2ee551e5d
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
ArrowUp-selecting one of your own prompt cards and pressing `e` loads
the message into the composer for editing (kind:40003, the same relay
edit path as the standard UI — no delete-and-recompose needed). Enter
saves; Escape cancels and lands back on the card. Someone else's or a
still-pending prompt is a silent no-op.

Dev mode now also resolves kind:40003 edits when rendering: roots,
inline replies, and the thread panel overlay the latest edit's content
and imeta tags (attachments ride along) and show an "(edited)" marker.
Original attachments and spoiler state are preserved on save, and only
newly added mentions get p tags so a typo fix re-wakes nobody.

Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019fb8ef-ce3c-72fc-9eae-ab89a72c6177
Message-like notifications (DMs, mentions, thread replies) now pick their
sound by who sent the message: humans default to ping, agents to the new
amp sound (derived from the Amp CLI's completion chime, macOS Submarine).
Non-message slots (needs action, job events) keep per-slot sounds.

Also relabels the misleading master 'Sound' switch to 'Event alerts' —
it gates the entire notification (toast + sound), not just audio, which
made it look like sounds were broken when toggled off.

Amp-Thread-ID: https://ampcode.com/threads/T-019fb8fe-48d2-734b-a35a-13b5f10b9982
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Dev mode gains the standard UI's agent status indicator, restyled for
the terminal aesthetic: while agents work in the channel, a quiet
monospace line pinned between the transcript and the composer shows
each working agent's name (chat color) and the newest headline from its
observer transcript — the command being run, file being edited, or
thought in progress. It reads the shared agentWorkingSignal (observer
turn events) and reuses the standard BotActivityBar's spine/meaningful
headline tiers, but renders a single in-place-updating line per agent:
no spinner, no rotating carousel, one fade-in on appearance. Falls back
to "working…" until the first observer frame lands, and clears when
the turn completes.

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019fb909-becd-72e9-abe2-cdbcbda7350e
…play-style

* joah/dev-mode-status-indicators:
  feat(desktop): agent activity status line in dev mode

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Move the card-selection keydown effect into a
useCardSelectionShortcuts hook — DevModeShell crossed the 1000-line
file-size ratchet after the status-line and message-edit features
landed together. Also convert two upstream string concatenations to
template literals; they trip biome's useTemplate lint in the pre-push
check.

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019fb909-becd-72e9-abe2-cdbcbda7350e
The pubkey-truncation guard flags workingPubkeys.slice(0, N) in
DevAgentStatusLine — an array window over a pubkey list, not string
truncation; names render via useMemberNameResolver.

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019fb909-becd-72e9-abe2-cdbcbda7350e
Dev mode is now opted into per session: the display style no longer
persists across launches, so the app always opens in the standard
sidebar layout. The standard top chrome gains a prominent Dev Mode
button (with the ⌘⇧D hint) to switch over; dev mode already surfaces
⌘⇧D in its shortcuts overlay and command palette for the way back.

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019fb92b-b150-70a1-bc16-2cc313923268
…-style

* origin/main:
  docs(nips): add single-coordinate manual-unread override layer and verification model to NIP-RS (block#2864)
  chore(release): release Buzz Desktop version 0.5.3
  fix(release): make immutable desktop release operable (block#3943)

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Restore the localStorage persistence dropped in 17f2387 — only the
first launch (nothing stored) should land in standard mode; after
that the chosen display style sticks across relaunches.

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019fb92b-b150-70a1-bc16-2cc313923268
Agents that were running when the app quit are now restarted on the next
launch even when start_on_app_launch is off. shutdown_managed_agents marks
each agent it stops with a one-shot stopped_by_app_shutdown flag; launch
restore treats flagged agents as candidates and consumes the flag, so an
agent the user explicitly stopped before quitting stays stopped.

Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019fba6f-56dd-73b1-86d8-30f8bc7dd0c1
Co-authored-by: Joah Gerstenberg <me@joahg.com>
Signed-off-by: Joah Gerstenberg <me@joahg.com>
Co-authored-by: Joah Gerstenberg <me@joahg.com>
Signed-off-by: Joah Gerstenberg <me@joahg.com>
Co-authored-by: Joah Gerstenberg <me@joahg.com>
Signed-off-by: Joah Gerstenberg <me@joahg.com>
Co-authored-by: Joah Gerstenberg <me@joahg.com>
Signed-off-by: Joah Gerstenberg <me@joahg.com>
Co-authored-by: Joah Gerstenberg <me@joahg.com>
Signed-off-by: Joah Gerstenberg <me@joahg.com>
Co-authored-by: Joah Gerstenberg <me@joahg.com>
Signed-off-by: Joah Gerstenberg <me@joahg.com>
Buzz-Channel: 6f9e6bb7-7b2b-4361-820b-8e3f7f8941d0
Co-authored-by: Joah Gerstenberg <me@joahg.com>
Signed-off-by: Joah Gerstenberg <me@joahg.com>
Co-authored-by: Joah Gerstenberg <me@joahg.com>
Signed-off-by: Joah Gerstenberg <me@joahg.com>
@joahg
joahg force-pushed the joah/dev-mode-display-style branch from b274ea6 to 682f645 Compare August 1, 2026 03:00
joahg and others added 4 commits August 1, 2026 11:07
Text typed into the dev-mode composer used to follow the user across
channel switches. The shell now stashes the draft per scope (channel id,
or the fresh composer) on exit and restores it on return; a send
consumes the origin scope's draft and a failed send restores its prompt
to the channel it was sent from. Switching away mid-`e`-edit abandons
the edit buffer and keeps the pre-edit draft, matching Escape.
In-memory only: drafts survive display-style toggles, not app restarts.

Reported in [#buzz-dev-mode--per-channel-message-drafts](buzz://message?channel=125f8109-1b00-41cb-a859-4d3bf4412104&id=aaeb6f582c91a763471e638bf39072d4f1ef9d744f9b2a96c5bd034d43cc4db5)

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019fbdef-4c11-755c-9c6c-01545566fcc5
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
…-style

* origin/main:
  fix(relay): allow open relays to set their NIP-11 workspace icon (kind:9033) (block#3998)
  docs: note that addressable channel events scope by d, not h (block#4103)
  docs: fix stale kind count, quick-start numbering, and empty Further Reading (block#2613)
  fix(desktop): keep thread-open affordance in archived channels (block#4012)

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
The stopped_by_app_shutdown field (afcc2ec) grew seven over-limit Rust
files by one line each (types.rs by seven), tripping the desktop file-size
ratchet against main in Desktop Core CI. Reclaim the lines by splitting
packed use groups into rustfmt-stable single-line statements and rewrapping
doc comments to the 100-column budget. No code changes.

Amp-Thread-ID: https://ampcode.com/threads/T-019fbdef-4c11-755c-9c6c-01545566fcc5
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
…-style

* origin/main:
  docs(chart): fix ArgoCD example for native OCI sources (full artifact repoURL + path) (block#3426)
  docs(readme): clarify which release asset to download per platform (block#3481)

Amp-Thread-ID: https://ampcode.com/threads/T-019fbdef-4c11-755c-9c6c-01545566fcc5
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>

# Conflicts:
#	README.md
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