Skip to content

Commit c25c208

Browse files
committed
feat: add promoted template edit contracts
1 parent 41af866 commit c25c208

24 files changed

Lines changed: 585 additions & 79 deletions

File tree

packages/cli/src/registry/registryBlocks.test.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,21 @@ import { describe, expect, it } from "vitest";
88
const blocksDir = resolve(dirname(fileURLToPath(import.meta.url)), "../../../../registry/blocks");
99

1010
interface RegistryManifest {
11-
files: Array<{ path: string; type: string }>;
11+
files: Array<{ path: string; target: string; type: string }>;
1212
}
1313

14+
const promotedTemplateIds = [
15+
"notification-cascade",
16+
"share-sheet-carousel",
17+
"ai-chat-reveal",
18+
"message-thread-reveal",
19+
"notes-reveal",
20+
"chatgpt-exchange",
21+
"claude-exchange",
22+
"heygen-avatar-promo-card",
23+
"slack-notification-ad",
24+
] as const;
25+
1426
function findMissingLocalScripts(itemDir: string, manifest: RegistryManifest): string[] {
1527
const manifestPaths = new Set(manifest.files.map((file) => file.path));
1628
const missing: string[] = [];
@@ -32,6 +44,31 @@ function findMissingLocalScripts(itemDir: string, manifest: RegistryManifest): s
3244
}
3345

