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 = (
77 renderer,
88) => {
99 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) {
1111 // Marko Custom Tags
1212 const cloverAsyncMarker = { isAsync: false };
1313 const r = render.current;
1414 // 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 }
1622 render.setCurrent(null);
1723 const markoResult = renderFn.call(renderer, {
1824 ...props,
framework/generate.ts+1-1
......@@ -286,7 +286,7 @@ export async function preparePage(
286286 // -- html --
287287 let page = render.element(Page);
288288 if (layout?.default) {
289 page = render.element(layout.default, { children: page });
289 page = render.element(layout.default, { content: page });
290290 }
291291
292292 const sitegen = sg.context.init();
framework/global.d.ts+11
......@@ -1,2 +1,13 @@
11type Timer = ReturnType<typeof setTimeout>;
22type 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>(
7272
7373 // -- html --
7474 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 });
7676 const sitegen = sg.context.init();
7777 const { text: body } = await render.async(page, [sitegen]);
7878
framework/mdx-hack.tsx+5-5
......@@ -1,15 +1,15 @@
11export function useMDXComponents() {
22 return {
33 pre: async (
4 { filename, whitespace, tint, children: [, , { className, children }] },
4 { filename, whitespace, tint, content: [, , { className, content }] },
55 ) => {
66 const lang = className.split("-")?.[1] ?? null;
77 const code = ["tsx", "jsx", "json", "html", "ts", "js"].includes(lang)
88 ? await highlight.highlightCode(
9 children.trim(),
9 content.trim(),
1010 lang,
1111 )
12 : children.trim();
12 : content.trim();
1313 return (
1414 <figure class={["code", tint]}>
1515 {filename && <figcaption>{filename}</figcaption>}
......@@ -19,10 +19,10 @@ export function useMDXComponents() {
1919 </figure>
2020 );
2121 },
22 code: ({ children }) => {
22 code: ({ content }) => {
2323 return (
2424 <code>
25 {children}
25 {content}
2626 </code>
2727 );
2828 },
lib/render.ts+18-9
......@@ -14,7 +14,7 @@
1414 * import * as render from "@clo/lib/render.ts";
1515 * console.info(render.sync(<h1>good morning</h1>));
1616 * // 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" })));
1818 * ```
1919 *
2020 * NOTE: This module is not production ready, but it is in use at https://paperclover.net
......@@ -154,9 +154,9 @@ export function element(
154154export function resolvedElement(
155155 type: ResolvedElement[1],
156156 attrs: ResolvedElement[2],
157 children: ResolvedElement[3],
157 content: ResolvedElement[3],
158158): ResolvedElement {
159 return [kElement, type, attrs, children];
159 return [kElement, type, attrs, content];
160160}
161161
162162/** internal global state while rendering */
......@@ -259,8 +259,8 @@ export function resolveNode(r: State, node: unknown): ResolvedNode {
259259 }
260260 }
261261 if (typeof tag !== "string") throw new Error("Unexpected " + inspect(type));
262 const children = attrs?.children;
263 if (children) return [kElement, tag, attrs, resolveNode(r, children)];
262 const content = attrs?.content;
263 if (content) return [kElement, tag, attrs, resolveNode(r, content)];
264264 return node;
265265 }
266266 if (type === kRaw) return node[1];
......@@ -275,7 +275,7 @@ export type ResolvedElement = [
275275 tag: typeof kElement,
276276 type: string,
277277 attrs: Record<string, unknown>,
278 children: ResolvedNode,
278 content: ResolvedNode,
279279];
280280/**
281281 * 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 {
307307}
308308
309309function stringifyElement(element: ResolvedElement) {
310 const { 1: tag, 2: attrs, 3: children } = element;
310 const { 1: tag, 2: attrs, 3: content } = element;
311311 let out = "<" + tag;
312312 for (const prop in attrs) {
313313 const value = attrs[prop];
......@@ -336,8 +336,17 @@ function stringifyElement(element: ResolvedElement) {
336336 throw new Error(
337337 "Do not use the `dangerouslySetInnerHTML` attribute. Use `render.raw()`",
338338 );
339 // Do not process these
340339 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;
341350 case "ref":
342351 case "key":
343352 continue;
......@@ -346,7 +355,7 @@ function stringifyElement(element: ResolvedElement) {
346355 out += attr;
347356 }
348357 out = out.trimEnd() + ">";
349 if (children) out += stringifyNode(children);
358 if (content) out += stringifyNode(content);
350359 if (
351360 tag !== "br" &&
352361 tag !== "img" &&
lib/render/jsx-runtime.ts+22-20
......@@ -6,11 +6,11 @@
66 *
77 * declare global {
88 * namespace JSX {
9 * export type IntrinsicElements = jsx.IntrinsicElements;
10 * export type ElementChildrenAttribute = jsx.ElementChildrenAttribute;
11 * export type Element = jsx.Element;
12 * export type ElementType = jsx.ElementType;
13 * export type ElementClass = jsx.ElementClass;
9 * export type IntrinsicElements = jsx.ts.IntrinsicElements;
10 * export type ElementChildrenAttribute = jsx.ts.ElementChildrenAttribute;
11 * export type Element = jsx.ts.Element;
12 * export type ElementType = jsx.ts.ElementType;
13 * export type ElementClass = jsx.ts.ElementClass;
1414 * }
1515 * }
1616 * import * as jsx from "@clo/lib/render/jsx-runtime.ts";
......@@ -18,30 +18,30 @@
1818
1919/** this element returns just its contents, and is used by `<>` fragment syntax. */
2020export function Fragment(
21 { children }: { children: render.Node[] },
21 { content }: { content: render.Node[] },
2222): render.Node[] {
23 return children;
23 return content;
2424}
2525
2626/** construct a JSX element in production mode. */
2727export function jsx(
2828 type: string | render.Component,
29 attrs: Record<string, unknown>,
29 { children, ...attrs }: Record<string, unknown>,
3030): render.Element {
3131 if (typeof type !== "function" && typeof type !== "string") {
3232 throw new Error("Invalid component type: " + render.inspect(type));
3333 }
34 return render.element(type, attrs);
34 return render.element(type, { content: children, ...attrs });
3535}
3636
3737/** construct a JSX element in development mode. */
3838export function jsxDEV(
3939 type: string | render.Component,
40 attrs: Record<string, unknown>,
40 { children, ...attrs }: Record<string, unknown>,
4141 // Unused with the clover engine
4242 _key: string,
4343 // Unused with the clover engine
44 _isStaticChildren: boolean,
44 _staticContent: boolean,
4545 source: render.SrcLoc,
4646): render.Element {
4747 ASSERT(
......@@ -59,21 +59,23 @@ export function jsxDEV(
5959 );
6060 }
6161
62 return render.element(type, attrs, source);
62 return render.element(type, { content: children, ...attrs }, source);
6363}
6464
6565// Clover SSR engine does not yet distinguish between static and non-static JSX.
6666export { jsx as jsxs };
6767
68export interface IntrinsicElements {
69 [name: string]: Record<string, unknown>;
70}
71export interface ElementChildrenAttribute {
72 children: render.Node;
68export declare namespace ts {
69 export interface IntrinsicElements {
70 [name: string]: Record<string, unknown>;
71 }
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>;
7378}
74export type Element = render.Element;
75export type ElementType = keyof IntrinsicElements | render.Component;
76export type ElementClass = ReturnType<render.Component>;
7779
7880import * as render from "../render.ts";
7981import { ASSERT } from "../assert.ts";
run.js+1
......@@ -49,6 +49,7 @@ console.debug = log.debug;
4949console["log"] = log.log;
5050
5151process.on("uncaughtException", (error) => {
52 console.error("Uncaught Exception");
5253 console.error(error);
5354 process.exit(1);
5455});
src/blog/pages/community-translations.mdx+1-1
......@@ -1,5 +1,5 @@
11export 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
44<a href="/">back to the home page</a>
55
src/blog/tags/heading.tsx+2-2
......@@ -1,9 +1,9 @@
1export default function ({ level: Level, slug, children }) {
1export default function ({ level: Level, slug, content }) {
22 return (
33 <>
44 <Level id={slug}>
55 <a class="return" href="#toc">§</a>
6 {children}
6 {content}
77 </Level>
88 </>
99 );
src/blog/tags/layout.tsx+3-3
......@@ -11,7 +11,7 @@ interface LayoutProps {
1111 meta: { title: string; description: string; keywords: string[] };
1212 date: string;
1313 slug: string;
14 children: render.Node;
14 content: render.Node;
1515 translation?: {
1616 lang: string;
1717
......@@ -26,7 +26,7 @@ export async function Layout({
2626 meta: { title, description, keywords },
2727 date,
2828 slug,
29 children,
29 content,
3030 translation,
3131}: LayoutProps) {
3232 const translations =
......@@ -95,7 +95,7 @@ export async function Layout({
9595 </div>
9696 <hr />
9797 </header>
98 {children}
98 {content}
9999 </main>
100100 </>
101101 );
src/blog/tags/table-of-contents.tsx+2-2
......@@ -1,10 +1,10 @@
11import "./table-of-contents.css";
22
3export default function ({ children }) {
3export default function ({ content }) {
44 return (
55 <div id="toc">
66 <h2>Contents:</h2>
7 {children}
7 {content}
88 </div>
99 );
1010}
src/file-viewer/backend.tsx+1-1
......@@ -249,7 +249,7 @@ function getPartialPage(c: Context, rawFilePath: string) {
249249 if (!checkCotyledonCookie(c)) {
250250 let root = Speedbump();
251251 // 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
254254 const html = render.sync(root).text;
255255 c.header("X-Cotyledon", "true");
src/pages/license.mdx+1-1
......@@ -1,5 +1,5 @@
11export const CurrentYear = () => new Date().getFullYear();
2export default ({children}) => <main>{children}</main>;
2export default ({ content }) => <main>{content}</main>;
33export const theme = {
44 fg: '#ffe',
55 bg: '#434',
src/q+a/layout.tsx+4-5
......@@ -1,6 +1,6 @@
11import "./questions.css";
22
3export default function QuestionLayout({ children }: { children: any }) {
3export default function QuestionLayout({ content }: render.Node) {
44 addScript("./header.client.ts");
55 return (
66 <main class="qa">
......@@ -9,7 +9,7 @@ export default function QuestionLayout({ children }: { children: any }) {
99 <span class="n" style="--s:#999;margin:0 5px">&</span>
1010 <span class="q" style="color:var(--b);--s:#994253">questions</span>
1111 </h1>
12 {children}
12 {content}
1313 </main>
1414 );
1515}
......@@ -21,10 +21,9 @@ export const theme = {
2121};
2222
2323export const minimal = {
24 default: ({ children }: { children: any }) => (
25 <main class="qa">{children}</main>
26 ),
24 default: ({ content }: { content: any }) => <main class="qa">{content}</main>,
2725 theme,
2826};
2927
3028import { 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) {
1111 time.textContent = "copied permalink to question";
1212 time.classList.add("winner");
1313 } catch (error) {
14 console.error(error);
1415 time.textContent = "failed to copy :(";
1516 }
1617 setTimeout(() => {
tsconfig.json+1-1
......@@ -18,5 +18,5 @@
1818 "noUncheckedIndexedAccess": true,
1919 "resolveJsonModule": true
2020 },
21 "include": ["framework/**/*", "src/**/*", "lib/**/*"]
21 "include": ["framework/**/*", "src/**/*", "lib/**/*", "framework/global.d.ts"]
2222}