Registrations declare a dictionary namespace (locale: NS) and the renderer
synthesizes a typed t prop for the entry's component from the installed
LocaleFace; the seat binding is re-derived per locale revision, so a language
switch hands out fresh t references and memoized consumers re-render through
ordinary shallow comparison. LocaleNamespaceMap is the declare-merge table
(namespace -> dictionary key union); TranslateNS<'ns'> is the
namespace-addressed translate type (namespace keys plus the shared common
vocabulary), carried by the t seat and by the locale service's typed bind.
LocaleService implements the face (lookup ns -> common -> zh -> key,
revision-carrying snapshots with subscriber isolation) and installs it
through the boot-once slots.installLocale seam, mirroring the renderer
install. The typed register(ns, {zh, en}) overload checks each dictionary
against the namespace's key union and requires every shipped locale, so a
missing or extra key and an unbalanced translation are compile errors.
Dictionary registration bumps the face revision without emitting
locale/change — the event now means exactly 'the active locale switched',
so registration-heavy boot cannot storm event listeners.
A provider roster change under a stable current id rematerialized every
scope's bundle but nothing notified React: SessionProvider resolved the
bundle from a current-id subscription only, so mounted entries kept the
obsolete hook/prop schema until an unrelated re-render. The sessions
service now owns an atomic currentProvide observable fed by both current
writes and roster changes; the renderer host exposes it as
sessions.provide, replacing the current/provideInfo/maybeProvideInfo
trio, and both providers subscribe to it.
The push-model client base (session-projection RFC final): ProjectionValueStore
holds key → {value, seq} per session, seeded by the tail page's projections
block and updated by session/projection frames under one rule — higher seq
wins on both paths (stale baseline cannot overwrite a newer frame; replayed
frames cannot regress; an omitting fresh baseline clears = capability absent);
truncate() drops phantom rows past a subscribed durable baseline. Per-key
identity-stable faces (always defined; absence is an undefined snapshot) feed
useProjection; the renderer contract's projections member becomes faceOf.
12 store specs cover both seq directions, absence, truncation, and batching.
React half of the session-projection client base: the renderer contract gains
an open-key projections face on SessionMaybeProvideInfo (cellOf(key), distinct
from the static hooks roster), web-react mints projectionHook (per-bundle cache;
per-cell uSES binding via the shared observableHook cache; unresolved keys read
undefined through the absent source so hook order stays constant), standardKit
delivers kit.useProjection, and the runtime merges UseProjection into
SessionStandardProps/SessionMaybeStandardProps (overloads mirror useSession).
3 jsdom specs (kit delivery + live re-render, selector over undefined, faceless
bundle = all absent); existing direct-prop-feed specs gain the one-line stub the
new required seat mandates.
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
The definitive slot model for the web client, replacing the first-generation
define/register two-step, ScopedSlots whitelist faces, and binding handles:
- 'root' is the only a-priori slot (SlotsService built-in); the shell renders
exactly ctx.slots.renderSlot('root', {}).
- register is the single API: children = slot declaration + render
authorization + runtime spec in one options object; misconfiguration fails
loud at load (duplicate declaration, undeclared contribution, one store
handle under two scopes).
- Component props arrive in four auto-derived shares: PropsRuntime<K>
(owner params + session/global standard kits via declare-merge),
PropsRenderSlots<S>, PropsStore<H>, and the inject business face.
sessionId is framework-supplied; hooks are framework-made only.
- Framework store seat: defineStore factories declare schema/actions/persist;
read = useStore, write = baked actions only; store scope derives from the
mounting entry; per-session persist keys and clearPersisted lifecycle.
- inject factories read the apply closure's own ctx (binding handles retired;
root-ctx back door closed); SessionProvider is self-wired render-prop.
- Rendering sits behind the SlotRenderer install seam; runtime stays
React-free; ownership ledger keyed to the single entry axis closes the
stale-authority window (StaleAuthorizationError probes).
Docs: the slot type-chain note is refreshed in place as the slot system
standard RFC (bilingual pair re-recorded); the web client architecture RFC
defers its slot sections there; packages/client/AGENTS.md gains the slot and
props discipline; gui-testing/web-styling notes drop missions/ references.
Tests: suites rewritten to the standard (props fed directly, real store
engines via createXXXStore().create(), no render machinery); load-time
negative samples for declaration/authorization/store conflicts; verified by
real-host playwright run (three columns, empty state, collapse, keyed session
remount, cross-slot selection sharing).
docs(ui-sidebar): point contract reference at the committed slot standard RFC
missions/ is workspace-local and never committed; the README must not cite it.