fix(planning): list plans newest started first, and let opening one move no row

Merged
DA
11d ago
+146-127

What

Dean: "whenever I select one, it jumps to the top of the list." Opening a plan set opened_at = now, and the sidebar list was sorted by opened_at desc, so every click moved the selected row to the top.

The list is now sorted the usual way: newest plan first (created_at desc, id desc). Opening, reading, or saving a plan doesn't change the order. Starting a new plan still puts it at the top, because the host reads the list again after a start.

Changes

  • Store (plan-store.ts): the list is ordered by created_at desc, id desc. openPlan and the stampOpened update are removed. The window's GET /api/plans/{id} now uses readPlan, the same read the planning model already used, so opening a plan writes nothing.
  • Host (compose-planning.ts): PLANNING_OPEN no longer reads the list again after opening. That extra read was only there to pick up the bump ("Opening moved the plan to the head of the list"), and with it gone the client can't reorder rows on a click either. The list is still read again after a start and after a delete.
  • Desktop renderer: I checked planning-model.ts, use-plans-tab.ts, desktop-sidebar.tsx and desktop-plans.tsx. None of them sort locally; they draw rows in the order the service returns. Renderer fixtures no longer set openedAt.
  • Migration 0059 (hand-written, with a journal entry): drops index plan_user_opened and creates plan_user_created (user_id, created_at), which covers the new list query.
  • Docs (docs/PLANNING.md, server/README.md, wire and client comments) now describe the new order.

What stays, and why

  • The opened_at column stays for now. Nothing reads or writes it. Vercel runs db:migrate during the new deployment's build, while the old deployment is still serving, and the old code selects and updates opened_at. Dropping the column in this same deploy would break plan list and open requests on production between the migration and the promote, and would break a rollback. I'll drop it in a follow-up migration once this deploy is live. plan-schema.ts still declares the column, because drizzle-schema.test.ts checks both directions.
  • openedAt stays on the wire, but it is now just the plan's start time. Desktop builds through v0.7.1 (released) decode the list strictly: an unknown key is refused and openedAt is required. If the server stopped sending it, those builds would get no plans. So the server still sends openedAt = createdAt, and a test checks that. In the schema the field is now optionalKey, so no build from this PR onward depends on it, and it can be removed later without breaking newer desktops.

Tests

  • Changed on purpose: hosted-plans.test.ts had a test, "the list holds only the account's plans, the most recently opened first", that asserted opening moves a plan to the top. That is exactly the behaviour being removed. It now asserts newest-started-first, and that reading and saving a plan don't reorder the list. It is merged with the old "the model's read does not reorder the window's list" test, which it covers. The other uses of openPlan in hosted-plans.test.ts and hosted-planning.test.ts now go through readPlan, the read the window now uses.
  • New, fails without the change: plans-app.test.ts › "opening a plan leaves the list newest started first". It starts two plans, GETs the older one, then lists. Run against main's store, the order comes back flipped.
  • New, fails without the change: compose-planning.test.ts › "opening a plan moves no row of the list already drawn".
  • New: plans-app.test.ts › "a listed plan still carries the openedAt a desktop through v0.7.1 requires". This guards the compatibility promise above.
  • Test count: +3 new, −1 merged (net +2).
  • JSON Schema goldens changed (re-recorded with LUKE_UPDATE_FIXTURES=1): in the four plan-wire-* goldens, openedAt is removed from required. Its property node is unchanged. These are wire schemas for the Mac's plan client, not schemas shown to a model.

Evidence

  • ./scripts/check.sh: exit 0 on Linux.
  • CI could not verify the Mac UI, and I did not run ./scripts/verify.sh (this was done on a Linux cloud VM; LUKE-159 is the manual Mac pass). There is no screenshot because nothing visual changed: the rows look the same, they just stop moving when clicked.

🤖 Generated with Claude Code


Open workspace in Conductor


Devin Review