The data layer no longer depends on the React glue package, and business plugins no longer depend on web-react at all: - The store engine (zustand vanilla + immer + persist + dev freeze), defineStore, and shallowEqual move to @deepseek-ai/dsh-client-runtime, exported from the ./client main entry — no ./store subpath survives on either package (the web-react one is deleted, none is opened on runtime). - Store products are bare snapshot sources: useSelector leaves SnapshotStore/StoreInstance and Session; every hook is composed at the binding site in web-react's renderer (per-source cached uSES binding). The SlotRendererHost sessions face carries bare observables only. - SessionProvider becomes a standard-kit seat: an entry whose children declare a session-scope slot receives the framework component as a prop, retiring the last value import of web-react from plugin packages. UseSession and the session-area types now live in ui-slots. - web-react shrinks to the shell-only React glue (renderer, providers, uSES bridge); zustand/immer belong to runtime alone; the module-table seed and tsdown externals drop the web-react/store seat. - NODE_ENV replacement is defined once in the shared tsdown client preset (browser bundles inline the engine and lost vite's define); the 3-line process.env typecheck shim moves to runtime with the engine. - Stray tsc artifacts (.js/.d.ts/.d.ts.map beside sources under src/) swept repo-wide; they shadow real sources under vitest resolution. Verified: both aggregate typecheck programs at zero; 604 client tests green; repo-wide grep for web-react/store at zero; real-host playwright run 7/7 including persist round-trip. ci: fix test/docs
3.5 KiB
@deepseek-ai/dsh-client-ui-slots
Slot registry pure core, slot terminal design: SlotMap declaration merging, the single register composition API on SlotCore, the four-share component-props type family, the store-seat type family, and the renderer install-seam contract. React types only at runtime — the package is React-free and cordis-free.
One register({ name, children?, store?, inject?, ...kind }, Component) call contributes a component into a declared slot and, in the same breath, declares child slots (declaration = render authorization = runtime spec, one table), a store seat, and the registrant's business face. The component is checked at the call site against ComposedProps — the intersection of four shares, each derived from its single source of truth:
| share | type | source |
|---|---|---|
| runtime | PropsRuntime<K> |
SlotMap entry: owner (parent's renderSlot call site) + session standard kit + global seat |
| child render | PropsRenderSlots<S> |
the register call's children key set (statically narrowed renderSlot) |
| store | PropsStore<H> |
the declared handle: useStore selector hook + draft-stripped actions |
| business | I |
inferred from the inject factory's return |
The standard-kit interfaces (SessionStandardProps, GlobalStandardProps) are declared empty here and merged by the runtime package (same declare-merge pattern as SlotMap keys). Inject factory parameters derive from the declaration (InjectParams): session slots get sessionId, a declared store appends baked actions, nothing else — data access lives in the apply closure's ctx.
The store family (defineStore spec in / StoreHandle<T, A> out) types the store seat: init infers the state schema, actions is the complete draft-transform write set, BakedActions strips the draft parameter into the callbacks components and inject factories receive. The defineStore value implementation lives in the runtime package (the engine's home) and satisfies the DefineStore contract exported here. Engine products and the renderer host contract carry bare snapshot sources (getSnapshot/subscribe), never React hooks — hook binding is the render machinery's side of the seam; only the props-contract hook type (SnapshotSelectorHook) lives here.
SlotCore seeds the a-priori 'root' slot at construction and enforces load-time validation (undeclared-slot registration, duplicate child declaration, one shared handle under two scopes — all throw at register). An entry's disposer collapses its declared child slots recursively: ledger rows, contributions, and store mounts die on one lifecycle axis. renderer.ts carries the install seam (SlotRenderer, SlotRendererHost) plus StaleAuthorizationError/SlotOwnershipError; the implementation lives in web-react, the installation in the shell boot.
Model Experience
None, as the slot registry is browser-side UI plumbing; nothing here reaches a model request.
KV Cache effect
None; this package neither assembles nor sends a provider request.
Known Limitations and Deferred Work
isLivescans all records linearly — fine at UI-plugin registration counts (tens); revisit with an entry→record backref if ledgers ever grow hot.- The
__rendersphantom anchor is visible onPropsRenderSlots— the same accepted noise as the type-chain design's__accepts: generic method signatures compare loosely across key unions, so the contravariant marker is what enforces "component key set ⊆ children declaration".