diff --git a/packages/cli/src/registry/registryBlocks.test.ts b/packages/cli/src/registry/registryBlocks.test.ts index 9c5c0a71c0..eae2a1d159 100644 --- a/packages/cli/src/registry/registryBlocks.test.ts +++ b/packages/cli/src/registry/registryBlocks.test.ts @@ -62,6 +62,10 @@ describe("registry blocks", () => { expect(contractFiles, templateId).toHaveLength(1); expect(composition, templateId).toBeDefined(); + const editingContract = readFileSync(join(itemDir, "TEMPLATE.md"), "utf8"); + expect(editingContract, templateId).toContain("## Safe editing mechanics"); + expect(editingContract, templateId).toContain("set_template_variable_defaults"); + expect(editingContract, templateId).toContain("HTML-entity-encoded JSON"); const html = readFileSync(join(itemDir, composition?.path ?? ""), "utf8"); const { document } = parseHTML(html); const declarations = JSON.parse( diff --git a/packages/lint/src/rules/composition.test.ts b/packages/lint/src/rules/composition.test.ts index 8f7cc95119..701807f230 100644 --- a/packages/lint/src/rules/composition.test.ts +++ b/packages/lint/src/rules/composition.test.ts @@ -1049,6 +1049,24 @@ describe("composition rules", () => { expect(finding).toBeUndefined(); }); + it("does not warn for an HTML-entity-encoded declarations array", async () => { + const declarations = JSON.stringify([ + { + id: "title", + type: "string", + label: "Title", + description: 'A "quoted" title', + default: "Hello", + }, + ]).replaceAll('"', """); + const html = `
`; + const result = await lintHyperframeHtml(html); + const finding = result.findings.find( + (f) => f.code === "invalid_composition_variables_declaration", + ); + expect(finding).toBeUndefined(); + }); + it("does not warn when data-composition-variables is absent", async () => { const html = `
`; const result = await lintHyperframeHtml(html); diff --git a/packages/lint/src/utils.ts b/packages/lint/src/utils.ts index 9823d90333..4487d7f812 100644 --- a/packages/lint/src/utils.ts +++ b/packages/lint/src/utils.ts @@ -194,27 +194,13 @@ export function readDecodedAttr(tagSource: string, attr: string): string | null } /** - * Read an attribute that may legitimately contain the opposite quote - * character. `readAttr` truncates `data-variable-values='{"title":"Hello"}'` - * at the first internal `"` because its `[^"']+` class excludes both quote - * types. This variant alternates: a double-quoted value never contains an - * unescaped `"`, and a single-quoted value never contains an unescaped `'`, - * so each branch can use a quote-specific class. - * - * Use for attributes whose values are JSON or otherwise carry the opposite - * quote character. Existing single-token attributes (`id`, `class`, etc.) - * stick with `readAttr` for consistency with the rest of the lint code. + * Read a JSON-bearing attribute with browser-equivalent character-reference + * decoding. Imported or formatter-serialized HTML commonly stores JSON quotes + * as `"`; lint must inspect the same decoded value that `getAttribute()` + * exposes at runtime. */ export function readJsonAttr(tagSource: string, attr: string): string | null { - if (!tagSource) return null; - const escaped = attr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - // See readAttr: `(? { diff --git a/registry/blocks/ai-chat-reveal/TEMPLATE.md b/registry/blocks/ai-chat-reveal/TEMPLATE.md index 3e957fbfc2..2d54e49d15 100644 --- a/registry/blocks/ai-chat-reveal/TEMPLATE.md +++ b/registry/blocks/ai-chat-reveal/TEMPLATE.md @@ -15,6 +15,10 @@ Only defaults declared in `data-composition-variables` are editable: Typed and streamed copy is length-locked to within 20% of the original. +## Safe editing mechanics + +Call `set_template_variable_defaults` once with the existing variable ids and their new defaults. Do not directly edit or rewrite `index.html` or its `data-composition-variables` attribute; the imported declaration is HTML-entity-encoded JSON and the setter preserves that encoding. Never edit `__template_baseline__.html` or a duplicate composition file. For an image slot, pass only the token returned by an image tool. Validate after the setter succeeds. + ## Protected Do not change chat chrome, keyboard, layout, palette, fonts, scene order, duration, timing, easing, typing cadence, or reveal logic. Do not replace any image outside the declared closing-card logo slot, and do not restyle the assistant interface to match the supplied website. This contract declares no color variables. diff --git a/registry/blocks/chatgpt-exchange/TEMPLATE.md b/registry/blocks/chatgpt-exchange/TEMPLATE.md index 33e731704b..532a87f706 100644 --- a/registry/blocks/chatgpt-exchange/TEMPLATE.md +++ b/registry/blocks/chatgpt-exchange/TEMPLATE.md @@ -14,6 +14,10 @@ Only defaults declared in `data-composition-variables` are editable: Typed and streamed copy is length-locked to within 20% of the original. +## Safe editing mechanics + +Call `set_template_variable_defaults` once with the existing variable ids and their new defaults. Do not directly edit or rewrite `index.html` or its `data-composition-variables` attribute; the imported declaration is HTML-entity-encoded JSON and the setter preserves that encoding. Never edit `__template_baseline__.html` or a duplicate composition file. Validate after the setter succeeds. + ## 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. diff --git a/registry/blocks/claude-exchange/TEMPLATE.md b/registry/blocks/claude-exchange/TEMPLATE.md index 60fd67e7a1..44a69af50a 100644 --- a/registry/blocks/claude-exchange/TEMPLATE.md +++ b/registry/blocks/claude-exchange/TEMPLATE.md @@ -13,6 +13,10 @@ Only defaults declared in `data-composition-variables` are editable: Typed and streamed copy is length-locked to within 20% of the original. +## Safe editing mechanics + +Call `set_template_variable_defaults` once with the existing variable ids and their new defaults. Do not directly edit or rewrite `index.html` or its `data-composition-variables` attribute; the imported declaration is HTML-entity-encoded JSON and the setter preserves that encoding. Never edit `__template_baseline__.html` or a duplicate composition file. Validate after the setter succeeds. + ## 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. diff --git a/registry/blocks/message-thread-reveal/TEMPLATE.md b/registry/blocks/message-thread-reveal/TEMPLATE.md index 24d7ac52b2..2c769aeb16 100644 --- a/registry/blocks/message-thread-reveal/TEMPLATE.md +++ b/registry/blocks/message-thread-reveal/TEMPLATE.md @@ -15,6 +15,10 @@ Only defaults declared in `data-composition-variables` are editable: Keep replacement copy within 20% of the original length. +## Safe editing mechanics + +Call `set_template_variable_defaults` once with the existing variable ids and their new defaults. Do not directly edit or rewrite `index.html` or its `data-composition-variables` attribute; the imported declaration is HTML-entity-encoded JSON and the setter preserves that encoding. Never edit `__template_baseline__.html` or a duplicate composition file. For an image slot, pass only the token returned by an image tool. Validate after the setter succeeds. + ## Protected Preserve messaging chrome, bubble styling, receipts, palette, typography, geometry, scene structure, duration, timing, easing, and reveal behavior. Do not replace any image outside the declared link-card and closing-card logo slots, and do not recolor the messaging interface to match the supplied website. diff --git a/registry/blocks/notes-reveal/TEMPLATE.md b/registry/blocks/notes-reveal/TEMPLATE.md index 707128b36c..e7e092aba7 100644 --- a/registry/blocks/notes-reveal/TEMPLATE.md +++ b/registry/blocks/notes-reveal/TEMPLATE.md @@ -14,6 +14,10 @@ Only defaults declared in `data-composition-variables` are editable: Typed copy is length-locked to within 20% of the original. +## Safe editing mechanics + +Call `set_template_variable_defaults` once with the existing variable ids and their new defaults. Do not directly edit or rewrite `index.html` or its `data-composition-variables` attribute; the imported declaration is HTML-entity-encoded JSON and the setter preserves that encoding. Never edit `__template_baseline__.html` or a duplicate composition file. Validate after the setter succeeds. + ## Protected Preserve notes chrome, paper treatment, fonts, colors, checklist geometry, scene structure, duration, timing, easing, handwriting motion, and reveal cadence. diff --git a/registry/blocks/notification-cascade/TEMPLATE.md b/registry/blocks/notification-cascade/TEMPLATE.md index a628e33a1c..c59acee58b 100644 --- a/registry/blocks/notification-cascade/TEMPLATE.md +++ b/registry/blocks/notification-cascade/TEMPLATE.md @@ -14,6 +14,10 @@ Only defaults declared in `data-composition-variables` are editable: Keep replacement copy within 20% of the original length. +## Safe editing mechanics + +Call `set_template_variable_defaults` once with the existing variable ids and their new defaults. Do not directly edit or rewrite `index.html` or its `data-composition-variables` attribute; the imported declaration is HTML-entity-encoded JSON and the setter preserves that encoding. Never edit `__template_baseline__.html` or a duplicate composition file. For an image slot, pass only the token returned by an image tool. Validate after the setter succeeds. + ## Protected Do not change CSS, layout, backdrop, notification chrome, scene structure, duration, timing, easing, stacking, or reveal behavior. Do not replace any image outside the declared closing-card logo slot. Colors are protected because this template declares no color variables. diff --git a/registry/blocks/share-sheet-carousel/TEMPLATE.md b/registry/blocks/share-sheet-carousel/TEMPLATE.md index 62446192e1..863e4586d7 100644 --- a/registry/blocks/share-sheet-carousel/TEMPLATE.md +++ b/registry/blocks/share-sheet-carousel/TEMPLATE.md @@ -15,6 +15,10 @@ Only defaults declared in `data-composition-variables` are editable: Keep replacement copy within 20% of the original length. +## Safe editing mechanics + +Call `set_template_variable_defaults` once with the existing variable ids and their new defaults. Do not directly edit or rewrite `index.html` or its `data-composition-variables` attribute; the imported declaration is HTML-entity-encoded JSON and the setter preserves that encoding. Never edit `__template_baseline__.html` or a duplicate composition file. For an image slot, pass only the token returned by an image tool. Validate after the setter succeeds. + ## Protected Preserve the share-sheet palette, typography, buttons, geometry, carousel layout, scene structure, duration, timing, easing, and tap animation. Do not replace any image outside the declared slide and logo slots, and do not recolor the operating-system interface. diff --git a/registry/blocks/slack-notification-ad/TEMPLATE.md b/registry/blocks/slack-notification-ad/TEMPLATE.md index 5dc81065e6..bc7142bbfb 100644 --- a/registry/blocks/slack-notification-ad/TEMPLATE.md +++ b/registry/blocks/slack-notification-ad/TEMPLATE.md @@ -13,6 +13,10 @@ Only defaults declared in `data-composition-variables` are editable: Keep replacement copy within 20% of the original length. The payoff logo must remain legible in the existing icon tile. +## Safe editing mechanics + +Call `set_template_variable_defaults` once with the existing variable ids and their new defaults. Do not directly edit or rewrite `index.html` or its `data-composition-variables` attribute; the imported declaration is HTML-entity-encoded JSON and the setter preserves that encoding. Never edit `__template_baseline__.html` or a duplicate composition file. For the payoff logo, pass only the token returned by an image tool. Validate after the setter succeeds. + ## Protected Preserve the Slack mark on request notifications, iOS status/date/clock labels, wallpaper, notification chrome, fonts, palette, stacking geometry, scene structure, duration, timing, easing, and arrival cadence.