handoff-app 2.0.0-1 → 2.0.0-10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +7 -0
- package/CLAUDE.md +7 -0
- package/README.md +732 -304
- package/UPGRADE.md +218 -0
- package/config/config.template.js +1 -4
- package/config/config.template.ts +1 -2
- package/config/docs/foundations/colors/guidelines.md +9 -0
- package/config/docs/foundations/effects/guidelines.md +9 -0
- package/config/docs/foundations/effects/resources.md +9 -0
- package/config/docs/foundations/grid.md +10 -0
- package/config/docs/foundations/icons/guidelines.md +9 -0
- package/config/docs/foundations/images/guidelines.md +9 -0
- package/config/docs/foundations/images/resources.md +9 -0
- package/config/docs/foundations/images.md +10 -0
- package/config/docs/foundations/logo/resources.md +9 -0
- package/config/docs/foundations/typography/guidelines.md +9 -0
- package/config/docs/foundations/typography/resources.md +9 -0
- package/config/docs/foundations.md +5 -3
- package/dist/ai/connections.d.ts +64 -0
- package/dist/ai/connections.js +136 -0
- package/dist/ai/define.d.ts +10 -0
- package/dist/ai/define.js +13 -0
- package/dist/ai/types.d.ts +27 -0
- package/dist/ai/types.js +10 -0
- package/dist/app-builder/build.d.ts +12 -0
- package/dist/app-builder/build.js +227 -69
- package/dist/app-builder/client-config.js +16 -12
- package/dist/app-builder/config-diff/strategies/pattern.js +3 -3
- package/dist/app-builder/nav-shell.js +8 -15
- package/dist/app-builder/paths.js +7 -9
- package/dist/app-builder/static-export.js +6 -6
- package/dist/app-builder/vercel-output.js +6 -6
- package/dist/app-builder/watchers/component.js +5 -5
- package/dist/app-builder/watchers/utils.d.ts +2 -3
- package/dist/app-builder/watchers.d.ts +8 -15
- package/dist/app-builder/watchers.js +70 -61
- package/dist/artifacts/url.d.ts +7 -7
- package/dist/artifacts/url.js +7 -7
- package/dist/cache/build-cache.js +23 -23
- package/dist/catalog/define.d.ts +6 -0
- package/dist/catalog/define.js +72 -0
- package/dist/catalog/implementation.d.ts +27 -0
- package/dist/catalog/implementation.js +133 -0
- package/dist/catalog/index.d.ts +6 -0
- package/dist/catalog/index.js +22 -0
- package/dist/catalog/normalize.d.ts +16 -0
- package/dist/catalog/normalize.js +118 -0
- package/dist/catalog/previews.d.ts +18 -0
- package/dist/catalog/previews.js +107 -0
- package/dist/catalog/renderers.d.ts +43 -0
- package/dist/catalog/renderers.js +62 -0
- package/dist/catalog/types.d.ts +71 -0
- package/dist/cli/auth/browser.d.ts +2 -0
- package/dist/cli/auth/browser.js +33 -0
- package/dist/cli/auth/device.d.ts +16 -0
- package/dist/cli/auth/device.js +158 -0
- package/dist/cli/auth/store.d.ts +38 -0
- package/dist/cli/auth/store.js +199 -0
- package/dist/cli/create.js +48 -3
- package/dist/cli/make.d.ts +0 -5
- package/dist/cli/make.js +24 -69
- package/dist/cli/scaffold.js +39 -181
- package/dist/cli.js +39 -7
- package/dist/commands/build/app.js +1 -5
- package/dist/commands/build/components.js +1 -5
- package/dist/commands/build/index.js +1 -5
- package/dist/commands/checkout/index.d.ts +18 -12
- package/dist/commands/checkout/index.js +19 -59
- package/dist/commands/compat/deprecations.d.ts +46 -0
- package/dist/commands/compat/deprecations.js +62 -0
- package/dist/commands/compat/push-args.d.ts +48 -0
- package/dist/commands/compat/push-args.js +73 -0
- package/dist/commands/db/migrate.js +1 -5
- package/dist/commands/dev/index.js +1 -5
- package/dist/commands/eject/config.js +1 -5
- package/dist/commands/eject/pages.js +1 -5
- package/dist/commands/eject/theme.js +1 -5
- package/dist/commands/fetch/index.js +1 -5
- package/dist/commands/index.js +8 -2
- package/dist/commands/login/index.d.ts +13 -0
- package/dist/commands/login/index.js +61 -0
- package/dist/commands/logout/index.d.ts +12 -0
- package/dist/commands/logout/index.js +89 -0
- package/dist/commands/make/component.js +1 -5
- package/dist/commands/make/page.js +3 -7
- package/dist/commands/publish/index.d.ts +21 -11
- package/dist/commands/publish/index.js +22 -58
- package/dist/commands/pull/index.d.ts +13 -0
- package/dist/commands/pull/index.js +20 -0
- package/dist/commands/push/index.d.ts +23 -0
- package/dist/commands/push/index.js +58 -0
- package/dist/commands/scaffold/index.js +1 -5
- package/dist/commands/start/index.js +1 -5
- package/dist/commands/types.d.ts +12 -0
- package/dist/commands/utils.d.ts +100 -1
- package/dist/commands/utils.js +103 -2
- package/dist/commands/validate/components.js +1 -5
- package/dist/config/catalog-include.d.ts +43 -0
- package/dist/config/catalog-include.js +203 -0
- package/dist/config/defaults.d.ts +4 -2
- package/dist/config/defaults.js +58 -49
- package/dist/config/env.d.ts +9 -0
- package/dist/config/env.js +43 -0
- package/dist/config/errors.d.ts +4 -0
- package/dist/config/errors.js +11 -0
- package/dist/config/from-env.d.ts +24 -0
- package/dist/config/from-env.js +7 -0
- package/dist/config/helpers.d.ts +2 -0
- package/dist/config/helpers.js +3 -1
- package/dist/config/index.d.ts +6 -2
- package/dist/config/index.js +12 -1
- package/dist/config/loader.d.ts +40 -9
- package/dist/config/loader.js +148 -35
- package/dist/config/normalizers/page.d.ts +4 -4
- package/dist/config/normalizers/page.js +2 -2
- package/dist/config/resolve-env.d.ts +6 -0
- package/dist/config/resolve-env.js +115 -0
- package/dist/config/runtime.d.ts +15 -16
- package/dist/config/runtime.js +178 -196
- package/dist/config/validator.d.ts +2 -2
- package/dist/config/validator.js +2 -4
- package/dist/handlebars.d.ts +17 -0
- package/dist/handlebars.js +16 -0
- package/dist/index.d.ts +120 -27
- package/dist/index.js +242 -53
- package/dist/nav/resolver.js +3 -1
- package/dist/nav/workspace.js +2 -2
- package/dist/pattern.d.ts +7 -0
- package/dist/pattern.js +13 -0
- package/dist/pipeline/index.d.ts +1 -1
- package/dist/pipeline/index.js +1 -1
- package/dist/react.d.ts +43 -0
- package/dist/react.js +11 -0
- package/dist/registry/asset-storage/adapters/vercel-blob.js +1 -1
- package/dist/registry/asset-storage/define.d.ts +1 -1
- package/dist/registry/asset-storage/define.js +1 -1
- package/dist/registry/asset-storage/resolve.d.ts +3 -3
- package/dist/registry/asset-storage/resolve.js +5 -2
- package/dist/registry/auth/access-tokens.d.ts +54 -0
- package/dist/registry/auth/access-tokens.js +191 -0
- package/dist/registry/auth/action-tokens.d.ts +66 -0
- package/dist/registry/auth/action-tokens.js +178 -0
- package/dist/registry/auth/ai-keys.d.ts +32 -0
- package/dist/registry/auth/ai-keys.js +69 -0
- package/dist/registry/auth/crypto.d.ts +21 -0
- package/dist/registry/auth/crypto.js +76 -0
- package/dist/registry/auth/database.d.ts +7 -0
- package/dist/registry/auth/database.js +22 -0
- package/dist/registry/auth/device-authorizations.d.ts +51 -0
- package/dist/registry/auth/device-authorizations.js +170 -0
- package/dist/registry/auth/gravatar.d.ts +1 -0
- package/dist/registry/auth/gravatar.js +8 -0
- package/dist/registry/auth/index.d.ts +13 -0
- package/dist/registry/auth/index.js +89 -0
- package/dist/registry/auth/installation.d.ts +42 -0
- package/dist/registry/auth/installation.js +105 -0
- package/dist/registry/auth/password.d.ts +12 -0
- package/dist/registry/auth/password.js +58 -0
- package/dist/registry/auth/rate-limits.d.ts +22 -0
- package/dist/registry/auth/rate-limits.js +75 -0
- package/dist/registry/auth/sync-secret.d.ts +27 -0
- package/dist/registry/auth/sync-secret.js +65 -0
- package/dist/registry/auth/types.d.ts +45 -0
- package/dist/registry/auth/types.js +11 -0
- package/dist/registry/auth/user-record.d.ts +13 -0
- package/dist/registry/auth/user-record.js +16 -0
- package/dist/registry/auth/users.d.ts +31 -0
- package/dist/registry/auth/users.js +135 -0
- package/dist/registry/auth/validation.d.ts +14 -0
- package/dist/registry/auth/validation.js +45 -0
- package/dist/registry/checkout/assets.d.ts +1 -1
- package/dist/registry/checkout/assets.js +25 -8
- package/dist/registry/checkout/index.d.ts +27 -6
- package/dist/registry/checkout/index.js +479 -131
- package/dist/registry/checkout/tokens.d.ts +1 -1
- package/dist/registry/checkout/tokens.js +16 -7
- package/dist/registry/client.d.ts +2 -1
- package/dist/registry/client.js +8 -0
- package/dist/registry/connection.d.ts +34 -11
- package/dist/registry/connection.js +77 -18
- package/dist/registry/content-kinds.d.ts +27 -0
- package/dist/registry/content-kinds.js +33 -0
- package/dist/registry/db/client.d.ts +14 -9
- package/dist/registry/db/client.js +22 -9
- package/dist/registry/db/driver.d.ts +39 -0
- package/dist/registry/db/driver.js +50 -0
- package/dist/registry/db/index.d.ts +2 -2
- package/dist/registry/db/index.js +14 -9
- package/dist/registry/db/migrate.d.ts +1 -1
- package/dist/registry/db/migrate.js +5 -5
- package/dist/registry/db/schema.d.ts +2992 -963
- package/dist/registry/db/schema.js +158 -8
- package/dist/registry/publish/assets.d.ts +2 -2
- package/dist/registry/publish/assets.js +46 -35
- package/dist/registry/publish/errors.js +3 -3
- package/dist/registry/publish/index.d.ts +45 -3
- package/dist/registry/publish/index.js +247 -24
- package/dist/registry/publish/package.js +14 -1
- package/dist/registry/publish/tokens.d.ts +2 -2
- package/dist/registry/publish/tokens.js +30 -24
- package/dist/registry/selection.d.ts +32 -0
- package/dist/registry/selection.js +58 -0
- package/dist/registry/transfer.d.ts +13 -0
- package/dist/store/filesystem.d.ts +3 -3
- package/dist/store/filesystem.js +7 -7
- package/dist/store/registry.d.ts +48 -0
- package/dist/store/registry.js +99 -15
- package/dist/transformers/plugins/csf-render.js +4 -2
- package/dist/transformers/plugins/ssr-render.js +52 -18
- package/dist/transformers/preview/component/api.d.ts +8 -0
- package/dist/transformers/preview/component/api.js +11 -15
- package/dist/transformers/preview/component/artifacts.d.ts +1 -1
- package/dist/transformers/preview/component/artifacts.js +11 -7
- package/dist/transformers/preview/component/builder.js +15 -18
- package/dist/transformers/preview/component/css.d.ts +3 -0
- package/dist/transformers/preview/component/css.js +93 -58
- package/dist/transformers/preview/component/html.js +18 -16
- package/dist/transformers/preview/component/javascript.js +2 -4
- package/dist/transformers/preview/pattern/builder.js +31 -18
- package/dist/transformers/preview/pattern/html.js +3 -1
- package/dist/transformers/preview/types.d.ts +30 -3
- package/dist/transformers/utils/build.d.ts +19 -0
- package/dist/transformers/utils/build.js +83 -1
- package/dist/transformers/utils/module.js +2 -1
- package/dist/types/config.d.ts +151 -40
- package/dist/utils/markdown.d.ts +10 -0
- package/dist/utils/markdown.js +17 -0
- package/dist/utils/menu-shell.js +14 -7
- package/dist/utils/pages.d.ts +22 -0
- package/dist/utils/pages.js +73 -0
- package/dist/utils/path.d.ts +5 -0
- package/dist/utils/path.js +9 -0
- package/docs/api.md +35 -3
- package/docs/cli.md +6 -2
- package/drizzle/0002_add_pages.sql +5 -3
- package/drizzle/0005_add_registry_auth.sql +95 -0
- package/drizzle/0006_add_component_source_format.sql +3 -0
- package/drizzle/0007_add_ai_keys.sql +11 -0
- package/drizzle/meta/0002_snapshot.json +17 -8
- package/drizzle/meta/0003_snapshot.json +17 -8
- package/drizzle/meta/0004_snapshot.json +17 -8
- package/drizzle/meta/0005_snapshot.json +1935 -0
- package/drizzle/meta/0006_snapshot.json +1941 -0
- package/drizzle/meta/0007_snapshot.json +2013 -0
- package/drizzle/meta/_journal.json +21 -0
- package/drizzle.config.ts +1 -1
- package/package.json +44 -5
- package/src/ai/README.md +42 -0
- package/src/ai/connections.ts +172 -0
- package/src/ai/define.ts +15 -0
- package/src/ai/types.ts +31 -0
- package/src/app/components/AiAssistant/AiAssistantPanel.tsx +144 -0
- package/src/app/components/AiAssistant/AiAssistantProvider.tsx +215 -0
- package/src/app/components/AiAssistant/AiConversation.tsx +674 -0
- package/src/app/components/AiAssistant/AiToolResult.tsx +99 -0
- package/src/app/components/AiAssistant/dockState.ts +86 -0
- package/src/app/components/AiAssistant/toolLabel.ts +22 -0
- package/src/app/components/AiAssistant/useAiChat.ts +147 -0
- package/src/app/components/AiAssistant/useAiConnections.ts +86 -0
- package/src/app/components/AiAssistant/usePageContext.ts +59 -0
- package/src/app/components/Auth/AccountLayout.tsx +124 -0
- package/src/app/components/Auth/AuthControls.tsx +99 -0
- package/src/app/components/Auth/AuthShell.tsx +73 -0
- package/src/app/components/Auth/RegistryInstallGate.tsx +23 -0
- package/src/app/components/Auth/RegistrySessionProvider.tsx +15 -0
- package/src/app/components/Auth/api.ts +20 -0
- package/src/app/components/Component/PageSliceResolver.tsx +1 -0
- package/src/app/components/Component/Preview.tsx +406 -226
- package/src/app/components/Component/slices/ComponentDisplaySlice.tsx +4 -0
- package/src/app/components/CopyToClipboard.tsx +27 -26
- package/src/app/components/Foundations/DisplayLogo.tsx +3 -3
- package/src/app/components/Layout/Header.tsx +10 -14
- package/src/app/components/Layout/RuntimeModeBadge.tsx +18 -0
- package/src/app/components/Markdown/CodeHighlight.tsx +73 -70
- package/src/app/components/Markdown/MarkdownComponents.tsx +30 -32
- package/src/app/components/McpIntegration/McpConfigDialog.tsx +349 -0
- package/src/app/components/McpIntegration/McpIcon.tsx +10 -0
- package/src/app/components/Navigation/MobileNav.tsx +7 -1
- package/src/app/components/Navigation/SideNav.tsx +1 -15
- package/src/app/components/context/PreviewContext.tsx +5 -5
- package/src/app/components/ui/attachment.tsx +50 -0
- package/src/app/components/ui/bubble.tsx +57 -0
- package/src/app/components/ui/button.tsx +3 -1
- package/src/app/components/ui/dialog.tsx +73 -0
- package/src/app/components/ui/marker.tsx +56 -0
- package/src/app/components/ui/sidebar.tsx +3 -1
- package/src/app/components/util/index.ts +14 -38
- package/src/app/css/index.css +64 -0
- package/src/app/lib/ai/auth.ts +33 -0
- package/src/app/lib/ai/context.ts +96 -0
- package/src/app/lib/ai/model.ts +85 -0
- package/src/app/lib/ai/prompt.ts +36 -0
- package/src/app/lib/ai/resolve.ts +97 -0
- package/src/app/lib/ai/tools.ts +120 -0
- package/src/app/lib/api/methods.ts +13 -0
- package/src/app/lib/auth/api.ts +105 -0
- package/src/app/lib/auth/config.ts +175 -0
- package/src/app/lib/auth/email.ts +34 -0
- package/src/app/lib/docs-api/backend.ts +16 -1
- package/src/app/lib/docs-api/errors.ts +8 -7
- package/src/app/lib/docs-api/handler.ts +2 -3
- package/src/app/lib/docs-api/index.ts +2 -0
- package/src/app/lib/docs-api/page-discovery.ts +121 -0
- package/src/app/lib/docs-api/page-rendering.ts +37 -0
- package/src/app/lib/docs-api/page-search.ts +191 -0
- package/src/app/lib/docs-api/records.ts +31 -24
- package/src/app/lib/docs-api/registry.ts +92 -11
- package/src/app/lib/docs-api/runtime-config.ts +42 -35
- package/src/app/lib/docs-api/search.ts +120 -0
- package/src/app/lib/mcp/auth.ts +64 -0
- package/src/app/lib/mcp/index.ts +2 -0
- package/src/app/lib/mcp/server.ts +357 -0
- package/src/app/lib/mcp/shape.ts +628 -0
- package/src/app/lib/registry-api/allowlist.ts +7 -5
- package/src/app/lib/registry-api/asset-transfer.ts +58 -31
- package/src/app/lib/registry-api/auth.ts +108 -45
- package/src/app/lib/registry-api/entity-routes.ts +17 -7
- package/src/app/lib/registry-api/errors.ts +12 -6
- package/src/app/lib/registry-api/handler.ts +22 -19
- package/src/app/lib/registry-api/meta.ts +4 -0
- package/src/app/lib/registry-api/revalidate.ts +31 -12
- package/src/app/lib/registry-api/store.ts +89 -26
- package/src/app/lib/registry-api/sync-compat.ts +166 -0
- package/src/app/lib/registry-api/token-transfer.ts +45 -13
- package/src/app/lib/registry-api/transfer-batch.ts +116 -0
- package/src/app/lib/registry-api/transfer.ts +99 -41
- package/src/app/lib/registry-api/validation.ts +33 -1
- package/src/app/lib/registry-connection.ts +2 -2
- package/src/app/lib/utils.ts +13 -0
- package/src/app/next.config.mjs +62 -16
- package/src/app/pages/[...slug]/index.tsx +52 -51
- package/src/app/pages/_app.tsx +14 -4
- package/src/app/pages/_document.tsx +6 -0
- package/src/app/pages/account/ai.tsx +162 -0
- package/src/app/pages/account/index.tsx +148 -0
- package/src/app/pages/account/tokens.tsx +232 -0
- package/src/app/pages/account/users.tsx +310 -0
- package/src/app/pages/api/account/ai/keys/[connectionId].ts +60 -0
- package/src/app/pages/api/account/ai/keys/index.ts +20 -0
- package/src/app/pages/api/account/index.ts +26 -0
- package/src/app/pages/api/account/tokens/[id].ts +17 -0
- package/src/app/pages/api/account/tokens/index.ts +35 -0
- package/src/app/pages/api/admin/users/[id]/[action].ts +89 -0
- package/src/app/pages/api/admin/users/index.ts +53 -0
- package/src/app/pages/api/ai/chat.ts +115 -0
- package/src/app/pages/api/ai/connections.ts +23 -0
- package/src/app/pages/api/auth/[...nextauth].ts +4 -0
- package/src/app/pages/api/auth/request-reset.ts +53 -0
- package/src/app/pages/api/auth/reset-password.ts +29 -0
- package/src/app/pages/api/docs/search/pages.json.ts +25 -0
- package/src/app/pages/api/install.ts +102 -0
- package/src/app/pages/api/mcp.ts +56 -0
- package/src/app/pages/api/oauth/device/approve.ts +23 -0
- package/src/app/pages/api/oauth/device/index.ts +41 -0
- package/src/app/pages/api/oauth/revoke.ts +31 -0
- package/src/app/pages/api/oauth/token.ts +31 -0
- package/src/app/pages/api/registry/components/[id]/files/[...filePath].ts +1 -1
- package/src/app/pages/api/registry/components/[id].ts +2 -2
- package/src/app/pages/api/registry/components/index.ts +1 -1
- package/src/app/pages/api/registry/health.ts +4 -3
- package/src/app/pages/api/registry/pages/[...id].ts +20 -0
- package/src/app/pages/api/registry/pages/index.ts +19 -0
- package/src/app/pages/api/registry/patterns/[id]/files/[...filePath].ts +1 -1
- package/src/app/pages/api/registry/patterns/[id].ts +2 -2
- package/src/app/pages/api/registry/patterns/index.ts +1 -1
- package/src/app/pages/api/registry/transfer/[kind]/[id].ts +3 -3
- package/src/app/pages/api/registry/transfer/[kind]/index.ts +21 -0
- package/src/app/pages/api/registry/transfer/assets/[collection].ts +2 -1
- package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +2 -1
- package/src/app/pages/api/registry/transfer/assets/blobs/have.ts +2 -2
- package/src/app/pages/api/registry/transfer/tokens/[...setId].ts +2 -2
- package/src/app/pages/api/sync/upload.ts +21 -0
- package/src/app/pages/cli/device.tsx +143 -0
- package/src/app/pages/foundations/icons/index.tsx +2 -2
- package/src/app/pages/foundations/icons/{[name] → library/[name]}/index.tsx +6 -6
- package/src/app/pages/index/index.tsx +14 -0
- package/src/app/pages/index.tsx +31 -5
- package/src/app/pages/install.tsx +336 -0
- package/src/app/pages/login.tsx +117 -0
- package/src/app/pages/reset-password.tsx +159 -0
- package/src/app/types/next-auth.d.ts +27 -0
- package/src/app-builder/build.ts +225 -86
- package/src/app-builder/client-config.ts +18 -14
- package/src/app-builder/config-diff/strategies/pattern.ts +1 -1
- package/src/app-builder/nav-shell.ts +8 -13
- package/src/app-builder/paths.ts +7 -9
- package/src/app-builder/static-export.ts +6 -6
- package/src/app-builder/vercel-output.ts +6 -6
- package/src/app-builder/watchers/component.ts +3 -3
- package/src/app-builder/watchers/utils.ts +2 -3
- package/src/app-builder/watchers.ts +63 -57
- package/src/artifacts/url.ts +7 -7
- package/src/cache/build-cache.ts +16 -15
- package/src/catalog/README.md +95 -0
- package/src/catalog/define.ts +79 -0
- package/src/catalog/implementation.ts +145 -0
- package/src/catalog/index.ts +31 -0
- package/src/catalog/normalize.ts +145 -0
- package/src/catalog/previews.ts +114 -0
- package/src/catalog/renderers.ts +94 -0
- package/src/catalog/types.ts +82 -0
- package/src/cli/README.md +1 -1
- package/src/cli/auth/browser.ts +29 -0
- package/src/cli/auth/device.ts +186 -0
- package/src/cli/auth/store.ts +194 -0
- package/src/cli/create.ts +58 -3
- package/src/cli/make.ts +22 -74
- package/src/cli/scaffold.ts +39 -222
- package/src/cli.ts +31 -7
- package/src/commands/README.md +1 -1
- package/src/commands/build/app.ts +2 -3
- package/src/commands/build/components.ts +2 -3
- package/src/commands/build/index.ts +2 -3
- package/src/commands/checkout/index.ts +29 -50
- package/src/commands/compat/deprecations.ts +79 -0
- package/src/commands/compat/push-args.ts +106 -0
- package/src/commands/db/migrate.ts +2 -3
- package/src/commands/dev/index.ts +2 -3
- package/src/commands/eject/config.ts +2 -3
- package/src/commands/eject/pages.ts +2 -3
- package/src/commands/eject/theme.ts +2 -3
- package/src/commands/fetch/index.ts +2 -3
- package/src/commands/index.ts +8 -2
- package/src/commands/login/index.ts +61 -0
- package/src/commands/logout/index.ts +104 -0
- package/src/commands/make/component.ts +2 -3
- package/src/commands/make/page.ts +4 -5
- package/src/commands/publish/index.ts +32 -49
- package/src/commands/pull/index.ts +32 -0
- package/src/commands/push/index.ts +115 -0
- package/src/commands/scaffold/index.ts +2 -3
- package/src/commands/start/index.ts +2 -3
- package/src/commands/types.ts +13 -1
- package/src/commands/utils.ts +107 -2
- package/src/commands/validate/components.ts +2 -3
- package/src/config/README.md +63 -2
- package/src/config/catalog-include.ts +227 -0
- package/src/config/defaults.ts +26 -13
- package/src/config/env.ts +41 -0
- package/src/config/errors.ts +7 -0
- package/src/config/from-env.ts +26 -0
- package/src/config/helpers.ts +4 -2
- package/src/config/index.ts +10 -2
- package/src/config/loader.ts +208 -43
- package/src/config/normalizers/page.ts +4 -4
- package/src/config/resolve-env.ts +107 -0
- package/src/config/runtime.ts +212 -226
- package/src/config/validator.ts +4 -6
- package/src/handlebars.ts +24 -0
- package/src/index.d.ts +2 -2
- package/src/index.ts +325 -72
- package/src/nav/resolver.ts +3 -1
- package/src/nav/workspace.ts +2 -2
- package/src/pattern.ts +14 -0
- package/src/pipeline/index.ts +1 -1
- package/src/react.ts +52 -0
- package/src/registry/asset-storage/adapters/vercel-blob.ts +1 -1
- package/src/registry/asset-storage/define.ts +1 -1
- package/src/registry/asset-storage/resolve.ts +7 -4
- package/src/registry/auth/access-tokens.ts +222 -0
- package/src/registry/auth/action-tokens.ts +225 -0
- package/src/registry/auth/ai-keys.ts +62 -0
- package/src/registry/auth/crypto.ts +75 -0
- package/src/registry/auth/database.ts +15 -0
- package/src/registry/auth/device-authorizations.ts +223 -0
- package/src/registry/auth/gravatar.ts +6 -0
- package/src/registry/auth/index.ts +105 -0
- package/src/registry/auth/installation.ts +117 -0
- package/src/registry/auth/password.ts +46 -0
- package/src/registry/auth/rate-limits.ts +84 -0
- package/src/registry/auth/sync-secret.ts +67 -0
- package/src/registry/auth/types.ts +57 -0
- package/src/registry/auth/user-record.ts +25 -0
- package/src/registry/auth/users.ts +145 -0
- package/src/registry/auth/validation.ts +42 -0
- package/src/registry/checkout/assets.ts +28 -9
- package/src/registry/checkout/index.ts +582 -130
- package/src/registry/checkout/tokens.ts +16 -8
- package/src/registry/client.ts +7 -1
- package/src/registry/connection.ts +91 -19
- package/src/registry/content-kinds.ts +39 -0
- package/src/registry/db/client.ts +34 -14
- package/src/registry/db/driver.ts +66 -0
- package/src/registry/db/index.ts +16 -5
- package/src/registry/db/migrate.ts +5 -5
- package/src/registry/db/schema.ts +225 -25
- package/src/registry/publish/assets.ts +47 -37
- package/src/registry/publish/errors.ts +3 -3
- package/src/registry/publish/index.ts +286 -27
- package/src/registry/publish/package.ts +14 -1
- package/src/registry/publish/tokens.ts +35 -26
- package/src/registry/selection.ts +63 -0
- package/src/registry/transfer.ts +14 -0
- package/src/store/filesystem.ts +6 -6
- package/src/store/registry.ts +131 -15
- package/src/transformers/plugins/csf-render.ts +4 -2
- package/src/transformers/plugins/ssr-render.ts +66 -19
- package/src/transformers/preview/component/api.ts +13 -1
- package/src/transformers/preview/component/artifacts.ts +8 -4
- package/src/transformers/preview/component/builder.ts +17 -4
- package/src/transformers/preview/component/css.ts +99 -60
- package/src/transformers/preview/component/html.ts +30 -19
- package/src/transformers/preview/component/javascript.ts +1 -3
- package/src/transformers/preview/pattern/builder.ts +33 -20
- package/src/transformers/preview/pattern/html.ts +3 -1
- package/src/transformers/preview/types.ts +31 -3
- package/src/transformers/utils/build.ts +80 -0
- package/src/transformers/utils/module.ts +4 -2
- package/src/types/config.ts +163 -40
- package/src/utils/README.md +1 -0
- package/src/utils/markdown.ts +11 -0
- package/src/utils/menu-shell.ts +14 -7
- package/src/utils/pages.ts +52 -0
- package/src/utils/path.ts +15 -6
- package/templates/default/README.md.tpl +68 -1
- package/templates/default/components/button/button.react.handoff.js.tpl +19 -23
- package/templates/default/components/button/button.react.handoff.ts.tpl +22 -22
- package/templates/default/env.tpl +10 -0
- package/templates/default/gitignore.tpl +26 -3
- package/templates/default/handoff.config.js.tpl +21 -166
- package/templates/default/handoff.config.ts.tpl +21 -150
- package/templates/default/handoff.config.with-examples.js.tpl +22 -164
- package/templates/default/handoff.config.with-examples.ts.tpl +22 -148
- package/templates/default/package.json.tpl +6 -1
- package/templates/default/vercel.json.tpl +4 -0
- package/config/docs/foundations/logos.md +0 -7
- package/config/templates/component/template.json +0 -49
- package/config/templates/template.html +0 -7
- package/dist/commands/make/template.d.ts +0 -8
- package/dist/commands/make/template.js +0 -47
- package/dist/config/normalizers/declaration.d.ts +0 -11
- package/dist/config/normalizers/declaration.js +0 -122
- package/dist/config/normalizers/pattern.d.ts +0 -8
- package/dist/config/normalizers/pattern.js +0 -41
- package/dist/declarations/helpers.d.ts +0 -6
- package/dist/declarations/helpers.js +0 -19
- package/dist/declarations/index.d.ts +0 -2
- package/dist/declarations/index.js +0 -9
- package/dist/declarations/types.d.ts +0 -60
- package/dist/registry/db/adapter.d.ts +0 -43
- package/dist/registry/db/adapter.js +0 -59
- package/src/app/lib/api/redact.ts +0 -46
- package/src/commands/make/template.ts +0 -47
- package/src/config/normalizers/declaration.ts +0 -179
- package/src/config/normalizers/pattern.ts +0 -59
- package/src/declarations/helpers.ts +0 -36
- package/src/declarations/index.ts +0 -19
- package/src/declarations/types.ts +0 -70
- package/src/registry/db/adapter.ts +0 -74
- /package/dist/{declarations → catalog}/types.js +0 -0
package/README.md
CHANGED
|
@@ -7,434 +7,862 @@
|
|
|
7
7
|
<img alt="" src="https://img.shields.io/npm/l/handoff-app?style=for-the-badge&labelColor=000000">
|
|
8
8
|
</a>
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* [Running locally](#running-locally)
|
|
25
|
-
* [Building artifacts](#building-artifacts)
|
|
26
|
-
* [Static site (default)](#static-site-default)
|
|
27
|
-
* [Registry app](#registry-app)
|
|
28
|
-
* [Publishing and checkout](#publishing-and-checkout)
|
|
29
|
-
* [Configuration](#configuration)
|
|
30
|
-
* [The runtime block](#the-runtime-block)
|
|
31
|
-
* [Environment variables](#environment-variables)
|
|
32
|
-
* [Hooks](#hooks)
|
|
33
|
-
* [CLI reference](#cli-reference)
|
|
34
|
-
* [Further reading](#further-reading)
|
|
35
|
-
* [Maintainers](#maintainers)
|
|
36
|
-
* [Contributing](#contributing)
|
|
37
|
-
* [License](#license)
|
|
38
|
-
|
|
39
|
-
## What is Handoff?
|
|
40
|
-
|
|
41
|
-
Handoff is an open source design system runtime that closes the gap between
|
|
42
|
-
design and development. By automating the delivery of your design system, it
|
|
43
|
-
helps eliminate the bottlenecks between the two.
|
|
44
|
-
|
|
45
|
-
It brings a few pieces together:
|
|
46
|
-
|
|
47
|
-
* **Token extraction.** Pull standardized foundations from your design source and
|
|
48
|
-
store them as JSON. Figma is supported out of the box through the Figma REST
|
|
49
|
-
API.
|
|
50
|
-
* **Transformation pipeline.** Produce SCSS, CSS, Style Dictionary, and preview
|
|
51
|
-
snippets from that data.
|
|
52
|
-
* **Documentation runtime.** Render live, working previews of your components,
|
|
53
|
-
tokens, and styles, served through a stable docs read API.
|
|
54
|
-
* **Delivery tooling.** Build and ship the deliverables, either as a static site
|
|
55
|
-
or as a deployed, database-backed catalog.
|
|
56
|
-
|
|
57
|
-
Out of the box Handoff ships SCSS and CSS maps for [Bootstrap 5](https://getbootstrap.com/).
|
|
58
|
-
If you use another framework or custom CSS, you can write your own map files to
|
|
59
|
-
connect the generated tokens to your project.
|
|
60
|
-
|
|
61
|
-
You install `handoff-app` as a project-local dependency and run its commands in
|
|
62
|
-
your project's context, so every project pins its own toolchain version.
|
|
63
|
-
|
|
64
|
-
## How it works
|
|
65
|
-
|
|
66
|
-
Handoff has two runtime modes. Mode is resolved solely from `runtime.mode` in
|
|
67
|
-
your config and defaults to `workspace`. It is never inferred from environment
|
|
68
|
-
variables or token presence, so setting `DATABASE_URL` can never silently flip
|
|
69
|
-
your app into another mode. The app chrome shows a `Workspace` or `Registry`
|
|
70
|
-
badge so the active mode is always clear.
|
|
71
|
-
|
|
72
|
-
* **Workspace mode (default).** Your local filesystem is the source of truth.
|
|
73
|
-
You author components, patterns, and tokens locally, preview them with a dev
|
|
74
|
-
server, and build a static documentation snapshot. Local authoring stays fast
|
|
75
|
-
and self-contained, with no database or registry required.
|
|
76
|
-
* **Registry mode.** A deployed, dynamic Next.js app backed by PostgreSQL serves
|
|
77
|
-
a shared catalog. It only ever serves what a workspace published to it. The
|
|
78
|
-
registry never compiles or materializes workspace source.
|
|
79
|
-
|
|
80
|
-
A **connected workspace** is workspace mode plus a registry connection. It still
|
|
81
|
-
renders only your local files and still reads as `Workspace`, but it gains two
|
|
82
|
-
commands: `publish` to push a locally built component or pattern up to a
|
|
83
|
-
registry, and `checkout` to pull one back down into your workspace.
|
|
84
|
-
|
|
85
|
-
Under the hood, documentation in every mode is consumed through one stable docs
|
|
86
|
-
read API and one canonical artifact URL scheme (`/api/docs/artifacts/{path}`),
|
|
87
|
-
so the same UI serves workspace, static, and registry backings without knowing
|
|
88
|
-
which store is behind it.
|
|
10
|
+
Design tokens, components, and patterns are turned by Handoff into working
|
|
11
|
+
documentation and distributable artifacts. A local workspace, a static site,
|
|
12
|
+
or a shared PostgreSQL-backed registry can be used.
|
|
13
|
+
|
|
14
|
+
## Runtime modes
|
|
15
|
+
|
|
16
|
+
- **Workspace** is the default. Local files are the source of truth and no
|
|
17
|
+
database is required.
|
|
18
|
+
- **Registry** is a deployed catalog backed by PostgreSQL.
|
|
19
|
+
- A **connected workspace** remains in workspace mode but can publish to and
|
|
20
|
+
check out from a registry.
|
|
21
|
+
|
|
22
|
+
Workspace mode is used by local projects for authoring. A separate
|
|
23
|
+
registry-mode application is created automatically by a registry build.
|
|
89
24
|
|
|
90
25
|
## Requirements
|
|
91
26
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
27
|
+
- Node.js 18.18 or newer; Node.js 20 LTS or newer is recommended
|
|
28
|
+
- npm 8 or newer
|
|
29
|
+
- A paid Figma account when fetching a Figma library
|
|
30
|
+
- PostgreSQL when running a registry
|
|
96
31
|
|
|
97
32
|
## Quick start
|
|
98
33
|
|
|
99
|
-
|
|
34
|
+
A project is created with the interactive wizard:
|
|
100
35
|
|
|
101
36
|
```bash
|
|
102
37
|
npx handoff-app init
|
|
38
|
+
cd my-handoff-project
|
|
103
39
|
```
|
|
104
40
|
|
|
105
|
-
The
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
41
|
+
The project name, sample content, JavaScript or TypeScript, optional Vercel
|
|
42
|
+
setup, and optional Figma credentials are requested by the wizard. Starter
|
|
43
|
+
files are created and `handoff-app` is installed locally. Generated npm scripts
|
|
44
|
+
are used for project operations so the pinned project version is shared by
|
|
45
|
+
local development and CI:
|
|
109
46
|
|
|
110
47
|
```bash
|
|
111
|
-
|
|
112
|
-
npm run
|
|
113
|
-
npm run
|
|
48
|
+
npm run fetch # optional: fetch Figma foundations
|
|
49
|
+
npm run start # local server with Handoff file watchers
|
|
50
|
+
npm run dev # local development server without Handoff file watchers
|
|
51
|
+
npm run build # static build by default
|
|
52
|
+
npm run db:migrate # registry database migrations
|
|
53
|
+
npm run validate
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Project layout
|
|
57
|
+
|
|
58
|
+
Every command runs against a working directory: the directory holding
|
|
59
|
+
`handoff.config.ts`. Generated output is written there.
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
<working directory>/
|
|
63
|
+
├─ handoff.config.ts
|
|
64
|
+
├─ .env
|
|
65
|
+
├─ components/
|
|
66
|
+
├─ patterns/
|
|
67
|
+
├─ pages/
|
|
68
|
+
├─ exported/ # fetched tokens and assets; commit this directory
|
|
69
|
+
├─ public/api/ # generated docs API; gitignored
|
|
70
|
+
├─ out/ # build output; gitignored
|
|
71
|
+
├─ .vercel/ # `--package vercel` output; gitignored
|
|
72
|
+
└─ .handoff/ # saved CLI login; gitignored
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
In a repository of its own, which is what `init` scaffolds, the working
|
|
76
|
+
directory is the repository root.
|
|
77
|
+
|
|
78
|
+
### Adding Handoff to an existing application
|
|
79
|
+
|
|
80
|
+
Declarations sit beside the components they document, so the catalog points
|
|
81
|
+
into the application source:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
catalog: { include: ['src/components', 'src/blocks'] },
|
|
114
85
|
```
|
|
115
86
|
|
|
116
|
-
|
|
87
|
+
**`handoff-app` must be a dependency of the package holding those
|
|
88
|
+
`*.handoff.ts` files.** They import `handoff-app/react`, which resolves from the
|
|
89
|
+
directory of the declaration, not from the directory of the config. Under npm a
|
|
90
|
+
missing dependency is usually hidden by hoisting. Under pnpm it is not: the
|
|
91
|
+
affected items are skipped with `Could not resolve "handoff-app/react"` and the
|
|
92
|
+
build still exits 0.
|
|
93
|
+
|
|
94
|
+
Two `tsconfig.json` files do different jobs:
|
|
95
|
+
|
|
96
|
+
- The config nearest the declarations type checks them. For files in `src/`
|
|
97
|
+
that is the one at the project root. An editor reads the nearest config only,
|
|
98
|
+
so a config in another directory that includes `src/` does not count, and
|
|
99
|
+
neither the editor nor the application build reports errors in a declaration.
|
|
100
|
+
- The config at the working directory generates the property tables. Without
|
|
101
|
+
it, Handoff warns `TypeScript config not found` and every table is empty.
|
|
102
|
+
|
|
103
|
+
Both files are needed when the working directory is not the project root.
|
|
104
|
+
|
|
105
|
+
Three placements work, all keeping one dependency tree so the catalog renders
|
|
106
|
+
the real components against the same React instance. A separate install with its
|
|
107
|
+
own `node_modules` outside that tree is not supported.
|
|
108
|
+
|
|
109
|
+
**Handoff in a subdirectory — recommended.** Nothing in the application changes.
|
|
110
|
+
`handoff-app` is a dependency of the application package, and
|
|
111
|
+
`HANDOFF_WORKING_PATH` keeps the generated directories in `handoff/`:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{ "scripts": { "handoff:dev": "HANDOFF_WORKING_PATH=handoff handoff-app start" } }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
An inline variable does not work in an npm script on Windows, where `cross-env`
|
|
118
|
+
is used instead.
|
|
119
|
+
|
|
120
|
+
**Handoff in the project root.** The working directory is the project root, so
|
|
121
|
+
one `tsconfig.json` does both jobs. Handoff writes to `public/api`, so a bundler
|
|
122
|
+
already serving static files from `public/` must be pointed elsewhere:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// vite.config.ts
|
|
126
|
+
export default defineConfig({
|
|
127
|
+
publicDir: 'static',
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Without that change the generated documentation API is copied into the
|
|
132
|
+
production build of the application. A Next.js application cannot use this
|
|
133
|
+
placement: `sitesOutputDirectory` moves `out/` away from its export directory,
|
|
134
|
+
but its `public` directory is a fixed convention with no setting to move it.
|
|
135
|
+
|
|
136
|
+
**Handoff as a workspace package.** For a repository that is already a
|
|
137
|
+
workspace. `handoff/` gets its own `package.json` and scripts, generated output
|
|
138
|
+
lands in `handoff/`, and the root forwards to it:
|
|
117
139
|
|
|
118
140
|
```json
|
|
119
141
|
{
|
|
120
|
-
"
|
|
121
|
-
|
|
122
|
-
"dev": "handoff-app dev",
|
|
123
|
-
"build": "handoff-app build",
|
|
124
|
-
"fetch": "handoff-app fetch"
|
|
125
|
-
}
|
|
142
|
+
"workspaces": ["handoff"],
|
|
143
|
+
"scripts": { "handoff:dev": "npm run dev --workspace handoff" }
|
|
126
144
|
}
|
|
127
145
|
```
|
|
128
146
|
|
|
129
|
-
|
|
130
|
-
|
|
147
|
+
The dependency rule is easiest to get wrong here: the declarations in `src/`
|
|
148
|
+
belong to the application package, not to the workspace, so both packages
|
|
149
|
+
declare `handoff-app`.
|
|
131
150
|
|
|
132
|
-
|
|
151
|
+
### pnpm
|
|
133
152
|
|
|
134
|
-
|
|
153
|
+
`build` and `build --package vercel` work with the default pnpm layout.
|
|
154
|
+
`build --target registry` cannot follow pnpm symlinks when it traces its runtime
|
|
155
|
+
dependencies into a standalone bundle, so it needs a flat layout:
|
|
135
156
|
|
|
157
|
+
```yaml
|
|
158
|
+
# pnpm-workspace.yaml
|
|
159
|
+
nodeLinker: hoisted
|
|
136
160
|
```
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
161
|
+
|
|
162
|
+
## Catalog items
|
|
163
|
+
|
|
164
|
+
Every documented UI entry is a catalog item. Each item declares either an implementation or a composition of other items.
|
|
165
|
+
An implementation names its renderer. `defineCatalogItem` comes from the module for that renderer.
|
|
166
|
+
The declaration supplies stable identity, documentation metadata, source entries, and previews.
|
|
167
|
+
|
|
168
|
+
### React
|
|
169
|
+
|
|
170
|
+
Handoff finds the implementation file from its import, so you do not need to repeat the path, and
|
|
171
|
+
builds the item's CSS from the stylesheets that import chain pulls in, descendants included.
|
|
172
|
+
Previews are named exports. `Preview<typeof item>` gives them the implementation's argument type.
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
// components/example/Component.tsx
|
|
176
|
+
export type ComponentProps = {
|
|
177
|
+
label: string;
|
|
178
|
+
onSelect?: () => void;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export default function Component({ label, onSelect }: ComponentProps) {
|
|
182
|
+
return <div onClick={onSelect}>{label}</div>;
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
// components/example/example.handoff.ts
|
|
188
|
+
import { defineCatalogItem, type Preview } from 'handoff-app/react';
|
|
189
|
+
import Component from './Component';
|
|
190
|
+
|
|
191
|
+
const item = defineCatalogItem({
|
|
192
|
+
id: 'component-id',
|
|
193
|
+
name: 'Component name',
|
|
194
|
+
description: 'Usage guidance for the component.',
|
|
195
|
+
group: 'Group name',
|
|
196
|
+
implementation: Component,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
export default item;
|
|
200
|
+
|
|
201
|
+
type ComponentPreview = Preview<typeof item>;
|
|
202
|
+
|
|
203
|
+
export const Default = {
|
|
204
|
+
args: { label: 'Example' },
|
|
205
|
+
} satisfies ComponentPreview;
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The export name is the preview name. Use `name` to set a different display title.
|
|
209
|
+
|
|
210
|
+
Arguments are serialized, so a preview that needs an icon, a callback, JSX children, or its own
|
|
211
|
+
state declares `render` instead. It renders as a component, so a capitalized name lets it use hooks, and the item keeps
|
|
212
|
+
documenting the implementation rather than a wrapper. Name a declaration that holds JSX
|
|
213
|
+
`example.handoff.tsx`.
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
export const Interactive = {
|
|
217
|
+
render: function Interactive() {
|
|
218
|
+
const [label, setLabel] = useState('Example');
|
|
219
|
+
return <Component label={label} onSelect={() => setLabel('Selected')} />;
|
|
220
|
+
},
|
|
221
|
+
} satisfies ComponentPreview;
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Handlebars
|
|
225
|
+
|
|
226
|
+
The implementation is the path to the template. Arguments are `Record<string, unknown>` by default;
|
|
227
|
+
an explicit argument type is supplied through `defineCatalogItem<BadgeArgs>`.
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
// components/badge/badge.handoff.ts
|
|
231
|
+
import { defineCatalogItem, type Preview } from 'handoff-app/handlebars';
|
|
232
|
+
|
|
233
|
+
const item = defineCatalogItem({
|
|
234
|
+
id: 'badge',
|
|
235
|
+
name: 'Badge',
|
|
236
|
+
implementation: './Badge.hbs',
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
export default item;
|
|
240
|
+
|
|
241
|
+
type BadgePreview = Preview<typeof item>;
|
|
242
|
+
|
|
243
|
+
export const Primary = {
|
|
244
|
+
args: { variant: 'primary', children: 'New' },
|
|
245
|
+
} satisfies BadgePreview;
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### CSF
|
|
249
|
+
|
|
250
|
+
CSF is a source format. A story file uses the renderer from the module that declares it. A React item references an existing CSF file with `fromCSF`.
|
|
251
|
+
The CSF file stays unchanged and defines its previews through named exports. Each story's `args` merge with and override `meta.args`.
|
|
252
|
+
Handoff preserves story names, `argTypes`, and `render` functions.
|
|
253
|
+
It finds the React component through `meta.component`, then documents and publishes it with the story file.
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
// components/card/card.handoff.ts
|
|
257
|
+
import { defineCatalogItem, fromCSF } from 'handoff-app/react';
|
|
258
|
+
|
|
259
|
+
export default defineCatalogItem({
|
|
260
|
+
id: 'card',
|
|
261
|
+
name: 'Card',
|
|
262
|
+
implementation: fromCSF('./Card.stories.tsx'),
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`Meta` and `StoryObj` are exported from `handoff-app/react`, so a story file type-checks without a
|
|
267
|
+
Storybook dependency. A project that already has Storybook keeps using its own types.
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
// components/card/Card.stories.tsx
|
|
271
|
+
import { type Meta, type StoryObj } from 'handoff-app/react';
|
|
272
|
+
import Card from './Card';
|
|
273
|
+
|
|
274
|
+
const meta = { component: Card } satisfies Meta<typeof Card>;
|
|
275
|
+
export default meta;
|
|
276
|
+
|
|
277
|
+
export const Primary: StoryObj<typeof meta> = {
|
|
278
|
+
args: { title: 'Example' },
|
|
279
|
+
};
|
|
149
280
|
```
|
|
150
281
|
|
|
151
|
-
|
|
282
|
+
A CSF preview is rendered on the server and is not hydrated. A direct React implementation is
|
|
283
|
+
hydrated in the browser.
|
|
152
284
|
|
|
153
|
-
|
|
154
|
-
declaration is discovered as `*.handoff.{ts,js,cjs,json}` (the legacy
|
|
155
|
-
`{dirname}.{js,cjs,json}` form keeps working too). List the directories you want
|
|
156
|
-
built under `entries.components` in your config.
|
|
285
|
+
### Compositions
|
|
157
286
|
|
|
158
|
-
|
|
287
|
+
A composition references other items by stable id. A named preview can be selected and its arguments
|
|
288
|
+
can be overridden by each reference. A composition needs no renderer, so it is declared with
|
|
289
|
+
`defineCatalogItem` from `handoff-app/pattern`.
|
|
159
290
|
|
|
160
291
|
```ts
|
|
161
|
-
|
|
162
|
-
import
|
|
163
|
-
|
|
164
|
-
export default
|
|
165
|
-
id: '
|
|
166
|
-
name: '
|
|
167
|
-
description: '
|
|
168
|
-
group: '
|
|
169
|
-
|
|
170
|
-
component: '
|
|
292
|
+
// patterns/example/example.handoff.ts
|
|
293
|
+
import { defineCatalogItem } from 'handoff-app/pattern';
|
|
294
|
+
|
|
295
|
+
export default defineCatalogItem({
|
|
296
|
+
id: 'pattern-id',
|
|
297
|
+
name: 'Pattern name',
|
|
298
|
+
description: 'Purpose and usage of the composition.',
|
|
299
|
+
group: 'Group name',
|
|
300
|
+
composition: [
|
|
301
|
+
{ ref: 'component-id', preview: 'Default' },
|
|
302
|
+
{ ref: 'badge', args: { children: 'Send' } },
|
|
303
|
+
],
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
An item declares `implementation` or `composition`, never both. Terms such as atom, element, and
|
|
308
|
+
block stay optional classification metadata on `type` and `categories`.
|
|
309
|
+
|
|
310
|
+
The `id` addresses the item everywhere: its artifacts, its documentation URL, and its publish and
|
|
311
|
+
checkout commands. Components and patterns share one namespace, so each id must be unique across
|
|
312
|
+
both.
|
|
313
|
+
|
|
314
|
+
If two declarations claim one id, Handoff keeps the first and skips the second. The warning names
|
|
315
|
+
both files. `publish` refuses to run until each id is unique.
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
### Registration
|
|
319
|
+
|
|
320
|
+
Catalog directories are registered in one place. A path is either an item directory or a collection
|
|
321
|
+
directory whose subdirectories are each treated as an item.
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
// handoff.config.ts
|
|
325
|
+
import { defineConfig } from 'handoff-app';
|
|
326
|
+
|
|
327
|
+
export default defineConfig({
|
|
328
|
+
catalog: {
|
|
329
|
+
include: ['components', 'patterns'],
|
|
171
330
|
},
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
args: { type: 'primary', children: 'Click me!' },
|
|
331
|
+
runtime: {
|
|
332
|
+
workspace: {
|
|
333
|
+
declarationFormat: 'ts',
|
|
176
334
|
},
|
|
177
335
|
},
|
|
178
336
|
});
|
|
179
337
|
```
|
|
180
338
|
|
|
181
|
-
|
|
339
|
+
### Upgrade from version 1.x.x to version 2.x.x
|
|
182
340
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
* `defineCsfComponent` for Component Story Format stories
|
|
186
|
-
* `defineComponent` for a generic, renderer-agnostic declaration
|
|
187
|
-
* `definePattern` for compositions that reference several components by `id`
|
|
341
|
+
See the [version 1.x.x to version 2.x.x migration guide](UPGRADE.md#version-1xx-to-version-2xx)
|
|
342
|
+
for breaking changes, migration steps, and upgrade verification.
|
|
188
343
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
can rename freely without breaking references. Identity is matched by `id`
|
|
192
|
-
during publish and checkout.
|
|
344
|
+
Custom documentation pages are Markdown files under `pages/`. Their relative
|
|
345
|
+
paths become their routes and registry IDs.
|
|
193
346
|
|
|
194
|
-
##
|
|
347
|
+
## Figma foundations
|
|
195
348
|
|
|
196
|
-
|
|
197
|
-
`.env
|
|
349
|
+
Figma integration is optional. When Figma credentials are entered during
|
|
350
|
+
`init`, they are written to `.env` by the wizard and the fetch command can be
|
|
351
|
+
run directly.
|
|
352
|
+
|
|
353
|
+
When that step is skipped or the Figma source must be changed, these values can
|
|
354
|
+
be added or updated in `.env`:
|
|
198
355
|
|
|
199
356
|
```bash
|
|
200
|
-
HANDOFF_FIGMA_PROJECT_ID=
|
|
201
|
-
HANDOFF_DEV_ACCESS_TOKEN=
|
|
357
|
+
HANDOFF_FIGMA_PROJECT_ID=figma-file-id
|
|
358
|
+
HANDOFF_DEV_ACCESS_TOKEN=figma-personal-access-token
|
|
202
359
|
```
|
|
203
360
|
|
|
204
|
-
|
|
361
|
+
The `file_content:read` and `library_content:read` scopes must be granted to the
|
|
362
|
+
personal access token. The configured library is fetched with:
|
|
205
363
|
|
|
206
364
|
```bash
|
|
207
365
|
npm run fetch
|
|
208
366
|
```
|
|
209
367
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
368
|
+
After design changes, the Figma library should be republished and the command
|
|
369
|
+
should be run again so the latest foundations are pulled.
|
|
370
|
+
|
|
371
|
+
Generated tokens, CSS, Sass, and available assets are written below
|
|
372
|
+
`exported/<projectId>`. The `exported/` directory should be committed so the
|
|
373
|
+
same inputs are used by local and CI builds. A successful fetch can be verified
|
|
374
|
+
by checking for generated token data such as `exported/<projectId>/tokens.json`;
|
|
375
|
+
an empty foundation route does not prove that the fetch succeeded.
|
|
214
376
|
|
|
215
|
-
##
|
|
377
|
+
## Workspace
|
|
216
378
|
|
|
217
|
-
|
|
379
|
+
Components, patterns, pages, styles, and configuration are authored as local
|
|
380
|
+
workspace files. For day-to-day authoring, the documentation server and file
|
|
381
|
+
watchers are started with:
|
|
218
382
|
|
|
219
383
|
```bash
|
|
220
|
-
|
|
221
|
-
handoff-app start # dev server plus file watchers that rebuild on change
|
|
384
|
+
npm run start
|
|
222
385
|
```
|
|
223
386
|
|
|
224
|
-
|
|
225
|
-
|
|
387
|
+
Changes are rebuilt and reflected in the documentation while the command is
|
|
388
|
+
running. When Handoff file watchers are not required, the development server
|
|
389
|
+
can be started with `npm run dev` instead.
|
|
390
|
+
|
|
391
|
+
The following should be visible at http://localhost:3000:
|
|
226
392
|
|
|
227
|
-
|
|
393
|
+
- the app reports `Workspace` as the active runtime mode;
|
|
394
|
+
- components appear in the component list;
|
|
395
|
+
- component detail pages render every configured preview;
|
|
396
|
+
- patterns render their referenced components; and
|
|
397
|
+
- foundation pages contain the fetched values.
|
|
228
398
|
|
|
229
|
-
|
|
230
|
-
decides what gets produced.
|
|
399
|
+
## Build outputs
|
|
231
400
|
|
|
232
|
-
|
|
401
|
+
A static documentation site is built with:
|
|
233
402
|
|
|
234
403
|
```bash
|
|
235
|
-
|
|
236
|
-
handoff-app build --target static # explicit, identical output
|
|
404
|
+
npm run build
|
|
237
405
|
```
|
|
238
406
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
static export to `out/<projectId>` (respecting `sitesOutputDirectory`). The
|
|
242
|
-
output is plain static files: it renders list, detail, preview, and inspect
|
|
243
|
-
pages and loads previews through canonical artifact URLs with no live server. It
|
|
244
|
-
works on a managed host like Vercel and on a plain static file server with no
|
|
245
|
-
host-specific rewrites.
|
|
407
|
+
Static output is written below `out/<projectId>` and can be served by any
|
|
408
|
+
static file server or CDN.
|
|
246
409
|
|
|
247
|
-
|
|
248
|
-
cannot be materialized, and it fails if the runtime is registry-only (there is
|
|
249
|
-
no local workspace to export).
|
|
250
|
-
|
|
251
|
-
### Registry app
|
|
410
|
+
A standalone registry application is built with:
|
|
252
411
|
|
|
253
412
|
```bash
|
|
254
|
-
|
|
413
|
+
npm run build -- --target registry
|
|
255
414
|
```
|
|
256
415
|
|
|
257
|
-
The
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
416
|
+
The generated application is automatically configured for registry mode by the
|
|
417
|
+
command. The database-backed Next.js bundle is written to `out/registry`, and
|
|
418
|
+
workspace source is not compiled or served. A Vercel Build Output bundle can be
|
|
419
|
+
produced for either target by adding `--package vercel`.
|
|
420
|
+
|
|
421
|
+
## Registry setup
|
|
422
|
+
|
|
423
|
+
### 1. Database migrations
|
|
262
424
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
deploy time:
|
|
425
|
+
Migrations are run from the source workspace or CI checkout, where the project
|
|
426
|
+
dependency and configuration are available:
|
|
266
427
|
|
|
267
428
|
```bash
|
|
268
|
-
DATABASE_URL="postgres
|
|
429
|
+
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/handoff?schema=public" \
|
|
430
|
+
npm run db:migrate
|
|
269
431
|
```
|
|
270
432
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
433
|
+
Migrations are a controlled release step and are separate from the build,
|
|
434
|
+
server startup, and browser installer.
|
|
435
|
+
|
|
436
|
+
### 2. Registry runtime
|
|
437
|
+
|
|
438
|
+
The generated registry bundle uses these environment variables:
|
|
439
|
+
|
|
440
|
+
```dotenv
|
|
441
|
+
PORT=4000
|
|
442
|
+
HOSTNAME=0.0.0.0
|
|
443
|
+
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/handoff?schema=public"
|
|
444
|
+
AUTH_SECRET="replace-with-at-least-32-random-characters"
|
|
445
|
+
AUTH_URL="http://localhost:4000"
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
`DATABASE_URL`, `AUTH_SECRET`, and `AUTH_URL` are required. The standalone
|
|
449
|
+
server also reads `PORT` and `HOSTNAME`. The generated entrypoint is
|
|
450
|
+
`out/registry/server.js`; how it is started and hosted depends on the deployment
|
|
451
|
+
environment.
|
|
452
|
+
|
|
453
|
+
### Vercel and Neon
|
|
454
|
+
|
|
455
|
+
Vercel packaging and the Neon PostgreSQL driver are supported independently.
|
|
456
|
+
Either integration can be used without the other.
|
|
457
|
+
|
|
458
|
+
#### Vercel packaging
|
|
459
|
+
|
|
460
|
+
A registry bundle that follows the Vercel Build Output API is produced with:
|
|
274
461
|
|
|
275
462
|
```bash
|
|
276
|
-
|
|
463
|
+
npm run build -- --target registry --package vercel
|
|
277
464
|
```
|
|
278
465
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
adapter (`pg` or `neon`) is chosen with `runtime.registry.database.adapter`, and
|
|
282
|
-
both adapters ship with the package.
|
|
466
|
+
The generated deployment output is written to `.vercel/output`. The registry
|
|
467
|
+
runtime variables listed above are supplied through the deployment environment.
|
|
283
468
|
|
|
284
|
-
|
|
469
|
+
#### Neon PostgreSQL
|
|
285
470
|
|
|
286
|
-
|
|
287
|
-
config and setting the access token env var:
|
|
471
|
+
Import `fromEnv` from `handoff-app`. Select the Neon connection driver in `handoff.config.ts`:
|
|
288
472
|
|
|
289
473
|
```ts
|
|
290
474
|
runtime: {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
475
|
+
registry: {
|
|
476
|
+
databaseUrl: fromEnv('DATABASE_URL'),
|
|
477
|
+
database: {
|
|
478
|
+
driver: 'neon',
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
A Neon PostgreSQL connection string is supplied through `DATABASE_URL`. The
|
|
485
|
+
same explicit database migration step is required before deployment.
|
|
486
|
+
|
|
487
|
+
#### Optional Vercel Blob storage
|
|
488
|
+
|
|
489
|
+
Registry assets are stored in PostgreSQL by default. Vercel Blob can be selected
|
|
490
|
+
when external asset storage is required:
|
|
491
|
+
|
|
492
|
+
```ts
|
|
493
|
+
runtime: {
|
|
494
|
+
registry: {
|
|
495
|
+
assetStorage: {
|
|
496
|
+
adapter: 'vercel-blob',
|
|
497
|
+
},
|
|
295
498
|
},
|
|
296
499
|
},
|
|
297
500
|
```
|
|
298
501
|
|
|
502
|
+
The associated `BLOB_READ_WRITE_TOKEN` is supplied through the deployment
|
|
503
|
+
environment.
|
|
504
|
+
|
|
505
|
+
### 3. Installation
|
|
506
|
+
|
|
507
|
+
The installer is opened at http://localhost:4000/install, where the first
|
|
508
|
+
administrator is created. The deployment is verified by the installer, but
|
|
509
|
+
migrations are never run by it.
|
|
510
|
+
|
|
511
|
+
An uninstalled registry can be claimed by the first visitor. A new deployment
|
|
512
|
+
should not be left unattended before installation is complete.
|
|
513
|
+
|
|
514
|
+
### 4. Workspace authorization
|
|
515
|
+
|
|
516
|
+
A workspace reaches a registry with a registry URL and an access token. There
|
|
517
|
+
are two ways to supply them. Both use the same kind of token, and both are
|
|
518
|
+
listed and revoked under Account → Access tokens in the registry.
|
|
519
|
+
|
|
520
|
+
#### Device login
|
|
521
|
+
|
|
522
|
+
CLI authorization is started from the source workspace with:
|
|
523
|
+
|
|
299
524
|
```bash
|
|
300
|
-
|
|
301
|
-
HANDOFF_REGISTRY_ACCESS_TOKEN=your-registry-token
|
|
525
|
+
npm run login -- --url http://localhost:4000
|
|
302
526
|
```
|
|
303
527
|
|
|
304
|
-
|
|
528
|
+
Registry sign-in, entry of the displayed device code, and CLI approval are
|
|
529
|
+
completed in the browser. The issued credential is saved in
|
|
530
|
+
`.handoff/cli-auth.json` for that exact registry URL.
|
|
531
|
+
|
|
532
|
+
One credential is saved per profile, so a workspace can stay signed in to
|
|
533
|
+
several registries at the same time:
|
|
305
534
|
|
|
306
535
|
```bash
|
|
307
|
-
|
|
308
|
-
|
|
536
|
+
npm run login -- --profile staging --url https://staging.example.com
|
|
537
|
+
npm run publish -- all --profile staging
|
|
309
538
|
```
|
|
310
539
|
|
|
311
|
-
|
|
312
|
-
that
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
before upload if required artifacts are missing, stale, or incompatible, and it
|
|
316
|
-
never touches unrelated entities' artifacts. Declaration files are never
|
|
317
|
-
uploaded.
|
|
540
|
+
A login profile name is free. It needs no `handoff.config.<name>.*` file, and
|
|
541
|
+
`login` is the only command that accepts a new name. A profile without its own
|
|
542
|
+
login falls back to the default login, so one `npm run login` is still enough
|
|
543
|
+
for a workspace with one registry.
|
|
318
544
|
|
|
319
|
-
`
|
|
320
|
-
|
|
321
|
-
`runtime.workspace.declarationFormat` (defaulting to `js`). Declarations are a
|
|
322
|
-
workspace-only concern, so they are always synthesized locally and never read
|
|
323
|
-
from the registry. Overwriting existing local files requires `--force` or an
|
|
324
|
-
interactive confirmation.
|
|
545
|
+
`npm run logout` revokes and removes the login of the selected profile. The
|
|
546
|
+
`--all` option removes every saved login.
|
|
325
547
|
|
|
326
|
-
|
|
548
|
+
#### Environment variables
|
|
327
549
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
unchanged.
|
|
550
|
+
A token is created in the registry under Account → Access tokens. Read and
|
|
551
|
+
write access is required to publish; read access is enough to check out. The
|
|
552
|
+
token and the registry URL are then set in `.env`:
|
|
332
553
|
|
|
333
|
-
```
|
|
334
|
-
|
|
554
|
+
```dotenv
|
|
555
|
+
HANDOFF_REGISTRY_URL=http://localhost:4000
|
|
556
|
+
HANDOFF_REGISTRY_ACCESS_TOKEN=hnd_...
|
|
557
|
+
```
|
|
335
558
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
559
|
+
No configuration file entry is needed, because these two variables are the
|
|
560
|
+
defaults for `runtime.registryConnection`. A selected profile reads
|
|
561
|
+
`.env.<profile>` on top of `.env`, so one workspace can address a different
|
|
562
|
+
registry per profile. In CI, the job environment supplies the same two
|
|
563
|
+
variables instead of a file.
|
|
564
|
+
|
|
565
|
+
Environment values win over a saved device login, so a CI job stays
|
|
566
|
+
deterministic on a machine where a developer is signed in. A token is only used
|
|
567
|
+
for the registry URL it was issued for. If the environment names a different
|
|
568
|
+
registry than the login of the selected profile, publish and checkout report
|
|
569
|
+
which login was skipped and why.
|
|
570
|
+
|
|
571
|
+
### 5. Content publishing
|
|
572
|
+
|
|
573
|
+
Every kind in the workspace is published in dependency order with:
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
npm run publish -- all
|
|
349
577
|
```
|
|
350
578
|
|
|
351
|
-
|
|
579
|
+
A single kind is published on its own:
|
|
352
580
|
|
|
353
|
-
|
|
354
|
-
|
|
581
|
+
```bash
|
|
582
|
+
npm run publish -- catalog
|
|
583
|
+
npm run publish -- pages
|
|
584
|
+
npm run publish -- tokens
|
|
585
|
+
npm run publish -- assets
|
|
586
|
+
```
|
|
355
587
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
mode: 'workspace', // 'workspace' | 'registry'
|
|
588
|
+
`catalog` covers every catalog item. An item is stored as a component or as a
|
|
589
|
+
pattern, and its declaration decides which. The command names items and never a
|
|
590
|
+
kind.
|
|
360
591
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
592
|
+
Publishing tokens or assets runs the Figma data pipeline before upload, so the
|
|
593
|
+
documented Figma credentials must be available.
|
|
594
|
+
|
|
595
|
+
One or more IDs can be appended to narrow a publish to those entities:
|
|
596
|
+
|
|
597
|
+
```bash
|
|
598
|
+
npm run publish -- catalog item-id another-id
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
`--dry-run` reports what would be uploaded and contacts no registry at all, so
|
|
602
|
+
it needs neither a registry URL nor a token. It still runs the build, which
|
|
603
|
+
refreshes generated output on disk; `--no-build` skips the build and publishes
|
|
604
|
+
the existing output, and the two combine to leave the workspace untouched:
|
|
605
|
+
|
|
606
|
+
```bash
|
|
607
|
+
npm run publish -- all --dry-run
|
|
608
|
+
npm run publish -- catalog --no-build
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
`checkout` takes the same `all`, multi-ID, and `--dry-run` forms. A dry-run
|
|
612
|
+
checkout reads from the registry, lists the files it would create or overwrite,
|
|
613
|
+
and writes nothing.
|
|
614
|
+
|
|
615
|
+
After the registry is reloaded, the published catalog items and foundations are
|
|
616
|
+
visible. Published database records are read by registry pages; the local
|
|
617
|
+
workspace is never read directly.
|
|
365
618
|
|
|
366
|
-
|
|
619
|
+
For CI, supply `HANDOFF_REGISTRY_URL` and `HANDOFF_REGISTRY_ACCESS_TOKEN`
|
|
620
|
+
through the job environment, as described under
|
|
621
|
+
[Workspace authorization](#4-workspace-authorization). A connection block in
|
|
622
|
+
`handoff.config.ts` is needed only to pin the URL in the repository, or to read
|
|
623
|
+
the values from differently named variables (`fromEnv` is imported from
|
|
624
|
+
`handoff-app`):
|
|
625
|
+
|
|
626
|
+
```ts
|
|
627
|
+
runtime: {
|
|
367
628
|
registryConnection: {
|
|
368
629
|
url: 'https://registry.example.com',
|
|
369
|
-
|
|
630
|
+
accessToken: fromEnv('HANDOFF_REGISTRY_ACCESS_TOKEN'),
|
|
370
631
|
},
|
|
632
|
+
},
|
|
633
|
+
```
|
|
371
634
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
635
|
+
## MCP
|
|
636
|
+
|
|
637
|
+
The documentation app serves a Model Context Protocol endpoint at `/api/mcp/`, so
|
|
638
|
+
coding agents can look up the pages, components and tokens that already exist instead
|
|
639
|
+
of inventing markup and values. It is stateless Streamable HTTP over `POST`;
|
|
640
|
+
there is nothing to start and no extra port. Keep the trailing slash: the app
|
|
641
|
+
sets `trailingSlash: true`, so `/api/mcp` answers with a 308 redirect.
|
|
642
|
+
|
|
643
|
+
| Target | Endpoint | Credential |
|
|
644
|
+
| --- | --- | --- |
|
|
645
|
+
| Workspace (`dev` / `start`) | `http://localhost:3000/api/mcp/` | none, as with `/api/docs/*` |
|
|
646
|
+
| Registry (standalone or Vercel) | `<registry url>/api/mcp/` | an access token, read-only is enough |
|
|
647
|
+
| Static export | not available, a static site has no API routes | n/a |
|
|
648
|
+
|
|
649
|
+
Registry tokens come from `handoff-app login` or Account settings in the
|
|
650
|
+
registry itself, and are the same credentials the rest of the registry API takes.
|
|
651
|
+
|
|
652
|
+
The endpoint is served unless a project turns it off:
|
|
653
|
+
|
|
654
|
+
```ts
|
|
655
|
+
runtime: {
|
|
656
|
+
mcp: false,
|
|
657
|
+
},
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
Like `runtime.mode`, this is config-only and baked at build time, so changing it
|
|
661
|
+
in a deployed registry means a rebuild. A disabled build answers 404 on the route,
|
|
662
|
+
drops the MCP SDK from the packaged bundle, and shows no connect affordance at all
|
|
663
|
+
rather than one that fails.
|
|
664
|
+
|
|
665
|
+
The documentation app carries its own endpoint and a ready-made client config
|
|
666
|
+
for Claude Code, Cursor and VS Code behind the plug icon in the header. By hand:
|
|
667
|
+
|
|
668
|
+
```json
|
|
669
|
+
{
|
|
670
|
+
"mcpServers": {
|
|
671
|
+
"handoff": {
|
|
672
|
+
"type": "http",
|
|
673
|
+
"url": "https://registry.example.com/api/mcp/",
|
|
674
|
+
"headers": { "Authorization": "Bearer hnd_..." }
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
## AI assistant
|
|
681
|
+
|
|
682
|
+
The documentation app can answer questions about the design system. The
|
|
683
|
+
assistant reads through the same MCP tools, so every answer comes from the
|
|
684
|
+
catalog and links to the pages and components it read. Open it from the search
|
|
685
|
+
control in the header, or with `⌘K`.
|
|
686
|
+
|
|
687
|
+
The deployment chooses the provider:
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
runtime: {
|
|
691
|
+
ai: {
|
|
692
|
+
connections: [
|
|
693
|
+
// Service key: the deployment pays for every reader.
|
|
694
|
+
{
|
|
695
|
+
id: 'gateway',
|
|
696
|
+
label: 'Acme LiteLLM',
|
|
697
|
+
baseUrl: 'https://llm.acme.internal/v1',
|
|
698
|
+
apiKey: fromEnv('LITELLM_API_KEY'),
|
|
699
|
+
models: ['gpt-4o', 'claude-sonnet-4-5', 'grok-4'],
|
|
700
|
+
},
|
|
701
|
+
// No credential: the endpoint needs none.
|
|
702
|
+
{ id: 'local', label: 'Ollama', baseUrl: 'http://127.0.0.1:11434/v1', models: ['llama3.1'] },
|
|
703
|
+
// User key: the config fixes the endpoint and models. Each reader adds their own key.
|
|
704
|
+
{
|
|
705
|
+
id: 'openai',
|
|
706
|
+
label: 'OpenAI',
|
|
707
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
708
|
+
credential: 'user',
|
|
709
|
+
models: ['gpt-4o', 'o3'],
|
|
710
|
+
},
|
|
711
|
+
],
|
|
712
|
+
defaultModel: 'gateway/claude-sonnet-4-5',
|
|
377
713
|
},
|
|
378
714
|
},
|
|
379
715
|
```
|
|
380
716
|
|
|
381
|
-
|
|
382
|
-
|
|
717
|
+
The `ai` block turns the assistant on, and `ai: { enabled: false }` turns it
|
|
718
|
+
back off. Like `runtime.mcp`, that flag is baked at build time, so a build
|
|
719
|
+
without it serves no chat route and shows no control.
|
|
720
|
+
|
|
721
|
+
**The declared connections are the whole surface.** A reader can only add a key
|
|
722
|
+
for a connection the config already declares, so the server never calls a URL a
|
|
723
|
+
reader chose. Without a `credential: 'user'` connection, there is no
|
|
724
|
+
reader-facing setting.
|
|
725
|
+
|
|
726
|
+
Every connection speaks the OpenAI-compatible `/chat/completions` API. This does
|
|
727
|
+
not limit which models a reader can use. Ollama serves an OpenAI-compatible API
|
|
728
|
+
at `/v1`, and LiteLLM, OpenRouter, vLLM, LM Studio and Azure OpenAI each front
|
|
729
|
+
Anthropic, Google and xAI models. A provider that fits nothing else names a
|
|
730
|
+
server-only module that default-exports `defineAiProvider()`, the way
|
|
731
|
+
`runtime.registry.assetStorage.module` does.
|
|
732
|
+
|
|
733
|
+
### Reader keys
|
|
734
|
+
|
|
735
|
+
A connection marked `credential: 'user'` asks each reader for their own key,
|
|
736
|
+
under Account → AI providers. Keys are encrypted with `HANDOFF_AI_KEY_SECRET`,
|
|
737
|
+
not hashed, because the server must send them to the provider. Set that variable
|
|
738
|
+
to a random value of at least 32 characters. A key is write-only across the API:
|
|
739
|
+
after a save, a read reports only that a key is in place.
|
|
740
|
+
|
|
741
|
+
This applies to registry mode only. A workspace has one user, whose config file
|
|
742
|
+
and `.env` are their user layer, so a workspace supplies a key through `apiKey`.
|
|
743
|
+
|
|
744
|
+
### Changing connections without a rebuild
|
|
383
745
|
|
|
384
|
-
|
|
746
|
+
The connection list is deployment data, not build shape, so a registry can
|
|
747
|
+
change it without a rebuild. `HANDOFF_AI_CONNECTIONS` holds a JSON array, read
|
|
748
|
+
at request time and merged over the baked list by `id`. It names each key
|
|
749
|
+
instead of holding its value:
|
|
750
|
+
|
|
751
|
+
```json
|
|
752
|
+
[{ "id": "gateway", "label": "Acme LiteLLM", "baseUrl": "https://llm.acme.internal/v1",
|
|
753
|
+
"apiKeyEnv": "LITELLM_API_KEY", "models": ["gpt-4o"] }]
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
Spend control belongs to the gateway. LiteLLM and OpenRouter both enforce
|
|
757
|
+
budgets and rate limits per key. The assistant caps only how many steps one
|
|
758
|
+
question can take.
|
|
759
|
+
|
|
760
|
+
## Configuration
|
|
761
|
+
|
|
762
|
+
Configuration is read from `handoff.config.ts`, `.js`, `.cjs`, or `.json`, in
|
|
763
|
+
that order. `defineConfig` provides typed authoring. Values merge onto the
|
|
764
|
+
defaults: plain objects merge recursively, and arrays, scalars, `null`, and
|
|
765
|
+
functions replace.
|
|
766
|
+
|
|
767
|
+
Useful environment variables:
|
|
385
768
|
|
|
386
769
|
| Variable | Purpose |
|
|
387
770
|
| --- | --- |
|
|
771
|
+
| `HANDOFF_PROFILE` | Config profile merged onto the base config |
|
|
388
772
|
| `HANDOFF_FIGMA_PROJECT_ID` | Figma file ID used by `fetch` |
|
|
389
773
|
| `HANDOFF_DEV_ACCESS_TOKEN` | Figma personal access token used by `fetch` |
|
|
390
|
-
| `HANDOFF_REGISTRY_URL` |
|
|
391
|
-
| `HANDOFF_REGISTRY_ACCESS_TOKEN` |
|
|
392
|
-
| `
|
|
393
|
-
| `
|
|
774
|
+
| `HANDOFF_REGISTRY_URL` | Connected workspace registry URL |
|
|
775
|
+
| `HANDOFF_REGISTRY_ACCESS_TOKEN` | Registry access token used by a connected workspace |
|
|
776
|
+
| `HANDOFF_SYNC_SECRET` | Optional deployment-wide registry credential |
|
|
777
|
+
| `DATABASE_URL` | Registry PostgreSQL connection string |
|
|
778
|
+
| `AUTH_SECRET` | Registry session-signing secret, at least 32 characters |
|
|
779
|
+
| `AUTH_URL` | Canonical public registry URL |
|
|
780
|
+
| `PORT` | Standalone registry server port |
|
|
781
|
+
| `HOSTNAME` | Standalone registry bind hostname |
|
|
782
|
+
| `BLOB_READ_WRITE_TOKEN` | Credential for the Vercel Blob asset adapter |
|
|
783
|
+
| `HANDOFF_AI_KEY_SECRET` | Encrypts reader-supplied AI provider keys, at least 32 characters |
|
|
784
|
+
| `HANDOFF_AI_CONNECTIONS` | JSON array of AI connections, merged over the baked list by `id` |
|
|
785
|
+
| `HANDOFF_OUTPUT_DIR` | Override the fetched output directory |
|
|
786
|
+
| `HANDOFF_SITES_DIR` | Override the build output directory |
|
|
787
|
+
| `HANDOFF_WORKING_PATH` | Directory holding `handoff.config.ts`; defaults to the current directory |
|
|
788
|
+
| `HANDOFF_APP_PORT` | Workspace documentation server port |
|
|
789
|
+
| `HANDOFF_WEBSOCKET_PORT` | Workspace live-reload server port |
|
|
790
|
+
|
|
791
|
+
Registry assets are stored in PostgreSQL by default with a 4 MB per-blob limit.
|
|
792
|
+
Vercel Blob or a custom adapter can be selected through
|
|
793
|
+
`runtime.registry.assetStorage`. The `pg` or `neon` connection driver can be
|
|
794
|
+
selected through `runtime.registry.database.driver`; PostgreSQL is used by
|
|
795
|
+
both.
|
|
796
|
+
|
|
797
|
+
## Profiles
|
|
798
|
+
|
|
799
|
+
A profile is a sidecar config file that merges onto the base config. It holds
|
|
800
|
+
only what changes between environments, so a project keeps one shared config
|
|
801
|
+
instead of a second complete copy of it.
|
|
802
|
+
|
|
803
|
+
- The base config is `handoff.config.ts`, `.js`, `.cjs`, or `.json`. It is the
|
|
804
|
+
only config file loaded when no profile is selected.
|
|
805
|
+
- A profile file is `handoff.config.<profile>.*`, with the same four
|
|
806
|
+
extensions.
|
|
807
|
+
- Select a profile with `--profile <name>` or with `HANDOFF_PROFILE`.
|
|
808
|
+
`--profile` takes precedence over `HANDOFF_PROFILE`.
|
|
809
|
+
- Profile names are not predefined. A name can contain lowercase letters,
|
|
810
|
+
numbers, and hyphens.
|
|
811
|
+
- A selected profile must exist. If no `handoff.config.<name>.*` file is found,
|
|
812
|
+
the command stops with an error.
|
|
813
|
+
- `login`, `logout`, `publish`, and `checkout` also accept a profile that has
|
|
814
|
+
only a saved registry login, because there the profile selects the registry.
|
|
815
|
+
Every other command still needs the config file.
|
|
816
|
+
- Layers resolve as defaults, base config, profile, then programmatic config,
|
|
817
|
+
with the merge rules described under [Configuration](#configuration).
|
|
818
|
+
- A selected profile also reads `.env.<profile>` on top of `.env`, when that
|
|
819
|
+
file exists. Both are read from the directory the command runs in, and a
|
|
820
|
+
variable already set in the environment beats both files.
|
|
821
|
+
|
|
822
|
+
```bash
|
|
823
|
+
npx handoff-app build --target registry --profile registry
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
`build --target registry` always packages a registry-mode application, so a
|
|
827
|
+
profile does not need to set `runtime.mode`. This makes a profile a good place
|
|
828
|
+
for the registry build settings: the database driver, the database
|
|
829
|
+
environment-variable name, the asset storage, and whether MCP is enabled. A
|
|
830
|
+
hosting provider or a CI job selects the profile through `HANDOFF_PROFILE`.
|
|
394
831
|
|
|
395
|
-
|
|
832
|
+
`defineConfig` types a profile as well as a base config. The generated
|
|
833
|
+
`.gitignore` lists `handoff.config.local.*`, which makes `local` the usual name
|
|
834
|
+
for a profile that stays on one machine.
|
|
396
835
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
example, `registerHandlebarsHelpers` runs after Handoff registers the built-in
|
|
400
|
-
`field` and `eq` helpers, so you can register your own helpers for `.hbs`
|
|
401
|
-
preview templates.
|
|
836
|
+
Arrays replace, so a profile that declares `catalog.include` also decides what
|
|
837
|
+
`make` and `checkout` register.
|
|
402
838
|
|
|
403
|
-
|
|
839
|
+
`.env.<profile>` is read once at startup rather than watched.
|
|
840
|
+
`HANDOFF_WORKING_PATH` cannot be set from it, because the working path is
|
|
841
|
+
resolved before any profile is known.
|
|
404
842
|
|
|
405
843
|
## CLI reference
|
|
406
844
|
|
|
407
845
|
| Command | Description |
|
|
408
846
|
| --- | --- |
|
|
409
|
-
| `init` |
|
|
410
|
-
| `fetch` |
|
|
411
|
-
| `
|
|
412
|
-
| `
|
|
413
|
-
| `build [--target static\|registry]` |
|
|
414
|
-
| `
|
|
415
|
-
| `
|
|
416
|
-
| `
|
|
417
|
-
| `
|
|
418
|
-
| `
|
|
419
|
-
| `
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
reference.
|
|
429
|
-
|
|
430
|
-
## Further reading
|
|
431
|
-
|
|
432
|
-
* [Configure your project](https://www.handoff.com/docs/customization)
|
|
433
|
-
* [Customize the content](https://www.handoff.com/docs/customization/content)
|
|
434
|
-
* [Integrate tokens with your project](https://www.handoff.com/docs/tokens/integration)
|
|
435
|
-
* [Build to static assets](https://www.handoff.com/docs/tokens/publishing)
|
|
436
|
-
* [GitHub Actions CI/CD](https://www.handoff.com/docs/infrastructure/github/)
|
|
437
|
-
* [Bitbucket Pipelines CI/CD](https://www.handoff.com/docs/infrastructure/bitbucket/)
|
|
847
|
+
| `npx handoff-app init` | A workspace is scaffolded with the interactive wizard |
|
|
848
|
+
| `npm run fetch` | Design tokens and assets are fetched from Figma |
|
|
849
|
+
| `npm run start` | The workspace server and Handoff file watchers are started |
|
|
850
|
+
| `npm run dev` | The workspace development server is started |
|
|
851
|
+
| `npm run build -- [--target static\|registry]` | The static site or standalone registry bundle is built |
|
|
852
|
+
| `npm run db:migrate` | Registry database migrations are applied |
|
|
853
|
+
| `npm run validate` | Configured components are validated |
|
|
854
|
+
| `npm run publish -- <kind\|all> [id...]` | Catalog items, pages, tokens, or assets are published |
|
|
855
|
+
| `npm run checkout -- <kind\|all> [id...]` | Published content is pulled into a workspace |
|
|
856
|
+
| `npm run login -- [--profile <name>] --url <url>` | The CLI is authorized through the registry device flow |
|
|
857
|
+
| `npm run logout -- [--profile <name>] [--all]` | Saved CLI credentials are revoked and removed |
|
|
858
|
+
|
|
859
|
+
Every command except `init` accepts `-c, --config`, `--profile`, `-d, --debug`,
|
|
860
|
+
and `-f, --force`. `publish` and `checkout` additionally accept `--dry-run`, and
|
|
861
|
+
`publish` accepts `--no-build`.
|
|
862
|
+
|
|
863
|
+
Arguments after `--` are forwarded to the local CLI. Exact options can be shown
|
|
864
|
+
by adding `--help` after the separator. Advanced configuration and hooks are
|
|
865
|
+
documented in [docs/api.md](docs/api.md).
|
|
438
866
|
|
|
439
867
|
## Maintainers
|
|
440
868
|
|
|
@@ -446,8 +874,8 @@ reference.
|
|
|
446
874
|
|
|
447
875
|
## Contributing
|
|
448
876
|
|
|
449
|
-
|
|
450
|
-
|
|
877
|
+
[Issues](https://github.com/Convertiv/handoff-app/issues/new) and pull requests
|
|
878
|
+
are welcome.
|
|
451
879
|
|
|
452
880
|
Handoff follows the [Contributor Covenant](http://contributor-covenant.org/version/1/3/0/)
|
|
453
881
|
Code of Conduct.
|