3446
describe("registry blocks", () => {
47+
it("ships an editing contract and declared variables for every promoted template", () => {
48+
for (const templateId of promotedTemplateIds) {
49+
const itemDir = join(blocksDir, templateId);
50+
const manifest = JSON.parse(
51+
readFileSync(join(itemDir, "registry-item.json"), "utf8"),
52+
) as RegistryManifest;
53+
const contractFiles = manifest.files.filter(
54+
(file) =>
55+
file.path === "TEMPLATE.md" &&
56+
file.target === "TEMPLATE.md" &&
57+
file.type === "hyperframes:asset",
58+
);
59+
const composition = manifest.files.find((file) => file.type === "hyperframes:composition");
60+
61+
expect(contractFiles, templateId).toHaveLength(1);
62+
expect(composition, templateId).toBeDefined();
63+
const html = readFileSync(join(itemDir, composition?.path ?? ""), "utf8");
64+
const { document } = parseHTML(html);
65+
const declarations = JSON.parse(
66+
document.documentElement.getAttribute("data-composition-variables") ?? "[]",
67+
) as unknown[];
68+
expect(declarations.length, templateId).toBeGreaterThan(0);
69+
}
70+
});
71+
3572
it("installs every local script referenced by a block composition", () => {
3673
const missing: string[] = [];
3774

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# AI Chat Reveal editing contract
2+
3+
## Surface ownership
4+
5+
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.
6+
7+
## Editable slots
8+
9+
Only defaults declared in `data-composition-variables` are editable:
10+
11+
- `botName`, `userMessage`, `answer1` through `answer3`
12+
- `bullet1` through `bullet3`
13+
- `ecHeadline`, `ecSub`, `ecCta`, and `ecFooter`
14+
15+
Typed and streamed copy is length-locked to within 20% of the original.
16+
17+
## Protected
18+
19+
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.

registry/blocks/ai-chat-reveal/registry-item.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@
119119
"path": "assets/hyperframes-logo-white.svg",
120120
"target": "assets/hyperframes-logo-white.svg",
121121
"type": "hyperframes:asset"
122+
},
123+
{
124+
"path": "TEMPLATE.md",
125+
"target": "TEMPLATE.md",
126+
"type": "hyperframes:asset"
122127
}
123128
]
124129
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# ChatGPT Exchange editing contract
2+
3+
## Surface ownership
4+
5+
This template depicts the ChatGPT application. OpenAI owns the surrounding application identity. The supplied website is only the subject discussed inside the conversation.
6+
7+
## Editable slots
8+
9+
Only defaults declared in `data-composition-variables` are editable:
10+
11+
- The user prompt and two response-introduction paragraphs
12+
- The three comparison-table headings
13+
- The use case, tool, explanation, and source-chip copy for each of four rows
14+
15+
Typed and streamed copy is length-locked to within 20% of the original.
16+
17+
## Protected
18+
19+
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.
20+
21+
Website colors and typography must never be applied to the ChatGPT shell. If a requested value has no declared variable, leave it unchanged.

registry/blocks/chatgpt-exchange/chatgpt-exchange.html

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
11
<!doctype html>
2-
<html lang="en" data-resolution="portrait">
2+
<html
3+
lang="en"
4+
data-resolution="portrait"
5+
data-composition-variables='[
6+
{ "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" },
7+
{ "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." },
8+
{ "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:" },
9+
{ "id": "tableHeadUse", "type": "string", "role": "content", "label": "Table heading 1", "description": "Use-case column heading.", "default": "Use case" },
10+
{ "id": "tableHeadTool", "type": "string", "role": "content", "label": "Table heading 2", "description": "Tool column heading.", "default": "Best tool" },
11+
{ "id": "tableHeadWhy", "type": "string", "role": "content", "label": "Table heading 3", "description": "Explanation column heading.", "default": "Why" },
12+
{ "id": "row1Use", "type": "string", "role": "content", "label": "Row 1 use case", "description": "First comparison use case.", "default": "Overall realism" },
13+
{ "id": "row1Tool", "type": "string", "role": "content", "label": "Row 1 tool", "description": "First recommended product.", "default": "HeyGen" },
14+
{ "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." },
15+
{ "id": "row1Chip", "type": "string", "role": "content", "label": "Row 1 source", "description": "First source-chip label.", "default": "Official A.I Ranking" },
16+
{ "id": "row2Use", "type": "string", "role": "content", "label": "Row 2 use case", "description": "Second comparison use case.", "default": "Enterprise/training" },
17+
{ "id": "row2Tool", "type": "string", "role": "content", "label": "Row 2 tool", "description": "Second recommended product.", "default": "Synthesia" },
18+
{ "id": "row2Why", "type": "string", "role": "content", "label": "Row 2 explanation", "description": "Second recommendation explanation.", "default": "Better collaboration, SCORM, compliance, team workflows; less creator-focused." },
19+
{ "id": "row2Chip", "type": "string", "role": "content", "label": "Row 2 source", "description": "Second source-chip label.", "default": "Official A.I Ranking" },
20+
{ "id": "row3Use", "type": "string", "role": "content", "label": "Row 3 use case", "description": "Third comparison use case.", "default": "Mobile UGC" },
21+
{ "id": "row3Tool", "type": "string", "role": "content", "label": "Row 3 tool", "description": "Third recommended product.", "default": "Captions" },
22+
{ "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." },
23+
{ "id": "row3Chip", "type": "string", "role": "content", "label": "Row 3 source", "description": "Third source-chip label.", "default": "Creator Stack" },
24+
{ "id": "row4Use", "type": "string", "role": "content", "label": "Row 4 use case", "description": "Fourth comparison use case.", "default": "Real-time conversations" },
25+
{ "id": "row4Tool", "type": "string", "role": "content", "label": "Row 4 tool", "description": "Fourth recommended product.", "default": "Tavus" },
26+
{ "id": "row4Why", "type": "string", "role": "content", "label": "Row 4 explanation", "description": "Fourth recommendation explanation.", "default": "Interactive avatars that can hold live conversations." },
27+
{ "id": "row4Chip", "type": "string", "role": "content", "label": "Row 4 source", "description": "Fourth source-chip label.", "default": "Creator Stack" }
28+
]'
29+
>
330
<head>
431
<meta charset="UTF-8" />
532
<meta name="viewport" content="width=1080, height=1920" />
@@ -626,12 +653,16 @@
626653
</div>
627654

628655
<script>
629-
/* ==================================================================
630-
TEMPLATE CONFIG — edit only this block to re-skin the exchange.
631-
================================================================== */
656+
const variables =
657+
window.__hyperframes && typeof window.__hyperframes.getVariables === "function"
658+
? window.__hyperframes.getVariables()
659+
: {};
660+
const content = (id, fallback) =>
661+
typeof variables[id] === "string" && variables[id].trim() ? variables[id] : fallback;
662+
632663
const CONFIG = {
633664
status: { clock: "1:29", clockLate: "1:30", net: "5G+", battery: 89, batteryLate: 88 },
634-
prompt: "Hey what's the best tool for ai avatars",
665+
prompt: content("prompt", "Hey what's the best tool for ai avatars"),
635666
suggestions: [
636667
{ icon: "slack", label: "Summarize my to-dos" },
637668
{ icon: "gmail", label: "Draft follow-up emails" },
@@ -643,39 +674,55 @@
643674
end: ['"avatars"', "avatar sick", "avatar stressed"],
644675
},
645676
paragraphs: [
646-
"It really depends on what you're trying to do, because “AI avatars” has split into a few different categories.",
647-
"For **most creators and marketers**, here's how I'd rank them today:",
677+
content(
678+
"intro1",
679+
"It really depends on what you're trying to do, because “AI avatars” has split into a few different categories.",
680+
),
681+
content("intro2", "For **most creators and marketers**, here's how I'd rank them today:"),
648682
],
649683
table: {
650-
head: ["Use case", "Best tool", "Why"],
684+
head: [
685+
content("tableHeadUse", "Use case"),
686+
content("tableHeadTool", "Best tool"),
687+
content("tableHeadWhy", "Why"),
688+
],
651689
rows: [
652690
{
653691
emoji: "🥇",
654-
use: "Overall realism",
655-
tool: "HeyGen",
656-
why: "Most natural facial expressions, lip sync, gestures, voice cloning and localization. Benchmark for talking head videos.",
657-
chip: "Official A.I Ranking",
692+
use: content("row1Use", "Overall realism"),
693+
tool: content("row1Tool", "HeyGen"),
694+
why: content(
695+
"row1Why",
696+
"Most natural facial expressions, lip sync, gestures, voice cloning and localization. Benchmark for talking head videos.",
697+
),
698+
chip: content("row1Chip", "Official A.I Ranking"),
658699
},
659700
{
660701
emoji: "🏢",
661-
use: "Enterprise/training",
662-
tool: "Synthesia",
663-
why: "Better collaboration, SCORM, compliance, team workflows; less creator-focused.",
664-
chip: "Official A.I Ranking",
702+
use: content("row2Use", "Enterprise/training"),
703+
tool: content("row2Tool", "Synthesia"),
704+
why: content(
705+
"row2Why",
706+
"Better collaboration, SCORM, compliance, team workflows; less creator-focused.",
707+
),
708+
chip: content("row2Chip", "Official A.I Ranking"),
665709
},
666710
{
667711
emoji: "📱",
668-
use: "Mobile UGC",
669-
tool: "Captions",
670-
why: "Extremely fast mobile workflow and social editing. Great for Reels creators.",
671-
chip: "Creator Stack",
712+
use: content("row3Use", "Mobile UGC"),
713+
tool: content("row3Tool", "Captions"),
714+
why: content(
715+
"row3Why",
716+
"Extremely fast mobile workflow and social editing. Great for Reels creators.",
717+
),
718+
chip: content("row3Chip", "Creator Stack"),
672719
},
673720
{
674721
emoji: "💬",
675-
use: "Real-time conversations",
676-
tool: "Tavus",
677-
why: "Interactive avatars that can hold live conversations.",
678-
chip: "Creator Stack",
722+
use: content("row4Use", "Real-time conversations"),
723+
tool: content("row4Tool", "Tavus"),
724+
why: content("row4Why", "Interactive avatars that can hold live conversations."),
725+
chip: content("row4Chip", "Creator Stack"),
679726
},
680727
],
681728
},

registry/blocks/chatgpt-exchange/registry-item.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
"path": "assets/fonts/inter-LICENSE.txt",
3737
"target": "assets/fonts/inter-LICENSE.txt",
3838
"type": "hyperframes:asset"
39+
},
40+
{
41+
"path": "TEMPLATE.md",
42+
"target": "TEMPLATE.md",
43+
"type": "hyperframes:asset"
3944
}
4045
]
4146
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Claude Exchange editing contract
2+
3+
## Surface ownership
4+
5+
This template depicts the Claude application. Anthropic owns the surrounding application identity. The supplied website is only the subject discussed inside the conversation.
6+
7+
## Editable slots
8+
9+
Only defaults declared in `data-composition-variables` are editable:
10+
11+
- The user prompt, thinking line, search lead, and search query
12+
- The ten answer paragraphs or bullets
13+
14+
Typed and streamed copy is length-locked to within 20% of the original.
15+
16+
## Protected
17+
18+
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.
19+
20+
Website colors and typography must never be applied to the Claude shell. If a requested value has no declared variable, leave it unchanged.

0 commit comments

Comments
 (0)