Skip to content

v0.0.5: native frost, browser polish, session persistence, and ESLint fixes - #69

Merged
fazxes merged 71 commits into
mainfrom
fix/0.0.5
Mar 2, 2026
Merged

fazxes merged 71 commits into
mainfrom
fix/0.0.5

Conversation

@fazxes

@fazxes fazxes commented Feb 24, 2026

Copy link
Copy Markdown
Member

Summary

  • Native frost layer: macOS vibrancy with CIScreenBlendMode tint overlay, tuned for light/dark mode transparency
  • Browser panel polish: native corner radius on WebKit view, element selection/context chips, toolbar improvements
  • Session persistence: AI session renames persist to disk across app restarts; sidecar killed on app exit
  • UI layout: status bar moved to activity panel, chat input padding, branch switching via sidebar worktree badge
  • Agent-bridge ESLint: resolved 46 no-unsafe-* false positives with local SDK mirror types (LocalSDKMessage) instead of rule suppression; fixed runtime crash from string | unknown[] user message content

Changes (91 files, +3200 / -1019)

Native / Rust

  • Frost vibrancy with NSVisualEffectView + screen blend tint overlay
  • Browser WKWebView corner radius and element selection handlers
  • Sidecar lifecycle: kill agent-bridge on app exit
  • Session rename persistence in conversations crate

Frontend

  • Status bar → activity panel, chat input padding
  • Branch switching moved to sidebar worktree badge
  • Browser toolbar, element context chips, file viewer fixes
  • Popover/button styling polish

Agent Bridge

  • LocalSDKMessage discriminated union types (replaces unsafe casts)
  • Array.isArray() guard for user message content (string | unknown[])
  • CLAUDE.md updated with workaround documentation and troubleshooting

Test plan

  • All pre-push checks pass (TypeScript, ESLint, Vitest 13 tests, cargo 229 tests)
  • Manual: verify frost transparency in light and dark mode
  • Manual: verify browser corner radius and element selection
  • Manual: rename a session, restart app, verify name persists
  • Manual: send a chat message, verify no J.every crash in logs

- Model selector popover: match chat input box bg/border/shadow
- Dropdown menus: same bg-gray-1 + white border treatment
- Context window hover card: consistent styling
- Input mode, model selector, effort, image buttons: pill shape
- Worktree path: use GitBranch icon, pill shape
- Coming Soon box: match model button corner radius
- Update docs and add release guide
- Add NSVisualEffectView frost layer (decorum plugin) replacing CSS
  backdrop-filter which caused white glow at macOS window rounded corners
- Add AI-powered session titles using Haiku via query() for fast ~1-2s
  title generation after first conversation turn, with smart fallback
  truncation and pending-title flush on system:init
- Persist custom titles to JSONL via custom-title convention (matching
  Claude Code CLI), with POSIX-safe atomic appends
- Unify tool widget borders and backgrounds across all tool widgets
- Polish light-mode popover shadows, sidebar selection opacity, and
  rounded corner consistency (rounded-[9px])
- Fix Immer nested mutation in updateConversationTitle (whole-object replace)
- Bump claude-agent-sdk to 0.2.50 and claude-code to 2.1.50
Remove HeaderBranchSelector from chat-header and content-top-bar —
branch switching now lives in the WorktreeItem sidebar badge using a
Select dropdown with checkout, status refresh, and UIStore update.

Branches are prefetched on badge hover and only fetched once on open
(prevents async setBranches from triggering Radix item-aligned
repositioning while the dropdown is visible).
Switch from .run(context) to .build(context) + .run(callback) to hook
into RunEvent::Exit. Explicitly calls SessionManager::shutdown() before
the process terminates, preventing orphan agent-bridge processes.
Move StatusBar from ContentCard to ActivityPanel so it only shows when
a file is open in the editor. Remove all git-related info (branch,
ahead/behind, changes) from the status bar. Add pb-2 to chat input
container for bottom spacing.
Two bugs combined to silently drop manual renames:

