Skip to main content

kindle-ui

Write the Kindle page of a bundle (kindle/src/ in a studio project): Solid JSX compiled to ES5 for the Kindle's old WebKit, the api.js pattern (one websocket, the host Envelope, the engine's view decoded with the wire codec, events encoded the same way), the <name>-wire virtual module with the view/event descriptors, ownPackage/staleness and reloading `/`, the 15 s silence timeout, and the build the studio runs.

Informations de source

Dépôt
esensible/extremers
Dernière activité de la source
13 octobre 2026 à 04:29
Langue détectée de SKILL.md
anglais
Étoiles
0
Forks
0

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
kindle-ui
description
Write the Kindle page of a bundle (kindle/src/ in a studio project): Solid JSX compiled to ES5 for the Kindle's old WebKit, the api.js pattern (one websocket, the host Envelope, the engine's view decoded with the wire codec, events encoded the same way), the <name>-wire virtual module with the view/event descriptors, ownPackage/staleness and reloading `/`, the 15 s silence timeout, and the build the studio runs.
# kindle-ui — the Kindle page of a bundle The Kindle is a **display kept in sync by the C6**, not an independent client. It opens one URL, `/`, which the C6 serves as the active bundle's page. The page shows what the engine publishes (its view, or its state when there is no view schema) and sends the engine's own events. The Race template's `kindle/src/` is the reference; read it alongside this (`race-example` skill). ## Files | Path | What | |---|---| | `kindle/src/index.jsx` | the entry: renders the app into `document.body` | | `kindle/src/api.js` | the link to the C6 (below): the store of the view, `postEvent`, `timestamp()` | | `kindle/src/*.jsx` | the pages, one per phase (Race: `idle.jsx`, `sequence.jsx`, `race.jsx`), shared bits (`common.jsx`, `confirm.jsx`) | | `kindle/src/style.css` | imported from `index.jsx`; every imported CSS file goes, in import order, into one minified `bundle-<hash>.css` | | `kindle/src/assets/*.svg` | `import url from './assets/x.svg'`: a `data:` URL (base64); also `.png .jpg .jpeg .gif .webp` | ## The language target: ES5 The Kindle browser is an old mobile WebKit. Write modern JS/JSX; the build turns it into ES5 (`@babel/preset-env`, targets `last 2 versions, IE 11`) and checks the result parses as ES5. Syntax is transformed; browser APIs are not polyfilled, so use only what the Race page uses (the DOM, `WebSocket`, `setTimeout`, `Date`) unless you have tried more on the Kindle. CSS `touch-action` is ignored there (the page's viewport meta stops pinch/double-tap zoom). The shared runtime (`client-common/host.js`) is written in ES5 itself. ## `api.js`: the pattern ```js import { createSignal } from 'solid-js'; import { createStore, reconcile } from 'solid-js/store'; import { decodeEnvelope, encodeHostEvent } from '../../../protocol-js/protocol.js'; import wire from '../../../wire/js/codec.js'; import { VIEW, EVENT } from 'race-wire'; // '<bundle name>-wire' import { ownPackage, staleness, reloadEntry, engineEvent, backEvent } from '../../../client-common/host.js'; const VIEW_DESC = wire.load(VIEW); const EVENT_DESC = wire.load(EVENT); const PACKAGE = ownPackage(); // from /pkg/<id>/ in this page's script URL const stale = staleness(PACKAGE); ``` - **The relative imports stay exactly as written.** `../../../protocol-js/…`, `../../../wire/js/codec.js` and `../../../client-common/host.js` resolve to the studio's vendored runtimes: the build mirrors the monorepo's depth. - **`<name>-wire`** is a virtual module: `VIEW` and `EVENT`, the bitwire descriptors compiled from `engine/view.schema.json` (or the state schema when there is no view) and `engine/event.schema.json`, as `Uint8Array`s. For a bundle named `race` it is `race-wire`; `engine-wire` works for any name. - **One websocket**, `ws://<host>/socket`, `binaryType = 'arraybuffer'`. Each message is a host **Envelope** (`decodeEnvelope`): `{ active, ui: { web, watch }, timestamp, state, … }`. `state` is the engine's view bytes: `wire.decode(VIEW_DESC, new Uint8Array(message.state))`, applied with `setStore(reconcile(view))` so only changed fields notify and a variant that went away disappears. - **Device clock.** Every Envelope carries `timestamp` (the C6's clock, epoch ms). Keep `offset = message.timestamp - Date.now()` and use `Date.now() + offset` for "now" everywhere: countdowns, and the `tapped_at` of an event. Never compare the view's times with the Kindle's own clock. - **Staleness.** `if (stale(message)) { reloadEntry(); return; }` before decoding: another bundle is active, or this bundle's page changed (its `ui.web` hash differs from the first Envelope's). `reloadEntry()` loads `/` and the C6 serves the right page. `ui.watch` changes don't reload the Kindle. - **Silence timeout: 15 s.** The C6 re-sends the state at least every 5 s (heartbeat). No message for 15 s means the link is dead even if the browser hasn't noticed: abandon the socket (don't wait for its close), show "Connection lost", and reconnect after 5 s. Restart the timer on every message. - **Events.** `send(engineEvent(PACKAGE, wire.encode(EVENT_DESC, event)))` with `send = (e) => socket.send(new Uint8Array(encodeHostEvent(e)).buffer)`, e.g. `postEvent({ line_stbd: {} })`, `postEvent({ bump_seq: { seconds: 30, tapped_at: timestamp() } })`. An event that fails the schema's checks on the C6 is refused and changes nothing. - **Back to Choose:** `send(backEvent())` (`SelectDefault`). - Start the socket on `DOMContentLoaded`. ## Reading the view Variants arrive as objects with exactly one key: test presence, never a tag string — `p.countdown !== undefined ? … : p.racing !== undefined ? …`. An optional field the engine left out is absent (`undefined`): give each reader a neutral value (`race.speed !== undefined ? race.speed : 0`). Until the first message the store has no phase variant: show "Loading…". The view holds values, not text: format times, speeds and distances in the page. ## The build (what `api.ui.build({ target: 'kindle' })` does) Rollup 4 in a Web Worker, over a virtual file system, with the Race client's plugin order. The entry is `kindle/src/index.jsx` (else `kindle/src/index.js`). **What an import may name** (anything else is a `B-RESOLVE` error at the import, listing these): | Import | Resolves to | |---|---| | `./x.jsx`, `../y.js` | the project's files; the extension may be left off (`.js`, `.jsx`, `.mjs`, `/index.js`, `/index.jsx` are tried) | | `../../../protocol-js/protocol.js` | the host protocol (Envelope, HostEvent) | | `../../../client-common/host.js`, `confirm.js`, `confirm-place.js` | packageId/ownPackage/staleness/reloadEntry/engineEvent/backEvent; the confirm logic | | `../../../wire/js/codec.js` | the bitwire codec (default export: `load`, `decode`, `encode`) | | `solid-js`, `solid-js/store`, `solid-js/web` | solid-js 1.9.17, the browser build | | `<name>-wire`, `engine-wire` | the descriptors: `VIEW` (or `STATE` when there is no `engine/view.schema.json`), and `EVENT` when there is an `engine/event.schema.json`; `Uint8Array`s. `<name>` is the bundle name, `studio.json`'s (`api.project.settings()`), which `api.ui.build()` and `api.bundle.pack()` both use; or import `engine-wire` | | `./style.css` | collected into the CSS file | | `./a.svg` etc. | a data URL string | JSON files cannot be imported (put the data in a `.js` module). The `../../../` paths are relative to `kindle/src/`, as in the monorepo. **In order:** `<name>-wire` → images → CSS → Solid JSX (`babel-preset-solid`, DOM output) → `@babel/preset-env` (`last 2 versions, IE 11`, helpers bundled once) in a **second pass** (one pass can lose a `_tmpl$` template declaration) → UMD named `main` → terser → `bundle-<hash>.js`, the CSS as `bundle-<hash>.css` (csso), and `index.html` (`@rollup/plugin-html`: title the bundle name, the no-zoom viewport meta, script and stylesheet at `/pkg/<id>/`, `<id>` the FNV-1a package id of the bundle name). The same files give the same bytes. **Checks** (each in `targets.kindle.checks`, and a diagnostic when it fails): `es5` — the JS parses as ES5 (acorn; `B-ES5` with line, column and the code near it: something preset-env cannot lower, e.g. a BigInt literal); `templates` — no `_tmpl$` left after minification (`B-TMPL`: a template called but never declared). Other diagnostics: `B-ENTRY` (no entry), `B-RESOLVE`, `B-SYNTAX` (Babel, with line/column), `B-WIRE` (a schema the wire compiler refuses), `B-CSS`, `B-ROLLUP`; warnings are `B-WARN`. All are in the result's `diagnostics` and the `#build-log` panel. The result is the project's last build: `bundle.pack` puts its files under `web/`. The C6 serves `index.html` at `/` and the other files under `/pkg/<id>/`, which is how `ownPackage()` knows its bundle. ## Touch and layout The Kindle is a touch e-ink screen: big buttons, high contrast, no animation. Race's `touch.js` blocks WebKit gesture events, multi-touch sequences (water droplets register as extra fingers), long-press selection and the context menu; its `confirm.jsx` asks for a second tap before an action that is easy to hit by mistake. Import `touch.js` from your entry as Race does.
Voir sur GitHub