fix(planning): list plans newest started first, and let opening one move no row
Merged
DA
11d ago+146+146+012345678901234567890123456789-127-127-012345678901234567890123456789
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 bycreated_at desc, id desc.openPlanand thestampOpenedupdate are removed. The window'sGET /api/plans/{id}now usesreadPlan, the same read the planning model already used, so opening a plan writes nothing. - Host (
compose-planning.ts):PLANNING_OPENno 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.tsxanddesktop-plans.tsx. None of them sort locally; they draw rows in the order the service returns. Renderer fixtures no longer setopenedAt. - Migration 0059 (hand-written, with a journal entry): drops index
plan_user_openedand createsplan_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_atcolumn stays for now. Nothing reads or writes it. Vercel runsdb:migrateduring the new deployment's build, while the old deployment is still serving, and the old code selects and updatesopened_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.tsstill declares the column, becausedrizzle-schema.test.tschecks both directions. openedAtstays 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 andopenedAtis required. If the server stopped sending it, those builds would get no plans. So the server still sendsopenedAt = createdAt, and a test checks that. In the schema the field is nowoptionalKey, 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.tshad 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 ofopenPlaninhosted-plans.test.tsandhosted-planning.test.tsnow go throughreadPlan, 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 againstmain'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 fourplan-wire-*goldens,openedAtis removed fromrequired. 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