- HANDOFF: rewritten resume point. First action on resume is `git push` (commits0f00ee5and665530fare local-only). Visual QA + bug bash is the active work; 4 plan-locked items + the structural task-lane fix all need real-browser verification. - CURRENT_TASK: add0f00ee5and665530fto the commit table; reframe "Just shipped" as a per-commit summary; flag the task-lane fix as needing visual confirmation. - SESSION_LOG: chronological entry for this session with full detail (audit, four polish items, race-condition wiring, structural task-lane fix, test status, files touched). - DECISIONS: new entry "Tag the task-lane state with an owner chatId" documenting the structural pattern, what was rejected, and the forward implication that future task-lane state slices follow the same owner-tagging pattern. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
53 lines
5.3 KiB
Markdown
53 lines
5.3 KiB
Markdown
# DECISIONS.md
|
|
|
|
> Append-only architectural decision log. Newest entries at the top.
|
|
> Entry format:
|
|
>
|
|
> ```
|
|
> ## YYYY-MM-DD — <short title>
|
|
> **Context:** why this came up
|
|
> **Decision:** what we chose
|
|
> **Rejected:** what we didn't choose and why
|
|
> **Consequences:** what this means going forward
|
|
> ```
|
|
|
|
---
|
|
|
|
## 2026-04-28 — Tag the task-lane state with an owner chatId
|
|
|
|
**Context:** A recurring bug — every time the user returned to test escalation work, creating a new session would flash the previous session's task-lane data (questions, actions, "Tasks" pill counts) before the new session's AI response landed. The first attempt to fix it (`8914391`) added initializer-time guards (`incomingPrefill || isPickup`) that skipped the sessionStorage restore on mount. That covered exactly two entry paths and missed every other case: in-place URL navigation, mid-flight pickup, HMR re-runs, and the gap between `setActiveChatId(B)` and the AI response that finally populates B's questions/actions. The persistence effect made it worse by writing `{chatId: activeChatId, questions: activeQuestions}` — at any moment where activeChatId had flipped before the questions were updated, sessionStorage was stamped with `{chatId: B, questions: [A's data]}` and a subsequent restore would happily render A's data for B.
|
|
|
|
The root cause was that `activeQuestions` / `activeActions` / `showTaskLane` were three independent state slices implicitly assumed to be in sync with `activeChatId`. The synchronization was by convention, not by structure. Every code path that mutated them had to remember to call `resetSessionDerivedState` first; missing one created stale UI.
|
|
|
|
**Decision:** Add a `taskLaneOwnerChatId` state that records *which chatId the in-memory questions/actions belong to*, set at every site that populates them (sendPrefill, selectChat, handleSend, handleTaskSubmit, handleResumeNew, refreshFacts, handleApplyFix), cleared in `resetSessionDerivedState`. The persistence effect writes ownerChatId as the chatId tag. Render is gated on `taskLaneOwnerChatId === activeChatId` and ANDed into all three render conditions (toolbar Tasks button, narrow-viewport floating drawer, main side panel). The mount-time `skipTaskLaneRestore` guard stays as belt-and-braces for the prefill/pickup entry-flash window, which the owner-gate alone doesn't cover.
|
|
|
|
**Rejected:**
|
|
- **More entry-path guards.** That's whack-a-mole — the next path nobody anticipated will reproduce the bug. The owner-gate makes the bug structurally impossible regardless of which path triggers it.
|
|
- **Combining the four state slices into a single tagged object.** Cleaner long-term but a bigger refactor with more touch points. The owner-tracking approach gets the structural guarantee with a minimal diff and keeps the existing setState patterns.
|
|
- **Inlining the comparison at every render site.** Works but proliferates the comparison; one named derived value (`taskLaneIsForActiveChat`) reads better and groups the gate with the persistence-effect / state declarations as a named concept.
|
|
|
|
**Consequences:**
|
|
- Stale task-lane data is structurally unable to display. The lane is hidden during any window where `ownerChatId !== activeChatId`, no matter what mutation path got you there.
|
|
- Adding new sites that populate `activeQuestions` / `activeActions` requires also setting `taskLaneOwnerChatId`. The pattern is documented in the commit message and visible in every existing populate site as a paired call.
|
|
- The mount-time `skipTaskLaneRestore` guard is now redundant in steady-state but kept for the few-hundred-ms flash window between component mount and the first sendPrefill / selectChat effect. Deleting it would re-introduce a (smaller) flash without strong reason.
|
|
- Future task-lane state slices (e.g. `facts`, `activeFix`) follow the same pattern: gate their visibility on the owner check via the existing render conditions. Tagging more slices with their own `*OwnerChatId` is a future refactor if the slices diverge.
|
|
|
|
---
|
|
|
|
## 2026-04-24 — Adopt dual-agent handoff system (`.ai/` + `CLAUDE.md` + `AGENTS.md`)
|
|
|
|
**Context:** Claude Code hits session and weekly usage limits. Work stalls when the primary agent is locked out. Needed a structured way for OpenAI Codex to resume where Claude left off without losing architectural truth or drifting across sessions.
|
|
|
|
**Decision:** Split the old CLAUDE.md into `.ai/PROJECT_CONTEXT.md` (stable repo truth), agent-specific root files (`CLAUDE.md`, `AGENTS.md`) with a shared protocol block, and a small handoff toolkit (`CURRENT_TASK.md`, `HANDOFF.md`, `TODO.md`, `DECISIONS.md`, `SESSION_LOG.md`, `README.md`). Previous CLAUDE.md snapshotted in commit `e110fed` before the migration.
|
|
|
|
**Rejected:**
|
|
- Single symlinked CLAUDE.md/AGENTS.md — diverges silently, hides agent-specific tooling differences.
|
|
- Putting GitNexus/gstack content in AGENTS.md — Codex doesn't have those tools; would mislead the resume agent.
|
|
- Keeping the old CLAUDE.md as-is and adding AGENTS.md alongside it — duplicated truth, drift guaranteed.
|
|
|
|
**Consequences:**
|
|
- First read for either agent: `.ai/PROJECT_CONTEXT.md` + `.ai/CURRENT_TASK.md` + `.ai/HANDOFF.md`.
|
|
- Architectural changes in the repo require updating PROJECT_CONTEXT.md, not the root agent files.
|
|
- Git trailers differ per agent (`Claude Opus 4.7` vs `Codex`) — preserved in each root file.
|
|
- Legacy `SESSION-HANDOFF.md` deleted in the same commit; superseded by `.ai/HANDOFF.md`.
|