MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-1319

12 Work items list shows the previous org's items after an org/workspace switch (stale lazy tree)

Done
Description

Type: Bug — stale CLIENT ISLAND after a tenant-context switch (implementation defect; the planning-contract gap is logged as notes.html mistake #85).

Parent: None — root sibling, dotted id 12. Both related epics are done, so per the log-bug parent rule the bug is logged parentless at the root: Epic 2 Issue tracking core (MOTIR-68 — owns the /items list where the defect lives) and Epic 6 Search, reporting & admin (MOTIR-326 — owns the org/workspace switcher, Story 6.10.5). Numbering: epics occupy dotted ids 1–10, existing root bugs are 9/10/11, so the next free integer is 12.

Discovered in: Manual dogfooding (out-of-band). On the Work items page (/items), switching the active org (or workspace) does not refresh the list — it keeps showing the PREVIOUS tenant's work items until a hard reload.

Root cause / fix (VERIFIED, debug-first — not the surface claim):

The reported "the page won't refresh" is a false surface read — router.refresh() is in fact already called on switch (app/(authed)/_components/OrgControl.tsx:60-69; the workspace switcher does the same). The real defect is a client island that router.refresh() cannot reach.

  1. The default unfiltered Tree view of /items renders the lazy IssueTreeTable ('use client'). It seeds the project's root issues into client state once at mount: useState(() => ({ [ROOTS]: { rows: initialLevel.rows, … } }))app/(authed)/items/_components/IssueTreeTable.tsx:92-100.
  2. Switching org (switchOrganizationAction, app/(authed)/_actions.ts:102-130) re-points the workspace_id cookie and router.refresh() re-runs the server tree: IssuesPagegetActiveProject() resolves the NEW project (app/(authed)/items/page.tsx:57; lib/projects/index.ts:56lib/workspaces/index.ts:30), and IssueTreeSection re-reads the new project's roots (IssueTreeSection.tsx:180) and passes them down as the initialLevel prop.
  3. But the island is not remounted, so the new prop is ignored: its React key is serializeSort(sort) (IssueTreeSection.tsx:184) and the page <Suspense> key is view:sort:filter:page (page.tsx:210) — neither includes projectId/workspaceId. A context switch keeps view/sort/filter/page constant → both keys unchanged → React reuses the instance → the useState initializer never re-runs → the ROOTS level keeps the OLD tenant's rows.
  4. The island's only external refresh hook is the issuesChangedAt tick from CreateIssueProvider (IssueTreeTable.tsx:152-159), added to fix bug-issue-list-not-refreshed-after-create. The switch actions do NOT bump that tick, so the effect-refetch path doesn't fire either.

Net: after an org/workspace switch the Tree shows the previous tenant's items until F5. (This is the same class as the create bug fixed a few lines above the stale seed, and root bug MOTIR-756 "sprint complete … not refreshed".) The filtered/static tree and the List view re-render from server props on each refresh and are likely fine — but they share the same projectId-less <Suspense> key, so verify them in the same pass.

Reproduce: Sign in to a multi-org/multi-workspace account, open /items (default Tree view, no filter) in org A. Switch to org B via the org control (or switch workspace). The list still shows org A's roots; a hard reload corrects it.

Fix direction: Remount the lazy tree on tenant/project identity — add projectId (or workspaceId) to the IssueTreeTable key at IssueTreeSection.tsx:184 (e.g. key={${projectId}:${serializeSort(sort)}}), mirroring the existing remount-on-sort, and/or to the <Suspense> key at page.tsx:210. Belt-and-braces: have switchOrganizationAction/switchWorkspaceAction bump an issuesChangedAt-style tick every server-prop-seeded island watches. Add the missing E2E: switch workspace/org on /items and assert the list ROWS change — tests/e2e/shell-flows.spec.ts currently only asserts the sidebar project-switcher label, never the items list. Write the failing repro test first (run.md code+tests rule).

Resolution: Open — the fix ships as a separate type: code subtask (a root-level epic-direct subtask, since the cause spans Epic 2's list + Epic 6's switcher, both done). Per notes.html mistake #85, the fix should generalise the page-state-after-mutation contract to tenant-context switches.