Skip to content

feat(imago): tasks data layer and pure task model (IMG-9.1) - #2780

Merged
2witstudios merged 3 commits into
pu/imagofrom
pu/img-9-1
Oct 5, 2026
Merged

2witstudios merged 3 commits into
pu/imagofrom
pu/img-9-1

Conversation

@2witstudios

@2witstudios 2witstudios commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Summary

Tasks data layer for imago in apps/imago/src/ui/tasks/ — data and a pure model, no views (those are IMG-9.2+).

  • task-model/ — the response shapes read off apps/web's task route handlers (/api/pages/[pageId]/tasks{,/[taskId],/reorder,/statuses} and the drive pages route in ls mode), and the mapping into imago's model: multiple assignees from task_assignees (legacy single-assignee fields as a fallback), per-list statuses ordered by position, default statuses that a parity test holds to DEFAULT_TASK_STATUSES.
  • task-tree/ and task-edit/ — myimago's pure task model, ported onto PageSpace's shape: locate, status groups, open/done/seed status picks (the same picks the server makes), five-level nesting, and edits (status, toggle, fields, assignees, add, remove, move) that validate and stamp the way the server does. Completing a parent is refused exactly as the server's completion guard does: any move into a done-group status, even from another done status, is refused while a direct untrashed subtask has no completedAt, in the server's own words (Complete all sub-tasks first (n of m remaining)).
  • task-api/ — the existing endpoints through IMG-1.5's ApiClient (session cookie, X-CSRF-Token on writes). loadTaskTree pages through GET /tasks (200 per page, capped at the 5000 that reorder accepts) and loads sub-lists only for tasks the server counts subtasks for, because GET /tasks lazily writes a task list for any page it hasn't seen (the same gate classic's useTaskSubTasks uses).
  • use-tasks/ — useDriveTaskLists, useTaskStatuses, useTaskList (whole tree plus actions). Each write runs the pure edit first, so it can refuse without a request, then applies it optimistically, sends it, rolls back if the server refuses (e.g. a 422 SUBTASKS_INCOMPLETE race), and always revalidates from the server.
  • api/swr-provider.tsx now also exposes its client through useApiClient(), so the hooks that write use the same client SWR reads through.

No new dependencies. Classic apps/web is untouched.

PageSpace

Builder: ag-lfkk4w4e

Criteria

Criterion Code Test Evidence
IMG-9.1-AC1 — Given a drive, should list its TASK_LIST pages and load tasks with per-list statuses. apps/imago/src/ui/tasks/task-model/from-api.ts:92, apps/imago/src/ui/tasks/task-api/task-api.ts:42, :67, :78, apps/imago/src/ui/tasks/use-tasks/use-tasks.ts:177 apps/imago/src/ui/tasks/task-model/from-api.test.ts:211, apps/imago/src/ui/tasks/task-api/task-api.test.ts:44, :131, apps/imago/src/ui/tasks/use-tasks/use-tasks.test.tsx RED (modules missing) → GREEN at 836bf39
IMG-9.1-AC2 — Given the ported pure task model, should map PageSpace tasks (multiple assignees, status groups, 5-level nesting) and refuse completing a parent with open subtasks, matching the server. apps/imago/src/ui/tasks/task-model/from-api.ts:38, apps/imago/src/ui/tasks/task-tree/task-tree.ts:86, :123, apps/imago/src/ui/tasks/task-edit/task-edit.ts:90 apps/imago/src/ui/tasks/task-model/from-api.test.ts:58, apps/imago/src/ui/tasks/task-tree/task-tree.test.ts:217, apps/imago/src/ui/tasks/task-edit/task-edit.test.ts:23, :349 RED → GREEN at 836bf39; negative controls: breaking the guard fails 7 tests, raising the depth cap fails 2
IMG-9.1-AC3 — Given create, update, status, assignee, delete and reorder, should call the existing task endpoints with CSRF and revalidate. apps/imago/src/ui/tasks/task-api/task-api.ts:100-135, apps/imago/src/ui/tasks/use-tasks/use-tasks.ts:89 apps/imago/src/ui/tasks/task-api/task-api.test.ts:175, apps/imago/src/ui/tasks/use-tasks/use-tasks.test.tsx:179, :224, :297 RED → GREEN at 836bf39; negative controls: addressing writes to the root list fails 2 tests, dropping revalidation fails 2

Verification (head 836bf39)

Gate Result
bun run typecheck pass
turbo run lint --filter=@pagespace/imago pass
bun run knip:check 4 findings, all the known .pu/worktrees false positives (packages/lib env-bridge/drive-envs); none from this change
apps/imago: bun run test:coverage 24 files, 223 tests pass; thresholds met (lines 96.31, branches 98.27)
turbo run build --filter=@pagespace/imago pass
bun run test:security NOT RUN — no trust boundary, auth, permission or migration changed (client-side calls to existing routes only)
apps/e2e NOT RUN — no UI flow in this leaf (IMG-9.6 covers task e2e)

Client calls are tested against a fake fetch behind the real createApiClient (CSRF minting included), as the scope asks; the routes themselves are not exercised here.

Limitations

  • Each write revalidates the whole tree: one GET per loaded list that has subtasks. Correct, and cheap for typical lists, but a large nested list costs more requests than a targeted refresh would.
  • A drive's task lists are TASK_LIST pages the drive route doesn't flag isTaskLinked; a TASK_LIST page dropped inside a list counts as a list until GET /tasks backfills its task row.

No agent merges this PR; the owner may merge before the independent review lands.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UxyeCYTQzNU9fkbDQHb1Yn

2witstudios and others added 3 commits October 5, 2026 00:59
Ports myimago's task-tree and task-edit onto the real GET /tasks shape:
multiple assignees from task_assignees (legacy fields as fallback), per-list
statuses with todo/in_progress/done groups, five-level nesting, and
the server's completion guard (any move into a done status is refused while
a direct subtask has no completedAt, in the server's own words). Default
statuses are held to DEFAULT_TASK_STATUSES by a parity test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxyeCYTQzNU9fkbDQHb1Yn
Lists a drive's TASK_LIST pages, loads a list's whole tree (paged, gated on
subTaskCount so leaves never trigger GET /tasks' lazy writes) with each
sub-list's statuses, and reads /tasks/statuses. Create, update, status,
assignees, delete and reorder go through the IMG-1.5 client with CSRF,
apply optimistically via the pure model, roll back on refusal and
revalidate. The SWR provider now exposes its client to hooks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxyeCYTQzNU9fkbDQHb1Yn
@coderabbitai

coderabbitai Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f917d59b-ee27-448c-adb5-318c6f31167c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@2witstudios

Copy link
Copy Markdown
Owner Author

PageSpace records for IMG-9.1:

@2witstudios

Copy link
Copy Markdown
Owner Author

Independent review: IMG-9.1 @ 836bf39284269b27113658d294a45620522935f2

0 blocker / 0 major / 2 minor / 3 nit — APPROVE WITH MINORS

Review record: https://pagespace.ai/dashboard/omziyxp4skckh7ixi2sxzhuk/o1ujmqnf0hh9325k2hgl0hi1

Gates (reviewer-run on the SHA):

  • typecheck: PASS (imago re-run with --force)
  • lint (imago): PASS
  • imago test:coverage: 223/223, run twice
  • imago build: PASS
  • knip: only the 4 known worktree false positives
  • test:security, e2e and the apps/web suite: NOT RUN, because nothing server-side changed
  • CI: all green (test.yml 37270435823, Imago Image 37270435454)

Negative controls: 8 run. 7 were caught. One was not: rollbackOnError: false still passes all 79 tests.

Findings

  • minor · use-tasks.test.tsx:241, :274 (code use-tasks.ts:108) · The rollback tests cannot fail. The revalidating GET serves the original value, so it restores the tree anyway. The control rollbackOnError: false still passes all 79 tests. Fix: hold the revalidation (or answer it differently) and assert the rolled-back tree before it lands.
  • minor · use-tasks.ts:140-141 · setAssignees applies the de-duplicated set locally but PATCHes the raw chosen. With [ada, ada], the server inserts both rows, and task_assignees has unique(taskId,userId), so I expect a 500 and a rollback (suspected; not run against a DB). Fix: send the pure edit's de-duplicated assignees.
  • nit · use-tasks.ts:97,187 · Actions plan against latest.current, which only advances on render. Two actions in one tick plan from the same tree, and the revalidation heals it.
  • nit · task-api.ts:81 · Classic renders existing level-6 tasks inline (0-based MAX_TASK_DEPTH = 5); imago loads 5 levels. The creation cap matches classic, and the server has no depth limit. Worth confirming for IMG-9.2.
  • nit · use-tasks.ts:114 · Every write revalidates the whole tree. This is disclosed and acceptable for now.

Criteria

  • AC1: PASS
  • AC2: PASS. The completion guard is an exact port of completion-guard.ts and PATCH :256, including done→done and the config-less fallback, and it uses identical count semantics.
  • AC3: PASS. The bodies match the handlers, CSRF is asserted on every write, and writes go to the holding list. Rollback proof is missing (minor Upload files, agents, dm's, more #1).

@2witstudios
2witstudios merged commit ab68876 into pu/imago Oct 5, 2026
5 checks passed
@2witstudios
2witstudios deleted the pu/img-9-1 branch October 5, 2026 06:40
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.

1 participant