Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .agents/skills/constructive-blocks/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ to endpoints, sessions, adapters, or tenant data.
| Secure document rooms shared inside and outside an organization (roles, folder shares, agreements, Q&A, insights) | `data-rooms` |
| Global commands and workflows | `command-palette` |
| Metadata-driven data grid | `sheets` |
| List, detail, and form screens for an app's own tables (relations, many-to-many, delete) | `resource-kit` |
| Sign-in, sign-up, check-email, verify-email, and password reset screens | `auth-pages` |
| An app's home screen with counts and recent records | `dashboard-tiles` |
| PostgreSQL schema workspace | `schema-builder` |
| Reporting hierarchy | `org-chart` |
| Object storage workspace | `storage-browser` or a focused storage leaf |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ live `shadcn@latest search` and `view`.
| Secure document rooms | `data-rooms` | Installed source owns the rooms, documents, people, Q&A, insights, settings, and access-map views, the access explanations and previews, optimistic local updates, and scripted assistant answers; the host supplies workspace data and owns storage, delivery, authorization, the activity log, and assistant runs. |
| Searchable application commands | `command-palette` | The npm package owns the headless engine; installed source owns the dialog and task presentation; the host owns routing and actions. |
| Arbitrary application-table CRUD | `sheets` | Installed source owns the grid; the data runtime interprets current `_meta`; the host owns endpoint and session discovery. |
| Generated app screens for known tables | `resource-kit` | Installed source owns the list, detail, form, relation picker, and delete views, query caching, and invalidation after writes; the host supplies resource definitions, each table's source (list, get, create, update, remove, link), and the router. |
| Public account screens | `auth-pages` | Installed source owns the panels' validation, pending, and result states; the host owns routing, sessions, tokens, and every auth call. |
| App home screen | `dashboard-tiles` | Installed source owns the tiles and their layout and reads counts and recent rows through `resource-kit` sources; the host chooses what to show. |
| Database schema editing | `schema-builder` | Installed source owns the workspace; a host adapter performs control-plane operations and confirmation workflows. |
| Reporting relationships | `org-chart` | The controlled block owns hierarchy presentation; the host loads and persists reporting lines. |
| Object storage management | `storage-browser` or a storage leaf | Blocks are controlled and transport-neutral; the host filters, authorizes, fetches, uploads, and mutates objects. |
Expand Down Expand Up @@ -75,6 +78,7 @@ and dependency closure. The public documentation provides richer examples:
- Agents Builder: `https://constructive-io.github.io/blocks/blocks/agents-builder/`
- Coding Agent: `https://constructive-io.github.io/blocks/blocks/coding-agent/`
- Data Rooms: `https://constructive-io.github.io/blocks/blocks/data-rooms/`
- Resource Kit, Auth Pages, Dashboard Tiles: `https://constructive-io.github.io/blocks/blocks/resource-kit/`, `/blocks/auth-pages/`, `/blocks/dashboard-tiles/`
- Billing: `https://constructive-io.github.io/blocks/blocks/billing/` (Billing Account at `/blocks/billing/account/`, Billing Console at `/blocks/billing/console/`)
- Feature packs: `https://constructive-io.github.io/blocks/blocks/features/`
- Console Kit: `https://constructive-io.github.io/blocks/blocks/console-kit/`
Expand Down
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@ jobs:
with:
path: .artifacts/pages

- name: Upload the registry for registry-dist
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
uses: actions/upload-artifact@v7
with:
name: registry-dist
path: .artifacts/pages/r
if-no-files-found: error
retention-days: 1

deploy:
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
needs: [quality, delivery]
Expand All @@ -124,5 +133,39 @@ jobs:
curl --fail --silent --show-error --retry 8 --retry-all-errors --retry-delay 5 "$blocks_pages_base/r/registry.json" | grep -q '"items"'
curl --fail --silent --show-error --retry 8 --retry-all-errors --retry-delay 5 "$blocks_pages_base/llms.txt" | grep -q 'npx skills add constructive-io/blocks'

# Commits each main build's registry, the r/ that Pages serves, to the
# registry-dist branch, so consumers can pin it by commit on
# raw.githubusercontent.com (see README). Its own job, so only it can write
# to the repository, and its own concurrency group, so two runs never push
# to the branch at once.
registry-dist:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: [quality, delivery]
runs-on: ubuntu-latest
timeout-minutes: 10
concurrency:
group: registry-dist
cancel-in-progress: false
permissions:
contents: write
steps:
# Keeps the job's token in the git config: the script pushes with it.
- name: Checkout
uses: actions/checkout@v7

- name: Download the registry
uses: actions/download-artifact@v8
with:
name: registry-dist
path: ${{ runner.temp }}/r

- name: Commit the registry to registry-dist
env:
GIT_AUTHOR_NAME: github-actions[bot]
GIT_AUTHOR_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
GIT_COMMITTER_NAME: github-actions[bot]
GIT_COMMITTER_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
run: scripts/publish-registry-dist.sh "$RUNNER_TEMP/r" "$GITHUB_SHA"

# Package tags validate publishable tarballs and Pages inputs, but npm
# publication remains a manual maintainer action.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ The documentation site is published at
<https://constructive-io.github.io/blocks/>. Registry JSON is served from
`https://constructive-io.github.io/blocks/r/{name}.json`.

That URL always serves the latest build of `main`. For a registry that never
changes under you, such as the one an app template is composed from, pin a
commit of the `registry-dist` branch instead:
`https://raw.githubusercontent.com/constructive-io/blocks/<dist commit>/r/{name}.json`.
CI commits each `main` build's registry there as `registry from <blocks sha>`,
so `git log origin/registry-dist --grep <blocks sha>` finds the commit for a
blocks commit. A build whose registry didn't change adds no commit; the one
before it serves the same files.

The npm and registry distributions are independent. Console Kit source installs
expect an existing shadcn project; initialize one if the application does not
already contain `components.json`:
Expand Down
23 changes: 23 additions & 0 deletions apps/blocks/src/app/blocks/auth-pages/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Metadata } from 'next';

import { ApplicationBlockDocsPage } from '@/components/application-block-showcase/application-block-docs-page';
import { getApplicationBlock } from '@/lib/application-blocks';
import { OG_IMAGE, withBase } from '@/lib/site';

const block = getApplicationBlock('auth-pages')!;

export default function AuthPagesPage() {
return <ApplicationBlockDocsPage block={block} />;
}

export const metadata: Metadata = {
title: block.title,
description: block.description,
alternates: { canonical: withBase('/blocks/auth-pages') },
openGraph: {
title: block.title,
description: block.description,
url: withBase('/blocks/auth-pages'),
images: [OG_IMAGE],
},
};
18 changes: 18 additions & 0 deletions apps/blocks/src/app/blocks/auth-pages/preview/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Metadata } from 'next';

import { ApplicationBlockShowcaseCanvas } from '@/components/application-block-showcase/application-block-showcase-canvas';
import { withBase } from '@/lib/site';

export default function AuthPagesPreviewPage() {
return (
<>
<h1 className="sr-only">Auth Pages live preview</h1>
<ApplicationBlockShowcaseCanvas name="auth-pages" />
</>
);
}

export const metadata: Metadata = {
alternates: { canonical: withBase('/blocks/auth-pages') },
robots: { follow: false, index: false },
};
23 changes: 23 additions & 0 deletions apps/blocks/src/app/blocks/dashboard-tiles/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Metadata } from 'next';

import { ApplicationBlockDocsPage } from '@/components/application-block-showcase/application-block-docs-page';
import { getApplicationBlock } from '@/lib/application-blocks';
import { OG_IMAGE, withBase } from '@/lib/site';

const block = getApplicationBlock('dashboard-tiles')!;

export default function DashboardTilesPage() {
return <ApplicationBlockDocsPage block={block} />;
}

export const metadata: Metadata = {
title: block.title,
description: block.description,
alternates: { canonical: withBase('/blocks/dashboard-tiles') },
openGraph: {
title: block.title,
description: block.description,
url: withBase('/blocks/dashboard-tiles'),
images: [OG_IMAGE],
},
};
18 changes: 18 additions & 0 deletions apps/blocks/src/app/blocks/dashboard-tiles/preview/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Metadata } from 'next';

import { ApplicationBlockShowcaseCanvas } from '@/components/application-block-showcase/application-block-showcase-canvas';
import { withBase } from '@/lib/site';

export default function DashboardTilesPreviewPage() {
return (
<>
<h1 className="sr-only">Dashboard Tiles live preview</h1>
<ApplicationBlockShowcaseCanvas name="dashboard-tiles" />
</>
);
}

export const metadata: Metadata = {
alternates: { canonical: withBase('/blocks/dashboard-tiles') },
robots: { follow: false, index: false },
};
23 changes: 23 additions & 0 deletions apps/blocks/src/app/blocks/resource-kit/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Metadata } from 'next';

import { ApplicationBlockDocsPage } from '@/components/application-block-showcase/application-block-docs-page';
import { getApplicationBlock } from '@/lib/application-blocks';
import { OG_IMAGE, withBase } from '@/lib/site';

const block = getApplicationBlock('resource-kit')!;

export default function ResourceKitPage() {
return <ApplicationBlockDocsPage block={block} />;
}

export const metadata: Metadata = {
title: block.title,
description: block.description,
alternates: { canonical: withBase('/blocks/resource-kit') },
openGraph: {
title: block.title,
description: block.description,
url: withBase('/blocks/resource-kit'),
images: [OG_IMAGE],
},
};
18 changes: 18 additions & 0 deletions apps/blocks/src/app/blocks/resource-kit/preview/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Metadata } from 'next';

