feat(mobile): show new-task drafts alongside pending tasks in the thread list#10260
1 unresolved thread ยท Jump to file
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(":");๐ก 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:
| 1 | In 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.