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

9.7 KiB

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: a0722574f3
  • Owner: codex-root
  • Status: In Progress

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. 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.

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 / 13 tests passed, including delayed gap GET + concurrent patch replay and reconnect Snapshot optimistic-node continuity.
  • 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: 8 files / 75 tests passed.
  • 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.
  • pnpm test: 211 files / 2275 passed / 2 skipped (2277 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. Both independent reproductions were accepted and fixed locally with the focused regressions above; fixed-range re-review remains required before PI-110 can be marked Done.

Follow-ups

  • PI-120 should bind the Composer/timeline to this store without restoring any read from src/stores/opencode.ts.

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, 8-file 75-test impacted suite, and full 211-file 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.