From 7a810e9fd6caca3bc561959ea07d14a0a25a2aca Mon Sep 17 00:00:00 2001 From: Melvin Carvalho Date: Tue, 9 Jun 2026 06:34:57 +0200 Subject: [PATCH 1/3] schema-apps: shared app.css + app.js, beautified baseline Move all styling to a shared app.css (gradient header band, icon, accent per type, hover rows, styled edit form) and the render/edit engine to a shared app.js. Each app is now just a JSON-LD island + a window.APP config. Retrofit person/organization/event/recipe; update the gate to inline app.js and verify each app renders (#type-label == @type, title + rows + form present). --- README.md | 67 ++++++++++++----------- app.css | 108 +++++++++++++++++++++++++++++++++++++ app.js | 75 ++++++++++++++++++++++++++ event.html | 132 +++++++--------------------------------------- gate/check.mjs | 61 ++++++++++----------- organization.html | 132 +++++++--------------------------------------- person.html | 130 +++++++-------------------------------------- recipe.html | 132 +++++++--------------------------------------- 8 files changed, 319 insertions(+), 518 deletions(-) create mode 100644 app.css create mode 100644 app.js diff --git a/README.md b/README.md index b2729ec..bc4fbe6 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,49 @@ # schema-apps -Single-page HTML apps for [schema.org](https://schema.org) types. Each app is a -**self-contained `.html` file** holding a **JSON-LD data island** plus an -auto-generated **read view** and **edit form** on top of it. No build, no -dependencies, no framework — open the file and it works. Served straight from -the `gh-pages` branch (this branch is the site). +Single-page apps for [schema.org](https://schema.org) types. Each app is a +JSON-LD **data island** plus an auto-generated **render + edit UI**. All styling +lives in one shared **`app.css`** and all behaviour in one shared **`app.js`**, +so every app stays tiny — just its data and a small config — and the whole +collection is restyled by editing a single file. Served straight from the +`gh-pages` branch (this branch is the site). -Live: `https://solid-apps.github.io/schema-apps/person.html` +Live: -## The app contract +## Anatomy of an app -Every `.html` (the reference is [`person.html`](./person.html)) must: +`person.html` is the reference. Each `.html` contains only: -1. Contain **exactly one** JSON-LD island: - `` that `JSON.parse`s, - with `"@context": "https://schema.org"` and `"@type"` equal to the type. -2. Declare its config in the page script: `const TYPE = "Person"` and a - `FIELDS` array (`{prop, label, type}` per property), and a `TITLE_PROP`. -3. Be named the **kebab-case** of the type — `Person` → `person.html`, - `JobPosting` → `job-posting.html`. -4. Include the contract elements: `#data` (island), `#view` (render target), - and `
`. -5. **Render and edit with no errors**: on load it shows the island's data, and - typing in the form writes changes back into the island live. +1. A JSON-LD island: `` + with `"@context": "https://schema.org"` and `"@type"`. +2. A mount point: `
`. +3. A config object: + ```js + window.APP = { + type: "Person", titleProp: "name", icon: "👤", accent: "#2d6cdf", + fields: [ { prop: "name", label: "Name", type: "text" }, … ], + }; + ``` +4. The two shared includes: `` and + ` -
-
-

Event

-
- -
- Edit - -
- -
- JSON-LD source -

-    
-
-
+
+ diff --git a/gate/check.mjs b/gate/check.mjs index ab9b2fd..bc8fb93 100644 --- a/gate/check.mjs +++ b/gate/check.mjs @@ -1,14 +1,13 @@ #!/usr/bin/env node // Structural + functional gate for solid-apps/schema-apps. -// For every .html app (excluding index.html) it asserts the contract: -// 1. exactly one JSON-LD island that JSON.parses -// 2. @context is schema.org and @type matches the file's `const TYPE` -// 3. the filename is the kebab-case of TYPE -// 4. the contract elements exist (#data, #view, #edit form) -// 5. it actually RUNS in a DOM (jsdom) with no console errors / uncaught throws -// 6. after load, the read view (#view) is populated and shows the title value -// Fails (exit 1) listing every problem. This is what lets prose-free app -// scaffolding auto-merge safely: a broken app cannot pass. +// Each app is a JSON-LD island + a window.APP config that the shared app.js +// renders. For every .html this asserts: +// 1. exactly one JSON-LD island that JSON.parses, @context schema.org +// 2. filename is the kebab-case of @type +// 3. it links ./app.css and ./app.js and has #data +
+// 4. it RUNS (jsdom, with app.js inlined) with no console errors +// 5. after render: #type-label === @type, the title + rows + form are present +// A broken app cannot pass — which is what lets app scaffolding auto-merge. import { readFileSync, readdirSync } from "node:fs"; import { dirname, join, basename } from "node:path"; import { fileURLToPath } from "node:url"; @@ -16,54 +15,52 @@ import { JSDOM, VirtualConsole } from "jsdom"; const root = join(dirname(fileURLToPath(import.meta.url)), ".."); const kebab = (s) => s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); +const appjs = readFileSync(join(root, "app.js"), "utf8"); const files = readdirSync(root).filter((f) => f.endsWith(".html") && f !== "index.html"); const fails = []; -const add = (f, msg) => fails.push(`${f}: ${msg}`); +const add = (f, m) => fails.push(`${f}: ${m}`); for (const f of files) { const html = readFileSync(join(root, f), "utf8"); - // 1. JSON-LD island + // 1. island const islands = [...html.matchAll(/]*application\/ld\+json[^>]*>([\s\S]*?)<\/script>/gi)]; if (islands.length !== 1) { add(f, `expected exactly 1 JSON-LD island, found ${islands.length}`); continue; } let data; try { data = JSON.parse(islands[0][1]); } catch (e) { add(f, `island is not valid JSON (${e.message})`); continue; } - - // 2. @context / @type const ctx = Array.isArray(data["@context"]) ? data["@context"].join(" ") : String(data["@context"] || ""); - if (!/schema\.org/.test(ctx)) add(f, `@context must reference schema.org (got "${data["@context"]}")`); - const typeMatch = html.match(/const\s+TYPE\s*=\s*["']([^"']+)["']/); - if (!typeMatch) add(f, `missing \`const TYPE = "..."\` declaration`); - const TYPE = typeMatch?.[1]; - if (TYPE && data["@type"] !== TYPE) add(f, `island @type ("${data["@type"]}") must equal const TYPE ("${TYPE}")`); + if (!/schema\.org/.test(ctx)) add(f, `@context must reference schema.org`); + const type = data["@type"]; + if (typeof type !== "string" || !type) { add(f, `island needs a string @type`); continue; } - // 3. filename == kebab(TYPE) - if (TYPE && basename(f, ".html") !== kebab(TYPE)) add(f, `filename should be "${kebab(TYPE)}.html" for type ${TYPE}`); + // 2. filename + if (basename(f, ".html") !== kebab(type)) add(f, `filename should be "${kebab(type)}.html" for @type ${type}`); - // 4. contract elements + // 3. wiring if (!/id=["']data["']/.test(html)) add(f, `missing #data island id`); - if (!/id=["']view["']/.test(html)) add(f, `missing #view render container`); - if (!/]*id=["']edit["']/.test(html)) add(f, `missing
`); + if (!/]*id=["']app["']/.test(html)) add(f, `missing
mount`); + if (!/href=["']\.\/app\.css["']/.test(html)) add(f, `must link ./app.css`); + if (!/src=["']\.\/app\.js["']/.test(html)) { add(f, `must include ./app.js`); continue; } - // 5/6. run it in jsdom + // 4/5. run with app.js inlined so jsdom executes the real engine + const runnable = html.replace(/\s*<\/script>/i, ``); const errors = []; const vc = new VirtualConsole(); vc.on("jsdomError", (e) => errors.push(e.message)); vc.on("error", (m) => errors.push(String(m))); try { - const dom = new JSDOM(html, { runScripts: "dangerously", virtualConsole: vc, pretendToBeVisual: true }); + const dom = new JSDOM(runnable, { runScripts: "dangerously", virtualConsole: vc, pretendToBeVisual: true }); await new Promise((r) => setTimeout(r, 30)); const doc = dom.window.document; + const label = doc.querySelector("#type-label"); + if (!label || label.textContent.trim() !== type) add(f, `#type-label ("${label?.textContent.trim()}") must equal @type ("${type}")`); const view = doc.querySelector("#view"); - if (!view || view.textContent.trim() === "") add(f, `#view is empty after load (render did not run)`); - else { - const titleProp = (html.match(/TITLE_PROP\s*=\s*["']([^"']+)["']/) || [])[1] || "name"; - const titleVal = data[titleProp]; - if (titleVal && !view.textContent.includes(String(titleVal))) - add(f, `#view does not show the ${titleProp} value ("${titleVal}")`); - } + if (!view || view.children.length === 0) add(f, `#view rendered no rows`); + const title = doc.querySelector("#title"); + if (!title || title.textContent.trim() === "" || title.textContent.includes("untitled")) + add(f, `#title did not render the title value`); const inputs = doc.querySelectorAll("#edit input, #edit textarea"); if (inputs.length < 2) add(f, `edit form has too few inputs (${inputs.length})`); dom.window.close(); diff --git a/organization.html b/organization.html index 45162c1..4afd148 100644 --- a/organization.html +++ b/organization.html @@ -4,39 +4,9 @@ Organization — schema.org app - - + - -
-
-

Organization

-
- -
- Edit - -
- -
- JSON-LD source -

-    
-
-
+
+ diff --git a/person.html b/person.html index ef2b088..257f90a 100644 --- a/person.html +++ b/person.html @@ -4,36 +4,7 @@ Person — schema.org app - - + @@ -50,90 +21,25 @@ } -
-
-

