Recognize Android certificate-path validation failures before the generic not-found classification, so Direct connection no longer tells users to debug DNS for an untrusted certificate. Keep the existing TLS copy and technical details.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Retained panels keep their children mounted, so mount-only registration kept hidden rings subscribed to the shared UI clock. Detach inactive rings and retain a local phase so hidden animated styles also stop receiving updates while visible rings continue.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Pete Keen <pete@petekeen.net>
* perf(app): load translations when their language is selected
Eager locale imports evaluated and registered all nine translation bundles on every cold start. Load bundled locales through a synchronous i18next backend so language selection keeps its before-render contract and offline behavior.
Co-authored-by: Ivan Malison <IvanMalison@gmail.com>
* test(app): normalize translation entry paths across platforms
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Ivan Malison <IvanMalison@gmail.com>
* fix(android): align Unistyles NDK version
* fix(android): use the project NDK for every native module
The diff module still selected the Android Gradle default NDK, causing a second installation after Unistyles was aligned. Inherit the project toolchain and check all configured native projects.
---------
Co-authored-by: dwyanewang <908280968@qq.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(workspaces): observe setup output only in the visible setup panel
Setup logs were requested for the whole session, including unrelated workspaces. Use directory status for discovery and source-owned workspace observations for details, preserving legacy delivery only for clients without the negotiated capability.
* fix(workspaces): publish external setup progress once per daemon
MCP and scheduled setup enter through the broadcast API. Publish them to the shared setup owner before session fanout so each observation gets one update and snapshots survive periods without connected clients. Keep the setup action available on older hosts to explain the required host update.
* fix(app): replace setup details with each observation snapshot
A host restart can return an empty setup snapshot. Apply that authoritative snapshot so an open panel does not retain output from the previous daemon. Assert setup membership requests as well as delivery in the browser lifecycle regression.
* test(workspaces): cover setup status catch-up after directory release
* test(workspaces): synchronize setup output and release fixture resources
* fix(app): reveal Android settings inputs above the keyboard
PageLayout kept its full-height scroll viewport while the keyboard controller handled Android insets, leaving Code size covered. Use the existing keyboard-aware scroller for Android pages and cover the real focused field and keyboard geometry in the mobile regression harness.
* fix(app): keep settings field frames above the Android keyboard
Keyboard scrolling revealed only the native text input, leaving the enclosing field border and padding covered. Allow theme spacing around the input and assert the visible Plugins source frame against the real keyboard.
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Tab and workspace navigation need a final 1–9 index. Prevent saving an incomplete recorded sequence and explain the requirement while preserving arbitrary chord prefixes.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Every contribution `icon` now takes a Lucide name, `{ svg }`, or a
component. Attachment sources take only the first two because composer
drafts persist the source icon.
The daemon's provider-icon check and the app's inline-SVG check are the
same function in the shared SDK root, so a plugin sees the same reasons
in both places. The app parses each icon once at registration and fails
the plugin with that reason. Rendering goes through one module that
turns any form into a component, so surfaces never branch on the form.
* perf(app): throttle workspace layout writes and skip no-op persists
Zustand persist calls setItem after every set, even when the updater
returns the same state. The layout store re-normalized every workspace
tree, re-validated the envelope, stringified it, and wrote to
AsyncStorage on each agent status change because reconcileTabs runs from
an effect keyed on agent visibility.
The draft store's throttled writer moves to storage/ as a generic
throttled persist storage, gains a referential short-circuit for a value
whose partialized state was already accepted, and now backs both the
draft and layout stores. The layout store's partialize reuses its
previous projection while every persisted slice is the same reference,
and the store is flushed alongside drafts when the app leaves the
foreground.
Before: 50 no-op reconciles produced 50 writes. After: 0 writes; 50 real
sets inside one interval produce 2 writes (leading and trailing).
Fixes#4814
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(app): build layout stores with options and flush before rehydrating
Six call sites still passed the id factories positionally, which this
branch replaced with an options object, so those stores silently used
default ids. The once-only PR test also rehydrated a second store before
the throttled write landed; flush the source first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(app): avoid shadowing layout options
---------
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>
The compact Changes toolbar flattens every git action into one menu, and
`secondary` keeps the actions the policy can promote to `primary`. When the
policy promoted one of them - Pull behind origin, Push ahead of it, the merge
and PR actions - it rendered twice, under the same React key.
Dedupe by id in `actionsWithoutPrimary` so the split button and the command
center share one rule, cover it in policy.test.ts against real buildGitActions
output, and pin the rendered menu in a compact Changes browser spec.
(cherry picked from commit 9bb26cc471)
Co-authored-by: az33zy <10743275+az33zy@users.noreply.github.com>
* fix(app): dismiss touch composer focus when switching tabs
A retained tab can hide and return before Chromium paints, preserving the focused textarea without another focus call. Blur web touch editors when their pane becomes inactive so returning leaves the conversation ready to read. Exercise the same mounted editor through a second edit and keyboard navigation cycle.
* test(app): reproduce touch tab focus race before paint
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
The subagent track rows were as dense on a phone as in the desktop popover,
and the archive and detach actions were hand-rolled pressables that only
recoloured the glyph, so a tap or hover never showed the rounded highlight
every other icon action has.
Rows now take the compact menu-row height and padding below the `md`
breakpoint, decided once in the track row style; the desktop popover keeps
its density. The actions are `ToolbarButton`, which owns the hitbox, the
rounded highlight on hover and press, the compact size, and the tooltip.
A repo oxlint rule, `paseo/no-raw-pressable`, reports `Pressable` and
`Touchable*` imports outside `components/ui/` so the next tap target reaches
for a primitive. Existing importers are on a burn-down list they can only
leave. The rule is its own name rather than another `no-restricted-imports`
entry so its override cannot interact with the existing burn-downs. Its test
runs the real oxlint binary and is wired into the CI quality job.
* fix(terminal): select complete Korean words on long press
Wide-character continuation cells carry a blank character but occupy no extra glyph. Treat only actual cells as word separators so either half selects the complete word while real spaces still delimit it.
* fix(terminal): preserve selected words until a drag begins
An unmoved touch release contracted the word selected by the long-press timer. Only extend selection after crossing the existing drag threshold. The Android regression checks the copied whole word after the finger lifts.
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Hold the stream through the click so autoscroll cannot move the group between pointer-down and pointer-up. Resume after expansion and verify that inline calls keep updating.
The shared MD4C permissive autolink scanner consumed trailing path periods and rejected colon boundaries. Fix recognition in the dependency patch and rebuild its web parser so native and web agree. Preserve balanced path parentheses and explicit destinations.
The Markdown DOM container bypasses React Native Web style prefixing. Safari inherits the gesture host selection suppression unless the selectable boundary also sets WebkitUserSelect. Keep container styling in the existing style configuration so its selection contract is covered by the renderer tests.
* perf(app): skip workspace agent visibility rebuild on stream ticks
The workspace screen derived agent visibility on every session-store
update: it copied every agent and agent detail into a new Map, walked
all agents, and allocated two Sets, then compared them. Stream ticks
update the store but leave the agent maps untouched, so this was pure
waste at stream rate.
createWorkspaceAgentVisibilitySelector returns the previous result until
session.agents or session.agentDetails changes, and keeps it when a
recompute yields the same sets. Parent lookups read the two maps
directly instead of merging them.
Fixes#4817
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* perf(app): skip subagent row rebuild on stream ticks
Every mounted agent panel selected its subagent rows by walking all
session agents, allocating rows, sorting, and deep-comparing on every
session-store update, including every stream tick.
createSubagentsForParentSelector returns the previous rows until
session.agents changes, and keeps them when a recompute yields equal
rows. The hook rebuilds the selector when the parent or the pending
archive ids change.
Refs #4817
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* perf(app): check pending submissions without allocating
selectAgentTurnPresentation runs from several session-store selectors
and filtered the submission list only to read its length. Use some().
Refs #4817
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Ivan Malison <IvanMalison@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* Add missing monochrome provider icons (kiro, devin, hermes, minimax-code, traecli, grok, deepseek-tui)
* fix(app): add Kiro catalog icon and Antigravity builtin icon
The ACP catalog's kiro entry had iconId null and no vendored svg, and
the native Antigravity provider resolved to the Bot fallback before the
daemon snapshot arrived. Vendor the Kiro brand mark, wire iconId, and
register a builtin AntigravityIcon matching the plugin's theme-aware
icon.svg.
* fix(app): keep Kiro eyes in monochrome icon
* test(providers): cover catalog artwork and snapshot-free icons
Lock down the missing provider marks through the catalog and the shared icon hook. Exercise the rendered catalog artwork in Chromium alongside coverage for the pre-snapshot fallback.
Co-authored-by: kevenhu001-cyber <keven.hu001@gmail.com>
Co-authored-by: Lu <jack1125516912@outlook.com>
* fix(website): preserve unrelated agent directory artwork
Drop the unrelated website mark introduced with the provider artwork. Cover the existing directory fallback and retained provider mark in a browser regression.
---------
Co-authored-by: kevenhu001-cyber <keven.hu001@gmail.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Lu <jack1125516912@outlook.com>
* fix(app): wrap sidebar empty-state actions so longer labels stay inside the card
The "Add project" / "Import session" row in the sidebar's empty state is a
non-wrapping flex row. With longer translations (e.g. Russian) the second
button overflows the card at the default 320px sidebar width.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(app): cover sidebar empty-state actions staying inside the card
Renders the empty state in a real browser at the default sidebar width and
checks both actions' bounds against the card, for Russian (fails without the
wrap) and English.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(app): keep long sidebar actions inside narrow cards
Wrapping the row still lets a single translated action exceed its available width. Bound each empty-state action to the row and allow its label to shrink and wrap.
Co-authored-by: Vyacheslav Scherbinin <gentslava@mail.ru>
---------
Co-authored-by: Vyacheslav Scherbinin <gentslava@mail.ru>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Grouped sidebar projects chose the first eligible host even while a workspace on another host was active. Prefer that active host when online and eligible, keeping the existing fallback for other placements.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Michael Whitman <michael.whitman@getsteward.ai>
* fix(app): report offline hosts in new agent drafts
A disconnected client still exists, and pending form resolution and retained loading catalogs outlive the connection. Use host connection state for draft readiness and model loading while keeping known models available for reconnect.
Co-authored-by: Ivan Malison <IvanMalison@gmail.com>
* test(app): confirm draft recovery on a fresh host connection
---------
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Ivan Malison <IvanMalison@gmail.com>
* fix(scripts): recover installs when a dependency patch changed
patch-package cannot apply a patch over a package patched with an earlier
version of it, and without --error-on-fail it exits 0 locally. Checkouts kept
the stale patch silently: the Markdown code block spacing fix never reached
them. The script now fails the install and removes that package so the next
npm install reinstalls it clean with the current patch. A patch-package that
cannot start fails the install without removing anything.
* fix(app): give Markdown blocks one gap and roomier desktop spacing
Every Markdown block is followed by one gap, density.blockGap: 12 on compact
layouts and 16 wider. The library's element styles and the views stacked
outside it (Mermaid segments on native, streaming head and tail) take the same
token, so no block type carries its own margins. Desktop was too tight at 12.
Markdown images show a zoom-in cursor on web, since tapping opens the lightbox.
Keep drag widths out of the shared stylesheet and skip unchanged panes during width previews. Preserve the existing resize state and persistence behavior.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Ivan Malison <1246619+colonelpanic8@users.noreply.github.com>
* feat(app): rank, highlight, and timestamp Command Center results
Workspace and agent rows now show when they were last active, rank by
match quality and then recency, mark the query terms in every result
kind, and lead workspaces with their project icon.
Ties used to be broken by where the match landed in the string, so
"Deliver sidebar usage" beat "expose usage data" on sentence position
and recency almost never decided anything. Built-in rows now compare
only which fields matched and the matcher tier, then the most recent
row wins. The empty query follows the same rule, so it lists the most
recent workspaces and agents instead of alphabetical workspaces and
status-first agents.
Row and section heights live in list-metrics: a layout picks one row
inset and every height derives from it plus shared line boxes, which
the styles also read. The layout is chosen once at the top, so rows
never branch on it, and two-line rows are now vertically centered.
Desktop is denser (32/46); compact keeps 44pt touch rows (44/58).
findHighlightRanges takes the matcher's options so the marks match
what the list matched: the Command Center is not typo-tolerant, the
history list is.
* fix(app): keep the project icon out of Command Center row names
The fallback project icon draws the project's initial as text, which
became the first word of the row's accessible name ("C Command Center
..."), so screen readers announced it. The icon is decorative.
The scroll spec now reads the newest seeded workspace: the empty query
lists the most recent workspaces, so the first one seeded is no longer
among them.
Paseo subagent rows in the subagents list had an empty info strip: the row
only rendered the daemon-provided subtitle, which only provider subagents
carry. The row now uses the same facts line an agent's sheet shows when it
opens without its composer, so both surfaces resolve model, thinking, mode,
and context the way the composer's controls do.
The facts sit under the title instead of beside it, so a long task name
keeps the row's width instead of fighting the strip for it.
Paseo tool rows put the variable part first ("QA messenger finished"), so
truncation hid the fixed part, and MCP calls showed only the humanized tool
name with no context. Every Paseo tool now has one catalog entry in
protocol/paseo-tool-presentation owning its fixed label, the input field
shown as context, and its detail layout. Agent-message events read
noun-first ("Agent finished" + sender) and use the Paseo icon; the app
ignored it before because the wire detail's legacy "bot" icon won.
Summaries are capped in code at 120 characters because prompts can be one
enormous line; the renderer is not trusted to clip them.
The activity curator behind `paseo logs` and get_agent_activity printed a
bare "[Agent message]" because the server tool-call type never declared the
agentMessage field the projection sets; it now prints the event, sender,
and body.
Two causes, both reproduced on a clone of a real long thinking-partner chat
(replies and thinking rows several screens tall, GFM tables).
1. The window lowered in e42771970 (initial 2 / batch 2 / window 5) holds only
two or three tall rows, so a fling outruns the mounted range and the viewport
crosses unmounted rows: 65 blank frames over a 4,152-frame traversal. Keep the
small initial render that bounds cold-open measurement, but refill a window of
10 in batches of 10: 0 blank frames over 4,347, p95 25 ms.
2. Scroll-to-bottom from deep history animated across every row in between,
which no finite window can mount in time: 9 blank frames (~150 ms) per jump.
Animate only when the current offset is within the bottom-side overscan,
otherwise jump instantly. Near-bottom jumps still animate. Web already does
not animate this jump.
Cold open on the same chat is unchanged (first content 569 -> 568 ms); the
deeper window costs first-scroll acknowledgement (2.58 -> 2.94 s in a debug
build).
* fix(markdown): restore link hover underline and code block spacing on web
The web renderer swap dropped both. Links: the library styles anchors
inline with text-decoration: none, so the link renderer has to track hover
itself; a stylesheet :hover rule cannot win. Spacing: the patched
renderers.codeBlock override returned the custom block bare, so it lost
the library's codeBlock margins and whatever followed sat flush against
it. The override now keeps those margins, including the zeroed trailing
margin of the last block, so web and native share one spacing config.
* test(desktop): expect Find to return focus to the chat viewport
#7263 moved focus on closing Find from the no-drag focus scope to the
scroll viewport so Page Up and Page Down keep working. The viewport emits
no window region by design (#5233), so the old assertion that the focused
element is no-drag failed on every run. Assert the new focus target and
re-check that the titlebar still has no drag exclusions.
* fix(usage): retain last successful data when polls fail
Keep the latest poll outcome separate from its successful snapshot so errors remain visible without discarding known usage or advancing its freshness. Preserve attempt timestamps for response ordering and isolate retained values by scoped login set.
* fix(usage): preserve history through discovery failures
A failed account-store read does not confirm that an account was removed. Retain its existing host mapping until successful discovery replaces it, without carrying mappings across agent launch environments.
Closing Find focused its non-scrolling wrapper, leaving Page Up and Page Down unable to move the feed. Return focus through the viewport handle while preserving the reading position.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Include the leading cell when a selection starts on a wide character continuation, matching the native renderer highlight. Keep continuation cells out of copied text so real spaces remain unchanged.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
A catch-up projection could replace the live reply while leaving its cached prefix in the committed rows. Completion then discarded the complete reply as a duplicate. Reconcile the covered cached prefix together with the live reply.
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(terminal): preserve Korean text when copying native terminal cells
* fix(terminal): omit continuation cells from copied text
Native terminal buffers already retain authoritative cell widths. Skip zero-width continuation cells when serializing a selection so Korean text copies with only its real spaces, while preserving the buffer representation.
Co-authored-by: helloimdevman <333048235+helloimdevman@users.noreply.github.com>
---------
Co-authored-by: helloimdevman <333048235+helloimdevman@users.noreply.github.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): find text in rows a timeline plugin draws
Chat find located a replaced message by its source id but counted matches
only inside elements marked as message text, which a plugin renderer never
sets, so a replaced prompt or reply reported no matches. Mark plugin rows and
search the whole row, with each view bounding a match.
* docs(plugins): qualify search in replacement timeline rows
Chat find discovers source-message matches before inspecting a replacement renderer. Document that renderer-added text is not independently indexed.
Co-authored-by: a-grasso <antonino.grasso@device-insight.com>
* fix(app): find visible text within contiguous plugin blocks
A whole-row walk counted closed popover text and reunited text before and after nested blocks. Keep consecutive block groups and exclude plugin text the browser is not drawing.
---------
Co-authored-by: a-grasso <antonino.grasso@device-insight.com>
Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
* fix(app): restore inline-code file path links
Classify complete inline-code spans with the file-link classifier before adapting them to the Markdown engine recognition pattern. Keep bare-text heuristics separate so route punctuation and line fragments work without adding prose false positives.
* perf(app): avoid parsing inline-link source on every reveal
Collect candidate values between equal-length backtick runs and let the Markdown engine own code boundaries. This preserves classifier decisions without reparsing long fenced blocks in JavaScript during streaming.
* fix(app): keep the compact sidebar close button beside the first nav row
The close button was absolutely positioned over the nav group, so it covered
the first row's label and hover fill. It now shares a flex row with the first
item, which shrinks to make room, and uses the compact header's icon button so
it gets the same size and round press highlight.
* test(app): cover the compact sidebar close button beside the first nav row
* Redesign the compact New workspace form as full-width field triggers
Each compact field is one full-width trigger with its label inside, above the
value, so the whole field is the tap target and presses highlight. The stack
stays anchored on the composer with Isolation and Branch rows reserved, so
nothing shifts as they come and go. Import session moves into the compact
header, and the title is gone; the fields name themselves.
The form's presentational code moves out of new-workspace-screen.tsx into
screens/new-workspace/form, where desktop badges and compact fields share one
FieldTrigger.
* Restore the desktop badge pressed colour
Main pressed badges to surface0; the field-trigger extraction folded it into the surface2 hover state.
* Use base text size for compact field values
docs/design.md sets interface text at fontSize.base; hierarchy comes from the muted label above.
An image alone on its line, inside a paragraph with other lines, rendered inline
on iOS and web: the library only treats sole-child paragraph images as blocks,
and with hardSoftBreaks every newline is a break node. The source adaptation now
gives such images their own paragraph (keeping quote markers and list
indentation), so all platforms render them as blocks with image margins. Images
sharing a line with text stay inline. Verified through the library's md4c wasm
parser in the renderer tests.
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>
The compact composer used a hardcoded box shadow shared by both themes,
so it read as a heavy band on light backgrounds. It now uses the shared
sm shadow token, whose light value is raised from 0.02 to 0.08 opacity
so the lift stays visible. Dark sm is unchanged.