Files
makelore/.project-docs/30-worklog/tasks/20260823-pi-renderer-store-b7e2c4a1.md
T

194 lines
11 KiB
Markdown

# Task: Implement PI-110 Renderer Snapshot Patch Store
## Identity
- Task ID: 20260823-pi-renderer-store-b7e2c4a1
- Mode: Feature
- Branch: codex/20260823-pi-renderer-store-b7e2c4a1-pi-renderer-store
- Worktree: D:\Datas\OthersProjects\makelore-pi-renderer-store-b7e2c4a1
- Base commit: a0722574f3b36cb73ec8833787711e6bef1c5c54
- Owner: codex-root
- Status: Done
## Scope
- Implement PI-110's vendor-neutral Renderer facade and one Zustand store keyed
by stable Makelore `conversationId`.
- Consume the shared PI-010 Snapshot/Patch reducer for snapshot-first SSE,
stale-generation discard, target-only gap recovery, and reconnect.
- Own per-Conversation snapshot/run/error/queue/model/draft/unread/cursor plus
optimistic prompt request state and fine-grained selectors.
- Add focused facade/store/SSE/render-count tests. Do not migrate Chat UI,
feature-complete controls, or remove the old OpenCode store in this ticket.
## Intent And Constraints
- Base is the planner-approved PI-100 cumulative HEAD `a072257`; the exact and
sole Ready Frontier is `{PI-110}`.
- Renderer access goes only through `src/lib/host-api.ts`; the SSE facade must
initialize the cached Host token/base before constructing EventSource.
- Reuse the PI-010 contract/reducer as the single product normalization seam.
The first real Renderer consumer exposed that its Electron-only source
location requires unavailable composite-project declarations in a clean
worktree, so the canonical modules now live in `shared/` and Electron keeps
thin re-exports. Do not duplicate Pi/OpenCode event mapping or import vendor
types, paths, wire enums, credentials, or raw diagnostics.
- A generation/sequence gap invalidates and reloads only its target
Conversation. Old generations are ignored and accepted prompts are never
replayed because an event stream reconnects.
- Optimistic user identity is stable across durable reconciliation by
`clientRequestId`. Definite rejection and uncertain acceptance retain enough
request/draft/attachment state for explicit UI recovery; neither path
silently resends.
- Store updates replace only the target Conversation entry. Timeline selectors
for the selected Conversation must not rerender when a hidden Conversation
streams; hidden/sidebar consumers use lightweight summaries.
- PI-120 owns Composer and timeline UI; PI-130 owns model/thinking/queue/
compaction/interaction/subagent/changes UI; PI-140 owns old OpenCode removal.
- Real Provider remains Explicitly Waived / Accepted Risk with
`realTurnVerified=false`; macOS remains deferred to mandatory PI-150. Neither
is Pass evidence. No subagents are authorized.
## Project Context Loaded
- Task identity: `20260823-pi-renderer-store-b7e2c4a1`, feature mode, isolated
branch/worktree above, base `a0722574f3b36cb73ec8833787711e6bef1c5c54`.
- Concurrent Task Gate passed. The only semantically adjacent planning task,
`20260820-partner-create-model-parity-c83d`, still has no defined scope or
owned files. The canonical main integration task is Blocked and is not a
write target. Other active tasks concern unrelated Canvas/Robot/Learning or
review scopes.
- Planning Gate inputs read: memory index, project positioning/success fields,
current state, decision/system/architecture/domain documents, evidence/
reflection/commitment/stale indexes, PI-010 and PI-100 records, and the exact
PI-110 ticket plus relevant Spec sections.
- The canonical project-positioning fields remain placeholders; repository
guidance and the planner-owned Pi Spec/ticket are authoritative here.
- Confirmed seams: vendor-neutral contracts and strict reducer in
`electron/coding-runtime`, snapshot-first `/api/coding/events`, target GET
snapshot, `hostApiFetch`, `ensureHostApiToken`, and Zustand selector patterns.
- No accepted ADR changes product positioning or conflicts with this task.
Existing real OpenCode/provider commitments remain historical and do not
authorize a compatibility store or runtime fallback.
- Gate result: Passed.
## Plan
1. Add a Renderer-owned type surface and minimal typed `/api/coding` facade for
target snapshot, prompt acceptance, and initialized global/target SSE.
2. Implement a dependency-injectable Zustand store that keeps reducer state,
summary, draft, unread, request lifecycle, loading/recovery and errors per
Conversation while owning selection and one global event stream.
3. Route snapshot and ordered patch events through the shared reducer; ignore
stale generation, reload only a gapped target, and never replay mutations on
reconnect.
4. Add optimistic user-node/draft/attachment reconciliation and granular
selectors that preserve unrelated Conversation object identity.
5. Add facade, two-Conversation SSE, gap/reconnect, optimistic failure, vendor
boundary and render-count regressions; then run focused/full verification,
build, documentation gates, and planner review.
## Outcome
- Added a Renderer-owned product type surface and minimal typed Host facade for
target snapshots, prompt acceptance, and EventSource creation. EventSource
construction first hydrates the cached Host token/base, including a cold
Renderer process.
- Added one dependency-injectable vanilla Zustand store with React selectors.
Each Conversation owns its reducer snapshot/cursor, load and target error,
unread state, lightweight summary, draft revision/attachments, and prompt
request lifecycle. Store updates preserve unrelated Conversation identity.
- Added snapshot-first SSE ingestion, native reconnect state, stale-generation
discard, per-target sequence/generation-gap recovery, load single-flight,
and connection-generation protection. Reconnect never replays a prompt and
an obsolete connection flight cannot clear a newer one.
- Closed the planner review's two P1 recovery windows: patches arriving while
a target GET is in flight are buffered only for that invalidated Conversation
and replayed in generation/sequence order after a valid Snapshot; unresolved
pending/accepted/uncertain prompt requests re-overlay their local optimistic
nodes after snapshot-first reconnect until a durable `clientRequestId`
reconciliation arrives. If the reconnect Snapshot already contains that
durable message, its message id is first reconciled to the local request's
stable UI node id before the request is removed. Neither mechanism replays a
mutation.
- Added optimistic user nodes keyed by `clientRequestId`. Durable upsert keeps
the optimistic UI node id; definite rejection restores an untouched draft
and marks the node failed; uncertain delivery restores the draft while
retaining the request/node for explicit reconciliation without resend.
- Promoted the existing product-neutral PI-010 contracts, reducer, product
detail validator, and subagent detail validator to `shared/`. Existing
Electron paths are compatibility-free source re-exports of the same
functions, so Main, Renderer, fixtures, and tests cannot drift into separate
reducer implementations.
- Added facade/store tests for two interleaved Conversations, target-only gap
recovery, stale generations, native reconnect, connection-flight ABA,
optimistic accepted/rejected/uncertain outcomes, attachment restoration,
vendor-neutral source boundaries, shared reducer identity, and render-count/
summary identity under hidden streaming.
- PI-120/130/140 boundaries remain unchanged. No Chat page or legacy OpenCode
store consumer was changed by PI-110.
- Implementation commits: `36626c8` (Renderer store/shared seam), `1abb2ff`
(gap recovery + snapshot optimistic overlay), and `934ba02` (durable Snapshot
UI identity reconciliation).
## Verification
- `pnpm exec vitest run tests/unit/coding-conversations-facade.test.ts tests/unit/coding-conversations-store.test.tsx`:
initial implementation 2 files / 12 tests passed; after review fixes, 2 files
/ 14 tests passed, including delayed gap GET + concurrent patch replay,
reconnect Snapshot optimistic-node continuity, and a reconnect Snapshot that
already contains the durable user message while preserving `node-1`.
- `pnpm exec vitest run` for the two PI-110 tests plus PI-010 contracts,
coding core routes, event projector, Conversation runtime, product tools,
and subagent suites: initial 8 files / 75 tests; final review-fix range 8
files / 77 tests passed. One intervening parallel run hit a non-reproducible
`CODING_STORAGE_WRITE_FAILED` in the unrelated Conversation runtime temp
persistence test; that file passed alone immediately afterward and the same
8-file command then passed in full.
- `pnpm run typecheck`: passed after the shared product seam removed the clean
Renderer -> composite Electron declaration dependency.
- `pnpm run lint:check`: passed with zero errors and the same six unrelated
Renderer warnings in `ExecutionGraphCard.tsx`, Home, and Makelore.
- Final `pnpm test` at `934ba02`: 211 files / 2277 passed / 2 skipped (2279
total).
- `pnpm run build:vite`: Renderer, Electron Main, Preload, and release utility
production builds passed; existing dynamic-import and large-chunk warnings
remain.
- Electron E2E is not applicable yet: PI-110 intentionally exposes no page or
user-visible interaction. The real Chat/Composer binding and its E2E belong
to PI-120; the render-count store acceptance is covered directly here.
- Real Provider remains Explicitly Waived / Accepted Risk with
`realTurnVerified=false`. macOS x64/arm64 remains deferred to mandatory
PI-150. Neither is recorded as Pass.
- Planner review of `a072257...36626c8`: Standards PASS / 0 findings; Spec
Needs Fix with two P1 findings for recovery-window patch loss and reconnect
Snapshot optimistic-node loss. The first fixed-range re-review accepted gap
replay and missing-durable-node overlay, then found one remaining P1 where a
Snapshot already containing the durable node adopted the server id. That
reproduction is now fixed by `clientRequestId` id reconciliation before
request cleanup, with the focused regression above.
- Planner final review of `a072257...934ba02`: Standards PASS / 0 findings and
Spec PASS / 0 findings. Independent reproduction ended with `node-1`, the
durable `sourceEntryId`, zero pending requests, and exactly one prompt call;
no Conversation leakage, retry loop, or mutation replay was found.
## Follow-ups
- PI-120 should bind the Composer/timeline to this store without restoring any
read from `src/stores/opencode.ts`.
- Planner dependency computation identifies `{PI-120}` as the next exact Ready
Frontier after this task reaches `ready_for_integration`; PI-130/140 remain
locked by their declared dependencies.
## Promotion Candidates
- Type: Architecture. Proposal: keep Makelore-owned Conversation contracts and
the strict Snapshot/Patch reducer in `shared/`, with Main and Renderer
importing/re-exporting that single implementation rather than depending on
Electron composite build artifacts or copying normalization logic. Evidence:
clean `pnpm run typecheck`, shared/Main function identity regression, final
8-file / 77-test impacted suite, and 211-file / 2277-passed full unit/build
verification. Future
impact: PI-120/130 can consume product DTOs without vendor/Main imports; any
contract amendment must retain Main/Renderer reducer equivalence.