Skip to content
This repository was archived by the owner on Apr 14, 2026. It is now read-only.

feat: persona picker with @ mention support#24

Merged
wesbillman merged 8 commits intomainfrom
feat/persona-picker
Mar 31, 2026
Merged

feat: persona picker with @ mention support#24
wesbillman merged 8 commits intomainfrom
feat/persona-picker

Conversation

@wesbillman
Copy link
Copy Markdown
Collaborator

Summary

  • Persona-first chat experience: Replace the bare provider dropdown with a persona picker. Users select a persona (Goose, Scout, Reviewer, Architect, or custom) which sets the system prompt, display name, and default provider for the chat.
  • @ mention autocomplete: Type @ in the chat input to switch personas mid-conversation. Floating autocomplete with keyboard navigation (arrow keys, Enter, Escape).
  • Persona persistence: Selected persona is saved per-session to ui_state.json and restored across tab switches and app restarts.
  • System prompt injection: Persona system prompts flow through to the ACP backend and are prepended to user messages.
  • Bug fixes from audit: Error state recovery, stop button cancels backend, @ mention race condition fix, deleted persona fallback, eager persona loading at startup.

Changes

New components:

  • PersonaPicker — dropdown persona selector used in HomeScreen and ChatInput
  • MentionAutocomplete — floating @ mention popup with useMentionDetection hook
  • ChatInputToolbar — extracted picker dropdowns from ChatInput

Modified:

  • ChatView — persona tracking, system prompt passing, deferred send on persona switch
  • ChatInput — persona indicator, @ mention integration
  • HomeScreen — persona picker replaces provider dropdown
  • AppShell — persona ID in tab/session flow, eager persona loading
  • useChat — system prompt parameter, error recovery, backend cancellation
  • chatSessionStore — persona persistence in session metadata
  • Backend: acp_send_message + ui_state support for system prompts and persona mapping

16 files changed, ~1300 additions, ~400 deletions.

Test plan

  • All 158 tests pass
  • TypeScript, biome lint, cargo clippy, cargo fmt all clean
  • Vite build succeeds
  • Manual: select persona on home screen, verify system prompt affects responses
  • Manual: switch tabs, verify persona persists
  • Manual: restart app, verify persona restores
  • Manual: type @ in chat, verify autocomplete and persona switch
  • Manual: delete a custom persona, verify fallback to Goose

Generated with Claude Code

wesbillman and others added 8 commits March 31, 2026 04:48
Replace the provider-first model with a persona-first interaction pattern.
Users now pick a Persona (with its own system prompt, default provider, and
model) from a dropdown in both the home screen and chat input. Typing @ in
the chat textarea opens an autocomplete to mention any persona inline,
switching context for that message. Provider selection is still available as
a secondary override.

New components:
- PersonaPicker: dropdown grouped by built-in / custom personas
- MentionAutocomplete: floating autocomplete triggered by @ in textarea
- ChatInputToolbar: extracted bottom bar to keep ChatInput under 500 lines
- useMentionDetection: hook for detecting @ mention state in text input

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ion UX

- Pass persona system_prompt from frontend through Tauri to AcpService
- Prepend persona instructions to user prompt in <persona-instructions> tags
- Add keyboard navigation (arrow keys, Enter/Tab) to @ mention autocomplete
- Fix PersonaPicker description extraction (was using index [1] instead of [0])
- Simplify ChatView handleSend by letting useChat handle system prompt injection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…otifications, and per-message persona tracking

Adds @ mention autocomplete to the HomeScreen input so users can invoke personas
before starting a chat. Shows system notifications when switching personas mid-chat,
and tracks which persona generated each assistant message in the timeline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store the selected persona per session in ui_state.json alongside
the existing tab state. When switching tabs or reopening the app,
the previously selected persona is restored for each chat session.

Changes:
- Backend: add persona_per_session HashMap to UiState with
  serde(default) for backward compatibility
- chatSessionStore: add personaId to ChatSession, save/restore
  persona mapping in persistTabState/loadTabState
- ChatView: save persona changes to session store
- AppShell: pass session's stored personaId to ChatView, store
  personaId when creating new sessions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Error state stuck: acpSendMessage errors now reset chatState to
   "idle" so the user can continue sending messages after failures.

2. Stop button cancels backend: stopGeneration now calls
   acp_cancel_session to terminate the Rust ACP session, preventing
   orphaned streaming events after the UI resets.

3. @ mention persona race condition: when switching personas via @
   mention, the message send is deferred to the next render cycle
   so useChat picks up the new persona's system prompt instead of
   the stale one.

4. Deleted persona fallback: if a persisted personaId references a
   deleted persona, ChatView now falls back to builtin-goose (or
   the first available persona) and updates the session store.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Personas were only loaded when visiting the Personas page (via the
usePersonas hook in AgentsView). This meant the persona picker in
HomeScreen and ChatInput showed empty until the user navigated to
the Personas page at least once.

