Files
deepseek-harness/missions/tasks/20260721-p1-fw-react/notes.md
imccyu 0681ac47de chore(gui): mission work logs
chore(gui): mission work logs — cordis design finalization, tool-card wire archive, incident records

chore: missions

chore: missions

chore(gui): mission ledger — batch-2 answers, parallel dispatch state, jsdom coverage re-scope

chore(gui): ledger — night-mode standing orders (self-commit small, no push, 5-min refresh)

chore(gui): ledger — jsdom batches 2-4 landed (233 green), coverage probe next

chore(gui): ledger — web-ui coverage probe 65%, four-tier fill plan approved

chore(gui): ledger — cordis-impl B1 state after third API drop, decisions on file

chore(gui): ledger — 01:32 patrol snapshot (jsdom tier-1 landed, coverage-fixer probed)

chore(gui): ledger — 01:37 patrol (peer src trio landed, coverage-fixer still silent)

chore(gui): ledger — 01:42 patrol (jsdom tier-2 landed, peer committed x2, coverage-fixer 2nd probe)

chore(gui): ledger — coverage diagnosis complete (6-file gap list), web-ui at 91.4%

chore(gui): ledger — 01:46 patrol (B1 done, jsdom tier-3 landed, coverage fix batch running)

chore(gui): ledger — 01:51 patrol (jsdom tails x2 landed, B2 underway)

chore(gui): ledger — 01:56 patrol (gateway.ts 337 lines, checkpoint T-7min)

chore(gui): ledger — hold/pending split ruling, coverage-fixer externalize-or-restart ultimatum

chore(gui): ledger — 02:01 patrol (B2 done, jsdom final arms, coverage ultimatum pending)

chore(gui): ledger — 02:03 checkpoint executed (fixer2 respawn, four lanes released, three owners cold-started)

chore(gui): ledger — all six lanes acked, type isolation first live proof (client closure clean)

chore(gui): ledger — 02:08 patrol (all seven lanes active, wire carrier assembled)

docs(gui): respond-design task checkpoint — apiproxy wire-layer recon done

chore(gui): ledger — P0-2 contributor AGENTS.md landed (dd28a5019)

docs(gui): respond-design checkpoint 2 — host-side recon (stub respond, frame types, approval seam, ACP answerer precedent)

docs(gui): OOP debt inventory — seven territories, 2 real debts (createApiProxy, createFixtureApi), rest ruled keep-as-is

docs(gui): disambiguation note on the archived i18n design task

chore(gui): ledger — 02:12 patrol (exclude removed, mixed-knife incident under reconciliation)

chore(gui): ledger — 02:15 wave (jsdom mission closed, OOP audit done, B3 isolation proof, mixed-knife resolved)

chore(gui): ledger — 02:17 patrol (attribution reversal filed, arch-session probed)

chore(gui): ledger — 02:22 patrol (B4 done, B5+B6 merged batch, arch-session deadline set)

docs(gui): respond-design checkpoint 3 — client-side recon (pending map, PendingCard onRespond stub, AbstractApiClient.respond ready, bootHost missing approval mounts)

docs(gui): peer carrier territory review — 2 fixes (SSE cancel leak, route-reservation guard), 1 ruling ask (RPC-log visibility), compliance ledger

chore(gui): ledger — 02:27 (arch-shell respawn, territory review verdicts routed, ask-deny finding flagged)

docs(gui): P1-5 respond design page complete — pending registry, wire answerer, client state machine, first-wins arbitration

chore(gui): ledger — 02:31 patrol (respond design complete, B5 wire smoke green, shell knife 1 underway)

docs(gui): respond design — add §0 status warning (web host ask defaults to deny), mount-behavior delta, no-timeout ruling with Config discipline

chore(gui): ledger — 02:42 patrol (respond line closed pending review, B7 last piece underway)

docs(gui): respond design contract review — direction pass, 2 doc fixes (settle-order contradiction, answering-state race), A/B/C compliance ledger

docs(gui): respond design — contract review fixes (R1 verify-before-delete arbitration, R2 answering+resolved-frame transition, ask dual-source wording, rejected-is-ok-value note)

