Skip to content

[Air #1440] feat: dedicated send-queue/open-terminal Stream Deck action icons - #1443

Merged
amrmelsayed merged 7 commits into
mainfrom
builder/air-1440
Aug 13, 2026
Merged

[Air #1440] feat: dedicated send-queue/open-terminal Stream Deck action icons#1443
amrmelsayed merged 7 commits into
mainfrom
builder/air-1440

Conversation

@amrmelsayed

@amrmelsayed amrmelsayed commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Give the send-queue (Send Feedback) and open-terminal (Open Terminal) Stream Deck actions their own icons instead of borrowing the shared action asset, and drop six verified-dead PNGs. Icons are rendered from the glyph vectors already in src/face.ts — the same comment / terminal vectors the runtime key face draws — so the action-picker icon and the hardware key agree by construction.

Closes #1440

What Changed

  • Two new icon sets, four PNGs each (icons/<name>.png 72, @2x 144, icons/list/<name>.png 20, @2x 40):
    • send-queue ← the comment glyph
    • open-terminal ← the terminal glyph
  • manifest.json: repointed both actions' Icon and States[].Image off icons/(list/)action onto their dedicated assets. Elgato streamdeck validate passes.
  • Removed six verified-dead PNGs (grep-confirmed unreferenced in source + manifest): approve-gate-empty, approve-gate-pending, gate-nav, each +@2x. Live approve-gate.* / list/approve-gate.* kept.
  • scripts/render-action-icons.mjs: a committed one-off render script (see decisions below).
  • Tests: manifest-icons.test.ts (asset-integrity, dedicated-wiring, and committed-dimension guards) and render-action-icons.test.ts (single-source extraction contract).

Key Decisions

  • Single source, honored by parsing face.ts at build time. The render script reads src/face.ts, extracts the comment / terminal entries from the GLYPHS map (and reproduces the stroked() wrapper), and rasterizes from that. The PNGs therefore derive from the exact vector the runtime face uses — no re-drawn duplicate, and face.ts is not edited (bugfix-1431 is in flight there; this PR's source diff is manifest.json + the new test/script files only).
  • Fit to the glyph's true bounding box, not its 24×24 box. The glyphs don't fill their authored box (comment ≈ 18×17, terminal ≈ 20×16), and a transparent list icon needs far less padding than a rounded key image. The script rasterizes the glyph, trims to its drawn bbox, then scales that to the fill fraction the existing icons use (measured: list/* ≈ 95%, key images ≈ 56%), with a build-time self-check that fails if a list icon drops below 80% coverage. (This corrects a first-pass defect the CMAP round caught — see below.)
  • Rasterization: system rsvg-convert (librsvg) + magick (ImageMagick), no new npm dependency. Per the issue scope, a one-time asset build prefers repo-available tooling over adding a dependency solely to turn SVG into PNG. The script is committed for reproducibility; re-run with node scripts/render-action-icons.mjs (needs librsvg + imagemagick, both checked with a friendly error). Alternatives considered: adding @resvg/resvg-js or sharp as a devDep — rejected as a dependency for a one-off.
  • Frame matches the existing convention, measured from the live action / approve-gate assets: key image = rounded rect rx=12, fill #1C2128, white glyph; list icon = white glyph on transparent.

CMAP Review (AIR / pr)

  • Gemini — APPROVE (HIGH): clean single-source render, good coverage, zero runtime deps.
  • Claude — REQUEST_CHANGES (HIGH), now addressed: flagged the first-pass list icons filling ~45% of the frame vs the ~95% convention (undersized in the picker). Verified against the PNGs and fixed via the bbox-fit pipeline above; re-measured list @2x 38×36 / 38×30 (sibling 38×34), key @2x 81×77 / 81×65 (sibling 80×68). Also applied its minors: friendly ENOENT for both tools, quoted-key-safe extractGlyph regex, committed-dimension test guard.
  • Codex — unavailable: external OpenAI billing error ("no credits remaining"), not a code finding.
  • Deferred (architect's call): open-terminal's glyph resembles the still-shared icons/action used by the catch-all "Codev Action"; re-glyphing Codev Action (e.g. to bolt) would fully resolve the picker ambiguity. The terminal → open-terminal mapping itself is baked scope.

Test Plan

  • Unit tests added (manifest-icons.test.ts, render-action-icons.test.ts)
  • Build passes (pnpm --filter @cluesmith/codev-streamdeck build)
  • All tests pass (vitest: 162 passed)
  • streamdeck validate passes
  • Glyphs visually confirmed (comment bubble + terminal window), sized to match siblings

Review Notes

  • The render script parses GLYPHS out of face.ts via regex rather than importing it (the map is module-private, and face.ts is off-limits while bugfix-1431 is in flight). If that declaration shape drifts, extractGlyph throws — a loud build failure, covered by a test — rather than silently shipping a stale icon.
  • The pre-populated SD+ profile from the issue is intentionally out of scope (deferred — it needs hardware-verified import before it can ship), per the architect's authoritative scoping comment.

…dered from face.ts glyphs

Render the send-queue (comment glyph) and open-terminal (terminal glyph) manifest
action icons from the same GLYPHS vectors the runtime key face uses, so the
action-picker icon and the hardware key agree by construction. Repoint the manifest
Icon + States[].Image off the shared 'action' asset. Remove six verified-dead PNGs
(approve-gate-empty, approve-gate-pending, gate-nav, each +@2x).

A committed one-off render script (scripts/render-action-icons.mjs) parses the glyph
vector out of src/face.ts and rasterizes via system rsvg-convert (no new npm dep),
keeping the PNGs derived from a single source without editing face.ts.
Claude's PR review (verified against the PNGs) caught the first-pass list icons
filling ~45% of the frame vs the ~95% every other list/* asset uses: listSvg reused
the key-frame padding, and the glyphs don't fill their authored 24x24 box. Rewrite the
render pipeline to rasterize the glyph, trim to its true bbox, then fit it to the
convention's fill fraction (list 0.94, key 0.56) via rsvg-convert + magick, with a
build-time self-check that fails below 80% list coverage. Re-measured against siblings:
list @2x 38x36 / 38x30 (sibling 38x34), key @2x 81x77 / 81x65 (sibling 80x68).

Also from the review: friendly ENOENT for both system tools, quoted-key-safe
extractGlyph regex, and a zero-dep PNG-dimension test guard.
@amrmelsayed

Copy link
Copy Markdown
Collaborator Author

Integration review (streamdeck architect) — APPROVE. Low-risk tier (assets + manifest), verified by looking at the rendered PNGs, not just reading the diff — the right check for an image change.

Verified:

  • Single source honoured. render-action-icons.mjs parses the glyph vectors out of face.ts's GLYPHS rather than re-drawing them, so the picker icon and the runtime key face agree by construction. face.ts itself untouched (bugfix-1431 owns it — caution respected).
  • No new dependency. Rasterization uses system rsvg-convert + magick as one-time dev tooling, which is what the scope asked for; the script is committed for reproducibility with the toolchain documented in its header.
  • The fit work is the real craft here (and closes claude's REQUEST_CHANGES): the glyphs don't fill their authored 24×24 box, so the script trims to the true drawn bbox and scales to the measured fill fractions of the existing assets (~95% for list/*, ~56% for key images). Sizes all match convention: 72/144 and 20/40.
  • Dead assets gone: approve-gate-empty, approve-gate-pending, gate-nav (+@2x); the live approve-gate.* pair kept.
  • Manifest repointed for both actions, Icon and States[].Image.

Board was degraded (codex unavailable — OpenAI billing, raised to the owner), so this ran 2-way + architect verification per the interim policy.

Residual, non-blocking — the builder flagged it and it is real. I compared the renders side by side: the new open-terminal icon (terminal window with a > prompt) is visually near-identical to the existing catch-all Codev Action icon, which is itself a terminal glyph. So that particular picker ambiguity is not resolved by this PR — but it is not regressed either: today those two actions literally share one asset file, so they are identical by definition. This PR is a strict improvement (Send Feedback is now unmistakable, dedicated assets exist, dead weight gone). Filed the re-glyph as a follow-up rather than expanding an AIR lane.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stream Deck: polish follow-ups from #1410 (dedicated action icons; optional pre-populated SD+ profile)

1 participant