- Redesign sidebar: persistent header with logo/status/nav buttons, "New Session" button, settings at bottom
- Add zen mode: hides all sidebars, simplifies input bar, Escape to exit (web)
- Add unread session tracking: solid blue dot when agent finishes while user is elsewhere
- Simplify ChatHeaderView: remove back button, clickable title for session info
- Remove diff sidebar toggle: always visible, hidden only in zen mode
- Persistent header overlay stays in same position regardless of zen mode
- Add i18n strings for zen/unread/sidebar across all languages
via [Happy](https://happy.engineering)
Swaps the hand-rolled DiffView + calculateDiff.ts for @pierre/diffs,
which brings Shiki-backed syntax highlighting, proper split/unified
modes, and smart overflow handling. Native keeps a plain-text renderer
to avoid pulling WASM into Hermes.
- Add @pierre/diffs dep and patches/expose-pierre-diffs-style.cjs
(adds ./style.js to the package's exports map so we can import CSS).
- New PierreDiffView component:
* Web: lazy-imports @pierre/diffs + @pierre/diffs/react, caches
the bundle in a module-scoped promise and prefetches on session
mount so the first chat diff doesn't wait on chunk download.
* Native: routes `patch` through a prefix-colored PlainPatchView
and `oldFile/newFile` through the classic DiffView (restored
from git history for the native code path). Always unified.
- Rewrite ToolDiffView to accept `patch` or `oldText`/`newText` +
`fileName`, map wrapLinesInDiffs/showLineNumbersInToolViews to
Pierre options, drop the outer horizontal ScrollView (Pierre
handles overflow itself).
- Migrate consumers (CodexDiffView, CodexPatchView, MultiEditView,
MultiEditViewFull, InlineFileDiff) onto the new API.
Diff-fetch corrections:
- Tracked files in InlineFileDiff use `git diff HEAD -- path` so the
pane shows staged + unstaged changes together regardless of which
sidebar section was clicked. Previously staged-only files rendered
as "No changes".
- Untracked files read contents via `cat` and render as a pure addition
through PierreDiffView's oldFile/newFile API. `git diff` without
--cached returns empty for untracked files.
- SessionView stores the whole GitFileStatus so the file status can
be passed into InlineFileDiff.
- gitStatusFiles uses `git diff --numstat` (not `--numstat HEAD`) for
unstaged row stats so files with both staged and unstaged changes
don't double-count staged lines.
Root cause: @livekit/components-react's useLiveKitRoom creates one Room
instance and reuses it forever because `token` is missing from the effect
deps array. After disconnect(), reconnecting the same Room silently fails.
Fix: postinstall patch adds `token` to the Room creation effect's deps,
forcing a fresh Room per session. The existing cleanup effect disconnects
the old one automatically.
Also reverts broken @elevenlabs/react import on native (needs react-native),
restores ElevenLabsProvider wrapping, and removes the voiceSessionGeneration
counter workaround that caused phantom keyboard on cold launch.
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
* feat: migrate from yarn 1 to pnpm 10
Yarn install was taking ~82s (warm cache, no scripts). pnpm achieves
~7.5s for the same operation — an 11x speedup thanks to its
content-addressable store and hard-linking.
Key changes:
- Add pnpm-workspace.yaml, .npmrc (shamefully-hoist for RN compat)
- Replace yarn.lock with pnpm-lock.yaml (imported via pnpm import)
- Use workspace:* protocol for @slopus/happy-wire cross-references
- Move happy-cli resolutions to root pnpm.overrides
- Add pnpm.onlyBuiltDependencies allowlist for native build scripts
- Update all 3 Dockerfiles to use corepack + pnpm
- Update CI workflows to use pnpm/action-setup@v4
- Update all scripts, tests, and docs referencing yarn
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
* chore: bump changelog to version 8 for pnpm migration OTA
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
* fix(ci): pnpm pack outputs to repo root, use --pack-destination
pnpm pack puts the tarball in the current directory (repo root) unlike
yarn which puts it in the package directory. Use --pack-destination to
match the expected path in the smoke test workflow.
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
- Switch to signedUrl for ElevenLabs session auth (websocket default)
- Bypass mode only with custom agent ID (can't bypass default agent)
- Voice settings shows usage bar (UsageBar component)
- Hard limit: "configure your own ElevenLabs agent" modal
- Subscription required: paywall presentation
- All 10 language translations for usage + limit strings
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Switch to ElevenLabs signed URLs with include_conversation_id=true
for server-side conversation tracking. Per-user rate limiting via
VoiceConversation DB model instead of ElevenLabs user_id (which gets
overridden on authorized agents).
- POST /v1/voice/conversations: usage check, signed URL, DB record
- GET /v1/voice/usage: per-user usage for settings UI
- 1hr free → subscription required → 5hr hard block
- ELEVENLABS_API_KEY and REVENUECAT_API_KEY required or 500
- VoiceConversation model with lazy duration backfill
- Signed URL conversation_id is cryptographically bound (can't fake)
- scripts/voice-usage.py for admin analytics
- docs/paid-voice.md, docs/user-identity.md
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
The adapter's parsePgBytes returns Uint8Array which doesn't serialize
correctly across the JS-WASM boundary. Patch converts to Array.from
via postinstall script.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>