Skip to content

feat(mobile): share target with a share gate for new and existing sessions - #4779

Merged
iscekic merged 11 commits into
mainfrom
feat/mobile-share-target
Jul 27, 2026
Merged

feat(mobile): share target with a share gate for new and existing sessions#4779
iscekic merged 11 commits into
mainfrom
feat/mobile-share-target

Conversation

@iscekic

@iscekic iscekic commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

What

  • Kilo now appears as a share target in the iOS and Android OS share sheets, accepting plain text, URLs, images, and documents — the types the new-session composer already accepts.
  • Sharing into Kilo opens a share gate: a native formSheet that previews the payload and lets the user send it to a new session or an existing session — cloud-agent or remote CLI. Nothing is ever auto-sent.
  • CLI destinations are capability-gated honestly: a CLI session whose owning CLI is offline, or that can't receive files when the payload carries files, stays listed but tapping it surfaces a specific blocking alert explaining why — the payload is never dropped or partially delivered.

How

  • expo-share-intent@6.1.1 (the SDK 55 line), plus a small local wrapper config plugin for two concrete issues: upstream requires @expo/plist without declaring it (breaks under pnpm's isolated linker), and iosShareExtensionName: 'Kilo' collides with the main app target name, so the wrapper forces target ShareExtension and rewrites the share-sheet display name to Kilo (failing prebuild loudly if the rewrite ever misses).
  • Shared files are copied into the app cache during normalization — before the gate renders — so the presigned PUT can read them after the share extension exits, and the gate's pre-flight and the composer's authoritative validation read the same bytes.
  • Payloads live in a small keyed, non-reactive module store; delivery is keyed by a shareId route parameter end to end — no reactive store, no claim/generation state.
  • The gate opens from the app shell via an explicit, unit-tested readiness predicate plus React state (no try/catch-as-readiness, no timers); +native-intent swallows the share deep link (initial ? '/' : null) so a warm share never yanks the user to root.
  • Gate states — happy / empty / retryable / non-retryable (classification) / stale-share / signed-out — each with pure-selector tests including CTA presence or complete absence.
  • Destinations come from the org-scoped stored page only (active sessions are used solely as an id/capability lookup to mark and hoist live rows and to read capabilities.attachments — their schema carries no organization, so rendering them as rows would leak cross-org), capped at 30, cloud-agent and CLI.
  • Commit is enabled only once payload validation has settled; composers prefill imperatively through the existing voice-draft helper and upload hook (showAttachments keeps prefilled chips visible before the session resolves; the picker stays gated on attachmentsEnabled).

Notable

  • pnpm-workspace.yaml gains packageExtensions for @sentry/react-native and expo-share-intent: both have undeclared upstream dependencies that break Expo config evaluation outside the Expo CLI under pnpm (autoInstallPeers: false). This blocked the mandatory native rebuilds on both platforms (:expo-constants:createExpoConfig / the EXConstants Xcode script phase). The failure class is pre-existing — it lives at the base commit — and is surfaced here because this feature requires real native builds.
  • pnpx expo-doctor currently fails spawning expo config --json --full; the failure reproduces at the base commit, so it is pre-existing and unrelated. npx expo config itself evaluates fine, and expo-doctor does not run in CI.
  • The Google reversed-client-id URL scheme is not verifiable in the local environment (the env var resolves empty here, so the Google plugin is not registered); the kiloapp scheme is verified in the generated Info.plist, and the generated AndroidManifest.xml carries ACTION_SEND / ACTION_SEND_MULTIPLE.
  • Release-time follow-ups for the next store build (not this PR): the new iOS share extension target needs its own provisioning profile, and app group group.com.kilocode.kiloapp must be registered and enabled on both the app and extension identifiers.

Tests

From apps/mobile: pnpm format, pnpm typecheck, pnpm lint (0/0), pnpm check:unused, pnpm test (237 files / 2014 tests) all pass; git diff --check clean. Unit coverage includes every gate feature state (selection logic and CTA presence/absence), the readiness predicate per clause, the payload store semantics, the navigation resolver, the destination selector (live hoist, cap, no cross-org rows), the CLI destination admission matrix (offline / no-file-capability / capable / text-only), the validated-hasFiles rule, the capabilities-preserving live-sync merge (upgrade / downgrade / legacy-absent on snapshot and heartbeat), the run-on share-staged latch and spawn-cancel predicates, the send-path silent-drop refusal, copy-failure surfacing (unreadable notes), supersede/eviction cache cleanup, the preflight validator, and the prefill ordering/failure rules.

Device E2E on iOS (real OS share sheet from Safari and Photos against a worktree-local stack, a live kilo remote CLI, and an advertised CLI instance): OS-share → gate delivery warm and cold; CLI destinations listed with live hoist; text delivered to a live CLI session (CLI pane mirrored); files admitted to a live attachment-capable CLI with the chip staged from first paint; offline-CLI and Run-on blocking alerts with payload intact; second share replacing the gate; stale-share; abandonment; cache hygiene — all pass. The files end-to-end upload leg is blocked locally by a stale shared dev R2 credential (401 on the signed PUT, reproduced from the host; presign itself returns 200) — an environment limitation, not product code. Android: emulator failed to boot twice under host load (environment); the deltas since the last full-matrix run are platform-agnostic JS and the native Android share plumbing is untouched.

@iscekic iscekic self-assigned this Jul 26, 2026
@iscekic

iscekic commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

(bot) Standin review of the full PR diff at head 748246a82 (git diff origin/main...HEAD).

This review was produced by a dedicated reviewer agent (fresh mobile-reviewer, high-reasoning model) because the automated reviewer posts nothing on this PR — the Kilo Code Review status check posts no findings, so this standin review is the review of record. It is not a Kilobot review.

Checks the reviewer ran (all green): pnpm typecheck, pnpm test (234 files / 1965 tests), pnpm lint (0 warnings), pnpm check:unused.

Findings (ordered by severity)

1. Major — silent share-content loss via "New session" → "Run on" remote instance

  • apps/mobile/src/app/(app)/agent-chat/new.tsx:273 (the isRepositorySectionVisible(runOnInstance) branch) with apps/mobile/src/components/agents/use-remote-spawn-dispatch.ts:120-130 and remote-spawn-composer.tsx:34-70.
  • Failure mode: on a personal (non-org) route the Run-on selector is visible. After useSharePrefill consumes the payload (text into promptRef, files uploaded through the screen-level attachments hook), selecting a kilo remote instance unmounts NewSessionPrompt for RemoteSpawnComposer, which has no prompt box and no attachments. remoteSpawn.spawn(connectionId) sends neither promptRef nor the uploaded attachments, and getSpawnedAgentSessionPath carries no shareId. The already-consumed shared text/files vanish with no message.
  • Required outcome: when a shareId is staged on the new-session route, the remote-spawn path must either carry the staged content, be blocked/hidden with user feedback, or the payload must remain unconsumed. It must never be silently discarded.

2. Medium — second share can be silently dropped by the ingest effect

  • apps/mobile/src/app/_layout.tsx (ingest effect keyed only on hasShareIntent with eslint-disable react-hooks/exhaustive-deps) and share-gate-sheet.tsx:137-161 (abandon/unmount resetShareIntent).
  • Failure mode: a second share intent arriving while the first normalizeShareIntent (cache-copying up to 5 files) is in flight does not re-fire the effect; the first ingest's resetShareIntent() then clears the second intent. The gate's resetShareIntent() on dismiss/unmount can likewise clear a just-arrived intent before ingest reads it. Silent loss, no toast.
  • Required outcome: every received share intent is either ingested or surfaced as an error; none is cleared unread.

3. Medium — staged attachments can be silently omitted from the wire at send

  • session-detail-content.tsx:514-551 (resolveSendAttachmentKind'none') with chat-composer.tsx:220 (new showAttachments = attachmentsEnabled || upload.attachments.length > 0 fallback).
  • Failure mode: the gate's admission-time capability lookup (activeSessions.list) is the only guard. If the manager's supportsAttachments/activeSessionType disagrees at send time, or flips after staging (CLI disconnects mid-compose), the strip stays visible and send proceeds, but both attachments and attachmentParts are omitted — text sends without the files and without any feedback.
  • Required outcome: when attachments are staged but the resolved session cannot take them, block or warn explicitly; never silently send text-only.

4. Low — share to a read-only destination strands the consumed payload in a disabled composer

  • share-cli-admission.ts:20-22 (non-cli rows pass unconditionally) + session-detail-content.tsx:488-494 (isComposerDisabled includes isReadOnly).
  • Failure mode: a cloud-agent session that resolves read-only admits the share; the payload is taken and staged into a composer where canSend is false. No message; the content is stuck until the user re-shares.
  • Required outcome: an admission or post-resolve guard with a user-visible message, or leave the payload unconsumed.

5. Low — cache-copied shared files are never deleted

  • src/lib/share-payload.ts:96-105 (defaultCopyToCache) vs. take/clear/evict (map entry only).
  • Failure mode: consumed, abandoned, and evicted payloads leave their cache copies behind until OS purge. App-private directory, so this is hygiene, not world-readability.
  • Required outcome: best-effort file cleanup on consume/clear/evict, or a documented rationale for relying on OS purge.

6. Nit — +native-intent.tsx:10 drops any deep link containing dataUrl=<shareExtensionKey>, including crafted ones; matches the upstream expo-share-intent pattern, but the reviewer could not verify expo-router's null-return semantics against installed sources in this worktree. Confirm null means "no navigation" on the pinned expo-router 55.0.16.

7. Nit — pnpm-workspace.yaml packageExtensions also patches @sentry/react-native → @expo/config-plugins, unrelated to this feature; and with @expo/plist now declared via packageExtensions, the Module._resolveFilename patch in plugins/withExpoShareIntent.js:19-36 is likely redundant (kept deliberately per its comment).

Verified contract points (no findings)

  • Four-state contract satisfied: stale-share and non-retryable-classification show a message with no CTA; retryable shows a message + Retry + New session; empty shows a message + New session; all asserted in share-gate-state.test.ts (including precedence and activeIsError-never-retryable).
  • CLI admission matrix matches the contract exactly, with full unit coverage.
  • Destinations derive only from the org-scoped stored page (cliSessionsV2.list, page size 30 = SHARE_DESTINATION_CAP); the active list is used only as an id/capability lookup — no cross-org row leak. shareId is an opaque in-memory key; no injection path.
  • Payload lifecycle (commit vs. abandon, replace semantics, take-once prefill, text-before-files ordering) is coherent across gate, navigator, and composer; no double-delivery path found.

Verdict: changes-required (finding 1 is a reachable silent-loss path; findings 2–3 should also be addressed before merge).

Triage and repairs are in progress; outcomes (fixes and any rejections with evidence) will be posted as follow-up comments.

@iscekic

iscekic commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

(bot) Triage outcome for the standin review posted above. Repairs landed in 26d0c38ee and a5efdc2c6 (head a5efdc2c6), reviewed by three consecutive fresh mobile-reviewer rounds (round 3: "No findings", verdict: wave is ready to ship).

Fixed

Finding 1 (Major) — "Run on" remote instance swallowing a staged share. Now blocked for the whole staged lifetime, not just while shareId sits in the URL: a one-way render-time latch (useShareStagedLatch) marks the screen as share-staged from the first render that observes a shareId; selecting a remote instance while latched is refused with a specific alert (Can't share to a new remote session — it names the working alternative, picking the running CLI session from the share list). A share arriving while an instance is selected silently resets to Cloud Agent (the visible swap to the prefilled composer is the feedback), and an in-flight spawn that readies while a share is staged no longer navigates: it toasts (Shared content can't start a remote session. The spawned session is in your session list.) and leaves the user on the cloud composer.

Finding 2 (Medium) — second share dropped by the ingest effect. The _layout.tsx ingest effect is now keyed on [hasShareIntent, shareIntent] — per-intent identity, which is stable per native delivery in expo-share-intent@6.1.1 — so a newer intent cancels and supersedes an in-flight ingest instead of being cleared by the first ingest's reset. resetShareIntent (unstable in the provider) is routed through a ref so it can't re-fire the effect per render. The share gate no longer calls resetShareIntent at all: the gate only ever sees intents the layout ingest already consumed, so its resets could only clear a newer, unread intent.

Finding 3 (Medium) — staged files silently omitted at send. handleSend now refuses exactly that case: when the resolved wire kind is 'none' while the composer holds uploaded attachments (e.g. the CLI's capability flipped after staging), send is blocked with "This session can't receive files. Remove the attachments to send your message." and the throw preserves the draft and attachments, instead of silently sending text-only.

Finding 5 (Low) — cache-copied files never deleted. clearSharePayload and cap eviction now best-effort delete the payload's cache copies (deleteAsync, idempotent). takeSharePayload deliberately never deletes: the composer's uploads read those files after take, and the take→clear interleaving (composer still needs the files) is pinned by a test.

Rejected (with evidence)

Finding 4 (Low) — read-only destination strands the payload. This is plan-documented known-expected behavior (Batch H plan assumption 7): read-only-with-messages sessions mount no composer, so a delivered share is never consumed from the store and remains intact; the zero-message read-only case mounts a disabled composer where the staged content stays visible and recoverable. Reachability is additionally narrowed by the new CLI admission gate (share-cli-admission.ts), which blocks the common case — a CLI session that is not live — at destination-selection time with a specific alert. The only residue is a seconds-wide race (CLI disconnects between gate tap and session resolve), accepted by the plan.

Finding 6 (Nit) — +native-intent.tsx drops dataUrl= deep links. Verified against the installed expo-router@55.0.16 type contract (build/types.d.ts, NativeIntent.redirectSystemPath): "When a falsy value is returned (for example, null), no redirection occurs and the app stays on the current path." Returning null for share deep links (warm path) is the intended no-navigation behavior, and dropping a crafted dataUrl= link is safe — it cannot navigate.

Finding 7 (Nit) — packageExtensions scope / redundant resolver patch. The @sentry/react-native → @expo/config-plugins extension is not unrelated: this feature requires real native builds, and @sentry/react-native's undeclared dependency breaks Expo config evaluation under pnpm's isolated linker at build time (failure class pre-exists at the base commit and is documented in the PR description). The Module._resolveFilename patch in plugins/withExpoShareIntent.js is deliberately kept as a loud fail-safe where packageExtensions may not apply (e.g. EAS builds).

Local checks on the repair head from apps/mobile: pnpm format, pnpm typecheck, pnpm lint (0/0), pnpm check:unused, pnpm test (235 files / 1986 tests) — all pass. Device E2E covering the CLI destinations and these guards runs next; results will be posted.

@iscekic

iscekic commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

(bot) Device E2E update for the follow-up scope (CLI share destinations + silent-loss guards), iOS simulator, worktree-local stack.

Round 1 (head a5efdc2c6) — 10 pass / 1 fail / 1 blocked

Row Result
OS share → gate delivery, warm (Safari URL → gate → New session → prefilled, nothing sent) PASS
OS share → gate delivery, cold start PASS
CLI destinations listed (live row hoisted with live marker; offline CLI rows present) PASS
Files → live capable CLI FAIL — root-caused to active-sessions-live.ts: WS snapshot/heartbeat merges rebuilt cached rows without capabilities, so a live CLI session advertising attachments: true read as incapable and the gate wrongly showed This session can't receive files
Text → live CLI (send delivered, CLI pane mirrored) PASS
Offline CLI tap → Session not connected alert, gate open, payload intact PASS
Run-on gate BLOCKED (no advertised CLI instance in that environment)
Cloud-agent destination (via live session) PASS
Second share while gate open (Y replaces X) PASS
Stale-share deep link PASS
Abandonment (no payload anywhere after dismiss) PASS
Cache hygiene (share-* files gone after abandon) PASS

The round-1 failure was fixed in af87eeac9 (merges now carry capabilities — wire value wins when present, cached value preserved when omitted) and reviewed.

Round 2 (head 0e2703447) — all rows pass, one environment limitation

Row Result
Files → live CLI: admission + composer staging with attachment chip from first paint PASS
Files → live CLI: upload + send end-to-end BLOCKED — environment: presign returns 200 with a well-formed signed URL, but the R2 PUT is rejected 401 Unauthorized. Independently reproduced from the host: a presigned GET on a nonexistent key also 401s, and a nextjs restart with fresh env injection does not help — the shared local dev R2 credential for cloud-agent-attachments-dev is stale/mis-scoped at the source. Not product code: the API path, URL shape, and signed headers are correct, and this affects all local attachment uploads equally.
Run-on gate (instance listed → pick → Can't share to a new remote session, selection unchanged, prefill intact) PASS
Warm smoke PASS
Text → live CLI, delivered PASS
Offline reject PASS

Android was attempted for this follow-up and is recorded as an environment limitation: the emulator (AVD kilo_pixel9_api35, -gpu host, locks cleared) failed to boot to adb visibility twice under current host load. The changes since the last full-matrix pass are platform-agnostic JS; native Android share plumbing is untouched by them. Prior Android evidence on this branch: Kilo appears in the OS share chooser, and a direct SEND opens the gate.

Share-extension end-to-end delivery into a remote CLI session will be exercised in staging/production where the R2 credentials are valid; locally everything up to the R2 boundary is verified.

@kilo-code-bot

kilo-code-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the incremental diff since the last review (commit ca833fee46ac79b381) — the abandon-on-committed-id fix, the FIFO pending-navigation queue, and the organizationId param propagation on focused delivery — and found no high-confidence security, correctness, or memory-leak issues in changed lines.

Files Reviewed (4 files)
  • apps/mobile/src/components/share/share-gate-sheet.tsx
  • apps/mobile/src/components/share/share-payload-navigator.tsx
  • apps/mobile/src/lib/share-navigation.ts
  • apps/mobile/src/lib/share-navigation.test.ts
Previous Review Summary (commit ca833fe)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit ca833fe)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the full mobile share-target feature diff (44 files, ~4,700 changed lines) across payload/navigation libraries, share-gate UI, CLI admission/active-sessions merging, app shell navigation, composer/session-detail wiring, and build config; found no high-confidence security, correctness, or memory-leak issues in changed lines.

Files Reviewed (44 files)
  • apps/mobile/app.config.ts
  • apps/mobile/package.json
  • apps/mobile/plugins/withExpoShareIntent.js
  • apps/mobile/src/app/(app)/_layout.tsx
  • apps/mobile/src/app/(app)/agent-chat/[session-id].tsx
  • apps/mobile/src/app/(app)/agent-chat/new.tsx
  • apps/mobile/src/app/(app)/share-gate.tsx
  • apps/mobile/src/app/+native-intent.tsx
  • apps/mobile/src/app/_layout.tsx
  • apps/mobile/src/components/agents/chat-composer.tsx
  • apps/mobile/src/components/agents/new-session-cloud-form.tsx
  • apps/mobile/src/components/agents/new-session-prompt.tsx
  • apps/mobile/src/components/agents/session-detail-content.test.ts
  • apps/mobile/src/components/agents/session-detail-content.tsx
  • apps/mobile/src/components/agents/session-detail-send-attachment.ts
  • apps/mobile/src/components/agents/session-row.tsx
  • apps/mobile/src/components/agents/use-remote-spawn-dispatch.ts
  • apps/mobile/src/components/share/share-cli-admission.test.ts
  • apps/mobile/src/components/share/share-cli-admission.ts
  • apps/mobile/src/components/share/share-destination-list.tsx
  • apps/mobile/src/components/share/share-destinations.test.ts
  • apps/mobile/src/components/share/share-destinations.ts
  • apps/mobile/src/components/share/share-gate-sheet.tsx
  • apps/mobile/src/components/share/share-gate-state.test.ts
  • apps/mobile/src/components/share/share-gate-state.ts
  • apps/mobile/src/components/share/share-payload-navigator.tsx
  • apps/mobile/src/components/share/share-payload-preview.tsx
  • apps/mobile/src/components/share/share-payload-validation.test.ts
  • apps/mobile/src/components/share/share-payload-validation.ts
  • apps/mobile/src/lib/active-sessions-live.capabilities.test.ts
  • apps/mobile/src/lib/active-sessions-live.ts
  • apps/mobile/src/lib/agent-attachments/validate.test.ts
  • apps/mobile/src/lib/agent-attachments/validate.ts
  • apps/mobile/src/lib/pending-share-navigation.test.ts
  • apps/mobile/src/lib/pending-share-navigation.ts
  • apps/mobile/src/lib/share-navigation.test.ts
  • apps/mobile/src/lib/share-navigation.ts
  • apps/mobile/src/lib/share-payload.normalize.test.ts
  • apps/mobile/src/lib/share-payload.test.ts
  • apps/mobile/src/lib/share-payload.ts
  • apps/mobile/src/lib/share-prefill.test.ts
  • apps/mobile/src/lib/share-prefill.ts
  • apps/mobile/src/lib/share-to-new-remote-session.test.ts
  • apps/mobile/src/lib/share-to-new-remote-session.ts
  • apps/mobile/src/lib/use-new-session-share-remote.ts
  • apps/mobile/src/lib/use-share-aware-run-on-change.ts
  • apps/mobile/src/lib/use-share-staged-latch.ts
  • pnpm-workspace.yaml

Reviewed by claude-sonnet-5 · Input: 46 · Output: 9.2K · Cached: 1.2M

Review guidance: REVIEW.md from base branch main

@iscekic

iscekic commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

(bot) Standin review loop — final outcome.

Round 3 (0e2703447, approve-with-minor-fixes): 4 findings, all repaired in 8f9b63179 — copy-failed files now surface in the gate preview as {name}: Couldn't read this file via the existing rejection-notes channel (new unreadable reason); a superseded pending share is released at supersede time (resolveSupersededPendingShareId + clearSharePayload, which also frees its cache copies); the shareIntentError effect now uses the stabilized reset ref so one provider error produces exactly one toast + one Sentry event; truncation copy interpolates AGENT_ATTACHMENT_MAX_FILES.

Round 4 (8f9b63179, approve with one low): cancelled mid-copy ingests could leak their completed cache copies outside the store lifecycle. Repaired in ca833fee4 — the cancelled path now calls discardUnstoredSharePayload, closing the last untracked-copy path (unit-tested).

Round 5 (ca833fee4): fresh full-diff standin review returned "No findings." (verdict: approve). Its verified-sound list explicitly re-checked the capabilities merge rule on both merge entry points, the six-state gate machine and CTA matrix, the admission matrix incl. validated-hasFiles wiring, the payload lifecycle (prefill ordering, unreadable notes, take-never-deletes, supersede/abandon clears), the run-on remote gate (render-time latch, spawn cancel), navigation/deep-link handling, and repo conventions.

Repair-loop record: every valid finding went implementer → fresh reviewer → commit; two findings were rejected with evidence earlier in this thread (read-only stranding per plan assumption 7; +native-intent null semantics per the installed expo-router contract). Local checks on the final head: pnpm format, pnpm typecheck, pnpm lint (0/0), pnpm check:unused, pnpm test (237 files / 2008 tests) — all pass.

@iscekic

iscekic commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

(bot) Standin review, final-head pass at ca833fee4 (git diff origin/main...HEAD) — produced by a fresh dedicated reviewer agent because the automated reviewer posts nothing on this PR. Round 5 on this same diff had returned "No findings."; this independent round (per supervisor direction) found three NEW items. Verdict: approve-with-minor-fixes.

1. Medium — focused delivery drops the committed organizationId. share-payload-navigator.tsx's deliver() compares paths only, then router.setParams({ shareId }) — preserving a stale organizationId on a focused new-session screen. Multi-org path: screen mounted at /agent-chat/new?organizationId=orgA, global org switched to orgB, gate commits the orgB href, pathname matches, delivery keeps orgA → the session is silently created in the wrong org. (Existing-session delivery unaffected — row org is intrinsic and the personal route self-heals.)

2. Low — single-slot pending navigation drops a previously committed share. Commit A → gate dismisses → share B ingested and gate replaced → commit B before A delivers: setPendingShareNavigation({B}) overwrites the undelivered {A}; A's committed payload strands in the store (it's exempt from every gate-side clear by design).

3. Low — abandon() ignores the committed latch. dismiss()abandon() clears the owned shareId unconditionally; a close gesture inside the post-commit dismiss window clears the committed payload while its pending navigation still delivers → empty composer, silent loss. Both sibling clear paths exempt the committed id; this one must too.

Triage: all three verified valid against the code — repair wave dispatched. Outcomes will be posted here; a fresh standin review follows the push.

…tinations, keep committed payloads on dismiss
@iscekic

iscekic commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

(bot) Round-6 standin findings — repaired and closed.

Repairs landed in 6ac79b381, reviewed by a fresh mobile-reviewer ("No findings."):

  • Wrong-org focused delivery: the navigator's focused branch now sets organizationId parsed from the committed href (parseShareHrefParams), so a mounted new-session screen can no longer create the session under a stale org param; undefined clears it, covering personal↔org in both directions.
  • Committed navigation overwrite: the pending-navigation slot is now a FIFO queue capped at SHARE_PAYLOAD_MAX_ENTRIES (oldest dropped beyond cap, matching the payload store's eviction order), so a second commit can no longer discard an undelivered committed share.
  • abandon() committed latch: the gate's abandon path now exempts the committed share id, exactly like the param-change and unmount cleanups — a close gesture in the post-commit dismiss window no longer empties the destination composer.

Regression E2E on the pushed head (iOS, real OS share sheet): 4/4 PASS — warm share → New session prefill (nothing sent), text delivered to the live CLI session (CLI pane mirrored), offline CLI reject unchanged, abandonment unchanged.

Round 7 standin review over the full diff at 6ac79b381: "No findings" — the review loop is closed on the final head. Local checks: pnpm format, pnpm typecheck, pnpm lint (0/0), pnpm check:unused, pnpm test (237 files / 2014 tests) — all pass; CI all green on 6ac79b381; PR mergeable and assigned.

@iscekic
iscekic merged commit 0515d14 into main Jul 27, 2026
68 checks passed
@iscekic
iscekic deleted the feat/mobile-share-target branch July 27, 2026 09:07
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