feat(mobile): show new-task drafts alongside pending tasks in the thread list#10260

merged2
t3code/fix-offline-pending-tasks
t3code/mobile-drafts-in-list
openedSep 6, 2026byJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 4d ago

Stacked on #10245.

Problem

On mobile, text typed into a project's new-task composer is only discoverable by opening that project's new-task screen again. Nothing on Home or the iPad sidebar shows that a draft exists, so unsent work is easy to lose track of. That also made the outbox's restore path (a rejected pending task gets merged back into the project draft) feel like the task vanished: it moved from the visible Pending section into an invisible draft. Web already lists drafts at the top of its sidebar.

Fix

PendingNewTask is now a union of two kinds of unsent work: pending (queued in the outbox, sends itself when the environment reconnects) and draft (the project's new-task composer content, sends only when the user submits). Only drafts with user content (text or attachments) are listed; a bare model pick is not surfaced. The pure builder lives in pending-new-tasks-model.ts.

Both list surfaces render drafts in the same slot as pending tasks:

  • v2 flat list (Home and iPad sidebar): the section divider now reads Unsent. Drafts get an amber Draft label with a pen icon; queued tasks say Sends on reconnect in the uncolored status slot, describing what happens next rather than where the item sits.
  • v1 grouped list: drafts get an amber Draft pill and pen icon. The pill has room for one word, so queued tasks keep the Pending pill and the subtitle leads with Sends on reconnect. Drafts have no timestamp since they have no creation time.

Tapping a draft opens the project's new-task composer, which already loads that draft. Long-press offers Discard (clears the draft, same reset a submit performs). Deleting a pending task is unchanged.

Drafts sort first (they are what the user is writing now), then queued tasks newest-first.

Verification

vp test run on the touched suites in apps/mobile: pending-new-tasks-model.test.ts (new), threadListV2.test.ts, homeThreadList.test.ts, homeListItems.test.ts, showcasePendingTasks.test.ts โ€” 73 passed. vpr typecheck passes. Lint on the touched files shows only pre-existing warnings (same 23 on the base branch).

Before / after โ€” v2 flat list (default)

Before: only the queued task appears under Pending, labelled Queued. After: the Unsent section shows the amber Draft row above a row labelled Sends on reconnect

Before / after โ€” v1 grouped list (Legacy Thread List setting)

Before: only the Pending pill row. After: a Draft pill row with pen icon leads the group, followed by the Pending row whose subtitle reads Sends on reconnect

Tapping the draft row

The new-task composer opens on the draft's project with the draft text loaded

Captured on an iPhone 17 Pro simulator with the dev client, same seeded state (one draft, one queued task, one real thread) on both revisions, environment filter scoped to the test backend.

Surfaces

  • Entry points: Home list (v1 and v2), iPad ThreadNavigationSidebar (v1 and v2), new-task composer (open target).
  • Clients: mobile only. Web already has draft rows; the server is untouched.
  • Reverse state: Discard from the row menu, or clear the composer.

Model: Claude Fable 5 ยท Harness: Claude Code in T3 Code

๐Ÿค– Generated with Claude Code

apps/mobile/src/state/pending-new-tasks-model.tsline 55Outdated ยท e55f2fdOpen thread
โ€ฆโ€ฆ@@ -0,0 +1,134 @@
52+ return null;
53+ }
54+ const scope = draftKey.slice(NEW_TASK_DRAFT_PREFIX.length);
55+ const separator = scope.lastIndexOf(":");
MmacroscopeappBoton line 55Sep 6, 2026

๐ŸŸก Medium state/pending-new-tasks-model.ts:55

parseNewTaskDraftKey misidentifies the project for valid keys containing colons: new-task:env:project:part is parsed as environment env:project and project part, so the draft is grouped and opened against the wrong project and cannot be reached from its list row. Splitting at the last colon cannot disambiguate IDs that both allow colons; serialize the environment and project components with an unambiguous encoding and update the corresponding key producer/parser.

๐Ÿš€ Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/mobile/src/state/pending-new-tasks-model.ts around line 55:
2
3`parseNewTaskDraftKey` misidentifies the project for valid keys containing colons: `new-task:env:project:part` is parsed as environment `env:project` and project `part`, so the draft is grouped and opened against the wrong project and cannot be reached from its list row. Splitting at the last colon cannot disambiguate IDs that both allow colons; serialize the environment and project components with an unambiguous encoding and update the corresponding key producer/parser.

Not changing this. Both ids are server-generated UUIDs (crypto.randomUUIDv4 in ServerEnvironment.ts and serverRuntimeStartup.ts/cli/project.ts), so neither can contain a colon. The new-task:<env>:<project> key is the pre-existing persisted format written by new-task-flow-provider.tsx, and composerDraftEnvironmentId in composerAttachmentUploadQueue.ts already parses it with the same last-colon split; this parser mirrors that. Changing the encoding would orphan every draft already on users' devices for a case that cannot occur.

Synced from GitHub

Thread transfer impact

โœ… Thread transfer remains within every enforced ceiling.

โ„น๏ธ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wireโ€”13.6 KiBโ€”15.1 KiBโœ…
CodexThread snapshot wireโ€”7.0 KiBโ€”7.3 KiBโœ…
CodexLive turn WebSocket wireโ€”6.6 KiBโ€”7.8 KiBโœ…
CodexLive turn WebSocket decodedโ€”57.1 KiBโ€”66.4 KiBโœ…
CodexLive turn messagesโ€”10โ€”21โœ…
ClaudeTotal thread wireโ€”13.6 KiBโ€”15.1 KiBโœ…
ClaudeThread snapshot wireโ€”7.0 KiBโ€”7.3 KiBโœ…
ClaudeLive turn WebSocket wireโ€”6.5 KiBโ€”7.8 KiBโœ…
ClaudeLive turn WebSocket decodedโ€”57.8 KiBโ€”66.4 KiBโœ…
ClaudeLive turn messagesโ€”9โ€”21โœ…

Baseline: unavailable ยท PR result: 0286e75 ยท Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.9 KiB
  • Claude decoded thread snapshot: 114.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable โ€” This adds automatic draft discovery, navigation, and discard behavior to the mobile Home and thread lists, backed by a new production model and composer-state subscription. It also changes default list presentation and item identity across both legacy and v2 surfaces, so human review is appropriate.

No code changes detected at 0286e75. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Bugbot is paused โ€” on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

Added 2 commits

  • feat(mobile): say what a queued task will do instead of where it sits0286e75
  • feat(mobile): show new-task drafts alongside pending tasks in the thread liste2d6c8e