1. remapConversation used .find() + direct property mutation (Immer
   nested mutation bug) — sessionId remap could be silently dropped,
   causing persistTitle to look for a non-existent frontend UUID JSONL.

2. update_title() used conversation_path() (single workspace) instead
   of find_conversation_path() (searches all workspaces like delete()
   does) — workspace path mismatches caused silent skips.
…verlay

The NSVisualEffectView frost appeared as solid grey in light mode because
blurring the desktop wallpaper produces inherently grey output. CSS overlays
covered the blur pattern entirely.

Added a native screen-blend tint overlay (NSView with CIScreenBlendMode
compositing filter) between the frost and webview. Screen blend lightens
per-pixel proportionally — preserving blur texture contrast unlike alpha
blending. Also added runtime theme configuration (material, appearance,
emphasis) and removed all CSS ::before tint hacks.
Reduce frost alpha to 0.2 for more desktop see-through, switch to
Clear liquid glass variant with stronger white tint (#FFFFFF80) from
below to lighten the grey, and use .titlebar material (lightest) instead
of .menu for the NSVisualEffectView.
- Apply CALayer corner radius to browser child window (macOS) so it clips
  flush inside the activity card, replacing the old bottom-inset workaround
- Add react-grab element selection: intercept orbit-eval:// URL callbacks
  from the embedded browser and bridge to frontend via Tauri events
- Add browser_focus command for immediate input after activating selection
- Unify element context chips and file/skill chips into a single row
- Round the browser URL bar (rounded-full) and empty-state card (rounded-2xl)
- Update decorum plugin with set_child_windows_corner_radius API
- Add docs: browser corner radius decision, navigation history & worktree sync plans
ESLint's projectService cannot resolve Claude Agent SDK types (SDKMessage,
BetaMessage, MessageParam) across the agent-bridge tsconfig boundary.
tsc --noEmit passes — this is a known tooling limitation matching the
existing Vitest globals and agent-bridge test overrides.
Create shared LocalSDKMessage types in agent-bridge/src/common/types/
that ESLint can resolve (matching the pattern session-manager.ts uses).
Cast SDK messages at the for-await iteration boundary so all property
access is type-safe for both TypeScript and ESLint.

Files changed:
- New: common/types/claude-sdk.ts — local mirror of SDK message union
- agent/core/agent.ts — use LocalSDKMessage, remove unsafe casts
- agent/session/session-manager.ts — use typed result narrowing
- canvas/core/canvas-agent.ts — use LocalSDKMessage with exhaustive switch
- canvas/orchestrator/agents/base-agent.ts — same pattern
- Tests updated to use typed property access
… crash

SDK user messages have content: string (text) or content: ContentBlock[] (tool results).
The local mirror type incorrectly assumed always unknown[], causing .every() crash on strings.
Also documents the agent-bridge SDK type workaround and risks in CLAUDE.md.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f91e1a72ec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/common/conversations/src/lib.rs Outdated
))
})?;

