/packages/zudo-doc/CLAUDE.md
CLAUDE.md at /packages/zudo-doc/CLAUDE.md
Path: packages/
@takazudo/zudo-doc
Shared layout + content-rendering package consumed by both this repo's showcase
(workspace:*) and every project scaffolded by create-zudo-doc (published npm).
Components are Preact .tsx compiled by tsup (bundle:false, 1:1 source→dist/
so "use client" directives survive — see tsup.config.ts). The exports map
in package.json is the API surface; consumers import from dist/.
Shared-surface (exports / tsup) append convention — package-first migration
The package.json#exports map and tsup.config.ts are a shared surface that
the package-first migration (epic #2321) touches from several parallel tasks
(S3/S4/S7/S8/S9). To keep those edits conflict-free, the convention established
by S2 (#2325) is:
New
.ts/.tsxsource undersrc/**(e.g.src/) is compiled automatically by the tsuppreset. ts entryglobs — notsup.config.tsedit needed. Just append oneexportsentry (a{ "types", "default" }pair pointing at the matchingdist/*path). Append it to the JS subpath group, right before the.cssstatic-asset entries at the bottom of the map (the.entry is the current tail of that group — append after it). Order within the group is cosmetic; keep one entry per line so parallel diffs touch disjoint lines./ preset exportscannot carry inline comments — Node (and esbuild) reject a"//"key sitting alongside.-prefixed subpath keys. So the append point is documented here and intsup.config.ts, not as a JSON comment.Source files the tsup globs do NOT match (e.g. S3's relocated
.mjsplugin wrappers) append at the markedENTRY APPEND POINTintsup.config.ts#entry, or copy via theonSuccesschain.
. / preset — zudoDocPreset()
src/ (exported as @takazudo/) returns the zfb config
fragment every project used to hand-write in zfb.config.ts — collections loop,
markdown.features, dual-theme codeHighlight, resolveMarkdownLinks,
stripMdExt, trailingSlash, and the integration plugins array. The host
spreads it into defineConfig and keeps only the shell fields it still owns
(framework, port, tailwind, bundle, base, adapter).
Signature:
zudoDocPreset({ settings, buildDocsSchema, directiveVocabulary }).buildDocsSchemaanddirectiveVocabularyare passed in, not imported, so the preset never re-imports the project'ssettings/tag-vocabulary/docs-schemasingletons (already in the config eval) and its own import graph stays node-builtin-free.Plugins are bare-specifier descriptors (
{ name:), never imported plugin functions — importing the plugin modules would drag their"@takazudo/ zudo- doc/ plugins/ <x>", options } node:fs/node:pathgraph into the config eval.copy-publicstays a project-relative.descriptor (not relocated)./ plugins/ copy- public- plugin. mjs Node-free eval-graph guard (
src/): esbuild-bundles_ _ tests_ _ / preset. test. ts src/withpreset. ts --platform=neutral(mirrors zfb'sloader.rs:277), noexternal, and FAILS on any reachablenode:*builtin. Underplatform: neutralesbuild does NOT shim builtins — an unresolvablenode:*makesbuild()reject with aCould not resolve "node:…"diagnostic, so the guard scans BOTH the rejection's.errorsAND (defensively) the emitted bundle for a literal passthrough. A companion self-test bundles anode:fsprobe to prove the detector stays live (not dead code). Non-negotiable — keep it green when adding imports to the preset.zodis a required peerDependency.preset.tsimportszodforz.toJSONSchema; withbundle:falsethat bare import ships verbatim indist/and resolves against the consumer'spreset. js node_modules. The host already supplies zod (it ownsbuildDocsSchema), so a required peer shares that single instance — avoiding a dual-zod hazard fortoJSONSchemaand aCannot find package 'zod'at config-eval time in generated projects.
Shipped CSS artifacts (four)
tsup only compiles .ts/.tsx. CSS is produced by the tsup onSuccess hook
(runs after every build/--watch, because clean:true wipes dist/ first):
onSuccess: "node scripts/ copy- content- css. mjs & & node scripts/ copy- page- loading- css. mjs & & node scripts/ copy- features- css. mjs & & node scripts/ gen- safelist. mjs"dist/← copied verbatim fromcontent. css src/bycontent. css scripts/. Exported ascopy- content- css. mjs @takazudo/. This is the single source of truth forzudo- doc/ content. css .zd-contentcontent typography (flow-space rhythm, headings'--flow-space, minor elements, admonitions, mermaid layout). Both the showcasesrc/and thestyles/ global. css create-zudo-doctemplate@importit instead of inlining the rules — this is what killed the old showcase↔template copy-drift (zudolab/zudo-doc#2188).Consumer contract (documented in full at the top of
src/): the consumer must declarecontent. css @layer zd-preflight, zd-flow;, define the@themedesign tokens the rules consume (--color-*,--spacing-*,--text-*,--font-*,--leading-*,--radius-DEFAULT), and also importsafelist.cssso the component-emitted utility classes are generated.Major-element visuals (h2–h4, p, a, strong, blockquote, ul, ol, table) do NOT live here — they are emitted by the
htmlOverridescomponents insrc/content/(Tailwind classes + inline styles).content.cssowns only what those components don't emit.Editing: change
src/, then rebuild the package socontent. css dist/updates.content. css tsup --watchdoes NOT re-copy on a bare.csschange (it only watches.ts/.tsx), so re-runpnpm buildafter editing the stylesheet.
dist/← generated bysafelist. css scripts/, which scans the compiledgen- safelist. mjs dist/**/*.jsfor Tailwind class candidates and emits a single@source inline(...). Exported as@takazudo/. Consumers import it so the utilities the components emit (which the consumer's own Tailwind scanner can't see insidezudo- doc/ safelist. css node_modules) are generated.dist/← copied verbatim frompage- loading. css src/bypage- loading. css scripts/. Exported ascopy- page- loading- css. mjs @takazudo/. Provides the full visual contract for the page-loading overlay, spinner, and pending-navigation link indicator. Consumerszudo- doc/ page- loading. css @importit alongside the<PageLoadingOverlay>component rather than inlining these rules per-project.Consumer contract: the stylesheet consumes host tokens
--color-page-loading-overlay(falling back tocolor-mix(in oklch, var(--color-overlay, #000) 60%, transparent)),--color-fg(spinner border; falls back to#fff),--color-accent(pending-nav link colour), and--z-index-modal(overlay stack level; falls back to100). All tokens are optional — bare consumers get sensible defaults.Editing: change
src/, then rebuild the package sopage- loading. css dist/updates.page- loading. css tsup --watchdoes NOT re-copy on a bare.csschange (it only watches.ts/.tsx), so re-runpnpm buildafter editing the stylesheet.
dist/← copied verbatim fromfeatures. css src/byfeatures. css scripts/. Exported ascopy- features- css. mjs @takazudo/. Contains all non-island feature CSS that every project using the package needs: code block buttons, syntect/shiki dual-theme token color rule,zudo- doc/ features. css .zd-html-preview-code, KaTeX, desktop sidebar toggle geometry, and view-transition chrome. Island-coupled CSS (enlarge / ai-chat / diff) stays in the project'sglobal.cssuntil a later epic moves it. See zudolab/zudo-doc#2331.Consumer contract: must @import AFTER
@takazudo/andzudo- doc/ content. css @takazudo/(thezudo- doc/ page- loading. css @importorder inglobal.cssis:@takazudo/,zdtp/ styles. css content.css,page-loading.css,features.css). Cascade order matters: features.css rules are unlayered and rely on the token definitions from@themewhich must precede this file in the compiled output.Editing: change
src/, then rebuild the package.features. css tsup --watchdoes NOT re-copy on a bare.csschange — re-runpnpm build.
prepack guards all four (check-safelist.mjs && check-content-css.mjs && check-page-loading-css.mjs && check-features-css.mjs)
so a build that skipped the onSuccess step fails loudly instead of publishing a package
whose . / . / . / . export 404s for consumers.