← Commit history
SKILL.md+6−10
@@ -4,7 +4,7 @@ user-invocable: true description: >-   The Adom UI Design System: how the AI builds AND tests interfaces people love to use in the Adom   ecosystem. The AI writes the app, shows it to the user (in the user's own already-signed-in native-  browser via Adom Bridge, in pup for the logged-out experience, or in a Hydrogen webview), drives+  browser via Adom Desktop, in pup for the logged-out experience, or in a Hydrogen webview), drives   the front-end over a live SSE/WebSocket channel to exercise every path (auto-click buttons,   show/hide floaty toolbars, load a different symbol or footprint, click send-to-kicad), and   ralph-loops with screenshots until the UI is perfect. Covers the user-first interaction rules@@ -67,8 +67,8 @@ The AI picks the surface by what it needs to test, and it can use more than one  | Surface | What it is | Use it for | |---|---|---|-| **Native browser** (**nb**) | The user's own Chrome or Edge, driven through Adom Bridge | The real, signed-in experience. The browser is already the user's, so the app opens **logged in with zero friction**. Huge for demoing and testing anything behind a login. |-| **pup** | A clean, scriptable Chrome the AI fully owns, via Adom Bridge's pup bridge | The **fresh, logged-out, first-time-user** experience, and automated screenshot loops that should not fight the user's own tabs. |+| **Native browser** (**nb**) | The user's own Chrome or Edge, driven through Adom Desktop | The real, signed-in experience. The browser is already the user's, so the app opens **logged in with zero friction**. Huge for demoing and testing anything behind a login. |+| **pup** | A clean, scriptable Chrome the AI fully owns, via `adom-desktop` | The **fresh, logged-out, first-time-user** experience, and automated screenshot loops that should not fight the user's own tabs. | | **Hydrogen webview** | An in-workspace browser tab | Showing a result inside the workbench, next to the code and the chat. |  The native-browser and pup split is the point: the AI can open the **logged-in path in the native browser** (real cookies) and the **logged-out path in pup** at the same time, and verify both experiences. Choose deliberately, and tell the user which one and why.@@ -158,7 +158,6 @@ When several iterations of the same animation each help only a little, stop tuni  The same app should feel native on a 32 inch monitor and on a phone. -- **Fill the window.** An app gets 100% of the width it is given. A centered column with a `max-width` leaves dead margin on a wide monitor, and on a dashboard that margin is columns the user paid for: a card grid on `repeat(auto-fill, minmax(Npx, 1fr))` should buy MORE columns as the window grows, never a wider gutter. Cap the measure only where a cap is the point, which is running prose, and cap that element, never the page. The linter's fluid-layout check (UI-09) flags a large fixed width; the fix is to remove the cap, not to hide the number in a variable. - **Design the layout to reshape, not to shrink.** Define a few breakpoints (phone, tablet, desktop) and change the layout at each, rather than scaling one fixed desktop layout down until it is unusable. - **Rich on desktop, focused on mobile.** A desktop has room for side panels, dense tables, persistent toolbars, secondary stats. On a phone, collapse those into a single column, move actions into a bottom bar or a menu, and reveal secondary detail on demand. The header models this already: the subject meta line hides below roughly 720px and returns on request. - **Touch targets are at least 44 by 44 px.** Fingers are not cursors.@@ -418,9 +417,7 @@ slip straight through it, and all three have shipped:  ## 13. The skill map -This whole system ships as **one installable skill**. `adom-wiki pkg install adom/adom-ui-design` drops the design-system skill **plus every sub-skill below** onto your container as separate skill files, so the AI loads each by its own trigger words. The rows that link into `files/skills/` live in **this** repo; click to read them. The rest are existing platform skills.--If you add a sub-skill to `skills/`, ADD ITS ROW HERE in the same commit. A skill that ships but is not listed is a skill nobody loads: `scrollbars` sat in this repo unlisted, and apps kept shipping the default OS scrollbar because the AI had no way to discover it.+This whole system ships as **one installable skill**. `adom-wiki pkg install adom/adom-ui-design` drops the design-system skill **plus all seven skills below** onto your container as separate skill files, so the AI loads each by its own trigger words. The first seven live as files in **this** repo; click to read them. The rest are existing platform skills.  | Skill | Governs | |---|---|@@ -435,7 +432,6 @@ If you add a sub-skill to `skills/`, ADD ITS ROW HERE in the same commit. A skil | [**status-leds**](https://wiki.adom.inc/adom/adom-ui-design/files/skills/status-leds/SKILL.md) | One status language: a compact LED pill row (the chip-fetcher pattern canonized), prose on tap never by default, red LEDs always carry their next step, staleness escalates, and the row never disagrees with the dock card's LED | | [**newbie-graduation**](https://wiki.adom.inc/adom/adom-ui-design/files/skills/newbie-graduation/SKILL.md) | The scaffolding-fade contract: Fresh, Graduated, Recallable states per app; triggers are dismissal, 3 green launches, or the first AI-driven action the bridge sees; the say strip folds last because handing the user to the AI is the point | | [**tooltips**](https://wiki.adom.inc/adom/adom-ui-design/files/skills/tooltips/SKILL.md) | Great desktop tooltips: 600ms reveal, the fixed-div renderer, placement that never covers what you hover, edge-aware flipping, the z-index ladder |-| [**scrollbars**](https://wiki.adom.inc/adom/adom-ui-design/files/skills/scrollbars/SKILL.md) | The pretty scrollbar and only the pretty one: a thin translucent pill on a transparent track, teal only on hover, the `background-clip: padding-box` trick that floats it off the edge, the inset track that stops a bar slicing a rounded container's corners, and never a page-level horizontal scrollbar | | [**floating-windows**](https://wiki.adom.inc/adom/adom-ui-design/files/skills/floating-windows/SKILL.md) | Draggable floaty tool windows and HUDs that never get in the way | | [**toasts**](https://wiki.adom.inc/adom/adom-ui-design/files/skills/toasts/SKILL.md) | Non-blocking confirmations, and the AI's live narration channel to the user | | [**responsive-ui**](https://wiki.adom.inc/adom/adom-ui-design/files/skills/responsive-ui/SKILL.md) | Phone-to-desktop layouts, touch targets, and tooltips on touch where hover does not exist |@@ -446,7 +442,7 @@ If you add a sub-skill to `skills/`, ADD ITS ROW HERE in the same commit. A skil | [app-creator](https://wiki.adom.inc/adom/app-creator) | Scaffolding a webview app: two-way comms, proxy URL, favicon, publish flow | | `adom-app-header` | The 44px three-zone header and the app-info popover | | [adom-screenshot](https://wiki.adom.inc/adom/adom-screenshot) | Ralph loops and the visual feedback loop for debugging UI |-| `pup-vs-webview` / the pup bridge skill | Choosing and driving pup, webview, or the user's native browser |+| `pup-vs-webview` / `adom-desktop-pup` | Choosing and driving pup, webview, or the user's native browser | | [adom-cli-design](https://wiki.adom.inc/adom/adom-cli-design) | The `OK:`/`ERROR:`/`Hint:` CLI contract, in Rust | | `ai-hints-in-clis` / `relay-hint-pattern` | Writing hints, and redirecting direct HTTP callers to the CLI | | [3d-viewer-design](https://wiki.adom.inc/adom/3d-viewer-design) / `measure-tool-design` | Every rule for Adom 3D viewports and measurement |@@ -456,7 +452,7 @@ If you add a sub-skill to `skills/`, ADD ITS ROW HERE in the same commit. A skil | `adom-wiki-hero` | Rendering a billboard-grade hero image | | [adom-ui-linter](https://wiki.adom.inc/adom/adom-ui-linter) | The deterministic check suite that enforces this page | -*(Rows linking into `files/skills/` are files in this repo and install together with the design system. The other linked skills are existing platform skills with their own pages; plain-text ones have no standalone page yet.)*+*(The first seven are files in this repo and install together with the design system. The other linked skills are existing platform skills with their own pages; plain-text ones have no standalone page yet.)*  --- 
assets/module/newbie-panel.js+84−7
@@ -112,6 +112,23 @@   var TWO_COL_MIN = 700, PHRASE_BUDGET = 336;   var MIN_API = [1, 0, 142]; +  /* Hydrogen 1.0.187+ answers a send with a JOB and reports progress on+   * GET /ai-threads/run-prompt/status?job=<uuid>. Measured on the VM: tab visible ~0.1s,+   * session id ~2.5s, sent by 3-5s, a trivial answer done ~8s. One poll a second is the+   * right cadence for that shape, and it is an in-memory read on the other end. */+  var PHASE = {+    opening:               'Opening a new AI chat…',+    prefilled:             'Prompt ready…',+    submitted:             'Sent, waiting for the AI…',+    working:               'The AI is working…',+    needs_you:             'The AI needs you. Open the tab.',+    done:                  'Done.',+    submit_failed:         'The prompt is in the tab. Press Enter there to send it.',+    submitted_unidentified:'Sent, but the AI never checked in.',+    submit_unconfirmed:    'Sent, but delivery was not confirmed.',+    still_working:         'The AI is still working. Open the tab.'+  };+   function css() {     if (document.getElementById('anp-css')) return;     var s = document.createElement('style');@@ -174,6 +191,9 @@     if (!toastHost) { toastHost = el('div', 'anp-toasts'); document.body.appendChild(toastHost); }     var t = el('div', 'anp-toast ' + (kind || 'info'), esc(msg));     toastHost.appendChild(t);+    /* retextable, so a long send can narrate its phases in ONE toast instead of+     * stacking six of them on top of each other. */+    t.setText = function (next) { t.innerHTML = esc(next); return t; };     setTimeout(function () { t.remove(); }, ms || (kind === 'err' ? 7000 : 4000));     return t;   }@@ -224,7 +244,7 @@       })       .catch(function () { return { ok: true, version: null, open: true }; });   }-  function send(base, phrase, appName, slug) {+  function send(base, phrase, appName, slug, onProgress) {     return gate(base).then(function (g) {       if (!g.ok) throw new Error('Adom needs updating to send prompts (found ' + g.version + ', needs 1.0.142). Run: adom update');       var ctl = new AbortController(), timer = setTimeout(function () { ctl.abort(); }, 120000);@@ -241,12 +261,55 @@         .then(function (res) {           clearTimeout(timer);           var j = res.j || {};-          /* THREE states, never two. delivered_to is an ARRAY; a thread that opened but did-           * not confirm delivery is NOT a failure, and retrying it double-sends. */+          if (!(res.status >= 200 && res.status < 300))+            throw new Error((j._hints && j._hints.note) || j.error || ('AI threads returned ' + res.status));+          /* PRE-1.0.187 SHAPE, kept because older Hydrogen builds still answer this way.+           * delivered_to is an ARRAY; a thread that opened but did not confirm delivery is+           * NOT a failure, and retrying it double-sends. */           var dt = j.delivered_to, arr = Array.isArray(dt) ? dt : (dt ? [dt] : []);           if (arr.length || (j.inject && j.inject.delivered_in_conversation)) return { state: 'delivered' };-          if (res.status >= 200 && res.status < 300) return { state: 'opened', hint: j._hints && j._hints.note };-          throw new Error((j._hints && j._hints.note) || j.error || ('AI threads returned ' + res.status));+          /* 1.0.187+ returns in well under a second with a JOB, not a delivery. ok:true means+           * the job STARTED. delivered_to is never filled on this path any more, so without+           * the poll below every send would report "delivery not confirmed" forever. */+          if (!j.job) return { state: 'opened', hint: j._hints && j._hints.note };+          var started = Date.now(), missing = false;+          return new Promise(function (resolve) {+            (function tick() {+              /* the deadline is checked in BOTH paths. Checking it only after a successful+               * poll leaves a dashboard whose server stops answering polling forever. */+              function again() {+                if (Date.now() - started > 120000)+                  return resolve({ state: 'opened', hint: 'no confirmation within 120s' });+                setTimeout(tick, 1000);+              }+              fetch(base + '/ai-threads/run-prompt/status?job=' + encodeURIComponent(j.job))+                .then(function (r) {+                  /* THE STATUS ROUTE IS A THIRD PROXY ROUTE. Dashboards proxy /updater/status+                   * and POST /ai-threads/run-prompt only. If this one was never added, every+                   * poll 404s and the user watches "Opening…" for the full 120s before a+                   * hedge, which is worse than the behaviour this replaced. Fail FAST and say+                   * exactly which route is missing instead of hanging. */+                  if (r.status === 404) { missing = true; return null; }+                  return r.json();+                })+                .then(function (st) {+                  if (missing) return resolve({ state: 'opened', phase: 'no_status_route',+                    hint: 'Your AI chat opened. This app does not proxy GET ' ++                          '/ai-threads/run-prompt/status yet, so progress cannot be shown.' });+                  var state = (st.job && st.job.state) || '';+                  if (typeof onProgress === 'function') { try { onProgress(state, st.job); } catch (e) {} }+                  /* working or later IS sent, for the user-facing toast. */+                  if (state === 'working' || state === 'done' || state === 'needs_you' ||+                      state === 'still_working') return resolve({ state: 'delivered', phase: state });+                  if (state === 'submit_failed' || state === 'submitted_unidentified' ||+                      state === 'submit_unconfirmed')+                    return resolve({ state: 'opened', phase: state,+                                     hint: (st.job && (st.job.error || st.job.note)) || state });+                  again();+                })+                .catch(again);+            })();+          });         })         .catch(function (e) {           clearTimeout(timer);@@ -316,7 +379,10 @@         if (sd.disabled) return;         sd.disabled = true;         var pending = toast('Opening a new AI thread…', 'info', 120000);-        send(base, phrase, appName, slug).then(function (r) {+        send(base, phrase, appName, slug, function (state, job) {+          if (PHASE[state]) pending.setText(PHASE[state]);+          if (typeof cfg.onProgress === 'function') { try { cfg.onProgress(state, job); } catch (e) {} }+        }).then(function (r) {           pending.remove();           if (r.state === 'delivered') toast('Sent. Your AI thread has the prompt.', 'ok');           else toast('Thread opened, delivery not confirmed. Check your AI threads before sending again.', 'info', 8000);@@ -605,10 +671,21 @@               real !== 'ERR' && real !== 404 && real !== fake);         });       })+      .then(function () {+        /* THE STATUS ROUTE, added for Hydrogen 1.0.187+. It is a THIRD proxy route and no+         * dashboard had it before 2.22.0, so it is the row most likely to be red. A GET with+         * a job id that cannot exist must still be ANSWERED (any non-404), because a 404 here+         * means the route was never proxied and progress can never be reported. */+        return fetch(base + '/ai-threads/run-prompt/status?job=__anp_probe__')+          .then(function (r) { return r.status; }, function () { return 'ERR'; })+          .then(function (st) {+            add('progress route wired', 'answers (not 404)', String(st), st !== 'ERR' && st !== 404);+          });+      })       .then(function () { return {ok: ok, rows: rows}; });   }    root.AdomNewbiePanel = { mount: mount, toast: toast, selfCheck: selfCheck, copyText: copyText,-                           checkSend: checkSend, version: '2.21.0',+                           checkSend: checkSend, version: '2.22.0',                            TWO_COL_MIN: TWO_COL_MIN, PHRASE_BUDGET: PHRASE_BUDGET }; })(typeof window !== 'undefined' ? window : this);
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-ui-design",-  "version": "3.0.2",+  "version": "3.1.0",   "type": "skill",   "description": "The Adom UI Design System plus a bundle of focused UI skills (tooltips, floating-windows, toasts, responsive-ui, app-shell, ai-driven-apps, clickable-paths, dark-default, input-feedback, settings-layout). One install drops the whole set of skill files onto your container, plus shared icon assets (the settings cog) so every Adom app draws the same glyph.",   "dependencies": {},
page.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-ui-design",-  "version": "3.0.2",+  "version": "3.1.0",   "type": "skill",   "description": "The Adom UI Design System plus a bundle of focused UI skills (tooltips, floating-windows, toasts, responsive-ui, app-shell, ai-driven-apps, clickable-paths, dark-default, input-feedback, settings-layout). One install drops the whole set of skill files onto your container, plus shared icon assets (the settings cog) so every Adom app draws the same glyph.",   "dependencies": {},
skills/ai-driven-apps/SKILL.md+3−3
@@ -3,7 +3,7 @@ name: ai-driven-apps user-invocable: true description: >-   How the AI builds, shows, drives, and tests an Adom app. In Adom the AI writes the app, shows it to-  the user (in their own already-signed-in native browser via Adom Bridge, in pup for the+  the user (in their own already-signed-in native browser via Adom Desktop, in pup for the   logged-out experience, or in a Hydrogen webview), drives the front-end over a live SSE or WebSocket   channel to exercise every path (auto-click buttons, show and hide floaty toolbars, load a   different symbol or footprint, click send-to-kicad), reads back state, console, and screenshots,@@ -70,10 +70,10 @@ Pick the surface by what you need to test, and use more than one at once when it  | Surface | What it is | Use it for | |---|---|---|-| **Native browser** (**nb**) | The user's own Chrome or Edge, driven through Adom Bridge | The real, signed-in experience. The browser is already the user's, so the app opens **logged in with zero friction**. Use it for anything behind a login. |+| **Native browser** (**nb**) | The user's own Chrome or Edge, driven through Adom Desktop | The real, signed-in experience. The browser is already the user's, so the app opens **logged in with zero friction**. Use it for anything behind a login. | | **pup** | A clean, scriptable Chrome the AI fully owns | The **fresh, logged-out, first-time-user** experience, and automated screenshot loops that should not fight the user's own tabs. | | **Hydrogen webview** | An in-workspace browser tab | Showing a result inside the workbench, next to the code and the chat. |-| **hdbw** (Hydrogen Browser Window) | Under Hydrogen Desktop: a standalone Tauri browser window Hydrogen manages directly (window label `hd-browse-*`, destination id `webview-window`) | A dedicated app window beside the Hydrogen shell — bigger than a pane tab, still Hydrogen-managed. Note: the in-Hydrogen *pane tab* is the wv surface (the `webview` skill routes there inside Hydrogen). |+| **hdbw** (HD Browser Window) | Under Hydrogen Desktop: a standalone Tauri browser window HD manages directly (window label `hd-browse-*`, destination id `webview-window`) | A dedicated app window beside the HD shell — bigger than a pane tab, still HD-managed. Note: the in-HD *pane tab* is the wv surface (the `webview` skill routes there inside HD). |   ### VS Code's "Simple Browser" is NOT a surface
skills/control-api-proxy/SKILL.md+20−5
@@ -28,15 +28,30 @@ and needs no access to anyone's machine.  ## PROXY THE TWO API CALLS THROUGH YOUR OWN SERVER -`apiBase` defaults to `''`, same origin, and it should stay that way. Add two routes to your-own server and forward them to the Hydrogen control API using whatever discovery you already-have (`~/.adom/hydrogen-control-url`, 47084 as the fallback):+`apiBase` defaults to `''`, same origin, and it should stay that way. Add **three** routes to+your own server and forward them to the Hydrogen control API using whatever discovery you+already have (`~/.adom/hydrogen-control-url`, 47084 as the fallback):  ```-GET  /updater/status        ->  control API /updater/status-POST /ai-threads/run-prompt ->  control API /ai-threads/run-prompt   (120s, matching the module)+GET  /updater/status                 ->  control API /updater/status+POST /ai-threads/run-prompt          ->  control API /ai-threads/run-prompt   (120s, matching the module)+GET  /ai-threads/run-prompt/status   ->  control API /ai-threads/run-prompt/status   (query string INTACT) ``` +⚠ **The third route is new for Hydrogen 1.0.187+ and it is the one apps forget.** On 1.0.187++the POST no longer reports delivery: it returns in well under a second with a **job**+(`{ok:true, job:"<uuid>", state:"opening"}`) and `delivered_to` is never filled on that path+again. Progress lives entirely on the status route, polled once a second. Two traps come with+it:++- **Forward the query string.** The route is called as `?job=<uuid>`. A proxy that forwards+  only the path answers about no job at all.+- **A missing route costs 120 seconds, not zero.** If it is not proxied, every poll 404s and+  the user watches "Opening a new AI chat" for the full timeout before a hedge, which is worse+  than the behaviour it replaced. `newbie-panel.js` 2.22.0+ fails fast on a 404 and says which+  route is missing, and `checkSend()` carries a `progress route wired` row so you can see it+  before a user does. Neither is a substitute for adding the route.+ Three reasons, and parts-search, nb and pup each found them separately: it removes any CORS dependency on ah; it keeps exactly ONE copy of the send logic in the fleet rather than one per app; and it keeps working in both deployment shapes, the container-served dashboard and
skills/dark-default/SKILL.md+1−1
@@ -6,7 +6,7 @@ description: >-   writes defaults to dark mode — light is the opt-in override, never the base. Read this the instant   you are about to write ANY HTML or CSS, even a throwaway cheat sheet or a two-minute static page   that does not feel like "an app". The classic mistake is styling :root light and adding dark as a-  prefers-color-scheme override — that is BACKWARDS for Adom, whose surfaces (wv tabs, ah panels)+  prefers-color-scheme override — that is BACKWARDS for Adom, whose surfaces (wv tabs, hd panels)   render dark by default, so a light-base page flashes white and reads as off-brand. This is the   30-second rule; the parent adom-ui-design skill is the full design system (tokens, header, linter).   Read BEFORE writing any HTML/CSS. Trigger words: html, write html, static page, static html, css,
skills/settings-layout/SKILL.md+1−1
@@ -430,7 +430,7 @@ IntersectionObserver unless you have hundreds of sections.    John asked for this directly: "put what the defaults are in the settings window." 4. **Put the full key in `title`** as a tooltip. That is how a user reports a setting precisely    and how an AI finds it.-5. Fix initialisms your case helper mangles: `Wsl` to `WSL`, `Hd` to `Hydrogen`, `Api` to `API`.+5. Fix initialisms your case helper mangles: `Wsl` to `WSL`, `Hd` to `HD`, `Api` to `API`.  ## Type scale