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
10 KiB
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。
- 持自己的 listeners Set;替换
- 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):
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 坑:
- session binding 来源=BindingContext(web-react 模块私有 React context,SessionProvider 写、SlotOutlet 读);session 坑在 Provider 外渲染(context 空)=throw fail loud。
- 标配注入:session 坑注
- 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 仲裁,不自行改接口)
- 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。 - 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 广播后)
- store 子路径全量+store.spec(可独立编译单元,最先落)。
- bindSnapshotSelector+shallowEqual re-export+spec。
- useInvoke+spec。
- BindingContext+createSessionProvider+spec(缺口 #1 裁决前先按提议形态留内部实现位,导出面不动)。
- scopedSlots+ErrorBoundary+缓存链+spec(缺口 #2 同上)。
- T1 后删 ui-slots 桩换真包,激活类型负样本。