Skip to content

Mobile J5 gap audit: verify findings on the iOS Simulator, then decide scope #337

Description

@Jacksondr5

Part of #40. Related: #128 (Squadron-first creation), #254 (archive semantics), #285 (structured A2A records), #182 (J5 change stream).

This is a read-only code audit of apps/mobile against the J5 product docs, the web app, and J5's commit history. Nothing in it has been observed in a running app yet. The audit host is Linux and can't run the iOS Simulator. Every "what the user sees" statement below was read from code, and items marked inferred were reasoned rather than traced.

All file:line references are against origin/j5/main at 89c5547b75 (2026-09-27). Check out that commit to read them. Line numbers will drift on newer heads.

For the agent picking this up on the Mac

Your job is verification and evidence, not implementation or decisions. Leave §4 (Decisions) for Jackson.

  1. Follow the test-t3-mobile skill. On the simulator host, run node scripts/mobile-native-client.ts ensure ios <device-id> before starting Metro.

  2. Seed an isolated server with a copy of real data (VACUUM INTO from ~/.j5code/userdata/statev2.sqlite; see AGENTS.md → Test data). Never point the app at the live install. An empty database will not reproduce most of these. You need threads that contain:

    • peer A2A messages, an Exchange ask with a reply, a machine-sender message, and a person's Inbox answer
    • a silence or exchange-dropped notice
    • a spawned Peer Agent (for its spawn brief)
    • a Crew with seats, plus an open propose_crew proposal
    • a queued A2A delivery
    • an artifacts/… link in a message

    If the copy lacks one of these, create it on the isolated server with real agents rather than hand-writing SQL.

  3. For each checkbox in §1, mark it confirmed or not reproduced, attach a screenshot, and add one line on anything that differs from the description. Push images to j5/evidence under mobile-audit-40/ (fetch and rebase first; other agents push there too). Embed them as raw links. Check each with file and curl -sI before posting.

  4. Post the results as one comment on this issue. If a finding is wrong, say so plainly.