chore(gui): ledger — 02:46 patrol (respond line final, two user decisions distilled, arch-shell deadline)

docs(gui): respond review addendum — contract-gap ruling: approve plan A (ApprovalRequest.id), wire unchanged, drop plan B backscan

chore(gui): ledger — cordis B7 summit: real-browser 10/10 green, user acceptance criterion proven

docs(gui): respond design — contract gap #4 approved as plan A (ApprovalRequest.id), backscan fallback retired, blade 0 prepended

docs(gui): respond design — final polish (owner-approval vs user-go-ahead wording, implementation handoff notes)

chore(gui): ledger — 02:51 (shell-exec third respawn with operational script, respond line 4-knife final)

chore(gui): ledger — 02:53 wave (respond five-knife true final, B7 closed 12/12, client.ts green)

chore(gui): ledger — 02:56 patrol (cordis closeout bounced pending R1/R2/N1, shell-exec first sign of life)

chore(gui): ledger — 03:01 patrol (R1/R2/N1 remediation in flight across four files)

chore(gui): ledger — cordis line officially closed and archived, verified on disk (24 knives, 12/12, reviews closed)

chore(gui): ledger — 03:06 patrol (shell-exec final window, lowered first-knife bar)

chore(gui): ledger — 03:11 patrol (shell line iced-broken: three registries on disk)

chore(gui): ledger — 03:15 patrol (quiet window, both active lanes within threshold)

chore(gui): ledger — 03:20 patrol (shell five files up, api-proxy plan reported)

chore(gui): ledger — 03:25 patrol (shell migration in flight with history-preserving moves, webserver green)

chore(gui): ledger — 03:30 patrol (shell knife-1 in verification, api-proxy patching)

chore(gui): ledger — shell knife 1 accepted (694cecc53), knife 2 released

chore(gui): ledger — 03:40 patrol (knife 2 pre-move stage, cold-list spec appears)

chore(gui): ledger — 03:45 patrol (rpclog moves staged, api-proxy two specs in flight)

chore(gui): ledger — 03:54 patrol (knife-2 code done, coverage full-run final check)

docs(gui): coverage-fixer task ledger — fixer2 takeover, per-file fix log, isolated reportsDirectory pitfall

chore(gui): ledger — coverage lane closed and accepted (a19f069a5), the PR #443 CI fix knife

chore(gui): ledger — 04:04 patrol (knife-2 calibration, sole active lane)

chore(gui): ledger — shell knife 2 accepted (f8fb77b95), knife 3 released as final night task

chore(gui): ledger — 04:19 patrol (knife-3 past half: callback chain through, ToolCallDetail up)

chore(gui): ledger — night closeout summary: seven lanes closed, wake-up decision sheet

chore: missions

chore: missions

chore: missions

chore(gui): mission-local browser/probe verify scripts under missions/scripts/

The six acceptance/probe scripts move here as mission-side working
material (headers and relative imports adjusted for the new location):
carrier-errors, rpclog-panel, session, session-real,
webserver-backpressure, webserver-hardening.

chore(gui): verify-relocate mission log

chore(gui): verify-relocate mission log — R1 guard addendum

chore(gui): gates-continue mission log — CI-equivalent sequence all green

chore(gui): VS Code 扩展体系双边设计调研报告

chore(gui): 调研追加 4.5 节——git 扩展数据面与 scope 绑定

docs(gui): web plugin system RFC — walkthrough + design notes

docs(gui): RFC — restore existing SSE/POST as the v1 transport; envelope rides on it (D16)

docs(gui): RFC — envelope demoted to chan-dispatch, scope out of envelope, rpc-log cut, peer deferred, scope tree is native cordis (D17-D21)

docs(gui): RFC — hooks re-derived from component needs: useWatch/useAction only, useService removed; sessionHub cut, projections user-space, router rename, loader-only root (D22-D25)

docs(gui): RFC — drop stale fork vocabulary (vendored cordis has Fiber only; scope = mintScope pattern), hook idempotence contract (D26-D27)

