194 lines
11 KiB
Markdown
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.
|