import { ApplicationBlockShowcaseCanvas } from '@/components/application-block-showcase/application-block-showcase-canvas';
import { withBase } from '@/lib/site';

export default function ResourceKitPreviewPage() {
return (
<>
<h1 className="sr-only">Resource Kit live preview</h1>
<ApplicationBlockShowcaseCanvas name="resource-kit" />
</>
);
}

export const metadata: Metadata = {
alternates: { canonical: withBase('/blocks/resource-kit') },
robots: { follow: false, index: false },
};
3 changes: 3 additions & 0 deletions apps/blocks/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
@source '../../../../packages/ui/src/components/billing-kit';
@source '../../../../packages/ui/src/components/billing-account';
@source '../../../../packages/ui/src/components/billing-console';
@source '../../../../packages/ui/src/components/resource-kit';
@source '../../../../packages/ui/src/components/auth-pages';
@source '../../../../packages/ui/src/components/dashboard-tiles';

html {
scroll-padding-top: 4.5rem;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use client';

import { LayoutDashboard } from 'lucide-react';
import { type ReactNode, useState } from 'react';

import {
CRM_DEMO_ICONS,
CRM_DEMO_NOW,
createCrmDemo,
MemoryRoute,
ResourceKitProvider,
ResourceNav,
useMemoryRouter,
} from '@/components/ui/resource-kit';
import { NavIcon, NavRow, NavSection, SidebarFrame, WorkspaceShell } from '@/components/ui/workspace-kit';

/**
* The CRM demo inside the workspace shell, routed by component state the way
* a host routes it by URL: `/` for home, `/<resource>` for a list, and
* `/<resource>/<id>` for a record.
*/
export function AppKitPreviewShell({ initialPath, home }: { initialPath: string; home?: ReactNode }) {
const { path, setPath, router } = useMemoryRouter(initialPath);
const [resources] = useState(() => createCrmDemo({ latency: 200 }));

return (
<div className="flex h-dvh min-h-[640px] w-full flex-col" data-slot="application-block-showcase-canvas">
<ResourceKitProvider router={router} resources={resources} icons={CRM_DEMO_ICONS} now={CRM_DEMO_NOW}>
<WorkspaceShell
className="min-h-0 flex-1"
sidebar={({ mode, collapsed, onCollapsedChange, onNavigate }) => {
const rail = mode === 'rail' && collapsed;
return (
<SidebarFrame
label="Acme CRM"
menu={<span className="min-w-0 flex-1 truncate px-1.5 text-sm font-medium">{rail ? 'A' : 'Acme CRM'}</span>}
collapsed={collapsed}
onCollapsedChange={onCollapsedChange}
drawer={mode === 'drawer'}
onClose={onNavigate}
>
<nav aria-label="Acme CRM" className="flex w-full flex-col gap-3">
{home ? (
<NavSection collapsed={rail}>
<NavRow
label="Overview"
collapsed={rail}
active={path === '/'}
leading={<NavIcon icon={LayoutDashboard} active={path === '/'} />}
onClick={() => {
setPath('/');
onNavigate();
}}
/>
</NavSection>
) : null}
<ResourceNav collapsed={rail} activeHref={path} onNavigate={onNavigate} title={home ? 'Records' : undefined} />
</nav>
</SidebarFrame>
);
}}
>
<MemoryRoute path={path} home={home} />
</WorkspaceShell>
</ResourceKitProvider>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ const OrgChartPreview = dynamic(() =>
const StorageBrowserPreview = dynamic(() =>
import('./storage-browser-preview').then((module) => module.StorageBrowserPreview),
);
const ResourceKitPreview = dynamic(() =>
import('./resource-kit-preview').then((module) => module.ResourceKitPreview),
);
const AuthPagesPreview = dynamic(() =>
import('./auth-pages-preview').then((module) => module.AuthPagesPreview),
);
const DashboardTilesPreview = dynamic(() =>
import('./dashboard-tiles-preview').then((module) => module.DashboardTilesPreview),
);

export function ApplicationBlockShowcaseCanvas({
name,
Expand All @@ -37,6 +46,9 @@ export function ApplicationBlockShowcaseCanvas({
if (name === 'data-rooms') return <DataRoomsPreview />;
if (name === 'billing-account') return <BillingAccountPreview />;
if (name === 'billing-console') return <BillingConsolePreview />;
if (name === 'resource-kit') return <ResourceKitPreview />;
if (name === 'auth-pages') return <AuthPagesPreview />;
if (name === 'dashboard-tiles') return <DashboardTilesPreview />;
return name === 'org-chart' ? (
<OrgChartPreview />
) : (
Expand Down
Loading
Loading