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
@deepseek-ai/dsh-client-ui-layout
Shell plugin: three-column AppFrame (drag handles, concession chain) + ctx.layout viewing-state service (nav, panel widths, persist); defines the sidebar/conversation/details/conversation.empty slots. Contract: api-contracts v3 §5.
Slot declarations use the composed-props entry form (owner share, no full props): the exported OwnerShare contracts are SidebarOwnerProps / ConvOwnerProps / DetailsOwnerProps / EmptyOwnerProps — registrants reference them via OwnerOf<'sidebar' | ...> and compose their own injected share locally. No entry declares children (declaring it requires the registered component to carry the slots face — reserved for future business slots): delegation authority is the component-side whitelist, i.e. AppFrame's ScopedSlots<FrameSlotKey> face over sidebar/conversation/details/conversation.empty. Since the root-slot rework the frame itself registers into 'root' and renders those child slots at its own render sites; the shell only renders 'root'.
The export surface is the cross-package contract only: the AppFrame trio (+ AppFrameProps) consumed by the web shell's assembly, LayoutService with its store shapes (NavState/PanelState/ViewId), and the OwnerShare contracts. The concession-chain solver (computeColumns) and its geometry constants are package-internal; tests import them from /src.
Model Experience
None, as the layout shell manages browser viewing state; nothing here reaches a model request.
KV Cache effect
None; this package neither assembles nor sends a provider request.
Known Limitations and Deferred Work
- Details open/width state is global — it does not follow the session (arbitrated for P-I); the per-session keyed upgrade slot is reserved.
- Concession-chain auto-close derives a zero width without touching the persisted open flag — the panel restores itself when the window widens; consumers must not read
details.openas the rendered truth. - Scroll anchoring during squeeze reflow is not implemented — deferred with the virtualized-list project.