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
1 unresolved thread ยท Jump to file
Selected thread: apps/mobile/src/state/pending-new-tasks-model.ts:55 โ€” ๐ŸŸก **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. <details> <summary>๐Ÿš€ Reply "<strong>fix it for me</strong>" or copy this <strong>AI Prompt</strong> for your agent:</summary> ```text In file @apps/mobile/src/state/pending-new-tasks-model.ts around line 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. ``` </details> <!-- macroscope-meta: {"kind":"code_review","variant":"195358a06841b2d4d03feb0d212d1ca7"} -->
0 / 12 viewed12 files changedLine totals unavailable

apps/mobile/src/state/pending-new-tasks-model.ts ยท Earlier revision e55f2fdb ยท right 55

Original diff
@@ -0,0 +1,134 @@
+import { EnvironmentId, ProjectId } from "@t3tools/contracts";
+
+import { deriveThreadTitleFromPrompt } from "../lib/projectThreadStartTurn";
+import type { QueuedThreadCreation, QueuedThreadMessage } from "./thread-outbox-model";
+import type { ComposerDraft } from "./use-composer-drafts";
+
+/**
+ * Unsent work that will become a thread, shaped for thread-list presentation.
+ * A `pending` task sits in the outbox and sends itself when its environment
+ * reconnects; a `draft` is the project's new-task composer content, which
+ * only sends when the user submits it. Both share the list slot so the user
+ * can find everything they have written but not yet started in one place.
+ */
+export type PendingNewTask = PendingQueuedTask | PendingDraftTask;
+
+export interface PendingQueuedTask {
+  readonly kind: "pending";
+  readonly key: string;
+  readonly environmentId: EnvironmentId;
+  readonly projectId: ProjectId;
+  readonly projectTitle: string | undefined;
+  readonly projectCwd: string | undefined;
+  readonly branch: string | null;
+  readonly title: string;
+  readonly createdAt: string;
+  readonly message: QueuedThreadMessage;
+  readonly creation: QueuedThreadCreation;
+}
+
+export interface PendingDraftTask {
+  readonly kind: "draft";
+  readonly key: string;
+  readonly environmentId: EnvironmentId;
+  readonly projectId: ProjectId;
+  readonly projectTitle: undefined;
+  readonly projectCwd: undefined;
+  readonly branch: string | null;
+  readonly title: string;
+  /** Drafts have no creation timestamp; they sort as current work. */
+  readonly createdAt: string;
+  readonly draftKey: string;
+  readonly draft: ComposerDraft;
+}
+
+const NEW_TASK_DRAFT_PREFIX = "new-task:";
+
+/** Parses a `new-task:<environmentId>:<projectId>` composer draft key. */
+export function parseNewTaskDraftKey(
+  draftKey: string,
+): { readonly environmentId: EnvironmentId; readonly projectId: ProjectId } | null {
+  if (!draftKey.startsWith(NEW_TASK_DRAFT_PREFIX)) {
+    return null;
+  }
+  const scope = draftKey.slice(NEW_TASK_DRAFT_PREFIX.length);
+  const separator = scope.lastIndexOf(":");
M
macroscopeappBotSep 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.
J
Julius MarmingeSep 6, 2026

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.