docs(gui): RFC — session precision seam: plugins read scope key (host paradigm), React gets it from tree position via SlotOutlet (D28)

docs(gui): RFC — domain hooks owned by plugins over framework primitives; useConversation paradigm carried over (D29)

docs(gui): RFC — ctx services are the inter-plugin API (cordis proper); declarations are wire-only; get(id) returns scoped ctx (D30)

docs(gui): RFC — full ctx.conversation walkthrough: root-singleton scope-sensitive service, caller-ctx scope key, get(key) as scoped ctx (D31)

docs(gui): RFC — no client-side agents collection: session state machine already expresses the duality; agent resolution stays host authority (D32)

docs(gui): RFC — v1 stays session-precision, no agent-level isolation; incarnation/agent-axis designs archived in ledger (D33)

docs(gui): RFC walkthrough — full rewrite to final state (D16-D33 consolidated), end-to-end chain restored

docs(gui): RFC — apiproxy demoted to generic channel routing; domain RPCs dissolve into owner plugins (D34)

docs(gui): RFC — TS-interface-first wire contract (zod internal), conversation owns the dialogue frame with pluggable views (D35)

docs(gui): RFC — page skeleton (sidebar+conversation), projects as plugin not service, nested slots via owner registries (D36)

docs(gui): RFC — SlotMap declaration-merging slot model: single register API, inject-as-ownership, FC-typed registration, typed outlets (D37)

docs(gui): RFC — slot props whitelist: identity, display params, materialized snapshot slices, stable UI callbacks (D38)

docs(gui): RFC — end-to-end data flow: three transforms, equality protocol table, immer placement; i18n/theme kept standard (D39-D40)

docs(gui): RFC — full external-injection model: props carry values + stable injected hooks; shared/client/react example rewritten (D41-D43)

docs(gui): RFC final trio — modules.md (agent implementation spec), architecture.md (human walkthrough), plugins.md (business plugin inventory)

docs(gui): RFC — props three-source merge (scope-standard useSession auto-injected); keyed key vs list id disambiguated (D44)

docs(gui): RFC — inject comment says what it is (the React-facing props bundle); SessionHandle rename; snapshot-production story unified on buildSnapshot

docs(gui): RFC architecture — full React component tree walkthrough: props three sources, slot vs plain children, hook taxonomy per node

docs(gui): RFC — module map finalized (ui-slots/web-react/connection/runtime/ui-*/web); slots onChange replaced by cordis events; toolcall dimension; detail sidebar default-collapsed with toolName-keyed routing

docs(gui): RFC plugins — openDetail relay chain: toolcard calls chat-view injected action, chat-view relays to conversation sidebar

chore(gui): progress ledger — full archive rewrite: RFC outcome digest, open gaps, dispatch plan, cold-start entry

docs(gui): RFC grill pass 1 — SlotScope axis (root/session) on declares, Gate dependency inversion, inject handle by scope, W5 acceptance list, gantt relay chain fixed

docs(gui): figma analysis — sidebar/projects/sessions 区域交互视觉理解报告

docs(gui): figma 解析报告 — details 面板/多视图 tabs/未来功能区盘点 + slot 需求清单

docs(gui): figma 对话主区解析报告 — 消息流/tool calls 变体/审批接管输入框/Header tabs/视觉 token

docs(gui): plugins.md rewritten from figma analysis — three-column layout, full slot reservation table, selection channel, composer-takeover approvals, phased scope

docs(gui): layout dynamics ruled (drag+collapse both rails, details yields first, composer swap-panel, same-component transition); toolviews promoted to named scope-aware registry

docs(gui): P-I scope locked (details minimal, dual theme, chat-view, custom toolview sample); teammate dispatch plan — 6 owners by package, dependency-driven waves, contract arbitration

docs(gui): P-I api-contracts (full inter-package API spec) + dispatch plan (T0 skeleton knife, 7-dev roster, task briefs, milestones)

