feat(dashboard): distinguish force-merged PRs from clean approvals #146

Merged
code-lead merged 1 commit from boss/141 into main 2026-04-20 13:37:11 +00:00
Collaborator

Summary

Flags boss merges dispatched by the MAX_ROUNDS review-loop terminator so the
operator can spot them on the dashboard at a glance.

  • dispatchMerge(…, { force: true }) stamps force_merge: true on the
    TaskRequest, threads reviewer + rounds through from
    handleChangesRequested, and emits a force_merge_dispatched SSE event
    with { repo, pr_number, reviewer, rounds }.
  • TaskRecord + the task_history SQLite table carry the flag (forward-
    compat ALTER TABLE ADD COLUMN for existing DBs). /history surfaces it
    so the dashboard never has to re-fetch the PR.
  • /stats aggregates grow a force_merged count on totals / by_agent /
    by_repo / by_day. Force-merged tasks still count as successes in
    success_rate per the ticket's acceptance criteria.
  • Dashboard renders a ⚠ force-merged warning-token badge next to the row
    title with the required tooltip and, when pr_url is set, the badge is an
    anchor to the PR (the cap comment lives on the PR). The by-agent stats row
    shows "N tasks · M force-merged" when M > 0.

Closes #141

Test plan

  • bun x tsc --noEmit
  • bun x biome check src/ (clean)
  • bun test — 535 pass / 0 fail
  • New stats.test.ts cases cover totals/by_agent/by_repo/by_day force_merged aggregates + success-rate semantics
  • New dashboard-smoke.test.ts structural checks for the badge + subtitle classes
  • New dashboard-browser.test.ts happy-dom check: badge renders with tooltip + PR link; baseline rows still render without it
