Files
deepseek-harness/packages/client/web/src/app-shell.ts
imccyu b58f0989f9 refactor(gui): rebuild the client loading kernel as dsh-client-modules with a two-phase boot
The module system moves out of dsh-client-runtime (./loader retired) into
its own package: a lazy CJS table where executing a bundle only registers
its factory and materialization happens at first require, memoized, with
recursive requires self-ordering. ClientModuleSystem is a class; index.ts
keeps the types and a thin factory. Boot is two-phase: phase one prefetches
the immediately tier in parallel (registration only, failures deferred to
phase two's loud import); phase two mounts the vendored Loader with the
module system as internal, creates one entry per graph row plus the
app-shell pseudo-row the kernel appends itself, and settles on an
all-ACTIVE sweep. The shell kernel is self-sufficient: hand-rolled
loader-status stores, no plugin value imports, platform seed list single-
sourced in platform.ts.
2026-07-24 02:09:42 +08:00

60 lines
2.3 KiB
TypeScript

/**
* App-shell assembly plugin (design §3.4): the shell's ONLY composition
* responsibility, packaged as a normal static-arrival entry so the host graph
* stays the single composition authority. It rides the same entry lifecycle
* as every other plugin — the fiber waits on slots/sessions/layout, so by the
* time apply runs the layout entry is mounted and its export surface is
* readable from the governance side (module loadCache, design §2.6).
*
* The pseudo package id exists only in the host graph and the shell's static
* registry; there is no npm package behind it.
*/
import type { ReactNode } from 'react'
import type { Context } from 'cordis'
import { createSlotRenderer } from '@deepseek-ai/dsh-client-web-react'
import { buildRenderApp } from './app.tsx'
/** Shell-owned pseudo entry id under which the host graph mounts this plugin. */
export const APP_SHELL_ID = '@deepseek-ai/dsh-client-app-shell'
/** The assembled-UI face AppRoot renders once the boot settles. */
export interface AppShellService {
/** Build (once) and render the real UI tree. */
renderApp: () => ReactNode
}
declare module 'cordis' {
interface Context {
/** The shell assembly face, provided by the app-shell entry once its inject set is active. */
appShell: AppShellService
}
}
/** Cordis plugin name. */
export const name = 'app-shell'
/** Required services: the product services the assembly closes over (layout registers the 'root' slot entry). */
export const inject = ['slots', 'sessions', 'layout']
/**
* Plugin body: install the React renderer into the slot system and provide
* the renderApp face (one ctx-level renderSlot('root') call).
* @param ctx - plugin context (inject set active).
*/
export function apply(ctx: Context): void {
// The renderer install is shell territory (web-react is shell-bundled),
// but ctx.slots exists only once the runtime entry is active — so it lands
// here, on the entry whose inject set guarantees that ordering.
ctx.slots.install(createSlotRenderer())
// Assemble once on first render: the closure must be identity-stable
// across AppRoot re-renders.
let renderApp: (() => ReactNode) | undefined
ctx.reflect.provide('appShell', {
renderApp: (): ReactNode => {
renderApp ??= buildRenderApp({ ctx })
return renderApp()
},
})
}