Skip to content

chore: sync upstream (file editing, image paste, schedule fixes) - #18

Merged
Demonic1594 merged 12 commits into
mainfrom
ccr-69714d30-8euut8
Oct 26, 2026
Merged

Demonic1594 merged 12 commits into
mainfrom
ccr-69714d30-8euut8

Conversation

@Demonic1594

Copy link
Copy Markdown
Owner

Summary

Merges yuga-hashimoto/and-code main up to 414c780 (6 commits):

Merge with --no-ff per docs/FORK.md, so upstream history is kept. Please merge this PR with a merge commit, not squash, so the next sync starts from here.

Conflict resolution

  • CodeViewerViewModel: upstream's editing backend plus the fork's editor features.
    • From upstream:
      • exact on-disk read on edit (readFileForEditing);
      • the write is refused if the file changed underneath (expectedContent);
      • writes are bounded to the workspace (editTargetOf);
      • it covers all local runtimes.
    • From the fork, on top: undo/redo with typing coalescing and a size-bounded history, replace-all, and the savedCount confirmation. The fork's shell-based save is no longer used by the viewer.
  • CodeViewerScreen: the fork's editor UI on upstream's state (isEditing, editError) and strings. The fork-only duplicate strings are removed. Start-edit failures now show in the viewer.
  • ChatComposer: upstream's image paste (text toolbar and Ctrl+V) moved into the composer file the fork split out of ChatHomeScreen. The ChatHomeScreen side (gallery helper, onPasteImage) came through the merge.
  • strings: both sides kept in all 8 locales.
  • CodeViewerEditingInstrumentedTest: follows the fork's behaviour, where back closes the editor before it leaves the screen.

Follow-up commit (from review)

  • Back is held while a save is in flight; before, discarding there would be overridden when the save landed.
  • The editor is read-only while saving, and has autocorrect and auto-capitals off.
  • A test checks that a save confirms once and clears history.
  • Two imports left unused by the merge are removed.

FORK.md post-sync checks: the debug-variant files are present, largeHeap appears once, and the .heap suffix and runtime port offset are intact.

Pre-PR review (repo-reviewer)

Verdict: APPROVE. Applied from it: back and typing are held during save, the keyboard options, the unused imports, and the savedCount test.

Left as is:

  • WorkspaceShell.writeFile is now only used by its own test. It will be revisited in the upcoming editor rework.
  • replaceAll ignore-case counting differs from the replacement count for non-ASCII. This is pre-existing fork behaviour.

Local checks

./gradlew detekt spotlessCheck :app:lintGithubDebug :app:testGithubDebugUnitTest :app:assembleGithubDebug :app:assembleGithubDebugAndroidTest passes.


Generated by Claude Code

yuga-hashimoto and others added 12 commits October 24, 2026 01:07
Three file polls between the app and the in-guest agent ran for the life of
the process, waking it every few hundred milliseconds whether or not anything
could have been written:

- Claude Code permission bridge: every 250 ms, started by the first session
  and never cancelled, even after every session had ended.
- Schedule bridge: every 800 ms on the application scope.
- Guest browser command watcher: every 1 s from a LaunchedEffect, which keeps
  running while the activity is stopped.

Add Flow<Boolean>.pollWhileActive (poll at once when the gate opens, then every
interval, and stay suspended with no timer while it is closed) and gate each
loop on the condition under which a writer can exist:

- permission bridge: a CLI process is registered. The hook that parks a
  request dies with its process, so with none alive nothing can appear.
- schedule bridge: the app is in the foreground or runtimeWork is active.
  With neither, the device may suspend and the guest freezes with it.
- browser command watcher: the app is in the foreground.

ScheduleBridge.run now requires the gate rather than defaulting to "always" so
a future call site cannot quietly bring the unbounded loop back.

Covered by PollWhileActiveTest plus a gating test each for the schedule bridge
and the browser watcher. Disabling the gate makes exactly those five fail.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Follow-ups from the pre-PR review of the polling gate.

- ScheduleBridge: with the poll parked while the app is backgrounded and idle,
  a request written in that window used to run on the way back in even though
  the guest MCP had already given up after 90 s and told the agent the call
  failed. A create/delete/runNow the agent was told did not happen would then
  happen anyway, and a retry could duplicate it. Requests older than two
  minutes are now answered with an "expired" error and discarded. A missing or
  zero createdAtMs is "unknown" and still runs.
- ClaudeCodeRuntime: route every change to the session map through
  registerSession/unregisterSession, which keep hasLiveSessions in step, so a
  future edit cannot forget to publish and leave permission prompts silently
  unanswered.
