* refactor: simplify focus-visible behavior and remove keyboard nav detection
Remove the useKeyboardNavDetection hook that gated focus rings behind a
data-keyboard-nav attribute. Focus-visible rings are now always available
via the browser's native :focus-visible heuristic, which already
distinguishes keyboard from pointer interaction.
- Delete useKeyboardNavDetection hook and its App.tsx usage
- Make global focus-visible ring unconditional in globals.css
- Switch Dialog/Sheet close buttons from focus: to focus-visible:
- Opt Input/Textarea out of global ring (rely on border change)
- Remove input-group focus ring state (handled by global rule)
* style: enhance focus-visible styles for input components
- Added focus-visible border styles to InputGroup, Input, and Textarea components to improve accessibility and visual feedback.
- Updated class names to ensure consistent focus behavior across all input-related components.
* fix: preserve tool call expand/collapse state across re-renders
When viewing a session, expanded tool calls would collapse whenever a new
message arrived or the streaming message updated. This happened because the
Radix Collapsible was uncontrolled — its open/closed state lived in React
internal state and reset on every re-render.
Changes:
- Lift expand/collapse state into ToolChainCards via a tracked Set<string>
- Pass controlled open/onOpenChange props through ToolCallAdapter to Collapsible
- Wrap MessageBubble in React.memo so non-streaming messages skip re-renders
- Extract ToolChainCards into its own file to stay under file size limit
* chore: remove justfile changes from tool-call fix
* fix: stabilize callback props so MessageBubble memo works
Pass onRetryMessage/onEditMessage as stable references instead of
creating fresh inline closures per render. This ensures React.memo
on MessageBubble actually prevents re-renders for non-streaming
messages when the parent updates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Bradley Axen <baxen@squareup.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: sidebar highlight pill follows active session after list re-sort
When the session list re-sorts (e.g. because updateSession bumps
updatedAt), the active SidebarChatRow moves to a new DOM position but
the highlight pill stays at its old pixel offset. This happens because
the SidebarChatRow effect that re-measures position depends on
[activeRef, isActive] — neither of which changes on a re-sort.
Fix: compute a listOrderKey (joined session IDs) that changes whenever
the list order changes, and include it in the activeSessionRefCallback
and activeProjectRefCallback dependency arrays. This makes the callback
a new reference on re-sort, which triggers SidebarChatRow's existing
effect to re-fire and re-measure the highlight position.
* fix: use MutationObserver for layout-driven highlight remeasurement
Replace the listOrderKey workaround with a MutationObserver in
useSidebarHighlight that watches for any DOM subtree changes in the
nav container. This handles all cases where the active row's pixel
position changes: list re-sorts, project expand/collapse, filtering,
and show-more toggles.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: debounce MutationObserver callback with requestAnimationFrame
Wrap the MutationObserver callback in rAF to measure after the browser
has completed layout, avoiding stale geometry from pre-layout reads.
The trailing rAF also coalesces rapid-fire mutations into a single
measurement, preventing redundant intermediate renders.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract the elapsed time from the tool title string into a separate
`tabular-nums` badge rendered before the chevron. This prevents the
title text and subsequent elements from shifting each second as the
number width changes.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Record the epoch timestamp when a tool call begins executing and pass it
through to the elapsed-time hook, so the timer reflects actual wall-clock
duration instead of resetting when the component re-mounts.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bump the acp-client crate from block/builderbot to pick up the
raw_input forwarding change (builderbot#495). This updates the
MessageWriter trait to accept optional raw_input parameters on
record_tool_call and update_tool_call_title.
Changes:
- src-tauri/Cargo.toml: update acp-client rev to 6cb674a7
- src-tauri/Cargo.lock: updated lockfile
- writer.rs: adapt TauriMessageWriter to new trait signatures
(raw_input ignored for now — no frontend consumer yet)
- dispatcher.rs: forward raw_input from ACP notifications to the
writer, and guard update_tool_call_title on title OR raw_input
being present
User messages were rendered through Streamdown (markdown), which collapses
single newlines per markdown rules. Now user text content renders as plain
text with whitespace-pre-wrap so newlines are preserved as typed.
* fix: treat Option+Return as newline in multiline text inputs
Currently only Shift+Return inserts a newline in multiline text areas
(like the chat input). Option+Return (Alt+Return) was treated the same
as plain Return, triggering the send action.
Add altKey checks alongside the existing shiftKey checks in both:
- ChatInput: skip send when altKey is held
- PromptInputTextarea: early-return (allow newline) when altKey is held
* test: add alt-enter multiline regression coverage
Opening a session triggers acpLoadSession which replays events like
acp:session_info and acp:model_state. Each of these called updateSession
which unconditionally stamped a fresh updatedAt, causing the session to
jump to the top of the sidebar recents list (sorted by updatedAt desc).
Change updateSession to preserve the existing updatedAt unless the caller
explicitly provides one in the patch. Callers that intentionally want to
bump the timestamp (e.g. sendMessage in useChat) already pass updatedAt
explicitly, so their behavior is unchanged.
Update the test to verify both behaviors: updatedAt is preserved when not
in the patch, and updated when explicitly provided.
* fix: highlight project row in sidebar when starting new chat in a project
When starting a new conversation with a project selected, the sidebar
now highlights the project row instead of leaving the previous session
highlighted (or showing no highlight at all).
The root cause: draft sessions are excluded from the sidebar's session
list, so the activeSessionRefCallback never fires for them. The
highlight useEffect would early-return when activeSessionId was set,
leaving a stale highlight.
The fix computes an activeProjectId when the active session is a draft
belonging to a project, and uses an activeProjectRefCallback to position
the sliding highlight on the parent project row.
* Clear sidebar highlight for standalone draft sessions
When starting a new conversation without a project, the sidebar now
shows no highlight instead of keeping the previous selection.
The highlight logic now distinguishes three draft cases:
- Draft in project → highlight the project row (previous commit)
- Standalone draft → clear the highlight entirely (this commit)
- Non-draft session → ref callback positions the highlight as before
Replace the old chat/sessions commands and frontend stores with direct
ACP integration. Sessions are created lazily, loaded via list_sessions
and load_session, and messages stream through the existing ACP event
pipeline.
- Add acp::create_session, list_sessions, load_session Tauri commands
- Simplify chatSessionStore to drive state from ACP responses
- Add ChatLoadingSkeleton for session loading transitions
- Add shared/api/acp.ts frontend bindings
- Remove legacy chat.rs, sessions.rs, catchup.rs, and store.rs
- Support GOOSE_LOCAL_BINARY env var for local dev
- Add tests for session loading and draft persistence
Co-authored-by: morgmart <98432065+morgmart@users.noreply.github.com>
* feat: add single-message queue during agent streaming
Enable typing while the agent is responding. When the user sends a
message during streaming, it is held in a one-slot queue and auto-sent
once the current response completes. The send button contextually
replaces the stop button when text is entered; stop only appears on a
truly empty input.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: persist queued messages across tab switches
Move message queue state from React local state into the Zustand store
keyed by session ID so queued follow-ups survive navigation between
sessions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: persist draft input text across tab switches
Store the ChatInput text field value in the Zustand store keyed by
session so typed-but-unsent text survives navigating between sessions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: persist draft input text to localStorage
Drafts now survive app restarts. Uses the same load-on-init /
persist-on-write pattern as session and project caches. Empty drafts
are pruned from storage automatically.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add useMessageQueue and draft localStorage tests
- useMessageQueue: auto-send on idle, enqueue/dismiss, session scoping,
image forwarding
- chatStore drafts: localStorage persist/clear/cleanup, cross-session
isolation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: lazy session creation with empty draft cleanup
Sessions start as local-only drafts and are promoted to backend
sessions on first message send. Empty drafts (no typed text, no
messages) are cleaned up when navigating away. Drafts with typed text
persist across tab switches and app restarts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review comments
- Pass personaId through when auto-sending queued messages so the
original persona is preserved instead of using the current one
- Persist draft sessions with typed text to localStorage so drafts
survive app restart (previously filtered out, orphaning draft text)
- Skip empty draft cleanup when reusing the active session to avoid
deleting the session we're about to activate
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: drag and drop chats between projects
Chat rows are draggable. Drop on a project header to move a chat into
that project, or drop on the Recents header to unassign from a project.
Visual feedback (highlight + ring) on valid drop targets.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: expand drop zones to full section containers and fix grab cursor
Drop targets now cover the entire project section (header + chat list)
and entire recents section, not just the headers. Grab cursor now
appears on the button element directly to override default pointer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace drop zone indicators with ghost fill on dragged element
Remove ring/highlight from drop targets. Instead, the dragged chat row
gets opacity-40 with a subtle accent fill to indicate it's being moved.
Cleaned up unused dragOver state variables.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add black line drop indicator below project and recents headers
A thin horizontal line appears below the section header when dragging
a chat over a project or recents section, indicating where it will land.
Uses proper dragLeave with contains() check to prevent flicker.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent layout shift from drop indicator and fix grab cursor
Drop indicator line is now absolutely positioned within the section
header so it doesn't push content down. Grab cursor only appears
during active drag (active:cursor-grabbing), not on hover.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove leftover rounded-md from drop target wrapper divs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: always render Recents drop target even when empty
The Recents section header and drop zone now render regardless of
whether standalone chats exist, so users can always drag a project
chat to Recents to unassign it. The chat list inside still only
renders when there are standalone sessions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add session management design spec
Covers three features: Session History view, search-as-you-type,
and smart session naming. Defers import/export, fork, and
multi-window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add session management implementation plan
12-task plan covering smart naming, session history view,
and sidebar search. TDD approach with exact file paths and code.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add userSetName field to session model
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add useSessionAutoTitle hook for AI session naming
Watches for the streaming → idle transition when messageCount === 2 to
auto-generate a session title via the backend LLM. Falls back to the
first 50 characters of the user's message on API failure. Skips sessions
where the user has manually set a title (userSetName).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: wire auto-title hook into ChatView
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add groupSessionsByDate utility
Groups chat sessions into date-labeled buckets (Today, Yesterday, or
formatted date) sorted newest-first within each group. Uses UTC-based
day boundaries for consistent behavior across timezones.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add filterSessions utility for search-as-you-type
Case-insensitive text filter that matches against session title,
resolved persona name, resolved project name, and formatted date.
Accepts resolver functions to decouple from store dependencies.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add SessionCard component
Presentational card component for displaying session metadata (title,
relative date, message count, persona, project with color dot, working
directory, and archived state). Includes 5 passing tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add SessionHistoryView page component
Full-page session history view with date-grouped cards, search
filtering, archived session loading, and empty states. Composes
SessionCard, SearchBar, groupSessionsByDate, and filterSessions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add Session History route and sidebar nav item
Add "session-history" to AppView type, render SessionHistoryView in
AppShell, and add Session History nav item with History icon to
the sidebar navigation. Bump AppShell file-size limit to 575.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add search-as-you-type to sidebar
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent infinite re-render in SessionHistoryView
Zustand selector returning .filter() created a new array reference
on every render, causing React useSyncExternalStore to loop.
Memoize the filtered result instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add card actions and separate archived sessions view
- SessionCard now has dropdown menu with Rename/Archive (or Restore
for archived sessions), inline title editing on rename
- Archived sessions moved behind an "Archived" toggle button instead
of being mixed in with active sessions
- SessionHistoryView wired to AppShell's rename and archive handlers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use consistent ghost variant for archived toggle button
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use normal font weight on archived toggle button
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add ghost-light button variant with normal font weight
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add right padding to session card title to clear menu button
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove spec and plan docs from PR
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: resizable sidebar with snap-to-collapse
Add drag handle between sidebar and main content for free resizing.
Sidebar snaps to collapsed below 100px threshold, min width 180px,
max 380px. Double-click handle resets to default 240px.
Also: bump session title limit from 40 to 100 chars so titles aren't
pre-truncated in storage, position chat row menu button absolutely
to reclaim horizontal space for titles, and hide the sliding highlight
when sidebar is collapsed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: only bump chat updatedAt when messages are sent
Remove unconditional updatedAt bump from updateSession so metadata
changes (provider, persona, model) don't reorder the sidebar list.
Explicitly pass updatedAt in the message-sending paths instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: swap visibility of section header vs project row action buttons
Make New Project and New Chat section header buttons always visible
(muted) so users discover them without hover. Make per-project "+"
buttons hover-only to reduce visual noise.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use opacity instead of visibility for project + button
Replace invisible/visible with opacity-0/opacity-100 so E2E tests
can still interact with the button via getByTitle while keeping the
hover-only visual behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address code review — timestamp divergence and resize cleanup
Backend: only bump updated_at when title changes, not on metadata-only
patches (provider, persona, model), preventing local/server timestamp
divergence that caused sidebar reordering after app restart.
Frontend: add window blur listener as fallback cleanup for sidebar
resize, preventing stuck cursor/userSelect if mouseup fires outside
the window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update sidebar button visibility per feedback
Per morganm's feedback: keep per-project + buttons always visible
since new-chat-in-project is the most common CTA. Switch Recents
new chat icon to IconPlus for consistency. Revert New Project button
to hover-only to reduce confusion with new chat actions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add --radius-overlay (20px) as a semantic token for floating UI
surfaces. Migrate dropdown-menu, context-menu, popover, hover-card,
select, and menubar from rounded-dropdown (10px) to rounded-overlay
for softer, more consistent rounding across all overlay components.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use rounded-card + pill tokens for sidebar dropdown menus
Sidebar context menus (chat row and project row) were using rounded-sm
with inline borderRadius: 0 overrides, bypassing the design system.
Updated to use rounded-card container with p-1 padding and rounded-pill
items to match other dropdown menus in the app.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: use shared DropdownMenu component for sidebar menus
Replace hand-rolled menu implementations with Radix DropdownMenu
component so future design system updates apply automatically.
Add icons to project menu (Edit/Archive) to match chat row menu.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: lazy session creation and draft content persistence
Prevent blank "New Chat" entries from accumulating in the sidebar by
deferring backend session creation until the first message is sent.
Unsent input text is now preserved across navigation for all chats
(new, existing, and home screen) via a debounced draft system backed
by localStorage.
* fix: skip backend call when setting title on a just-promoted draft
The title update fired before ensure_session created the backend
record, causing a swallowed console error. Pass localOnly to
updateSession so the title is only persisted locally until the
backend session exists.
* test: add E2E tests for draft persistence and session lifecycle
Covers home-screen draft survival across navigation, project-scoped
draft reuse, empty-draft cleanup, and sidebar filtering. Extends
the Tauri mock with session and project handlers so draft flows can
run without the real backend.
* fix: shorten ChatView constants to stay under 500-line limit
* fix: sync draft metadata to backend and flush draft on unmount
After ensure_session creates the backend record, push the draft's
local metadata (title, projectId, providerId, personaId) so it
survives a loadSessions refresh.
On ChatInput unmount, flush the pending debounced draft value
instead of dropping it, preventing draft loss on fast navigation.
Scrollbar thumbs are now hidden by default and appear when the user
scrolls, fading out after 1.2s of inactivity. Uses a global scroll/wheel
event listener (useScrollFade hook) that swaps injected stylesheet
content to work around WebKit's limitation of not re-painting scrollbar
pseudo-elements on class changes. Removes hardcoded scrollbar colors in
favor of the muted-foreground semantic token. Adds 4px right padding to
the chat column so the scrollbar doesn't sit flush against the window
edge.
* feat: sidebar UI tweaks
- Fix context menu hover: remove pill rounding on menu items, use
rounded-sm container with overflow-hidden for edge-to-edge hover
- Fix inline rename offset: match Input height to Button (32px),
remove unnecessary wrapper div
- Fix hover lag: set sliding highlight transition to 0ms so it syncs
with the ... button appearance
- Add New Chat button next to Recents header with IconMessagePlus,
wired to createNewTab()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add aria-label and keyboard focusability to new chat button
- Add aria-label="New chat" for screen reader accessibility
- Replace invisible/visible with opacity-only hiding so the button
stays in tab order and is keyboard reachable (focus-visible:opacity-100)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add native zoom support (Cmd+/- and Cmd+0)
Adds keyboard-driven zoom using Tauri's webview setZoom API.
Zoom level persists across restarts via localStorage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add max boundary clamp test for useZoom
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- In check mode, exit code 2 signals 'not ready' so just dev/dev-debug
can block with a clear message instead of silently continuing without
a goose binary.
- Replace pull --ff-only with reset --hard in the managed build checkout
so force-pushed (rebased/amended) remote branches don't break setup.
The ProjectOption type used a singular workingDir that only captured the
first directory. Switch to workingDirs array so every directory added in
the project dialog is shown in the context panel, toolbar tooltip, and
project selector dropdown.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add getAllSessionArtifacts to ArtifactPolicyContext
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: use template literal in shortenPath
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add resolvedDirectoryPath and share artifacts index
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add FilesList component with file icons and search filter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: wire FilesList into context panel files tab
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add FilesList tests and update ContextPanel tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use biome-ignore for directory path click handler inside button
* fix: add overflow-hidden to file rows to prevent text overflow
* fix: recognize edit/update/modify tools as write-oriented for artifact extraction
* fix: filter out non-existent files from context panel files list
* fix: add width constraints to prevent file list overflow
* fix: add min-w-0 to button element to allow text truncation
Button elements have an intrinsic min-width of min-content in browsers,
preventing flex children from shrinking below content width. Adding
min-w-0 overrides this default and allows truncate to work properly.
* refactor: match skills card styling, remove version badges, use SearchBar component
* feat: add file type icons for spreadsheets, images, and more code extensions
* fix: memoize artifacts to prevent render loop, restore .hermit/ gitignore
* fix: add pathExists to ContextPanel test mock
* revert: reduce lucide icon imports to original 5 to lower memory pressure
* fix: increase Node heap limit for unit tests to prevent OOM in CI
* feat: restore expanded file type icons (spreadsheet, image, 40+ extensions)
* fix: prevent infinite rerender loop in FilesList empty-state effect
setExistingPaths(new Set()) created a new object each render,
triggering state update → rerender → effect → state update loop.
Use functional update to check if already empty before setting.
* revert: remove NODE_OPTIONS heap increase, root cause was rerender loop
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Polish chat input and sidebar interactions.
Standardize keyboard focus behavior and tighten sidebar and composer controls so the compact UI feels more consistent while preserving drag-and-drop image workflows.
* Fix lint follow-ups for UI polish.
Extract the chat image drop-target logic and tighten the shared focus selector so CI passes without changing the polished behavior.
* Fix keyboard focus detection for non-Tab navigation.
Preserve hidden pointer focus rings while restoring visible focus for keyboard shortcuts and other keyboard-driven flows that move focus without Tab.
- Default context panel to closed; persist open/close state per session
in chatSessionStore (in-memory, not localStorage)
- Redesign panel sections as widget cards with bordered containers and
filled headers, replacing the accordion pattern
- Show worktree list directly in workspace widget instead of count text
- Migrate icons from lucide-react to @tabler/icons-react
- Clean up placeholder section copy
* fix: refresh sidebar when project is edited from Projects page
Route loadProjects() through the Zustand projectStore instead of
calling listProjects() directly so mutations update the sidebar.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prevent project edit dialog from resetting form fields mid-typing
The useEffect that populated form fields ran on every editingProject prop
change. Since AppShell recreated this object on every render, any parent
re-render would reset all form fields, losing user input.
- Track dialog open transition with prevOpenRef to only populate on open
- Track editingProject.id with prevEditingIdRef for project identity changes
- Memoize editingProject prop in AppShell to stabilize object reference
- Add 12 tests covering form state preservation behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use stable Zustand selector to prevent fetch loop in ProjectsView
Address PR review feedback: useProjectStore() subscribed to the full
store, causing loadProjects to be recreated on every state change and
triggering repeated IPC calls. Select fetchProjects directly for a
stable reference.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* UI polish: provider icons, dynamic placeholder, sidebar refinements
- Add provider icons (Goose, Claude Code, Codex) to the provider
selector dropdown and trigger button
- Make chat input placeholder dynamic based on selected provider
("Message Claude Code, @ to mention personas")
- Lighten placeholder text color and font weight
- Update toolbar button variant to use text-foreground
- Fix dropdown icon alignment for single-line vs multi-line items
- Hide sidebar scrollbar while preserving scroll functionality
- Shrink sidebar collapse toggle button from 32px to 24px
- Add right padding to chat row for ellipsis menu breathing room
- Extract formatProviderLabel as shared utility
* fix code review issues: lint, dead code, theme token, tests
- Remove unused Plus import and onNewChat prop from Sidebar
- Fix Biome formatting in ChatInputToolbar, ProviderIcons, HomeScreen
- Fix invalid text-foreground-muted token to text-muted-foreground
- Remove dead onCreateProjectFromFolder prop chain across all components
- Fix CodexIcon hardcoded white fill to transparent
- Update tests for renamed labels (provider, project, placeholder)
- Remove extra blank line in Sidebar
* fix e2e smoke test for updated placeholder text
Update Playwright regex to match new placeholder format
"Message {name}, @ to mention personas".
* fix keyboard accessibility for show-on-hover sidebar buttons
Add group-focus-within:visible and group-focus-within:opacity-100
so the "New project" and item menu buttons are reachable via tab
navigation, not just mouse hover.
* fix: sidebar project/recent items hover text and styling
- Style project items to match recent chat row appearance (padding, gap, font-light)
- Fix hover text color on project and recent items by adding relative z-10
to the projects section so it renders above the sliding highlight overlay
- Use group-hover for consistent text color change across the full row
- Switch sidebar settings to top bar, remove overflow-hidden on main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve biome lint errors
Replace `any` with `unknown` in debounce, remove unused biome-ignore
comments, fix exhaustive-deps warning, and suppress static element
interaction lint for hover wrapper div.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract useSidebarHighlight hook to stay under file size limit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(chat): add context panel toggle to ChatView
- Introduced a context panel in ChatView that can be opened or closed via a button, enhancing the user interface by allowing users to manage context visibility.
- Updated the layout to accommodate the context panel, ensuring a responsive design that maintains usability.
- Added imports for new components and adjusted state management to handle the context panel's open/close state.
This change improves the overall chat experience by providing users with more control over the interface elements they wish to view.
* feat(chat): integrate @tabler/icons-react for context panel toggle
- Added @tabler/icons-react to the project dependencies to enhance icon usage.
- Replaced existing icon imports in ChatView with Tabler icons for better visual consistency.
- Updated the context panel toggle button to utilize the new icons, improving the user interface.
- Refactored the ContextPanel component to implement tabs for better organization of content.
This update enhances the chat interface by providing a more modern and visually appealing icon set, while also improving the overall user experience with better content organization.
* chore: transition from lucide-react to @tabler/icons-react for icon usage
- Updated AGENTS.md to reflect the migration from `lucide-react` to `@tabler/icons-react` for icon components.
- Adjusted ChatView to maintain existing `lucide-react` usage until full migration is complete.
- Enhanced documentation to guide future icon implementation.
This change ensures a consistent iconography across the project while preparing for a complete transition to the new icon library.
* refactor(git): streamline git state retrieval and improve code readability
- Consolidated the retrieval of the current repository root and branch into a more concise format.
- Enhanced the readability of the code by restructuring the way worktree paths and branches are processed.
- Improved the handling of dirty file counts by maintaining consistent function calls.
These changes optimize the git state retrieval process and enhance code clarity, making future maintenance easier.
The Tailwind v4 upgrade (#85) replaced dialog components with Radix UI
primitives that use aria-labelledby instead of aria-label. Update E2E
tests to check DialogTitle h2 text instead of missing aria-label attrs,
and use getByRole("button", { name: "Close" }) for the X close button.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Add descending updatedAt sort for project chat lists in sidebar so new chats appear at the top instead of the bottom
- Increase MAX_VISIBLE_CHATS from 3 to 5 so more chats are visible before the 'Show more' button
* feat(chat): enhance image handling in chat input and views
- Added image resizing functionality to optimize pasted images before sending.
- Updated ChatInput to utilize the new resizing feature when adding images.
- Modified ChatView and AppShell to support initial image state and handle images in message sending.
- Adjusted HomeScreen to pass images along with messages during chat initiation.
This improves the user experience by ensuring images are properly processed and displayed in the chat interface.
* refactor(chat): centralize PastedImage type definition
- Moved the PastedImage type definition from ChatInput to a shared types file for better code organization and reusability.
- Updated imports in ChatInput, ChatView, HomeScreen, and AppShell to reference the new location of PastedImage.
This change enhances maintainability by ensuring a single source of truth for the PastedImage type across the application.
* refactor(chat): extract resizeImage to lib and fix formatting
* fix(chat): enhance initial message handling in ChatView
- Updated the condition for sending the initial message to include checks for both initialMessage and initialImages, ensuring that images are also sent when available.
- This change improves the functionality of the chat interface by allowing for a more comprehensive initial message setup.
* fix(chat): fix formatting and type error in initial message handler
* feat: import boss-ui components + upgrade to Tailwind v4
- Upgrade Tailwind CSS v3 → v4 with @tailwindcss/postcss, tw-animate-css
- Replace tailwind.config.js with CSS @theme blocks in globals.css
- Import boss-ui token system (semantic backgrounds, borders, text, shadows,
shapes, typography scale, animations, layout tokens)
- Import 49 shared UI components from squareup/boss-ui
- Import 48 AI element components from squareup/boss-ui
- Import 4 hooks from squareup/boss-ui
- Install all required dependencies (Radix UI primitives, UI libs, content
libs, form/utility libs)
- Migrate all 35+ feature files from custom tokens to standard shadcn/boss-ui
tokens (bg-background, text-foreground, bg-accent, text-muted-foreground,
border-border, etc.)
- Align to boss-ui visual language: flat border-driven design, no elevation
on sidebars/inputs, shadow-popover for menus, shadow-card for dialogs
- Fix low-contrast text by removing opacity modifiers on text-muted-foreground
- Update biome.json, tsconfig.json, check-file-sizes.mjs for imported code
- Re-add custom Button variants (toolbar, xs, icon-sm, icon-lg)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: swap vanilla HTML to boss-ui components
- Replace hand-rolled dialogs with Dialog/DialogContent in
CreateSkillDialog, CreateProjectDialog, PersonaEditor
- Replace delete confirmations with AlertDialog in AgentsView,
ProjectsView, SkillsView, SettingsModal, DoctorCheckRow
- Replace raw <input> with Input component (6 instances)
- Replace raw <textarea> with Textarea component (3 instances)
- Replace raw <select> with Select/SelectTrigger/SelectContent/
SelectItem (5 instances)
- Replace raw <label> with Label component throughout
- Replace <input type="checkbox"> with Checkbox component
- Replace div border-t dividers with Separator in settings
- Fix Radix SelectItem empty-string value crash by using sentinel
- Set sidebar to bg-background
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use default Button variant for send and remove focus ring on textarea
Use the boss-ui Button default variant (bg-primary) for the send button
instead of custom bg-foreground overrides. Suppress the global
focus-visible ring on the chat textarea to prevent the black outline.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add Cash Sans Wide display font to headlines
- Download Cash Sans Wide font files (5 weights) from boss-ui
- Add @font-face declarations in globals.css
- Switch page titles, settings headings, home clock/greeting, and
chat empty state to font-display (Cash Sans Wide) with tracking-tight
- Use Badge component with secondary variant for PersonaCard pills
- Remove redundant "Personas" section subtitle
- Upgrade "Active Agents" heading to display font
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: swap remaining vanilla HTML to boss-ui components
Replace ~56 raw <button> elements across 20 files with the Button
component, hand-rolled toggle groups with ToggleGroup, img/div avatar
patterns with Avatar, animate-pulse skeletons with Skeleton, and a
raw <input> with Input.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move ai-elements into shared/ui directory
Relocate src/components/ai-elements/ to src/shared/ui/ai-elements/
to colocate with the rest of the boss-ui component library. No import
updates needed as these components are not yet consumed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: remove mono font from skill names
Use default sans-serif for skill names in the list and create dialog
to match persona card styling. Keep mono only for instructions textarea.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: swap chat sub-components to boss-ui ai-elements
Replace hand-rolled ToolCallCard, ThinkingBlock, and MarkdownContent
with the ai-elements Tool, Reasoning, and MessageResponse compounds.
Action buttons now use MessageAction with built-in tooltip support.
Add @source for streamdown so Tailwind generates its utility classes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent whole chat pane from scrolling
Add overflow-hidden to main content area so the flex layout chain
properly constrains scrolling to the MessageTimeline, keeping the
chat input and status bar pinned at the bottom.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent document-level scrolling in chat view
Two issues caused the entire page to scroll when viewing chat:
1. scrollIntoView() in MessageTimeline scrolls all ancestors including
the document. Replaced with container.scrollTo() which only scrolls
within the message timeline container.
2. Tailwind v4 preflight doesn't set height/overflow on html/body like
v3 did. Added height: 100% + overflow: hidden on html, body, #root
outside @layer base so the cascade can't override them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent tool call results from overflowing chat viewport
Add min-w-0 and w-full to message content containers so flex items
respect the max-w-[85%] boundary. Wrap tool output pre blocks with
whitespace-pre-wrap so result text flows within the code block.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use accent color for text selection highlight
The previous selection background (#1a1a1a) was nearly invisible against
the dark input background. Now uses the user's accent color at 60% opacity.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: minimize tool call UI — remove icon, borders, and completed badge
Strip visual noise from collapsed tool calls: remove wrench icon
(via optional showIcon prop), remove border/rounded container, replace
status pill with plain text, hide "Completed" status entirely, and
use inline chevron caret next to the tool name.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: center sidebar icons in collapsed state
Add gap-0 to collapsed button styles to prevent hidden text spans
(w-0 opacity-0) from creating flex gaps that shift icons off-center.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: align button, input-group, and toggle to pill shape
Restore rounded-full on button base and remove redundant rounded-md
from size variants. Update input-group container to rounded-full and
toggle to rounded-full to match the pill design language.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: override browser text-align:center default on buttons
Browsers default <button> to text-align:center. This causes short text
inside flex-1 spans to appear centered even when justify-start is set,
since justify-start controls flex child positioning but text-align
controls text within those children.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace main's color tokens with our theme tokens
The merge from main brought in color tokens (foreground-tertiary,
foreground-secondary, background-primary, background-tertiary, etc.)
that don't exist in our theme. Map them to our equivalents:
- foreground-tertiary/secondary → muted-foreground
- foreground-danger → destructive
- background-primary → background
- background-tertiary → accent
- background-danger → destructive
- border-border-secondary → border-border
Fixes invisible send button arrow (text-background-primary → text-background).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add compat color scales and register brand/danger in theme
The Tailwind v4 theme resets all colors with --color-*: initial then
only declares a minimal palette. This silently broke ~120 class usages
across ai-elements that rely on standard Tailwind scales (red-400,
green-600, zinc-800, etc.).
Add compat color stops for the specific scales actually used:
- red-{50,400,500,600,700,900}
- green-{400,500,600,700,900}
- blue-{400,500,600,700,900}
- yellow-{400,500,600,700,900}
- orange-{100,400,600,700,900}
- zinc-{100,400,800,950}
- amber-500
Also register brand and danger in @theme inline so Tailwind can
generate text-brand, bg-brand, text-danger, etc. utilities.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review — SidebarRail type + artifact link policy
1. Add type="button" to SidebarRail button to prevent accidental form
submissions per AGENTS.md requirement.
2. Restore artifact policy enforcement for markdown links in chat.
The switch to MessageResponse/Streamdown lost the link interception
that enforced allowed roots. Extract useArtifactLinkHandler hook
that intercepts local link clicks via event delegation and routes
them through useArtifactPolicyContext.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore artifact open-file actions in tool call cards
The switch from ToolCallCard to ToolCallAdapter dropped the artifact
policy integration that showed "Open file" buttons on tool results.
Add ArtifactActions component that uses resolveToolCardDisplay from
ArtifactPolicyContext to render primary and secondary open actions
with path safety enforcement.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add confidence labels, blocked reasons, and artifact policy tests
ArtifactActions was missing "detected" labels for low-confidence
candidates and per-candidate blockedReason text. This made the
feature appear broken even though the plumbing was correct. Also
adds test coverage for ToolCallAdapter artifact integration and
useArtifactLinkHandler to prevent future regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(chat): enhance chat input components with project color and icon support
- Added optional `color` property to `ProjectOption` interface for project color representation.
- Updated `ChatInputSelectorItem` to include an optional `icon` property for enhanced item display.
- Introduced `ProjectDot` component in `ChatInputToolbar` to visually represent project colors.
- Modified `ChatView` and `HomeScreen` to utilize the new `color` property for projects.
- Improved button components to support left and right icons for better UI flexibility.
* fix: apply biome formatting and guard asChild against icon props
* fix: add new project includes to the bottom instead of the top
Previously, `buildEditorText` placed `include:` lines at the top of
the editor text, above the prompt. This meant clicking "Add directory"
would insert the new include above any text the user had already typed.
Now includes are placed at the bottom of the editor as a trailing block,
and `parseEditorText` collects them from the bottom instead of the top.
This feels more natural — the user's prompt text stays at the top and
directory includes accumulate below it.
No migration needed: workingDirs and prompt are stored separately in
project.json, and `buildEditorText` reconstructs the editor text in
the new layout when the dialog opens.
* fix: parse project includes anywhere in prompt editor
Without an explicit [[bin]] section, Cargo uses the package name 'goose2'
as the binary name. macOS uses the process/binary name for the Dock and
App Switcher labels in dev mode, so the app appeared as 'goose2' instead
of 'Goose'.
* refactor: remove top-level tabs
Replace tab-based navigation with a single active session flow in the app shell and sidebar. Remove the persisted ui_state save/load path from the frontend and Tauri backend, keeping only the unrelated home-directory command in a dedicated system command module. Update affected tests and fixtures to match the new session-only model.
* feat: restore top bar home button
* feat: add image paste support to chat input
Users can now paste screenshots and images directly into the chat input with Cmd+V. Pasted images show as thumbnails with a click-to-expand lightbox and a hover-reveal remove button. Images are forwarded to the ACP agent as base64 content blocks alongside the text prompt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: update ChatInput tests for new images argument
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: scope object URL cleanup to unmount only
Revoke only runs on component unmount (via a ref to latest images),
not on every add/remove. Previously the effect's cleanup re-ran on
every images state change, revoking URLs that were still in use.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: render image content blocks in message timeline
Add 'image' case to renderContentBlock in MessageBubble so pasted
images appear in the chat history after sending. Base64 sources are
rendered as data: URLs; URL sources rendered directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: handle image-only sends and clean up debug logs
- Pass a single space as prompt when images are present but text is empty,
preventing goose from rejecting empty-text ACP content blocks
- Make images in the message timeline clickable via lightbox (ClickableImage)
- Suppress Radix Dialog aria-describedby warning in ImageLightbox
- Remove temporary debug console.logs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add drag-and-drop image support to chat input
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: extract ClickableImage to fix file-size lint limit
MessageBubble.tsx was 516 lines (limit 500). Moves ClickableImage
into its own file to bring it under the limit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Add select-none to the tab container className, consistent with
how other interactive elements like dropdown menus are styled.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When a user sends a message from a "New Chat" tab, the session title
now updates instantly from the message text instead of waiting for the
full AI response to complete (acp:done) or the backend title generation
(acp:session_info). This gives immediate feedback in the sidebar and
tab bar.
The backend-generated title from acp:session_info will still overwrite
this initial title if it arrives later, providing the best of both
worlds: instant feedback + quality titles.