Now AppShell loads personas from the backend in its startup effect,
alongside sessions and tab state. This ensures personas are
available immediately in all views.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update ChatInput tests for new toolbar structure and persona props
- Update HomeScreen tests to mock persona store instead of ACP providers
- Add #[allow(clippy::too_many_arguments)] on send_prompt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@wesbillman wesbillman force-pushed the feat/persona-picker branch from 7a230d0 to e48dfc6 Compare March 31, 2026 15:39
@wesbillman wesbillman merged commit 5e189c4 into main Mar 31, 2026
7 checks passed
@wesbillman wesbillman deleted the feat/persona-picker branch March 31, 2026 17:30
tellaho added a commit that referenced this pull request Apr 8, 2026
…onts' model

Upstream block/ghost PRs #23 and #24 removed all HK Grotesk @font-face
declarations and switched to a 'consumers bring their own fonts' model.
Align our branch with this change:

- Replace --font-sans and --font-display with system font stack
- Remove all 7 @font-face declarations (300–900 weights)
- Delete 7 HKGrotesk-*.woff2 font files from src/assets/fonts/

The system font stack (system-ui, -apple-system, BlinkMacSystemFont,
Segoe UI, Roboto, sans-serif) matches upstream Ghost's new defaults.

318/318 tests passing. Biome clean.
tellaho added a commit that referenced this pull request Apr 8, 2026
Remove migration-specific context from globals.css comments that
wouldn't make sense to future consumers of the design system:

- Remove '(upstream Ghost PRs #23/#24)' from font comment
- Replace redundant font narration with '@font-face — add your own here'
- Remove '(from dsgn-playground)' from gray scale comment

Comments should help the next person, not document our migration.
tellaho added a commit that referenced this pull request Apr 8, 2026
…onts' model

Upstream block/ghost PRs #23 and #24 removed all HK Grotesk @font-face
declarations and switched to a 'consumers bring their own fonts' model.
Align our branch with this change:

- Replace --font-sans and --font-display with system font stack
- Remove all 7 @font-face declarations (300–900 weights)
- Delete 7 HKGrotesk-*.woff2 font files from src/assets/fonts/

The system font stack (system-ui, -apple-system, BlinkMacSystemFont,
Segoe UI, Roboto, sans-serif) matches upstream Ghost's new defaults.

318/318 tests passing. Biome clean.
tellaho added a commit that referenced this pull request Apr 8, 2026
Remove migration-specific context from globals.css comments that
wouldn't make sense to future consumers of the design system:

- Remove '(upstream Ghost PRs #23/#24)' from font comment
- Replace redundant font narration with '@font-face — add your own here'
- Remove '(from dsgn-playground)' from gray scale comment

Comments should help the next person, not document our migration.
tellaho added a commit that referenced this pull request Apr 9, 2026
* feat: migrate from boss-ui to Ghost UI foundation

Swap the design system foundation from boss-ui to Ghost UI, the
open-source successor design language from block/ghost.

Font swap:
- Remove Cash Sans (CDN) and Cash Sans Wide (local) font faces
- Add HK Grotesk (local woff2, 7 weights: 300–900)
- Update --font-sans, --font-display to "HK Grotesk"
- Update --font-mono to "Geist Mono"

CSS tokens (globals.css):
- Adopt Ghost's main.css as the token source of truth
- Add hero-blur-in, fadeToFull, fadeToSubtle keyframes from Ghost
- Add scrollbar hover styles from Ghost
- Preserve all goose2-specific tokens: --color-brand, --density-spacing,
  --text-subtle, --radius-overlay, compat color scales for ai-elements
- Preserve goose2 custom utilities: density spacing, page transitions,
  content-fade-in, prefers-reduced-motion, keyboard-nav focus, app shell lock

UI primitives (47 of 49 files):
- Copy Ghost UI components with import path adjustment
  (@/lib/utils → @/shared/lib/cn) and "use client" removal
- Preserve goose2's button.tsx (leftIcon/rightIcon, forwardRef,
  ghost-light/toolbar variants, xs/icon-lg sizes, compound variants)
- Preserve goose2's tabs.tsx (CVA variants: default, buttons)
- Restore dialog.tsx showCloseButton prop (used by ImageLightbox)
- Fix sonner.tsx useTheme import to @/shared/theme/ThemeProvider

Not included (PR 2):
- ai-elements migration (48 files, separate scope)

* fix: add type="button" to SidebarRail per AGENTS.md

Ghost upstream omits type="button" on the SidebarRail <button>.
Goose2 requires it on all button elements to prevent accidental
form submission (AGENTS.md coding conventions).

PR review feedback from Marge.

* fix: restore 20px radius on popup surfaces (dropdown → overlay parity)

