Adom Screensaver
Public Made by Adomby adom
Vibe-coding screensaver, fades up a live billboard of the latest Adom Wiki drops (truly cached to disk; plays webm silently, renders SVG) with a power-aware nerd-stats splash, while signed native keep
name: adom-screensaver-billboard description: > How the Adom Screensaver's Wiki billboard pipeline works: where it pulls content, how it caches, how often it re-checks, how it minimizes hits to wiki.adom.inc, how it minimizes downloads to the user's machine, and how it scores which drop to show. Read this before touching the Wiki loader, the cache, the scoring, or the refresh cadence in adom-screensaver.cs / billboard.html. Triggers: screensaver caching, billboard refresh, hero cache, manifest, delta skip, scoring, freshness, why is the screensaver showing an old hero, minimize wiki hits, screensaver bandwidth.
Adom Screensaver - billboard caching, scoring & refresh
The screensaver plays Adom Wiki pages as billboards. This documents the data pipeline so it stays
honest about server load and bandwidth. Source of truth: Wiki class in adom-screensaver.cs +
billboard.html. Everything below is verified behavior, not aspiration.
One source only
It pulls from exactly one wiki: VW = https://wiki.adom.inc (the canonical Adom Wiki). The old
V1 / V2 constants are dead (no code path queries them). All requests are authenticated with
the user's bearer token (wiki-token.txt), so the billboards are the user's filtered view (public +
their private/org pages, each badged).
When it refreshes
Wiki.Start() → spawns Wiki.Run() once per screensaver launch (every idle-fire, or Win+Shift+L).
There is no in-session wiki re-poll. billboard.html re-reads the local manifest.json every 3 s
(never touches the wiki). Last run's billboards are shown instantly from the cached manifest while the
fresh Run() reconciles in the background.
In-session in-place refresh (the one-launch-lag fix). loadManifest doesn't just append new slugs -
it also refreshes a slide already on screen when its media/updated changes (repainting the current
card if it's the one showing). Without this, the page kept showing the CACHED hero from launch even after
the fresh Run() downloaded an updated one into the manifest, so a hero you updated would only appear on
the NEXT launch. Now it appears within ~3 s of the background fetch, same session, no reordering.
Where heroes live now (wiki change, 2026-06). Hero images are no longer committed repo files - the
page's /files listing returns NO images. The hero is in a hero store, exposed via the page DETAIL's
hero_path (e.g. screenshots/hero.png) + hero_type, and served at the usual
/blob/<type>/<slug>/<hero_path>. So discovery MUST read hero_path from the detail (the LIST omits
it - it comes back empty there) and the RepoFilesAll fallback is now mostly dead weight (kept for older
pages). The canonical /blob/.../screenshots/hero.png URL still works, so updated heroes are fetched
correctly (verified: the cached -hid hero is byte-identical to live).
Minimizing wiki-server load - the DELTA SKIP (the important part)
Per launch, Run() issues just 2 list calls: GET /api/v1/pages?type=app&limit=100 and …type=skill….
Those list items already carry each page's updated_at.
Then for each page, ProcessItem does a delta skip: if last run's manifest already has that slug
at the same updated_at and its cached hero file is still on disk, it reuses that entry verbatim
- no per-page detail fetch, no file-list, no hero probe, no download. Only new or changed pages do the full network work (detail + file-list + ranged-GET hero probes + maybe a hero download).
Net: a re-fire where nothing changed costs 2 requests total (verified: WIKI: run done, 60 reused (delta-skip), 0 fetched, 60 total). Before this, every launch re-fetched all ~60 pages' detail +
files + probes - hundreds of requests per idle-fire.
The cache (what's on disk)
%LOCALAPPDATA%\adom-screensaver\cache\:
manifest.json- the slide list (slug, title, brief, prompt, media ref, vis, owner,updated, …). Rewritten each run from the fresh+reused set; a page removed from the wiki simply drops out.- Hero images (image/SVG) - downloaded and served locally, filename keyed on the page's update
stamp:
SafeName(slug)__<compacted updated_at>.png. So:- page unchanged → same filename → served from disk, no re-download;
- page's hero updated → new
updated_at→ new filename → re-downloaded once, and the old stamped file (+ the pre-stamp legacyslug.png) is pruned. - The download URL is cache-busted (
?v=<stamp>) so no proxy serves stale bytes.
showcounts.json- per-slug lifetime view counts (host-written synchronously on each show), used by the scorer. Survives hard kills (unlike browser localStorage).
Minimizing downloads / bandwidth
- Heroes are downloaded at most once per content change (the stamp key above). Unchanged heroes are never re-fetched.
- Videos are STREAMED, never downloaded (they can be tens of MB); the manifest carries the remote URL and the WebView2 streams it.
- The delta skip means unchanged pages download nothing.
Repo type (App / Skill / Component) - capsule + components opt-in
The wiki's type field is first-class (app / skill / component / bootstrap) - it's what the header
tabs filter on, NOT tags. The fetch reads it per page and the manifest carries s.type. Each billboard
shows a color-coded type capsule leading its meta row: App = blue (square dot), Skill = purple (diamond),
Component = pink (round). IsHeroType decides what's eligible: app + skill always; component only when
opted in; bootstrap never.
Components are opt-in (Prefs.ShowComponents(), default OFF; config toggle compToggle ->
components:on/components:off). When ON, AddWork also fetches type=component and Start() widens
Target 200 -> 360 so components survive the per-launch collection cap (app+skill alone already exceed 200).
Takes effect on the next saver launch.
Component heroes - raster product visuals only, never a symbol
Most type=component pages are jellybean passive parts (0402 caps/resistors) whose only image is a
part-number-named svg, i.e. a schematic symbol, and they rarely set hero_path. Showing those
full-screen is a joke. So RepoFilesAll is component-strict:
- Skip ALL svg for components (svg == symbol / schematic / pcb diagram / part-number symbol). Real
product visuals (3D renders, photos) are raster (png/jpg). Also skip any
schematic/symbol/footprintfile for every type. - Prefer a clean 3D hero angle:
herofiles first, then 3D renders rankedhome/iso/persp(clean product shots) > plainfront/board(often a viewer screenshot or a dimensioned view) > edge/undersideback/bottom/left/right/top/side, then screenshots, thenpcblayout last. - Components show ONLY with a genuine hero / 3D render / photo: the assembled candidate list for a component
is
hero + 3D + screenshotonly (NO "rest" random-raster or bare-PCB fallback). A component with no real product visual gets no hero and is dropped - better no card than a junk card. - Net (verified on ALL 200 live components): 38 billboard, 162 dropped, 0 junk - and all 38 have a real
visual (32 resolve to a clean
3d-home.pngrender, the rest to*-hero.png/product-photo.jpg). Reference:cosmocoil->3d-home.png.
No network (offline) - never dead-stop, never burn in
The saver must survive a launch with no wifi (e.g. a fresh Airbnb) without stranding a bright static splash on the panel. Three guarantees, none of which depend on the wiki being reachable:
- Don't clobber the cache.
AddWorkreturns whether the list call got a real HTTP response. If the wiki was unreachable,Run()skipsWriteManifest()so last run's goodmanifest.jsonstays on disk, and re-publishes the cached entries intoCount. (PreviouslyRun()always wrote the manifest from an emptypublishedlist, wiping last run's billboards - the hero files stayed on disk but the manifest that indexed them was zeroed, so the page had 0 slides and froze on the splash.) - Play cached drops offline. The page reads the preserved
manifest.jsonvia the virtual host and rotates the cached billboards normally. The splash saysOffline: playing your N saved drops from last time.(orOffline: no saved drops yet; dimming to protect your screen.on a true first run). - Burn-in backstops the network can't defeat (host
OnStatusTick): dim early (45s) when the run finished/failed with nothing to show (Wiki.Done && Wiki.Count==0), and an absolute 8-min ceiling (HardRestCapMs) so evenRestAfter=0("never") can't hold a lit screen all night. The normalRestAfterSectimer (default 300s) still applies on top.
Scoring - what plays first (billboard.html shuffleSlides)
Higher score plays sooner; the goal is to lead with NEW + UNSEEN, sink OLD + over-seen:
- Freshness (from
updated):<7d+100,<30d+60,<90d+35,<365d+15, older/unknown +5. - Novelty (from
showcounts.json):max(0, 60 - 20 × timesShown)(never-seen +60; gone after ~3 views). - Jitter: +0..12 so the order varies run-to-run and ties never lock.
Each billboard prints its own
priority N (fresh F + new V)in the footer for transparency.
Public vs private background (so the two never get confused)
Each billboard's full background is tinted by visibility (set per slide in showSlide via a
viz-pub/viz-priv body class driving #bgwash): PUBLIC = the clean dark screensaver (no wash);
PRIVATE / org-only = an Adom TEAL wash + a teal screen frame (brand #00B8B1). This makes a private page (e.g. adom-gchat-adom, adom-google-adom,
or any -dev source page) unmistakable next to its public twin.
Private pages only appear if the saver is authenticated. The list/detail/blob calls all send the
wiki-token.txt bearer. The hero-probe (UrlExists) MUST send it too: a private page's hero blob
401/404s anonymously, so without the token every private page was silently dropped (the bug that made
private -adom/-dev pages never show). Public heroes probe fine anonymously, which masked it.
Freshness display
Each slide shows a chip from updated: "New today" / "Updated 3 days ago" / "Updated 2 weeks ago" …,
teal for recent, dim for old.
Exclude list
exclude.txt (beside the .scr) holds slugs kept OUT of the rotation, one per line. Skip() also
hard-excludes adom-screensaver, test-/smoke-/shared- prefixes, adom-desktop/hd-, and
auth pages. On startup FilterCachedManifestByExclude() drops now-excluded slugs from the cached
manifest so a just-excluded card never flashes.
Correctness invariants (don't regress)
- One entry per slug.
AddWorkdedups by slug within the work list; each launch is a fresh process sopublishednever accumulates across runs. (If you ever see two of the same slug, that's the bug.) Note: two different slugs about the same topic (e.g.adom-gchatthe CLI +adom-gchat-adomthe Org Config page) are two real pages, not a duplicate. - A reused (delta-skip) entry is only trusted if its hero file still exists (
CachedMediaOk); else it falls through to full processing. - The manifest is rebuilt from the current wiki list each run, so removed pages drop and the set is never stale.
Known gaps → asks filed for Colby (see colby-feedback.md, 2026-06-19)
/blob/...sendsno-cachewith no ETag/Last-Modified and ignores conditional GET, so the only way to know a hero changed without a page edit is to download it. Want ETag +304.last_commit_hashis only in the page detail, not the list - with it in the list we'd have a perfect per-page/per-hero delta key in one call.- No bulk popularity/trending endpoint (stars are per-page only) - want
star_countin the list and/or/api/v1/trendingto factor popularity into the score.
Quick reference
| Question | Answer |
|---|---|
| Which wiki? | wiki.adom.inc only |
| Re-check cadence | once per saver launch (no in-session poll) |
| Requests when nothing changed | 2 (the two list calls); 0 per-page |
| Hero re-download | only when updated_at changes |
| Videos | streamed, never downloaded |
| Order | freshness + novelty + jitter (highest first) |
---
name: adom-screensaver-billboard
description: >
How the Adom Screensaver's Wiki billboard pipeline works: where it pulls content, how it caches,
how often it re-checks, how it minimizes hits to wiki.adom.inc, how it minimizes downloads to the
user's machine, and how it scores which drop to show. Read this before touching the Wiki loader,
the cache, the scoring, or the refresh cadence in adom-screensaver.cs / billboard.html.
Triggers: screensaver caching, billboard refresh, hero cache, manifest, delta skip, scoring,
freshness, why is the screensaver showing an old hero, minimize wiki hits, screensaver bandwidth.
---
# Adom Screensaver - billboard caching, scoring & refresh
The screensaver plays Adom Wiki pages as billboards. This documents the data pipeline so it stays
honest about server load and bandwidth. Source of truth: `Wiki` class in `adom-screensaver.cs` +
`billboard.html`. Everything below is verified behavior, not aspiration.
## One source only
It pulls from exactly **one** wiki: `VW = https://wiki.adom.inc` (the canonical Adom Wiki). The old
`V1` / `V2` constants are **dead** (no code path queries them). All requests are authenticated with
the user's bearer token (`wiki-token.txt`), so the billboards are the user's filtered view (public +
their private/org pages, each badged).
## When it refreshes
`Wiki.Start()` → spawns `Wiki.Run()` **once per screensaver launch** (every idle-fire, or Win+Shift+L).
There is **no** in-session wiki re-poll. `billboard.html` re-reads the **local** `manifest.json` every 3 s
(never touches the wiki). Last run's billboards are shown instantly from the cached manifest while the
fresh `Run()` reconciles in the background.
**In-session in-place refresh (the one-launch-lag fix).** `loadManifest` doesn't just append new slugs -
it also **refreshes a slide already on screen** when its `media`/`updated` changes (repainting the current
card if it's the one showing). Without this, the page kept showing the CACHED hero from launch even after
the fresh `Run()` downloaded an updated one into the manifest, so a hero you updated would only appear on
the NEXT launch. Now it appears within ~3 s of the background fetch, same session, no reordering.
**Where heroes live now (wiki change, 2026-06).** Hero images are **no longer committed repo files** - the
page's `/files` listing returns NO images. The hero is in a hero store, exposed via the page DETAIL's
`hero_path` (e.g. `screenshots/hero.png`) + `hero_type`, and served at the usual
`/blob/<type>/<slug>/<hero_path>`. So discovery MUST read `hero_path` from the **detail** (the LIST omits
it - it comes back empty there) and the `RepoFilesAll` fallback is now mostly dead weight (kept for older
pages). The canonical `/blob/.../screenshots/hero.png` URL still works, so updated heroes are fetched
correctly (verified: the cached `-hid` hero is byte-identical to live).
## Minimizing wiki-server load - the DELTA SKIP (the important part)
Per launch, `Run()` issues just **2 list calls**: `GET /api/v1/pages?type=app&limit=100` and `…type=skill…`.
Those list items already carry each page's `updated_at`.
Then for each page, `ProcessItem` does a **delta skip**: if last run's manifest already has that slug
**at the same `updated_at`** and its cached hero file is still on disk, it **reuses that entry verbatim**
- no per-page detail fetch, no file-list, no hero probe, no download. Only **new or changed** pages do
the full network work (detail + file-list + ranged-GET hero probes + maybe a hero download).
Net: a re-fire where nothing changed costs **2 requests total** (verified: `WIKI: run done, 60 reused
(delta-skip), 0 fetched, 60 total`). Before this, every launch re-fetched all ~60 pages' detail +
files + probes - hundreds of requests per idle-fire.
## The cache (what's on disk)
`%LOCALAPPDATA%\adom-screensaver\cache\`:
- **`manifest.json`** - the slide list (slug, title, brief, prompt, media ref, vis, owner, `updated`, …).
Rewritten each run from the fresh+reused set; a page removed from the wiki simply drops out.
- **Hero images** (image/SVG) - downloaded and served locally, filename **keyed on the page's update
stamp**: `SafeName(slug)__<compacted updated_at>.png`. So:
- page unchanged → same filename → served from disk, **no re-download**;
- page's hero updated → new `updated_at` → new filename → **re-downloaded once**, and the old
stamped file (+ the pre-stamp legacy `slug.png`) is **pruned**.
- The download URL is cache-busted (`?v=<stamp>`) so no proxy serves stale bytes.
- **`showcounts.json`** - per-slug lifetime view counts (host-written synchronously on each show), used
by the scorer. Survives hard kills (unlike browser localStorage).
## Minimizing downloads / bandwidth
- Heroes are downloaded **at most once per content change** (the stamp key above). Unchanged heroes
are never re-fetched.
- **Videos are STREAMED**, never downloaded (they can be tens of MB); the manifest carries the remote
URL and the WebView2 streams it.
- The delta skip means unchanged pages download **nothing**.
## Repo type (App / Skill / Component) - capsule + components opt-in
The wiki's `type` field is first-class (`app` / `skill` / `component` / `bootstrap`) - it's what the header
tabs filter on, NOT tags. The fetch reads it per page and the manifest carries `s.type`. Each billboard
shows a color-coded **type capsule** leading its meta row: App = blue (square dot), Skill = purple (diamond),
Component = pink (round). `IsHeroType` decides what's eligible: **app + skill always; component only when
opted in; bootstrap never.**
**Components are opt-in** (`Prefs.ShowComponents()`, default OFF; config toggle `compToggle` ->
`components:on`/`components:off`). When ON, `AddWork` also fetches `type=component` and `Start()` widens
`Target` 200 -> 360 so components survive the per-launch collection cap (app+skill alone already exceed 200).
Takes effect on the next saver launch.
## Component heroes - raster product visuals only, never a symbol
Most `type=component` pages are jellybean passive parts (0402 caps/resistors) whose only image is a
part-number-named **svg, i.e. a schematic symbol**, and they rarely set `hero_path`. Showing those
full-screen is a joke. So `RepoFilesAll` is component-strict:
- **Skip ALL svg for components** (svg == symbol / schematic / pcb diagram / part-number symbol). Real
product visuals (3D renders, photos) are raster (png/jpg). Also skip any `schematic`/`symbol`/`footprint`
file for every type.
- **Prefer a clean 3D hero angle**: `hero` files first, then 3D renders ranked `home`/`iso`/`persp` (clean
product shots) > plain `front`/`board` (often a viewer screenshot or a dimensioned view) > edge/underside
`back`/`bottom`/`left`/`right`/`top`/`side`, then screenshots, then `pcb` layout last.
- Components show ONLY with a genuine hero / 3D render / photo: the assembled candidate list for a component
is `hero + 3D + screenshot` only (NO "rest" random-raster or bare-PCB fallback). A component with no real
product visual gets **no hero and is dropped** - better no card than a junk card.
- Net (verified on ALL 200 live components): **38 billboard, 162 dropped, 0 junk** - and all 38 have a real
visual (32 resolve to a clean `3d-home.png` render, the rest to `*-hero.png` / `product-photo.jpg`).
Reference: `cosmocoil` -> `3d-home.png`.
## No network (offline) - never dead-stop, never burn in
The saver must survive a launch with **no wifi** (e.g. a fresh Airbnb) without stranding a bright
static splash on the panel. Three guarantees, none of which depend on the wiki being reachable:
1. **Don't clobber the cache.** `AddWork` returns whether the list call got a real HTTP response. If the
wiki was unreachable, `Run()` **skips `WriteManifest()`** so last run's good `manifest.json` stays on
disk, and re-publishes the cached entries into `Count`. (Previously `Run()` always wrote the manifest
from an empty `published` list, **wiping** last run's billboards - the hero files stayed on disk but the
manifest that indexed them was zeroed, so the page had 0 slides and froze on the splash.)
2. **Play cached drops offline.** The page reads the preserved `manifest.json` via the virtual host and
rotates the cached billboards normally. The splash says `Offline: playing your N saved drops from last
time.` (or `Offline: no saved drops yet; dimming to protect your screen.` on a true first run).
3. **Burn-in backstops the network can't defeat** (host `OnStatusTick`): dim **early (45s)** when the run
finished/failed with **nothing to show** (`Wiki.Done && Wiki.Count==0`), and an **absolute 8-min ceiling**
(`HardRestCapMs`) so even `RestAfter=0` ("never") can't hold a lit screen all night. The normal
`RestAfterSec` timer (default 300s) still applies on top.
## Scoring - what plays first (`billboard.html shuffleSlides`)
Higher score plays sooner; the goal is to lead with NEW + UNSEEN, sink OLD + over-seen:
- **Freshness** (from `updated`): `<7d` +100, `<30d` +60, `<90d` +35, `<365d` +15, older/unknown +5.
- **Novelty** (from `showcounts.json`): `max(0, 60 - 20 × timesShown)` (never-seen +60; gone after ~3 views).
- **Jitter**: +0..12 so the order varies run-to-run and ties never lock.
Each billboard prints its own `priority N (fresh F + new V)` in the footer for transparency.
## Public vs private background (so the two never get confused)
Each billboard's **full background** is tinted by visibility (set per slide in `showSlide` via a
`viz-pub`/`viz-priv` body class driving `#bgwash`): PUBLIC = the clean dark screensaver (no wash);
PRIVATE / org-only = an Adom TEAL wash + a teal screen frame (brand #00B8B1). This makes a private page (e.g. `adom-gchat-adom`, `adom-google-adom`,
or any `-dev` source page) unmistakable next to its public twin.
**Private pages only appear if the saver is authenticated.** The list/detail/blob calls all send the
`wiki-token.txt` bearer. The hero-probe (`UrlExists`) MUST send it too: a private page's hero blob
401/404s anonymously, so without the token every private page was silently dropped (the bug that made
private `-adom`/`-dev` pages never show). Public heroes probe fine anonymously, which masked it.
## Freshness display
Each slide shows a chip from `updated`: "New today" / "Updated 3 days ago" / "Updated 2 weeks ago" …,
teal for recent, dim for old.
## Exclude list
`exclude.txt` (beside the .scr) holds slugs kept OUT of the rotation, one per line. `Skip()` also
hard-excludes `adom-screensaver`, `test-`/`smoke-`/`shared-` prefixes, `adom-desktop`/`hd-`, and
auth pages. On startup `FilterCachedManifestByExclude()` drops now-excluded slugs from the cached
manifest so a just-excluded card never flashes.
## Correctness invariants (don't regress)
- **One entry per slug.** `AddWork` dedups by slug within the work list; each launch is a fresh process
so `published` never accumulates across runs. (If you ever see two of the same slug, that's the bug.)
Note: two *different* slugs about the same topic (e.g. `adom-gchat` the CLI + `adom-gchat-adom` the
Org Config page) are two real pages, not a duplicate.
- A reused (delta-skip) entry is only trusted if its hero file still exists (`CachedMediaOk`); else it
falls through to full processing.
- The manifest is rebuilt from the current wiki list each run, so removed pages drop and the set is
never stale.
## Known gaps → asks filed for Colby (see colby-feedback.md, 2026-06-19)
- `/blob/...` sends `no-cache` with **no ETag/Last-Modified** and ignores conditional GET, so the only
way to know a hero changed without a page edit is to download it. Want ETag + `304`.
- `last_commit_hash` is only in the page **detail**, not the **list** - with it in the list we'd have a
perfect per-page/per-hero delta key in one call.
- No bulk popularity/trending endpoint (stars are per-page only) - want `star_count` in the list and/or
`/api/v1/trending` to factor popularity into the score.
## Quick reference
| Question | Answer |
|---|---|
| Which wiki? | `wiki.adom.inc` only |
| Re-check cadence | once per saver launch (no in-session poll) |
| Requests when nothing changed | 2 (the two list calls); 0 per-page |
| Hero re-download | only when `updated_at` changes |
| Videos | streamed, never downloaded |
| Order | freshness + novelty + jitter (highest first) |