Person

-
- -
- Edit -
-
- -
- JSON-LD source -

-    
-
-
+
+ + diff --git a/recipe.html b/recipe.html index 28546ea..52098a7 100644 --- a/recipe.html +++ b/recipe.html @@ -4,39 +4,9 @@ Recipe — schema.org app - - + - -
-
-

Recipe

-
- -
- Edit -
-
- -
- JSON-LD source -

-    
-
-
+
+ From 5dd8ba9a87e6a98231dc449d52e8f7f9d03f8fe9 Mon Sep 17 00:00:00 2001 From: Melvin Carvalho Date: Tue, 9 Jun 2026 06:36:13 +0200 Subject: [PATCH 2/3] schema-apps: retrofit Product app to shared-asset format --- product.html | 132 +++++++-------------------------------------------- 1 file changed, 18 insertions(+), 114 deletions(-) diff --git a/product.html b/product.html index 2b05d46..65e82b0 100644 --- a/product.html +++ b/product.html @@ -4,39 +4,9 @@ Product — schema.org app - - + - -
-
-

Product

-
- -
- Edit -
-
- -
- JSON-LD source -

-    
-
-
+
+ From b54cdcb33f408a39f6b8d33e409d027b412c0e15 Mon Sep 17 00:00:00 2001 From: Melvin Carvalho Date: Tue, 9 Jun 2026 06:37:52 +0200 Subject: [PATCH 3/3] schema-apps: retrofit Book app to shared-asset format --- book.html | 132 ++++++++---------------------------------------------- 1 file changed, 18 insertions(+), 114 deletions(-) diff --git a/book.html b/book.html index a74a12d..b8c0e3d 100644 --- a/book.html +++ b/book.html @@ -4,39 +4,9 @@ Book — schema.org app - - + - -
-
-

Book

-
- -
- Edit -
-
- -
- JSON-LD source -

-    
-
-
+
+