refactor(gui): move the snapshot-store engine into the client runtime

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
This commit is contained in:
imccyu
2026-07-23 02:56:16 +08:00
parent 004a988168
commit 8b3d1ac943
77 changed files with 484 additions and 317 deletions
@@ -57,7 +57,11 @@ function makeHost(bodies: { root: (rp: (key: string, owner: object) => React.Rea
host,
current,
addSession: (id: string) => {
const cell: SessionCell = { sessionId: id, useSession: { hookTag: id } }
// Bare source per cell (identity-stable): the machinery binds useSession from it.
const cell: SessionCell = {
sessionId: id,
session: { getSnapshot: () => ({ sid: id }), subscribe: () => () => {} },
}
cells.set(id, cell)
return cell
},
@@ -121,15 +125,23 @@ describe('SessionProvider', () => {
const h = makeHost({
root: (renderSlot) => <SessionProvider>{() => renderSlot('k.session', {})}</SessionProvider>,
})
const s1 = h.addSession('s1')
const s2 = h.addSession('s2')
h.registerSession({ component: (props: object) => { seen.push(props as Record<string, unknown>); return null }, options: {} })
h.addSession('s1')
h.addSession('s2')
h.registerSession({
component: (props: { useSession?: <S>(sel: (s: { sid: string }) => S) => S; sessionId?: string }) => {
// The bound hook reads the cell's bare source — asserting through it
// proves the machinery wired THIS session's source, not another's.
seen.push({ sessionId: props.sessionId, read: props.useSession!((s) => s.sid) })
return null
},
options: {},
})
render(<>{createSlotRenderer().renderRoot(h.host, {})}</>)
act(() => { h.current.set('s1') })
expect(seen.at(-1)!['useSession']).toBe(s1.useSession)
expect(seen.at(-1)!['read']).toBe('s1')
expect(seen.at(-1)!['sessionId']).toBe('s1')
act(() => { h.current.set('s2') })
expect(seen.at(-1)!['useSession']).toBe(s2.useSession)
expect(seen.at(-1)!['read']).toBe('s2')
expect(seen.at(-1)!['sessionId']).toBe('s2')
})