- KDoc: say what pollWhileActive does to a suspending action, and that the
  browser watcher's foreground flow must be a stable reference.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Review follow-up. The expiry added for requests that sat unanswered while the
bridge poll was parked was two minutes against the guest's 90 s reply timeout,
which left a 90-120 s window where a request ran after its caller had already
given up. The stated reason for the extra headroom, clock drift, does not
apply: the guest runs under PRoot and reads the same kernel clock as the app.

- REQUEST_EXPIRES_AFTER_MILLIS is now 95 s, with a note to keep it in step
  with RESPONSE_TIMEOUT_S in andcode-schedule-mcp.py.
- Tests pin the boundary and the cases that must still run: a timestamp in the
  future and a request with no timestamp at all.
- docs/LOCAL_RUNTIME.md says when the bridge polls and that stale requests are
  discarded.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ery-consumption-optimization-961b0f

perf: park always-on bridge polls while nothing can write to them
A copied screenshot or image gave the message field nothing to paste, so
it had to be saved and re-attached from the gallery. The field now offers
Paste in its selection toolbar when the clipboard holds an image and no
text, and Ctrl+V / Shift+Insert attach it from a hardware keyboard. The
image goes through the same decode-and-attach path as the gallery picker,
so it shows the usual thumbnail before sending. A clipboard that carries
text keeps the normal text paste.

The gallery picker now shares decodeImageUri with the paste path, which
reads through the existing 25 MB readLimited cap instead of readBytes().

Closes yuga-hashimoto#401

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Resolve the clip's URI types off the main thread: getType() calls into
  the app that copied the image, which can be slow to answer.
- Accept only content:// URIs from the clipboard, so a file:// URI chosen
  by the clip's owner cannot point at this app's own files.
- Attach on the first KeyDown only, so holding Ctrl+V does not attach the
  same image repeatedly.
- Tell the user when a pasted or gallery image cannot be attached, using
  the existing attachment_load_failed string.
- Move decodeImageUri next to decodeSampledBitmap/readLimited and cover
  the 25 MB readLimited boundary with unit tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ew-new-issues-9d0867

feat(chat): paste clipboard images into the composer
Adds an Edit action to the code viewer for runtimes whose workspace is a
folder on the device (OpenCode local, Claude Code, Codex, Antigravity).
Remote OpenCode servers have no write endpoint, so they stay read-only
(RuntimeCapabilities.fileEditing).

Writes go through the host path of the guest folder via WorkspaceFileWriter
(plain Kotlin, unit tested):
- root and target are canonicalised, so `..` and symlinks cannot leave the
  selected workspace; `/` is never an editing root
- only existing UTF-8 text up to 256 KB; binary, non-UTF-8, read-only
  files and directories are refused
- temp file + fsync + atomic move; the original survives any failure and
  permission bits are preserved
- the editor starts from the exact on-disk text and the save is refused if
  the file changed meanwhile, so an agent's concurrent edit is not lost

The viewer guards unsaved changes on back, system back and stop-editing, and
shows localized errors (15 new strings in all 8 locale files).

Refs yuga-hashimoto#402 (file-editing part only).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…it-local-workspace-files

feat: edit files in local workspaces from the code viewer (refs yuga-hashimoto#402)
Brings in yuga-hashimoto/and-code up to 414c780:
- edit files in local workspaces from the code viewer
- paste clipboard images into the composer
- schedule bridge expiry and polling fixes

Conflict resolution:
- CodeViewerViewModel: upstream's editing backend (exact on-disk read,
  write refused when the file changed underneath, workspace bound) with
  the fork's undo/redo, replace-all and save confirmation on top. The
  fork's shell-based save is no longer used by the viewer.
- CodeViewerScreen: the fork's editor UI, now on upstream's state and
  strings; the fork-only duplicates are removed. The instrumented
  editing test follows the fork's behaviour, where back closes the
  editor before it leaves the screen.
- ChatComposer: upstream's image paste (toolbar and Ctrl+V) moved into
  the composer file the fork split out of ChatHomeScreen.
- strings: both sides kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJMh6NPYDcxwnfsgGh54JU
Back during a save could open the discard dialog and then lose to the
save landing; it now waits. The editor is read-only while saving, turns
off autocorrect and auto-capitals for code, and a test pins that a save
confirms once and clears the undo history. Two imports the merge left
unused are dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJMh6NPYDcxwnfsgGh54JU
@Demonic1594
Demonic1594 marked this pull request as ready for review October 26, 2026 19:54
@Demonic1594
Demonic1594 merged commit 041d7c8 into main Oct 26, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants