From 706ae32b8b03cd6effaa9d0d5f385d93529635df Mon Sep 17 00:00:00 2001 From: yyyyaaa Date: Mon, 3 Aug 2026 15:24:39 +0700 Subject: [PATCH 1/3] Add a composable Constructive App Kit --- .github/workflows/ci.yml | 5 +- apps/blocks/package.json | 4 + apps/blocks/registry.json | 364 ++++++++ apps/blocks/src/app/blocks/app-kit/page.tsx | 373 ++++++++ apps/blocks/src/app/blocks/page.tsx | 12 +- apps/blocks/src/app/layout.tsx | 2 +- .../src/app/opengraph-image.png/route.tsx | 2 +- apps/blocks/src/app/sitemap.test.ts | 3 +- apps/blocks/src/app/sitemap.ts | 2 + .../src/blocks/app-kit/board/board.test.tsx | 131 +++ .../blocks/src/blocks/app-kit/board/board.tsx | 261 ++++++ .../blocks/app-kit/board/connected-board.tsx | 130 +++ apps/blocks/src/blocks/app-kit/board/index.ts | 2 + .../blocks/app-kit/calendar/calendar.test.tsx | 106 +++ .../src/blocks/app-kit/calendar/calendar.tsx | 435 +++++++++ .../app-kit/calendar/connected-calendar.tsx | 64 ++ .../src/blocks/app-kit/calendar/index.ts | 2 + .../src/blocks/app-kit/core/contracts.test.ts | 568 ++++++++++++ .../src/blocks/app-kit/core/contracts.ts | 446 +++++++++ apps/blocks/src/blocks/app-kit/core/index.ts | 4 + .../blocks/app-kit/core/navigation.test.ts | 68 ++ .../src/blocks/app-kit/core/navigation.ts | 76 ++ .../src/blocks/app-kit/core/runtime.test.tsx | 504 ++++++++++ .../src/blocks/app-kit/core/runtime.tsx | 443 +++++++++ .../blocks/app-kit/core/schema-validation.ts | 862 ++++++++++++++++++ apps/blocks/src/blocks/app-kit/core/scope.ts | 62 ++ .../app-kit/dashboard/connected-dashboard.tsx | 139 +++ .../app-kit/dashboard/dashboard.test.tsx | 233 +++++ .../blocks/app-kit/dashboard/dashboard.tsx | 357 ++++++++ .../src/blocks/app-kit/dashboard/index.ts | 5 + .../app-kit/dashboard/layout-store.test.ts | 52 ++ .../blocks/app-kit/dashboard/layout-store.ts | 123 +++ .../dashboard/persisted-dashboard.test.tsx | 70 ++ .../app-kit/dashboard/persisted-dashboard.tsx | 99 ++ .../src/blocks/app-kit/dashboard/widgets.tsx | 609 +++++++++++++ .../src/blocks/app-kit/data/action-bars.tsx | 89 ++ .../src/blocks/app-kit/data/collections.tsx | 457 ++++++++++ .../src/blocks/app-kit/data/controls.tsx | 223 +++++ .../src/blocks/app-kit/data/data.test.tsx | 484 ++++++++++ .../src/blocks/app-kit/data/detail-form.tsx | 610 +++++++++++++ apps/blocks/src/blocks/app-kit/data/index.ts | 9 + .../src/blocks/app-kit/data/relations.tsx | 350 +++++++ .../blocks/src/blocks/app-kit/data/states.tsx | 109 +++ apps/blocks/src/blocks/app-kit/data/types.ts | 182 ++++ .../app-kit/event-studio/definitions.test.ts | 359 ++++++++ .../app-kit/event-studio/definitions.ts | 730 +++++++++++++++ .../event-studio/event-studio.test.tsx | 427 +++++++++ .../app-kit/event-studio/event-studio.tsx | 709 ++++++++++++++ .../src/blocks/app-kit/event-studio/index.ts | 3 + .../blocks/app-kit/event-studio/state.test.ts | 76 ++ .../src/blocks/app-kit/event-studio/state.ts | 177 ++++ .../src/blocks/app-kit/workflow/actions.tsx | 423 +++++++++ .../app-kit/workflow/connected-actions.tsx | 335 +++++++ .../src/blocks/app-kit/workflow/index.ts | 3 + .../src/blocks/app-kit/workflow/stepper.tsx | 104 +++ .../blocks/app-kit/workflow/workflow.test.tsx | 173 ++++ .../src/components/site/site-sidebar.tsx | 7 +- .../src/components/site/site-topbar.test.tsx | 7 + .../src/components/site/site-topbar.tsx | 9 + apps/blocks/src/lib/app-kit-catalog.ts | 67 ++ .../src/lib/application-doc-navigation.ts | 2 + apps/blocks/src/lib/site.ts | 2 +- apps/registry/scripts/build.ts | 2 + apps/registry/scripts/compiler.test.ts | 89 ++ apps/registry/scripts/compiler.ts | 129 +++ apps/registry/scripts/smoke-install.ts | 416 +++++++++ packages/ui/package.json | 21 + packages/ui/registry.json | 21 + packages/ui/scripts/build-registry.ts | 1 + packages/ui/scripts/check-package-parity.ts | 51 +- packages/ui/src/components/chart.tsx | 31 + packages/ui/tsdown.config.ts | 14 +- pnpm-lock.yaml | 316 +++++-- scripts/check-packed-packages.ts | 85 +- 74 files changed, 13800 insertions(+), 120 deletions(-) create mode 100644 apps/blocks/src/app/blocks/app-kit/page.tsx create mode 100644 apps/blocks/src/blocks/app-kit/board/board.test.tsx create mode 100644 apps/blocks/src/blocks/app-kit/board/board.tsx create mode 100644 apps/blocks/src/blocks/app-kit/board/connected-board.tsx create mode 100644 apps/blocks/src/blocks/app-kit/board/index.ts create mode 100644 apps/blocks/src/blocks/app-kit/calendar/calendar.test.tsx create mode 100644 apps/blocks/src/blocks/app-kit/calendar/calendar.tsx create mode 100644 apps/blocks/src/blocks/app-kit/calendar/connected-calendar.tsx create mode 100644 apps/blocks/src/blocks/app-kit/calendar/index.ts create mode 100644 apps/blocks/src/blocks/app-kit/core/contracts.test.ts create mode 100644 apps/blocks/src/blocks/app-kit/core/contracts.ts create mode 100644 apps/blocks/src/blocks/app-kit/core/index.ts create mode 100644 apps/blocks/src/blocks/app-kit/core/navigation.test.ts create mode 100644 apps/blocks/src/blocks/app-kit/core/navigation.ts create mode 100644 apps/blocks/src/blocks/app-kit/core/runtime.test.tsx create mode 100644 apps/blocks/src/blocks/app-kit/core/runtime.tsx create mode 100644 apps/blocks/src/blocks/app-kit/core/schema-validation.ts create mode 100644 apps/blocks/src/blocks/app-kit/core/scope.ts create mode 100644 apps/blocks/src/blocks/app-kit/dashboard/connected-dashboard.tsx create mode 100644 apps/blocks/src/blocks/app-kit/dashboard/dashboard.test.tsx create mode 100644 apps/blocks/src/blocks/app-kit/dashboard/dashboard.tsx create mode 100644 apps/blocks/src/blocks/app-kit/dashboard/index.ts create mode 100644 apps/blocks/src/blocks/app-kit/dashboard/layout-store.test.ts create mode 100644 apps/blocks/src/blocks/app-kit/dashboard/layout-store.ts create mode 100644 apps/blocks/src/blocks/app-kit/dashboard/persisted-dashboard.test.tsx create mode 100644 apps/blocks/src/blocks/app-kit/dashboard/persisted-dashboard.tsx create mode 100644 apps/blocks/src/blocks/app-kit/dashboard/widgets.tsx create mode 100644 apps/blocks/src/blocks/app-kit/data/action-bars.tsx create mode 100644 apps/blocks/src/blocks/app-kit/data/collections.tsx create mode 100644 apps/blocks/src/blocks/app-kit/data/controls.tsx create mode 100644 apps/blocks/src/blocks/app-kit/data/data.test.tsx create mode 100644 apps/blocks/src/blocks/app-kit/data/detail-form.tsx create mode 100644 apps/blocks/src/blocks/app-kit/data/index.ts create mode 100644 apps/blocks/src/blocks/app-kit/data/relations.tsx create mode 100644 apps/blocks/src/blocks/app-kit/data/states.tsx create mode 100644 apps/blocks/src/blocks/app-kit/data/types.ts create mode 100644 apps/blocks/src/blocks/app-kit/event-studio/definitions.test.ts create mode 100644 apps/blocks/src/blocks/app-kit/event-studio/definitions.ts create mode 100644 apps/blocks/src/blocks/app-kit/event-studio/event-studio.test.tsx create mode 100644 apps/blocks/src/blocks/app-kit/event-studio/event-studio.tsx create mode 100644 apps/blocks/src/blocks/app-kit/event-studio/index.ts create mode 100644 apps/blocks/src/blocks/app-kit/event-studio/state.test.ts create mode 100644 apps/blocks/src/blocks/app-kit/event-studio/state.ts create mode 100644 apps/blocks/src/blocks/app-kit/workflow/actions.tsx create mode 100644 apps/blocks/src/blocks/app-kit/workflow/connected-actions.tsx create mode 100644 apps/blocks/src/blocks/app-kit/workflow/index.ts create mode 100644 apps/blocks/src/blocks/app-kit/workflow/stepper.tsx create mode 100644 apps/blocks/src/blocks/app-kit/workflow/workflow.test.tsx create mode 100644 apps/blocks/src/lib/app-kit-catalog.ts create mode 100644 packages/ui/src/components/chart.tsx diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b07b00..154586b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,9 +72,12 @@ jobs: - name: Build shadcn registry run: pnpm build:registry + - name: Install Chromium for App Kit hydration smoke + run: pnpm --filter blocks exec playwright install --with-deps chromium + - name: Verify representative registry installs env: - SMOKE_CASE: ai,schema-builder,command-palette + SMOKE_CASE: ai,schema-builder,command-palette,app-kit-data-default,app-kit-data-custom,app-kit-event-studio-next16 SMOKE_REUSE_PACKED_ARTIFACTS: '1' run: pnpm --filter @constructive-io/registry smoke:install diff --git a/apps/blocks/package.json b/apps/blocks/package.json index c45e527..e316064 100644 --- a/apps/blocks/package.json +++ b/apps/blocks/package.json @@ -39,11 +39,15 @@ "@constructive-io/schema-builder": "workspace:*", "@constructive-io/sheets": "workspace:*", "@constructive-io/ui": "workspace:*", + "@tanstack/charts": "0.6.4", + "@tanstack/charts-scales": "0.6.4", + "@tanstack/react-charts": "0.6.4", "@tanstack/react-form": "^1.27.7", "@tanstack/react-query": "^5.90.16", "ai": "^7.0.26", "clsx": "^2.1.1", "dompurify": "^3.3.1", + "d3-scale": "4.0.2", "gql-ast": "^3.3.3", "graphql": "16.13.0", "lucide-react": "^0.525.0", diff --git a/apps/blocks/registry.json b/apps/blocks/registry.json index a1296c6..14c2f5a 100644 --- a/apps/blocks/registry.json +++ b/apps/blocks/registry.json @@ -1051,6 +1051,370 @@ "type": "registry:lib" } ] + }, + { + "name": "app-kit-core", + "type": "registry:block", + "title": "Constructive App Kit Core", + "description": "Typed, server-safe application resources, queries, actions, scope partitioning, schema validation, and opt-in client runtime.", + "docs": "Install this root to define Constructive-native application contracts without choosing a view family. Import the client runtime explicitly when a connected view needs TanStack Query. Credentials never belong in definitions or AppScope.\n\nGuide: https://constructive-io.github.io/blocks/blocks/app-kit/", + "dependencies": [ + "@tanstack/react-query" + ], + "meta": { + "constructive": { + "version": 1, + "family": "app-kit", + "kind": "runtime", + "boundary": "mixed", + "provider": "app-kit", + "dataShapes": ["record", "collection", "aggregate", "temporal"], + "intents": ["define-resources", "load-data", "execute-actions", "validate-schema"], + "capabilities": ["resource-contracts", "query-contracts", "action-contracts", "scoped-cache", "schema-validation", "navigation-adapters"], + "slots": ["record-opener", "url-state-adapter"], + "events": ["query-settled", "action-settled", "scope-changed"], + "compatibleWith": ["app-kit-data", "app-kit-board", "app-kit-dashboard", "app-kit-calendar", "app-kit-workflow", "app-kit-event-studio"] + } + }, + "files": [ + { + "path": "registry/constructive/blocks/app-kit/core/contracts.ts", + "target": "src/blocks/app-kit/core/contracts.ts", + "type": "registry:lib" + }, + { + "path": "registry/constructive/blocks/app-kit/core/navigation.ts", + "target": "src/blocks/app-kit/core/navigation.ts", + "type": "registry:lib" + }, + { + "path": "registry/constructive/blocks/app-kit/core/schema-validation.ts", + "target": "src/blocks/app-kit/core/schema-validation.ts", + "type": "registry:lib" + }, + { + "path": "registry/constructive/blocks/app-kit/core/scope.ts", + "target": "src/blocks/app-kit/core/scope.ts", + "type": "registry:lib" + }, + { + "path": "registry/constructive/blocks/app-kit/core/runtime.tsx", + "target": "src/blocks/app-kit/core/runtime.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/core/index.ts", + "target": "src/blocks/app-kit/core/index.ts", + "type": "registry:lib" + } + ] + }, + { + "name": "app-kit-data", + "type": "registry:block", + "title": "Constructive App Kit Data Views", + "description": "Controlled and connected tables, lists, cards, record details, forms, relations, collection controls, and action bars.", + "docs": "Use this root for record and collection applications. Loaders stay server-driven, relation pickers search remotely, ambiguous identities fail read-only, and every view distinguishes loading, empty, denied, and error states.\n\nGuide: https://constructive-io.github.io/blocks/blocks/app-kit/", + "dependencies": [ + "@tanstack/react-query", + "lucide-react" + ], + "meta": { + "constructive": { + "version": 1, + "family": "app-kit", + "kind": "view", + "boundary": "client", + "provider": "app-kit", + "dataShapes": ["record", "collection"], + "intents": ["browse", "search", "filter", "sort", "edit", "relate", "bulk-act"], + "capabilities": ["table", "list", "cards", "record-detail", "generated-form", "relation-picker", "relation-panel", "pagination", "bulk-actions"], + "slots": ["cell-renderer", "record-renderer", "form-field", "toolbar", "footer"], + "events": ["state-change", "open-record", "submit", "link-record", "unlink-record", "bulk-action"], + "compatibleWith": ["app-kit-core", "app-kit-board", "app-kit-dashboard", "app-kit-calendar", "app-kit-workflow", "app-kit-event-studio"] + } + }, + "files": [ + { + "path": "registry/constructive/blocks/app-kit/data/types.ts", + "target": "src/blocks/app-kit/data/types.ts", + "type": "registry:lib" + }, + { + "path": "registry/constructive/blocks/app-kit/data/states.tsx", + "target": "src/blocks/app-kit/data/states.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/data/controls.tsx", + "target": "src/blocks/app-kit/data/controls.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/data/collections.tsx", + "target": "src/blocks/app-kit/data/collections.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/data/detail-form.tsx", + "target": "src/blocks/app-kit/data/detail-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/data/relations.tsx", + "target": "src/blocks/app-kit/data/relations.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/data/action-bars.tsx", + "target": "src/blocks/app-kit/data/action-bars.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/data/index.ts", + "target": "src/blocks/app-kit/data/index.ts", + "type": "registry:lib" + } + ] + }, + { + "name": "app-kit-board", + "type": "registry:block", + "title": "Constructive App Kit Board", + "description": "A typed controlled board and connected resource wrapper with semantic moves, keyboard alternatives, and mutation rollback.", + "docs": "Board movement is disabled until the host supplies an explicit semantic action. Pointer and keyboard moves use the same action contract, restore focus, and roll back optimistic state on denial or failure.\n\nGuide: https://constructive-io.github.io/blocks/blocks/app-kit/", + "dependencies": [ + "lucide-react" + ], + "meta": { + "constructive": { + "version": 1, + "family": "app-kit", + "kind": "view", + "boundary": "client", + "provider": "app-kit", + "dataShapes": ["collection", "board"], + "intents": ["plan", "prioritize", "move-records", "inspect"], + "capabilities": ["board", "typed-columns", "semantic-move", "keyboard-move", "optimistic-rollback"], + "slots": ["card"], + "events": ["open-record", "move-record", "move-denied"], + "compatibleWith": ["app-kit-core", "app-kit-data", "app-kit-dashboard", "app-kit-calendar", "app-kit-workflow", "app-kit-event-studio"] + } + }, + "files": [ + { + "path": "registry/constructive/blocks/app-kit/board/board.tsx", + "target": "src/blocks/app-kit/board/board.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/board/connected-board.tsx", + "target": "src/blocks/app-kit/board/connected-board.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/board/index.ts", + "target": "src/blocks/app-kit/board/index.ts", + "type": "registry:lib" + } + ] + }, + { + "name": "app-kit-dashboard", + "type": "registry:block", + "title": "Constructive App Kit Dashboard", + "description": "Explicit analytical widgets and a versioned, replaceable layout store on the accessible SVG and SSR foundation from TanStack Charts.", + "docs": "Dashboard widgets always use explicit analytical loaders; they never derive totals from a paginated collection page. Runtime customization is limited to an approved typed catalog, while layout persistence is replaceable.\n\nGuide: https://constructive-io.github.io/blocks/blocks/app-kit/", + "dependencies": [ + "@tanstack/charts@0.6.4", + "@tanstack/charts-scales@0.6.4", + "@tanstack/react-charts@0.6.4", + "d3-scale@4.0.2", + "lucide-react" + ], + "meta": { + "constructive": { + "version": 1, + "family": "app-kit", + "kind": "view", + "boundary": "client", + "provider": "app-kit", + "dataShapes": ["aggregate", "series", "breakdown"], + "intents": ["monitor", "analyze", "compare", "configure-layout"], + "capabilities": ["kpi", "bar-chart", "line-chart", "breakdown-table", "widget-catalog", "layout-store", "layout-persistence"], + "slots": ["widget"], + "events": ["layout-change"], + "compatibleWith": ["app-kit-core", "app-kit-data", "app-kit-board", "app-kit-calendar", "app-kit-workflow", "app-kit-event-studio"] + } + }, + "files": [ + { + "path": "registry/constructive/blocks/app-kit/dashboard/widgets.tsx", + "target": "src/blocks/app-kit/dashboard/widgets.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/dashboard/layout-store.ts", + "target": "src/blocks/app-kit/dashboard/layout-store.ts", + "type": "registry:lib" + }, + { + "path": "registry/constructive/blocks/app-kit/dashboard/dashboard.tsx", + "target": "src/blocks/app-kit/dashboard/dashboard.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/dashboard/connected-dashboard.tsx", + "target": "src/blocks/app-kit/dashboard/connected-dashboard.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/dashboard/persisted-dashboard.tsx", + "target": "src/blocks/app-kit/dashboard/persisted-dashboard.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/dashboard/index.ts", + "target": "src/blocks/app-kit/dashboard/index.ts", + "type": "registry:lib" + } + ] + }, + { + "name": "app-kit-calendar", + "type": "registry:block", + "title": "Constructive App Kit Calendar", + "description": "Localized controlled month and agenda views with explicit timezones and connected range-query loading.", + "docs": "The host owns record opening and editing, while the connected wrapper loads only the visible range. Recurrence, resource scheduling, and drag rescheduling are intentionally outside V1.\n\nGuide: https://constructive-io.github.io/blocks/blocks/app-kit/", + "dependencies": [ + "lucide-react" + ], + "meta": { + "constructive": { + "version": 1, + "family": "app-kit", + "kind": "view", + "boundary": "client", + "provider": "app-kit", + "dataShapes": ["temporal", "collection"], + "intents": ["schedule", "browse-by-date", "inspect"], + "capabilities": ["temporal", "month", "agenda", "range-query", "localization", "timezone"], + "events": ["range-change", "view-change", "open-record"], + "compatibleWith": ["app-kit-core", "app-kit-data", "app-kit-board", "app-kit-dashboard", "app-kit-workflow", "app-kit-event-studio"] + } + }, + "files": [ + { + "path": "registry/constructive/blocks/app-kit/calendar/calendar.tsx", + "target": "src/blocks/app-kit/calendar/calendar.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/calendar/connected-calendar.tsx", + "target": "src/blocks/app-kit/calendar/connected-calendar.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/calendar/index.ts", + "target": "src/blocks/app-kit/calendar/index.ts", + "type": "registry:lib" + } + ] + }, + { + "name": "app-kit-workflow", + "type": "registry:block", + "title": "Constructive App Kit Workflow UI", + "description": "Controlled and connected action surfaces, confirmation and input dialogs, bulk execution, and a multi-step stepper.", + "docs": "This root composes application and server actions into accessible interaction surfaces; it is not a durable workflow engine. Validation, permission failures, cancellation, and partial errors remain explicit.\n\nGuide: https://constructive-io.github.io/blocks/blocks/app-kit/", + "dependencies": [ + "lucide-react" + ], + "meta": { + "constructive": { + "version": 1, + "family": "app-kit", + "kind": "composition", + "boundary": "client", + "provider": "app-kit", + "dataShapes": ["record", "collection", "action-input"], + "intents": ["execute-action", "confirm", "collect-input", "bulk-execute", "guide-steps"], + "capabilities": ["action-button", "action-menu", "input-dialog", "confirmation-dialog", "bulk-execution", "stepper"], + "slots": ["action-input", "confirmation-body", "step-content"], + "events": ["action-start", "action-settled", "step-change", "cancel"], + "compatibleWith": ["app-kit-core", "app-kit-data", "app-kit-board", "app-kit-dashboard", "app-kit-calendar", "app-kit-event-studio"] + } + }, + "files": [ + { + "path": "registry/constructive/blocks/app-kit/workflow/actions.tsx", + "target": "src/blocks/app-kit/workflow/actions.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/workflow/connected-actions.tsx", + "target": "src/blocks/app-kit/workflow/connected-actions.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/workflow/stepper.tsx", + "target": "src/blocks/app-kit/workflow/stepper.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/workflow/index.ts", + "target": "src/blocks/app-kit/workflow/index.ts", + "type": "registry:lib" + } + ] + }, + { + "name": "app-kit-event-studio", + "type": "registry:block", + "title": "Constructive App Kit Event Studio", + "description": "An opt-in page-scale proof starter combining analytical, collection, board, calendar, relation, form, and action views.", + "docs": "Event Studio pairs with the supported b2b:storage blueprint recipe and demonstrates explicit analytical loaders, semantic board movement, localized range queries, searchable relations, permission-aware actions, and host-controlled URL state. Supply adapters generated from the final executable GraphQL schema.\n\nGuide: https://constructive-io.github.io/blocks/blocks/app-kit/", + "dependencies": [ + "lucide-react", + "zod" + ], + "meta": { + "constructive": { + "version": 1, + "family": "app-kit", + "kind": "starter", + "boundary": "mixed", + "provider": "app-kit", + "dataShapes": ["record", "collection", "aggregate", "temporal", "board"], + "intents": ["manage-events", "analyze", "schedule", "publish", "relate-people", "configure-views"], + "capabilities": ["event-studio", "dashboard", "collections", "record-detail", "generated-form", "relations", "board", "calendar", "actions", "url-state"], + "slots": ["record-opener", "dashboard-layout-store", "url-state-adapter"], + "events": ["view-change", "open-record", "move-session", "publish-session", "schedule-session", "link-person"], + "compatibleWith": ["app-kit-core", "app-kit-data", "app-kit-board", "app-kit-dashboard", "app-kit-calendar", "app-kit-workflow"] + } + }, + "files": [ + { + "path": "registry/constructive/blocks/app-kit/event-studio/definitions.ts", + "target": "src/blocks/app-kit/event-studio/definitions.ts", + "type": "registry:lib" + }, + { + "path": "registry/constructive/blocks/app-kit/event-studio/state.ts", + "target": "src/blocks/app-kit/event-studio/state.ts", + "type": "registry:lib" + }, + { + "path": "registry/constructive/blocks/app-kit/event-studio/event-studio.tsx", + "target": "src/blocks/app-kit/event-studio/event-studio.tsx", + "type": "registry:component" + }, + { + "path": "registry/constructive/blocks/app-kit/event-studio/index.ts", + "target": "src/blocks/app-kit/event-studio/index.ts", + "type": "registry:lib" + } + ] } ] } diff --git a/apps/blocks/src/app/blocks/app-kit/page.tsx b/apps/blocks/src/app/blocks/app-kit/page.tsx new file mode 100644 index 0000000..08cc046 --- /dev/null +++ b/apps/blocks/src/app/blocks/app-kit/page.tsx @@ -0,0 +1,373 @@ +import type { Metadata } from 'next'; + +import { Badge } from '@constructive-io/ui/badge'; + +import { ApplicationDocPagination } from '@/components/docs/application-doc-pagination'; +import { CodeBlock } from '@/components/docs/code-block'; +import { APP_KIT_CATALOG } from '@/lib/app-kit-catalog'; +import { registryAdd } from '@/lib/install-mode'; +import { OG_IMAGE, withBase } from '@/lib/site'; + +const TITLE = 'Constructive App Kit'; +const DESCRIPTION = + 'A source-installed application-composition layer for building arbitrary Constructive-native apps from typed resources, queries, actions, and domain-neutral views.'; + +const ROOT_INSTALLS = APP_KIT_CATALOG + .filter(({ name }) => name !== 'app-kit-event-studio') + .map(({ name }) => registryAdd(name)) + .join('\n'); + +const CORE_ENTRYPOINTS = `// Server-safe definitions and build-time validation +import { + defineAction, + defineQuery, + defineResource, + validateAppResource, + type AppScope, +} from '@/blocks/app-kit/core' + +// Client-only TanStack Query runtime +import { + AppKitProvider, + useAppAction, + useAppQuery, +} from '@/blocks/app-kit/core/runtime'`; + +const SCOPE_EXAMPLE = `const scope: AppScope = { + endpointId: 'data', + databaseId, + sessionPartition: authenticatedSession.id, + organizationId, + schemaRevision, + securityRevision, +} + +// Tokens, cookies, headers, and CSRF values stay in the injected transport. +// They never belong in AppScope, definitions, URLs, stores, or query keys.`; + +const VIEW_CONTRACTS = [ + ['Records', 'AppDataTable · AppDataList · AppDataCards · AppRecordDetail · AppRecordForm', 'ConnectedAppDataTable · ConnectedAppDataList · ConnectedAppDataCards · ConnectedAppRecordDetail · ConnectedAppRecordForm'], + ['Relations', 'AppRelationPicker · AppRelationPanel', 'ConnectedAppRelationPicker · ConnectedAppRelationPanel'], + ['Board', 'AppBoard', 'ConnectedAppBoard'], + ['Dashboard', 'AppDashboard', 'ConnectedAppDashboard · PersistedAppDashboard'], + ['Calendar', 'AppCalendar', 'ConnectedAppCalendar'], + ['Actions', 'AppActionButton · AppActionMenu · AppActionDialog · AppBulkActionBar', 'ConnectedAppActionButton · ConnectedAppActionMenu · ConnectedAppActionDialog · ConnectedAppBulkActionBar'], +] as const; + +const SELECTIONS = [ + { + shape: 'Records and collections', + geometry: 'Table, list, cards, detail, forms, relations', + root: 'app-kit-data', + }, + { + shape: 'Categorical state', + geometry: 'Board columns with a semantic move action', + root: 'app-kit-board', + }, + { + shape: 'Analytical results', + geometry: 'KPI, bar, line, and breakdown widgets', + root: 'app-kit-dashboard', + }, + { + shape: 'Time-bounded records', + geometry: 'Localized month and agenda views', + root: 'app-kit-calendar', + }, + { + shape: 'Application commands', + geometry: 'Buttons, menus, dialogs, bulk actions, and steppers', + root: 'app-kit-workflow', + }, +] as const; + +export default function AppKitPage() { + return ( +
+
+

Application composition

+

+ Constructive App Kit +

+

+ {DESCRIPTION} Console Kit, Sheets, Stack navigation, and platform feature + packs remain optional capabilities that you add when the application + needs them. +

+
+ +
+
+
+

+ One resource model, independently installed views +

+

+ Server-safe definitions describe identity, final GraphQL fields, + loaders, forms, relations, and available actions. Client entrypoints + bind them to TanStack Query and controlled views; credentials stay in + host closures and never enter definitions, URLs, stores, or cache keys. +

+
+ +
    + {[ + ['01', 'Validate', 'Reconcile _meta database facts with final executable GraphQL introspection during generation or build.'], + ['02', 'Bind', 'Partition loaders and mutations with endpoint, database, session, organization, and schema revision scope.'], + ['03', 'Compose', 'Choose controlled view geometry from the data shape and workflow, then let the host own routing and shareable state.'], + ].map(([step, title, body]) => ( +
  1. + {step} +

    {title}

    +

    {body}

    +
  2. + ))} +
+
+ +
+
+

+ Public contract reference +

+

+ Definitions and validation stay importable from a server module. The + runtime has its own client entrypoint, so a generator can validate a + resource without pulling React or a provider into server code. +

+
+ +
+ {CORE_ENTRYPOINTS} + {SCOPE_EXAMPLE} +
+ +
+ + + + + + + + + + {VIEW_CONTRACTS.map(([family, controlled, connected]) => ( + + + + + + ))} + +
FamilyControlled componentConnected wrapper
{family}{controlled}{connected}
+
+ +
+
+

Resource definition

+

+ defineResource(){' '} + binds database and final GraphQL names, ordered identity fields, + display fields, relations, list/detail queries, operation-specific + forms, and semantic actions. +

+
+
+

Query definition

+

+ defineQuery(){' '} + receives typed input, the active scope, and an AbortSignal. It may + return a record, collection page, relation search page, range, total, + or purpose-built analytical payload. +

+
+
+

Action definition

+

+ defineAction(){' '} + owns transformed validation, input-aware presentation policy, + confirmation, abortable execution, targeted invalidation, and + optional optimistic apply, settle, and rollback behavior. +

+
+
+
+ +
+
+

+ Install only the roots the application uses +

+

+ This catalog is projected from the same versioned{' '} + meta.constructive{' '} + contract validated during registry compilation and pinned by the + Constructive Blocks skill. +

+
+ +
    + {APP_KIT_CATALOG.map((item) => ( +
  • +
    +
    +

    {item.title}

    +

    + @constructive/{item.name} +

    +
    +
    + {item.metadata.kind} + {item.metadata.boundary} +
    +
    +

    + {item.description} +

    +

    + {item.metadata.capabilities.join(' · ')} +

    +
  • + ))} +
+ + + {ROOT_INSTALLS} + +
+ +
+
+

+ Select by shape, geometry, and workflow +

+

+ Department names do not determine composition. Start from what the + query returns, how people need to inspect it, and which server action + changes it. +

+
+
+ + + + + + + + + + {SELECTIONS.map((selection) => ( + + + + + + ))} + +
Data shapePresentation geometryRoot
{selection.shape}{selection.geometry}{selection.root}
+
+
+ +
+
+

+ Schema and form compatibility +

+

+ validateAppResource(){' '} + compares the declared PostgreSQL names and facts with final GraphQL + type, field, relation, identity, nullability, list shape, enum, and + operation evidence. Its result reports separate read, create, update, + and delete capabilities plus field-level issues; it never grants an + operation or introspects at runtime. +

+
+
    +
  • + String, integer, float, boolean, date, datetime, enum, and scalar-array + fields have generated presentation. JSON and unknown custom scalars + remain read-only until a renderer is supplied. +
  • +
  • + Resource forms declare ordered create and update fields separately. + Required presentation may tighten a nullable field for one workflow, + but it cannot weaken database or executable-schema requirements. +
  • +
  • + Missing or ambiguous identity disables writes. Composite identity + keeps its declared field order, and relation pickers link existing + records through server search rather than nested creation. +
  • +
+
+ +
+
+
+

Event Studio starter

+ Page-scale recipe +
+

+ Event Studio composes org-scoped programs, sessions, people, venues, + and the explicit session_people relation into analytical widgets, a + semantic session board, month and agenda schedules, searchable + collections, details, forms, relations, and publish or schedule + actions. The paired skill recipe provisions through the supported B2B + blueprint path, so the starter contains no raw SQL. +

+

+ It is an opt-in proof application and integration fixture. No App + Kit capability root depends on it, and ordinary application + composition should select the smaller roots above. +

+ + {registryAdd('app-kit-event-studio')} + +
+
+ +
+

Runtime boundaries

+
    +
  • + Remote state belongs to TanStack Query. AppScope changes create a new + cache partition, cancellation uses AbortSignal, and actions invalidate + explicit cross-view targets. +
  • +
  • + URL or controlled props own shareable view, filter, and selection + state. Local reducers own transient interaction; App Kit adds no global + Zustand store. +
  • +
  • + Runtime introspection is out of scope. Agents validate generated + contracts against _meta and the final inflected GraphQL schema before + the application ships. +
  • +
  • + V1 has no subscriptions or interval polling. Action invalidation, + manual refresh, and focus or reconnect refetching provide freshness. +
  • +
+
+
+ + +
+ ); +} + +export const metadata: Metadata = { + title: TITLE, + description: DESCRIPTION, + alternates: { canonical: withBase('/blocks/app-kit') }, + openGraph: { + title: TITLE, + description: DESCRIPTION, + url: withBase('/blocks/app-kit'), + images: [OG_IMAGE], + }, +}; diff --git a/apps/blocks/src/app/blocks/page.tsx b/apps/blocks/src/app/blocks/page.tsx index fe79855..23bcb64 100644 --- a/apps/blocks/src/app/blocks/page.tsx +++ b/apps/blocks/src/app/blocks/page.tsx @@ -12,6 +12,12 @@ const TITLE = 'Setup'; const DESCRIPTION = 'Choose npm package distribution or source installation through the shadcn CLI.'; const APPLICATION_CATALOG = [ + { + href: '/blocks/app-kit', + title: 'App Kit', + description: + 'Compose arbitrary Constructive-native apps from typed resources, queries, actions, and domain-neutral views.', + }, { href: '/blocks/features', title: 'Feature packs', @@ -74,9 +80,9 @@ export default function SetupPage() { Application blocks

- Start with a capability-aligned feature pack, add a composed - workflow block, or install the full Next.js console with its - route-neutral app shell and dynamic data explorer. + Start with App Kit for application composition, then add focused + feature packs, workflow blocks, or Console Kit when the application + needs those platform capabilities.