app-shell.ts 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. /**
  2. * App-shell assembly plugin. Its pseudo package id exists only in the host
  3. * graph and shell registry; there is no npm package behind it.
  4. */
  5. import type { ReactNode } from 'react'
  6. import type { Context } from 'cordis'
  7. import { createSlotRenderer } from '@deepseek-ai/dsh-client-web-react'
  8. import { buildRenderApp } from './app.tsx'
  9. /** Shell-owned pseudo entry id under which the host graph mounts this plugin. */
  10. export const APP_SHELL_ID = '@deepseek-ai/dsh-client-app-shell'
  11. /** The assembled-UI face AppRoot renders once the boot settles. */
  12. export interface AppShellService {
  13. /** Build (once) and render the real UI tree. */
  14. renderApp: () => ReactNode
  15. }
  16. declare module 'cordis' {
  17. interface Context {
  18. /** The shell assembly face, provided by the app-shell entry once its inject set is active. */
  19. appShell: AppShellService
  20. }
  21. }
  22. /** Cordis plugin name. */
  23. export const name = 'app-shell'
  24. /** Services required before shell assembly. */
  25. export const inject = ['slots', 'sessions', 'layout']
  26. /** Installs the React renderer and exposes the assembled application.
  27. * @param ctx - Plugin context.
  28. */
  29. export function apply(ctx: Context): void {
  30. // The renderer install is shell territory (web-react is shell-bundled),
  31. // but ctx.slots exists only once the runtime entry is active — so it lands
  32. // here, on the entry whose inject set guarantees that ordering.
  33. ctx.slots.install(createSlotRenderer())
  34. // Assemble once on first render: the closure must be identity-stable
  35. // across AppRoot re-renders.
  36. let renderApp: (() => ReactNode) | undefined
  37. ctx.reflect.provide('appShell', {
  38. renderApp: (): ReactNode => {
  39. renderApp ??= buildRenderApp({ ctx })
  40. return renderApp()
  41. },
  42. })
  43. }