Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
194 commits
Select commit Hold shift + click to select a range
d4981a0
test(native-chat): a Stop over a card sent now into the running turn …
brennanb2025 Sep 30, 2026
943bbe4
fix(native-chat): a Stop's queue pause holds a card whose hand-off is…
brennanb2025 Sep 30, 2026
071e2c0
test(native-chat): the Claude re-send case fails on its diff, inside …
brennanb2025 Sep 30, 2026
7de5fca
test(native-chat): a pause held by an unanswered hand-off ends on eve…
brennanb2025 Sep 30, 2026
c07320f
Merge remote-tracking branch 'origin/main' into brennanb2025/queue-st…
brennanb2025 Sep 30, 2026
6ce9464
fix(native-chat): narrow the pause's settled hand-off, and assert the…
brennanb2025 Sep 30, 2026
d9e9b97
Merge remote-tracking branch 'origin/main' into brennanb2025/queue-st…
brennanb2025 Sep 30, 2026
2af16ab
refactor(native-chat): derive the queue's pause from Stop and Resume …
brennanb2025 Sep 30, 2026
438240a
fix(native-chat): a card queued after a Stop sends normally, never ah…
brennanb2025 Sep 30, 2026
6de6b32
fix(native-chat): the queue's own send re-checks the no-overtake rule…
brennanb2025 Sep 30, 2026
340f4a6
Merge remote-tracking branch 'origin/main' into brennanb2025/queue-st…
brennanb2025 Sep 30, 2026
d8b9e48
fix(native-chat): stop creating the unused queue pause table
brennanb2025 Sep 30, 2026
44e5184
fix(native-chat): a Stop's pause never hides the restart pause
brennanb2025 Sep 30, 2026
a90bd87
test(native-chat): pin the Stop's no-resend, lift and held-card rules
brennanb2025 Sep 30, 2026
fbd56c6
Merge remote-tracking branch 'origin/main' into brennanb2025/queue-st…
brennanb2025 Sep 30, 2026
99589e9
refactor(native-chat): a Stop writes one Stop event with its reason, …
brennanb2025 Sep 30, 2026
e0087ee
test(native-chat): a rewind keeps a lifted /clear pause lifted and re…
brennanb2025 Sep 30, 2026
e447899
test(native-chat): pin that Stop and Resume rows never reach apps or …
brennanb2025 Sep 30, 2026
60500d8
test(native-chat): only a person's Stop event pauses the queue
brennanb2025 Sep 30, 2026
839f6f4
test(native-chat): pin that a Stop's event precedes the interrupt and…
brennanb2025 Sep 30, 2026
d5b48ef
test(native-chat): a card held at a starting agent is checked before …
brennanb2025 Sep 30, 2026
5689a9d
test(native-chat): a released build keeps and folds a journal holding…
brennanb2025 Sep 30, 2026
8c5523e
style(native-chat): format the Stop event changes
brennanb2025 Sep 30, 2026
5c38ea6
test(native-chat): type the released build's exports through one chec…
brennanb2025 Sep 30, 2026
a12b65a
Merge remote-tracking branch 'origin/main' into brennanb2025/queue-st…
brennanb2025 Sep 30, 2026
26e9419
fix(native-chat): the Stop/Resume row guard narrows to those tombston…
brennanb2025 Sep 30, 2026
977d500
Merge remote-tracking branch 'origin/main' into brennanb2025/queue-st…
brennanb2025 Sep 30, 2026
806765b
test(native-chat): run the Stop-event downgrade test in CI, and cover…
brennanb2025 Sep 30, 2026
8ec0fbf
fix(native-chat): a Stop that stops nothing new writes no Stop event
brennanb2025 Sep 30, 2026
722e964
test(native-chat): pin that a later host, eviction or close Stop neve…
brennanb2025 Sep 30, 2026
74adc16
chore(native-chat): put each Stop-row doc on its own declaration, and…
brennanb2025 Sep 30, 2026
4750645
fix(native-chat): any later Stop event ends a person's Stop pause
brennanb2025 Sep 30, 2026
c0d9689
fix(native-chat): a second Stop press is a repeat even when the first…
brennanb2025 Sep 30, 2026
989f90b
fix(native-chat): the queue's pause ignores a Stop or Resume row hold…
brennanb2025 Oct 1, 2026
be2be9f
fix(native-chat): a Stop still reads as yours after Orca restarts bef…
brennanb2025 Oct 1, 2026
935d24a
Merge remote-tracking branch 'origin/main' into brennanb2025/queue-st…
brennanb2025 Oct 1, 2026
cc32533
refactor(native-chat): a stop no longer carries its cause; the turn's…
brennanb2025 Oct 1, 2026
93e2f55
test(native-chat): a turn a later send opened is no Stop's that named…
brennanb2025 Oct 1, 2026
4be2b8b
test(native-chat): the restart test's death proof carries its detail
brennanb2025 Oct 1, 2026
c98b280
Merge remote-tracking branch 'origin/main' into brennanb2025/queue-st…
brennanb2025 Oct 1, 2026
8276989
Merge #24072's branch (with #24235) into stop-event part B
brennanb2025 Oct 1, 2026
20852ee
Merge remote-tracking branch 'origin/main' into brennanb2025/queue-st…
brennanb2025 Oct 1, 2026
d388b9c
refactor(native-chat): a refused Stop leaves no record; a Stop only e…
brennanb2025 Oct 1, 2026
f85a984
fix(native-chat): a Stop pressed before any turn showed stops only th…
brennanb2025 Oct 1, 2026
2ba34a9
fix(native-chat): an older Claude's error end after a Stop pressed be…
brennanb2025 Oct 1, 2026
4fdd9ad
fix(native-chat): a person's Stop and /clear each name why they end t…
brennanb2025 Oct 1, 2026
98ee326
fix(native-chat): a host stop judges whether it ends work after the p…
brennanb2025 Oct 1, 2026
d0e4cfc
fix(native-chat): a Claude Stop naming a turn that just ended still m…
brennanb2025 Oct 1, 2026
771ad35
fix(native-chat): the idle sweep reads working by the same rule as a …
brennanb2025 Oct 1, 2026
a51a1ee
test(native-chat): an aborted eviction's injected drain failure lands…
brennanb2025 Oct 1, 2026
eb27559
fix(native-chat): the idle sweep's rest writes no Stop event; it evic…
brennanb2025 Oct 1, 2026
693583d
fix(native-chat): stopping a start that carries no send writes no Sto…
brennanb2025 Oct 1, 2026
3b8c6e7
test(native-chat): a Stop's pause across a restart is tested with a r…
brennanb2025 Oct 1, 2026
506c4fa
Merge remote-tracking branch 'origin/main' into brennanb2025/queue-st…
brennanb2025 Oct 1, 2026
37ed748
fix(native-chat): a host stop of a turn a person's Stop is still endi…
brennanb2025 Oct 1, 2026
69b4dec
fix(native-chat): an older Claude's error on a later turn keeps its e…
brennanb2025 Oct 1, 2026
ac4a987
Merge remote-tracking branch 'origin/brennanb2025/queue-stop-keeps-st…
brennanb2025 Oct 1, 2026
5ead1f6
fix(native-chat): a Stop of a start that never landed binds no later …
brennanb2025 Oct 1, 2026
aa7f3a7
test(native-chat): a rewind's restated turnless Stop binds no turn op…
brennanb2025 Oct 1, 2026
a5960f7
fix(native-chat): a relaunch settles a person's stopped turn with no …
brennanb2025 Oct 1, 2026
78369ee
test(native-chat): the unexpected-exit settle's journal fake answers …
brennanb2025 Oct 1, 2026
e9c8af8
fix(native-chat): a host stop whose sink drain fails reads the journa…
brennanb2025 Oct 1, 2026
8550aa1
fix(native-chat): a Stop that named no turn applies only to a turn a …
brennanb2025 Oct 1, 2026
ee54861
fix(native-chat): a host stop whose sink drain runs long reads the ag…
brennanb2025 Oct 1, 2026
bd16848
test(native-chat): a host stop with no turn running defers only while…
brennanb2025 Oct 1, 2026
ec0533b
fix(native-chat): a slow sink drain reads working only while an accep…
brennanb2025 Oct 1, 2026
3fba742
test(native-chat): the host-stop control keeps the stopped send unans…
brennanb2025 Oct 1, 2026
6a1f65b
fix(native-chat): a steer is no send owed a turn when a slow drain ju…
brennanb2025 Oct 1, 2026
e644e58
feat(native-chat): a chat reads Stopping from the person's Stop until…
brennanb2025 Oct 1, 2026
42964ad
test(native-chat): pin Stopping on the phone and across mixed versions
brennanb2025 Oct 1, 2026
79f661c
test: give touched fake journals and mocks their SAFETY notes
brennanb2025 Oct 1, 2026
c2bd622
test(native-chat): a turn waiting on the person reads attention, neve…
brennanb2025 Oct 1, 2026
fbca7ee
test(native-chat): the sidebar row follows Stopping when it is the on…
brennanb2025 Oct 1, 2026
f4b3942
test(mobile): the phone reads Stopping from its own Stop until the re…
brennanb2025 Oct 1, 2026
8f32fc2
test(mobile): type the held Stop request instead of casting it
brennanb2025 Oct 1, 2026
dd8836d
chore: keep the base lockfile (a local pnpm run rewrote it)
brennanb2025 Oct 1, 2026
fe5ed91
fix(native-chat): narrow the Stop note's optional failure; type the p…
brennanb2025 Oct 1, 2026
b10fb50
test(native-chat): type the Stop test envelope's fields narrowly
brennanb2025 Oct 1, 2026
3ca4853
Merge remote-tracking branch 'origin/brennanb2025/stop-event-b' into …
brennanb2025 Oct 1, 2026
3d10733
fix(native-chat): a Stop the agent declined, or whose child end faile…
brennanb2025 Oct 1, 2026
5042f69
fix(native-chat): Stopping holds while any press of the Stop took
brennanb2025 Oct 1, 2026
552a4ad
fix(native-chat): keep Stop enabled while the host says Stopping
brennanb2025 Oct 1, 2026
28a4de4
fix(sidebar): every agent row says Stopping in place of its tool line
brennanb2025 Oct 1, 2026
cd44de4
fix(mobile): the worktree list sees Stopping change on its own
brennanb2025 Oct 1, 2026
514a216
refactor(native-chat): fold the status feed in src/shared for both cl…
brennanb2025 Oct 1, 2026
6b65121
feat(mobile): let phones read the structured session status stream
brennanb2025 Oct 1, 2026
e6b8632
feat(mobile): the phone chat reads Stopping from the host, like the d…
brennanb2025 Oct 1, 2026
d59d573
test: give the new fakes checked types or a SAFETY reason
brennanb2025 Oct 1, 2026
bc5dcad
revert(native-chat): drop the refused-named-turn rewrite of a Stop's …
brennanb2025 Oct 1, 2026
287c86c
fix(mobile): release the status stream when the host ends it; refusal…
brennanb2025 Oct 1, 2026
feeb589
perf(native-chat): read the live turn's opener from its record when d…
brennanb2025 Oct 1, 2026
c9ba088
refactor(mobile): move the method-unavailable check into transport
brennanb2025 Oct 1, 2026
0c36f02
test(native-chat): a send after a Stop reads Working before its turn …
brennanb2025 Oct 1, 2026
6dc89f6
fix(sidebar): the compact row leads with Stopping so a narrow sidebar…
brennanb2025 Oct 1, 2026
3bf1013
Merge remote-tracking branch 'origin/main' into brennanb2025/stop-eve…
brennanb2025 Oct 1, 2026
fc600d3
Merge remote-tracking branch 'origin/main' into brennanb2025/stop-eve…
brennanb2025 Oct 1, 2026
32c945f
test(native-chat): the retry of a close whose exit was unproven write…
brennanb2025 Oct 1, 2026
c39f2ad
Merge remote-tracking branch 'origin/brennanb2025/stop-event-b' into …
brennanb2025 Oct 1, 2026
eee56e6
fix(native-chat): read and write a Stop's answer by the turn its even…
brennanb2025 Oct 1, 2026
528d56a
refactor(native-chat): read a Stop's target once for its event and it…
brennanb2025 Oct 1, 2026
3764929
Merge remote-tracking branch 'origin/main' into brennanb2025/stopping…
brennanb2025 Oct 1, 2026
03d1fcc
fix(native-chat): the host never steers a message into a turn a Stop …
brennanb2025 Oct 2, 2026
723fa84
feat(native-chat): while Stopping, the composer says a message runs a…
brennanb2025 Oct 2, 2026
b32f574
refactor(native-chat): the chat pane's Stop controls live in their ow…
brennanb2025 Oct 2, 2026
bc4f09c
fix(native-chat): hold a send at its handover, reading the feed's own…
brennanb2025 Oct 2, 2026
8d4f441
Merge remote-tracking branch 'origin/main' into brennanb2025/stopping…
brennanb2025 Oct 2, 2026
1699657
refactor(native-chat): one display status decides Stopping on every s…
brennanb2025 Oct 2, 2026
aff5849
fix(native-chat): Stopping holds until the stopped turn ends, whateve…
brennanb2025 Oct 2, 2026
486a4aa
fix(native-chat): a Stop binds only the turn it actually stopped
brennanb2025 Oct 2, 2026
338180b
Merge #24864 (a Stop binds only the turn it stopped) into the Stoppin…
brennanb2025 Oct 2, 2026
4d5e13e
fix(native-chat): keep the person's queue pause and close binding aft…
brennanb2025 Oct 2, 2026
bfc51ce
Merge #24864's review fixes into the Stopping state
brennanb2025 Oct 2, 2026
4ed011b
fix(native-chat): keep a failed Stop's turn display-only, and settle …
brennanb2025 Oct 2, 2026
8447d44
Merge origin/main into stop-binds-at-settle
brennanb2025 Oct 2, 2026
a7a9551
Merge #24864 (with main) into the Stopping state
brennanb2025 Oct 2, 2026
9f61404
fix(native-chat): a Codex Stop agrees on both presses when a turn is …
brennanb2025 Oct 2, 2026
78eda4a
Merge #24864's final fixes into the Stopping state
brennanb2025 Oct 2, 2026
c2f54be
fix(native-chat): a Stop that failed before its turn showed still rea…
brennanb2025 Oct 2, 2026
34acafa
Merge remote-tracking branch 'origin/main' into brennanb2025/stop-bin…
brennanb2025 Oct 3, 2026
67a06b8
Merge remote-tracking branch 'origin/main' into brennanb2025/stop-bin…
brennanb2025 Oct 3, 2026
057e364
Merge remote-tracking branch 'origin/main' into brennanb2025/stop-bin…
brennanb2025 Oct 3, 2026
06261d0
test(native-chat): a Stop whose event row failed binds no turn to an …
brennanb2025 Oct 3, 2026
60572cc
test(native-chat): name the settle, not a stream drain, in the Stop's…
brennanb2025 Oct 3, 2026
5fdc221
Merge remote-tracking branch 'origin/main' into brennanb2025/stop-bin…
brennanb2025 Oct 3, 2026
3a3b7f6
Merge origin/brennanb2025/stop-binds-at-settle (#24864 with main's on…
brennanb2025 Oct 3, 2026
0855e21
test(native-chat): a Codex Stop answered before Codex ends the turn r…
brennanb2025 Oct 3, 2026
f7c08df
Merge remote-tracking branch 'origin/brennanb2025/stop-binds-at-settl…
brennanb2025 Oct 3, 2026
8b526b7
test(native-chat): Stopping ends with a Codex turn whose interrupt is…
brennanb2025 Oct 3, 2026
bd95932
fix(native-chat): a Codex Stop interrupts a turn Codex answered but h…
brennanb2025 Oct 3, 2026
ffd8d6a
Merge remote-tracking branch 'origin/main' into brennanb2025/stop-bin…
brennanb2025 Oct 3, 2026
a7b2a5a
Merge remote-tracking branch 'origin/brennanb2025/stop-binds-at-settl…
brennanb2025 Oct 3, 2026
c120cb0
fix(mobile): name how the phone's status stream is released in the su…
brennanb2025 Oct 3, 2026
980f0e7
fix(native-chat): every Codex Stop waits for an answered turn to star…
brennanb2025 Oct 3, 2026
f7594b6
Merge remote-tracking branch 'origin/brennanb2025/stop-binds-at-settl…
brennanb2025 Oct 3, 2026
0138f0b
fix(codex): never steer a turn whose interrupt Codex answered
brennanb2025 Oct 3, 2026
8a46f7c
fix(native-chat): a message sent while Stopping is queued as a card, …
brennanb2025 Oct 3, 2026
3bdb4de
fix(native-chat): a message queued while Stopping is a card at once, …
brennanb2025 Oct 3, 2026
a2ba0c5
fix(native-chat): a Codex Stop that ends the child before any turn op…
brennanb2025 Oct 3, 2026
a6b8185
fix(native-chat): derive the withdrawal of a Codex send whose turn ne…
brennanb2025 Oct 3, 2026
6622a37
Merge remote-tracking branch 'origin/brennanb2025/stop-binds-at-settl…
brennanb2025 Oct 3, 2026
910989f
fix(native-chat): a card queued while Stopping runs past the cards th…
brennanb2025 Oct 3, 2026
78da557
fix(native-chat): a paused queue labels only the cards it holds
brennanb2025 Oct 3, 2026
497098d
revert(native-chat): defer the per-card queue pause label to the queu…
brennanb2025 Oct 3, 2026
84f7a5f
fix(native-chat): while Stopping, say and show what a send does where…
brennanb2025 Oct 3, 2026
70601f5
fix(native-chat): keep a send in doubt when a turn was open for it
brennanb2025 Oct 3, 2026
e1d45a1
Merge remote-tracking branch 'origin/brennanb2025/stop-binds-at-settl…
brennanb2025 Oct 3, 2026
980bf93
fix(native-chat): restore the per-card queue pause label
brennanb2025 Oct 3, 2026
6186504
fix(native-chat): draw only a send made while Stopping after the Stop…
brennanb2025 Oct 3, 2026
8e3240e
fix(native-chat): withdraw a Codex send by whether it started its own…
brennanb2025 Oct 3, 2026
3c7222a
Merge remote-tracking branch 'origin/brennanb2025/stop-binds-at-settl…
brennanb2025 Oct 3, 2026
a66d22b
refactor(native-chat): move queue-while-stopping to its own follow-up
brennanb2025 Oct 3, 2026
5b60057
fix(native-chat): leave no Stop row when the Stop took back a send th…
brennanb2025 Oct 3, 2026
4888740
Merge remote-tracking branch 'origin/brennanb2025/stop-binds-at-settl…
brennanb2025 Oct 3, 2026
b47e696
fix(native-chat): count a send whose answer was lost when a Stop take…
brennanb2025 Oct 4, 2026
385dd20
Merge remote-tracking branch 'origin/brennanb2025/stop-binds-at-settl…
brennanb2025 Oct 4, 2026
22d2271
test(native-chat): pin which sends a Stop's child end can take back
brennanb2025 Oct 4, 2026
fdbc922
Merge remote-tracking branch 'origin/brennanb2025/stop-binds-at-settl…
brennanb2025 Oct 4, 2026
2daeeea
Merge remote-tracking branch 'origin/main' into brennanb2025/stop-bin…
brennanb2025 Oct 4, 2026
5b4c595
Merge remote-tracking branch 'origin/brennanb2025/stop-binds-at-settl…
brennanb2025 Oct 4, 2026
ff02198
fix(native-chat): read the host's Stopping beside main's startup phase
brennanb2025 Oct 4, 2026
f0c185e
Merge remote-tracking branch 'origin/main' into brennanb2025/stop-bin…
brennanb2025 Oct 5, 2026
d8422dd
test(native-chat): read the outbox reconcile from where main moved it
brennanb2025 Oct 5, 2026
6b2f194
Merge remote-tracking branch 'origin/brennanb2025/stop-binds-at-settl…
brennanb2025 Oct 5, 2026
eb76da2
fix(native-chat): a retried message no longer waits behind a later Stop
brennanb2025 Oct 5, 2026
7d137d5
Merge remote-tracking branch 'origin/main' into brennanb2025/stopping…
brennanb2025 Oct 5, 2026
b36a22f
Merge remote-tracking branch 'origin/main' into brennanb2025/stopping…
brennanb2025 Oct 5, 2026
e278b7a
fix(native-chat): word a send after a Stop by whether this send will …
brennanb2025 Oct 5, 2026
c6f9699
test(native-chat): one test per case for the words of a send after a …
brennanb2025 Oct 5, 2026
a299ee4
Merge remote-tracking branch 'origin/main' into brennanb2025/stopping…
brennanb2025 Oct 5, 2026
7603054
refactor(native-chat): the dictation hook owns the composer's dictati…
brennanb2025 Oct 5, 2026
4acb3df
refactor(native-chat): name the dictation hook for what it owns now
brennanb2025 Oct 5, 2026
8f5f49f
Merge remote-tracking branch 'origin/main' into brennanb2025/stopping…
brennanb2025 Oct 5, 2026
b0ea254
fix(native-chat): a Stop's note says it took once a joined close prov…
brennanb2025 Oct 5, 2026
9224f99
fix(native-chat): a proven turn end says a Stop's unconfirmed note took
brennanb2025 Oct 5, 2026
a5429e8
test(native-chat): a Stop's unconfirmed note says it took when the ag…
brennanb2025 Oct 5, 2026
bbee774
Merge remote-tracking branch 'origin/main' into brennanb2025/stopping…
brennanb2025 Oct 5, 2026
6503268
test(native-chat): name the Codex handle as main's opaque handle does
brennanb2025 Oct 5, 2026
b740069
Merge remote-tracking branch 'origin/main' into brennanb2025/stopping…
brennanb2025 Oct 5, 2026
a353cc6
Merge remote-tracking branch 'origin/main' into brennanb2025/stopping…
brennanb2025 Oct 6, 2026
c8a5ac8
test: restore the provider handle import the main merge dropped
brennanb2025 Oct 6, 2026
f803bb0
fix: derive Stop note wording from interrupted turns
brennanb2025 Oct 6, 2026
3700e2d
test: name the raw replay case for what it covers
brennanb2025 Oct 6, 2026
35b6b72
Merge remote-tracking branch 'origin/main' into brennanb2025/stopping…
brennanb2025 Oct 6, 2026
53ae708
refactor(native-chat): move the waiting-slot split into its own hook
brennanb2025 Oct 6, 2026
e58e9f8
Merge remote-tracking branch 'origin/main' into brennanb2025/stopping…
brennanb2025 Oct 6, 2026
a7a5492
Merge remote-tracking branch 'origin/main' into brennanb2025/stopping…
brennanb2025 Oct 6, 2026
970c6a1
test: follow main's chat font-size rename in the stopping tests
brennanb2025 Oct 6, 2026
2fd4ad3
Merge remote-tracking branch 'origin/main' into brennanb2025/stopping…
brennanb2025 Oct 6, 2026
082ffca
test: follow main's single live-line value in the Stopping tests
brennanb2025 Oct 6, 2026
0291a43
test: give the android live-line fixtures the stopping field
brennanb2025 Oct 6, 2026
0516948
chore: keep the session host under its line limit after the main merge
brennanb2025 Oct 6, 2026
7c4be11
Merge remote-tracking branch 'origin/main' into brennanb2025/stopping…
brennanb2025 Oct 6, 2026
5dce67a
chore: keep the composer test and the phone chat view under their lin…
brennanb2025 Oct 6, 2026
4418399
perf(native-chat): read a Stop note's fields before parsing its key o…
brennanb2025 Oct 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion mobile/src/files/MobileFileExplorerPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
resetDirectoryLoadRevisions,
type DirectoryLoadRevisions
} from './directory-load-revisions'
import { directoryCacheFromFileList, isMobileMethodUnavailableError } from './file-list-fallback'
import { isMobileMethodUnavailableError } from '../transport/mobile-method-unavailable'
import { directoryCacheFromFileList } from './file-list-fallback'
import { fileDirectoryRead, legacyFileListRead } from './mobile-file-explorer-operations'
import { fileExplorerStyles as styles } from './mobile-file-explorer-styles'
import { MobileFileExplorerRow } from './mobile-file-explorer-row'
Expand Down
2 changes: 1 addition & 1 deletion mobile/src/files/file-explorer-reply-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const fileDirectoryEntriesSchema = salvagingArray(
* The capped flat list an older desktop answers when `files.readDir` is not allowlisted.
*
* `files` and `truncated` are both required and both read unguarded: directoryCacheFromFileList
* walks `files` and splits each `relativePath` (file-list-fallback.ts:48), and
* walks `files` and splits each `relativePath` (file-list-fallback.ts:34), and
* MobileFileExplorerPanel.tsx:136 publishes `truncated` into the state that draws the "Showing
* first 5000" note. A row without a string `relativePath` drops — it can name no directory — where
* main crashed the whole fallback on it.
Expand Down
17 changes: 1 addition & 16 deletions mobile/src/files/file-list-fallback.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,7 @@
import { describe, expect, it } from 'vitest'
import { directoryCacheFromFileList, isMobileMethodUnavailableError } from './file-list-fallback'
import { directoryCacheFromFileList } from './file-list-fallback'
import { getDirectoryCacheState } from './file-tree'

describe('isMobileMethodUnavailableError', () => {
it('detects old-desktop allowlist and missing-method failures', () => {
expect(isMobileMethodUnavailableError('forbidden', undefined)).toBe(true)
expect(isMobileMethodUnavailableError('method_not_found', undefined)).toBe(true)
expect(
isMobileMethodUnavailableError(
'some_code',
"Method 'files.readDir' is not available to mobile clients"
)
).toBe(true)
expect(isMobileMethodUnavailableError('internal', 'boom')).toBe(false)
expect(isMobileMethodUnavailableError(undefined, undefined)).toBe(false)
})
})

describe('directoryCacheFromFileList', () => {
it('synthesizes every ancestor directory from flat paths', () => {
const cache = directoryCacheFromFileList([
Expand Down
14 changes: 0 additions & 14 deletions mobile/src/files/file-list-fallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,6 @@ export type LegacyFilesListResult = {
truncated: boolean
}

// Same detection shape as isMobileGitUnavailable in mobile-git-status.ts:
// 'forbidden' = method exists but is not mobile-allowlisted on the old
// desktop; 'method_not_found' = desktop predates the method entirely.
export function isMobileMethodUnavailableError(
code: string | undefined,
message: string | undefined
): boolean {
return (
code === 'forbidden' ||
code === 'method_not_found' ||
message?.includes('not available to mobile clients') === true
)
}

// Takes only the member it reads: the explorer's reply reader checks `relativePath` and passes the
// rest of each row through, so naming the whole row here would re-declare what it deliberately did not.
export function directoryCacheFromFileList(
Expand Down
24 changes: 21 additions & 3 deletions mobile/src/session/MobileNativeChatLiveLine.android.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,13 @@ describe('MobileNativeChatLiveLine on Android', () => {
act(() => {
renderer = create(
createElement(MobileNativeChatLiveLine, {
line: { thinking: true, activityText: null, reasoning: block, reasoningExpanded: true },
line: {
thinking: true,
stopping: false,
activityText: null,
reasoning: block,
reasoningExpanded: true
},
onToggleReasoning: vi.fn(),
fontScale: 1
})
Expand All @@ -78,7 +84,13 @@ describe('MobileNativeChatLiveLine on Android', () => {
it('keeps the sheet through the block ending, and never opens one for the next by itself', () => {
const line = (reasoning: NativeChatLiveReasoning | null) =>
createElement(MobileNativeChatLiveLine, {
line: { thinking: true, activityText: null, reasoning, reasoningExpanded: true },
line: {
thinking: true,
stopping: false,
activityText: null,
reasoning,
reasoningExpanded: true
},
onToggleReasoning: vi.fn(),
fontScale: 1
})
Expand All @@ -103,7 +115,13 @@ describe('MobileNativeChatLiveLine on Android', () => {
it('opens no sheet for the next block when the first ends with the sheet open', () => {
const line = (reasoning: NativeChatLiveReasoning | null) =>
createElement(MobileNativeChatLiveLine, {
line: { thinking: true, activityText: null, reasoning, reasoningExpanded: true },
line: {
thinking: true,
stopping: false,
activityText: null,
reasoning,
reasoningExpanded: true
},
onToggleReasoning: vi.fn(),
fontScale: 1
})
Expand Down
7 changes: 7 additions & 0 deletions mobile/src/session/MobileNativeChatLiveLine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ describe('MobileNativeChatLiveLine', () => {
function element(
fields: {
thinking?: boolean
stopping?: boolean
activityText?: string | null
reasoning?: NativeChatLiveReasoning | null
reasoningExpanded?: boolean
Expand All @@ -69,6 +70,7 @@ describe('MobileNativeChatLiveLine', () => {
return createElement(MobileNativeChatLiveLine, {
line: {
thinking: true,
stopping: false,
activityText: null,
reasoning: null,
reasoningExpanded: false,
Expand Down Expand Up @@ -110,6 +112,11 @@ describe('MobileNativeChatLiveLine', () => {
expect(labels(render({ activityText: 'Running pnpm test' }))).toEqual(['Running pnpm test'])
})

it("reads Stopping over provider activity once a person's Stop is ending the turn", () => {
const root = render({ thinking: false, activityText: 'Running pnpm test', stopping: true })
expect(labels(root)).toEqual(['Stopping…'])
})

it('announces what it says to assistive tech, and is no button while it discloses nothing', () => {
const line = header(render())
expect(line.props.accessibilityLiveRegion).toBe('polite')
Expand Down
5 changes: 3 additions & 2 deletions mobile/src/session/MobileNativeChatLiveLine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { MobileNativeChatMessageActionsSheet } from './MobileNativeChatMessageAc
import { MobileNativeChatReasoningBody } from './MobileNativeChatReasoningRow'
import type { MobileNativeChatLiveLine as LiveLine } from './use-mobile-native-chat-turn-disclosure'

/** The live turn's tail line: a spinner beside what the provider says it is doing, else
* "Thinking", else "Working…". The clock stays in the turn bar. While the agent's open reasoning
/** The live turn's tail line: a spinner beside "Stopping…" once the person's Stop is ending the
* turn, else what the provider says it is doing, else "Thinking", else "Working…". The clock
* stays in the turn bar. While the agent's open reasoning
* block has text it is also that block's disclosure, and the block's row draws nothing.
* Desktop parity: `NativeChatTurnActivityLine`. */
export function MobileNativeChatLiveLine({
Expand Down
32 changes: 32 additions & 0 deletions mobile/src/session/MobileNativeChatOverlay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ type Tick = {
streamingText?: string
streamLive?: boolean
identity?: string
/** The host says a person's Stop is ending the turn. */
stopping?: boolean
}

function overlayElement(tick: Tick): ReturnType<typeof createElement> {
Expand All @@ -34,6 +36,11 @@ function overlayElement(tick: Tick): ReturnType<typeof createElement> {
nativeChatSession: { messages: tick.messages ?? [], status: 'ready' },
nativeChatAgent: 'claude',
nativeChatAgentWorking: tick.streamLive ?? false,
nativeChatTurnIndicator: {
thinking: false,
activityText: null,
stopping: tick.stopping ?? false
},
nativeChatStreamingText: tick.streamingText,
nativeChatStreamLive: tick.streamLive ?? false,
nativeChatStreamScopeKey: tick.identity ?? 'tab-a',
Expand Down Expand Up @@ -185,3 +192,28 @@ describe('MobileNativeChatOverlay streaming gate', () => {
expect(streaming()).toBeNull()
})
})

describe("MobileNativeChatOverlay while a person's Stop ends the turn", () => {
let renderer: ReactTestRenderer | null = null

afterEach(() => {
act(() => renderer?.unmount())
renderer = null
})

async function steerHeld(tick: Tick): Promise<unknown> {
await act(async () => {
renderer = create(overlayElement(tick))
})
const view = renderer!.root.find((node) => node.type === 'ChatView')
return view.props.queuedSlot?.cards?.props?.steerHeld
}

it("holds the queued cards' Steer while the agent works and the host says Stopping", async () => {
expect(await steerHeld({ streamLive: true, stopping: true })).toBe(true)
})

it('leaves Steer to the cards otherwise', async () => {
expect(await steerHeld({ streamLive: true, stopping: false })).toBe(false)
})
})
5 changes: 4 additions & 1 deletion mobile/src/session/MobileNativeChatOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ export function MobileNativeChatOverlay({
onEdit: queued.edit,
pause: queued.pause,
onResume: queued.resume,
sessionKey: queued.sessionKey
sessionKey: queued.sessionKey,
// Nothing steers into a turn a Stop is ending; the host holds such a send until it ends.
// The indicator's `stopping` is the display status, decided once in the session hook.
steerHeld: controller.nativeChatTurnIndicator?.stopping === true
})
if (!controller.showNativeChat) {
return null
Expand Down
19 changes: 19 additions & 0 deletions mobile/src/session/MobileNativeChatQueuedMessages.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,25 @@ describe('MobileNativeChatQueuedMessages', () => {
expect(nodeTypes(rows[1]!)).toContain('CornerDownRight')
})

it("holds Steer while a person's Stop ends the turn; Delete still works", async () => {
const onSend = vi.fn(async () => true)
const onDelete = vi.fn(async () => true)
const mounted = await mount({
cards: [card({ messageId: 'w' })],
onSend,
onDelete,
steerHeld: true
})
const steer = mounted.root.findByProps({
accessibilityLabel: 'Submit without interrupting the model'
})
expect(steer.props.disabled).toBe(true)
expect(steer.props.accessibilityState).toEqual({ disabled: true })
const trash = mounted.root.findByProps({ accessibilityLabel: 'Delete this queued message' })
await act(async () => trash.props.onPress())
expect(onDelete).toHaveBeenCalledWith('w')
})

it('deletes a card from its trash button', async () => {
const onDelete = vi.fn(async () => true)
const mounted = await mount({ cards: [card({ messageId: 'w' })], onDelete })
Expand Down
11 changes: 7 additions & 4 deletions mobile/src/session/MobileNativeChatQueuedMessages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export type MobileNativeChatQueuedMessagesProps = {
/** The whole queue's pause: the box's first row, with Resume. */
pause?: MobileQueuePause
onResume?: () => Promise<boolean>
/** The chat reads Stopping: no card steers into the turn a Stop is ending. */
steerHeld?: boolean
}

/** The host-held queued drafts, as one box of compact rows between transcript and
Expand All @@ -44,7 +46,8 @@ export function MobileNativeChatQueuedMessages({
onDelete,
onEdit,
pause,
onResume
onResume,
steerHeld = false
}: MobileNativeChatQueuedMessagesProps): React.JSX.Element | null {
// One in-flight action per card; a second tap must not double-consume. The ref
// closes the same-frame double tap the disabled state cannot.
Expand Down Expand Up @@ -136,7 +139,7 @@ export function MobileNativeChatQueuedMessages({
</View>
<Pressable
accessibilityRole="button"
accessibilityState={{ disabled: busy }}
accessibilityState={{ disabled: busy || steerHeld }}
accessibilityLabel={
returned
? 'Send this message again'
Expand All @@ -147,9 +150,9 @@ export function MobileNativeChatQueuedMessages({
style={({ pressed }) => [
styles.textAction,
pressed && styles.pressed,
busy && styles.disabled
(busy || steerHeld) && styles.disabled
]}
disabled={busy}
disabled={busy || steerHeld}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Returned cards cannot be resent

While a turn reads “Stopping…”, this disables a returned card’s Send action as well as a waiting card’s Steer action. A Stop can return an in-flight message as a card while the turn is still running, so the user cannot resend it until stopping clears, even though the host holds sends until the turn ends. The desktop card has the same issue.

Knowledge Base Used: Agent workspaces and sessions

onPress={() => void run(card.messageId, onSend)}
>
{steers ? (
Expand Down
28 changes: 28 additions & 0 deletions mobile/src/session/MobileNativeChatStopButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Pressable, Text } from 'react-native'
import { Square } from 'lucide-react-native'
import { NATIVE_CHAT_TURN_STATUS_COPY } from '../../../src/shared/native-chat-turn-status'
import { colors } from '../theme/mobile-theme'
import { styles } from './mobile-native-chat-view-styles'

/** The chat header's Stop. `held`: this phone's own Stop request is still in flight. */
export function MobileNativeChatStopButton({
onStop,
held
}: {
onStop?: () => void
held: boolean
}): React.JSX.Element {
const label = held ? NATIVE_CHAT_TURN_STATUS_COPY.stopping : 'Stop'
return (
<Pressable
style={({ pressed }) => [styles.stopButton, pressed && styles.pressed]}
onPress={onStop}
disabled={held}
hitSlop={8}
accessibilityLabel={held ? label : 'Stop the agent'}
>
<Square size={13} color={colors.statusRed} strokeWidth={2.4} fill={colors.statusRed} />
<Text style={styles.stopLabel}>{label}</Text>
</Pressable>
)
}
49 changes: 49 additions & 0 deletions mobile/src/session/MobileNativeChatView.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,55 @@ describe('MobileNativeChatView', () => {
})
}

it("holds Stop and says Stopping while this phone's own Stop request is in flight", async () => {
await render({
structuredActivityUi: true,
agentWorking: true,
canStop: true,
turnIndicator: {
thinking: false,
activityText: null,
stopping: true,
stopRequestInFlight: true
}
})
const stop = renderer!.root.find(
(node) => node.type === 'Pressable' && node.props.accessibilityLabel === 'Stopping…'
)
expect(stop.props.disabled).toBe(true)
})

it.each([
['queue', 'Queue a message to run after the stop'],
['send', 'Send a message to run after the stop']
] as const)(
'tells the composer a message sent now runs after the stop (%s)',
async (afterStop, placeholder) => {
await render({
structuredActivityUi: true,
agentWorking: true,
canStop: true,
turnIndicator: { thinking: false, activityText: null, stopping: true, afterStop }
})
const composer = renderer!.root.find((node) => node.type === 'Composer')
expect(composer.props.placeholder).toBe(placeholder)
}
)

// A Stop the provider took and never answered ends only at a repeat Stop.
it('keeps Stop for the repeat that escalates while the host alone says Stopping', async () => {
await render({
structuredActivityUi: true,
agentWorking: true,
canStop: true,
turnIndicator: { thinking: false, activityText: null, stopping: true }
})
const stop = renderer!.root.find(
(node) => node.type === 'Pressable' && node.props.accessibilityLabel === 'Stop the agent'
)
expect(stop.props.disabled).toBe(false)
})

/** Ids of the rows the list is currently rendering. */
it('keeps Stop hidden during a structured dispatch until a provider turn can be cancelled', async () => {
const props = { structuredActivityUi: true, agentWorking: true, canStop: false }
Expand Down
Loading
Loading