mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 08:02:21 +00:00
024010ecfc91b47e98cd89c75681fa4401d99558
16
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
024010ecfc |
feat(native-chat): read an open reasoning block through the one live "Thinking" line
While the agent's open reasoning block has text, the turn's live activity line is its disclosure: collapsed by default, expandable to the live text (capped and scrollable), and the block's row draws nothing meanwhile. When the block ends, its row appears in place, open if the reader opened it live, because the line and the row read one disclosure key. Which block the line discloses is derived from the line's own render condition, so a row is never hidden while nothing on screen shows it; any other open block (a subagent's, or one a prompt pushed off the line) draws as "Reasoning". Desktop and mobile alike; no host or wire change. |
||
|
|
9f3e184915 | fix(native-chat): route the reasoning glyph through the shared icon names, keep its chevron findable, and match it on mobile | ||
|
|
f39eae6c89 |
Merge remote-tracking branch 'origin/main' into brennanb2025/nc-reasoning-row
# Conflicts: # mobile/src/session/MobileNativeChatMessage.tsx # src/main/codex/codex-structured-item-streams.ts # src/renderer/src/i18n/locales/en.json # src/renderer/src/i18n/locales/es.json # src/renderer/src/i18n/locales/fr.json # src/renderer/src/i18n/locales/ja.json # src/renderer/src/i18n/locales/ko.json # src/renderer/src/i18n/locales/zh.json # src/shared/agent-session-journal-schemas.ts |
||
|
|
c2c7649849 |
fix(mobile): stop Android from selecting words while the chat transcript scrolls (#22871)
* fix(mobile): stop Android from selecting words while the chat transcript scrolls On Android every paragraph, heading, quote, code block and table cell in the native chat transcript was a selectable TextView. Android starts a word selection, with the magnifier, on a double tap or a long press, and two flicks in the same spot while scrolling a FlatList register as a double tap, so scrolling the chat kept selecting words. iOS is unaffected: its UITextView path arbitrates scroll against selection itself. Android now renders transcript text without inline selection: one gate in MarkdownText covers every selectable span, and the user bubble follows it. A long press on a message opens a sheet with "Copy message" and "Select text", the latter a screen whose only content is one selectable Text, so a selection can only start where the user asked for it. The message row owns that sheet and mounts it only while open. iOS and web keep their inline selection and get no long-press handler. Verified on a Pixel 10 Pro Fold (Android 17): an adb double tap on the transcript selects nothing, the same double tap inside "Select text" selects a word, tool rows inside the bubble still expand on tap, and the long press opens the sheet. * fix(mobile): scope the Android selection gate to the transcript and route span long presses Review follow-ups on #22871: - The gate now applies only where `rangeSelectable` is passed (the chat transcript). Task comments and file previews keep their selectable text on Android as before. - On the Android transcript, spans that take taps (links, file paths) also take the row's long press, so a link under the finger no longer swallows the copy/select sheet. - Copied text keeps its whitespace; only whitespace-only blocks are dropped. - The Android markdown test compares `String(node.type)` instead of a type assertion, which the changed-code quality gate rejects. * fix(mobile): route long presses on Markdown images to the row on Android An image block is a Pressable of its own, so on the Android transcript it now carries the row's onLongPress like tappable spans do; a long press on an image opens the copy/select sheet instead of being swallowed. Test extended with an image block. * test(mobile): pin the Android long press from a chat row to its actions sheet The existing row suite runs as iOS, where the bubble has no long press. This one runs as Android: the bubble's long press mounts the actions sheet with the message, the markdown receives the same handler, closing unmounts the sheet, and the user bubble carries no inline selection. * fix(mobile): preserve Android message selection while replies stream --------- Co-authored-by: Neil <neil@stably.ai> |
||
|
|
f199a20c3a |
Preserve OpenCode reasoning and recorded patches in native history (#24790)
* Use bounded OpenCode context for vault session continuation OpenCode database and synthetic row paths are not text transcripts. Use the vault preview or captured pane context, preserving actual transcript paths containing a hash and supporting both OpenCode lanes and Windows paths. Adapted the intent of #11859 and extended it to actual installed v2 vault rows. Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com> * Read real OpenCode sessions in terminal-backed native Chat Reuse the bounded AI Vault SQLite worker for v1 and v2 session pages and live updates. Keep terminal input as the real execution path and pace OpenCode Stop through its two-Escape interrupt. Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com> * fix(opencode): publish approval cards for permission requests * Send OpenCode native approval through its Enter selector * Resolve mobile Chat readability for folder workspaces * Bound OpenCode part batches and preserve v2 image attachments * Prefer live migrated OpenCode sessions over legacy copies * Consolidate mobile Chat eligibility test imports * Consolidate OpenCode SQLite protocol type imports * fix(native-chat): preserve OpenCode reasoning and patch parts Separate genuine reasoning from answer blocks in both native SQLite schemas and retain recorded patches as completed patch tools. Keep each database row together at page boundaries so the existing raw-row cursors cannot drop half of a mixed row. Adapted from @akhan157's OpenCode native history work in #13287 at bb661d10d716764fb472d824cd434678875b1947; retains the current bounded reader and account discovery instead of restoring the older capture and cursor implementation. Verified against genuine private installed 2.0.16 and official 1.18.30 CLI ingestion. Co-authored-by: Adnan Khan <adnank11427@gmail.com> * fix(native-chat): keep split OpenCode rows intact on desktop and mobile Preserve the native reader's bounded OpenCode row groups in paired reads and snapshot/replacement frames so a second presentation-count slice cannot drop reasoning while advancing the database cursor. Sort derived reasoning before its answer under the same provider timestamp while retaining journal order. These two boundaries were reproduced with genuine installed 2.0.16 and official 1.18.30 sessions in a hidden desktop renderer and the current mobile view over an actual authenticated encrypted pairing. Completes the semantic presentation from @akhan157's #13287 without importing its older clipping or cursor implementation. Co-authored-by: Adnan Khan <adnank11427@gmail.com> * fix(native-chat): keep reasoning and answers together in live windows * Bound OpenCode transcript RPC pages and present omission notices * Bound OpenCode transcript RPC pages and present omission notices * Bound OpenCode transcript RPC pages and present omission notices * Update native worker oversized-history notice contract --------- Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com> Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com> Co-authored-by: nwparker <nwparker@users.noreply.github.com> |
||
|
|
213076b9bb |
fix(mobile): label the reasoning toggle and give it a full touch target
The toggle now tells a screen reader what it is, "Reasoning: Thought for 3s", as desktop's prefix does, and reaches a 44 pt target. The shared English copy stays private to the module that formats it. |
||
|
|
171840f100 |
feat(mobile): collapse reasoning rows the way desktop does
With summaries on, every Claude turn now carries reasoning text, and the phone drew all of it inline, dimmed, between the prompt and the answer. Mobile now draws a reasoning row as desktop does: collapsed to "Thought for N s", "Thought" or "Reasoning", its text mounted only once opened, and nothing at all while the row is still being written in the live turn or has no text. The headline and the visibility rule live in one shared module both clients read, so they cannot drift. |
||
|
|
21124db4d5 |
refactor(native-chat): a subagent's rows live in its own section, not in the parent's conversation (#23752)
* refactor(native-chat): a subagent's rows live with that subagent, not in the conversation
A subagent's rows were drawn in its parent's conversation, each captioned with
the subagent's name. They now belong to the subagent: the transcript projection
keeps the session's own rows as the conversation and each subagent's rows apart,
keyed by the agent id its roster entry already carries, folded on their own.
Desktop: a subagent's rows open in a section under the roster row that names it,
from that agent's roster entry, and are windowed like any other rows. A subagent
no loaded roster names opens where its first row happened, inside the section of
the agent that spawned it or in the conversation. Its edits still count in the
turn they were made, and revealing one opens the sections around it.
Mobile shows the conversation, with each spawn's roster line. Worker reads and
structured terminal reads serve the worker's own rows.
Removes what the move makes redundant: the per-row caption and its copy, the
producer check in the tool fold and the turn answer, the per-agent frontier
interleaved in the conversation, worker-text subagent tags, and the agent id on
worker-read messages.
* refactor(native-chat): a diff target names the sections its row sits in
Revealing a subagent's edit opens the sections around it from the target the
rollup already holds, instead of looking the row up at click time. The section
head keeps to the agent's name and dot; its state in words stays on the roster
entry. The worker page test stubs the host through its module rather than a cast.
* fix(native-chat): a working subagent's section is open; a worker page windows its own rows
A subagent's section is open while its agent works and closes once it settles,
the way the turn's own live run does; a section the reader opened or closed by
hand keeps that choice. A subagent another subagent spawned opens inside that
one's section, so a working grandchild shows inside its working parent. Openness
is derived from the roster's state and the reader's choices; nothing stores an
automatic open.
A worker page is now the newest page of the worker's own rows. The host windows
the read over them before the limit, so a subagent's burst can no longer crowd
the worker's rows off the page, and "older" still means older worker rows. The
scope is an in-process argument of the host's history read; no wire request
carries it.
* fix(native-chat): a subagent section head names the turn it sits in, for the outline rail
* fix(native-chat): a subagent section's rows sit in the turn the section is shown in, for the outline rail
A background subagent's rows written during a later turn carried that later
turn onto their slots, so scrolling through its section lit the later turn's
rail tick and then snapped back. The rollup still counts each edit in the turn
it was made; only the slot, which the rail reads, takes the shown turn.
* fix(mobile): Load earlier reads past pages that hold only a subagent's rows
Mobile draws only the session's own rows, so an older page made entirely of a
subagent's rows landed as nothing: the reader tapped Load earlier, saw the
spinner, and got the same transcript back. One load now reads on (up to 8 pages)
until a page holds a row of the session's own, then applies the pages in order.
* test(mobile): stub the RPC client the way the other structured-session hook tests do
* perf(native-chat): order subagent rows for the changed-files rollup once per change to them
The rollup flattened and re-sorted every subagent row on each update, including
every token the parent streamed. The ordering now keys on the projection's
subagent rows, which keep their identity while only the conversation changes.
* refactor(native-chat): order subagent rows in the sections hook, keeping the list under its line limit
* fix(mobile): a transcript whose newest page is only a subagent's rows reads back on its own
Opened while a subagent is busy, the newest page can hold nothing but that
subagent's rows. Mobile draws none of them, so the reader saw an empty chat with
a Load earlier button, and an empty list cannot be scrolled to page. The hook now
reads back once from each such head, and the read runs on to the session's own rows.
* fix(native-chat): count the live window in the session's own rows, so a subagent's burst keeps its roster
The live window kept the newest 1,024 rows of every agent. A subagent writing
more than that trimmed its own spawn's roster row and the prompt, and its
section fell back to a closed, unnamed header. The window now keeps the newest
1,024 of the session's own rows and everything after, with an 8,192-row cap on
every agent's rows as the memory backstop. A transcript with no subagent rows
trims exactly as before.
* fix(agent-session): window history pages by the session's own rows, with a subagent's rows riding along
A history page held the newest 200 rows of every agent, so a subagent's burst
could fill a page on its own: the phone opened on an empty chat and "Load
earlier" landed nothing. A page now starts at the oldest of the newest `limit`
rows of the session's own and serves every row from there, so the subagent's
rows come with the conversation they happened in. The page stays contiguous,
the cursor still names its first row, and the byte bound still applies. A
transcript with no subagent rows gets the same pages as before.
Clients already take a page larger than its limit: both reducers raise their
retained window to the page's size. The mobile read-on and read-back stay for
older hosts.
* test(agent-session): a page reaches back to the start rather than leaving a subagent-only page
* fix(native-chat): an own-row trim takes a trimmed roster's subagent rows with it
The live window trimmed to just after the own row it dropped, so a subagent
whose roster row went kept its rows at the top as an unnamed section until
the parent wrote again. Trim to the oldest own row kept instead; it still
fires only once an own row passes the limit, so a paged-in run of subagent
rows at the head stays until then. With no subagent rows nothing changes.
* perf(native-chat): cap the live window at 4,096 rows, bounding each delta's re-derivation
Every live batch re-derives the transcript over every retained row. On the
largest real window (7,374 rows) that cost 7-8 ms a delta on desktop against
0.6 ms at the old 1,024-row window, and held about 26 MB of row content.
4,096 halves both. The most rows any local journal puts between a roster and
its subagent's last row, with the parent inside its own-row limit, is 3,005,
so no observed subagent loses its roster to the lower cap.
* fix(native-chat): a subagent section opens only while its roster is the running scope's live frontier
A section used to open whenever its roster said the subagent was working, anywhere
in the transcript and whether or not the session was running, so a background
subagent's section stayed open and grew mid-transcript while the parent moved on.
It now opens by default only while the session runs and the roster row naming the
subagent is the newest thing the parent produced, user rows aside. Newer parent
output closes it even while the subagent still works; the roster row keeps
showing that live state. A subagent still working is a running scope of its own
for the sections it spawned; a settled one closes its scope. Derived every
render, no latch; the reader's own open or close still wins.
* fix(native-chat): name a subagent's section from a client roster the window never trims
A section took its name and state from a roster row in the loaded window. Once a
burst trimmed that row, or the row sat on an older page, the section fell back to
an unnamed, closed "Subagent" header.
The shared reducer now keeps a roster keyed by agent id, folded from every roster
row and revision the client receives: pages, older pages and live batches,
including revisions of roster rows outside the window, which live batches already
carry. The first roster naming an agent wins and its revisions update it; a
removed roster row drops its entries; it is rebuilt on every page that replaces
the window and bounded to 512 agents. Sections take their name, state and
live-frontier place from it; placement stays under the loaded roster row, else
at the section's first loaded row. Only a subagent no roster ever named stays
unnamed.
* feat(agent-session): a history page names the subagents whose roster row is older than it
A page is a contiguous run of the journal whose older-page cursor is its first
item, so it cannot pull an older roster row in without skipping the rows between.
When a page held a subagent's rows but not the roster row naming it (about 11% of
the moments a reader could open a session on local journals), that subagent drew
as an unnamed "Subagent" header.
History and hydration pages now carry an optional `subagentRoster`: the first
roster entry naming each subagent whose rows are on the page and whose roster row
is not, with the row's id, sequence and revision; bounded to 64 entries and
16 KB. Items and cursor are unchanged. The client seeds its roster from it.
Rule 1 in docs/reference/remote-wire-compatibility.md: an optional field on an
existing frame, no capability gate. An older client ignores it (the released
reducer reads a page with it exactly as one without); against an older host the
field is absent and the section falls back to an unnamed header.
* Revert "fix(native-chat): an own-row trim takes a trimmed roster's subagent rows with it"
This reverts commit
|
||
|
|
e8e144bf3c |
fix(native-chat): a subagent's words are presented as that subagent's, never the parent's (#23605)
* fix(native-chat): a subagent's words are presented as that subagent's, never the parent's The journal already names the agent that produced every row, but the transcript projection dropped it, so a subagent's prose rendered as the parent's reply, its tool calls folded into the parent's runs, and a settled turn could fold down to a subagent's words as its only visible answer. The transcript message now keeps the row's producer. The fold keeps each agent's calls in that agent's own run, a turn's answer is the session's own agent's last prose, and a subagent's row names the subagent on desktop, mobile and a worker's transcript text. * test(native-chat): give the window fixture's slot the attribution field it now carries * fix(mobile): read the subagent label the row is given, and pin the caption * fix(native-chat): keep interleaved agents in order and each agent's own run live Review follow-ups: - the fold is main's adjacency fold plus one condition: a row never folds into another agent's run, so an agent's later call stays below its subagent's work instead of jumping back into its earlier row - each agent has its own live frontier, so a parent still inside its spawn call reads as running while its subagent works below it - mobile names no one on a row whose only content is hidden behind its settled turn - a pending question from a subagent keeps its producer - worker reads serve only the producing agent's id, bounded like the roster key that names it, and drop the provenance fields - the single-message worker formatter is private, so no caller can drop names |
||
|
|
fab78c7669 |
fix(native-chat): show one live-turn indicator, and make Thinking mean reasoning (#19977)
* native-chat: render one indicator row for the live desktop turn The turn-timing row and the spinner+activity line were two rows saying "Working" at once. A settled turn keeps its own row; the live turn now has only the spinner row, labelled provider activity -> Thinking -> Working for N through the shared resolver. Reasoning is the turn's content, so it no longer becomes the activity label, and "Thinking" now means the turn is reasoning right now rather than that it has produced no output yet. * mobile: give the live turn row a spinner and the shared indicator label Mobile's per-turn row is already the only live indicator on the structured lane, but it pulsed a bare word and never showed what the provider said it was doing. It now renders a spinner beside the same resolved label desktop uses, and reads reasoning from the journal instead of inferring it from missing output. The bridge lane's four prompt/interrupt write seams move to one module so the controller stays under its line cap. * codex: mark streamed reasoning as reasoning too, and pin the provider markers The settled reasoning item carried the marker but the streaming one did not, so a live Codex turn - the only time the indicator is on screen - never read as reasoning. Both paths now stamp it; a plan document keeps its own presentation and must never read as reasoning. * fix(native-chat): tighten live turn reasoning state --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
4b4acf26a4 |
fix(mobile): enable patch-free iOS text selection in native chat (#19769)
* fix(mobile): make every native-chat text node selectable Long-press selection worked on some chat text and not others. Markdown paragraphs — the default block for agent prose — were the one block type left out when headings, quotes, code, lists and table cells gained `selectable`, and tool result output, diff rows, the unloadable-image placeholder, permission/question bodies and the send-error banner never had it at all. Selection is now set on every content Text in the chat surface, on the outermost block Text so nested inline spans inherit it. Labels inside a Pressable (option rows, tool-line headers, buttons) are deliberately left alone: selection there would swallow the tap they exist for. Extracting MobileNativeChatEmptyState keeps the view under its max-lines cap and matches desktop, where NativeChatEmptyState is already its own component. Tests render each surface and assert selection on the block that carries the prose; both files were ablated against the unfixed source (4/10 and 3/5 red) so they pin the defect rather than the current behavior. * fix(mobile): support native text range selection on iOS * fix(mobile): remove persistent assistant message controls * fix(mobile): scope patch-free text selection to chat Use the stock react-native-uitextview dependency behind an iOS adapter and opt assistant Markdown into range selection only in native chat. Preserve the existing React Native Text behavior elsewhere and remove the persistent assistant controls. --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
b0c67eaf88 |
feat(mobile): port the restructured native-chat turn status and live tool progress (#18761)
* feat(mobile): port the restructured native-chat turn status and live tool progress Mobile chat had a single static "Agent is working" row and no live tool activity, while the desktop restructure (#17597, #18705) replaced that with a per-turn status row and a running-tool label. This brings mobile to parity and puts the derivation in one place instead of two. Shared (new, pure, RN-safe — desktop uses them as i18n fallbacks, mobile directly, matching the native-chat-empty-state pattern): - `native-chat-turn-status.ts`: duration formatting, label selection, the turn-timing state machine, and the active/settled split. - `native-chat-tool-activity.ts`: command-tool classification, the running-tool label descriptor, and running-call selection. Desktop now consumes both; `NativeChatWorkingStatus`, `NativeChatToolRun` and `use-native-chat-turn-status` keep their existing behavior and strings. Mobile gains the "Thinking" / "Working for 12s" / "Worked for 3m 4s" row with a caret that discloses the turn's tool activity, the pulsing "Running npm test" row with terminal-vs-wrench glyphs, and desktop's rule that a completed turn's tool run hides behind the turn caret. The bridge lane is untouched and keeps its three-dot indicator. Headings, quotes, code, lists and table cells are now selectable. Files at their max-lines cap were split rather than bumped: the tool-run subtree, the prompt card, the session-lane wiring, and the turn-disclosure state each move to their own module. * perf(mobile): stop the turn-status rows from re-rendering the whole transcript A streaming turn re-renders the chat list many times a second. The disclosure wiring handed every row a fresh status object and a fresh toggle closure on each of those renders, so `MobileNativeChatMessage`'s memo never held and every visible row re-rendered per tick — including settled turns that had not changed. Memoize the status selection on the timing map, and keep one stable toggle handler per turn (pruned when a turn leaves the transcript) attached only to the settled rows that can actually disclose anything. Now only the live turn's row changes identity while the agent works. * fix(mobile): keep the turn clock running when the optimistic echo is replaced An accepted send renders as `pending-N` until the transcript echo lands under its real message id. That flips the active turn key mid-turn, and the timing reducer treated the new key as a new turn — so a turn that had reached "Working for 8s" visibly restarted at "Working for 0s". The reducer now carries the start over when the previous key names a turn that has since left the transcript, which is exactly the echo-replacement case. A genuinely new turn (the previous key still in the transcript) and a turn that had already settled both keep their own clock; both are pinned by tests. Desktop does not pass the new key and is unaffected. * fix(mobile): keep the Tools toggle working on settled turns Hiding a settled turn's tool run behind the turn caret (desktop parity) also made the composer's global Tools control a no-op on every completed turn: the run it wanted to expand was not rendered at all. Let that toggle override the hiding, so it still reveals every run at once the way it did before. * fix(mobile): re-key the turn timing instead of only carrying its start The previous fix carried the start forward only while the turn was still working. When the transcript echo landed after the turn had already settled, the new key inherited nothing, the settled timing was pruned with the old key, and the turn's "Worked for N" row disappeared entirely. Move the timing onto the new key instead, which covers both orderings: an in-flight turn keeps counting from its original start (and later settles against it), and an already-settled turn keeps its duration. Both orderings are pinned. * test(mobile): pin the structured turn-status wiring at the view level Emulator QA could not reach the structured lane (mobile's Create Tab -> Codex falls back to a terminal tab when agentSession.createSupport says unsupported), so the view's own lane wiring had no coverage — the one seam between the shared turn-timing reducer and the rendered rows. Assert what the view hands each row: the live user turn gets a status object and the three-dot indicator is gone on the structured lane; the bridge lane keeps the indicator and gets no status; a finished turn settles to a numeric duration with a toggle; and an assistant row never carries a status row of its own. * fix(mobile): isolate structured chat turn state * fix(mobile): let the capability RPC actually store what a phone advertises `runtime.clientCapabilities.update` records the advertised set by assigning `authenticatedSocket.clientCapabilities`, but the socket handed to the dispatcher defined that property with a getter only. In strict mode the assignment throws `TypeError: Cannot set property clientCapabilities ... which has only a getter`, so the RPC answered `runtime_error` and the set was never stored. The consequence is not subtle: `supportsStructuredAgentSessions` requires the capability, so `projectSessionTabAgentStatus` removed every `agent-session` tab from a phone that had advertised it correctly. A paired phone saw ZERO tabs on a worktree whose only tab was a structured Codex chat — structured native chat was unreachable on mobile over this transport, not just missing its new turn UI. Give the socket a setter that writes through to the channel, which already owns the set for the connection's lifetime, so later requests on the same socket see it. Found while trying to capture emulator screenshots of the turn-status port: two full QA runs reported the new UI "missing" because the phone could only ever get a bridge/PTY tab. * fix(mobile): carry the turn key instead of caching a handler in a ref Builds on the scope-isolation fix: that kept (and extended) a ref that is written during render — once to memoize a per-turn handler, once to prune dead turns, once to reset on a scope change. React Doctor's "Ref mutated during render" is what CI's `check:react-doctor:changed` was failing on (x2), and on mobile it is a real hazard rather than a style note: react-freeze discards renders, and a discarded render would leave the cache mutated. Pass the settled turn's key down the row instead and let it call one stable handler with it. That preserves both properties the cache was bought for — per scope isolation, and identity stability so a streaming transcript does not defeat the row's memo — with no ref writes and no pruning to get wrong. The scope-keyed expanded set and the 128-turn cap are untouched; their tests move to the new contract and one now pins handler identity across a re-render. Note for future changes here: `check:code-quality:changed` does NOT cover this. CI additionally runs the standalone react-doctor CLI, which has rules the oxlint plugin config does not enable. * fix: ship native chat status translations * test(native-chat): pin the shared copy against the English catalog The shared constants are desktop's i18n fallback and mobile's actually-rendered string. If one changes without the other, desktop keeps rendering en.json while mobile renders the constant — and nothing fails, because a fallback is only used when the key is missing. That silent divergence is the exact thing the shared module exists to prevent, and it is now reachable precisely because these strings are runtime-required rather than statically extracted. Assert every key in both shared copy objects matches en.json byte for byte, plus the interpolation placeholders the catalog interpolates on. --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
6c765d5ad3 | refactor(mobile): remove native chat re-export facades (#13436) | ||
|
|
c56ff58dd7 | test(mobile): centralize renderer setup (#13408) | ||
|
|
c511e51442 |
fix(mobile): label native-chat tool rows with a clean, expandable input summary (STA-3333) (#12498)
* fix(mobile): label tool rows with a clean summary, expand full input (STA-3333)
Mobile tool rows showed the raw input JSON (`{"file_path":…}`) as the row
label, and the expanded detail just repeated that same truncated string.
- `describeToolInput` labels a row with the target file path, else the
primary argument (command/cmd/query/pattern/url/description), else the
bounded JSON preview.
- Codex delivers tool arguments as a JSON string; normalize those into the
object shape the helpers already understand, so labels, file links,
run summaries and the expanded detail all work for Codex calls too.
- The expanded detail now renders the fully formatted input, capped at
MAX_TOOL_RESULT_CHARS like desktop's tool detail (and like the result
body), and a structured input makes the row expandable.
* fix(mobile): name search rows by their term and keep the filename in path labels (STA-3333)
Review follow-ups to the tool-row summary, all in the shared helper:
- A Grep/Glob row labelled itself with the directory it scanned and dropped
the pattern entirely, because `toolFilePath` treats `path` as a file target.
That path is a scan root, so it also rendered a tap-to-open link that asked
the app to open a folder. `toolFilePath` now ignores the generic `path` key
for search-shaped input, which lets the pattern win the label and drops the
bogus link; an explicit `file_path` still wins.
- An overlong path was truncated from the head, cutting off the basename —
the one part that tells two rows apart. Trim from the front instead, so
the label reads `…/session/MobileNativeChatMessage.tsx`.
- The primary-argument chain used `??`, so a present-but-blank key selected
itself and swallowed the keys ranked after it, dropping the label all the
way back to raw JSON. Take the first key that actually yields a label.
Refs STA-3333.
* fix(mobile): don't offer an expander whose detail repeats the row (STA-3333)
An empty tool input formats back to the row label verbatim, so `{}` and `[]`
advertised an expander and then re-showed the label — the same repeat-the-JSON
problem this change set out to remove. Gate `isStructuredToolInput` on the
collection actually having contents; the lazy detail path is untouched.
Also pins the overlong-path test to the path itself: asserting only length<=80
plus a `…` passed just as well with path labelling deleted.
* fix(mobile): gate the tool detail panel on having detail (STA-3333)
The Tools toggle opens every row at once, bypassing the row's tap guard,
so a row with nothing to expand rendered its own label again underneath
itself — and the tap that would dismiss it is a no-op. Matches desktop.
* fix(mobile): keep a blank tool argument out of the run header (STA-3333)
Skipping a present-but-blank primary key let `briefToolArg` fall through
to the raw JSON preview, so a run header read `Bash {"command":""}` where
it used to read `Bash`. Also state the search-path trade-off honestly:
suppressing the link costs a file-scoped search its tap target.
* fix(mobile): only treat a blank primary key as a missing argument (STA-3333)
The previous guard tested key presence, so a populated but non-string
argument — a mixed argv like ['kill','-9',pid], or a structured query —
dropped out of the run header instead of falling back to the preview.
* test(mobile): pin the tool-row chevron to the detail panel (STA-3333)
The panel gate was covered but the chevron beside it was not: swapping
`showDetail` back to `expanded` on the icon alone left all 909 mobile
tests green, so the affordance lie this branch fixes could return
unnoticed — a down-chevron over no panel, on a row whose tap is guarded
off.
Asserts both icon counts on the fixture that test already renders. The
two halves now die for distinct reasons: the panel gate on the duplicate
label text, the chevron on the icon count.
* test(shared): pin the blank-search-key guard in the tool label (STA-3333)
Dropping `.trim()` from summarizePrimaryToolArg left all 32 tests green,
yet it leaks through isSearchToolInput: a whitespace-only `query` starts
counting as a search term, which suppresses `path`. One character takes
the row's label, its tap-to-open link and its run-header argument at
once, and puts the raw JSON label back — the bug this branch removes.
Asserts all three outputs on that shape. Kills only that mutant; the
isSearchToolInput mutant still dies on the existing search test.
* fix(native-chat): share tool input display semantics (STA-3333)
Build the tool row label, file target, detail eligibility and bounded detail from one normalized input model. Mobile no longer reparses JSON-string input across independent helpers or repeats an already-complete plain label, and desktop now uses the same clean row summary instead of retaining raw JSON.\n\nKeep full detail formatting lazy for collapsed rows and share the 4000-character detail cap across both renderers. Tests pin desktop adoption, mobile disclosure parity, one-pass JSON parsing and the shared bound.
|
||
|
|
9500ca7a65 |
fix(mobile): show attached images in native (rich) chat (#10135)
* fix(mobile): show attached images in native (rich) chat Attaching an image in the mobile native chat did nothing visible — it reused the terminal attach flow, which pastes a bracketed host path into the hidden terminal, so there was no composer preview and nothing in the transcript. Give native chat the desktop model instead: - pick + upload shows a removable thumbnail chip in the composer (no early paste) - on submit, images ride along: Ctrl+U clear -> bracketed paste(s) -> settle -> text + Enter (idempotent on retry) - the optimistic echo carries the local preview URIs and the message renderer draws image-ref blocks as real thumbnails when the URI is loadable, so the sent photo appears in the conversation immediately - image-only echoes reconcile by ordinal against user turns after their tail (ignores agent replies / paginated history / the 'unknown' ack-loss path) Terminal chat attach is unchanged (both flows consolidated behind useMobileSessionImageAttachments). Adds unit coverage for pick+upload, the ride-along byte order, chip render/remove, and echo reconciliation. * test(mobile): interactive native-chat image proof (real hooks, click-driven) Replace the hand-fed component render with an interactive harness that mounts the real MobileNativeChatComposer/Message + useMobileNativeChatImageAttachments + drafts under react-native-web and drives the actual flow via clicks. Only the two OS boundaries are faked: the photo picker and the paired-host RPC socket. Screenshots (mobile/docs/native-chat-image-attachment/) are produced by real clicks, not props: - attach -> real upload pipeline -> chip appears, nothing pasted yet - send -> real ride-along emits Ctrl+U clear, bracketed image paste, text+Enter (shown in the live byte trace) and the sent bubble renders the photo thumbnail * fix(mobile): scope native-chat image attachments by active tab Images are now scoped to the tab that initiated the pick, so switching tabs during upload cannot ride an image into another terminal. Chips stay with their original tab, and only the active scope's images send with text. Improved error handling with user-facing toast messages for disconnection and send failures. * test(mobile): add image attachment tab-scoping and error tests Add comprehensive test coverage for tab-scoped attachment behavior, error handling when transport fails or lease is gated, and edge cases like attaching images during an in-flight send. Extract baseArgs and update helpers to reduce boilerplate across test cases. * fix(mobile): show attached images in native rich chat Images attached in the mobile native (rich) chat now display as: - Removable composer chips while composing - Thumbnails in the sent user bubble after sending (desktop parity) Implements proper image echo reconciliation by distinguishing image-source marker turns from text echoes, so an image send isn't cleared by an unrelated text echo. Adds scope isolation to prevent chips and drafts from leaking between tabs, and detects tab switches during the image-paste settle window to abort the send. Fixes Android tap-target positioning for the image removal badge and clears stale terminal input after failed pastes to avoid gluing fragments onto the next message. * rm stubs --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> |