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
45 changes: 41 additions & 4 deletions packages/cli/src/registry/registryBlocks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ import { describe, expect, it } from "vitest";
const blocksDir = resolve(dirname(fileURLToPath(import.meta.url)), "../../../../registry/blocks");

interface RegistryManifest {
files: Array<{ path: string; type: string }>;
name: string;
tags?: string[];
files: Array<{ path: string; target: string; type: string }>;
}

const promotedTemplateTag = "ad-template";

function loadRegistryManifest(itemDir: string): RegistryManifest {
return JSON.parse(readFileSync(join(itemDir, "registry-item.json"), "utf8")) as RegistryManifest;
}

function findMissingLocalScripts(itemDir: string, manifest: RegistryManifest): string[] {
Expand All @@ -32,16 +40,45 @@ function findMissingLocalScripts(itemDir: string, manifest: RegistryManifest): s
}

describe("registry blocks", () => {
it("ships an editing contract and declared variables for every promoted template", () => {
const promotedManifests = readdirSync(blocksDir, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => ({
itemDir: join(blocksDir, entry.name),
manifest: loadRegistryManifest(join(blocksDir, entry.name)),
}))
.filter(({ manifest }) => manifest.tags?.includes(promotedTemplateTag));

expect(promotedManifests.length).toBeGreaterThan(0);
for (const { itemDir, manifest } of promotedManifests) {
const templateId = manifest.name;
const contractFiles = manifest.files.filter(
(file) =>
file.path === "TEMPLATE.md" &&
file.target === "TEMPLATE.md" &&
file.type === "hyperframes:asset",
);
const composition = manifest.files.find((file) => file.type === "hyperframes:composition");

expect(contractFiles, templateId).toHaveLength(1);
expect(composition, templateId).toBeDefined();
const html = readFileSync(join(itemDir, composition?.path ?? ""), "utf8");
const { document } = parseHTML(html);
const declarations = JSON.parse(
document.documentElement.getAttribute("data-composition-variables") ?? "[]",
) as unknown[];
expect(declarations.length, templateId).toBeGreaterThan(0);
}
});

it("installs every local script referenced by a block composition", () => {
const missing: string[] = [];

for (const entry of readdirSync(blocksDir, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;

const itemDir = join(blocksDir, entry.name);
const manifest = JSON.parse(
readFileSync(join(itemDir, "registry-item.json"), "utf8"),
) as RegistryManifest;
const manifest = loadRegistryManifest(itemDir);

for (const src of findMissingLocalScripts(itemDir, manifest)) {
missing.push(`${entry.name}: ${src}`);
Expand Down
19 changes: 19 additions & 0 deletions registry/blocks/ai-chat-reveal/TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# AI Chat Reveal editing contract

## Surface ownership

This is a generic assistant conversation followed by a brandable closing card. The website brand may appear in the declared conversation and closing-card slots; the chat shell and motion remain template-owned.

## Editable slots

Only defaults declared in `data-composition-variables` are editable:

- `botName`, `userMessage`, `answer1` through `answer3`
- `bullet1` through `bullet3`
- `ecHeadline`, `ecSub`, `ecCta`, and `ecFooter`

Typed and streamed copy is length-locked to within 20% of the original.

## Protected

Do not change chat chrome, keyboard, layout, logo asset, palette, fonts, scene order, duration, timing, easing, typing cadence, or reveal logic. This contract declares no image or color variables.
5 changes: 5 additions & 0 deletions registry/blocks/ai-chat-reveal/registry-item.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@
"path": "assets/hyperframes-logo-white.svg",
"target": "assets/hyperframes-logo-white.svg",
"type": "hyperframes:asset"
},
{
"path": "TEMPLATE.md",
"target": "TEMPLATE.md",
"type": "hyperframes:asset"
}
]
}
21 changes: 21 additions & 0 deletions registry/blocks/chatgpt-exchange/TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# ChatGPT Exchange editing contract

## Surface ownership

This template depicts the ChatGPT application. OpenAI owns the surrounding application identity. The supplied website is only the subject discussed inside the conversation.

## Editable slots

Only defaults declared in `data-composition-variables` are editable:

- The user prompt and two response-introduction paragraphs
- The three comparison-table headings
- The use case, tool, explanation, and source-chip copy for each of four rows

Typed and streamed copy is length-locked to within 20% of the original.

## Protected

Preserve the ChatGPT name, labels, suggestions, predictive keyboard text, header, composer, keyboard, icons, fonts, palette, layout, status UI, table geometry, scene structure, duration, timing, easing, typing cadence, and reveal behavior.

Website colors and typography must never be applied to the ChatGPT shell. If a requested value has no declared variable, leave it unchanged.
96 changes: 72 additions & 24 deletions registry/blocks/chatgpt-exchange/chatgpt-exchange.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
<!doctype html>
<html lang="en" data-resolution="portrait">
<html
lang="en"
data-resolution="portrait"
data-composition-variables='[
{ "id": "prompt", "type": "string", "role": "content", "label": "User prompt", "description": "Prompt typed into ChatGPT; keep close to the original length.", "default": "Hey what&#39;s the best tool for ai avatars" },
{ "id": "intro1", "type": "string", "role": "content", "label": "Answer introduction 1", "description": "First streamed answer paragraph.", "default": "It really depends on what you&#39;re trying to do, because “AI avatars” has split into a few different categories." },
{ "id": "intro2", "type": "string", "role": "content", "label": "Answer introduction 2", "description": "Second streamed answer paragraph.", "default": "For **most creators and marketers**, here&#39;s how I&#39;d rank them today:" },
{ "id": "tableHeadUse", "type": "string", "role": "content", "label": "Table heading 1", "description": "Use-case column heading.", "default": "Use case" },
{ "id": "tableHeadTool", "type": "string", "role": "content", "label": "Table heading 2", "description": "Tool column heading.", "default": "Best tool" },
{ "id": "tableHeadWhy", "type": "string", "role": "content", "label": "Table heading 3", "description": "Explanation column heading.", "default": "Why" },
{ "id": "row1Use", "type": "string", "role": "content", "label": "Row 1 use case", "description": "First comparison use case.", "default": "Overall realism" },
{ "id": "row1Tool", "type": "string", "role": "content", "label": "Row 1 tool", "description": "First recommended product.", "default": "HeyGen" },
{ "id": "row1Why", "type": "string", "role": "content", "label": "Row 1 explanation", "description": "First recommendation explanation.", "default": "Most natural facial expressions, lip sync, gestures, voice cloning and localization. Benchmark for talking head videos." },
{ "id": "row1Chip", "type": "string", "role": "content", "label": "Row 1 source", "description": "First source-chip label.", "default": "Official A.I Ranking" },
{ "id": "row2Use", "type": "string", "role": "content", "label": "Row 2 use case", "description": "Second comparison use case.", "default": "Enterprise/training" },
{ "id": "row2Tool", "type": "string", "role": "content", "label": "Row 2 tool", "description": "Second recommended product.", "default": "Synthesia" },
{ "id": "row2Why", "type": "string", "role": "content", "label": "Row 2 explanation", "description": "Second recommendation explanation.", "default": "Better collaboration, SCORM, compliance, team workflows; less creator-focused." },
{ "id": "row2Chip", "type": "string", "role": "content", "label": "Row 2 source", "description": "Second source-chip label.", "default": "Official A.I Ranking" },
{ "id": "row3Use", "type": "string", "role": "content", "label": "Row 3 use case", "description": "Third comparison use case.", "default": "Mobile UGC" },
{ "id": "row3Tool", "type": "string", "role": "content", "label": "Row 3 tool", "description": "Third recommended product.", "default": "Captions" },
{ "id": "row3Why", "type": "string", "role": "content", "label": "Row 3 explanation", "description": "Third recommendation explanation.", "default": "Extremely fast mobile workflow and social editing. Great for Reels creators." },
{ "id": "row3Chip", "type": "string", "role": "content", "label": "Row 3 source", "description": "Third source-chip label.", "default": "Creator Stack" },
{ "id": "row4Use", "type": "string", "role": "content", "label": "Row 4 use case", "description": "Fourth comparison use case.", "default": "Real-time conversations" },
{ "id": "row4Tool", "type": "string", "role": "content", "label": "Row 4 tool", "description": "Fourth recommended product.", "default": "Tavus" },
{ "id": "row4Why", "type": "string", "role": "content", "label": "Row 4 explanation", "description": "Fourth recommendation explanation.", "default": "Interactive avatars that can hold live conversations." },
{ "id": "row4Chip", "type": "string", "role": "content", "label": "Row 4 source", "description": "Fourth source-chip label.", "default": "Creator Stack" }
]'
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1080, height=1920" />
Expand Down Expand Up @@ -626,12 +653,16 @@
</div>

<script>
/* ==================================================================
TEMPLATE CONFIG — edit only this block to re-skin the exchange.
================================================================== */
const variables =
window.__hyperframes && typeof window.__hyperframes.getVariables === "function"
? window.__hyperframes.getVariables()
: {};
const content = (id, fallback) =>
typeof variables[id] === "string" && variables[id].trim() ? variables[id] : fallback;

const CONFIG = {
status: { clock: "1:29", clockLate: "1:30", net: "5G+", battery: 89, batteryLate: 88 },
prompt: "Hey what's the best tool for ai avatars",
prompt: content("prompt", "Hey what's the best tool for ai avatars"),
suggestions: [
{ icon: "slack", label: "Summarize my to-dos" },
{ icon: "gmail", label: "Draft follow-up emails" },
Expand All @@ -643,39 +674,55 @@
end: ['"avatars"', "avatar sick", "avatar stressed"],
},
paragraphs: [
"It really depends on what you're trying to do, because “AI avatars” has split into a few different categories.",
"For **most creators and marketers**, here's how I'd rank them today:",
content(
"intro1",
"It really depends on what you're trying to do, because “AI avatars” has split into a few different categories.",
),
content("intro2", "For **most creators and marketers**, here's how I'd rank them today:"),
],
table: {
head: ["Use case", "Best tool", "Why"],
head: [
content("tableHeadUse", "Use case"),
content("tableHeadTool", "Best tool"),
content("tableHeadWhy", "Why"),
],
rows: [
{
emoji: "🥇",
use: "Overall realism",
tool: "HeyGen",
why: "Most natural facial expressions, lip sync, gestures, voice cloning and localization. Benchmark for talking head videos.",
chip: "Official A.I Ranking",
use: content("row1Use", "Overall realism"),
tool: content("row1Tool", "HeyGen"),
why: content(
"row1Why",
"Most natural facial expressions, lip sync, gestures, voice cloning and localization. Benchmark for talking head videos.",
),
chip: content("row1Chip", "Official A.I Ranking"),
},
{
emoji: "🏢",
use: "Enterprise/training",
tool: "Synthesia",
why: "Better collaboration, SCORM, compliance, team workflows; less creator-focused.",
chip: "Official A.I Ranking",
use: content("row2Use", "Enterprise/training"),
tool: content("row2Tool", "Synthesia"),
why: content(
"row2Why",
"Better collaboration, SCORM, compliance, team workflows; less creator-focused.",
),
chip: content("row2Chip", "Official A.I Ranking"),
},
{
emoji: "📱",
use: "Mobile UGC",
tool: "Captions",
why: "Extremely fast mobile workflow and social editing. Great for Reels creators.",
chip: "Creator Stack",
use: content("row3Use", "Mobile UGC"),
tool: content("row3Tool", "Captions"),
why: content(
"row3Why",
"Extremely fast mobile workflow and social editing. Great for Reels creators.",
),
chip: content("row3Chip", "Creator Stack"),
},
{
emoji: "💬",
use: "Real-time conversations",
tool: "Tavus",
why: "Interactive avatars that can hold live conversations.",
chip: "Creator Stack",
use: content("row4Use", "Real-time conversations"),
tool: content("row4Tool", "Tavus"),
why: content("row4Why", "Interactive avatars that can hold live conversations."),
chip: content("row4Chip", "Creator Stack"),
},
],
},
Expand Down Expand Up @@ -799,6 +846,7 @@
const el = (cls, html, tag) => {
const n = document.createElement(tag || "div");
if (cls) n.className = cls;
// Editable strings reach innerHTML; the template delivery gate rejects angle brackets.
if (html != null) n.innerHTML = html;
return n;
};
Expand Down
5 changes: 5 additions & 0 deletions registry/blocks/chatgpt-exchange/registry-item.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
"path": "assets/fonts/inter-LICENSE.txt",
"target": "assets/fonts/inter-LICENSE.txt",
"type": "hyperframes:asset"
},
{
"path": "TEMPLATE.md",
"target": "TEMPLATE.md",
"type": "hyperframes:asset"
}
]
}
20 changes: 20 additions & 0 deletions registry/blocks/claude-exchange/TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Claude Exchange editing contract

## Surface ownership

This template depicts the Claude application. Anthropic owns the surrounding application identity. The supplied website is only the subject discussed inside the conversation.

## Editable slots

Only defaults declared in `data-composition-variables` are editable:

- The user prompt, thinking line, search lead, and search query
- The ten answer paragraphs or bullets

Typed and streamed copy is length-locked to within 20% of the original.

## Protected

Preserve the Claude name, model name, usage notice, placeholders, disclaimer, source treatment, header, composer, icons, starburst, fonts, palette, layout, status UI, scene structure, duration, timing, easing, typing cadence, and reveal behavior.

Website colors and typography must never be applied to the Claude shell. If a requested value has no declared variable, leave it unchanged.
Loading
Loading