Skip to content

feat(mobile): persist and recover pending task prompts on failure (port #3053)#3087

Merged
Gilbert09 merged 2 commits into
mainfrom
posthog-code/mobile-persist-recover-pending-prompts
Jul 2, 2026
Merged

feat(mobile): persist and recover pending task prompts on failure (port #3053)#3087
Gilbert09 merged 2 commits into
mainfrom
posthog-code/mobile-persist-recover-pending-prompts

Conversation

@Gilbert09

Copy link
Copy Markdown
Member

Summary

Ports desktop #3053 (persist pending task prompts and recover them on failure) to the mobile app, so a user's typed new-task prompt survives a failed or interrupted task creation instead of being silently lost.

On mobile the prompt is now durably recorded the moment a create begins, and recovered into the composer on the next launch if the task never finished creating.

What changed

  • New pendingPromptRecoveryStore (apps/mobile/src/features/tasks/stores/pendingPromptRecoveryStore.ts) — a small AsyncStorage-backed persisted store, kept deliberately separate from the existing transient in-memory echo store (pendingTaskPromptStore). The echo store must stay ephemeral (its entries disappear the instant the live SSE user_message_chunk echoes back), so persisting it would replay stale echoes on launch. The recovery store instead records { promptText, createdAt }, caps itself to the newest ~20 entries, and tracks a hasHydrated flag exposed via whenHydrated().
  • New PendingPromptRecovery component (apps/mobile/src/features/tasks/components/PendingPromptRecovery.tsx) — mounted once at the app root; after hydration it takes the newest orphaned prompt exactly once per launch, clears it, and routes to the new-task composer (/task) pre-filled via the existing prompt route param. Guarded by a module-level flag so it runs once.
  • Create flow wiring (apps/mobile/src/app/task/index.tsx) — records the prompt when submission begins, clears it once the task is confirmed created, and clears it on failure (the text stays live in the composer, so nothing is lost).

Design notes

  • Recovery restores prompt text only, matching desktop (mobile attachment URIs are ephemeral and don't survive an app kill).
  • The separate-store approach was chosen over extending the echo store to keep the transient echo and durable recovery concerns cleanly isolated.

Tests

Vitest coverage for the store (persist-on-submit, clear-on-success, cap-to-newest eviction, newest-first ordering, hydration gating) and the recovery component (recovers newest orphan once, no-op when empty, auth-gated). All green.

Ref: desktop PR #3053.

#3053)

Ports desktop #3053 to the mobile app so a user's typed new-task prompt is
never silently lost when task creation fails or the app is killed mid-flight.

- Add a separate AsyncStorage-backed `pendingPromptRecoveryStore` (distinct
  from the transient in-memory echo store, whose entries must vanish when the
  live SSE copy lands). Records a prompt when creation begins, clears it once
  the task exists or on failure, and caps to the newest 20 entries.
- Add a mount-once `PendingPromptRecovery` component that, after the store
  hydrates, recovers the newest orphaned prompt exactly once per launch and
  routes to the new-task composer pre-filled with it.
- Wire clear-on-success / clear-on-failure into the create flow.

Generated-By: PostHog Code
Task-Id: 398f655d-ec85-4295-99cd-585d3debfd17
@Gilbert09 Gilbert09 requested a review from a team July 2, 2026 10:54
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit 92d7e1b.

@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "feat(mobile): persist and recover pendin..." | Re-trigger Greptile

Comment on lines +9 to +17
let recoveryStarted = false;

export function PendingPromptRecovery(): null {
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);

useEffect(() => {
if (!isAuthenticated || recoveryStarted) return;
recoveryStarted = true;
void recoverNewestPrompt();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 recoveryStarted not reset on logout within the same session

recoveryStarted is module-level and never cleared. If a user's session expires or they explicitly log out while the app is still running (without killing the process), isAuthenticated flips to false then back to true, the useEffect fires again, but recoveryStarted is already true — so recovery is silently skipped for the new session. Resetting the flag when isAuthenticated transitions from true back to false would cover this case (e.g. add a branch in the same useEffect to reset recoveryStarted = false when !isAuthenticated).

Comment on lines +76 to +94
it("does nothing when there is no orphaned prompt", async () => {
getAllNewestFirst.mockReturnValue([]);

await mountRecovery();

expect(mockPush).not.toHaveBeenCalled();
expect(clear).not.toHaveBeenCalled();
});

it("does not recover until the user is authenticated", async () => {
authState.isAuthenticated = false;
getAllNewestFirst.mockReturnValue([
{ key: "newest", prompt: { promptText: "Newest prompt" } },
]);

await mountRecovery();

expect(mockPush).not.toHaveBeenCalled();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Prefer parameterised tests for the "no-op" cases

The team rule is to prefer parameterised tests. The two "does nothing" cases — empty store and unauthenticated — both assert the same outcome (mockPush not called, clear not called) with different preconditions. Combining them into a single it.each block would express that intent more concisely and make it easier to add further no-op scenarios in the future.

Context Used: Do not attempt to comment on incorrect alphabetica... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

…o-op tests

Addresses Greptile review on #3087:

- Reset the module-level `recoveryStarted` guard when `isAuthenticated` flips
  to false, so a logout/login within the same process re-attempts recovery of
  any prompt still orphaned in storage (previously skipped until a full app
  restart). Adds a regression test.
- Collapse the two "does nothing" recovery cases into a parameterised
  `it.each` block, per the team's parameterised-test rule.

Generated-By: PostHog Code
Task-Id: 398f655d-ec85-4295-99cd-585d3debfd17
@Gilbert09 Gilbert09 merged commit 43cd348 into main Jul 2, 2026
23 checks passed
@Gilbert09 Gilbert09 deleted the posthog-code/mobile-persist-recover-pending-prompts branch July 2, 2026 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants