Authored Markdown destinations were filtered by the filename heuristics intended for inferred text, silently ignoring screenshot links. Resolve explicit destinations directly through the existing workspace opener so reads and failures reach the file pane.
Preview links bypassed the directory-aware opener and opened folder destinations as file tabs. Use the existing opener and explorer reveal actions, retaining preferred file placement and canceling pending checks when leaving the source tab.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Explicit Markdown hrefs were rejected by the filename heuristic used for inferred text. Let authored nested paths resolve without a recognized extension while preserving inline-code, automatic-link, and domain handling.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Wait for plugin reload completion instead of the request deadline
The daemon answers plugin.reload.request only once the plugin is running
again or has failed, and a reload queues behind other plugin lifecycle
work such as an install with a preparation command. The shared client
applied its sixty-second session RPC deadline to that request, so a slow
reload failed with "Timeout waiting for message" while the daemon kept
loading the plugin, inviting a reload the daemon was still running.
Reload now waits for the daemon's own answer. Enable and disable keep the
session deadline, and a real startup failure still reaches the caller.
* Cover a slow plugin reload end to end
The CLI fixture now holds its own git build on a gate file, starts a
reload of an installed plugin behind that queued install, observes the
reload request at the proxy, keeps the gate closed past the client's
former one-minute deadline, and then asserts the reload's real result:
CLI exit 0, the plugin running, and exactly one reactivation. Listing the
plugin during the wait stays readable and does not restart it. A plugin
that cannot start still fails its reload with the daemon's error.
The request proxy that recorded install sources becomes a small observer
that records any session request, so the wait is measured from the
request boundary rather than from process start.
Verified: npx tsx packages/cli/tests/e2e/plugin-lifecycle.test.ts exits 0
in 135 s; the same fixture without the new scenario takes 66 s.
* Document how a reload reports its result
The CLI docs now say `paseo plugin reload` waits for the daemon to stop
and start the plugin, that cancelling the command or losing the
connection does not stop the daemon's work, and that `plugin ls` shows
the previous state until the reload finishes.
The plugin reference records the lifecycle fact behind it: a reload
queues behind the daemon's other plugin work, the daemon answers when
the plugin is running again or has failed, and status reads, Settings and
the logs are not the reload's outcome.
* Cover the Settings reload wait end to end
Adds a browser scenario for the ticket's user-facing half: with a gated
git install holding the daemon's plugin lifecycle queue, the row's Reload
goes out, the test keeps the gate closed past sixty seconds, asserts the
row stays pending with no error feedback, then releases the build and
asserts the daemon's plugin.reload.response and the app's "Reloaded
e2e-plugin" result.
The scenario turns plugins on through the daemon instead of the global
switch, whose transient "Plugins enabled" confirmation is not its
subject. The wait is measured from the reload request frame, which is an
outbound frame, not from the response stream.
Verified: npx playwright test --project=browser
e2e/browser/plugin-management.spec.ts -g "keeps Reload pending past a
minute" -> 1 passed (1.5m); plugin-reload-pending.png,
plugin-reload-past-deadline.png and plugin-reload-completed.png show the
pending row, the surface past the former deadline with no error, and the
finished reload.
* Correct the status guidance for a cancelled reload
Review finding: the note said a listing keeps the plugin's previous
state until the reload finishes, but that only holds while the reload is
still queued. Once it executes, the daemon removes the plugin from the
runtime catalog before stopping it (plugins/runtime.ts:377-382) and
resolvePluginStatus maps an enabled plugin that is not running to
"failed" (plugins/index.ts:71-78), so a listing read in that window
shows failed for a healthy reload.
Both passages now separate the two windows and say that neither a
listing nor the logs report the outcome of the outstanding reload.
The interim "failed" itself is TUC-1401.
* Harden the reload coverage against failures and lost connections
Review findings folded into the tests:
The Settings scenario releases its gated build in a finally block. Before
this, a failing assertion left the build polling forever, the daemon's
plugin lifecycle queue stayed blocked, and the fixture teardown's own
removePlugin timed out behind it. It also asserts the row's actions are
enabled again once the reload settles, next to the result feedback.
The connection-close regression now reconnects and asserts nothing
resends the lost reload; the no-replay invariant already covered for
legacy requests now holds for a pending reload too.
Verified: npx vitest run packages/client/src/daemon-client.test.ts -> 161
passed. npx playwright test --project=browser
e2e/browser/plugin-management.spec.ts -g "keeps Reload pending past a
minute" -> 1 passed (1.4m).
* Let the gated build finish before its files go
Review finding on packages/cli/tests/e2e/plugin-lifecycle.test.ts:450:
the finally block released the gate and deleted the gate directory
without waiting for the install and the reload it had started. The build
polls for the gate file, so cleanup could delete the release before the
child saw it and leave the build waiting until daemon shutdown.
The fixture now keeps both promises, releases the gate, and awaits them
before deleting their files.
Verified: npx tsx packages/cli/tests/e2e/plugin-lifecycle.test.ts -> exit
0, 161 s.
* Read the reload scenario as intent, not transport code
Review finding: the new browser scenario kept frame parsing, selectors,
polling, deadline arithmetic, screenshots and gate cleanup in one body.
docs/testing.md asks for complex setup in reusable helpers.
The wire observation, the gated queue hold, the row-state assertions, the
deadline wait and the surface captures are helpers now, and the body
reads as the flow: open Settings, hold the queue, reload, still pending
past the deadline, release, read the daemon's result.
Verified: npx playwright test --project=browser
e2e/browser/plugin-management.spec.ts -g "keeps Reload pending past a
minute" -> 1 passed (1.4m).
* Keep the reload lifecycle fact in one document
Review finding: the cli.md paragraph repeated the queue and interim-state
explanation that the plugin reference already owns. CLAUDE.md: "One fact,
one doc. Every other mention is a link."
The reference keeps the full explanation; the CLI page states what the
command does and links there.
* Release the gated build when its setup fails
Review finding: holdPluginLifecycleQueue started the install, then
waited up to 30 seconds for the build's started marker. A preparation
that took longer made the helper throw before it returned its cleanup
handle and before the test entered its try block, so nothing wrote the
release file: the build polled forever, the daemon's plugin lifecycle
queue stayed blocked, and the fixture's own removePlugin timed out
behind it.
The helper now owns that failure path: it releases the gate and drains
the install before rethrowing, so a queue it hands out is always
settleable.
Verified on Linux (Chromium, Playwright):
- With the started marker suppressed in a temporary local patch, the run
fails at the 30-second marker wait inside the helper and the teardown
still completes (both attempts ~33 seconds, no removePlugin request
timeout).
- With the marker in place, the scenario passes: 1 passed (1.4m).
* Keep an early install failure out of the unhandled path
Review finding: the marker wait in holdPluginLifecycleQueue ran before
the install promise had a rejection handler, so a preparation that failed
or a connection that closed before the build wrote its marker surfaced as
an unhandled rejection, which fails the run before the helper's own catch
can drain the queue.
The helper attaches a handler the moment the install starts and races the
marker wait against the install, so an early failure reaches the caller
as the daemon's own error, a build that never writes its marker still
fails as a 30-second marker timeout, and every path releases the gate and
drains the install.
Verified on Linux (Chromium, Playwright), patching the build command
locally for each failure branch:
- Early failure (build exits 1): fails in 4.2 s with the daemon's
DaemonRpcError for plugin.source.install.request, no unhandled
rejection, teardown completes.
- No marker (build waits for the gate): fails at the 30-second marker
wait, no unhandled rejection, teardown completes with no removePlugin
request timeout.
- Unpatched: 1 passed (1.5m).
* Keep a failed launch from ending the run before its cleanup
* fix(app): open folders from inline path links in the file explorer
Clicking a folder path in an assistant message opened a file tab that
failed with "Requested path is not a file". Extension-less
workspace-relative paths without a line number are now probed with a
directory listing before opening: directories open the Files explorer
navigated to that folder, everything else opens as a file like before.
Lexical directories (trailing slash, workspace root) now navigate the
explorer to the folder instead of only preloading its listing.
* fix(app): reveal linked folders and their contents
Resolve workspace folder links from directory metadata and expand their ancestor paths in Files. Preserve file and line-reference opening, including extensionless files.
Co-authored-by: Moy <moyfoxther@gmail.com>
* fix(app): cancel stale path opens and reveal hidden folders
Keep pending directory checks inside the path-opening model so a later click or navigation supersedes them. Explicitly revealing a hidden directory also enables the existing hidden-file visibility preference.
---------
Co-authored-by: Moy <moyfoxther@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): make home actions reachable on short screens
Keep the home cards and community links in one scrollable flow so overflow stays reachable and the footer cannot overlap the cards.
* fix(app): keep home scrolling clear of window drag regions
* fix(app): render terminal-profile icons instead of the generic Bot
* test(app): verify terminal profile artwork in the launch menu
Exercise the rendered SVG, dimensions, theme color, catalog artwork and fallback through the real browser app instead of inspecting hook results in JSDOM. The browser regression fails on main before the lookup fix.
---------
Co-authored-by: Noah <noahguerin20@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Keep keyboard translation attached to the native animation graph when fullscreen resizes the dock. Apply the enabled mode on the native animation frame so Fabric does not restore the previous transform during layout.
An iOS pan waiting for manual activation can deliver finger-up without finalizing, leaving the row active and the outer scroller locked. Fail a pending pan when its touch stream ends so the existing finalization path cancels the drag. Track activation immediately so an active drag keeps its normal completion path.
Based on the touch-release diagnosis in #5586. The native regression fails before the fix and passes on the Namespace iOS simulator.
Co-authored-by: Kassam Khoja <kassam@shopwrk.com>
* fix(app): group native assistant prose into selectable runs
* fix(app): render selectable native prose with enriched markdown
* docs: explain native prose selection boundaries and tradeoffs
* fix(app): preserve markdown semantics and render limits in prose runs
* docs: record native prose compatibility fixes and heading limits
* fix(app): allow additive task lists and admonitions in native prose
* fix(app): preserve inline image formatting and reference scope
* fix(app): preserve markdown source and file actions in native prose
Prepare original source blocks with source-position edits from the assistant parser. Keep reference scope and live/history projection consistent, and retain link text when dispatching workspace file actions.
* fix(app): preserve native prose entities and links while bounding preparation cost
* fix(app): preserve provisional syntax at cached reveal boundaries
* fix(app): bound prose append work and preserve markdown edge cases
* fix(app): preserve home paths and normalized prose links
* fix(app): preserve parser-owned links and streaming tilde tails
* fix(app): keep growing markdown on the streaming block renderer
* fix(app): preserve bracketed IPv6 links in native prose
* fix(app): skip prose classification for blocks over the render cap
* fix(app): share selectable native prose across Markdown surfaces
* fix(app): preserve Markdown containers before grouping prose
* revert(app): remove shared prose-run renderer changes
Reverts ab4737bb2 and ac47e1063 before replacing the Markdown engine across all platforms.
* feat(markdown): use enriched renderer across all platforms
* test: verify library image controls and isolate website config
* fix(markdown): preserve file links and image revisions
* fix(markdown): restore diagrams and preserve link origin
* fix(markdown): retain diagram identity and reference scope
* fix(markdown): avoid empty web history measurements
* fix(markdown): preserve authored empty link titles
* test(markdown): exercise find across whole-message history
* fix(markdown): preserve encoded image paths after rebase
Keep the shared renderer image normalization and file-read regression coverage that originally lived in the main integration merge.
* fix(markdown): derive table and task list colours from the theme
* fix(markdown): restore selection highlight on web
* fix(markdown): restore file link hover tooltips on web
* fix(markdown): normalize inline HTML before rendering
* fix(markdown): create renderer types when applying package patch
The published nightly omits both MarkdownRenderers type files. Represent them as new files so patch-package succeeds on a fresh npm install, matching the already patched development package.
* fix(markdown): keep the renderer tree stable while images resolve
* fix(markdown): size inline images to the line and collapse failed loads
* fix(markdown): normalize only parsed HTML nodes
* fix(markdown): keep the first image reference definition
* fix(markdown): preserve complete assistant messages on native
* fix(markdown): classify web links explicitly and complete renderer types
* fix(markdown): preserve spacing between HTML elements
* fix(markdown): normalize HTML following code elements
* fix(markdown): preserve literal image fallback labels
* fix(markdown): retry transient image decode failures
* docs(markdown): record native selection and image limitations
* fix(markdown): preserve first definitions across Mermaid segments
* fix(markdown): reserve pending images and harden web rendering
* test(markdown): cover state retention during image arrival
* fix(markdown): preserve reader position while responses grow
Keep the same message component boundary when a live row becomes retained history, preserving Mermaid runtimes and selection nodes. Observe paced child text commits at the web viewport so sticky-bottom follows before the next frame.
* test(markdown): require growth while holding a streaming paragraph
* test(markdown): isolate image arrival from reload positioning
Mounted row totals change as history virtualizes, so observe the new response and tool directly. Hold the real PNG response after reload until the reading position settles, then verify its reserved height and viewport instead of attributing initial bottom-follow positioning to image decoding.
* fix(markdown): preserve adjacency around inline HTML
* fix(markdown): keep summary block boundaries outside bold text
* perf(markdown): scan HTML block boundaries incrementally
* test(markdown): gate delivery while establishing a held reader
* test(markdown): name shared renderer setup and file actions
* test(markdown): assert linear HTML boundary work without timing
* test(markdown): check large HTML table output without observing built-ins
* fix(markdown): restore accentBright link colour
* fix(markdown): require letters and known extensions in bare path recognition
* fix(markdown): match main's blockquote, bullet and h6 colours
* fix(android): keep inverted chat still during text selection
React Native decomposes Android list inversion into a 180-degree rotation with positive scaleY, so the existing visibility-scroll guard never recognized the inverted viewport. Inspect the composed matrix and reproduce the actual list transform through the native view manager in regression tests.
* fix(markdown): linked inline code keeps inline code size
* fix(markdown): reserve a placeholder box with an image glyph while images load
* fix(markdown): bare prose paths need an extension, line suffix or depth
Slash-separated English pairs were recognized as file links in prose. Require stronger path intent there while preserving the existing inline-code recognition rule.
* fix(markdown): horizontal blocks own their drags on compact layouts
* perf(markdown): keep native streaming updates in the unfinished tail
Native markdown reparses each updated view in full. Keep parser-closed blocks in a memoized head during paced reveal, then reunite completed prose for cross-paragraph selection. Preserve trailing spacing and source indentation at the split.
* perf(markdown): reuse Android measurements across view mounts
* test(markdown): bare Fabel/model and daemon/client stay plain text when copied
a502cfd0d stopped linking bare two-segment prose paths; the clipboard
expectation still carried the links from the earlier loose pattern and
failed on CI (playwright shard 1/4 on 60b4e2d05). main expects the plain
text, so restore that.
* perf(app): limit native chat mount batches
Whole-message Markdown rows measure synchronously during Fabric layout. Render fewer history rows per batch and retain a smaller offscreen window so opening a chat does not measure twelve history rows at once.
* fix(app): keep populated markdown in sync with native layout
Fabric can collapse streaming blocks before asynchronous rendering replaces the old text, producing a blank frame. Commit populated-view updates synchronously while retaining worker preparation for first mounts to avoid moving large history parsing onto the UI thread.
* perf(app): preserve unchanged sidebar workspace projections
Reopening a retained sidebar rebuilt its entry collection and invalidated every project row despite unchanged workspace data. Preserve collection identity as well as row identity, while keeping membership and ordering changes observable.
* Revert "perf(app): preserve unchanged sidebar workspace projections"
This reverts commit a3919faed4.
Header islands lose their filled background and shadow; the round tap
highlight stays. The floating header's fade now carries the icon contrast
the island fill used to give, so it stays solid through the header and
runs a longer tail into the content. The fade lives inside the overlay
because Android clips children that overhang their parent, and keeps
three gradient stops because Android rendered a four-stop version far
more transparent than specified.
Single-line header titles read one step larger. The compact composer
gets a soft drop shadow. The compact settings root groups App and Host
into islands of tiles, driven by breakpoint style values so desktop
keeps its plain list.
Limit implicit pane and remount focus replay to the existing fine-pointer policy. Fullscreen keeps one editor mounted, so touch navigation does not need the restoration introduced for the former portal handoff.
* feat(app): run and watch workspace scripts from the compact header menu
The compact header redesign dropped the mobile scripts button, so phones had
no way to run, stop, or restart workspace scripts. Scripts are now a page of
the ⋯ sheet: the root row shows what is running, tinted by health, and the ⋯
carries a dot while a running service is unhealthy. Desktop keeps its popover;
both surfaces share one set of script actions and one status model.
Script status also froze on native whenever the sidebar was closed and no
agent tab was viewed. DirectorySynchronizer releases its subscriptions,
including script_status_update, when nothing demands the directory, and the
workspace screen never demanded the workspace it shows. Web hid this because
the favicon sync holds demand for the whole session. The focused workspace now
holds directory demand, which also lets Restart's second half (start once the
daemon reports the script stopped) complete.
* perf(app): keep workspace script status live without syncing the directory
The focused workspace held full directory demand so the scripts menu saw
script_status_update on phones with the sidebar closed and no agent tab.
That fetched the host's agent and workspace lists on every focus and
streamed every agent and workspace update for the whole host.
Script status now has its own event subscription, which a workspace
screen can hold alone. It fetches the workspace's scripts once when the
subscription lands, because the subscription only carries changes.
Directory demand also holds it, so the sidebar and agent tabs see the
same updates as before.
* fix(app): let Escape close an open popover before it interrupts the agent
The agent.interrupt shortcut listens on the window in the capture phase, so
it sees Escape before anything else. Registered overlays get first refusal
through dispatchTopWebOverlayKeyDown, but plugins cannot register, and draw
their floating panels with the Popover API instead. With such a panel open
and the composer focused while an agent runs, Escape closed the panel and
interrupted the agent in the same press.
While a popover is open, Escape is left to it and no app shortcut runs. A
second Escape, with nothing open, interrupts as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
* test(app): cover Escape with a popover open through the shortcut path
The first test only checked hasOpenDomPopover and would still pass with the
guard removed from useKeyboardShortcuts. The check is now keyBelongsToDomPopover,
called by the hook, and a browser test drives it the way the app does: a
window capture listener for shortcuts, a popover closed by its own document
Escape listener, focus in a textarea. The first Escape closes the popover
with no interrupt; the second interrupts. Both new tests fail with the guard
removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
* test(app): exercise plugin Escape through the real composer
Keep the contributor fix and replace the substitute shortcut-listener test with an installed-plugin browser regression. The first Escape must close the preview without cancelling the turn; the second must cancel it.
Co-authored-by: luke <luopeihuan@gmail.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
* test(app): check dismissed model pickers leave the viewport
---------
Co-authored-by: luke <luopeihuan@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): scroll the Add Project results to the keyboard-selected row
The results list had no scroll ref, so ArrowUp/ArrowDown moved the selection off-screen; the autocomplete reveal math is now shared through useRevealActiveItem.
* test(app): verify the directory selected beyond the results viewport
Check scrolling in both directions and across wrapping, then confirm Enter opens the selected directory. The previous regression used an aria-selected attribute that result buttons do not render.
Co-authored-by: superheher <heh@vivaldi.net>
* test(app): verify scroll reveal through browser behavior
Keep pure offset checks and cover keyboard navigation, wrapping, and replacing search results in the real Add Project flow.
* fix(app): measure retained Add Project rows when revealing selection
* fix(app): measure scroll content through a native View reference
---------
Co-authored-by: superheher <heh@vivaldi.net>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* perf(app): stop re-walking the timeline on every streamed delta
Every coalesced delta rebuilds the stream render model with the same
tail. The model re-sliced the tail at the history-window start on every
call, so the rendered tail was a fresh array per delta and every WeakMap
keyed on it (ordering, history split, turn timing, the layout history
cache, row revision) missed each time. Cache the slice by tail identity
and window start.
Turn timing then still walked the whole rendered tail per delta and
allocated a new assistant-timing Map, which the layout memo depends on.
Split the walk: the tail is walked once per tail identity and the head is
walked from the carried open turn per delta. The Map keeps its identity
while the head adds no completed turn.
Adds identity-contract tests and a vitest bench for the per-delta
pipeline. 200 deltas over a 2,000-item timeline, mean per run:
default mounted window 5.68ms -> 2.82ms, history fully revealed
16.95ms -> 1.81ms.
Fixes#4816
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* perf(app): keep the stream view's per-delta work bounded by the head
The chat outline concatenated tail and head, reduced over the result in
the render body, and rebuilt jumpToPrompt on every delta even when the
outline was disabled, which is every native viewport. Memoize the tail's
contribution, scan only the head per delta, do nothing while disabled,
make jumpToPrompt a stable event so the rail's memo holds, and republish
the active prompt only when the index or reading position changes.
The view built a Set of every mounted id plus the live head per delta for
the outline's pending-jump check; key it on mounted history only and
treat live head rows as always mounted. Split plugin projection so a head
change does not re-project the tail, memoize the history-window boundary
lookup, and read the turn presentation's submission flag with some()
instead of allocating a filtered array.
Refs #4816
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(app): confirm full history before measuring streaming
---------
Co-authored-by: Ivan Malison <IvanMalison@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): focus chat feed when clicking message content
* test(app): wait for new chat output before checking scroll position
Co-authored-by: Ivan Pantic <panta@talentkit.io>
* test(app): capture the keyboard paging regression
* test(app): verify paged chat text stays in place
---------
Co-authored-by: Ivan Pantic <panta@talentkit.io>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): preserve usage provider order across refreshes
* test(usage): keep refresh and scrolling journeys stable
Usage reports can follow the long fixture card, so a fixed wheel distance
can scroll past its final window. Target the named windows with wheel
input while preserving the visibility checks. Assert refreshed provider
order through visible names and keep viewport setup in journey helpers.
Co-authored-by: Sleroq <hireme@sleroq.link>
---------
Co-authored-by: Sleroq <hireme@sleroq.link>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): refine provider catalog actions and error feedback
Use compact row tiles and the shared error toast so failed Add actions remain visible while browsing the catalog. Point Hoplite installation instructions at the CLI install section.
* test(app): supply toast context in provider settings tests
Import the toast API independently of its renderer so the provider section only depends on the context it consumes.
Preserve the bundled Hoplite ACP adapter and provisioning transformer, activate it only after Add, and provide a searchable provider grid. Restore the contributor's original icon without the copyright mark.
Co-authored-by: Odin Glynn-Martin <odin.glynn@icloud.com>
* Put sheet tabs on the one sheet stack
Sheets are one primitive with one stack. A sheet opened from any sheet
stacks on top of it, and closing it uncovers the sheet underneath. The
compact workspace's file and subagent sheets were an in-tree Gorhom
BottomSheet with their own stacking, so a subagent chosen from the
subagents list sheet replaced the list instead of covering it.
- SheetHostScope: a subtree hosts the sheets presented inside it, so the
sheet tabs draw inside the compact column and read the workspace
providers while still joining the app-wide stack. Needs Gorhom's
BottomSheetModalInternalProvider, which the patch exports.
- IsolatedBottomSheetModal takes enableDismissOnClose; false keeps the
content mounted through a close so the unsaved-changes guard can veto
it. Its backdrop then closes the sheet rather than dismissing the top
modal. The Gorhom patch also keeps a dismissing modal's status through
its final onChange(-1), without which such a modal unmounting while
presented never left the portal.
- SheetTabs (was WorkspaceSheetHost) renders one IsolatedBottomSheetModal
per store entry. The store never reorders sheets; a reveal of a covered
sheet drops the unmodified sheets above it.
- Subagent rows that open a sheet keep the list open (closeOnSelect
false); rows that open a tab still close it.
- A menu closes when its retained panel goes inactive, instead of hiding
and reappearing with the panel on the next tab switch.
Co-Authored-By: Claude <noreply@anthropic.com>
* Show an agent's model and thinking in its sheet, and fit Open as tab in the header rail
A Paseo agent shown in a sheet has no composer, so the facts the composer's
model and thinking controls would show now sit in one line under the sheet
header, the same row provider subagents already get from the daemon. The line
resolves exactly as the controls do (resolveAgentModelSelection), from the
session agent and the providers snapshot, and shows wherever the pane host is
a sheet.
Open as tab becomes the full-screen glyph beside Close, drawn by the shared
SheetHeaderIconButton, so the title keeps its width. The tab switcher sheet is
titled "Workspace tabs" so it reads as what it lists.
Co-Authored-By: Claude <noreply@anthropic.com>
* Show mode and context usage in the sheet facts line
The line under a sheet's header now carries everything the composer's
controls and context meter show: model, thinking, mode, and used/max context
tokens with the share, each resolved as its control resolves it.
Co-Authored-By: Claude <noreply@anthropic.com>
* Re-render subagent rows when the workspace's tabs change
A row decides at render time whether choosing it opens a sheet or reveals
a tab, and keeps the list open only for a sheet. The hook behind it only
subscribed to the layout mode, so a row rendered while its agent was a
full tab kept closing the list after that tab was gone: on iOS, promoting
a managed child, closing its tab, and reopening it from the list dropped
the list under the sheet. Subscribe to the workspace's layout too.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Separate composer keyboard eligibility from pane focus restoration. A sidebar dismisses the keyboard without hiding the pane, so its close must not restore editing focus.
Decode assistant Markdown image destinations before resolving a filesystem read. Keep plain filesystem paths and direct image URIs unchanged so Markdown Preview and literal percent filenames are decoded only once.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Stack workspace sheets and tighten sidebar density
- Workspace sheets stack: opening a target from a sheet covers it, Back and
drag close only the top sheet, and the header is the sheet's drag handle.
- The workspace sheet reuses the shared sheet handle, background and header.
- Fix the shared sheet handle indicator rendering black.
- Sidebar text returns to semantic sizes; Pinned and Workspaces share one
section header.
* Pause covered workspace sheets and mock the shared sheet handle in tests
* Export BottomSheetHandle from the browser test Gorhom stub
* Let a stacked sheet's backdrop catch taps on iOS
* Bring a revealed sheet to the top instead of duplicating it as a tab
Unfinished CSS strings in font preferences consumed the shared theme stylesheet. Reject incomplete syntax at the settings boundary, recover persisted malformed values through the existing default path, and restore rejected edits in the font field.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Let readFile callers disable the request timeout
readFile resolves only when FileEnd arrives, so the default 60 s request
timeout bounds the whole transfer. A large file over a slow relay fails
before it finishes even though every chunk is arriving. Thread an optional
timeout through requestFileExplorer; 0 disables the timer, as the
workspace.create request already does, and a dead connection still rejects
on disconnect.
The relay e2e case reads a 1 MiB file through a real daemon and a local
relay with timeout 0 and compares SHA-256 digests with the source.
* Reject a binary file transfer whose byte count differs from FileBegin.size
concatByteChunks allocated FileBegin.size bytes and copied the chunks in:
a short transfer resolved as a zero-padded file, and a long one threw a
RangeError inside the transport callback and left the request pending.
Count the received bytes at FileEnd and reject the read when they differ
from the announced size.
* Download files over the session socket when no direct connection is active
The download store only knew the HTTP token route and picked the first
directTcp connection in the host profile, whatever connection the host
runtime was using. A relay-only host has none, so every download ended in
"Download host is unavailable." (#543). SSH, socket and pipe hosts failed
the same way, and a stale directTcp entry won over a working relay (#3753).
Choose the transport from the active connection: directTcp keeps the HTTP
token download unchanged; anything else reads the file over the session
socket with the binary frames previews already use, then hands the bytes
to the browser's download manager on web and Electron or writes them to
the cache directory and offers the share sheet on native. Files over
64 MiB are refused before any byte moves, using the existing fileTooLarge
string, and a daemon response without content fails instead of saving an
empty file.
* fix(app): enforce the session download size limit during the read
The token size is sampled before the file is read. Pass the download byte budget through the actual read so a changed file cannot bypass the in-memory limit.
Co-authored-by: Michael Vandesompel <mv@droggol.be>
Co-authored-by: YASICJUNWOO <joonoo3@naver.com>
Co-authored-by: JrDw0 <JrDw0@users.noreply.github.com>
* test: verify TCP downloads and own relay fixture cleanup
---------
Co-authored-by: Michael Vandesompel <mv@droggol.be>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: YASICJUNWOO <joonoo3@naver.com>
Co-authored-by: JrDw0 <JrDw0@users.noreply.github.com>
Relative navigation shared the list capped for digit shortcuts, so it wrapped at the ninth workspace. Keep all sidebar targets and cap only numbered badges. Carry forward #4128 with keyboard-routing and browser regressions.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Raisal P Wardana <IEatCodeDaily@users.noreply.github.com>
* fix(app): keep long custom models removable
Custom model labels and IDs could consume the row width and push Remove outside the sheet. Allow those text cells to shrink while preserving the button width and short-name alignment.
* test(app): wait for legacy agent creation before checking prompts
Agent-list membership includes initialization. Waiting for finish at that point can return on the initial idle transition before the initial prompt starts. Wait for the matching creation response before checking each completed turn, preserving the existing exact-once assertions.
* test(app): wait for directory search keyboard focus
The search page becomes visible before its deferred focus callback runs. Wait for the input to own keyboard focus so the journey types its query rather than dropping it during navigation.
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Show Claude option previews in the question card
* Wrap long lines in question option previews
* Check option previews under the option that owns them
* Show the whole option preview on native so every line is reachable
On Android, a slow drag inside a preview capped at 240px did not scroll
it, so lines past the cap could not be read. The preview's nested
scroll sits inside the option's Pressable; the same drag scrolls it
once the preview is outside the Pressable. Native now renders the
preview at full height so the chat scroll reaches every line, and a tap
on it still selects the option. Web keeps the 240px cap.
The mock agent's rollout preview is now taller than the cap so the
Android replay can check the last line.
---------
Co-authored-by: Gabriel Giordano <hello@gabrielgiordano.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* feat(app): open files and subagents in a closable sheet on compact
On phones, opening a file, a provider subagent, or a Paseo subagent now shows it
in a bottom sheet over the current tab instead of adding a tab, so the tab strip
stays for terminals and root agents. Paseo subagents get "Open as tab" to move
the same tab (same tabId and state) into a full tab with a composer; agents in a
sheet hide the composer.
The decision lives in the workspace layout store's openTab, driven by a
per-kind `compactSheet` rule in the panel manifest, so every open path (chat
links, Explorer, subagents track, notifications, plugins) follows it without
call-site branching, and a new kind opts in with one manifest entry. The app
shell publishes the layout mode (compact / single-pane / split-panes) once, so
callers stop passing isCompact for placement.
A sheet holding unsaved edits is never implicitly displaced; later opens go to
tabs underneath, and closing it goes through the existing discard guard. The
editor host is position: sticky because CodeMirror's scrollIntoView otherwise
scrolls the bottom sheet's container and drags the sheet to full height.
* test(app): fail the compact sheet snap check when the sheet is oversized
---------
Co-authored-by: paseo-bot[bot] <paseo-bot[bot]@users.noreply.github.com>
* Keep pairing QR code within compact tile
* Move pairing screenshot after viewport assertion
* test(pairing): verify the complete QR code on Android
The native regression failed with the fixed-size SVG because the code was clipped at both screen edges. Verify the complete square and visible margins through the Android app, and keep browser geometry assertions in the pairing helper.
Co-authored-by: fdkevin <25277753+fdkevin0@users.noreply.github.com>
---------
Co-authored-by: fdkevin <25277753+fdkevin0@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): only offer Remove for providers defined in config.json
Plugin-registered providers (Antigravity, Muse Code) share source: "custom"
with config-defined ones, so Settings → Providers offered Remove on them.
The removeProviders patch only deletes config.json entries, so the action
succeeded silently while the plugin re-registered the provider.
Gate the Remove menu on the provider having an extends entry in config —
the actual definition custom providers live behind. Plugin providers keep
their enable/disable switch.
Fixes#6349
* Address review: named ownership check, E2E coverage for plugin providers
- Name the config-owned provider check instead of combining three
conditions inline in canRemove.
- Replace the JSDOM component tests with a Playwright case in the
existing provider-removal suite: a plugin-registered provider renders
without the Remove action and survives a removeProviders patch.
* fix(app): render local relative images in markdown file preview
File preview dropped relative images because it only accepted http/data URIs and never resolved paths against the opened markdown file.
* test(app): cover local relative images in markdown preview e2e
* fix(app): show local images in Markdown file previews
Carry forward document-relative image loading and preserve the host home
directory when resolving images beside home-relative Markdown files.
Decode file URIs once, and require decoded images in the file-pane regression.
Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com>
* fix(app): resolve local Markdown image URL suffixes
---------
Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): sync Android system bar icon style with app theme
With edge-to-edge enabled the app draws under the Android system bars,
but the bars keep their default light icon style regardless of theme,
so the status bar clock/battery/icons are invisible on light themes
(white on white). Call SystemBars.setStyle from the appearance provider
whenever the theme is applied: dark icons on light themes, light icons
on dark themes, auto following the system for adaptive themes and
plugin-contributed themes.
Refs #1272 (runtime only; the splash screen phase is not covered).
* fix(app): keep Android status icons readable as themes change
Keep system-bar contrast subscribed to the resolved app theme so automatic appearance changes update the icons as well as the app surface. Add a native regression covering explicit and automatic themes.
Co-authored-by: TeraYang <4110825+terayang@users.noreply.github.com>
* fix(app): map system-bar appearance through color scheme
Unistyles reserves style mappings for React Native style objects. Keep SystemBars style selection inside its appearance adapter and map the colorScheme prop instead.
---------
Co-authored-by: TeraYang <4110825+terayang@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): jump the chat outline to prompts a timeline plugin draws
A timeline transformer replaces a stream item with plugin rows whose ids
are "<pluginId>/<itemId>". The chat outline, chat find and history reveal
look a message up by the source item's id, so once a plugin drew the
prompts the outline found no row: the rail showed the prompts and
clicking one did nothing, for mounted, virtualized and unloaded prompts
alike.
Projected plugin rows now carry the id of the item they replaced, and
getStreamItemMessageId answers with it. Row wrappers, the virtualized
index lookup, the visible-message set and history reveal all read that
function, so they now find the plugin row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(app): verify outline jumps to virtualized plugin prompts
Replacing every prompt keeps all loaded rows mounted, so the original plugin regression never exercised a virtualized target. Retain newer native prompts and verify that a loaded unmounted plugin prompt lands without fetching.
Co-authored-by: luopeihuan <luopeihuan@roundreddot.com>
* test(app): retain outline navigation screenshots
---------
Co-authored-by: luopeihuan <luopeihuan@roundreddot.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* feat(app): add fullscreen editing to the compact composer
* fix(app): preserve read-only terminal command previews
Static command previews must render their published source; the live editor buffer is updated after render and has no mounted input to repaint read-only content.
* fix(app): import React for composer editing scope rendering
Workspace pane unit tests use the classic JSX transform, so the editing-scope provider needs React in scope when mounted.
* fix(app): preserve fullscreen composer controls and editing
Keep the fullscreen frame inside the visual viewport, gate hidden autocomplete keys, apply replacement commands to local line measurement, and return to inline before starting voice so its controls stay accessible.
* test(app): cover draft replacement through the production composer
Replace the measurement-hook fixture with profile switching and typing through the real composer. Keep measurement state inside its production surface; Android manual red/green evidence covers the native replacement flow.
* fix(tests): run React tests from production shells
Vitest preserves an inherited NODE_ENV, selecting React without its test APIs. Pin the environment before dependency resolution and worker startup in both test entry points.
Co-authored-by: Ivan Malison <IvanMalison@gmail.com>
* test(app): expect zero edits for a pure staged rename
The rename patch correction on main preserves unchanged content. Keep the discard journey intact and update its obsolete added-line assertion.
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Ivan Malison <IvanMalison@gmail.com>
* fix(app): decode percent-encoded assistant file links
markdown-it percent-encodes non-ASCII and space characters in link
hrefs, so a link like [x](docs/reports/报告.md) arrives as
docs/reports/%E6%8A%A5%E5%91%8A.md. The file:// and bare absolute-path
branches already run safeDecodeURIComponent, but the workspace-relative,
path:line, Windows, and home-relative branches did not, so clicking the
link tried to open the still-encoded name and failed with ENOENT.
Decode the path portion before the relative-path heuristics and in the
inline absolute/Windows branches so all href forms behave the same.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(app): decode hrefs only, never raw inline-code paths
Raw inline-code and linkified tokens reach the same parser as markdown
hrefs. Decoding unconditionally would send a real file named
my%20file.md to my file.md. Gate every decode on AssistantHrefParseOptions.decodeHref — set by the resolver only when the token
came from source.href — and extract parseWindowsFileLink to keep the
entry point under the complexity limit. Adds parse-level and
resolver-level regression tests for literal %20 filenames.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(app): keep raw absolute paths out of URL parsing
new URL() percent-encodes non-ASCII, so a raw inline-code path like
/tmp/开户.md became /tmp/%E5%BC%80%E6%88%B7.md when decodeHref was
false. Hrefs still go through URL parsing (normalizes dot segments
before decode); raw paths split the # fragment manually instead.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(app): keep encoded and literal file links distinct
Preserve inline-code provenance for Windows paths and key cached file lookups by their resolved query, path, and line range. Reproduce the reported non-ASCII Markdown link through the renderer and filesystem, with browser coverage for clicking it.
Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com>
Co-authored-by: dengjiawen <dengjiawen@hortorgames.com>
* test(app): assert file content after opening the markdown link
Co-authored-by: sunshinehomeyu-blip <267551119+sunshinehomeyu-blip@users.noreply.github.com>
* fix(app): preserve encoded line markers in filenames
---------
Co-authored-by: Ouyang Xingyuan <galaxyroaming@qq.com>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: dengjiawen <dengjiawen@hortorgames.com>
Co-authored-by: sunshinehomeyu-blip <267551119+sunshinehomeyu-blip@users.noreply.github.com>
* feat(app): modernize compact headers and sidebar affordances
Use a shared island header with quick tab creation and keep sheet footers inside their visible layout. Opt sidebar rows into touch sizing without changing global appearance settings.
* test(app): match darker light-theme workspace selection
* test(app): align plugin journeys with compact header
* fix: prevent file mention autocomplete from triggering on emails
Skip @ when preceded by a word character or +, and reject angle
brackets in mention queries so email addresses no longer open the
file autocomplete or steal Enter.
Closes#1364
* test(app): verify emails send intact without file completion
The word-character and plus guard tests previously passed because their queries included trailing spaces. Exercise each guard with the cursor at the end of the email, and cover Enter in the browser alongside normal file completion.
Co-authored-by: Jason <mildreins@gmail.com>
---------
Co-authored-by: Jason <mildreins@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* feat(review): edit compact diff comments in a sheet
* feat(review): send diff feedback to workspace agents
Send saved reviews from the diff while retaining file navigation and unrelated composer drafts. Clear acknowledged versions only so comments edited or added during submission remain available.
* fix(review): preserve drafts and coordinate feedback across surfaces
Share pending submission ownership by draft while keeping recipient menus local. Retain editable text across presentation changes and acknowledgement, and reserve safe-area clearance for floating actions.
* fix(review): center a small primary feedback button on desktop
* fix(review): restore focus to feedback button after closing menu