# feat\(mobile\): allow several new-task drafts per project · PR \#10327 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/10327)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: merged

Source repository: pingdotgg/t3code

Source branch: t3code/mobile-multi-draft

Target branch: t3code/mobile-drafts-in-list

Head commit: c1157a308da82eaa9fef9f54c7457a9df186db89

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/10327)

[Commits](https://git.cafe/pingdotgg/t3code/pull/10327/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/10327/diffs?format=markdown)

## Description

---

Stacked on #10260 (which is stacked on #10245).

## Problem

Mobile keyed new-task drafts by project (`new-task:<environmentId>:<projectId>`), one slot per project. Starting a new task in a project always landed in whatever draft already sat there, so there was no way to hold two ideas for the same repo. Web mints a draft id per draft and never had this limit. With #10260 listing drafts in the thread list, the single slot became visible.

## Fix

Drafts are keyed `new-task:<draftId>` and carry their project in a `project` stamp (`environmentId`, `projectId`, `createdAt`). Everything that used to read the project out of the key now reads the stamp:

- **Migration.** Legacy project-keyed entries are rewritten to id keys with the stamp filled in when the draft file loads, for live drafts and for drafts archived by sign-out, so nothing on users' devices is lost.
- **New Task mints a fresh draft.** Each entry into the composer binds to its own draft, whether it came from the compose button, a project row, or the provider's project fallback. Tapping a Draft row resumes that draft by id through a new `draftId` route param; the screen waits for draft hydration and for the project list before deciding a draft is gone.
- **Switching projects or environments retargets the same draft** (text and model/mode choices follow; branch and worktree are cleared since they belong to the old repo) instead of copy-if-empty across keys. `copyComposerDraftContent*` is removed.
- **Uploads and sign-out** resolve a draft's environment from the stamp (`composerDraftEnvironmentId` now takes the draft), so id-keyed drafts upload attachments and survive relay sign-out.
- **A rejected pending task restores into its own message-keyed draft** stamped with the project, rather than merging into whatever the user is typing.
- Drafts list newest first by their own `createdAt` instead of a render-time stamp.

## Verification

`apps/mobile`: `vp test run` across the draft store, list model, outbox drain/removal/editor, attachment upload queue, and thread-list suites — 239 passed. `vpr typecheck` passes. Lint on touched files shows only pre-existing warnings.

New tests: legacy key migration (live and archived), minting several drafts per project, retarget keeps text and clears workspace selection, environment lookup from the stamp, rejected-task restore into a stamped draft.

### Before / after — starting a fresh New Task in a project that already has a draft

![Before: the composer opens with the existing draft text "First idea, typed earlier" already in it. After: the composer opens empty; the earlier draft is kept separately](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/32044ed1d03a9a82/multi-draft-compose-before-after.png)

### After — three drafts for the same project in the thread list

One migrated from a legacy project key, one already id-keyed, and one typed live and retargeted across environments. Tapping any row opens that draft.

![Three Draft rows for pingdotgg/t3code above the Sends on reconnect row](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/3902c2985121c6de/multi-draft-after.png)

Captured on an iPhone 17 Pro simulator with the dev client against a disposable backend; the "before" is the head of #10260 with the same seeded state. Captures were taken before the stack was rebased onto current `main` (which retinted the Pending pill and reworked `carryDraftContentTo`); the rebase changed no draft behaviour shown here.

## Surfaces

- Entry points: compose button, project row "new thread", Draft rows (v1 and v2 lists, Home and iPad sidebar), pending-task restore.
- Clients: mobile only. Web already has per-draft ids; server untouched.
- Reverse state: Discard from the row menu, or clear the composer; both drop the stamp so the row disappears.

Model: Claude Fable 5 · Harness: Claude Code in T3 Code

🤖 Generated with [Claude Code](https://claude.com/claude-code)


