feat(genesis): add @paper/genesis docs design system#240
Merged
Conversation
Headless docs primitives extracted from apps/docs DocsExample, split into six composable components so consumers can use the whole bundle or any piece standalone: - GnDocsExample — orchestrator root - GnDocsExampleDescription — header strip with collapse toggle - GnDocsExamplePreview — splitter-resizable preview with drag indicator - GnDocsExampleCode — single-file code chrome (consumer supplies highlighter via slot) - GnDocsExampleTabs — multi-file tabs with overflow → dropdown and action emits - GnDocsExamplePeek — floating expand/collapse trigger for truncated mode Genesis ships zero highlighter code; the docs site already loads Shiki and pipes highlighted HTML in through the #code slot.
The only consumer in dev/ is the gitignored Playground.vue sandbox, so knip can't see the import and flags the dep as unused.
- Drop @vuetify/paper dependency - Add createGenesisPlugin, GenesisStyleSheetAdapter, theme.ts - Retool every component to reference --gn-* directly (no --v0-* refs, no per-component indirection layer) - Replace GnDocsIcon with slot-based icons + inline SVG defaults - Wire dev Playground to createGenesisPlugin + DevShikiBlock
SPEC.md captures the design contract for @paper/genesis (paper DS as category, --gn-* token namespace, headless icon/highlighter slots, non-goals, phase 2 roadmap). PLAN.md is the implementation plan that produced this PR.
v0's createThemePlugin uses createPluginContext('v0:theme', ...). The
install gate inside createPlugin is keyed by namespace and silently
no-ops on the second install for the same key, so calling
createThemePlugin a second time from createGenesisPlugin (after a
consumer has already installed v0's theme plugin) collided and Genesis's
adapter never ran — no stylesheet, no --gn-* tokens, components rendered
with no resolved colors.
Build Genesis's own theme trinity via createPluginContext('genesis:theme',
...) reusing createTheme as the factory. Genesis now installs alongside
v0's theme system without collision; both adapters write their own
stylesheets to adoptedStyleSheets, both cascades resolve on overlapping
subtrees. Default target moves from 'html' to 'body' so v0 (typically on
html or #app) and Genesis (on body) never fight for data-theme on the
same element.
Also exports useGenesisTheme() for consumers who want to drive Genesis
theme switching independently from v0's useTheme().
Phase 1 originally shipped only a light `genesis` theme. Adds
`genesisDarkColors` and registers both `genesis` (light) and
`genesis-dark` in the plugin defaults so consumers can pair Genesis
with either light or dark chrome without writing their own dark token
table from scratch.
Dev playground switches to `default: 'genesis-dark'` since v0's dev
theme is `dark` — Genesis components inside body now match the chrome.
Light remains the package default; consumers pass
`theme: { default: 'genesis-dark' }` to switch.
…s bleed When GnDocsExampleTabs is the last child of a rounded container (e.g. a hand-composed shell that pairs GnDocsExamplePreview + GnDocsExampleTabs), the container's nth-last-child border-radius applies to the tabs box, but the inner GnDocsExampleCode paints var(--gn-code-bg) with square corners and bleeds past the radius. Adding overflow: hidden to the tabs container makes tabs clip their own contents — works in any rounded parent without requiring the parent to have its own overflow:hidden (which would clip the splitter grip in a sibling preview). The smart GnDocsExample root continues to work via its existing __code wrapper; this just makes tabs safe in hand-composed layouts too.
b558a48 to
0e72247
Compare
|
commit: |
Phase 1 shipped Genesis as a "paper DS with own theme namespace" — its own createGenesisPlugin, createGenesisThemePlugin (createPluginContext on 'genesis:theme'), GenesisStyleSheetAdapter, and a --gn-* token map with genesis/genesis-dark themes. Integrating into apps/docs surfaced two problems: - A future GnDocsExample feature lets consumers render the preview in a specific theme (e.g., theme="corporateIndigo"). For the example UI inside the preview to actually re-theme, the override must affect --v0-* tokens — the theme system is v0's. A parallel --gn-* layer doesn't help. - Components and the example content rendered inside them must inherit the same theme cascade — otherwise chrome and inner example look like they're from different apps when the page theme changes. Genesis is now a thin component layer over v0 tokens. Every scoped <style> references var(--v0-*, fallback). When v0's theme plugin is installed, components inherit the active theme via the cascade; when Genesis is used in a v0-less app, fallbacks provide reasonable light defaults. No Genesis-specific plugin, adapter, theme, or stylesheet. Deletes packages/genesis/src/adapter.ts, plugin.ts, theme.ts, and the genesis-dark theme variant. Simplifies src/index.ts to re-export only components. Removes the createGenesisPlugin install from dev/main.ts. SPEC.md rewritten to reflect the v0-token-consuming architecture, with the Phase 1 → revised history captured at the bottom. PLAN.md (the abandoned implementation plan) removed.
Adds @paper/genesis as a workspace dependency of apps/docs. Adds the matching @paper/genesis and #genesis path aliases to apps/docs/vite.config.ts so source-mode resolution works in dev. Genesis components consume v0 tokens via the cascade, so no plugin install is needed — Genesis components placed inside the docs site simply inherit whatever theme v0 has applied (via the docs' existing createThemePlugin). Adds an exploratory /genesis-sandbox page that renders GnDocsExample with single-file, multi-file, and Shiki-highlighted variants. Uses the docs site's existing useHighlightCode composable via the code slot. Confirms Genesis components blend visually with the docs chrome (light or dark, theme-driven from v0).
Adds `theme?: string` prop to GnDocsExamplePreview and GnDocsExample. When set, applies `data-theme="<name>"` to the preview __panel div, scoping v0's theme cascade for the example content. The example UI inside (consuming --v0-* tokens via UnoCSS utility classes or scoped styles) renders in the named theme, independent of the page. Implementation: a single attribute binding on the panel div, no extra wrapper element, no JS theme switching. The theme name must match an entry registered with v0's createThemePlugin; if it doesn't, the cascade resolves to the parent's variables (no-op). Adds a per-example theme demo section to the apps/docs sandbox showing the same code rendering in light / dark / tailwind themes side by side.
Adds showPlayground and showBin props (default false) to GnDocsExampleTabs, with matching playground and bin emits that receive the current displayFiles. Inline-SVG defaults for playground-icon and bin-icon slots; consumers can override. Restored the actions slot for custom buttons inserted before the built-in ones. Forwards the same props/emits through GnDocsExample. These hooks let downstream docs sites wire their own playground/bin URL builders without modifying Genesis.
Adds DocsGenesisExample.vue — the apps/docs-side wrapper that pairs GnDocsExample with the docs site's features: - Auto-resolves example components + code via useExamples (filePath / filePaths props, same surface as the existing DocsExample.vue) - Wires playground/bin emits to usePlayground + getMultiFileBinUrl - Injects Shiki highlighting via a small DocsGenesisShikiBlock helper that uses the existing useHighlightCode composable - Mirrors DocsExample's public prop surface (file, filePath, filePaths, fileOrders, title, id, code, collapse, files, imports, peek, peekLines) so pages can opt in by changing only the import Updates the /genesis-sandbox page to demo the wrapper rendering the Snackbar basic example via filePath auto-resolution. Side-by-side migration: pages opt in by switching their import from DocsExample to DocsGenesisExample. The legacy DocsExample stays in place; nothing breaks during transition.
First real page migration. Replaces the ::: example markdown directives with explicit <DocsGenesisExample> Vue tags + script setup import. Markdown content inside the description slot (h3 heading, mermaid flowchart, paragraphs, table) renders correctly through unplugin-vue-markdown. Side-by-side migration validated: legacy ::: example directive still emits <DocsExample> for every other page; this page is the only opt-in so far.
Refactors apps/docs/build/markdown.ts to share the example-container logic between two parallel directives: - `::: example` → emits <DocsExample> (legacy, unchanged) - `::: gn-example` → emits <DocsGenesisExample> (new opt-in) The container factory `registerExampleContainer(name, tag)` carries the emit tag through `env._exampleTag`. Pages opt into Genesis by swapping the directive name; no script-setup imports, no inline Vue tags. Reverts aspect-ratio.md to directive form, now using ::: gn-example. Browser smoke-tested: 2 examples rendering, description markdown (h3 + mermaid + table) inside the slot, preview content resolving via useExamples.
…Example a real wrapper
GnDocsExample now exposes two scoped slots that consumers can use to
inject highlighting or custom code rendering:
- code (single-file mode) — { code, fileName, language }; forwarded to
the inner GnDocsExampleCode's default slot when set.
- panel (multi-file mode) — { file }; forwarded to GnDocsExampleTabs's
default slot when set. Consumer renders their own per-tab content.
Both fall through to GnDocsExampleCode's <pre> default when unset.
DocsGenesisExample is now a thin wrapper that delegates the entire
structure to GnDocsExample. It plugs DocsGenesisShikiBlock into both
slots, wires playground/bin emits to the docs site's usePlayground +
getMultiFileBinUrl, and auto-resolves filePath/filePaths through
useExamples — the docs-site features stack on top of Genesis's
structural primitives.
This way the directive (::: gn-example) and the wrapper share one
visual rendering — driven by GnDocsExample's own template, classes,
and CSS.
Side-by-side comparison with legacy DocsExample on the aspect-ratio page revealed a 56px height discrepancy: Genesis was rendering the filename + reset-button toolbar even in peek mode, while the legacy hides it (toolbar only appears once Show Code expands the source). Adds v-if="!peek" on .genesis-docs-example__code-bar so peek mode shows only preview + code peek + expand pill — matching the legacy visual. Post-fix delta vs legacy is 1px (border rounding).
The docs site has a global .shiki rule (App.vue) that paints a divider-colored border and 8px border-radius on every Shiki block. Inside the example container that creates a visible inner border and double-rounded corners that compete with the example's own outline. Mirrors the legacy .docs-example-code .shiki override: zero border, zero radius. The parent .genesis-docs-example__code wrapper already has overflow:hidden + a bottom border-radius via nth-last-child, so the shiki block's square corners get visually clipped to the example container's rounded outline without needing its own radius.
Swap border-top to border-bottom on the tabs __bar so the toolbar carries the divider between itself and the code panels, mirroring the single-file __code-bar pattern.
- Override .docs-genesis-shiki-block :deep(pre code) padding to 0 so the wrapping <pre> owns the padding (was double-padded by the global .markdown-body .shiki code rule in App.vue). - Wrap shiki blocks with an absolute-positioned actions overlay (Copy, Wrap, Bin, plus Playground for single-file) that fades in on hover/focus and is forced visible on mobile. - Wire line-wrap toggle through useSettings().lineWrap via useSyncedRef and apply pre-wrap when the --wrap modifier is set.
Pass reset-icon, playground-icon, bin-icon, combine-icon, and split-icon slots through to GnDocsExampleTabs so consumers can override the default inline SVGs once at the GnDocsExample level. The single-file reset-icon slot already existed; sharing the slot name means consumers only declare each icon once and it applies in both single-file and multi-file modes.
Override reset-icon, playground-icon, bin-icon, combine-icon, and split-icon with the docs-app AppIcon (restart, vuetify-play, vuetify-bin, combine, split) to match the legacy DocsExample toolbar style.
Keep horizontal/top padding at 0 so the wrapping <pre> owns the inset, but add 0.5rem bottom padding back to <code> for breathing room below the last line (and clearance above the horizontal scrollbar when one appears).
Render a named decoration slot at the preview container level (before the splitter/panel content), so consumers can layer absolute-positioned background elements like dot grids or mesh gradients behind the example preview. Forward the slot through GnDocsExample so it can be supplied from the top level.
Restore the dot-grid background that the legacy DocsExample renders behind each example preview, via the new GnDocsExamplePreview decoration slot.
Pass `combined` alongside `file` to the default slot so consumers can branch their per-file rendering on the current tabs/stacked mode (e.g. hide a filename badge in tabs view where the tab itself already labels the file). Forward the prop through GnDocsExample's panel slot.
When multi-file examples render as tabs, the tab itself shows the filename — the per-block badge is redundant. Pass `hide-filename` to GnDocsExampleCode whenever `combined` is false, restoring the badge when files are stacked.
BREAKING CHANGE: GnDocsExample no longer exposes a #panel named slot.
The #code slot now handles both single- and multi-file render with a
unified scope: { code, file, combined, language }. In single-file mode
file is undefined and combined is false; in multi-file mode file is the
GnDocsExampleFile and combined reflects the tabs/stacked state.
GnDocsExampleTabs renders GnDocsExamplePanel + GnDocsExampleCode itself
and forwards GnDocsExampleCode's default slot, attaching :file-name to
the inner code only when combined (the tab labels handle the filename
in non-combined mode). This eliminates the redundant per-file badge in
the tabs view without consumer wiring.
Drop the #panel template now that #code covers both single- and multi-file rendering. The single #code template branches on file presence: playground button visible when file is undefined (single-file), title falls back to fileName from props.
Add position: sticky to the toggle bar, single-file code bar, and multi-file tabs bar so they stay visible while scrolling long examples. Offset is driven by two CSS variables consumers set on the root: - --gn-docs-example-sticky-top: viewport offset of the outer header (default 0) - --gn-docs-example-toggle-h: height of the toggle bar (default 45px), used to stack the inner toolbar directly under it Switch the __code wrapper and tabs root from overflow: hidden to overflow: clip so they keep clipping rounded-corner overflow without establishing a non-scrolling container that traps sticky descendants.
Pass --gn-docs-example-sticky-top through inline style as calc(48px + var(--app-banner-h, 0px)) so the genesis example toolbars stick below the docs-site header and (when shown) announcement banner, matching the legacy DocsExample behavior.
Apply var(--v0-glass-surface) + backdrop-filter blur(12px) to the sticky toggle, code, and tabs toolbars via :deep() overrides so they read as a continuous surface with the AppBar while scrolling. Scoped to the docs site because --v0-glass-surface is a docs-theme token, not a v0-core token, and other genesis consumers may not define it.
When both sticky toolbars stack at the viewport top, the divider that normally lives on .genesis-docs-example__code (border-top) is scrolled out of view, leaving the toggle bar and the inner code/tabs toolbar visually fused. Add border-bottom on the toggle bar whenever data-expanded is set so the divider follows the toolbars into the sticky position.
Always render the bottom border on the toggle bar and drop the border-top on .__code so the divider has a single source of truth. The toggle bar's border now follows it into the sticky position without the previous data-expanded gate.
The toggle bar's bottom border only makes sense when there's an inner toolbar or code area below it to separate from. Restore the [data-expanded] selector so a collapsed example doesn't render a dangling bottom border above its rounded corner.
Drop the bar's vertical padding from 0.75rem to 0.5rem so the toolbar sits closer to the toggle bar and the code area below. Horizontal padding stays at 0.75rem.
Increase min-width and height from 30px to 32px so the toolbar action buttons render as a square hit target instead of 32x30.
…icons DocsCodeActions defaults to AppIconButton at 26x26 with 18px icons. Override locally inside the hover-actions overlay so the per-pane buttons match the GnDocsExampleActions toolbar (32x32 hit target, 16x16 icons) for a consistent visual rhythm.
AppIconButton is inline-flex but doesn't set align/justify, so the smaller icon (after the size override) was anchored to a corner. Add align-items: center + justify-content: center to the override and size the inner <i> wrapper to 16x16 so the SVG sits dead-center in the 32x32 hit target.
Drop the /playground redirect (to v0play.vuetifyjs.com) and the /genesis-sandbox demo page from the v0 docs site. Genesis will document itself in its own site, and the v0 docs no longer need to host either route. Also strip the inbound /playground links from the homepage cards (HomeDx, HomeFoundation) and from why-vuetify0.md + getting-started.md so navigation doesn't 404. The per-example "Open in Playground" buttons in code panes stay — they still link consumers out to v0play on demand.
7764503 to
0c95564
Compare
This reverts commit 0c95564.
DevShikiBlock was a local Shiki integration used only by the gitignored dev/src/Playground.vue scratchpad. With the playground reset to its template, the block has no consumer and shiki is no longer needed in dev. Resolves the long-standing knip warnings (unused file + unused dep) flagged by repo-integrity CI.
Genesis will document itself in its own site; the v0 docs no longer need to host the sandbox route. The /playground redirect stays since the homepage and intro pages still link to it.
…folder
Move all eight GnDocsExample* .vue files from individual sibling
folders into a single packages/genesis/src/components/GnDocsExample/
directory. The eight subfolder index.ts files collapse into one
index.ts that re-exports the whole suite.
BREAKING CHANGE: deep imports from the old subfolder paths
(`@paper/genesis/src/components/GnDocsExampleActions` etc.) no longer
resolve. Consumers of the package barrel (`import { X } from
'@paper/genesis'`) are unaffected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
@paper/genesis— a focused docs-primitives library that ships the first wave of components a documentation site needs. Genesis is a paper design system in the categorical sense: built on@vuetify/v0, self-contained, with no@vuetify/paperruntime dependency. If patterns prove shared with Emerald or future paper DSs, they can graduate into@vuetify/paperlater — until then, Paper's runtime stays optional and the category is the only contract.What's in the box
GnDocsExamplesuite — 8 compound components (GnDocsExampleroot +Description,Preview,Code,Tabs,Panel,Peek,Actions)var(--v0-*)(surface, surface-tint, on-surface, divider, primary, etc.). The earlier--gn-*indirection layer was dropped ind5a79aeeso the design system has one fewer hop to debug and stays aligned with v0's theme switching for freeGnDocsExampleCode; icons are consumer-injectable via 5 named slots (reset-icon,playground-icon,bin-icon,combine-icon,split-icon) forwarded fromGnDocsExampledown intoGnDocsExampleTabs, each with inline SVG defaults using MDI pathsGnDocsExamplePreviewexposes adecorationslot (forwarded throughGnDocsExample) so consumers can layer absolute-positioned background elements (dot grids, mesh gradients) behind the preview contentGnDocsIconcomponent — replaced by inline-SVG defaults so consumers aren't forced to adopt a bundled icon helperDocs-app integration
apps/docs/src/components/docs/DocsGenesisExample.vuewires the genesis suite to the docs site:apps/docs/src/examples/**viauseExamplesAppIcon(restart, vuetify-play, vuetify-bin, combine, split) for all five icon slotsAppDotGridin the decoration slot:focus-withinkeyboard reach and mobile-pinned visibilityuseSettings().lineWrapviauseSyncedRef, matching the legacyDocsExampleCodePanepatternDesign doc
packages/genesis/SPEC.md— long-lived design reference (identity, component contracts, non-goals, Phase 2 roadmap)Test plan
pnpm --filter @paper/genesis buildproducesdist/index.mjs+dist/style.csspnpm typecheckclean across@vuetify/v0,@vuetify/paper,@paper/genesispnpm test:run— 5845 tests pass (18 skipped)pnpm lint:fixcleanpnpm repo:check— currently flagsdev/src/DevShikiBlock.vueunused +shikiunused dep indev/package.json; clean these up before mergeGnDocsIconreferences inpackages/genesis/src/masterapps/docsaspect-ratio page (single-file + multi-file examples, hover actions, decoration grid, AppIcon swap)Phase 2 roadmap
Next features to pull out of the v0 docs, in order:
GnDocsCallout(TIP / WARNING / ERROR / INFO admonitions)GnDocsCodeGroup(tabbed code blocks)GnDocsKbd,GnDocsBadge,GnDocsCard(atomic primitives)GnDocsApi(API reference tables with prop / event / slot sections + hover popovers)Parked questions
What is Paper, formally? Concept vs. foundation — parked as "category for now." See SPEC.md "Deferred / parked questions" — revisit once Emerald and Genesis have shipped real users and we can see which patterns naturally converge.