docs(gui): api-contracts v2 — scope tree in P-I, bundle loader + per-plugin CSS isolation in P-I, agent-scoped toolviews live, zustand engine, renames (SessionProvider/ObservableSnapshot/SessionBinding), router owns all shell view-state

docs(gui): services roster + progressive loading (no blocking loadAll), SlotsService as real cordis Service, renderSlot/renderSuspenseSlot duo, ui-traj teammate

docs(gui): loading-chain gaps ruled — dev=rebundle no HMR, ui-primitives package, externals on globals (no import map), host injects __DSH_BOOT__ into HTML (zero round-trip)

docs(gui): api-contracts v3 + dispatch v2 final — 12 packages, services merged in, progressive loader, global externals, __DSH_BOOT__ injection, 8-dev roster with convo split and ui-traj

docs(gui): v3 amendments — router renamed ctx.layout, ui-trajectory has no service (pure consumer sample), wait-for-settled loading (no Suspense in P-I, ledger 6b)

chore(gui): progress — pre-compact final state: v3 revision chain, 8-dev roster, T0 procedure, doc authority order

docs(gui): authority banners — modules/architecture get v3 term-mapping headers, walkthrough marked as archived process doc

docs(gui): cssdesign token set is THE theme source (--dsw-* variables, data-ds-dark-theme switch); recorded in contracts + progress

docs(gui): architecture.md full v3 rewrite — loading chain, 12-package map, service roster, slot/inject/toolviews, data flow, component tree, perf model, all current

docs(gui): contracts — UI plugins are dual-entry host plugins (node half serves client asset via ctx.webPlugins; __DSH_BOOT__ derives from it; client-closure gate back in scope)

docs(gui): contracts — closure-factory bundles with DI require (no globals), package.json dshWeb declarative discovery (no serve ritual), create-then-send empty state with project picker, ancestry() for breadcrumb, unload stubbed until HMR, props.renderSlot confirmed

docs(gui): dshClient declaration (inject/platform/immediately, exports./client), closure-DI require loading — synced across contracts/dispatch/modules/architecture/walkthrough

chore(gui): progress — record final loading-chain rulings (dshClient declaration, closure-DI require, startSession) before compact

docs(gui): architecture.md — developer-facing whole-web architecture on master baseline 6b16a67cb: what exists, what is new, no process narrative

docs(gui): architecture.md — self-contained whole-web architecture: absorbs still-valid substance from the branch RFCs (host layering, four-quadrant RPC, object layer, testing tiers) under the new plugin system as the override

chore(gui): progress — final pre-compact snapshot: contracts digest, apiproxy purity ruling, T0 procedure with first-action list

docs(gui): api-contracts v3 §3.1 — apiproxy purity principle with three-way existing-code verdicts

docs(gui): api-contracts v3 — immediately reinterpreted as static-infra group (8-package dshClient scope, boot manifest reconciliation)

docs(gui): api-contracts v3 — immediately corrected to early-load dynamic group (prod shell must not rebundle); loader shell-held; bundles register their export surface into module table

docs(gui): architecture — align with immediately=early-load dynamic group ruling; loader shell-held; module-table registration of loaded bundles

docs(gui): T0 checklist — 12-package skeleton table, 4-cut sequence, mv/attic/rewire rules (pre-drafted, awaiting go)

docs(gui): t0-checklist — pin figma-flows findings (missing font-family base vars, three alias vars behind upstream)

docs(gui): dispatch v2.1 — drop cordis-web salvage wording, two-wave staffing, loader/immediately boundary updates

docs(gui): progress + t0-checklist ledger — T0 landed, staffing status, execution accounting

docs(gui): api-contracts v3 — arbitration round 1: renderBody deps, RootBindingProvider, flush default sync, prune current, loader subpath, config-source P-I bar

docs(gui): v3 §3.2 connection 导出清单附录(rt-core 对账)+ rt-core 实现计划档案

docs(gui): progress — T1 milestone, arbitration round 1 ledger, fw-react timeout escalation

docs(gui): progress rolling update — per-line battlefield state at 00:2x, mailbox-vs-contract lesson, small-batch discipline reinforced

docs(gui): fw-react notes — v3 §2 complete, seven knives, T1/T2 follow-ups

docs(fw-slots): archive — four packages landed, open tails logged

docs(gui): progress — framework layer complete (web-react five, fw-slots four packages), T2 gated on rt-core runtime knife only

docs: api-contracts

docs: style-spec

docs

docs(gui): tsconfig convergence ruling — no host.json, root resumes host-aggregate duty, typecheck = root + client aggregates

docs(gui): missions 根三份 07-18 世代档案加「已被取代」头注——指向 web-plugin-rfc 现行权威并注明新旧对应

missions

docs(gui): progress rewritten for post-closeout state — wave ledger, architecture finale, teammate roster with handover notes, pending-user-command queue
2026-07-22 21:30:30 +08:00

82 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# fw-react 实现计划(web-react 包;契约=api-contracts v3 §2)
> 2026-07-21 预习完成。权威序:v3 §2/§1 → architecture §11/§13/§17 → dispatch 任务行+§2 → conventions → t0-checklist。
> **状态 2026-07-22:v3 §2 全部完工**。七刀:e873a846e 依赖+.d.ts / e7384bc6e bind / c7d20d53a store 引擎 / 219ce8af9 useInvoke / f8890b960 bind.spec / 4de74fac5 SessionProvider+RootBindingProvider / 67679d870 scopedSlots。5 spec 35 测全绿,包内 typecheck 绿。
> 与计划的偏差:①immer 走 update() 内 produce 而非 immer 中间件(中间件 setState mutator 泛型与 vanilla StoreApi 冲突;行为面同);②仲裁采纳 renderBody/RootBindingProvider(932716e02)。
> 待 T1/T2:~~换真 ui-slots core 重跑 scoped-slots.spec;补 @ts-expect-error 越权负样本~~ 已落(f2eb2287c;初版 a367d78a3 被主会话 reset 误剥、暂存区无损重落,主会话认账并立规:共享 worktree 全员零历史改写)。剩:UseSession 收窄对齐 rt-core 的 ConversationSnapshot(rt-core 类型归家时 main 会叫)。
> 终态:6 spec / 39 测全绿;tsc -b 绿。待命=T2 后机动支援(convo-b Profiler 断言/W5 验收)。
## 1. createSnapshotStore(store 子路径)
**zustand 留 ~4.4.7 不升 v5**:我们只用 vanilla `createStore` + 中间件层,自己经 uSES 出 hook,v5 的变化全在它的 React useStore 层(我们不用);mutative 换 immer 无 P-I 级收益。不动=无需仲裁,报备即可。
**中间件链**(外→内):`persist?(rafFlush?(subscribeWithSelector(immer(init))))`
- **顺序理由**:rafFlush 必须在 subscribeWithSelector 外侧——sws 初始化时捕获当时的 `api.subscribe` 作为底座;rafFlush 先跑(外侧)才能让 sws 的 selector 订阅也走合批面。persist 不碰 subscribe,放最外。
- **rafFlush 设计**(自研,真中间件形态):
- 持自己的 listeners Set;替换 `api.subscribe` 为「注册进自己的 Set」;
- 用捕获的原始 subscribe 注册**唯一一个**内部监听:置脏标记 + `requestAnimationFrame(flush)`(已排帧则跳过);
- flush:清脏标记后逐个通知自己的 Set。一帧内 N 次 setState = 1 次通知。
- `flush:'sync'`(省略该中间件)= 每 setState 同步通知。**默认 'sync'**:契约未定默认;受控输入(drafts textarea)与用户手势要求同 tick 通知(architecture §5 Notifier 纪律:延迟到微任务/帧会 DOM 回滚+光标跳尾)。帧驱动 store(sessions.list 等)由创建方显式传 `'raf'`。→ 报备 main,契约若想钉死默认值再补一句。
- raf 缺席环境(node 单测)回退 queueMicrotask;测试用假 raf 注入计数。
- 已知取舍(注释入码):'raf' 模式下同帧内新挂载组件读到新 state 而旧组件未收通知——帧级瞬时不一致,与对象层 Notifier 微任务合批同性质;只用于帧驱动 store。
- **immer**:官方 `zustand/middleware/immer`,`update(mutator)=setState(mutator)`(recipe 形态)、`set(next)=setState(next, true)`(整体替换)。
- **dev freeze**:immer 产物自带 freeze;`set(next)` 整体替换绕过 immer → dev 下(`process.env.NODE_ENV!=='production'`)deepFreeze(next)。
- **persist**:opt-in `{name}` → zustand persist + localStorage(纯 CSR,无 SSR 分支)。
- 门面(SnapshotStore<T>):`getSnapshot=api.getState`、`subscribe`(走合批面)、`update`、`set`、`useSelector=bindSnapshotSelector(this)`。
## 2. bindSnapshotSelector(uSES 桥)
- 引擎:`use-sync-external-store/shim/with-selector` 的 `useSyncExternalStoreWithSelector`(zustand 4.x 既有传递依赖,需在 web-react package.json 补显式 dep + @types devDep——离线可解,包已在 store 里)。
- 绑定时(每 store/Session 构造一次)闭包生成稳定的 `sub=(cb)=>w.subscribe(cb)` 与 `getSnap=()=>w.getSnapshot()`——**方法经箭头闭包绑定 this**(直接传 `w.subscribe` 会丢 this),且闭包只建一次=引用永稳。
- uSES 契约四条落点:①getSnapshot 纯读缓存引用(源头保证:Session 恒返缓存、store 返 zustand state);②subscribe 稳定(上条闭包);③纯 CSR 不传 getServerSnapshot;④等值短路:eq 缺省=Object.is(with-selector 语义),对象切片调用方传 shallowEqual。selector 结果 memo 由 with-selector 自持(state ref + selector/eq 同一性缓存),并发重放安全(getSnapshot 幂等,无 render 期副作用)。
- shallowEqual = re-export `zustand/shallow`。
## 3. scopedSlots 工厂(renderSlot 出口)
- `scopedSlots(core, ...keys)` 返回 `{ renderSlot }`;renderSlot 返回内部组件 `<SlotOutlet>` 元素。运行时白名单双保险:key ∉ keys → throw(编译期由泛型 K 挡)。
- **订阅**:SlotOutlet 内 `useSyncExternalStore(cb=>core.subscribe(key,cb), ()=>core.getVersion(key))`——version tick 驱动 `core.entries(key)` 重读(微任务合批归 core)。
- **按 kind 渲染**:spec 缺失(define 前渲染)=throw fail loud;single:0 条→fallback??null;list:order 排序+`only` 过滤 id;keyed:`entryKey` 命中,无→fallback。
- **每 entry 包 ErrorBoundary**(自写小类组件,fallback=极简错误占位含 key/entry id;框架级英文文案,不进 i18n)。
- **props 三源合并**:`{...标配注入, ...cachedInject(binding), ...ownerProps}`(owner 最后=可覆写,与契约顺序一致)。
- 标配注入:session 坑注 `useSession=binding.session.useSelector`;root 坑无。
- **inject 缓存链**:
- session 坑:`WeakMap<SlotEntry, WeakMap<SessionBinding, props>>`——外层 key=entry 对象(依赖 fw-slots 保证 entry 对象跨 entries() 快照恒等,**跨包假设,需与 fw-slots 钉死**);内层 key=binding 对象(runtime 保证 per-session 恒等引用)。session 拆除→binding 失引→WeakMap 自动回收。
- root 坑:`WeakMap<SlotEntry, props>`,inject 只调一次。
- session binding 来源=BindingContext(web-react 模块私有 React context,SessionProvider 写、SlotOutlet 读);session 坑在 Provider 外渲染(context 空)=throw fail loud。
- P-I 单形态:无 Suspense,接口不留 renderSuspenseSlot(台账 6b)。
## 4. createSessionProvider / useInvoke
- SessionProvider:`useCurrent()` → 无 id 时 `renderEmpty?.()??null`;`resolveBinding(id)`(恒等引用)→ undefined 同走 empty;`<BindingContext.Provider value={binding} key={id}>` 重挂子树。
- useInvoke:pending **不走 setState**(architecture §17 幂等五条)——per-hook 外部微 store(useRef 持 `{count, listeners}`)+ uSES 读 `count>0`;invoke 经 latest-ref 模式包 fn=引用永稳;并发重入用计数(多次在途 pending 恒真);rejection 归 domain 层报错(事件回声),框架侧 console.error 兜底后复位 pending。
## 5. T0 桩对账(05756560b)
- store/index.ts:五件签名与 v3 §2 逐字一致 ✓。
- index.ts:结构一致;`SessionId→string`、`Context→unknown`、`ConversationSnapshot→ConversationSnapshotLike` 为声明的 T0 占位(桩注释已标 rt-core 替换)✓ 可接受。**遗留类型归家问题**:v3 §2 写 `UseSession=SnapshotSelectorHook<ConversationSnapshot>` 但 ConversationSnapshot 住 runtime(依赖方向 runtime→web-react,不可反向 import)——真类型的家需 rt-core/main 定(候选:类型下沉可共享位,或 runtime 侧收窄别名)。不阻塞我(实现全程泛型 T)。
- ui-slots 桩:`OwnerProps=Partial<E['props']> & object` 占位(精确 Omit 归 fw-slots)——我的越权/漏传 props 类型负样本在 fw-slots 真实现落地前只能部分生效,测试先写 @ts-expect-error 待激活。
## 6. 契约缺口(待 main 仲裁,不自行改接口)
1. **SessionProvider「渲染 conversation+details 两坑」但 deps 无 slots 面**:web-react 拿不到那两个 key(它们是 ui-layout 的 declare merge,web-react 引用=幽灵依赖,且违反白名单纪律「谁 own 谁 render」)。**提议**:deps 加 `renderBody:(id:SessionId)=>ReactNode`(装配方=ui-layout/壳闭包自己的 scopedSlots 供给),Provider 只管订 current+解析 binding+key 重挂+context。
2. **root 坑 inject 的 RootBinding.ctx 无供给通道**:scopedSlots(core,...keys) 签名里没有 ctx;sidebar 契约明写 `b.ctx.layout` 绑 actions。**提议**:web-react 补一个导出 `RootBindingProvider: FC<{binding: RootBinding; children}>`(React context,壳在 AppRoot 顶部挂一次),SlotOutlet 渲染 root 坑 inject 时读取;缺失=throw。
## 7. 测试计划(宽松期口径:关键逻辑有测)
- store.spec:结构共享(update 后未动分支引用不变);raf 合批计数(假 raf:N 次 update=1 通知);sync 模式同步通知;persist 往返;dev freeze 抛错;shallowEqual 语义。
- bind.spec(jsdom):等值短路(eq bail 不重渲);selector 变更响应;订阅引用稳定(重渲零重订);StrictMode 双调安全。
- slots.spec(jsdom,假 SlotCore+假 binding):三 kind 渲染矩阵;version tick 重读;ErrorBoundary 隔离(单 entry 炸不塌邻居);inject 缓存命中计数(同 entry×session 只调一次;换 session 重调);三源合并优先级;白名单运行时 throw+@ts-expect-error 负样本。
- provider.spec(jsdom):current 切换重挂(key 语义);binding 恒等透传;empty 分支。
- useInvoke.spec:pending 时序、并发重入计数、fn 变更后 invoke 引用不变。
- 工具面:vitest jsdom;渲染用 react-dom/client + act(若 workspace 已有 @testing-library/react 则用之,开工时查)。
## 8. 开工序(T0 广播后)
1. store 子路径全量+store.spec(可独立编译单元,最先落)。
2. bindSnapshotSelector+shallowEqual re-export+spec。
3. useInvoke+spec。
4. BindingContext+createSessionProvider+spec(缺口 #1 裁决前先按提议形态留内部实现位,导出面不动)。
5. scopedSlots+ErrorBoundary+缓存链+spec(缺口 #2 同上)。
6. T1 后删 ui-slots 桩换真包,激活类型负样本。