-
Notifications
You must be signed in to change notification settings - Fork 7
Add DG skill for wrapping HTML in Roam render code #1405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| --- | ||
| name: dg-create-roam-render-html-code | ||
| description: Wrap a completed HTML artifact in a Roam-compatible React iframe renderer and save it as a local code file. Use when the user asks to turn HTML output into Roam render HTML code. | ||
| --- | ||
|
|
||
| # DG Create Roam Render HTML Code | ||
|
|
||
| Take the completed HTML from the current request or from the HTML file the user identifies. Create one local file containing that HTML inside the following JavaScript wrapper. Default to a `.js` file in the current working directory named `<subject>-roam-render.js`; honor any filename, extension, or output path the user supplies. | ||
|
|
||
| Derive concise, descriptive names from the HTML's subject: | ||
|
|
||
| - Use an uppercase `UPPER_SNAKE_CASE` constant ending in `_HTML`. | ||
| - Use a lower camel case function name that describes the rendered artifact. | ||
| - Use a short, human-readable iframe title. | ||
|
|
||
| ```js | ||
| const ARTIFACT_HTML = "__HTML_SOURCE__"; | ||
|
|
||
| function usefulNamedFunction(props) { | ||
| const React = window.React; | ||
| if (!React) return null; | ||
|
|
||
| return React.createElement("iframe", { | ||
| title: "Useful named title", | ||
| srcDoc: ARTIFACT_HTML, | ||
| sandbox: "allow-scripts", | ||
| loading: "eager", | ||
| style: { | ||
| width: "100%", | ||
| height: "calc(100vh - 140px)", | ||
| minHeight: "720px", | ||
| display: "block", | ||
| border: "1px solid #d8d2c5", | ||
| borderRadius: "10px", | ||
| background: "#f4f0e6", | ||
| }, | ||
| }); | ||
| } | ||
| ``` | ||
|
|
||
| Replace the entire `"__HTML_SOURCE__"` string literal with a JSON-serialized JavaScript string containing the complete HTML. Do not summarize, redesign, minify, omit, trim, or normalize line endings in the HTML. Preserve the wrapper's styles unless the user explicitly requests changes. | ||
|
|
||
| Generate the literal mechanically rather than escaping template delimiters by hand. Given the original `html` string and the wrapper above as `wrapper`, use: | ||
|
|
||
| ```js | ||
| const htmlLiteral = JSON.stringify(html) | ||
| .replace(/\u2028/g, "\\u2028") | ||
| .replace(/\u2029/g, "\\u2029"); | ||
| const renderer = wrapper.replace('"__HTML_SOURCE__"', () => htmlLiteral); | ||
| ``` | ||
|
|
||
| The replacement callback preserves replacement-like text such as `$&` in the HTML. JSON serialization handles quotes, backslashes (including odd runs before backticks or `${` and a trailing backslash), control characters, and line endings without executing embedded expressions. Save `renderer` as the standalone `.js` file; do not wrap it in an HTML script tag. | ||
|
|
||
| Keep `sandbox: "allow-scripts"` for interactive artifacts. Omit `allow-same-origin` so embedded scripts cannot access Roam's parent DOM, authenticated storage, or APIs. For static HTML, use `sandbox: ""`. Add other sandbox permissions only for a specifically required capability; do not remove the sandbox or add `allow-same-origin` to restore parent access. Features requiring Roam APIs need a separately designed integration, not this iframe wrapper. | ||
|
|
||
| Before delivering a renderer, check its JavaScript syntax and verify that its evaluated `srcDoc` exactly equals the original HTML. Include backticks, `${`, quotes, dollar replacement patterns, CRLF, Unicode, and odd/even backslash runs before template delimiters and at end of input in regression checks. Run `node --test scripts/renderer.test.mjs` from this skill directory when changing this guidance. | ||
|
|
||
| After writing the file, report its path. Do not paste the complete generated file into the response unless the user asks. |
4 changes: 4 additions & 0 deletions
4
.agents/skills/dg-create-roam-render-html-code/agents/openai.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| interface: | ||
| display_name: "DG Create Roam Render HTML Code" | ||
| short_description: "Wrap HTML in a Roam iframe renderer" | ||
| default_prompt: "Use $dg-create-roam-render-html-code to save this HTML as Roam render code." |
46 changes: 46 additions & 0 deletions
46
.agents/skills/dg-create-roam-render-html-code/scripts/renderer.test.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import assert from "node:assert/strict"; | ||
| import { readFileSync } from "node:fs"; | ||
| import test from "node:test"; | ||
| import vm from "node:vm"; | ||
|
|
||
| const skill = readFileSync(new URL("../SKILL.md", import.meta.url), "utf8"); | ||
| const blocks = [...skill.matchAll(/```js\r?\n([\s\S]*?)```/g)].map( | ||
| (match) => match[1], | ||
| ); | ||
| const [wrapper, serializer] = blocks; | ||
|
|
||
| const render = (html) => { | ||
| const source = vm.runInNewContext(`${serializer}\nrenderer`, { | ||
| html, | ||
| wrapper, | ||
| }); | ||
| const script = new vm.Script(`${source}\nusefulNamedFunction({})`); | ||
| return script.runInNewContext({ | ||
| window: { React: { createElement: (tag, props) => ({ tag, props }) } }, | ||
| }); | ||
| }; | ||
|
|
||
| test("documented serialization preserves HTML exactly without evaluating it", () => { | ||
| const inputs = [ | ||
| "", | ||
| '<h1 title="quoted">Hello</h1>\r\n', | ||
| "` ${throwIfEvaluated()} $& $$ $' $`", | ||
| "Unicode: 🦉 \u2028 \u2029 \u0000\t\n", | ||
| "</script><script>window.parent.document</script>", | ||
| ]; | ||
| for (let count = 0; count <= 6; count++) { | ||
| const slashes = "\\".repeat(count); | ||
| for (const suffix of ["`", "${throwIfEvaluated()}", ""]) { | ||
| inputs.push(`<p>prefix</p>${slashes}${suffix}`); | ||
| } | ||
| } | ||
| for (const html of inputs) { | ||
| assert.equal(render(html).props.srcDoc, html, JSON.stringify(html)); | ||
| } | ||
| }); | ||
|
|
||
| test("documented iframe permits scripts without same-origin or other privileges", () => { | ||
| const result = render("<script>interactiveArtifact()</script>"); | ||
| assert.equal(result.tag, "iframe"); | ||
| assert.equal(result.props.sandbox, "allow-scripts"); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.