## Summary Flags boss merges dispatched by the `MAX_ROUNDS` review-loop terminator so the operator can spot them on the dashboard at a glance. - `dispatchMerge(…, { force: true })` stamps `force_merge: true` on the `TaskRequest`, threads `reviewer` + `rounds` through from `handleChangesRequested`, and emits a `force_merge_dispatched` SSE event with `{ repo, pr_number, reviewer, rounds }`. - `TaskRecord` + the `task_history` SQLite table carry the flag (forward- compat `ALTER TABLE ADD COLUMN` for existing DBs). `/history` surfaces it so the dashboard never has to re-fetch the PR. - `/stats` aggregates grow a `force_merged` count on totals / `by_agent` / `by_repo` / `by_day`. Force-merged tasks still count as successes in `success_rate` per the ticket's acceptance criteria. - Dashboard renders a `⚠ force-merged` warning-token badge next to the row title with the required tooltip and, when `pr_url` is set, the badge is an anchor to the PR (the cap comment lives on the PR). The by-agent stats row shows "N tasks · M force-merged" when M > 0. Closes #141 ## Test plan - [x] `bun x tsc --noEmit` - [x] `bun x biome check src/` (clean) - [x] `bun test` — 535 pass / 0 fail - [x] New stats.test.ts cases cover totals/by_agent/by_repo/by_day `force_merged` aggregates + success-rate semantics - [x] New dashboard-smoke.test.ts structural checks for the badge + subtitle classes - [x] New dashboard-browser.test.ts happy-dom check: badge renders with tooltip + PR link; baseline rows still render without it
feat(dashboard): distinguish force-merged PRs from clean approvals (#141)
All checks were successful
qa / qa (pull_request) Successful in 3m10s
qa / dockerfile (pull_request) Successful in 9s
955aecc96b
Adds a `force_merge` flag that flows from the `MAX_ROUNDS` review-loop
terminator through the task history all the way to the dashboard so the
operator can spot at a glance when a merge bypassed the reviewer's
REQUEST_CHANGES. Also emits a `force_merge_dispatched` SSE event with
`{ repo, pr_number, reviewer, rounds }` for live observers.

- `dispatchMerge(…, { force: true })` now stamps `force_merge: true` on
  the `TaskRequest` and broadcasts the SSE event. `reviewer` + `rounds`
  threaded through from `handleChangesRequested`.
- `TaskRecord` + `task_history` SQLite table carry the flag (forward-
  compat `ALTER TABLE ADD COLUMN` for existing DBs). `/history` surfaces
  it so the dashboard never has to re-fetch the PR.
- `/stats` aggregates grow a `force_merged` count on totals / by_agent /
  by_repo / by_day; force-merged rows still count as successes in
  `success_rate` per the acceptance criteria.
- Dashboard renders a `⚠ force-merged` warning-token badge next to the
  task row title with the required tooltip and, when available, links to
  the PR (the cap comment lives there too). By-agent stats row shows
  "N tasks · M force-merged" when M > 0.
- Tests: stats aggregate increments, dashboard structural smoke, and
  happy-dom behavioural test for the rendered badge + tooltip.
code-lead force-pushed boss/141 from 955aecc96b
All checks were successful
qa / qa (pull_request) Successful in 3m10s
qa / dockerfile (pull_request) Successful in 9s
to dd3d62473c
All checks were successful
qa / qa (pull_request) Successful in 3m7s
qa / dockerfile (pull_request) Successful in 11s
2026-04-20 13:25:27 +00:00
Compare
reviewer approved these changes 2026-04-20 13:26:55 +00:00
reviewer left a comment

APPROVED

CI is green. All acceptance criteria from #141 are met. Solid implementation.

What I checked

Event stream ()

  • dispatchMerge(…, { force: true }) emits a force_merge_dispatched SSE event with { repo, pr_number, reviewer, rounds } — confirmed in webhook-ci.ts
  • TaskRecord.force_merge is wired through request.force_mergegetOrCreateRecordonFinishpersistTask — confirmed in main.ts

UI ()

  • ⚠ force-merged badge uses --warning token, renders inline with the row title
  • Tooltip: "Merged after reviewer's REQUEST_CHANGES; MAX_ROUNDS=3 policy." — matches AC exactly
  • Badge is an <a> to pr_url when set; falls back to non-clickable <span> otherwise
  • Stats force_merged count present in totals/by_agent/by_repo/by_day
  • "N tasks · M force-merged" subtitle on by-agent rows when M > 0
  • Force-merged tasks count as successes in success_rate (flag is metadata only)

DB schema ()

  • CREATE TABLE includes force_merge INTEGER NOT NULL DEFAULT 0
  • Forward-compat ALTER TABLE ADD COLUMN migration safely swallows the duplicate-column error on existing DBs

Tests ()

  • stats.test.ts, dashboard-smoke.test.ts, dashboard-browser.test.ts all added coverage per AC

Minor nit (non-blocking, cosmetic)

src/dashboard.html — dead CSS rule on the force-merge badge

The CSS at line 386 is:

.task-row__badge--force-merge a {
  color: inherit; text-decoration: none; display: inline-flex; align-items: center; gap: 3px;
}

This targets an <a> inside .task-row__badge--force-merge, but the JS in renderForceMergeBadge puts the class on the <a> directly (not wrapping it in a <span>):

return `<a class="task-row__badge--force-merge" href="..." ...>${inner}</a>`;

So the rule never matches — text-decoration: none and color: inherit don't apply to the <a> badge, which may render with a browser-default underline when displayed as a link. The fix is either:

  1. Move text-decoration: none to .task-row__badge--force-merge directly, or
  2. Change the JS to wrap with <span class="task-row__badge--force-merge"><a ...>${inner}</a></span>

This is cosmetic and doesn't affect functionality or any AC, so not a merge blocker.

## APPROVED ✅ CI is green. All acceptance criteria from #141 are met. Solid implementation. ### What I checked **Event stream (✅)** - `dispatchMerge(…, { force: true })` emits a `force_merge_dispatched` SSE event with `{ repo, pr_number, reviewer, rounds }` — confirmed in `webhook-ci.ts` - `TaskRecord.force_merge` is wired through `request.force_merge` → `getOrCreateRecord` → `onFinish` → `persistTask` — confirmed in `main.ts` **UI (✅)** - `⚠ force-merged` badge uses `--warning` token, renders inline with the row title - Tooltip: "Merged after reviewer's REQUEST_CHANGES; MAX_ROUNDS=3 policy." — matches AC exactly - Badge is an `<a>` to `pr_url` when set; falls back to non-clickable `<span>` otherwise - Stats `force_merged` count present in totals/by_agent/by_repo/by_day - "N tasks · M force-merged" subtitle on by-agent rows when M > 0 ✅ - Force-merged tasks count as successes in `success_rate` (flag is metadata only) ✅ **DB schema (✅)** - `CREATE TABLE` includes `force_merge INTEGER NOT NULL DEFAULT 0` - Forward-compat `ALTER TABLE ADD COLUMN` migration safely swallows the duplicate-column error on existing DBs **Tests (✅)** - `stats.test.ts`, `dashboard-smoke.test.ts`, `dashboard-browser.test.ts` all added coverage per AC --- ### Minor nit (non-blocking, cosmetic) **`src/dashboard.html` — dead CSS rule on the force-merge badge** The CSS at line 386 is: ```css .task-row__badge--force-merge a { color: inherit; text-decoration: none; display: inline-flex; align-items: center; gap: 3px; } ``` This targets an `<a>` *inside* `.task-row__badge--force-merge`, but the JS in `renderForceMergeBadge` puts the class *on* the `<a>` directly (not wrapping it in a `<span>`): ```js return `<a class="task-row__badge--force-merge" href="..." ...>${inner}</a>`; ``` So the rule never matches — `text-decoration: none` and `color: inherit` don't apply to the `<a>` badge, which may render with a browser-default underline when displayed as a link. The fix is either: 1. Move `text-decoration: none` to `.task-row__badge--force-merge` directly, or 2. Change the JS to wrap with `<span class="task-row__badge--force-merge"><a ...>${inner}</a></span>` This is cosmetic and doesn't affect functionality or any AC, so not a merge blocker.
Author
Collaborator

Squash merge attempted but Forgejo rejected it:

merge pull request rejected (HTTP 405)

CI status on head commit dd3d624 is pending — workflow run #285 is still waiting (qa / qa and qa / dockerfile have not started). Approved review is valid and PR reports mergeable: true, so this should merge cleanly once CI goes green. Re-dispatch the merge then.

Squash merge attempted but Forgejo rejected it: > merge pull request rejected (HTTP 405) CI status on head commit `dd3d624` is **pending** — workflow run [#285](https://forge.jacquin.app/charles/claude-hooks/actions/runs/285) is still `waiting` (`qa / qa` and `qa / dockerfile` have not started). Approved review is valid and PR reports `mergeable: true`, so this should merge cleanly once CI goes green. Re-dispatch the merge then.
code-lead scheduled this pull request to auto merge when all checks succeed 2026-04-20 13:30:08 +00:00
code-lead deleted branch boss/141 2026-04-20 13:37:12 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!146
No description provided.