docs(client): state where a browser-only dependency is declared
Browser artifacts resolve nothing on a user's machine: tsdown inlines every non-platform specifier, the shell dist answers the rest from its frozen module table, and Vite inlines the shell's own imports into the published dist. Record the resulting declaration rule, and the Agent Note behind it.
This commit is contained in:
@@ -35,6 +35,15 @@ The `/client` entrypoint of a UI plugin package is its public browser API, not a
|
||||
2. **Same-package tests import internals directly** — relative `../src/client/xxx.ts` from package tests, or the `./src/*` subpath where a spec lives outside the package. Never widen the public API to make a test compile.
|
||||
3. **Cross-package imports of another plugin's symbols are in principle forbidden.** The sanctioned routes are the slot system (register/renderSlot) and ctx services. If neither fits, stop and escalate — do not add an export to unblock yourself.
|
||||
|
||||
## Dependency declaration
|
||||
|
||||
A browser artifact resolves nothing on the user's machine: tsdown inlines every non-platform specifier into `lib/client.js`, the shell `dist` answers `PLATFORM_MODULES` from its frozen module table, and Vite inlines the shell's own imports into the published `dist`.
|
||||
|
||||
- **An external package only browser code reaches belongs in `devDependencies`.** npm installs `dependencies` and non-optional `peerDependencies` for every consumer, so react, shiki, katex, or clsx declared there ships to users who never load it.
|
||||
- **A workspace name stays where it is.** Such a declaration also states which package supplies an injected service or a mounted Remote contribution — [verify-runtime-closure](../../scripts/verify-runtime-closure.ts) and the Loader read it, and the app installs the package regardless.
|
||||
- **The node half decides.** Anything its published entries reach — an erased type import, a `require.resolve`, and a Loader row in the package's own `cordis*.yml` included — stays declared as it is.
|
||||
- `pnpm run verify-client-runtime-deps` (inside `hygiene`) names each offending entry; knip owns whether the surviving declaration is used at all.
|
||||
|
||||
## ctx discipline (components never see ctx)
|
||||
|
||||
`ctx` belongs to the apply world only: the plugin body and the inject factories closed over it. Components — every `.tsx` under a feature domain — receive all data and callbacks **through the four props shares**; they never call a hook that reaches ctx, never import a service class to poke it, never read a React context (business components see zero contexts — `BindingContext` and its kin are renderer-internal). If a component needs something new, the answer is a prop threaded from its share's source (owner site, store declaration, or inject face), not a hook.
|
||||
@@ -91,7 +100,7 @@ If `test:gui` is red on code you did not touch, neither silently fix nor ignore
|
||||
|
||||
Bringing up a new `packages/client/<name>` plugin package (ui-workspace is a complete example; ui-sidebar/ui-user-questions are minimal skeletons):
|
||||
|
||||
1. **Package skeleton**: `package.json` (`@deepseek-ai/dsh-client-<name>`, exports `.`/`./invariant`/`./client`/`./src/*`/`./package.json`, `dsh.client` manifest, `files` list), `tsconfig.json` (extends `tsconfig.base.client.json`, one `references` entry per workspace dependency plus `runtime-diagnostics/invariants`), `tsdown.config.ts` (`clientBundle(id, ['lib/types/index.js', 'lib/types/invariant.js'])`), `src/index.ts` (empty node-half apply), `src/invariant.ts` (companion with a real reason), `src/css-modules.d.ts` when using CSS Modules, `README.md` with the Model Experience section.
|
||||
1. **Package skeleton**: `package.json` (`@deepseek-ai/dsh-client-<name>`, exports `.`/`./invariant`/`./client`/`./src/*`/`./package.json`, `dsh.client` manifest, `files` list, and every browser-side external package under `devDependencies` per the [declaration rules](#dependency-declaration)), `tsconfig.json` (extends `tsconfig.base.client.json`, one `references` entry per workspace dependency plus `runtime-diagnostics/invariants`), `tsdown.config.ts` (`clientBundle(id, ['lib/types/index.js', 'lib/types/invariant.js'])`), `src/index.ts` (empty node-half apply), `src/invariant.ts` (companion with a real reason), `src/css-modules.d.ts` when using CSS Modules, `README.md` with the Model Experience section.
|
||||
2. **Three registration surfaces, all required** (missing any one fails at a different, later point): the `tsconfig.client.json` aggregate `references` entry; a `dsh.client` row in `packages/bundle/web-app/cordis.patch.yml`; a `packages/bundle/web-app/package.json` dependency (profile boots resolve bare row names through the healed `$DSH_HOME/profiles/node_modules` fallback, which mirrors the app's and each bundle's declared dependencies — a row whose package no manifest declares fails to import). `pnpm-workspace.yaml` already globs `packages/*/*`.
|
||||
3. **dsh.client manifest semantics**: `platform: 'web'` always; `immediately: true` only for stage-one-prefetch infrastructure rows. `inject` lists package-name dependency edges — they are **informational only** (preflight display, HMR diffing); they do not sequence entry activation or apply order. Activation order is cordis fiber inject waiting on *services*, nothing else.
|
||||
4. **Registering into another package's slot**: apply order is unconstrained, and a business service is not a declaration barrier. Use `ctx.slots.inject(name, () => ctx.slots.register(...))`; it waits on the actual declaration, removes the contribution when that declaration collapses, reruns after redeclaration, and leaves with the caller's plugin fiber. Return a generator yielding each registration when several contributions must install and roll back atomically. A bare `slots.register` into an undeclared slot remains an error; keep service edges only for services the contribution actually reads.
|
||||
|
||||
Reference in New Issue
Block a user