47f875a61e
* fix(web): plan panel misses agent updates emitted before WS connects Agent auto-start can write a plan via MCP within ~100 ms of task creation, faster than the browser opens its WS connection — the `task_plan.created` broadcast is delivered to zero clients and the Plan tab never appears because `useTaskPlan` only fetches once the panel is mounted (chicken-egg). Eagerly fetch the plan from `usePlanPanelAutoOpen` once the WS is connected so the store is populated regardless of the panel's mount state. Also acknowledge the plan when the panel is restored from a saved layout with no `lastSeen` recorded — that's a page-reload hydrate, not a fresh agent update, so a stale indicator should not flash. * test(web): cover the new eager-fetch + reload-acknowledge branches Mock the new store selectors and `getTaskPlan` API in `use-plan-panel-auto-open.test.ts`, restore the original `panelExists → return` short-circuit (the previous version slipped through to `addPlanPanel` on a live update against an open panel), and add three cases: eager-fetch on cold load, fetch gated on WS connect, and the page-reload acknowledge. * fix(web): guard the eager plan fetch against an infinite retry loop `isLoading` flipping during the fetch was re-triggering the effect via its own dep update — on persistent failure it would keep firing because `isLoaded` never flips. Switch to a ref-keyed attempt set so each task is fetched at most once per WS-connected session, and clear the set on WS reconnect so transient failures still recover. Also drop the redundant `activeTaskId` guard inside the auto-open effect (`plan` is already derived from `activeTaskId`) and clarify the catch-block comment. Addresses Greptile P1 + Claude review suggestions on PR #749. * test(web): cover the disconnect→reconnect retry + tighten code comments Address Claude review on PR #749: - attemptedRef comment now reflects what the clear actually does (clears all task IDs on WS disconnect, not per-task on navigation). - Catch comment points at the disconnect/reconnect effect as the actual retry trigger instead of the misleading "useTaskPlan retries on panel mount" claim. - Reload-acknowledge comment is honest about the lastSeen ambiguity: optimistic suppression of the indicator flash, with live-update path re-arming after a post-reload update. - Added a regression test for the disconnect → reconnect → re-fetch path, which the previous test suite did not exercise. * docs(web): clarify reload-acknowledge comment to match the actual control flow * test(web): split test file under the 100-line per-function lint cap The new eager-fetch + reconnect-retry tests pushed the top-level describe callback to 114 lines, tripping max-lines-per-function. Split into a sibling describe for the eager-fetch scope. * fix(web): don't overwrite a WS-delivered plan with a stale null fetch The HTTP fetch and the WS \`task_plan.created\` event can resolve in either order on a fast auto-start: when the fetch was issued first (server didn't have the plan yet, returns null) and the WS event arrives mid-flight, the unconditional \`setTaskPlan(taskId, null)\` would clobber the real plan that just landed. Read the live store state inside the \`then\` and skip the write when null overlaps with a populated plan. Adds a regression test for the race. * docs(web): fix the disconnect/reconnect comment wording Effect fires on disconnect, not reconnect — wording was inverted. * fix(web): also guard the eager fetch against stale non-null overwrites CodeRabbit caught a residual race: when a newer plan lands via WS while the HTTP fetch is in flight, an older non-null HTTP response could still clobber it (regressing updated_at + content). Compare timestamps and skip the write when the live store entry is already at-or-newer. Adds a regression test, splits the test file into a third sibling describe so each callback stays under the 100-line lint cap. * style(web): apply prettier formatting