Skip to content

feat(widget): campaign-digest built-in — the home-dashboard digest over the campaign routes (#678) - #690

Merged
aarontrowbridge merged 3 commits into
mainfrom
feat-campaign-digest-widget
Aug 31, 2026
Merged

feat(widget): campaign-digest built-in — the home-dashboard digest over the campaign routes (#678)#690
aarontrowbridge merged 3 commits into
mainfrom
feat-campaign-digest-widget

Conversation

@aarontrowbridge

Copy link
Copy Markdown
Member

Closes #678

What

The first consumer of the campaign routes (#662): a built-in home-dashboard widget — the Campaign Inspector's digest surface, shipping before the Work Column tab per the approved Armonia design ("home digest widget ships FIRST — zero navigation").

The tile: eyebrow (campaign name) · one-line objective (ellipsized) · up to 3 verdict chips from the newest ledger (unit + leading status token, keyword-toned: DONE/MERGED → success, BLOCK/FAIL → danger) · the needs-you line (§4 blocked's first line — "⚠ needs you — …" or muted "nothing blocked") · click composes Open the campaign <slug> into the chat via amico.prompt.

Discipline: esc() on every interpolation; --amc-* tokens only (no hex, no rgb, no localStorage — the sandboxed frame forbids it); empty state on no campaigns / fetch failure / ok:false; onConfig/onTheme re-renders behind an epoch guard; ACTIVE pick is case-insensitive with newest-overall fallback (documented). Unread-state persistence is explicitly v2 (needs a durable last-seen signal the opaque-origin frame can't own).

Beyond the repo's string-level test decision, behavioral evidence: the emitted module was executed against a fake bridge across 7 scenarios — active pick, fallback, empty states, fetch rejection, click composition, and a hostile <script> objective rendering fully escaped.

Acceptance criteria — evidence (all six pass; director-verified)

  • Registered in BUILTIN_SOURCES after jump-back-in, valid tile manifest — registry tests 12/12 + HTTP smoke (/amicode/widgets lists both ids)
  • Fetches routes, renders active campaign, empty-state on none — string pins + scratch execution (3 scenarios)
  • Fetch failures → empty state — bridge rejection + ok:false scenarios
  • Click → amico.prompt('Open the campaign <slug>') — asserted in scratch execution
  • esc throughout, --amc-* only — vitest pin + hostile-input render
  • Registry/content-hash consistency — idempotence test + /amicode/widgets + widget-code route bodies served

Director gates (independently run)

Merge

Awaiting human review per campaign discipline. Merge order: #662, then this. CI runs on this PR.

…on, routes, escaping (#678)

RED state for issue #678: the pins fail against a registry without the
campaign-digest built-in (collection failure — the module does not exist
yet). String-level assertions per the issue's testing decision: the
widget's JS is a source string, so behavior is pinned on the registry
entry + the route bodies, not by executing the iframe.
… over the campaign routes (#678)

The first consumer of the campaign routes (#658/#662): a tile that fetches
/amicode/campaigns, picks the newest ACTIVE campaign (falling back to the
newest overall — a finished campaign still deserves a tile until the next
one starts), fetches its /amicode/campaign detail, and renders the
one-line objective, up to 3 verdict chips (slice/unit + the status cell's
leading token — display compression, not parsing), and the §4 blocked
section as the needs-you line. Click composes 'Open the campaign <slug>'
into chat via amico.prompt. Registered in BUILTIN_SOURCES after
jump-back-in (default tile order). Empty state on no campaigns and on any
fetch failure (the jump-back-in discipline); esc() throughout; theme via
--amc-* custom properties only; re-renders on onConfig/onTheme pushes
with an epoch guard against stale fetch writes.
… golden entries (#678)

The fork pin (v1.18.10-amicode.18) records /amicode/widgets and the
dashboard GET/POST with jump-back-in as the only built-in; the port now
ships campaign-digest, so the three registry-projecting entries drift.
Same treatment as normalizePortExtensions (#447): strip the port's
deliberate extension from BOTH sides so parity stays comparable across
the next pin bump; the widget's registration is unit-tested in
widgets_registry.test.ts.
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 4e02a42e-a474-40ab-b1c7-577d90527e60


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@aarontrowbridge
aarontrowbridge marked this pull request as ready for review August 31, 2026 21:38
@aarontrowbridge
aarontrowbridge merged commit 58a5cc9 into main Aug 31, 2026
8 checks passed
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.

widget: campaign-digest built-in — the home-dashboard digest over the campaign routes

1 participant