Skip to content

fix(ads): worker drains every approved spot per tick and Approve names the render window (gh-#745) - #764

Merged
genwave-radio merged 5 commits into
mainfrom
fix/ads-approve-drain-745
Sep 14, 2026
Merged

genwave-radio merged 5 commits into
mainfrom
fix/ads-approve-drain-745

Conversation

@genwave-radio

Copy link
Copy Markdown
Collaborator

Fixes #745 · STORY-432 (worker drains every approved spot per tick) · STORY-433 (Approve tells me when the station will render it) · PLAN T455–T459.

🐛 What was wrong

AdSpotWorker rendered one approved spot per 10-minute tick. Approve three spots in the wizard and the second and third sat in Approved for 20–30 minutes with nothing on screen saying why. The toast just said "Spot approved."

✅ What changed

  • T456 — the worker drains every Approved spot each tick (cap MaxRendersPerTick = 25, still yields to on-air renders). Log line: Ad spot worker rendered {Count} approved spot(s) this tick.
  • T457 — AdSpotDto.renderWithinMinutes = Ads:WorkerIntervalMinutes while a spot is Approved, null in every other state.
  • T458 — Approve toasts Approved. The station will render it within 10 minutes. (singular at 1) from both the row and the wizard's approve-without-a-preview step. describeApproved() in admin-ui/lib/ads-api.ts is the single copy source.
  • T455 — merge guard resolves its hook path via ${CLAUDE_PROJECT_DIR:-$PWD} so it works outside a Claude project session (.claude/settings.example.json, .claude/README.md, git-workflow skill). This is the settings fix that was blocking the guard locally; settings.local.json itself is personal and gitignored.

Specs: tests/GenWave.Host.Tests/Specs/Story432_DrainApprovedSpots.cs, Story433_RenderWindow.cs, admin-ui/__specs__/approve-copy.spec.tsx (red at a287e17, green now).

🔌 Wire evidence (T459, dev stack via ./launch.sh)

Approved spots 18, 19, 20 from the Ads page inside one interval. One worker pass:

api-1  |       Ad spot 20 rendered and is ready to air
api-1  |       Ad spot 19 rendered and is ready to air
api-1  |       Ad spot 18 rendered and is ready to air
api-1  |       Ad spot worker rendered 3 approved spot(s) this tick

GET /api/ads/{18,19,20} → "state":"ready". Approve responses carried "renderWithinMinutes":10.

Toast screenshot (Next dev server against a Release Kestrel with Ads__WorkerIntervalMinutes=7): reads "Approved. The station will render it within 7 minutes." — sent to Dean directly; the repo does not carry screenshots.

📝 Notes for review

  • T456 retires the Story391 "one render per tick" fact on purpose.
  • The toast copy reads as a promise. The worker caps at 25 per tick and yields to on-air renders, so a pile of >25 approvals or a busy on-air window can exceed the window. Judged acceptable for a private station; say if you want the copy hedged.
  • The old $HOME-based hook path is the root cause of the guard misfire; T455 is the fix, not a workaround.
  • Pre-existing, not this branch: toasts never mount on the production admin-ui build (the [data-sonner-toaster] node never appears; reproduced on main's image 747e5649686b). Filed as a follow-up issue. Approvals themselves succeed either way; the copy is verified on the dev server and in Jest.

Do not merge without Dean.

STORY-432/433 specs from /plan: the worker drains every approved spot per
tick (bounded), and an approved spot's DTO/toast names the render window.
Red until T456–T458 land.
…ject session

The tracked hook command used "$CLAUDE_PROJECT_DIR" alone, which expands to
/.claude/hooks/merge-guard.sh and fails open when the variable is unset. Fall
back to $PWD so the guard still fires in sessions started from the project root.
The README and git-workflow skill name the fallback. gh-#745 PR-A rider.
RenderOneIfDueAsync claimed one approved spot per tick, so approving three
spots left two of them waiting a full interval each (gh-#745). RenderDueAsync
now loops ClaimNextApprovedAsync until the queue is empty or
MaxRendersPerTick (25) is hit, re-checking the on-air render gate before
every claim; a per-spot failure marks that spot failed and the drain
continues, a claim conflict or a cancelled render stops the tick. Logs the
drained count once. Story391's one-per-tick fact pinned the old behaviour
and is retired on purpose; Story432's failure scenario gains the
InvokeDelegates=false arrange the render-service specs already use.
Add `renderWithinMinutes` to `AdSpotDto`: the configured
`Ads:WorkerIntervalMinutes` for a spot in the Approved state, `null` for
every other state. The admin UI uses it to tell the operator roughly when
the station will pick the spot up (STORY-433 AC1-AC3, gh-#745). The TS
`AdSpotDto` mirror and the spec fixtures gain the field so
`typecheck:specs` stays green.
The row toast and both wizard approve buttons now read "Approved. The
station will render it within N minutes." (singular at 1) from the
approve response's `renderWithinMinutes`, falling back to "Approved."
when the window does not apply. New `describeApproved` helper in
ads-api.ts keeps both callers on one sentence. "Approve without a
preview" no longer looks like nothing happened (STORY-433 AC4-AC7,
gh-#745).
@genwave-radio
genwave-radio merged commit 57dc1f9 into main Sep 14, 2026
11 checks passed
@genwave-radio
genwave-radio deleted the fix/ads-approve-drain-745 branch September 14, 2026 01:01
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 14, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ads: approved spots render one per 10-minute worker tick — approving several in the wizard looks like nothing happened

1 participant