1. Defects visible today (verify each)

  • B1. Every A2A delivery renders as a raw envelope in a right-aligned "you" bubble. Peer messages show [Cross-agent message from agent:j5:a2a:<id> in squadron <id>], the body, then the agent-facing send_message(...) instructions. There is no sender name and no reply state. A tappable "Sent by another agent" line is the only attribution. Evidence: apps/mobile/src/features/threads/ThreadFeed.tsx:1606-1660; lib/threadActivity.ts:1546-1566 does no A2A classification. Size M.

  • B2. Platform notices look like the user wrote them. These get a right-aligned bubble with no label at all:

    • silence notices
    • exchange-dropped notices
    • Crew gate and seat-finished notices (createdBy: "system")
    • the person's own Inbox answers (createdBy: "user")

    Same evidence as B1.

  • B3. Spawn briefs show raw <j5_spawn_context> / <spawner_brief> XML with participant, squadron, and spawner ids. fix(web): peer-agent spawn briefs render as attributed briefs instead of raw XML #186 fixed this on web only. Nothing on mobile shows who spawned a thread. Size S–M.

  • B4. Queued A2A deliveries show the raw envelope in the queue control. Web shows "From — ". Evidence: ThreadQueueControl.tsx:211-230, threadQueueControlPresentation.ts:38. Size S.

  • B5. Scheduled tasks created on mobile can never run. The mobile form has no thread binding and always sends threadId: null. J5's server refuses null-thread tasks when they fire ("Scheduled new-thread execution is unsupported…"), and nothing warns at save. Evidence: SettingsScheduledTasksRouteScreen.tsx:612; apps/server/src/scheduledTasks/ScheduledTaskService.ts:517-526. Size S–M.

  • B6. artifacts/… links in chat open as workspace files. Artifacts live in the state directory, not the workspace, so the file isn't there. Inferred: file-not-found. Evidence: ThreadFeed.tsx:2222-2248 → features/files/filePath.ts:71. Size S.

  • B7. The Crew-seat archive refusal points at a page mobile doesn't have ("retire the whole Crew with Archive crew on the Fleet page"). Evidence: crewSeatArchiveGuard.ts:25-26, shown by useThreadListActions.ts:82-88. Size S.

  • B8. Android notification settings copy says "select T3 Code", but the OS lists the app as J5 Code. Evidence: SettingsNotificationsRouteScreen.tsx:337,489. Size S. (Android only. Note it if you can't check it.)

  • B9. The Playbook Author launch records the thread as web-created. It omits creationSource, which defaults to "web". That default is how it gets a Squadron home while every other mobile launch doesn't. Evidence: packages/client-runtime/src/j5/playbooks.ts:88-140; operations/commands.ts:670-671. Size S. Verify by launching an author from mobile and checking the thread's creation source and home.

  • B10. The Playbook Author Squadron picker ignores read-only connections. It hard-codes available: true, so it offers a launch the server will refuse. Evidence: j5/playbooks/PlaybookLibrarySettingsScreen.tsx:49-53. Size S.

  • B11. Inferred, needs a repro: a persona thread can send a non-persona model selection on mobile. Web forces resolvedModelSelection through personaComposerSelection.ts; mobile doesn't, and the server rejects changes to an immutable route. Evidence: ThreadRouteScreen.tsx:477; ThreadComposer.tsx:796. Size S.

  • Stuck Captain. When a Captain calls propose_crew, the mobile user has no way to approve, edit, or decline. The proposal never expires, and no agent tool resolves it. Confirm what the Captain's thread shows on mobile.

  • Branding. Confirm which surfaces still show T3:

    • app icon
    • splash
    • header/sidebar lockup (components/CompactBrandTitle.tsx:33-39)
    • loading screen (BrandMark.tsx:30)
    • widget
    • Live Activity title (remoteRegistration.ts:531, ThreadComposer.tsx:617)

Web-side bugs found along the way

These affect the shared presenters. Fix them before lifting code for mobile.

  • Exchange-dropped notice falls through the web parser. The notice ends with "terminal notice", but the web parser requires "delivery signal". Web therefore shows it as an open "Raw A2A delivery" block. Evidence: ThreadA2ARenderer.tsx:243-250 vs LifecycleService.ts:159-163.
  • Web A2A cards deviate from thread-a2a-rendering.md. They show "Closed your exchange" and "Reply" badges, a raw recipientId on sent cards, and Via Inbox · human:<uuid>. Sender names aren't links.
  • The Fleet badge counts the wrong thing. It counts open asks (fleet.logic.ts:196-200). fleet-page AC23 says it counts measured problems and never open asks.
  • Three rules for which threads the list shows.
    • Web's isSidebarMember hides every agent-spawned thread unless pinned (SquadronScope.logic.ts:35-42).
    • fleet-page AC3 says nothing is hidden because of how it was created.
    • crews.md says Crew members are hidden.

2. Missing features by area

Squadrons (#128)

Mobile has no Squadron UI. The only Squadron read is the Playbook Author picker.

Gap Mobile today Web reference Size
Choose a Squadron for a new thread Every door says "Choose project": new task, home, adaptive layout, keyboard, command palette, thread header, share sheet, playbook start-chat ChatView.tsx:8481-8490,10744,10767; SquadronPicker.logic.ts; useSquadronNewThreadOnBranch.ts L
Give mobile threads a Squadron home See the paragraph below this table — M
Scope the thread list by Squadron; show a thread's Squadron on its row The filter menu offers Environment and Project only (home-list-filter-menu.ts:44,68) Sidebar.tsx:2452,2532,4470; ThreadCardIdentity M
Create, rename, and delete a Squadron; first-run gate Only Add Project SquadronCreate*, SquadronActions.logic.ts, FirstRunGate M
Cross-environment correctness Mobile already merges threads across environments. A Squadron port must key by scoped ref, lock a draft's environment to its Squadron, honour canOperate, and show old servers as unsupported readSources.ts; ChatView.logic.ts canSelectDraftEnvironment (in above)

Why mobile threads have no home. lib/projectThreadStartTurn.ts:51 sends creationSource: "mobile" and no squadronId. The server maps that to a no-home exception (SquadronLaunchPolicy.ts:49-55) and silently drops any squadronId that does arrive. As a result:

  • the agent can't message or spawn until it calls join_squadron
  • the thread is missing from Squadron-scoped web sidebars
  • the thread is missing from the Fleet page

Shared code. The J5 atoms are already in packages/client-runtime/src/j5/, and mobile already instantiates them (apps/mobile/src/j5/state.ts). They cover squadrons, createSquadron, renameSquadron, deleteSquadron, thread homes, and listThreadHomes.

Pure logic that is still web-local and needs lifting:

  • SquadronPicker.logic.ts (drop the web Project type)
  • SquadronScope.logic.ts
  • SquadronCreate.logic.ts
  • FirstRunGate.logic.ts
  • the wiring in ThreadHomesClient.ts and SquadronDirectory.ts

For refresh-on-focus, mobile can reuse the AppState pattern in j5/playbooks/useActivePlaybookRefresh.ts in place of web's document.visibilityState.

A2A messages and the Inbox (see #285)

Gap Notes Size
Delivery cards (peer, machine, person, silence, raw fallback) Fixes B1–B2. The parsers in ThreadA2ARenderer.tsx are pure and need only {id, role, text, createdBy}, so lift them to client-runtime. Peer deliveries now carry senderThreadId, so sender links need no server change. M
Participant names instead of ids /api/j5/a2a/client-reads/participant-identities is web-local and missing from J5_API_PATHS. Add it to contracts and to client-runtime http.ts. S
Cards for the agent's own send_message Today this is a generic tool row showing the raw tool name, with the body only in the JSON inspector. J5 tools aren't in t3McpToolPresentation.ts. S–M
Crew notice cards crewNotices.logic.ts has no imports and lifts as-is. M
Human Inbox (list, answer in place, Replied shelf, all environments) Absent. The inbox, count, and answer atoms are already in client-runtime and in mobile's J5 atom instance. The urgency ordering and answer/retry logic in HumanInboxPage.tsx is web-local. FORK.md case 34 defers it: "Native mobile screens are deferred". M–L
Inbox badge Absent. mergeOpenInboxCounts is shared. S–M
Notify the person when an agent asks them something Absent on both clients. Push phases come only from upstream's pendingRuntimeRequest, and a J5 ask isn't one, so the user likely gets "finished" rather than "needs you". This needs new server work on the relay. L

Archive (#254)

Swipe-archive dispatches thread.archive with no preflight, so there is no warning for open Exchanges, placed children, or live Crews. The server still cascades correctly; the person just never sees the facts first. FORK.md case 21 defers this: "Mobile archive doors remain explicitly deferred". The pre-archive facts schemas and route are web-local. A port must use the 2026-09-22 "placed agents keep running" wording. Size M.

Crews

Mobile has no Crew surface. The only written deferral is in the user guide (docs/user/personas.md:57). crews.md no longer mentions mobile.

Gap Consequence Size
Roster gate (approve, edit, or decline a propose_crew) A Captain that proposes while the person is on mobile is stuck until they open web or desktop. Mobile's atoms already include crewProposals, previewCrewProposal, and resolveCrewProposal, but nothing calls them. S stub / M approve–decline / L full editing
Crew add-member requests These are answered in the Inbox, so they're blocked until mobile has one. M after Inbox
Captain mark, member grouping, Stop/Archive crew Seats show as ordinary top-level threads, as the user guide says. M

Artifacts

Mobile has no artifact surface: no list, viewer, or delete. Web has routes/artifacts.tsx, j5/artifacts/*, and a right-panel tab. The contracts are shared; artifactClient.ts, artifactPath.ts, and artifactPreview.logic.ts are web-local. The handoff chip on mobile is static text. docs/user/artifacts.md says nothing about mobile. Size M read-only, L full.

Fleet

Mobile has no Fleet page. The doc and web now agree on the layout (the whole fleet, in Active, Settled, and Retired sections), which suits a phone. The fleet atom is shared; fleet.logic.ts is web-local and pure. The Fleet page also hosts the Playbook runs overview and Stop/Archive crew. Size L.

Branding

App name, scheme, and bundle id are J5. Still T3:

  • app and splash icons (assets/{prod,dev,nightly} and the Android layers)
  • the in-app lockup (components/CompactBrandTitle.tsx)
  • the loading screen
  • the widget mark
  • the Live Activity title

The icons need a native rebuild. Size S for the copy and lockup, M for the icons.

Smaller gaps, documented as web/desktop only

  • Skills settings page. docs/user/skills.md says mobile uses skills in chat but has no settings page. Size M for a read-only inventory.
  • Playbook YAML import and runs overview. docs/user/playbooks.md limits these to web and desktop. Size S for import; the runs overview needs Fleet.

3. Already at parity (no work)

  • Personas library in Settings: create, edit, duplicate, export, copy to environment, enable/disable, remove/restore, file and folder import with selective conflict replacement, and library sources. The copy says "Personas". Only "Open in editor" is missing, which doesn't apply on a phone.
  • Personas in chat: @persona: mentions, draft-as-persona launch, identity chips, and the persona lock.
  • Playbooks: boards, /playbook expansion, and the library (create, rename, delete, author).
  • Steer/queue: J5's truthful-steer layer was retired, and mobile matches upstream.
  • Contract drift: a static import check of 866 mobile files against the shared package exports found nothing missing. A full typecheck was not run.
  • Upstream features: scheduled tasks, PR views, and the Device panel come from upstream, so gaps there are upstream's, apart from B5.

4. Decisions needed (Jackson)

  1. Stopgap or full A2A rendering? A small change could fix the misleading parts now: left-align and label deliveries and notices, and strip the envelope. Or go straight to lifted card presenters. Or wait for Carry J5 A2A metadata as a message-context record instead of text envelopes #285, which would make parser work on both clients throwaway.

  2. Fix web's A2A deviations from its own doc first? Recommended, so one presenter gets lifted to client-runtime instead of two diverged copies.

  3. Lift the "native mobile screens deferred" rulings? That covers FORK.md case 34 (Squadron screens, Inbox), case 21 (archive), and the user guide's Crew deferral. feat(mobile): J5 daily-use parity — Squadrons, inbox, A2A, and lifecycle #40 and fix(mobile): replace project-first creation with Squadron creation and selection #128 suggest Squadrons and Inbox are wanted; confirm the order.

  4. What happens to older mobile builds once mobile sends a Squadron?

    • (a) Register a home when squadronId is present, and keep the no-home exception when it's absent.
    • (b) Require a Squadron and refuse old builds.
    • (c) Gate on a capability.

    fix(mobile): replace project-first creation with Squadron creation and selection #128 requires this to be decided explicitly. Fix B9 in the same change.

  5. Mobile Squadron UX. fix(mobile): replace project-first creation with Squadron creation and selection #128 already says to replace project-first creation with Squadron-first. Still open:

    • where the list scope lives on a phone (filter menu or header switcher)
    • whether Add Project becomes "New Squadron", as on web
    • whether a person, not only the agent, can give a homeless thread a home
  6. Minimum mobile Crew surface.

    • (a) a "proposal waiting, open on desktop" notice
    • (b) approve or decline, with the runtime preview and drop-seat
    • (c) full parity

    The stuck Captain argues for at least (b).

  7. Notifications for asks to the person.

    • none
    • a relay extension with a J5 "needs you" kind (new server work, and it touches an upstream relay file)
    • a local notification when a foreground poll finds a new ask
  8. Which list-membership rule is right, and what the Fleet badge counts. Settle both (see the web-side bugs in §1) before porting either.

  9. Scheduled tasks on mobile (B5).

    • add binding to an existing thread
    • block new-thread tasks at save
    • give scheduled tasks Squadron context on the server
  10. Artifacts, Fleet, Skills, icons. Build each for mobile, or document it as web/desktop only and fix just the misleading parts (B6, B7, B8).

  11. Tracking. feat(mobile): J5 daily-use parity — Squadrons, inbox, A2A, and lifecycle #40 doesn't cover artifacts, branding, scheduled tasks, Fleet, or the defects in §1. Split them out once the decisions above are made.

🤖 Generated with Claude Code. Audit by Claude Opus 5.5 in J5 Code.

Activity

  1. self-assigned this
    on Sep 28, 2026
  2. Jacksondr5 commented on Sep 28, 2026

    @Jacksondr5
    OwnerAuthor

    Simulator verification of §1

    I ran these checks on an iPhone 17 Pro simulator (iOS 26.5) using a J5 Code Dev build from j5/main at 1ff729bc0d. The server was isolated and seeded with a VACUUM INTO copy of the live database. The copy had no Crews, proposals, machine senders, or queued deliveries, so I created them on the isolated server with real Claude agents in a scratch project and Squadron ("Audit 337"):

    • a Captain thread started from mobile
    • two spawned peers, Echo (replies) and Mute (never replies)
    • an Inbox ask, answered through /api/j5/a2a/inbox/answer
    • a j5 a2a machine participant
    • a one-seat Crew, approved through the proposals API

    All screenshots show only fixture content. The copied data showed the same B1 and B3 rendering on real threads, but those threads belong to a private repo, so I replaced those captures with fixture ones.

    Summary: 10 of 13 confirmed (B1–B7, B9, the stuck Captain, and branding; B2 and branding only partly). B10 and B11 not reproduced, B8 not checked (no Android SDK on this host). Exchange-dropped not produced, because the archive door the audit assumes doesn't exist on an iPhone against a current server (see B2 and B7).

    B1: confirmed

    Peer replies and machine messages both render as the raw envelope in a right-aligned bubble. The only attribution is a "Sent by another agent" label, and the agent-facing trailer ("The platform closed this exchange…", "This message came from an automated sender…") is shown to the person.

    • Differs: the label is tappable and opens the sender's thread.
    • Differs: machine messages get the same label, "Sent by another agent", even though an automation sent them.
    • I didn't capture an open ask carrying the send_message(...) reply instruction. The replies and machine messages I did capture show the same pattern.

    B2: confirmed (silence, Inbox answer, Crew notices); exchange-dropped not produced

    These all appear as right-aligned bubbles with no label:

    • turn-ended-no-reply silence notices (createdBy: system)
    • the person's Inbox answer, shown as [Message from human:<uuid>] (createdBy: user)
    • <j5_crew_gate> and <j5_seat_finished>, as raw XML

    Extra: the platform's PlanHandoff reminder ("Your run ended without the declared PlanHandoff handoff…", createdBy: system) also looks like the user wrote it.

    Exchange-dropped: I tried to archive Mute while its Exchange was open. On this server the full left swipe settles the thread (thread.settled) and doesn't archive it, so no drop happened.

    B3: confirmed

    The full <j5_spawn_context> and <spawner_brief> XML is shown, including the participant, Squadron, and spawner ids.

    • Differs: the brief carries the "Sent by another agent" label, but it isn't a link (the message has no senderThreadId). So "nothing shows who spawned it" holds.

    B4: confirmed

    The queue sheet shows [Message from automation machine:audit-wa… with a Steer button beside it. The delivered bubble then carries a queued badge.

    B5: confirmed

    The form has no thread binding. Saving gives no warning, and the row is stored with thread_id NULL and creation_source: mobile. The first run failed with Scheduled new-thread execution is unsupported until scheduling context can select an explicit existing Squadron.

    • Differs: the list does show "Last run failed" afterwards.
    • The task stays enabled and fails again every interval. I deleted it.

    B6: confirmed (the inference was right)

    The chip opens the file viewer, which shows "File unavailable: Failed to read workspace file 'artifacts/plans/audit-337-fixture.md' in '/tmp/j5-337-scratch'".

    B7: confirmed, through Delete, not Archive

    On a server that supports settling, the iPhone thread row has no Archive action. The swipe is Settle and the menu offers Settle, Snooze, Pin, and Delete (thread-list-v2-items.tsx offers Archive only for pre-settlement servers). Deleting the seat gets the server refusal: "…Crew members are never deleted one by one; retire the whole Crew with Archive crew on the Fleet page…".

    New defect: after that refused delete, the seat row stays hidden from the list until the app relaunches. The server never deleted it. I reproduced this twice.

    B8: not checked

    The host has no Android SDK.

    B9: confirmed

    I launched "Create playbook" from mobile (scratch workspace, Audit 337 Squadron). The thread was stored with creationSource: "web" and was in the Audit 337 roster immediately. For comparison, the Captain started from mobile's own new-task door was stored as mobile, had no home, and had to call join_squadron before it could message or spawn.

    B10: not reproduced

    I couldn't create a read-only connection: j5 auth pairing create and j5 auth session issue have no scope option. Only the code reading supports this finding.

    B11: not reproduced

    On a thread started as the Navigator persona, the model chip is disabled ("Assigned model: Codex · gpt-5.6-sol · high"), and I found no other mobile control that changes the model. I didn't exercise the stale-draft modelSelection path the audit points at.

    Stuck Captain: confirmed

    After propose_crew, the Captain's thread shows only its own prose ("proposal is open, pending your approval") and raw tool rows (mcp__t3-code__propose_crew, mcp__t3-code__send_message). There is no gate or notice, and no approve or decline control anywhere. I approved it through the HTTP API to continue. This also confirms §2's point that the agent's own send_message appears as a raw tool-name row.

    Branding: partly confirmed

    Still T3:

    • App icon (confirmed)
    • Splash/loading mark (confirmed; one capture, and I didn't separate the native splash from the in-app BrandMark loader)
    • Header lockup "T3 Code" (confirmed)
    • Environment detail sheet (confirmed, no capture): "T3 Code · Version 0.0.44" and "Update and restart T3 Code on this machine."

    Not observed:

    • Live Activity: agent-device brings the app to the foreground on every command, so I couldn't capture the lock screen. The code confirms the title is hard-coded "T3 Code".
    • Widget: I didn't add one to the home screen.

    §2 items seen along the way

    The web-side bugs listed under §1 were not re-checked here.

    Other findings

    • Composer: in a persona thread's composer, the Send button sits almost entirely under the keyboard. I had to tap its visible sliver.
    • join_squadron errors: the agent passed the Squadron id without its squadron: prefix. The refusal was SquadronProjectReferenceSquadronNotFoundError with an empty message, so the agent couldn't tell what was wrong.

    Verification friction (tooling, not product)

    • scripts/mobile-native-client.ts and .agents/skills/test-t3-mobile/scripts/pair-client.sh still use T3 names: the T3CodeDev workspace, com.t3tools.t3code.dev, and t3code-dev://. Prebuild now emits J5CodeDev with codes.jackson.j5code.dev and j5code-dev://, so ensure fails at xcodebuild and the pairing helper opens the wrong app. I built, installed, and paired by hand with the J5 ids. The skill text also still refers to .t3 state.
    • Device hub: J5 caches its agent-device daemon endpoint (LocalDeviceHost.ensureAgentReady). The daemon shut down on 2026-09-26, and every device_open since has returned the dead 127.0.0.1:50639. The only built-in reset is turning agent access off and on in the live app's device settings, so I ran a private daemon with its own state dir. An Astra lane hit the same failure on 2026-09-26 (its coordinator's note: "J5's proxy to it didn't recover").

    🤖 Generated with Claude Code. Verification by Claude Opus 5.5 in J5 Code.

  3. Jacksondr5 commented on Sep 29, 2026

    @Jacksondr5
    OwnerAuthor

    Decisions (Jackson, 2026-09-28) and Phase 0

    Decided

    • Upstream seams for mobile: FORK.md gets a mobile section. It fixes the set of seams up front, adds one integration test per seam, and treats native changes as their own batched class.
    • CI and testing: fix the mobile test loop and add advisory native checks (native lint and an iOS PR build). The Expo fingerprint check is dropped because OTA updates are off.
    • Device hub: fix the dead-daemon bug as a temporary in-place patch and offer it upstream through Upstream give-back backlog (hold until V2 merges upstream) #276.
    • §4 question 4, older mobile builds: option (b). Mobile must supply a Squadron, the same as web, which closes the mobile-future-squadron exception. No capability flag for now: there is one mobile user, and servers are updated before the app. Release the server first, then the TestFlight build. B9 is fixed in the same change.

    Phase 0 (foundations, in progress)

    Later phases, open decisions noted

    1. Fix web's A2A deviations first, then lift shared presenters and add the mobile timeline and queue seams. Also: B5–B7, the refused-delete row bug, branding copy. Needs §4 item 2 confirmed and item 8.
    2. Inbox, Crew approve/decline, and a notification when an agent asks the person. Needs items 3, 6 and 7.
    3. Squadron-first creation through the resolver seam, with the (b) server change. Needs items 5 and 9.
    4. Fleet, the full artifact viewer and Crew management are deferred.

    🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions