authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2025-12-23 14:18:14-08:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-01-14 01:08:15-08:00
log18dc72c445e5feaed852e15d649d21e28992cb81
tree42a9ebbfb428acada18737904008ad47a6d3db7c
parentb2d921bae3440a3ea449d551a78cf81e4b13bd78
signature Commit is signed but in an unrecognized format.

chore(lib/render): rename `children` to `content`

this is not React and i want to make it extremely clear. i don't think `children` is a great word for the property. this now also lets you do this with meta tags: <meta name="description> enter your long description here </meta> but i find this not as compelling due to `meta.ts`

17 files changed, 83 insertions(+), 54 deletions(-)

framework/engine/marko-runtime.ts+8-2
...@@ -7,12 +7,18 @@ export const _template: typeof marko._template = (...@@ -7,12 +7,18 @@ export const _template: typeof marko._template = (
7 renderer,7 renderer,
8) => {8) => {
9 const { render: renderFn } = marko._template(templateId, renderer);9 const { render: renderFn } = marko._template(templateId, renderer);
10 function wrap(props: Record<string, unknown>, n: number) {10 function wrap({ content, ...attrs }: Record<string, unknown>, n: number) {
11 // Marko Custom Tags11 // Marko Custom Tags
12 const cloverAsyncMarker = { isAsync: false };12 const cloverAsyncMarker = { isAsync: false };
13 const r = render.current;13 const r = render.current;
14 // Support using Marko outside of Clover SSR14 // Support using Marko outside of Clover SSR
15 if (!r) return renderer(props, n);15 const props = {
16 content: content ? () => content : undefined,
17 ...attrs,
18 };
19 if (!r) {
20 return renderer(props, n);
21 }
16 render.setCurrent(null);22 render.setCurrent(null);
17 const markoResult = renderFn.call(renderer, {23 const markoResult = renderFn.call(renderer, {
18 ...props,24 ...props,
framework/generate.ts+1-1
...@@ -286,7 +286,7 @@ export async function preparePage(...@@ -286,7 +286,7 @@ export async function preparePage(
286 // -- html --286 // -- html --
287 let page = render.element(Page);287 let page = render.element(Page);
288 if (layout?.default) {288 if (layout?.default) {
289 page = render.element(layout.default, { children: page });289 page = render.element(layout.default, { content: page });
290 }290 }
291291
292 const sitegen = sg.context.init();292 const sitegen = sg.context.init();
framework/global.d.ts+11
...@@ -1,2 +1,13 @@...@@ -1,2 +1,13 @@
1type Timer = ReturnType<typeof setTimeout>;1type Timer = ReturnType<typeof setTimeout>;
2type Awaitable<T> = T | Promise<T>;2type Awaitable<T> = T | Promise<T>;
3
4declare global {
5 namespace JSX {
6 export type IntrinsicElements = jsx.ts.IntrinsicElements;
7 export type ElementChildrenAttribute = jsx.ts.ElementChildrenAttribute;
8 export type Element = jsx.ts.Element;
9 export type ElementType = jsx.ts.ElementType;
10 export type ElementClass = jsx.ts.ElementClass;
11 }
12}
13import * as jsx from "@clo/lib/render/jsx-runtime.ts";
framework/lib/view.ts+1-1
...@@ -72,7 +72,7 @@ export async function renderToString<K extends Key>(...@@ -72,7 +72,7 @@ export async function renderToString<K extends Key>(
7272
73 // -- html --73 // -- html --
74 let page: render.Element = render.element(component, props);74 let page: render.Element = render.element(component, props);
75 if (layout) page = render.element(layout, { children: page });75 if (layout) page = render.element(layout, { content: page });
76 const sitegen = sg.context.init();76 const sitegen = sg.context.init();
77 const { text: body } = await render.async(page, [sitegen]);77 const { text: body } = await render.async(page, [sitegen]);
7878
framework/mdx-hack.tsx+5-5
...@@ -1,15 +1,15 @@...@@ -1,15 +1,15 @@
1export function useMDXComponents() {1export function useMDXComponents() {
2 return {2 return {
3 pre: async (3 pre: async (
4 { filename, whitespace, tint, children: [, , { className, children }] },4 { filename, whitespace, tint, content: [, , { className, content }] },
5 ) => {5 ) => {
6 const lang = className.split("-")?.[1] ?? null;6 const lang = className.split("-")?.[1] ?? null;
7 const code = ["tsx", "jsx", "json", "html", "ts", "js"].includes(lang)7 const code = ["tsx", "jsx", "json", "html", "ts", "js"].includes(lang)
8 ? await highlight.highlightCode(8 ? await highlight.highlightCode(
9 children.trim(),9 content.trim(),
10 lang,10 lang,
11 )11 )
12 : children.trim();12 : content.trim();
13 return (13 return (
14 <figure class={["code", tint]}>14 <figure class={["code", tint]}>
15 {filename && <figcaption>{filename}</figcaption>}15 {filename && <figcaption>{filename}</figcaption>}
...@@ -19,10 +19,10 @@ export function useMDXComponents() {...@@ -19,10 +19,10 @@ export function useMDXComponents() {
19 </figure>19 </figure>
20 );20 );
21 },21 },
22 code: ({ children }) => {22 code: ({ content }) => {
23 return (23 return (
24 <code>24 <code>
25 {children}25 {content}
26 </code>26 </code>
27 );27 );
28 },28 },
lib/render.ts+18-9
...@@ -14,7 +14,7 @@...@@ -14,7 +14,7 @@
14 * import * as render from "@clo/lib/render.ts";14 * import * as render from "@clo/lib/render.ts";
15 * console.info(render.sync(<h1>good morning</h1>));15 * console.info(render.sync(<h1>good morning</h1>));
16 * // jsx is equivilent to:16 * // jsx is equivilent to:
17 * console.info(render.sync(render.element("h1", { children: "good morning" })));17 * console.info(render.sync(render.element("h1", { content: "good morning" })));
18 * ```18 * ```
19 *19 *
20 * NOTE: This module is not production ready, but it is in use at https://paperclover.net20 * NOTE: This module is not production ready, but it is in use at https://paperclover.net
...@@ -154,9 +154,9 @@ export function element(...@@ -154,9 +154,9 @@ export function element(
154export function resolvedElement(154export function resolvedElement(
155 type: ResolvedElement[1],155 type: ResolvedElement[1],
156 attrs: ResolvedElement[2],156 attrs: ResolvedElement[2],
157 children: ResolvedElement[3],157 content: ResolvedElement[3],
158): ResolvedElement {158): ResolvedElement {
159 return [kElement, type, attrs, children];159 return [kElement, type, attrs, content];
160}160}
161161
162/** internal global state while rendering */162/** internal global state while rendering */
...@@ -259,8 +259,8 @@ export function resolveNode(r: State, node: unknown): ResolvedNode {...@@ -259,8 +259,8 @@ export function resolveNode(r: State, node: unknown): ResolvedNode {
259 }259 }
260 }260 }
261 if (typeof tag !== "string") throw new Error("Unexpected " + inspect(type));261 if (typeof tag !== "string") throw new Error("Unexpected " + inspect(type));
262 const children = attrs?.children;262 const content = attrs?.content;
263 if (children) return [kElement, tag, attrs, resolveNode(r, children)];263 if (content) return [kElement, tag, attrs, resolveNode(r, content)];
264 return node;264 return node;
265 }265 }
266 if (type === kRaw) return node[1];266 if (type === kRaw) return node[1];
...@@ -275,7 +275,7 @@ export type ResolvedElement = [...@@ -275,7 +275,7 @@ export type ResolvedElement = [
275 tag: typeof kElement,275 tag: typeof kElement,
276 type: string,276 type: string,
277 attrs: Record<string, unknown>,277 attrs: Record<string, unknown>,
278 children: ResolvedNode,278 content: ResolvedNode,
279];279];
280/**280/**
281 * async rendering is done by creating an array of one item, which is already a281 * async rendering is done by creating an array of one item, which is already a
...@@ -307,7 +307,7 @@ export function stringifyNode(node: ResolvedNode): string {...@@ -307,7 +307,7 @@ export function stringifyNode(node: ResolvedNode): string {
307}307}
308308
309function stringifyElement(element: ResolvedElement) {309function stringifyElement(element: ResolvedElement) {
310 const { 1: tag, 2: attrs, 3: children } = element;310 const { 1: tag, 2: attrs, 3: content } = element;
311 let out = "<" + tag;311 let out = "<" + tag;
312 for (const prop in attrs) {312 for (const prop in attrs) {
313 const value = attrs[prop];313 const value = attrs[prop];
...@@ -336,8 +336,17 @@ function stringifyElement(element: ResolvedElement) {...@@ -336,8 +336,17 @@ function stringifyElement(element: ResolvedElement) {
336 throw new Error(336 throw new Error(
337 "Do not use the `dangerouslySetInnerHTML` attribute. Use `render.raw()`",337 "Do not use the `dangerouslySetInnerHTML` attribute. Use `render.raw()`",
338 );338 );
339 // Do not process these
340 case "children":339 case "children":
340 throw new Error(
341 "Do not set the `children` attribute to set element contents. Use `content`",
342 );
343 // Do not process these
344 case "content":
345 if (tag === "meta") {
346 attr = `${prop}=${quoteIfNeeded(escapeAttribute(String(value)))}`;
347 break;
348 }
349 continue;
341 case "ref":350 case "ref":
342 case "key":351 case "key":
343 continue;352 continue;
...@@ -346,7 +355,7 @@ function stringifyElement(element: ResolvedElement) {...@@ -346,7 +355,7 @@ function stringifyElement(element: ResolvedElement) {
346 out += attr;355 out += attr;
347 }356 }
348 out = out.trimEnd() + ">";357 out = out.trimEnd() + ">";
349 if (children) out += stringifyNode(children);358 if (content) out += stringifyNode(content);
350 if (359 if (
351 tag !== "br" &&360 tag !== "br" &&
352 tag !== "img" &&361 tag !== "img" &&
lib/render/jsx-runtime.ts+22-20
...@@ -6,11 +6,11 @@...@@ -6,11 +6,11 @@
6 *6 *
7 * declare global {7 * declare global {
8 * namespace JSX {8 * namespace JSX {
9 * export type IntrinsicElements = jsx.IntrinsicElements;9 * export type IntrinsicElements = jsx.ts.IntrinsicElements;
10 * export type ElementChildrenAttribute = jsx.ElementChildrenAttribute;10 * export type ElementChildrenAttribute = jsx.ts.ElementChildrenAttribute;
11 * export type Element = jsx.Element;11 * export type Element = jsx.ts.Element;
12 * export type ElementType = jsx.ElementType;12 * export type ElementType = jsx.ts.ElementType;
13 * export type ElementClass = jsx.ElementClass;13 * export type ElementClass = jsx.ts.ElementClass;
14 * }14 * }
15 * }15 * }
16 * import * as jsx from "@clo/lib/render/jsx-runtime.ts";16 * import * as jsx from "@clo/lib/render/jsx-runtime.ts";
...@@ -18,30 +18,30 @@...@@ -18,30 +18,30 @@
1818
19/** this element returns just its contents, and is used by `<>` fragment syntax. */19/** this element returns just its contents, and is used by `<>` fragment syntax. */
20export function Fragment(20export function Fragment(
21 { children }: { children: render.Node[] },21 { content }: { content: render.Node[] },
22): render.Node[] {22): render.Node[] {
23 return children;23 return content;
24}24}
2525
26/** construct a JSX element in production mode. */26/** construct a JSX element in production mode. */
27export function jsx(27export function jsx(
28 type: string | render.Component,28 type: string | render.Component,
29 attrs: Record<string, unknown>,29 { children, ...attrs }: Record<string, unknown>,
30): render.Element {30): render.Element {
31 if (typeof type !== "function" && typeof type !== "string") {31 if (typeof type !== "function" && typeof type !== "string") {
32 throw new Error("Invalid component type: " + render.inspect(type));32 throw new Error("Invalid component type: " + render.inspect(type));
33 }33 }
34 return render.element(type, attrs);34 return render.element(type, { content: children, ...attrs });
35}35}
3636
37/** construct a JSX element in development mode. */37/** construct a JSX element in development mode. */
38export function jsxDEV(38export function jsxDEV(
39 type: string | render.Component,39 type: string | render.Component,
40 attrs: Record<string, unknown>,40 { children, ...attrs }: Record<string, unknown>,
41 // Unused with the clover engine41 // Unused with the clover engine
42 _key: string,42 _key: string,
43 // Unused with the clover engine43 // Unused with the clover engine
44 _isStaticChildren: boolean,44 _staticContent: boolean,
45 source: render.SrcLoc,45 source: render.SrcLoc,
46): render.Element {46): render.Element {
47 ASSERT(47 ASSERT(
...@@ -59,21 +59,23 @@ export function jsxDEV(...@@ -59,21 +59,23 @@ export function jsxDEV(
59 );59 );
60 }60 }
6161
62 return render.element(type, attrs, source);62 return render.element(type, { content: children, ...attrs }, source);
63}63}
6464
65// Clover SSR engine does not yet distinguish between static and non-static JSX.65// Clover SSR engine does not yet distinguish between static and non-static JSX.
66export { jsx as jsxs };66export { jsx as jsxs };
6767
68export interface IntrinsicElements {68export declare namespace ts {
69 [name: string]: Record<string, unknown>;69 export interface IntrinsicElements {
70}70 [name: string]: Record<string, unknown>;
71export interface ElementChildrenAttribute {71 }
72 children: render.Node;72 export interface ElementChildrenAttribute {
73 content: render.Node;
74 }
75 export type Element = render.Element;
76 export type ElementType = keyof IntrinsicElements | render.Component;
77 export type ElementClass = ReturnType<render.Component>;
73}78}
74export type Element = render.Element;
75export type ElementType = keyof IntrinsicElements | render.Component;
76export type ElementClass = ReturnType<render.Component>;
7779
78import * as render from "../render.ts";80import * as render from "../render.ts";
79import { ASSERT } from "../assert.ts";81import { ASSERT } from "../assert.ts";
run.js+1
...@@ -49,6 +49,7 @@ console.debug = log.debug;...@@ -49,6 +49,7 @@ console.debug = log.debug;
49console["log"] = log.log;49console["log"] = log.log;
5050
51process.on("uncaughtException", (error) => {51process.on("uncaughtException", (error) => {
52 console.error("Uncaught Exception");
52 console.error(error);53 console.error(error);
53 process.exit(1);54 process.exit(1);
54});55});
src/blog/pages/community-translations.mdx+1-1
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1export const meta = { title: "about clover's community translations" };1export const meta = { title: "about clover's community translations" };
2export const layout = { default: ({ children }) => <main>{children}</main> };2export const layout = { default: ({ content }) => <main>{content}</main> };
33
4<a href="/">back to the home page</a>4<a href="/">back to the home page</a>
55
src/blog/tags/heading.tsx+2-2
...@@ -1,9 +1,9 @@...@@ -1,9 +1,9 @@
1export default function ({ level: Level, slug, children }) {1export default function ({ level: Level, slug, content }) {
2 return (2 return (
3 <>3 <>
4 <Level id={slug}>4 <Level id={slug}>
5 <a class="return" href="#toc">§</a>5 <a class="return" href="#toc">§</a>
6 {children}6 {content}
7 </Level>7 </Level>
8 </>8 </>
9 );9 );
src/blog/tags/layout.tsx+3-3
...@@ -11,7 +11,7 @@ interface LayoutProps {...@@ -11,7 +11,7 @@ interface LayoutProps {
11 meta: { title: string; description: string; keywords: string[] };11 meta: { title: string; description: string; keywords: string[] };
12 date: string;12 date: string;
13 slug: string;13 slug: string;
14 children: render.Node;14 content: render.Node;
15 translation?: {15 translation?: {
16 lang: string;16 lang: string;
1717
...@@ -26,7 +26,7 @@ export async function Layout({...@@ -26,7 +26,7 @@ export async function Layout({
26 meta: { title, description, keywords },26 meta: { title, description, keywords },
27 date,27 date,
28 slug,28 slug,
29 children,29 content,
30 translation,30 translation,
31}: LayoutProps) {31}: LayoutProps) {
32 const translations =32 const translations =
...@@ -95,7 +95,7 @@ export async function Layout({...@@ -95,7 +95,7 @@ export async function Layout({
95 </div>95 </div>
96 <hr />96 <hr />
97 </header>97 </header>
98 {children}98 {content}
99 </main>99 </main>
100 </>100 </>
101 );101 );
src/blog/tags/table-of-contents.tsx+2-2
...@@ -1,10 +1,10 @@...@@ -1,10 +1,10 @@
1import "./table-of-contents.css";1import "./table-of-contents.css";
22
3export default function ({ children }) {3export default function ({ content }) {
4 return (4 return (
5 <div id="toc">5 <div id="toc">
6 <h2>Contents:</h2>6 <h2>Contents:</h2>
7 {children}7 {content}
8 </div>8 </div>
9 );9 );
10}10}
src/file-viewer/backend.tsx+1-1
...@@ -249,7 +249,7 @@ function getPartialPage(c: Context, rawFilePath: string) {...@@ -249,7 +249,7 @@ function getPartialPage(c: Context, rawFilePath: string) {
249 if (!checkCotyledonCookie(c)) {249 if (!checkCotyledonCookie(c)) {
250 let root = Speedbump();250 let root = Speedbump();
251 // Remove the root element, it's created client side!251 // Remove the root element, it's created client side!
252 root = root[2].children as render.Element;252 root = root[2].content as render.Element;
253253
254 const html = render.sync(root).text;254 const html = render.sync(root).text;
255 c.header("X-Cotyledon", "true");255 c.header("X-Cotyledon", "true");
src/pages/license.mdx+1-1
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1export const CurrentYear = () => new Date().getFullYear(); 1export const CurrentYear = () => new Date().getFullYear();
2export default ({children}) => <main>{children}</main>;2export default ({ content }) => <main>{content}</main>;
3export const theme = {3export const theme = {
4 fg: '#ffe',4 fg: '#ffe',
5 bg: '#434',5 bg: '#434',
src/q+a/layout.tsx+4-5
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1import "./questions.css";1import "./questions.css";
22
3export default function QuestionLayout({ children }: { children: any }) {3export default function QuestionLayout({ content }: render.Node) {
4 addScript("./header.client.ts");4 addScript("./header.client.ts");
5 return (5 return (
6 <main class="qa">6 <main class="qa">
...@@ -9,7 +9,7 @@ export default function QuestionLayout({ children }: { children: any }) {...@@ -9,7 +9,7 @@ export default function QuestionLayout({ children }: { children: any }) {
9 <span class="n" style="--s:#999;margin:0 5px">&</span>9 <span class="n" style="--s:#999;margin:0 5px">&</span>
10 <span class="q" style="color:var(--b);--s:#994253">questions</span>10 <span class="q" style="color:var(--b);--s:#994253">questions</span>
11 </h1>11 </h1>
12 {children}12 {content}
13 </main>13 </main>
14 );14 );
15}15}
...@@ -21,10 +21,9 @@ export const theme = {...@@ -21,10 +21,9 @@ export const theme = {
21};21};
2222
23export const minimal = {23export const minimal = {
24 default: ({ children }: { children: any }) => (24 default: ({ content }: { content: any }) => <main class="qa">{content}</main>,
25 <main class="qa">{children}</main>
26 ),
27 theme,25 theme,
28};26};
2927
30import { addScript } from "#sitegen";28import { addScript } from "#sitegen";
29import * as render from "lib/render.ts";
src/q+a/tags/clickable-links.client.ts+1
...@@ -11,6 +11,7 @@ async function qaDate(el: HTMLAnchorElement) {...@@ -11,6 +11,7 @@ async function qaDate(el: HTMLAnchorElement) {
11 time.textContent = "copied permalink to question";11 time.textContent = "copied permalink to question";
12 time.classList.add("winner");12 time.classList.add("winner");
13 } catch (error) {13 } catch (error) {
14 console.error(error);
14 time.textContent = "failed to copy :(";15 time.textContent = "failed to copy :(";
15 }16 }
16 setTimeout(() => {17 setTimeout(() => {
tsconfig.json+1-1
...@@ -18,5 +18,5 @@...@@ -18,5 +18,5 @@
18 "noUncheckedIndexedAccess": true,18 "noUncheckedIndexedAccess": true,
19 "resolveJsonModule": true19 "resolveJsonModule": true
20 },20 },
21 "include": ["framework/**/*", "src/**/*", "lib/**/*"]21 "include": ["framework/**/*", "src/**/*", "lib/**/*", "framework/global.d.ts"]
22}22}