Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
7601199
feat(ui): adapter layer (RFC 0001) — builtin extraction for all Tier-…
mattboon Jul 26, 2026
defde97
feat(ui): composition-rules port — ref forwarding + spread + asChild …
mattboon Jul 26, 2026
3adbc4b
feat(chat): Batch A — additive rules-port for picker cluster (ref for…
mattboon Jul 26, 2026
1f63e3b
feat(chat): Batch C — additive rules-port for message + parts family …
mattboon Jul 26, 2026
accd11f
fix(ui): complete ref-forwarding on Popover/DropdownMenu/Command part…
mattboon Jul 26, 2026
e4e22a7
feat(chat): Batch B/attachments — additive rules-port for composer + …
mattboon Jul 26, 2026
e60388b
feat(chat): container batch — additive rules-port for sidebar + agent…
mattboon Jul 26, 2026
2fc3e24
feat(chat): additive canonical hook names — useChatScroll, useChatInp…
mattboon Jul 26, 2026
f428baa
feat(chat): add useMessageBranches hook (RFC 2980, was the one truly-…
mattboon Jul 26, 2026
6e192e6
docs(chat): @deprecated on Composer* aliases + useStickToBottom (back…
mattboon Jul 26, 2026
03a8f0f
test(chat): black-box backward-compat contract guard
mattboon Jul 26, 2026
e9197ff
feat(chat): useChatInput L3 headless hook + export canonical hooks fr…
mattboon Jul 30, 2026
5d7953f
feat(chat): useChatScroll superset — viewportRef, scrollToStart/End/M…
mattboon Jul 30, 2026
3b6d055
feat(chat): reshape internals slice — ChatInput action leaves take ch…
mattboon Jul 30, 2026
0097298
feat(chat): reshape sweep — children override icon on remaining actio…
mattboon Jul 30, 2026
adc49ff
style: deno fmt on this branch's changed files (pre-push format gate)
mattboon Jul 30, 2026
dc7c6d0
style: deno fmt on branch files (leaf primitives)
mattboon Jul 30, 2026
e81ff5f
style: fix deno lint (unused React imports/param, window->globalThis)
mattboon Jul 30, 2026
b63bc9e
test(chat): add canonical hooks to the public-entry export-freeze list
mattboon Jul 30, 2026
eb24db1
docs(ui+chat): OS-facing guide for veryfront/ui (primitives + adapter…
mattboon Jul 30, 2026
aac912c
docs: add ui+chat-to-spec handoff (goals, repos, LOCALDEV, spec refs,…
mattboon Jul 30, 2026
560bd50
docs+test: consolidate plan into docs/plans/ + deterministic coverage…
mattboon Jul 30, 2026
0f2e27e
docs: revert accidental deno-fmt churn on unrelated plan docs (eval-r…
mattboon Jul 30, 2026
e576479
docs(plan): handoff references — issue tracker (veryfront-issue-inbox…
mattboon Jul 30, 2026
7e06bce
docs(plan): force-track README/loop-goal/target-file-tree (docs/plans…
mattboon Jul 30, 2026
8f5020b
test(coverage): codify goal 5 + variant coverage; doc the agent-skill…
mattboon Jul 30, 2026
bc8d24c
docs(plan): the coverage suite IS the matrix + goal (drop hand-mainta…
mattboon Jul 30, 2026
d28f58b
docs(plan): fix remaining 'matrix tracker' refs -> coverage suite is …
mattboon Jul 30, 2026
a31f867
docs(plan): new scope = add a red coverage row, not /to-spec+/to-tick…
mattboon Jul 30, 2026
737419d
merge: origin/main into feat/ui-chat-to-spec (reconcile adapter layer…
mattboon Jul 30, 2026
94eeb08
docs(plan): #3185 reconciled with main — rebase gate becomes a stay-c…
mattboon Jul 30, 2026
1b63cf9
docs(plan): loop is TWO gates — framework coverage THEN router-testin…
mattboon Jul 30, 2026
813bceb
test(ui): add shared conformance harness (greens 'has a test' gate fo…
mattboon Jul 30, 2026
704e640
feat(ui): ship Separator primitive (build · story · docs · test)
mattboon Jul 30, 2026
b6ad19f
feat(ui): ship AspectRatio primitive (build · story · docs · test)
mattboon Jul 30, 2026
9c9ed86
feat(ui): ship Toggle primitive (build · story · docs · test)
mattboon Jul 30, 2026
2b1a057
feat(ui): ship ToggleGroup primitive (build · story · docs · test)
mattboon Jul 30, 2026
396632c
docs(storybook): add Card/Drawer/ColorModeToggle stories + register n…
mattboon Jul 30, 2026
5c9c11d
docs(ui): document CodeBlock in the UI guide primitives table
mattboon Jul 30, 2026
6889fb1
refactor(ui): docs live in the component, not the root guide; gate JS…
mattboon Jul 30, 2026
8b56be7
docs(plan): codify 'docs live in the component' + JSDoc-on-props into…
mattboon Jul 30, 2026
8874ed8
feat(ui): ship NumberField primitive — docs-writer standard (build · …
mattboon Jul 30, 2026
0de95d1
feat(ui): ship Slider primitive (build · story · docs · test)
mattboon Jul 30, 2026
3d244f4
feat(ui): ship Accordion primitive (build · story · docs · test)
mattboon Jul 30, 2026
1dd678c
test(ui/adapter): prove the adapter boundary is a real seam (independ…
mattboon Jul 30, 2026
1f1f496
feat(ui): ship Combobox — purpose-built ComboboxParts adapter slot + …
mattboon Jul 30, 2026
ce76837
docs(ui): add module @example to the 15 refactored components (new do…
mattboon Jul 30, 2026
21e9c95
feat(ui): ship ContextMenu, HoverCard, Menubar, Toast (parallel-built…
mattboon Jul 30, 2026
1afc90a
feat(ui): ship Autocomplete — UI coverage suite now fully GREEN
mattboon Jul 30, 2026
ac8d1cb
feat(ui): Toast rich API — icon, action, cancel, toast.custom (backwa…
mattboon Jul 30, 2026
498ecd4
feat(ui/adapter): ToastParts adapter slot — Toast is now engine-swapp…
mattboon Jul 30, 2026
9df8707
scope(ui): add 11 cross-library primitive gaps as red coverage rows
mattboon Jul 30, 2026
296f64c
refactor(chat): reshape coverage gate to docs-in-component (mirror ui…
mattboon Jul 30, 2026
bfb2372
refactor(chat): reconcile coverage manifest to the canonical 16 (per …
mattboon Jul 30, 2026
d1b3af4
feat(chat): export useChatSidebarItem from veryfront/chat (public bar…
mattboon Jul 30, 2026
e52a199
docs(chat): JSDoc every component prop + hook (parallel-authored) + f…
mattboon Jul 30, 2026
dce6f3c
Merge remote-tracking branch 'origin/main' into feat/ui-chat-to-spec
mattboon Jul 30, 2026
8835197
test(chat): behaviour coverage for the 8 untested hooks
mattboon Jul 30, 2026
fbb136f
test(chat): green the cva variant coverage rows
mattboon Jul 30, 2026
d29c2da
feat(ui): ship the 11 cross-library gap primitives to spec
mattboon Jul 30, 2026
0ebba34
test(ui): scope the 4 real BYO engines as planned coverage rows
mattboon Jul 30, 2026
f2c3d3d
feat(ui): ship the bring-your-own-engine adapter layer
mattboon Jul 31, 2026
01bbbb3
feat(ui): complete engine-adapter slot coverage (select/combobox/toast)
mattboon Jul 31, 2026
bf1688e
fix(ci): green format + integration (register the ui-components guide)
mattboon Jul 31, 2026
2149dde
refactor(chat): split 5 files back under their LOC ratchets (don't ra…
mattboon Jul 31, 2026
1ab7dc0
fix(ci): clear test-typecheck baseline (strict-null in new primitive …
mattboon Jul 31, 2026
0a7ea29
feat(ui): add the `disclosure` adapter slot (Collapsible is now swapp…
mattboon Jul 31, 2026
6c5f03f
feat(ui): add the toggleGroup adapter slot (ToggleGroup is swappable)
mattboon Jul 31, 2026
25d313f
feat(ui): add the toolbar adapter slot (Toolbar is swappable)
mattboon Jul 31, 2026
e630344
feat(ui): route Accordion through the disclosure slot (swappable)
mattboon Jul 31, 2026
9bb4e57
feat(ui): add the tabs adapter slot (Tabs is swappable)
mattboon Jul 31, 2026
d11f18c
feat(ui): route Drawer through the dialog slot (swappable)
mattboon Jul 31, 2026
bce6ac4
feat(ui): give Drawer its own slot + a Vaul specialist adapter
mattboon Jul 31, 2026
ca83f5a
fix(chat): prevent native submits in the headless form getter
kojiwakayama Aug 2, 2026
cce2cad
docs(cli): list vaul in the generate adapter engine note
kojiwakayama Aug 2, 2026
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
69 changes: 69 additions & 0 deletions cli/commands/generate/adapter-generator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import "#veryfront/schemas/_test-setup.ts";
/**
* Tests for `veryfront generate adapter <engine>` — vendoring a veryfront/ui
* engine adapter template into a consumer project.
*/
import { assert, assertRejects, assertStringIncludes } from "#veryfront/testing/assert.ts";
import { describe, it } from "#veryfront/testing/bdd.ts";
import { join } from "#std/path.ts";
import { generateUiAdapter } from "./adapter-generator.ts";
import { getUiAdapterTemplate, listUiAdapters } from "../../templates/loader.ts";

describe("commands/generate/adapter-generator", () => {
it("the manifest ships the four engine templates", () => {
const engines = listUiAdapters();
for (const engine of ["base-ui", "radix", "react-aria", "ariakit"]) {
assert(engines.includes(engine), `manifest missing ui-adapter:${engine}`);
}
});

it("base-ui template maps the overlay archetypes + exports its adapter", () => {
const files = getUiAdapterTemplate("base-ui");
assert(files && files.length === 1, "base-ui template should be one file");
const src = files![0]!.content;
for (const slot of ["popover:", "dialog:", "menu:", "tooltip:"]) {
assertStringIncludes(src, slot);
}
assertStringIncludes(src, "baseUiAdapter");
});

it("vendors ui-adapters/<engine>.tsx into the project", async () => {
const dir = await Deno.makeTempDir();
try {
await generateUiAdapter(dir, "radix");
const dest = join(dir, "ui-adapters", "radix.tsx");
const written = await Deno.readTextFile(dest);
assertStringIncludes(written, "radixAdapter");
assertStringIncludes(written, "REFERENCE TEMPLATE");
} finally {
await Deno.remove(dir, { recursive: true });
}
});

it("does not clobber an already-vendored adapter", async () => {
const dir = await Deno.makeTempDir();
try {
await Deno.mkdir(join(dir, "ui-adapters"));
const dest = join(dir, "ui-adapters", "base-ui.tsx");
await Deno.writeTextFile(dest, "// my edited adapter\n");
await generateUiAdapter(dir, "base-ui");
const after = await Deno.readTextFile(dest);
assert(after === "// my edited adapter\n", "must not overwrite a vendored file");
} finally {
await Deno.remove(dir, { recursive: true });
}
});

it("rejects an unknown engine with the available list", async () => {
const dir = await Deno.makeTempDir();
try {
await assertRejects(
() => generateUiAdapter(dir, "not-an-engine"),
Error,
"Unknown ui adapter engine",
);
} finally {
await Deno.remove(dir, { recursive: true });
}
});
});
86 changes: 86 additions & 0 deletions cli/commands/generate/adapter-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* `veryfront generate adapter <engine>` — vendor a `veryfront/ui` engine adapter.
*
* Copies the `<engine>.tsx` reference template (Base UI / Radix / React Aria /
* Ariakit) verbatim into the consumer's `./ui-adapters/`. From then on the
* consumer OWNS the file and the engine package is THEIR dependency — `veryfront/ui`
* core stays engine-free (enforced by a CI guard), so the swap is opt-in and the
* engine version is on the consumer's schedule. Wire it up once via
* `<UIAdapterProvider adapter={<engine>Adapter}>` (see the file's docstring).
*
* @module cli/commands/generate/adapter-generator
*/
import { join } from "#std/path.ts";
import { bold, brand, dim } from "#cli/ui";
import { cliLogger } from "#cli/utils";
import { createFileSystem } from "veryfront/platform";
import { createError, toError } from "veryfront/errors";
import { ensureDir } from "../../utils/fs.ts";
import { getUiAdapterTemplate, listUiAdapters } from "../../templates/loader.ts";

/** npm package + wiring hint per engine, shown after scaffolding. */
const ENGINE_PACKAGES: Record<string, { pkg: string; adapter: string }> = {
"base-ui": { pkg: "@base-ui/react", adapter: "baseUiAdapter" },
radix: {
pkg:
"@radix-ui/react-popover @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-tooltip",
adapter: "radixAdapter",
},
"react-aria": { pkg: "react-aria-components", adapter: "reactAriaAdapter" },
ariakit: { pkg: "@ariakit/react", adapter: "ariakitAdapter" },
vaul: { pkg: "vaul", adapter: "vaulAdapter" },
};

/** Scaffold `ui-adapters/<engine>.tsx` into the project (idempotent — never clobbers). */
export async function generateUiAdapter(
projectDir: string,
engine: string,
): Promise<void> {
const files = getUiAdapterTemplate(engine);
if (!files || files.length === 0) {
const available = listUiAdapters();
throw toError(
createError({
type: "config",
message: available.length
? `Unknown ui adapter engine "${engine}". Available: ${available.join(", ")}`
: `No ui adapter engines are available in this build.`,
}),
);
}

const fs = createFileSystem();
const targetDir = join(projectDir, "ui-adapters");
await ensureDir(targetDir);

const written: string[] = [];
for (const file of files) {
const dest = join(targetDir, file.path);
if (await fs.exists(dest)) {
cliLogger.warn(
`ui-adapters/${file.path} already exists — left as-is (delete it to regenerate).`,
);
continue;
}
await fs.writeTextFile(dest, file.content);
written.push(`ui-adapters/${file.path}`);
}

const meta = ENGINE_PACKAGES[engine];
for (const path of written) cliLogger.info(`Created ${brand(path)}`);
cliLogger.info("");
cliLogger.info(bold("Next steps:"));
if (meta) {
cliLogger.info(` 1. Install the engine: ${brand(`npm i ${meta.pkg}`)}`);
cliLogger.info(
` 2. Wrap your app: ${
brand(`<UIAdapterProvider adapter={${meta.adapter}}>…</UIAdapterProvider>`)
}`,
);
}
cliLogger.info(
dim(
" You own this file now. veryfront/ui core stays engine-free; slots you don't map fall back to builtin.",
),
);
}
5 changes: 4 additions & 1 deletion cli/commands/generate/command-help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ export const generateHelp: CommandHelp = {
"veryfront generate skill code-review",
"veryfront generate integration # Interactive wizard",
"veryfront generate integration twilio # With name preset",
"veryfront generate adapter base-ui # Vendor a veryfront/ui engine adapter",
"veryfront generate adapter radix",
],
notes: [
"Types: page, api, layout, component, tool, agent, prompt, workflow, task, resource, skill, integration",
"Types: page, api, layout, component, tool, agent, prompt, workflow, task, resource, skill, integration, adapter",
"Integration type launches interactive wizard if name not provided",
"Adapter type vendors a veryfront/ui engine adapter into ./ui-adapters/ (engines: base-ui, radix, react-aria, ariakit, vaul)",
],
};
6 changes: 6 additions & 0 deletions cli/commands/generate/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getConfig } from "veryfront/config";
import { cliLogger } from "#cli/utils";
import { createError, toError } from "veryfront/errors";
import { generateIntegration } from "./integration-generator.ts";
import { generateUiAdapter } from "./adapter-generator.ts";
import { isScaffoldType, scaffoldProjectFile } from "../../scaffold/engine.ts";

async function getPreferredRouter(
Expand Down Expand Up @@ -32,6 +33,11 @@ export async function generateCommand(
return;
}

if (type === "adapter") {
await generateUiAdapter(projectDir, name);
return;
}

if (!isScaffoldType(type)) {
throw toError(
createError({
Expand Down
2 changes: 1 addition & 1 deletion cli/commands/generate/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { ParsedArgs } from "#cli/shared/types";
import { cwd } from "veryfront/platform";
import { SCAFFOLD_TYPES } from "../../scaffold/engine.ts";

const VALID_TYPES = [...SCAFFOLD_TYPES, "integration"] as const;
const VALID_TYPES = [...SCAFFOLD_TYPES, "integration", "adapter"] as const;

const getGenerateArgsSchema = defineSchema((v) =>
v.object({
Expand Down
20 changes: 19 additions & 1 deletion cli/templates/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ export function getIntegrationTemplate(

export function listTemplates(): string[] {
return Object.keys(typedManifest.templates).filter(
(name) => !name.startsWith("integration:"),
(name) =>
!name.startsWith("integration:") &&
!name.startsWith("ui-adapter:") &&
!name.startsWith("ai-rules:"),
);
}

Expand All @@ -69,3 +72,18 @@ export function listIntegrations(): string[] {
.filter((name) => name.startsWith("integration:"))
.map((name) => name.replace("integration:", ""));
}

/** The `<engine>.tsx` reference adapter for `veryfront generate adapter <engine>`. */
export function getUiAdapterTemplate(engine: string): TemplateFile[] | null {
const entry = typedManifest.templates[`ui-adapter:${engine}`];
if (!entry) return null;

return getSortedFiles(entry);
}

/** Engine names with a shippable `ui-adapters/<engine>.tsx` reference template. */
export function listUiAdapters(): string[] {
return Object.keys(typedManifest.templates)
.filter((name) => name.startsWith("ui-adapter:"))
.map((name) => name.replace("ui-adapter:", ""));
}
28 changes: 28 additions & 0 deletions cli/templates/manifest.json

Large diffs are not rendered by default.

Loading