From 246377bc7bc1d2a853e023adf46abff987bd882e Mon Sep 17 00:00:00 2001 From: SandObserver <260779319+SandObserver@users.noreply.github.com> Date: Sun, 9 Aug 2026 15:14:01 -0400 Subject: [PATCH] Docs: separate the widget walkthrough from the reference widgets.md interleaved a three-step tutorial with lookup material, so reading it start to finish meant paging through field-type and ctx tables, and looking up ctx.fail meant scrolling past the walkthrough. The walkthrough now runs uninterrupted from the folder layout to the checklist, and every reference section sits below it under three headings. Each step links to the reference that expands it. No prose changed and every existing heading kept its text, so all anchors, including widgets.md#reporting-a-failure, still resolve. --- docs/widgets.md | 181 +++++++++++++++++++++++++++--------------------- 1 file changed, 103 insertions(+), 78 deletions(-) diff --git a/docs/widgets.md b/docs/widgets.md index f925899e..2a93107c 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -13,6 +13,12 @@ iframe and only ever fetches your own API. Copy [widget-template/](widget-template/) to `ui/widgets//` for a working starting point. +## Contents + +**Build a widget** [Folder structure](#folder-structure) · [1. The manifest](#1-the-manifest-widgetjson) · [2. Providing data](#2-providing-data-datajs) · [3. The frontend](#3-the-frontend-indexhtml) · [Cache-busting](#cache-busting) · [Checklist](#checklist) + +**Reference** [Manifest](#reference-the-manifest) · [data.js](#reference-datajs) · [Frontend](#reference-the-frontend) · [Toolbox](#toolbox-optional) + ## Folder structure ``` @@ -52,6 +58,101 @@ without entering one. `sizes` is the set of card sizes offered. a logged reason rather than crashing the server, so a typo disables just that widget. +Views, card backgrounds, every field type and the option pickers are in [the manifest reference](#reference-the-manifest). + +## 2. Providing data (data.js) + +Runs on the backend (Node, CommonJS). Export a single async function taking +`ctx`; the saved config is on `ctx.config`. + +```js +module.exports = async function (ctx) { + const { url, apiKey } = ctx.config; + const r = await ctx.fetchJSON(`${url}/api/items`, { + headers: { 'X-Api-Key': apiKey }, + timeout: 8000, + }); + return { items: r.data.slice(0, 10) }; +}; +``` + +What you return is served as-is at `/api/widget-data/`. Keep upstream calls +behind `ctx.fetchJSON` so they inherit the SSRF guard, IP pinning, size limit, +and the app's TLS-skip setting. + +The full `ctx` API, how to report a failure, and demo mode are in [the data.js reference](#reference-datajs). + +## 3. The frontend (index.html) + +Runs in a sandboxed iframe scaled to the widget's design resolution. Reads its +`id` from the query string, fetches its own data, and draws it. Keep everything +inline; there is no shared widget stylesheet, and the frontend must not make +external network calls. + +```html + + + + + + + +
Loading
+ + + +``` + +The widget's saved config, if the frontend needs it, is available the same way +at `/api/widget-config/`. + +The iframe's URL parameters, the widget CSP, text direction, the mobile active-state protocol and the canvas sizes are in [the frontend reference](#reference-the-frontend). + +## Cache-busting + +Nothing to do by hand. The release build hashes each widget entry file by content +and stamps the cache version into the manifest, the same way it version-stamps +`/css/` and `/js/` imports. + +## Checklist + +Manifests are validated in CI, so a schema mistake fails the PR rather than +silently disabling the widget at runtime. Run the same check locally with +`cd api && node --test`. + +- [ ] `ui/widgets//widget.json` with `name` (matching the folder), `label`, `sizes`, and `fields` +- [ ] `ui/widgets//data.js` exporting `module.exports = async (ctx) => ...`, for a widget that fetches. A widget that renders entirely in the browser (the clock, the dashboard switch) ships no `data.js` and never calls `/api/widget-data/`. +- [ ] `ui/widgets//index.html` that reads `?id=` and fetches `/api/widget-data/` +- [ ] For a multi-view widget: a `views` block with `viewField` and `defaultView` + +--- + +# Reference + +Lookup material for a widget already under way. The walkthrough above is the +other half of this page. + +## Reference: the manifest + ### Views (multiple looks) A widget can ship more than one frontend file and let the user pick between them @@ -264,25 +365,7 @@ if (ctx.endpoint === 'jobs') { `ctx.config` still holds the whole widget config, so secrets in the row are preserved the same way they are for a top-level field. -## 2. Providing data (data.js) - -Runs on the backend (Node, CommonJS). Export a single async function taking -`ctx`; the saved config is on `ctx.config`. - -```js -module.exports = async function (ctx) { - const { url, apiKey } = ctx.config; - const r = await ctx.fetchJSON(`${url}/api/items`, { - headers: { 'X-Api-Key': apiKey }, - timeout: 8000, - }); - return { items: r.data.slice(0, 10) }; -}; -``` - -What you return is served as-is at `/api/widget-data/`. Keep upstream calls -behind `ctx.fetchJSON` so they inherit the SSRF guard, IP pinning, size limit, -and the app's TLS-skip setting. +## Reference: data.js ### ctx reference @@ -394,48 +477,7 @@ Only the widget's own polling gets a demo body. A config-time `optionsFrom` fetch always runs the real code, so a visitor pressing Fetch in the settings sees it fail rather than a list of options that do not exist. -## 3. The frontend (index.html) - -Runs in a sandboxed iframe scaled to the widget's design resolution. Reads its -`id` from the query string, fetches its own data, and draws it. Keep everything -inline; there is no shared widget stylesheet, and the frontend must not make -external network calls. - -```html - - - - - - - -
Loading
- - - -``` - -The widget's saved config, if the frontend needs it, is available the same way -at `/api/widget-config/`. +## Reference: the frontend ### What the iframe is given @@ -584,20 +626,3 @@ success was. `sinceLabel(ts)` gives that "3m ago" label on its own. - `smoothPath(points)` returns a smoothed SVG path string through `[[x,y], ...]`. Check the toolbox before building a new visual by hand. - -## Cache-busting - -Nothing to do by hand. The release build hashes each widget entry file by content -and stamps the cache version into the manifest, the same way it version-stamps -`/css/` and `/js/` imports. - -## Checklist - -Manifests are validated in CI, so a schema mistake fails the PR rather than -silently disabling the widget at runtime. Run the same check locally with -`cd api && node --test`. - -- [ ] `ui/widgets//widget.json` with `name` (matching the folder), `label`, `sizes`, and `fields` -- [ ] `ui/widgets//data.js` exporting `module.exports = async (ctx) => ...`, for a widget that fetches. A widget that renders entirely in the browser (the clock, the dashboard switch) ships no `data.js` and never calls `/api/widget-data/`. -- [ ] `ui/widgets//index.html` that reads `?id=` and fetches `/api/widget-data/` -- [ ] For a multi-view widget: a `views` block with `viewField` and `defaultView`