* fix: remove HK Grotesk font — align with upstream Ghost 'no bundled fonts' model

Upstream block/ghost PRs #23 and #24 removed all HK Grotesk @font-face
declarations and switched to a 'consumers bring their own fonts' model.
Align our branch with this change:

- Replace --font-sans and --font-display with system font stack
- Remove all 7 @font-face declarations (300–900 weights)
- Delete 7 HKGrotesk-*.woff2 font files from src/assets/fonts/

The system font stack (system-ui, -apple-system, BlinkMacSystemFont,
Segoe UI, Roboto, sans-serif) matches upstream Ghost's new defaults.

318/318 tests passing. Biome clean.

* fix: restore rounded-overlay class on overlay surfaces

The migration commit incorrectly replaced rounded-overlay with
rounded-dropdown on all popup/overlay components. Both tokens resolve
to 20px currently, but rounded-overlay is the correct semantic token
for overlay surfaces (popovers, hover cards, context menus, selects,
dropdown menus, menubars).

Restores 9 occurrences across 6 shared UI components.

* fix: strip internal references from CSS comments

Remove migration-specific context from globals.css comments that
wouldn't make sense to future consumers of the design system:

- Remove '(upstream Ghost PRs #23/#24)' from font comment
- Replace redundant font narration with '@font-face — add your own here'
- Remove '(from dsgn-playground)' from gray scale comment

Comments should help the next person, not document our migration.

* feat: point components.json at ghost-ui registry + add ghost.config.ts

Step 2: Update components.json to consume from ghost-ui registry:
- style: new-york → ghost
- baseColor: zinc → neutral
- Add registryUrl pointing to block.github.io/ghost/registry.json
- Add iconLibrary: lucide

Future `npx shadcn add <component>` commands will now pull
ghost-styled components from the upstream registry.

Step 3: Add ghost.config.ts for drift detection:
- Points at ghost-ui registry for goose2's shared UI components
- Enables value + structure scanning
- Configures rules: hardcoded-color (error), token-override (warn),
  missing-token (warn), structural-divergence (error)

Run `ghost scan` to detect drift from the parent design system.

* fix: use raw GitHub URL for ghost-ui registry

The GitHub Pages deploy doesn't include registry.json in dist/ —
it only serves the Vite SPA. The raw.githubusercontent.com URL
points directly at the source file and resolves correctly.

Long-term: ghost repo should copy registry.json to public/ so
it's served at block.github.io/ghost/registry.json.

* chore: add TODO to switch registry URL once block/ghost#25 lands

The raw.githubusercontent.com URL is a workaround for registry.json
not being served on GitHub Pages. PR block/ghost#25 fixes the deploy
workflow. Once merged and deployed, both ghost.config.ts and
components.json should switch to:
  https://block.github.io/ghost/registry.json

* fix: use canonical Pages URL for ghost-ui registry

The registry was already being served at /ghost/r/registry.json via
shadcn build output in public/r/. Switch both components.json and
ghost.config.ts from the raw.githubusercontent.com workaround to the
proper GitHub Pages URL.

Removes the TODO tracking block/ghost#25 — that PR is still useful
for serving at the root path, but no longer blocking us.

* chore: revert formatting-only changes (import/export reordering)

Reverts Biome auto-sort import reordering and export alphabetizing
across 39 shared/ui component files. These were formatting-only changes
that added noise to the ghost-ui migration PR without any functional
impact.

9 files fully reverted (pure formatting noise):
- collapsible, hover-card, popover, tooltip, table, label,
  separator, progress, avatar

30 files surgically cleaned (import/export order restored,
real ghost-ui class changes preserved):
- accordion, alert, alert-dialog, badge, breadcrumb, button-group,
  calendar, carousel, chart, checkbox, command, context-menu, dialog,
  dropdown-menu, form, input-group, input-otp, menubar, navigation-menu,
  pagination, radio-group, resizable, scroll-area, select, sheet,
  sidebar, slider, switch, toggle, toggle-group

* chore: revert remaining export reordering (round 2)

Missed 13 more files with alphabetized exports in the first pass.
Restored original export order in: alert-dialog, card, carousel,
command, context-menu, drawer, dropdown-menu, form, input-group,
menubar, navigation-menu, pagination, sheet.

card.tsx and drawer.tsx are now fully clean (zero diff vs main).

* fix: restore rounded-full on InputGroup container

Reverts ghost-ui registry's rounded-md back to rounded-full per design
direction. The InputGroup container uses a fixed h-9 height, so pill
radius renders correctly.

Also restores rounded-[calc(var(--radius)-5px)] on the sm button variant
for proper nested radius calculation.

* chore: simplify ghost.config.ts — remove speculative scan/rules

The scan and rules blocks were added speculatively without explicit
design decisions. Strip down to just the registry pointer until we
decide on lint rules.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant