Commit Graph

1721 Commits

Author SHA1 Message Date
Mohamed Boudra
bc7798af28 Allow ACP providers without model lists 2026-05-19 20:01:56 +07:00
Mohamed Boudra
f238cbc20c Tighten DeepSeek TUI catalog copy 2026-05-19 20:01:56 +07:00
Mohamed Boudra
41c5eea678 Add DeepSeek TUI to ACP catalog 2026-05-19 20:01:56 +07:00
Mohamed Boudra
352e8fb6eb Show catalog provider icons (#1098) 2026-05-19 20:54:33 +08:00
Mohamed Boudra
2c561536e4 Merge branch 'main' of github.com:getpaseo/paseo 2026-05-19 18:12:20 +07:00
Mohamed Boudra
3743df09e6 Add rename for workspaces, terminals, and agent tabs (#531)
* Add rename for workspaces, terminals, and agent tabs

Surfaces rename via the sidebar workspace kebab (git branch rename with
client-side slugify), the terminal tab context menu (stops OSC 2 auto-title
overrides), and the agent tab context menu (locks the title against the
metadata generator). A shared RenameModal wraps AdaptiveModalSheet and
replaces the inline rename on the host page.

Auto-title races are closed from both sides: AgentManager gains
setGeneratedTitleIfUnset with an atomic per-agent write queue, and
TerminalSession replaces lockedTitle with a titleMode discriminated union
so user rename flips to manual and disposes the OSC subscription.

New WebSocket messages are additive: rename_terminal_request/response and
checkout_rename_branch_request/response (branch rename uses the
CheckoutError family). A new @getpaseo/server/utils/branch-slug subpath
export shares slugify + validateBranchSlug between server and app.

* Tighten rename feature and restore lost rebase wiring

Unslop pass on the rename commit plus two rebase artifacts:

- session.ts: collapse handleRenameTerminalRequest to a respond helper
  with early returns; restore workspaceGitWatchTargets.set() in
  syncWorkspaceGitObserver (lost during rebase onto main, which broke
  onBranchChanged firing on branch rename).
- terminal.ts: remove DA1 query handler accidentally re-introduced by the
  rebase (main intentionally removed it); restore conditional
  onTitleChange registration under titleMode === "auto".
- rename-modal.tsx: drop unconfident optional-call on setNativeProps and
  the unknown-cast HTMLInputElement narrowing dance.
- sidebar-workspace-list.tsx: remove unused branch field from rename
  result; flatten validateRenameSlug into early returns.
- host-page.tsx: drop ?? "" fallback on a typed-string field.

* Fix typecheck after rebase: pass parsed config to getScriptConfigs

main refactored getScriptConfigs to take the parsed paseo.json config
instead of a repo path. spawnWorktreeScripts (added in the rename
feature) was still passing repoRoot. Read and parse the config first,
matching how spawnWorkspaceScript already does it.

* Resolve lint regressions and restore lost rebase fixes

Post-rebase cleanup: drop the await on syncWorkspaceGitObservers so
fetch_workspaces emits the response before any cold registration-
triggered git work fires (workspace.id is already on the descriptor —
no registry lookup needed). Restore the DA1 CSI handler that answers
\x1b[?62;4;22c on the daemon-side xterm so foreground apps like nvim
get a reply on stdin. Extract WorkspaceTabRenameModal/useWorkspaceTabRename
to drop WorkspaceScreenContent below the cyclomatic-complexity ceiling.
Switch session test internals to handleMessage so we exercise the public
dispatcher and avoid casting through any. Convert literal 'type' aliases
to interfaces and stop spawning callbacks inline so the codebase passes
the post-rebase oxlint rules.

* Fix typecheck after rebase: wire workspaceGitService and worker setTitle

Bootstrap was missing workspaceGitService when constructing
CreatePaseoWorktreeWorkflowDependencies after main's worktree workflow
refactor. Worker terminal manager needed setTitle on the session and
setTerminalTitle on the manager to satisfy the rename additions to
TerminalSession/TerminalManager interfaces.

* Format session.test.ts after rebase

* Remove stray auto-spawn of workspace scripts after bootstrap

The rebase brought in a spawnWorktreeScripts helper and a call inside
runWorktreeSetupInBackground that auto-started every configured workspace
script after worktree setup completed. main never auto-started scripts —
this regressed the workspace-setup-streaming Playwright test, which
expects the "web" script to be idle so the user can click Run.

Drops the helper, the call, and the workspaceGitService dep that only
existed to feed it.

* Fix checkout branch rename tests

* Fix sidebar checkout action store import

* Skip POSIX terminal tests on Windows

* Unslop the rename-entities feature

Six audit findings closed and ~450 net lines trimmed from the branch:

- Remove the duplicate "rename-branch" union member in
  GitMutationRefreshReason.
- Fold dispatchStashMessage back into handleSessionMessage; the split was a
  feature-first artifact of adding checkout_rename_branch_request, with no
  documented rationale.
- Drop the protected beforeGeneratedTitleIfUnsetWrite test seam from
  AgentStorage; rewrite the race test to exercise real Promise.all
  concurrency against the existing per-agent write queue.
- Reshape useWorkspaceTabRename so the hook returns state and handlers
  only; promote WorkspaceTabRenameModal to an exported component the
  consumer renders directly.
- Rename RenameModal to AdaptiveRenameModal so it reads as a generic
  primitive next to AdaptiveModalSheet, and update host-page,
  sidebar-workspace-list, and the workspace tab rename hook to import it
  by its new name.
- Trim duplicate matrix coverage and Zod self-tests across
  rename-modal.test.tsx, terminal.test.ts, session.test.ts,
  agent-storage.test.ts, messages.rename-entities.test.ts and the three
  rename e2e specs; introduce packages/app/e2e/helpers/rename.ts to share
  setup across the e2e specs without merging coverage.

Behavior of the rename feature is unchanged; targeted vitest, branch-wide
typecheck, and lint all green.

* Restore agentMetadataMocks dropped during rebase

5e6aeb2d removed the agentMetadataMocks hoisted definition and its
vi.mock wiring when it deleted the import describe block. The block
was kept (it belongs to main's import feature) but the mock support
was left behind.

* Fix terminal-manager tests using hardcoded /tmp on Windows

setTerminalTitle tests used cwd: "/tmp" which is not a valid directory
on Windows, causing node-pty error code 267 (ERROR_DIRECTORY). Use
realpathSync(tmpdir()) like the rest of the test file.

* Fix typecheck and lint after rebase onto main

AdaptiveModalSheet moved from a `title` string prop to a structured
`header: SheetHeader`; update AdaptiveRenameModal to memoize and pass
a SheetHeader. invalidateCheckoutGitQueriesForClient moved from
git/actions-store to git/query-keys. useWorkspaceTerminals now owns
the terminal query, so workspace-screen pulls queryKey from the hook
and re-declares queryClient via useQueryClient.

* Update agent metadata test mock to match setGeneratedTitleIfUnset rename

The branch renamed AgentManager.setTitle to setGeneratedTitleIfUnset
for the rename feature; the generateTitlePromptWithConfig helper still
mocked the old method, so eight prompt-byte tests crashed with
"setGeneratedTitleIfUnset is not a function" on both ubuntu and
windows server-tests jobs. Sister mocks in the same file were already
on the new name.

* Fix rename modal showing empty input by using controlled TextInput

AdaptiveTextInput (introduced on main by 29ce6653f) is intentionally
uncontrolled: it drops the `value` prop and seeds the native input
once via `initialValue`. AdaptiveRenameModal still passed `value=
draft` from the pre-rebase shape, so every rename modal opened with
an empty textbox and a disabled Save button. Three playwright e2e
specs (settings-host-page, sidebar-workspace-rename, workspace-agent-
tab-rename) failed for this reason.

Switch the rename modal to a plain controlled TextInput so the input
seeds with the current label and the slug transform (used by sidebar
workspace rename) reflects live in the textbox as the user types.
The unit test's old AdaptiveTextInput mock is replaced with a
react-native mock providing a controlled TextInput shim that captures
the same onChangeText/onSubmitEditing handlers.

* Revert "Fix rename modal showing empty input by using controlled TextInput"

This reverts commit 6283deae842b8a5fed63cb8bf06c78029f98386e.

* Seed rename modal input via AdaptiveTextInput's initialValue + resetKey

AdaptiveTextInput is intentionally uncontrolled — it drops `value` and
seeds the native input once with `initialValue`. The rename modal was
passing `value={draft}` from the pre-rebase shape, so every rename
modal opened with an empty textbox (three playwright specs failing).

Pass `initialValue={draft}` and bump `resetKey` only when the
transform rewrote what the user typed. That seeds the native input
with the current label on open, lets the user keep typing inside
text without cursor jumps (no remount when transform is a no-op),
and remounts the native input with the slug when the transform
diverges so live slugification keeps working in sidebar workspace
rename. Keeping AdaptiveTextInput preserves the BottomSheetTextInput
swap on mobile so the keyboard stays above the sheet — using a plain
TextInput would break that.

The unit test mock is updated to read `initialValue`/`resetKey` so it
mirrors production behavior instead of pretending the input is
controlled.

* Slugify branch rename at submit only, drop live transform

The rename modal's transform prop remounted the native input every
time the slug diverged from what the user typed, which lost focus
mid-edit on every uppercase letter or space in the sidebar workspace
rename. Live-rewriting what the user typed was also surprising — the
expectation is that you type a name, the daemon stores a slug.

Drop the transform prop from AdaptiveRenameModal entirely. Sidebar
workspace rename now slugifies once in handleSubmitRename before
calling renameBranch, and validateRenameSlug runs validateBranchSlug
against the slugified value so the user sees inline errors. The
playwright spec drops the live-slug assertion; it still verifies the
post-submit branch on disk and the rename request payload.

* Rename new rename RPCs to dotted convention

docs/rpc-namespacing.md says new RPCs use dotted names with the
direction as the final segment, and explicitly bans new flat snake_
case names. This PR introduced two flat ones; rename them in place
before the protocol ships:

- checkout_rename_branch_request → checkout.rename_branch.request
- checkout_rename_branch_response → checkout.rename_branch.response
- rename_terminal_request → terminal.rename.request
- rename_terminal_response → terminal.rename.response

Touched: the Zod literals in messages.ts, the discriminated union
entries, the session dispatcher and its tests, the daemon client
wrappers and their tests, the terminal session controller, and the
message-parsing rename-entities test. No callers exist outside the
server package — app and CLI go through the daemon-client wrappers,
which now emit the dotted names.

* Match rename modal Save button and input focus to app conventions

Save uses Button variant=default so it gets the same accent
background + white text as every other primary action in the app
(open-project, settings host save, pair-link confirm, etc).

The input previously fell back to the browser's blue focus outline
on web because the rename modal never set outlineStyle: none — every
other AdaptiveTextInput call site that wants accent feedback already
does this. Kill the browser outline, track focus state, and switch
borderColor to accent while focused.

* Theme the focus-visible outline color via AdaptiveTextInput

public/index.html paints a 2px :focus-visible outline on every web
element with a hard-coded #20744a (Paseo green). On the Claude theme,
the rename modal's input got that green ring instead of the theme's
brown accent — visible mismatch against every other accent-colored
control (primary buttons, etc).

Add an outlineColor entry to AdaptiveTextInput's stylesheet sourced
from theme.colors.accent. Unistyles' Babel plugin tracks the read and
updates the native ShadowTree on theme switch — no React re-render,
no useUnistyles() call (forbidden on this hot path per docs/unistyles.md).
The inline outline-color on the rendered DOM input overrides the
selector-level color from index.html; outline-width/style/offset
still come from the global rule.

Consumer style merges in after, so existing callers that pass
outlineColor (message-input, review/surface, question-form-card)
still win.

Also drop the half-baked isFocused/focusedBorderStyle local state
I added to rename-modal earlier — the AdaptiveTextInput fix removes
the need for it.

* Drop useUnistyles() from rename modal

The rename modal only read theme.colors.foregroundMuted to pass as
the TextInput's placeholderTextColor prop. The rest already went
through StyleSheet.create((theme) => ...). Per docs/unistyles.md the
hook is forbidden when an alternative exists — and this is exactly
the alternative the rest of the codebase uses (project-settings-screen,
add-host-modal, pair-link-modal, command-center): put the muted color
in a tiny StyleSheet entry and read .color off it for the prop.

* Default placeholderTextColor in AdaptiveTextInput

placeholderTextColor was being duplicated at every AdaptiveTextInput
call site (add-host-modal, command-center, pair-link-modal,
project-picker-modal, provider-diagnostic-sheet, combobox, the
sheet's own search inputs, etc.) — all passing the same
theme.colors.foregroundMuted. The shared input should own this.

Move it into AdaptiveTextInput as a default sourced from the same
StyleSheet.create(theme => ...) block as the accent outline. Consumers
still override via the prop if they need a different color. Strip
the redundant placeholderTextColor and local placeholderColor style
entry from rename-modal; other call sites can be cleaned up in a
follow-up.

* Update rename e2e specs to use dotted RPC type strings

Earlier commit (5d5624943) renamed the new rename RPCs to the dotted
convention but missed the two e2e specs that capture the WebSocket
frames by type. captureWsSessionFrames matched the old flat names, so
renameRequests / renameFrames stayed empty even though the rename
went through end to end — the sidebar updated and the branch was
renamed on disk. The toBeGreaterThan(0) assertion fired and the test
failed in playwright CI.
2026-05-19 18:05:45 +08:00
Mohamed Boudra
0f6641c8c0 Show resolved file paths in agent file-link tooltips (#1088)
* fix: show resolved file paths in agent file-link tooltips

Bare filenames in agent messages now resolve to their full workspace
path on hover. Also raises the daemon's directory-suggestion scan
depth so files in deeper package layouts are reachable.

* fix(app): keep file-link wrapper stable to avoid layout shift on resolve

* fix(app): resolve file links on hover instead of at render time

useQuery was firing the daemon RPC for every ambiguous file reference
the moment a message rendered, fanning out a wave of requests on chat
scroll. Switch to enabled: false + prefetchQuery on hover so RPCs are
driven by user intent. Sync-resolvable refs (directFile, external)
still seed via initialData and render with no RPC.

Also memo on source primitives rather than identity so identical-
content sources constructed inline upstream don't bust the memo every
render.

* Redesign assistant file link resolution

* Stabilize assistant file link handlers
2026-05-19 15:32:48 +08:00
Mohamed Boudra
b2cdbdaed8 Hide import pill once the draft is submitting 2026-05-19 10:26:13 +07:00
Mingyang Sun
9ef7230417 Add Kiro CLI to ACP provider catalog
Adds Kiro CLI as an opt-in ACP provider catalog entry, plus a generic ACP extension-notification sink so unknown notifications no longer fail JSON-RPC dispatch.
2026-05-19 03:23:57 +00:00
Mohamed Boudra
339ca2fc83 Make zinc accent monochrome and respect accentForeground 2026-05-19 10:01:39 +07:00
Mohamed Boudra
501dcf373b Hide voice mode while agents run 2026-05-19 08:18:56 +07:00
Samatar
9bc6210823 fix(terminal): send SIGINT for hardware Ctrl+C on iPad
Fixes #1049
2026-05-19 09:02:48 +08:00
Mohamed Boudra
63fe5dd0df chore(release): cut 0.1.78 2026-05-18 17:59:57 +07:00
Mohamed Boudra
120f5d94a9 Fix compact web sheet swipe crashes 2026-05-18 15:29:01 +07:00
Mohamed Boudra
29ce6653fd Improve mobile selectors and sheet inputs
Keep sheet text inputs native-owned to avoid React Native controlled input flicker during fast typing. Add reset keys for fields that need programmatic clears, and move model, mode, and thinking pickers onto shared combobox rows with fuzzy ranked search and native virtualization.
2026-05-18 14:56:17 +07:00
Mohamed Boudra
82319f5805 Keep agent panes mounted and focused 2026-05-18 11:24:56 +07:00
Mohamed Boudra
77b92b58f7 Drop trailing periods from changelog and fix mobile release docs
- Strip end-of-bullet periods from CHANGELOG.md (whole file)
- Tighten 0.1.77 TLS bullet to drop LAN/public explanation
- Release docs: drop release-mobile.yml references (workflow does not exist),
  add EAS mobile-build babysitting section using the EAS CLI, clarify that
  releases are always patch, clarify that "stable" means stable
- Changelog style guide: no trailing periods, one sentence per bullet, lead
  with capability not mechanism
2026-05-18 09:33:46 +07:00
Mohamed Boudra
2f638fe6eb chore(release): cut 0.1.77 2026-05-18 09:22:18 +07:00
Mohamed Boudra
721f1ee8d3 Fix inline code file matching 2026-05-17 23:40:31 +07:00
Mohamed Boudra
b55aa042d4 Return to parent tabs on close 2026-05-17 23:06:39 +07:00
Mohamed Boudra
1f7fc232b3 Limit tool call coalescing to prepend boundary 2026-05-17 22:24:59 +07:00
Mohamed Boudra
ee44b536f5 Support line ranges in assistant file links (#1067) 2026-05-17 22:55:46 +08:00
Mohamed Boudra
efc9c2d345 Preserve tool call idempotency 2026-05-17 21:09:29 +07:00
Mohamed Boudra
c4e45af565 Coalesce prepended tool call history 2026-05-17 20:53:03 +07:00
Mohamed Boudra
e27734b218 Use surface3 for user message bubble
Drops the accentSubtle theme token in favor of an existing surface layer.
surface3 gives a clear step up from surface0 (chat bg) without the tinted
look the accent fill produced.
2026-05-17 19:41:49 +07:00
Mohamed Boudra
0e3a78b308 Stabilize mobile sidebar close test (#1065) 2026-05-17 20:32:06 +08:00
ezra
edb0ba888a Make terminal scrollback configurable (#1021)
* Make terminal scrollback configurable

* Fix terminal scrollback lint violation

* Fix terminal runtime test environment split

* Fix terminal scrollback updates

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-05-17 19:58:35 +08:00
c4605
b3c272f720 Fix native diff row expansion (#940)
* Fix structured diff parsing for no-prefix paths

Git can emit diff --git path path when diff.noprefix is enabled. The structured diff parser only handled the default a/path b/path form, so checkout diff assembly could lose hunks for affected files and render expanded rows empty.

Normalize optional a/ and b/ prefixes in both app and server parsers, and cover the no-prefix header form with regression tests.

* Fix no-prefix diff path parsing

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-05-17 18:58:10 +08:00
Mohamed Boudra
bb5c3dae00 Reduce user bubble desaturation 2026-05-17 15:39:34 +07:00
Mohamed Boudra
ed7944cf9d Distinguish user message bubbles with accentSubtle fill
User bubbles previously used surface2, which blended into the surrounding
chat surface. Add an `accentSubtle` theme token (accent darkened, slightly
desaturated, low alpha) and use it for the bubble fill so user messages
read as distinct without shouting.

Fixes #986
2026-05-17 15:36:12 +07:00
Bolun Zhang
91d257e103 fix(app): widen host switcher popover (#981)
Co-authored-by: zbl <zbl@zbl-M4Pro.local>
2026-05-17 08:27:06 +00:00
Myriad-Dreamin
e379707a68 Show diff file paths in a tooltip (#1061)
* fix: add file path titles to diff headers

* Show diff file paths in a tooltip

* Fix workspace layout hydration

* Revert "Fix workspace layout hydration"

This reverts commit 05c89909b7.
2026-05-17 16:02:29 +08:00
Mohamed Boudra
f6afe0d864 feat(app): syntax highlighting and copy button for chat code blocks (#1062)
Adds Lezer-based highlighting to fenced code blocks in assistant messages,
with a hover/tap copy button. Colors move into the theme as
`theme.colors.syntax`, so the chat blocks, file preview, and diff viewer
share one StyleSheet path — token color updates flow through the Unistyles
native ShadowRegistry with no React re-renders on the chat hot path.
2026-05-17 16:01:08 +08:00
ezra
952b58c0eb Fix Windows Android app scripts (#1058)
* Fix Windows Android app scripts

* ci: skip onnxruntime CUDA download
2026-05-17 14:06:42 +08:00
ezra
9324a5b67a feat(app): show mode icons in agent status menu (#1059) 2026-05-17 13:56:14 +08:00
Mohamed Boudra
1c38ffe0a9 Fix composer scrollbar resize loop (#1050) 2026-05-16 19:31:24 +08:00
Mohamed Boudra
f4a4e0c25c fix(ios-release): submit each iOS build to its own App Store version
The submit_review fastlane lane identified the just-uploaded build by
CFBundleVersion alone. In this project the production iOS profile resets
the build number per marketing version, so every release sits at build 2
and Spaceship::ConnectAPI::Build.all returns the whole TestFlight history.
The .find call picked an arbitrary match, which is how 0.1.76's binary got
attached to the 0.1.75 App Store version slot and submitted for review
under the wrong label.

The release workflow now forwards app_version and app_build_version from
the build_ios outputs into the fastlane step as env vars, and the Fastfile
filters builds by marketing version plus build number to pin the exact
binary. The standalone resubmit workflow takes the same identifiers as
optional workflow_dispatch inputs; with both unset it falls back to "most
recently uploaded iOS build" so manual reruns still work.

Pinning fastlane in the Gemfile to lock the Spaceship behavior this lane
depends on.
2026-05-16 16:46:10 +07:00
Mohamed Boudra
a7dd0da2aa app: use SyncedLoader for chat thinking indicator 2026-05-16 16:46:10 +07:00
Mohamed Boudra
618b4a30b1 Refactor chat file-link opens to a single disposition seam (#1038)
* Open chat file links in a side pane on cmd-click

* Add hover tooltips to file links showing cmd-click hint

* Collapse file-link open path to a single disposition seam

Replace the parallel "open in side" callback that was threaded
alongside the normal open callback at every layer between the chat
click handler and the workspace router. One callback now carries a
`OpenFileDisposition` ("main" | "side") so the modifier-key decision
lives at the leaf and the routing decision lives at the workspace
screen, with no branching in between.

* Give assistant file links a home
2026-05-16 17:27:20 +08:00
Mohamed Boudra
4b02daed8a Add slash commands for ending and restarting agents (#1034)
* Add slash commands for ending agents

* Stabilize slash command e2e submit

* Unslop client slash command draft setup

* Fix slash commands while agent is running

* Fix slash command submit race

* Reshape client slash command execution

* Fix slash command tab cleanup
2026-05-16 17:03:59 +08:00
Mohamed Boudra
667f441cc0 Fix mobile web gestures, DnD activation, iOS zoom, and Enter behavior (#1048)
* fix(app): require long-press for sidebar dnd on mobile

* fix(app): right-edge open gesture for explorer sidebar

* fix(app): left-edge open gesture for workspace sidebar

* fix(app): allow gesture children to scroll on web

* fix(app): lock viewport zoom on compact web

* fix(app): don't submit on enter in compact web composer

---------

Co-authored-by: jon <nikuscs@gmail.com>
2026-05-16 14:20:24 +08:00
nikuscs
35c582913d fix (app): mobile sidebar web interactions (#900)
* fix mobile sidebar web interactions

* fix(app): restore button role on sidebar project row

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-05-15 13:38:43 +00:00
Mohamed Boudra
39094078a9 chore(release): cut 0.1.76 2026-05-15 18:07:13 +07:00
Mohamed Boudra
160d861534 Fix agent turn timing persistence 2026-05-15 17:38:28 +07:00
Mohamed Boudra
0d05ff7efd Resolve inline code file links 2026-05-15 15:50:35 +07:00
Mohamed Boudra
bb6403e426 Stabilize agent turn footer layout 2026-05-15 15:19:00 +07:00
Mohamed Boudra
eed61d338c Tighten markdown list spacing around prose
Lists previously had a static marginVertical that stacked with
paragraph.marginBottom, producing 20px gaps and oversized whitespace
around paragraphs sandwiched between lists.

Spacing is now context-aware via the render rule: list -> list keeps
the normal paragraph gap (back-to-back lists read as continuous
content) while p <-> list transitions get a slightly larger gap (lists
read as their own section). Static margins can't satisfy both because
RN doesn't collapse margins, so the bullet_list / ordered_list rule
inspects the next sibling type and picks marginBottom accordingly.
2026-05-15 14:03:14 +07:00
Mohamed Boudra
37bb168204 Polish agent stream spacing, alignment, and metadata
- Centralize horizontal alignment: StreamItemWrapper / contentWrapper own
  the chat rail (paddingHorizontal: 8). Stream items stop setting their
  own outer horizontal padding. Copy icons get negative margins so the
  visible icon sits flush with the rail edges.

- Unify the inline working indicator and assistant turn footer into a
  shared slot (minHeight: 24, paddingBottom: 24). No layout shift on
  streaming -> idle, and both gain breathing room above the composer
  and the next user bubble.

- Equalize the gap on both sides of an assistant message adjacent to a
  tool call (20px each). Drop assistantMessageStylesheet.containerSpacing,
  which was a redundant marginBottom that only fired in one direction
  and duplicated getGapBetween's responsibility.

- Scale up the chevron via transform (size stays 12, viewBox padding no
  longer dominates), iconBadge dimensions unchanged. Tool icon and
  chevron now read as similar visual weight.

- Shared STREAM_METADATA_FONT_SIZE (13) for user message timestamp,
  assistant footer duration/timestamp, and the live elapsed timer.

- formatDuration: one rule for static and live displays - 0-10s as
  X.Xs, 10-60s as Xs, minutes/hours integers only. Drops the `mode`
  parameter.

- formatMessageTimestamp: explicitly carry the resolved hourCycle so
  the runtime applies an OS-level 24h preference when the platform
  exposes it (e.g. AppleICUForce24HourTime on macOS).
2026-05-15 13:30:57 +07:00
Mohamed Boudra
57db997028 docs: lock down hover pattern, fix user message bubble flicker
The user message bubble had hover on a `Pressable` wrapping the bubble,
with the timestamp and copy button as siblings outside it — moving the
cursor onto them lost hover. Wrapping both in a single Pressable then
flickered, because `TurnCopyButton` is itself a Pressable and nested
Pressables fight over hover state.

Rewrite `UserMessage` to match the workspace row pattern: hover-tracking
on a plain outer `View` with `onPointerEnter`/`onPointerLeave`, nested
Pressables freely inside. Pointer events are non-bubbling DOM events,
so descendants can't disrupt the outer hover.

Rewrite `docs/hover.md` with the workspace row as THE canonical pattern,
not one option among several. Names the three failure modes that come
from skipping it — including "nested Pressables fight over hover" — and
explicitly bounds when `Pressable.onHoverIn`/`onHoverOut` is acceptable
(self-styling only).

Update `constants/platform.ts` so its hover guidance points at the doc
instead of recommending the broken pattern.
2026-05-15 11:25:19 +07:00
Mohamed Boudra
a72c7f6c4b Use daemon timestamps for turn timing (#1033) 2026-05-15 11:09:13 +08:00