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
1 change: 1 addition & 0 deletions .agents/skills/constructive-blocks/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ to endpoints, sessions, adapters, or tenant data.
| Application shell (sidebar rail, drawer, views) | `workspace-kit` |
| AI or agent UI | `ai` |
| Complete agent workspace (chat, integrations, skills, agent canvas) | `agents-builder` |
| Coding agent workspace (parallel sessions, diff review, file and browser tabs, terminal) | `coding-agent` |
| 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` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ live `shadcn@latest search` and `view`.
| Button, form, overlay, navigation, or layout | A primitive name, `form-kit`, `overlay-kit`, `layout-kit`, `workspace-kit` | Installed source owns presentation; the host owns application behavior. |
| AI chat or agent traces | `ai` | Presentational only. The host owns the model runtime, streaming reducer, tools, IPC, and persistence. |
| Agent workspace template | `agents-builder` | Installed source owns the chat, integrations, skills, and agent-canvas views plus scripted playback; the host supplies workspace data and owns OAuth, persistence, routing, and model runs. |
| Coding agent workspace | `coding-agent` | Installed source owns session tabs and the sidebar layout, the transcript and composer, diff review with line comments, file and browser tabs, the terminal dock, and scripted playback; the host supplies projects and sessions and owns the agent runtime, the checkout, shell commands, browser previews, and persistence. |
| 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. |
Expand Down Expand Up @@ -72,6 +73,7 @@ and dependency closure. The public documentation provides richer examples:
- Org Chart: `https://constructive-io.github.io/blocks/blocks/org-chart/`
- Storage: `https://constructive-io.github.io/blocks/blocks/storage/` (Storage Browser at `/blocks/storage/browser/`, the feature pack at `/blocks/features/storage/`)
- 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/`
- 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/`
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Use Node 24 LTS and pnpm 10.28.0, then run `pnpm check`, `pnpm build:pages`, and
on `motion/react` or CSS, and keep npm package peers usable on React 18.
jsdom lacks `CSS.escape`, which React's ViewTransition calls; polyfill it in
tests that render these boundaries.
- Workspace templates (`agents-builder`, `billing-account`, `billing-console`)
- Workspace templates (`agents-builder`, `coding-agent`, `billing-account`, `billing-console`)
share `packages/ui/src/components/workspace-kit` (shell, sidebar rail and
drawer, view frames, filter pills, surfaces such as `Panel` and
`TableSurface`, and the pan-and-zoom canvas used by Agents Builder and
Expand Down
23 changes: 23 additions & 0 deletions apps/blocks/src/app/blocks/coding-agent/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('coding-agent')!;

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

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

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

export default function CodingAgentPreviewPage() {
return (
<>
<h1 className="sr-only">Coding Agent live preview</h1>
{/* The preview reads its example from the query string, which only exists in the browser. */}
<Suspense fallback={<div className="h-dvh bg-background" />}>
<ApplicationBlockShowcaseCanvas name="coding-agent" />
</Suspense>
</>
);
}

export const metadata: Metadata = {
alternates: { canonical: withBase('/blocks/coding-agent') },
robots: { follow: false, index: false },
};
1 change: 1 addition & 0 deletions apps/blocks/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

@source '../';
@source '../../../../packages/ui/src/components/agents-builder';
@source '../../../../packages/ui/src/components/coding-agent';
@source '../../../../packages/ui/src/components/data-rooms';
@source '../../../../packages/ui/src/components/workspace-kit';
@source '../../../../packages/ui/src/components/org-chart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ export function ApplicationBlockDocsPage({
</CodeBlock>
</DocSection>

{block.guides?.map((guide) => (
<DocSection description={guide.description} id={guide.id} key={guide.id} title={guide.title}>
<CodeBlock label={guide.title} language="tsx">
{guide.example}
</CodeBlock>
</DocSection>
))}

<DocSection
description={block.state.description}
id="state"
Expand All @@ -148,15 +156,11 @@ export function ApplicationBlockDocsPage({
<GuidanceList items={block.composition} />
</DocSection>

{block.buildingBlocks?.length ? (
<DocSection
description="Each of these installs with billing-kit and works outside the template: pass data and callbacks, and wrap them in BillingFormatProvider for locale, time zone, and clock."
id="building-blocks"
title="Building blocks"
>
{block.buildingBlocks ? (
<DocSection description={block.buildingBlocks.description} id="building-blocks" title="Building blocks">
<PublicContract
block={block}
rows={block.buildingBlocks}
rows={block.buildingBlocks.components}
caption={`Components ${block.title} is built from.`}
columns={['Component', 'Group', 'What it shows']}
/>
Expand All @@ -170,6 +174,12 @@ export function ApplicationBlockDocsPage({
>
<GuidanceList
items={[
...(block.examples?.length
? [
'Choose an example from the menu above the live preview; it reloads the same source opened that way.',
...block.examples.map((example) => `${example.label}: ${example.description}`),
]
: []),
'Use the breakpoint controls to inspect the same source at desktop, tablet, and mobile widths.',
'Open the full-screen preview when you need to test menus, sheets, panning, or dense table interactions without documentation chrome.',
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import type { ApplicationBlockDoc } from '@/lib/application-blocks';
const AgentsBuilderPreview = dynamic(() =>
import('./agents-builder-preview').then((module) => module.AgentsBuilderPreview),
);
const CodingAgentPreview = dynamic(() =>
import('./coding-agent-preview').then((module) => module.CodingAgentPreview),
);
const DataRoomsPreview = dynamic(() =>
import('./data-rooms-preview').then((module) => module.DataRoomsPreview),
);
Expand All @@ -30,6 +33,7 @@ export function ApplicationBlockShowcaseCanvas({
name: ApplicationBlockDoc['name'];
}) {
if (name === 'agents-builder') return <AgentsBuilderPreview />;
if (name === 'coding-agent') return <CodingAgentPreview />;
if (name === 'data-rooms') return <DataRoomsPreview />;
if (name === 'billing-account') return <BillingAccountPreview />;
if (name === 'billing-console') return <BillingConsolePreview />;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
'use client';

import { useId, useState } from 'react';

import { Badge } from '@constructive-io/ui/badge';
import { Field } from '@constructive-io/ui/field';
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@constructive-io/ui/select';

import { LivePreview } from '@/components/docs/live-preview';
import type { ApplicationBlockExample } from '@/lib/application-blocks';

export type LivePreviewBlock = Readonly<{
previewDescription: string;
previewHeight: number;
title: string;
/** Named ways to open the preview, passed to the preview route as `?example=`. The first is the default. */
examples?: readonly ApplicationBlockExample[];
}>;

export function ApplicationBlockShowcasePreview({
Expand All @@ -17,16 +24,46 @@ export function ApplicationBlockShowcasePreview({
block: LivePreviewBlock;
previewPath: string;
}) {
const controlId = useId();
const examples = block.examples ?? [];
const [example, setExample] = useState(examples[0]?.value);
const selected = examples.find((option) => option.value === example);

return (
<LivePreview
controls={
examples.length > 1 ? (
<Field className="w-full sm:w-56" htmlFor={controlId} label="Example">
<Select
value={example}
onValueChange={(value) => {
if (examples.some((option) => option.value === value)) setExample(value as string);
}}
>
<SelectTrigger id={controlId} size="lg">
<SelectValue>{() => selected?.label ?? 'Choose an example'}</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectGroup>
{examples.map((option) => (
<SelectItem className="min-h-11 sm:min-h-11" key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
) : undefined
}
defaultViewport="mobile"
frameSlot="application-block-preview-frame"
height={block.previewHeight}
meta={<Badge variant="secondary">Host-controlled</Badge>}
name={block.title}
responsive
slot="application-block-showcase-preview"
src={previewPath}
src={example ? `${previewPath}?example=${encodeURIComponent(example)}` : previewPath}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
'use client';

import { useSearchParams } from 'next/navigation';
import { useState } from 'react';

import {
CODING_AGENT_DEMO,
CodingAgent,
type CodingAgentAction,
type CodingAgentLayout,
} from '@/components/ui/coding-agent';
import type { CodingAgentExample } from '@/lib/application-blocks/coding-agent';

/** The docs preview's examples, picked with `?example=`; each opens the same demo data a different way. */
const EXAMPLES: Record<CodingAgentExample, { layout: CodingAgentLayout; sessionId: string; hint: string }> = {
tabs: {
layout: 'tabs',
sessionId: 'session-invites',
hint: 'Watch the first session run, approve its test command, review the diff, or answer the question waiting in another tab.',
},
sidebar: {
layout: 'sidebar',
sessionId: 'session-invites',
hint: 'Sessions sit under their project with their live status; collapse the sidebar to a rail or switch back to tabs from its footer.',
},
'sub-agents': {
layout: 'tabs',
sessionId: 'session-security',
hint: 'The lead agent runs three workers in parallel: open one from its card, approve the command one of them asks for, or follow them in the Agents tab.',
},
};

function describeCodingAgentAction(action: CodingAgentAction) {
switch (action.type) {
case 'new-session':
return `The host would start a session ${action.location === 'worktree' ? 'in a new worktree' : 'in the checkout'}.`;
case 'send':
return 'The host would send the message to the agent runtime.';
case 'stop':
return 'The host would stop the running agent.';
case 'stop-run':
return 'The host would stop that agent and everything it started.';
case 'pause-run':
case 'resume-run':
return `The host would ${action.type === 'pause-run' ? 'pause' : 'resume'} that agent.`;
case 'steer-run':
return 'The host would pass the message to that agent mid-run.';
case 'retry-run':
return 'The host would run that agent again.';
case 'permission':
return action.decision === 'deny'
? `The host would tell the agent not to run ${action.command}.`
: `The host would let the agent run ${action.command}.`;
case 'open-pull-request':
return `The host would open pull request #${action.number}.`;
case 'create-pull-request':
return `The host would open a pull request for ${action.worktreeId}.`;
case 'rerun-checks':
return `The host would re-run ${action.checkIds.length === 1 ? 'the failed check' : `${action.checkIds.length} failed checks`} on ${action.worktreeId}.`;
case 'answer':
return 'The host would hand the answer back to the waiting agent.';
case 'comment':
return `The host would attach a comment on ${action.path}:${action.line} to the next message.`;
case 'terminal-command':
return `The host would run ${action.command} in the session's shell.`;
case 'open-file':
return `The host would read ${action.path} from the checkout.`;
case 'open-url':
return `The host would load ${action.url} in the browser tab.`;
case 'layout':
return `Sessions now show as ${action.layout === 'tabs' ? 'tabs' : 'a sidebar'}.`;
default:
return `The host received ${action.type}.`;
}
}

export function CodingAgentPreview() {
const requested = useSearchParams().get('example');
const example = requested && requested in EXAMPLES ? EXAMPLES[requested as CodingAgentExample] : EXAMPLES.tabs;
const [message, setMessage] = useState(example.hint);

return (
<div
className="flex h-dvh min-h-[640px] w-full flex-col"
data-slot="application-block-showcase-canvas"
>
<CodingAgent
className="min-h-0 flex-1"
data={CODING_AGENT_DEMO}
defaultLayout={example.layout}
defaultActiveSessionId={example.sessionId}
onAction={(action) => setMessage(describeCodingAgentAction(action))}
/>
<p
aria-live="polite"
className="shrink-0 truncate border-t border-border bg-background px-3 py-1.5 text-xs text-muted-foreground"
role="status"
>
{message}
</p>
</div>
);
}
2 changes: 1 addition & 1 deletion apps/blocks/src/components/site/registry-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function RegistryShell({ children }: { children: ReactNode }) {
const isConsoleKitProof = pathname === '/__integration/console-kit';
const isStandalonePreview =
isConsoleKitProof ||
/^\/blocks\/(?:features\/[^/]+|org-chart|storage\/browser|agents-builder|data-rooms|billing\/(?:account|console)|sheets|schema-builder|documents|create)\/preview\/?$/.test(pathname);
/^\/blocks\/(?:features\/[^/]+|org-chart|storage\/browser|agents-builder|coding-agent|data-rooms|billing\/(?:account|console)|sheets|schema-builder|documents|create)\/preview\/?$/.test(pathname);
const menuButtonRef = useRef<HTMLButtonElement | null>(null);
const sidebarRef = useRef<HTMLElement | null>(null);
const previouslyFocusedRef = useRef<HTMLElement | null>(null);
Expand Down
Loading
Loading