writeln!(file, "{line}").map_err(|e| {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Serialize custom-title append as one atomic write

This append path can run while the Claude sidecar is simultaneously appending session events to the same JSONL, but writeln!(file, "{line}") formats and writes in multiple chunks, so only per-chunk append is atomic. In that concurrent case, bytes from two writers can interleave and produce malformed JSONL lines, which then breaks conversation parsing/loading for that session.

Useful? React with 👍 / 👎.

Comment on lines +125 to +127
const gitStore = useGitStore.getState();
gitStore.setStatus(newStatus);
gitStore.setBranches(newBranches);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope git store updates to the active worktree

After a badge checkout, this always pushes newStatus/newBranches into the global useGitStore even when the user switched a non-active worktree (the badge click explicitly stops row selection). That overwrites source-control/file-status state for the currently active repo with data from a different worktree until another refresh corrects it.

Useful? React with 👍 / 👎.

…evel files

Root CLAUDE.md trimmed from 45k to ~30k by moving area-specific content
to sub-level CLAUDE.md files. CLAUDE-CONTINUOUS.md eliminated entirely.

Created 3 new sub-level docs:
- crates/common/CLAUDE.md (11 Rust crates reference)
- packages/shared-schemas/CLAUDE.md (Zod schema domains)
- apps/common/CLAUDE.md (shared frontend exports)

Updated existing sub-level docs:
- agent-bridge/CLAUDE.md (+SDK type workaround, testing philosophy, Zod patterns, CVE)
- apps/agent/CLAUDE.md (+WKWebView blur workaround, barrel pattern)
- src-tauri/CLAUDE.md (+Rust module organization)
Replace bracket opacity notation (bg-black/[0.08]) with shorthand
(bg-black/8) and move important modifier from prefix (!border) to
postfix (border!) per Tailwind v4 canonical form.
- Retry title generation on subsequent turns if first attempt failed
- Pick first non-empty assistant message for title context
- Preserve in-memory AI titles when disk reads race ahead of persist
…ion button

- Convert ArrowLeft, ArrowRight, SidebarLeft, SquareAndPencil from SVG
  stroke paths to base64 PNG mask-image technique for pixel-identical
  macOS SF Symbol rendering
- Wire the "new session" button in ContentTopBar with handleNewSession
  (previously had no onClick handler)
- Export conversationBelongsToWorktree for reuse in content-top-bar
- Add audited LINE-ANNOTATION-FEATURE plan with inline review findings
Sidebar: replace two-property transition (width + margin-left) with
single margin-left slide mirroring the activity panel pattern. Keeps
wrapper at fixed expanded width during collapse — eliminates desync
jank on rapid open/close.

Header: replace conditional render of nav controls with always-rendered
overflow-hidden container animated via max-width + opacity in sync with
sidebar timing — prevents layout shift flash on project/session title.
…decisions

Add WORKTREE-AS-WORKSPACE-PLAN.md revised per combined audit (Claude +
Codex) — addresses 4 critical issues: empty conversation list handling,
CreateWorktreeDialog/removeWorktree repo targeting, file tree null-main
sync gap, and startup reconciliation. Plan grew from 5 to 8 sections.

Also adds PIERRE-DIFFS-MIGRATION-PLAN.md and
SIDEBAR-ANIMATION-SINGLE-PROPERTY.md decision doc. Updates docs/CLAUDE.md
index to reflect new files.
Split setWorkspace into initializeWorkspace (full project reset) and
transitional shim. Add clearActiveSession cross-store handoff on
worktree switch/remove. Complete call site inventory (8 + tests).
Worktree-as-workspace logic: initializeWorkspace, switchToWorktree,
repoRootPath, clearActiveSession, scoped file tree and conversations.

UI: context menu for worktree removal, folder icon → chevron on hover,
fading timeline line, consistent gap spacing, remove misleading
expanded background on non-active worktrees.
…ions

- Enforce 400px minimum width for chat panel via CSS min-width + dynamic
  getActivityMax constraint (replaces static PANEL_SIZES.review.max=800)
- Auto-collapse sidebar when window becomes too narrow for all open panels
- Lower INPUT_CONTROLS.collapseBreakpoint from 400 to 340 (fewer buttons)
- Add gradient fade overlay on header when project/chat name overflows
- Animate conversation list and Powers section expand/collapse using CSS
  Grid row transition (grid-template-rows 0fr↔1fr) with asymmetric
  enter/exit easing curves and opacity fade
- Add fading vertical timeline line to Powers section (matches conversations)
- Unify WorktreeItem border-radius to rounded-[9px] (matches Powers button)
…insight block parser

Replace @tanstack/react-virtual with normal document flow — CSS
containment (contain: paint, content-visibility: auto) causes
block-level selection highlights in WKWebView instead of proper
text-following selection. Messages now render in natural flow with
stick-to-bottom handling auto-scroll via ResizeObserver.

Also make rehype-insight-blocks more robust: support plain-text
openers (no backticks), multiple dash characters (─ — – -), and
titles appended after the opener pattern. Add stripInsightMarkers
safety net to prevent leaked patterns in insight body content.
- Change sidebar toggle from Cmd+/ to Cmd+S, remove save file shortcut
- Add Cmd+N for new session with ⌘N badge on sidebar button
- Add Cmd+T for projects dialog with ⌘T badge on sidebar button
- Add Shift+Tab to cycle input modes (default/plan/accept) with kbd in tooltip
- Unify input control buttons to rounded-[9px] (mode picker, model selector,
  effort level, image, context buttons)
Replace folder icons with Facehash identity avatars in the projects
dialog tiles and sidebar recent project rows. Adds facehash dependency.
…inements

Replace custom Shiki-based diff rendering with @pierre/diffs across all tool
widgets (edit, write, bash, glob, grep, etc.) for unified syntax-highlighted
diffs. Add file explorer context menu with Reveal in Finder and Open in
Default App actions backed by cross-platform Rust commands. Extend git crate
with get_file_at_ref() for reading files at any git ref or index. Add liquid
glass button-primary and textarea CSS styles. Clean up legacy diff-line and
diff-viewer components.
Move file watcher setup from tauri-provider into use-file-tree hook and
add debounced directory refresh (scheduleDirectoryRefresh) for create and
delete events instead of clearing parent cache. Introduce case-insensitive
path comparison throughout file-store for macOS where FSEvents can report
paths with different casing than what's cached. Extract toFileNodes mapper
from file-handlers to shared mappers module.
…ity guard

macOS FSEvents can misreport file deletions as 'modified' events. This
change removes the created/deleted filter so all file:changed events
trigger directory refresh, with structural equality checks to prevent
unnecessary re-renders from benign modified events. Adds concurrency
limiting (max 5 concurrent listDirectory calls), sticky structural
intent across debounce windows, dirty markers for collapsed folders,
and workspace-switch cleanup.
…tor tests

- Gate BROWSER_CORNER_RADIUS with #[cfg(target_os = "macos")] to match its usage
- Escape [warning] in rustdoc comment to prevent unresolved link error
- Update BranchSelector tests for dialog-based create flow (no longer CommandItem)
- Add lerp-based smooth scroll hook with external scroll coexistence
  (passive listener syncs with useStickToBottom, onScrollAway callback)
- Apply useSmoothScroll + overscroll-y-contain across all scrollable panels
  (chat, file explorer, sidebar, source control, settings, diagnostics, diffs)
- Style Streamdown table wrappers with border-radius and overflow hidden
- Disable table copy/download controls in chat, plan, and task widgets
- Use resize: 'smooth' + overflow-anchor: none for chat scroll container
…styling

Replace Streamdown's built-in MarkdownTable (which hardcodes w-full and adds
copy/download controls) with a custom component that wraps <table> in a
.table-wrapper div. Strip padding/background from inline code inside table
cells to prevent pill-shaped selections extending beyond text glyphs.
…switching

Replace the centered ThinkingDots spinner with a ChatSkeleton component
that mimics the visual rhythm of user/assistant message blocks. Adds a
minimum display time (500ms) to prevent the skeleton from flashing on
fast loads. Animation uses scale(0.98→1) + opacity with custom easing,
respects prefers-reduced-motion.
Show copy/rewind actions only on assistant message hover for a cleaner
chat interface. Add bottom padding to user bubbles and increase the
bottom spacer for better scroll ergonomics.
…ze tool widget padding

- Delay clearing isAgentRunning by 1.5s after tool-using turns so the
  loader persists through intermediate SDK result messages instead of
  flashing the action bar between every tool execution
- Add isLastMessage prop to hide action bar only on the actively
  streaming message while preserving it on previous completed turns
- Migrate delayed-clear timers across session ID remaps
- Cancel stale timers on permission:request to avoid race conditions
- Remove inner px-2.5 from all tool widget headers, gutters, and status
  rows to unify horizontal spacing at the parent level
- Update spring spacer plan with dedicated SpringSpacer component design
- Add tests for action bar visibility and agent running timer logic
Replace the centered ThinkingDots fallback in settings tab switching
with a SettingsSkeleton that mimics the SectionHeader + SettingItem
layout. Uses bg-foreground/[0.06] for subtle contrast on the darker
sidebar background. Adds lazyWithMinDelay (500ms) to prevent the
skeleton from flashing on fast chunk loads.
…on resume

The rewindFiles flow creates a temporary SDK query with `prompt: ''` that
writes an empty user message (`text: ""`) to the parent JSONL. On resume,
the API rejects this with 400 "text content blocks must be non-empty".

Defense layers:
- Rewind lock (rewindingSessionIds) blocks sendMessage during rewind
- Strip-on-resume in createSession scrubs before startSession
- Strip-on-send in sendMessage scrubs before queueMessage (primary fix)
- One-time startup repair via repairPollutedJsonls (flag-gated)

Only empty user messages are stripped. All other JSONL entries are
preserved: interrupt markers, queue-ops, titles, system metadata.

Also fixes dangling parentUuid chain in Rust conversation parser —
when a stripped message leaves an orphaned reference, fall back to
unfiltered parsing instead of returning an empty conversation.

Includes session-storage init mapping fix and tests from prior work.
…inux CI

Bun's jest.spyOn(os, 'homedir') doesn't reliably intercept calls across
module boundaries on Linux. Added getHomeDir() wrapper with _setHomeDirForTest()
override so session-storage tests work on both macOS and Linux CI.
Implement a phase-based launch animation (wallpaper fade → ASCII reveal →
sidebar slide) with runId cancellation tokens, watchdog fallbacks, and a
settings toggle to disable it. Hide back/forward/new-session buttons on
the welcome page header.
…s styling

Extract BranchPickerContent from BranchSelector into a reusable component
with searchable branch list (cmdk) and create-branch dialog. Sidebar
WorktreeItem now shows the full popover when source control is closed,
falling back to simple Select when open. Sidebar branch badge syncs with
GitStore for real-time updates after source control checkouts.

Unify popover/dropdown styling across branch selector, model selector,
and context hover card: frosted glass bg (bg-white/70 + backdrop-blur-sm
in light, bg-lg-control/80 + backdrop-blur-xl in dark), directional
shadows without white ring, and lg-control buttons for source control
Fetch/Refresh.
…wind

stripRewindPollution() was removing empty user messages without re-parenting
their children, breaking the parentUuid linked-list chain. When
build_active_uuid_set() in Rust walked the chain, it couldn't reach entries
before the break — so Claude saw the OG session as empty after a rewind+fork.

Changes:
- Rewrite stripRewindPollution() as two-pass: identify removals, then
  re-parent children to nearest surviving ancestor (chain-safe)
- Thread sessionId through error pipeline (bridge → Rust → frontend)
  so errors reach the correct session instead of being silently dropped
- Remove pre-send stripRewindPollution from sendMessage (race-prone;
  createSession and startup repair already cover all cases)
- Bump rewind pollution repair flag v3→v4 to heal corrupted JONLs
- Preserve fork records during session-storage repair (v1→v2 flag)
- Fix migration test flag mismatch (was checking v1, code uses v2)
Remove transition-[background-color] and transition-[color] from recent
project list items (Frequency Principle). The 100ms fade caused ghost
highlights when sweeping the cursor quickly across items. Also tune
ASCII logo animation durations for snappier launch sequence.
Adds VS Code-style source/preview switching for .md/.mdx files using
the existing Streamdown infrastructure from chat. Includes three-tier
link routing (fragment → relative → absolute), path normalization,
large-file threshold guard (10K lines), and derived isPreviewRendered
hook as single source of truth for effective preview state across
rendering, search guards, and keyboard shortcuts.
- Add drag-and-drop from file explorer to chat input (enqueues file chips)
- Add "Add to Chat" context menu item in file explorer
- Render @file tokens as clickable blue links in user message bubbles
- Resolve @filename to absolute path via attachedFiles or workspacePath
- Redesign context chips: borderless tinted pills with subtle hover
- Fix code block styling: wrap long lines, match line number font size,
  reduce header padding, 9px rounded copy button
…ility

Extract CreateBranchDialog from BranchPickerContent so it renders outside
the Popover tree — prevents Radix unmount when popover closes from killing
the dialog. Also fix sidebar branch picker showing Select instead of Popover
when activity panel is closed but activityTab is still 'source' (now checks
reviewPanelOpen too).
…, symlink safety

11 fixes from independent code reviews (Claude + Codex):
- updateActiveDocPath derives all identity fields (id, name, relativePath, extension)
- saveDocument captures content before await, re-checks after to avoid losing edits
- initializeVault only sets initializedWorkspace on success (retry no longer blocked)
- VaultActions shows Reload/Overwrite buttons when conflicted
- openDocument clears stale content on error
- CSS table overflow-x: auto no longer clobbered by overflow shorthand
- deleteVaultEntry re-reads state after await, handles directory descendants
- VaultCreateDialog resets mode to 'file' on close
- File watcher: conflict detection before markdown filter, ref for currentPath
- Rust: dead double-check removed in vault_search_all_docs
- Rust: recursive walkers skip symlinks via symlink_metadata
- Rust: clippy absolute_paths and map_unwrap_or warnings resolved
Replace verbose arbitrary value syntax with theme utilities:
bg-[var(--lg-control-bg)] → bg-lg-control,
bg-[var(--lg-control-bg-hover)] → bg-lg-control-hover,
[mask-image:...] → mask-[...]
Unify settings pages (slash commands, subagents, feedback, git) to use
bg-lg-control backgrounds instead of borders, consistent rounded corners,
and segmented edit/delete button groups. Fix light mode DiffFileCard and
code block colors in pierre-adapter. Skip XDG_CONFIG_HOME/APPDATA env
vars in agent-bridge test mode for proper storage isolation.
…hancements

- Enhanced DiffFileCard with expanded diff view and folder-level git status
- Improved ChangesList with folder grouping and status indicators
- Polished edit/write tool widgets with better UX
- Updated sidebar with scroll fade and layout refinements
- Extended Rust git backend with additional diff/status commands
- Added tests for source control hooks and ChangesList component
…inking box polish

- Refactor all tool widgets to remove gutter/connector lines, simplify expand/collapse animations with separate enter/exit transitions
- Add folder-level git status propagation (selectDirectoryStatus) with colored dots in file explorer
- Restyle tooltips to solid bg matching chat input area, rounded-[9px], remove backdrop-blur
- Simplify thinking-box by removing gutter lines and completed status row
- Replace actions-bar AI disclaimer tooltip with CSS-only chat bubble
- Polish message-actions: rounded-[9px], tooltip on top, remove scale transforms
- Add git store tests for buildStatusMaps and directory status propagation
…ary polish

- Add branch deletion option to worktree remove flow with granular toast
  feedback (success/warning/error) and double-click guard
- Migrate clone repo, SSH connection, delete worktree, onboarding, and
  vault create dialogs to liquid glass design system
- Restyle root error boundary fallback with liquid glass components
- Add integration tests for handleRemoveWorktree in useSidebarActions
- Return WorktreeRemoveResult from Rust backend with branchDeleteFailed field
- Extract delete_branch_after_worktree_remove helper to reduce cognitive complexity
@fazxes
fazxes merged commit 4ccf583 into main Mar 2, 2026
19 checks passed
@sentry

sentry Bot commented Mar 4, 2026

Copy link
Copy Markdown

Issues attributed to commits in this pull request

This pull request was merged and Sentry observed the following issues:

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