add-fetcher-renderer
Implements one fetcher or one renderer (not the whole Fetcher × Renderer × Layout composition — the layout slot is the user's call). Read AGENTS.md first for the architectural contracts; this skill only covers process.
Phases 0 and 1 are blocking on a user reply. Auto mode does not license you to pick a candidate or finalise a spec on the user's behalf — both decisions ship in user-visible config (render = "..." / fetcher = "...") and are expensive to rename. Always ask a concrete question and stop until the user answers. Only Phase 2 onward proceeds without further prompts.
Phase 0 — Pick the candidate (候補選定)
Skip this phase only if the user named a specific candidate ("add system_battery", "implement gauge_segment"). Otherwise — including any open-ended ask ("add some renderer", "implement a fetcher") — work through this phase before Phase 1.
Survey ALL relevant catalog issues in parallel. Don't pick from memory; the boxes churn — yesterday's open box may already be checked.
- Renderer ask →
#61 only.
- Fetcher ask → all of
#62 through #68, dispatched as a single batch of parallel gh issue view calls. No partial surveys "starting with Local/System and seeing what fits". The biggest daily-driver classes (task tools in #66, coding-time in #64, music in #67) sit exactly where a partial survey would stop early.
- Cross-cutting context (composition model, ReadStore recipes, rejected designs):
gh issue view 41 --json body -q .body.
Filter to the open boxes in the surveyed issues.
Score candidates with these four filters (these supersede #41's looser rubric for ranking — keep #41 for cross-cutting context, do scoring here):
A. Renderer primitive leverage (renderer asks). Counts only if you can name ≥2 specific consumer fetchers from the current catalog — shipped or in an open box, by name. Vague "could be useful for some Ratio fetcher" doesn't count. text_big_number clears it (github_repo_stars, oss_followers_delta, deploy_error_budget, package downloads). chart_funnel doesn't (no roadmap fetcher emits funnel-shaped Bars yet) — defer until a consumer lands.
B. Recurring-change × repeated-look-value (the real "daily-driver" test, replacing #41's vague rubric ②). Both sub-tests must pass for a high rank:
- Recurring-change: does displayed content change ≥1×/day?
- Repeated-look-value: would the user still want to see it after 100 splash opens? Novelty traps fail here —
iss_position (fun once, ignored after), f1_next_race (fan-only then forgotten), random_cat, fortune. todoist_today / linear_assigned / slack_unread pass — people check these on purpose every day.
C. Persona name test. Name in one phrase the user who'd open splash and benefit every day. "Anyone with a Todoist account" ✅. "Devs at modern startups using Linear" ✅. "Kids learning about space" ❌ (not a regular shell user). "People who like weather data" ❌ (they check phones). If you can't name a concrete recurring user, demote to ambient/novelty tier.
D. Setup-cost is an amortized tiebreaker, not a gate. Distinguish:
- Zero-config (drop-in TOML works) → no penalty.
- One-time setup (token / lat-lon / country code) → amortized over years of daily glances. Negligible penalty when B+C are strong.
todoist_today's token is paid back in week 1 of daily use.
- Per-use friction (OAuth re-auth, custom URL each invocation) → real penalty.
The old "Network class with required setup loses to a Safe sibling" rule was too coarse. A Safe widget no one looks at loses to a Network widget actively checked daily. Gate on whether the persona will pay the setup cost, not on Safety class itself.
Present a 3–5 candidate shortlist, one line each: catalog box being ticked, family, and which of A/B/C/D scored it where — not vague phrases like "fills a gap". Lead with the top pick, note a runner-up. Include candidates from at least 2 different scoring profiles (e.g., one A-strong renderer primitive, one B+C-strong daily-driver fetcher, optionally one ambient for contrast) so the user sees the real trade-off space, not just one cluster.
Ask the user which to implement. Stop. Do not start Phase 1 on a guess. Auto mode does not override this — the candidate decision is the user's, not yours.
Phase 1 — Spec-out (要件確認)
Don't write code yet. Confirm the shape of the work in conversation:
- Fetcher or renderer? If both, do them as two passes through this skill, not one.
- Catalog target. Cross-check
#41 and the relevant sub-issue (#61 renderers, #62–#68 fetchers). If the candidate is in a checkbox list, link it. If it isn't, ask whether to add it.
- For fetchers:
- Family prefix (
clock_*, system_*, git_*, …) — pick the right one or justify a new family.
- Cached or realtime? Realtime contract is
< 1ms, infallible, no I/O. Anything that touches reqwest / disk / process state is cached, not realtime.
- Safety class. Ask "can config control where the traffic goes?" If yes →
Network (rss / calendar / any fetcher with a url option). If no, even an authenticated fixed-host fetcher is Safe. Exec is closed (#5 / #20 — don't reintroduce).
- Output shape(s). A multi-shape fetcher branches on
ctx.shape inside compute / fetch and lists every variant in shapes(). Enumerate every Shape variant from src/render/mod.rs::Shape and accept-or-reject each one — don't just pick the obvious ones from the catalog blurb. The bar for acceptance is "can this data be meaningfully expressed in this form?", not "is this the most natural form?". A list of files renders as Text (comma-joined "main.rs (42), lib.rs (31), …"), TextBlock (one row each), Entries (path→count), Bars (label+value) — all four pass. Look at how the closest sibling fetcher in the same family handled each shape; family precedent is a strong signal. Reject only when no representation exists (Heatmap needs a 2D grid you don't have, Timeline needs chronological order, Calendar needs a year/month). "Feels redundant with another shape" or "the catalog didn't mention it" are not rejection reasons. Present the full 12-row table in the spec so the user can challenge any verdict before code lands.
- Options. Per-fetcher options struct (
#[derive(Deserialize)], serde(deny_unknown_fields)) parsed from ctx.options; surface each field as an OptionSchema entry so the docs generator picks it up.
- For renderers:
- Family prefix (
text_*, list_*, chart_*, gauge_*, grid_*, status_*, media_*, animated_*). Module name = registered name.
- Accepted shapes. Exactly one. Primary renderers are single-shape — the only exception is animated post-process wrappers (
animated_postfx, animated_boot, animated_scanlines, animated_splitflap, animated_wave) that delegate to an inner renderer via ALL_SHAPES. If two shapes feel like they should share a renderer, register two renderers that share helpers (text_plain vs list_plain is the canonical pair).
animates() — return true only if output changes across calls within a single draw cycle.
- Options. First check whether an existing field on
RenderOptions already covers it (align, color, label, marker, bullet, style, …). Add a new field only when none fits, and wire an OptionSchema entry on the renderer.
- Color semantics warning (renderers). Don't bake a context-specific colour map as the default. The
gauge_battery lesson: a battery-coloured fill (low → red) silently mis-coloured every "fraction used" widget. If the colour mapping depends on caller intent, default to theme.text and add an opt-in tone / mode option.
- Memory check. If the user says 考えたい / 検討したい, propose options and stop — don't branch yet.
State the plan back in 3–5 bullets and ask an explicit yes/no question ("Proceed with this plan?") — don't just narrate the plan and roll into Phase 2. Stop until the user answers. Auto mode does not override this; naming, options, and safety class all leak into user-facing config and are painful to undo.
Phase 2 — Implement (実装)
- Files.
- Fetcher:
src/fetcher/<family>/<name>.rs (or src/fetcher/<family>.rs if the family is one file).
- Renderer:
src/render/<family>_<variant>.rs.
- Pattern. Open the closest sibling and mirror its structure (constants → struct → trait impl → helpers → tests).
- Wire up.
- Renderer:
mod line + r.register(...) in src/render/mod.rs::Registry::with_builtins, add the name to the registry_resolves_all_builtins test list. If it becomes the default for a shape, update default_renderer_for. Override xtask/src/snapshots.rs::renderer_dimensions only when the default (40, 5) doesn't fit the renderer's natural aspect.
- Fetcher: add to
realtime_fetchers() / cached_fetchers() in the family module's registration entry point.
- New shape? Five-touch-point change per AGENTS.md:
Body variant + *Data struct, Shape enum, shape_of(), default_renderer_for(), at least one renderer's accepts().
- Tests. Inline
#[cfg(test)] mod tests.
- Renderer: use
src/render/test_utils.rs (render_to_buffer_with_spec, line_text). Cover each visual mode (compact vs boxed, with/without label, clamp out-of-range, narrow area).
- Fetcher: cover each shape branch + options parsing. Realtime fetchers test
compute directly; cached fetchers test sample_body and the actual fetch path with whatever stand-in is appropriate.
- House style. English in code/comments/commits.
map / filter / for_each over imperative loops. Functions ~10–15 lines, files ~100 lines as a guideline. Minimal comments — only when the why is non-obvious.
Phase 3 — Self-review (各種レビュー)
Run the gates before asking the user to verify:
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo test
Then read your own code critically. Common findings worth catching yourself:
- Naming: family prefix consistent? module name matches registered name?
- Edge cases: empty body,
width == 0, very narrow area, ratio out of [0,1], fetchers handling timezone / locale defaults.
- Color semantics (renderer): does the default mapping make sense for every fetcher listed under
Compatible fetchers? Add a tone-style opt-in if not.
- Safety class (fetcher): re-apply the Phase 1 rule — config-controlled destination →
Network, otherwise Safe.
- Test coverage: each visual mode / shape branch has a test?
clamps_out_of_range, empty_area_does_not_panic, narrow-area degradation if applicable.
- Catalog parity:
cargo run --bin splashboard -- catalog renderer <name> (or ... fetcher <name>) prints the option schema and compatible counterparts you intended.
Report findings as a punch list with severity (🔴 / 🟡 / 🟢). Apply 🔴 fixes before Phase 4.
Phase 4 — User smoke-test (動作確認)
The CLI requires a real tty, so you can't visually verify colours yourself. Hand off to the user:
- Write a temporary
./.splashboard/dashboard.toml exercising the new fetcher / renderer in 2–3 configurations (renderer: default + each opt-in tone / variant + compact size; fetcher: with and without each meaningful option).
- Tell the user:
cargo run --bin splashboard from the repo root.
- Wait for confirmation. Visual / colour issues that ASCII tests miss show up here.
Phase 5 — Wrap up (完了)
- Delete
.splashboard/dashboard.toml after the user signs off — it was a throwaway smoke-test fixture, not a ship artifact. Keep it only when the user explicitly says so. Note: in splashboard's own repo a committed .splashboard/dashboard.toml doubles as the "ship-with-the-clone" project dashboard, so committing one is a separate decision the user has to make deliberately, not a side-effect of the smoke-test.
- Don't commit unprompted. Wait for explicit ask. When you do commit:
- Conventional commit (
feat(render): ..., feat(fetcher): ...).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> trailer.
- Tick the relevant
#41 sub-issue checkbox in the PR description, not in code.
- PR description mirrors the auto-generated reference page so reviewers see what
/reference/... will show after the docs site rebuilds. Run cargo xtask locally; copy the relevant block from docs-site/src/content/docs/reference/{renderers,fetchers}/<family>/<name>.md (untracked — .gitignore'd). Strip the frontmatter, keep Accepts / Animates / Options / Theme tokens / Compatible fetchers (or Compatible renderers for fetchers), and rewrite the relative ../../...md links to absolute https://splashboard.unhappychoice.com/reference/... URLs.
- Merge strategy is
--merge (merge commit). Never rebase or squash — captured project preference.
- Tick the catalog checkbox after merge. Once the PR lands, edit the relevant sub-issue (
#61 for renderers, #62–#68 for fetchers) and flip the candidate's [ ] to [x]. Replace the planned blurb with a one-line "Shipped in #NNN" note plus any non-obvious behaviour decisions worth recording (auto-fit, opt-in options, threshold quirks). The PR description alone isn't enough — the issue body is the catalog index everyone scans for what's still open. Use gh issue view <N> --json body -q .body to dump it, edit, then gh issue edit <N> --body-file <path> to apply.
Out of scope
- Plugin protocol (#5) and command widget (#20) are closed. If the user's request shape is "run a subprocess and render its output", explain why it's rejected and steer toward
basic_read_store (the user writes the file) or a dedicated built-in.
- Screensaver-style persistent loops. Animation lives within the
ANIMATION_WINDOW; a dedicated idle-loop sub-mode is a different product.
Source: unhappychoice/splashboard — distributed by TomeVault.
1---2name: unhappychoice-splashboard-splashboard3description: add-fetcher-renderer4---56# add-fetcher-renderer78Implements one fetcher *or* one renderer (not the whole `Fetcher × Renderer × Layout` composition — the layout slot is the user's call). Read `AGENTS.md` first for the architectural contracts; this skill only covers process.910**Phases 0 and 1 are blocking on a user reply.** Auto mode does *not* license you to pick a candidate or finalise a spec on the user's behalf — both decisions ship in user-visible config (`render = "..."` / `fetcher = "..."`) and are expensive to rename. Always ask a concrete question and stop until the user answers. Only Phase 2 onward proceeds without further prompts.1112## Phase 0 — Pick the candidate (候補選定)1314Skip this phase only if the user named a specific candidate ("add `system_battery`", "implement `gauge_segment`"). Otherwise — including any open-ended ask ("add some renderer", "implement a fetcher") — work through this phase before Phase 1.15161. **Survey ALL relevant catalog issues in parallel.** Don't pick from memory; the boxes churn — yesterday's open box may already be checked.17 - Renderer ask → `#61` only.18 - Fetcher ask → **all of `#62` through `#68`**, dispatched as a single batch of parallel `gh issue view` calls. No partial surveys "starting with Local/System and seeing what fits". The biggest daily-driver classes (task tools in `#66`, coding-time in `#64`, music in `#67`) sit exactly where a partial survey would stop early.19 - Cross-cutting context (composition model, ReadStore recipes, rejected designs): `gh issue view 41 --json body -q .body`.202. **Filter to the open boxes** in the surveyed issues.213. **Score candidates with these four filters** (these supersede #41's looser rubric for ranking — keep #41 for cross-cutting context, do scoring here):2223 **A. Renderer primitive leverage (renderer asks).** Counts only if you can name **≥2 specific consumer fetchers** from the current catalog — shipped or in an open box, by name. Vague "could be useful for some Ratio fetcher" doesn't count. `text_big_number` clears it (github_repo_stars, oss_followers_delta, deploy_error_budget, package downloads). `chart_funnel` doesn't (no roadmap fetcher emits funnel-shaped Bars yet) — defer until a consumer lands.2425 **B. Recurring-change × repeated-look-value** (the real "daily-driver" test, replacing #41's vague rubric ②). Both sub-tests must pass for a high rank:26 - *Recurring-change*: does displayed content change ≥1×/day?27 - *Repeated-look-value*: would the user still want to see it after 100 splash opens? **Novelty traps fail here** — `iss_position` (fun once, ignored after), `f1_next_race` (fan-only then forgotten), `random_cat`, `fortune`. `todoist_today` / `linear_assigned` / `slack_unread` pass — people *check these on purpose every day*.2829 **C. Persona name test.** Name in one phrase the user who'd open splash and benefit **every day**. "Anyone with a Todoist account" ✅. "Devs at modern startups using Linear" ✅. "Kids learning about space" ❌ (not a regular shell user). "People who like weather data" ❌ (they check phones). If you can't name a concrete recurring user, demote to ambient/novelty tier.3031 **D. Setup-cost is an amortized tiebreaker, not a gate.** Distinguish:32 - *Zero-config* (drop-in TOML works) → no penalty.33 - *One-time setup* (token / lat-lon / country code) → amortized over years of daily glances. Negligible penalty when B+C are strong. `todoist_today`'s token is paid back in week 1 of daily use.34 - *Per-use friction* (OAuth re-auth, custom URL each invocation) → real penalty.3536 The old "Network class with required setup loses to a Safe sibling" rule was too coarse. A `Safe` widget no one looks at loses to a `Network` widget actively checked daily. Gate on whether the persona will pay the setup cost, not on Safety class itself.37384. **Present a 3–5 candidate shortlist**, one line each: catalog box being ticked, family, and **which of A/B/C/D scored it where** — not vague phrases like "fills a gap". Lead with the top pick, note a runner-up. Include candidates from at least 2 different scoring profiles (e.g., one A-strong renderer primitive, one B+C-strong daily-driver fetcher, optionally one ambient for contrast) so the user sees the real trade-off space, not just one cluster.395. **Ask the user which to implement.** Stop. Do not start Phase 1 on a guess. Auto mode does not override this — the candidate decision is the user's, not yours.4041## Phase 1 — Spec-out (要件確認)4243**Don't write code yet.** Confirm the shape of the work in conversation:44451. **Fetcher or renderer?** If both, do them as two passes through this skill, not one.462. **Catalog target.** Cross-check `#41` and the relevant sub-issue (`#61` renderers, `#62`–`#68` fetchers). If the candidate is in a checkbox list, link it. If it isn't, ask whether to add it.473. **For fetchers:**48 - **Family prefix** (`clock_*`, `system_*`, `git_*`, …) — pick the right one or justify a new family.49 - **Cached or realtime?** Realtime contract is `< 1ms, infallible, no I/O`. Anything that touches `reqwest` / disk / process state is cached, not realtime.50 - **Safety class.** Ask "can config control where the traffic goes?" If yes → `Network` (rss / calendar / any fetcher with a `url` option). If no, even an authenticated fixed-host fetcher is `Safe`. `Exec` is closed (#5 / #20 — don't reintroduce).51 - **Output shape(s).** A multi-shape fetcher branches on `ctx.shape` inside `compute` / `fetch` and lists every variant in `shapes()`. **Enumerate every `Shape` variant from `src/render/mod.rs::Shape` and accept-or-reject each one** — don't just pick the obvious ones from the catalog blurb. The bar for *acceptance* is "can this data be meaningfully expressed in this form?", not "is this the most natural form?". A list of files renders as Text (comma-joined `"main.rs (42), lib.rs (31), …"`), TextBlock (one row each), Entries (path→count), Bars (label+value) — all four pass. Look at how the closest sibling fetcher in the same family handled each shape; family precedent is a strong signal. Reject **only** when no representation exists (Heatmap needs a 2D grid you don't have, Timeline needs chronological order, Calendar needs a year/month). "Feels redundant with another shape" or "the catalog didn't mention it" are not rejection reasons. Present the full 12-row table in the spec so the user can challenge any verdict before code lands.52 - **Options.** Per-fetcher options struct (`#[derive(Deserialize)]`, `serde(deny_unknown_fields)`) parsed from `ctx.options`; surface each field as an `OptionSchema` entry so the docs generator picks it up.534. **For renderers:**54 - **Family prefix** (`text_*`, `list_*`, `chart_*`, `gauge_*`, `grid_*`, `status_*`, `media_*`, `animated_*`). Module name = registered name.55 - **Accepted shapes.** **Exactly one.** Primary renderers are single-shape — the only exception is animated post-process wrappers (`animated_postfx`, `animated_boot`, `animated_scanlines`, `animated_splitflap`, `animated_wave`) that delegate to an inner renderer via `ALL_SHAPES`. If two shapes feel like they should share a renderer, register two renderers that share helpers (`text_plain` vs `list_plain` is the canonical pair).56 - **`animates()`** — return `true` only if output changes across calls within a single draw cycle.57 - **Options.** First check whether an existing field on `RenderOptions` already covers it (`align`, `color`, `label`, `marker`, `bullet`, `style`, …). Add a new field only when none fits, and wire an `OptionSchema` entry on the renderer.585. **Color semantics warning** (renderers). Don't bake a context-specific colour map as the default. The `gauge_battery` lesson: a battery-coloured fill (low → red) silently mis-coloured every "fraction used" widget. If the colour mapping depends on caller intent, default to `theme.text` and add an opt-in `tone` / `mode` option.596. **Memory check.** If the user says 考えたい / 検討したい, propose options and stop — don't branch yet.6061State the plan back in 3–5 bullets and **ask an explicit yes/no question** ("Proceed with this plan?") — don't just narrate the plan and roll into Phase 2. Stop until the user answers. Auto mode does not override this; naming, options, and safety class all leak into user-facing config and are painful to undo.6263## Phase 2 — Implement (実装)64651. **Files.**66 - Fetcher: `src/fetcher/<family>/<name>.rs` (or `src/fetcher/<family>.rs` if the family is one file).67 - Renderer: `src/render/<family>_<variant>.rs`.682. **Pattern.** Open the closest sibling and mirror its structure (constants → struct → trait impl → helpers → tests).693. **Wire up.**70 - Renderer: `mod` line + `r.register(...)` in `src/render/mod.rs::Registry::with_builtins`, add the name to the `registry_resolves_all_builtins` test list. If it becomes the default for a shape, update `default_renderer_for`. Override `xtask/src/snapshots.rs::renderer_dimensions` only when the default `(40, 5)` doesn't fit the renderer's natural aspect.71 - Fetcher: add to `realtime_fetchers()` / `cached_fetchers()` in the family module's registration entry point.724. **New shape?** Five-touch-point change per AGENTS.md: `Body` variant + `*Data` struct, `Shape` enum, `shape_of()`, `default_renderer_for()`, at least one renderer's `accepts()`.735. **Tests.** Inline `#[cfg(test)] mod tests`.74 - Renderer: use `src/render/test_utils.rs` (`render_to_buffer_with_spec`, `line_text`). Cover each visual mode (compact vs boxed, with/without label, clamp out-of-range, narrow area).75 - Fetcher: cover each shape branch + options parsing. Realtime fetchers test `compute` directly; cached fetchers test `sample_body` and the actual fetch path with whatever stand-in is appropriate.766. **House style.** English in code/comments/commits. `map` / `filter` / `for_each` over imperative loops. Functions ~10–15 lines, files ~100 lines as a guideline. Minimal comments — only when the *why* is non-obvious.7778## Phase 3 — Self-review (各種レビュー)7980Run the gates **before** asking the user to verify:8182```bash83cargo fmt --all -- --check84cargo clippy --all-targets -- -D warnings85cargo test86```8788Then read your own code critically. Common findings worth catching yourself:8990- **Naming**: family prefix consistent? module name matches registered name?91- **Edge cases**: empty body, `width == 0`, very narrow area, ratio out of `[0,1]`, fetchers handling timezone / locale defaults.92- **Color semantics** (renderer): does the default mapping make sense for every fetcher listed under `Compatible fetchers`? Add a `tone`-style opt-in if not.93- **Safety class** (fetcher): re-apply the Phase 1 rule — config-controlled destination → `Network`, otherwise `Safe`.94- **Test coverage**: each visual mode / shape branch has a test? `clamps_out_of_range`, `empty_area_does_not_panic`, narrow-area degradation if applicable.95- **Catalog parity**: `cargo run --bin splashboard -- catalog renderer <name>` (or `... fetcher <name>`) prints the option schema and compatible counterparts you intended.9697Report findings as a punch list with severity (🔴 / 🟡 / 🟢). Apply 🔴 fixes before Phase 4.9899## Phase 4 — User smoke-test (動作確認)100101The CLI requires a real tty, so you can't visually verify colours yourself. Hand off to the user:1021031. **Write a temporary `./.splashboard/dashboard.toml`** exercising the new fetcher / renderer in 2–3 configurations (renderer: default + each opt-in tone / variant + compact size; fetcher: with and without each meaningful option).1042. **Tell the user:** `cargo run --bin splashboard` from the repo root.1053. **Wait for confirmation.** Visual / colour issues that ASCII tests miss show up here.106107## Phase 5 — Wrap up (完了)108109- **Delete `.splashboard/dashboard.toml`** after the user signs off — it was a throwaway smoke-test fixture, not a ship artifact. Keep it only when the user explicitly says so. Note: in splashboard's own repo a committed `.splashboard/dashboard.toml` doubles as the "ship-with-the-clone" project dashboard, so committing one is a separate decision the user has to make deliberately, not a side-effect of the smoke-test.110- **Don't commit unprompted.** Wait for explicit ask. When you do commit:111 - Conventional commit (`feat(render): ...`, `feat(fetcher): ...`).112 - `Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>` trailer.113 - Tick the relevant `#41` sub-issue checkbox in the PR description, not in code.114- **PR description** mirrors the auto-generated reference page so reviewers see what `/reference/...` will show after the docs site rebuilds. Run `cargo xtask` locally; copy the relevant block from `docs-site/src/content/docs/reference/{renderers,fetchers}/<family>/<name>.md` (untracked — `.gitignore`'d). Strip the frontmatter, keep `Accepts` / `Animates` / `Options` / `Theme tokens` / `Compatible fetchers` (or `Compatible renderers` for fetchers), and rewrite the relative `../../...md` links to absolute `https://splashboard.unhappychoice.com/reference/...` URLs.115- **Merge strategy** is `--merge` (merge commit). Never rebase or squash — captured project preference.116- **Tick the catalog checkbox after merge.** Once the PR lands, edit the relevant sub-issue (`#61` for renderers, `#62`–`#68` for fetchers) and flip the candidate's `[ ]` to `[x]`. Replace the planned blurb with a one-line "Shipped in #NNN" note plus any non-obvious behaviour decisions worth recording (auto-fit, opt-in options, threshold quirks). The PR description alone isn't enough — the issue body is the catalog index everyone scans for what's still open. Use `gh issue view <N> --json body -q .body` to dump it, edit, then `gh issue edit <N> --body-file <path>` to apply.117118## Out of scope119120- **Plugin protocol (#5)** and **command widget (#20)** are closed. If the user's request shape is "run a subprocess and render its output", explain why it's rejected and steer toward `basic_read_store` (the user writes the file) or a dedicated built-in.121- **Screensaver-style persistent loops.** Animation lives within the `ANIMATION_WINDOW`; a dedicated idle-loop sub-mode is a different product.122123---124> Source: [unhappychoice/splashboard](https://github.com/unhappychoice/splashboard) — distributed by [TomeVault](https://tomevault.io).125<!-- tomevault:4.0:skill_md:2026-06-29 -->