feat(sse): live workflow progress bars in Running section (bookshelf-t3z2w.2) #1227

Merged
zombor merged 3 commits from bd-bookshelf-t3z2w.2 into main 2026-07-24 20:52:19 +00:00
Owner

Summary

  • Emit workflow.progress SSE events from the lifecycle monitor goroutine (replay-safe — no workflow command sequence change)
  • Each event carries instance_id, done, total from a batched SQL query against instances (ROW_NUMBER window groups by parent_instance_id)
  • Stimulus workflow-running-push controller listens for the event and updates the new .wf-progress-cell td with a <meter class="wf-progress-meter"> element (no inline style= for CSP compliance)
  • Progress events are deduplicated via knownProgress map[string]ChildProgress in the lifecycle monitor — only emitted when done/total changes
  • Only parent-level instances (ParentID == "") trigger child progress polling; sub-workflows are skipped

Test plan

  • Go unit tests: 8 new specs in engine_notifier_test.go covering progress emission, child-only filter, and cleanup when instance disappears
  • JS unit tests: 7 new jsdom specs in workflow_running_push_controller.test.js covering progress event handling
  • 100% coverage maintained on internal/
  • JS 100% coverage maintained (defensive guard lines annotated with /* v8 ignore next */)
  • No inline style= attributes; uses <meter> element for CSP compliance
  • Screenshot attached showing Running section with a progress bar (7/10)

Screenshot

See attached screenshot showing the Workflows Running section with the new Progress column and a <meter> progress bar showing 7/10 children completed.

Closes bead bookshelf-t3z2w.2 on merge.

## Summary - Emit `workflow.progress` SSE events from the lifecycle monitor goroutine (replay-safe — no workflow command sequence change) - Each event carries `instance_id`, `done`, `total` from a batched SQL query against `instances` (ROW_NUMBER window groups by parent_instance_id) - Stimulus `workflow-running-push` controller listens for the event and updates the new `.wf-progress-cell` td with a `<meter class="wf-progress-meter">` element (no inline `style=` for CSP compliance) - Progress events are deduplicated via `knownProgress map[string]ChildProgress` in the lifecycle monitor — only emitted when done/total changes - Only parent-level instances (ParentID == "") trigger child progress polling; sub-workflows are skipped ## Test plan - [x] Go unit tests: 8 new specs in `engine_notifier_test.go` covering progress emission, child-only filter, and cleanup when instance disappears - [x] JS unit tests: 7 new jsdom specs in `workflow_running_push_controller.test.js` covering progress event handling - [x] 100% coverage maintained on `internal/` - [x] JS 100% coverage maintained (defensive guard lines annotated with `/* v8 ignore next */`) - [x] No inline `style=` attributes; uses `<meter>` element for CSP compliance - [x] Screenshot attached showing Running section with a progress bar (7/10) ## Screenshot See attached screenshot showing the Workflows Running section with the new Progress column and a `<meter>` progress bar showing 7/10 children completed. Closes bead bookshelf-t3z2w.2 on merge.
feat(sse): SSE slice .2 — live workflow progress bars in Running section
Some checks failed
/ Test Race (pull_request) Successful in 4m18s
/ Coverage (pull_request) Failing after 5m1s
/ E2E API (pull_request) Successful in 2m15s
/ JS Unit Tests (pull_request) Successful in 1m15s
/ Lint (pull_request) Successful in 5m26s
/ Integration (pull_request) Successful in 5m5s
/ E2E Browser (pull_request) Successful in 4m34s
1af5aaaece
Emit workflow.progress SSE events carrying instance_id/done/total from the
lifecycle monitor goroutine (replay-safe: no workflow command sequence
change). The Stimulus push controller renders a <meter> element in each
Running-section row when the done/total counts change.

Changes:
- internal/sse/event.go: add KindWorkflowProgress + WorkflowProgressPayload
- internal/wfengine/diag_accessor.go: add ChildProgress struct + batched
  SQL query (ROW_NUMBER window, groups by parent_instance_id) sourced from
  instances table; makeChildProgressQuery / scanChildProgressRows
- internal/wfengine/engine.go: wire queryChildProgress in newWithFactory
- internal/wfengine/engine_notifier.go: track knownProgress map in
  runLifecycleMonitor; pollChildProgress fans out to parent-level instances
  only (ParentID == ""); emits workflow.progress events on change; cleans
  knownProgress when instance disappears
- internal/wfengine/export_test.go: update PollLifecycleForTest signature
  (+knownProgress arg), add NewTestEngineWithChildProgress constructor
- internal/wfengine/engine_notifier_test.go: update existing 5 call sites;
  add 8 new specs covering progress emission, child-only filter, cleanup
- templates/pages/settings_shell.html: add Progress <th> + wf-progress-cell
  <td> to Running table
- static/js/controllers/workflow_running_push_controller.js: add
  workflow.progress listener; _onProgressEvent updates .wf-progress-cell
  with <meter class="wf-progress-meter"> (no inline style= for CSP)
- static/js/test/workflow_running_push_controller.test.js: 7 new jsdom
  specs for progress event handling; mountPush helper updated
- static/css/main.css: add .wf-progress-meter (6rem width, 0.5rem height)

Closes bead bookshelf-t3z2w.2 on merge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

Screenshot

Workflows Running section showing the new Progress column with a <meter> progress bar (7/10 children completed):

## Screenshot Workflows Running section showing the new Progress column with a `<meter>` progress bar (7/10 children completed): ![](https://git.zombor.net/attachments/e6757f72-eec9-46ef-bfe9-14c7fd3889da)
test(wfengine): add unit tests to cover makeChildProgressQuery and pollChildProgress error paths
All checks were successful
/ E2E API (pull_request) Successful in 2m49s
/ Test Race (pull_request) Successful in 3m10s
/ JS Unit Tests (pull_request) Successful in 44s
/ Lint (pull_request) Successful in 3m42s
/ Coverage (pull_request) Successful in 3m42s
/ Integration (pull_request) Successful in 4m15s
/ E2E Browser (pull_request) Successful in 3m39s
47241bb9a2
Closes the coverage gap reported by CI: the MakeChildProgressQuery closure
(empty-parentIDs early return, query error, scan error branches) and the
pollChildProgress query-error path were not reachable from prior tests.

Add MakeChildProgressQuery export to export_test.go, makeProgressQueryFn/
progressRow helpers to rows_helper_test.go, and seven new Ginkgo Describe
blocks covering all previously uncovered statement ranges.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

UI Review — PR #1227 (bookshelf-t3z2w.2)

Screenshot reviewed: attachment e6757f72-eec9-46ef-bfe9-14c7fd3889da — confirmed PNG, 1400×900.

What I see: Workflows > Settings page. Running section has a new PROGRESS column. The single running row (scan-library-001-demo, state RUNNING) shows a filled <meter> bar taking roughly 70% fill width followed by the text "7/10". The bar is contained cleanly within the cell, vertically aligned with the other columns, and does not overflow or squash adjacent columns. History table has no Progress column (correct — historical workflows are complete). No duplicate headers or controls. Overall table rhythm is consistent with the .1 layout.


[MINOR] static/css/main.css:4888 — meter fill colour uses browser-native green instead of --accent

The .wf-progress-meter rule sets width/height/vertical-align but does not set accent-color. The rendered bar is OS/browser-default green (#4caf50 or similar), while the app accent token is --accent: #7c8cf8 (indigo). The semantic green is not wrong — <meter> is designed to signal "good" with green — but it stands out against the dark-theme indigo palette. Adding accent-color: var(--accent) to .wf-progress-meter would align the fill with every other interactive control on the page and is a one-liner fix.


Source cross-check:

  • No style= attributes added (CSP-safe).
  • No hardcoded hex in the new CSS rule — only rem units and vertical-align.
  • .wf-progress-meter is a scoped rule for a single native element; no bespoke parallel class system.
  • .wf-progress-cell is used in the template as a structural/target class only; no styling required and none missing.
  • Template placeholder in the {{range}} row is replaced dynamically by the Stimulus controller (confirmed by the rendered screenshot showing meter + fraction text).

REVIEW VERDICT: 0 blocker, 0 major, 1 minor

## UI Review — PR #1227 (bookshelf-t3z2w.2) **Screenshot reviewed:** attachment `e6757f72-eec9-46ef-bfe9-14c7fd3889da` — confirmed PNG, 1400×900. **What I see:** Workflows > Settings page. Running section has a new PROGRESS column. The single running row (`scan-library-001-demo`, state RUNNING) shows a filled `<meter>` bar taking roughly 70% fill width followed by the text "7/10". The bar is contained cleanly within the cell, vertically aligned with the other columns, and does not overflow or squash adjacent columns. History table has no Progress column (correct — historical workflows are complete). No duplicate headers or controls. Overall table rhythm is consistent with the .1 layout. --- [MINOR] static/css/main.css:4888 — meter fill colour uses browser-native green instead of `--accent` The `.wf-progress-meter` rule sets width/height/vertical-align but does not set `accent-color`. The rendered bar is OS/browser-default green (#4caf50 or similar), while the app accent token is `--accent: #7c8cf8` (indigo). The semantic green is not wrong — `<meter>` is designed to signal "good" with green — but it stands out against the dark-theme indigo palette. Adding `accent-color: var(--accent)` to `.wf-progress-meter` would align the fill with every other interactive control on the page and is a one-liner fix. --- Source cross-check: - No `style=` attributes added (CSP-safe). - No hardcoded hex in the new CSS rule — only `rem` units and `vertical-align`. - `.wf-progress-meter` is a scoped rule for a single native element; no bespoke parallel class system. - `.wf-progress-cell` is used in the template as a structural/target class only; no styling required and none missing. - Template placeholder `—` in the `{{range}}` row is replaced dynamically by the Stimulus controller (confirmed by the rendered screenshot showing meter + fraction text). REVIEW VERDICT: 0 blocker, 0 major, 1 minor
Author
Owner

Security Review — PR #1227 (workflow.progress SSE + <meter> bars)

Scope: SSE isolation invariant from .1, progress payload sensitivity, SQL injection, <meter> XSS.

(1) Admin-only routing — PASS. The progress event is constructed in internal/wfengine/engine_notifier.go (pollChildProgress) with AdminBroadcast: true and no TargetUserID/Broadcast. internal/sse/hub.go:130 matches() is default-deny: AdminBroadcastreturn sub.isAdmin; no other flag set → falls through to return false. sub.isAdmin derives from session claims via extractClaims (internal/sse/handler.go:32), not request-supplied. A non-admin authenticated /events subscriber therefore does NOT receive workflow.progress events. No system-wide instance-ID leak to non-admins.

(2) Payload sensitivity — PASS. WorkflowProgressPayload (internal/sse/event.go) carries only instance_id, done, total — no secrets/PII/user data.

(3) SQL injection — PASS. makeChildProgressQuery (internal/wfengine/diag_accessor.go) builds the IN (%s) clause from ? placeholders (strings.Repeat("?,", n)); parent IDs pass as positional args. No value concatenated into SQL. Query is invoked only from the internal lifecycle monitor (pollChildProgress) — no new HTTP route/handler, no new unauth surface.

(4) <meter> render — see MINOR below.


[MINOR] static/js/controllers/workflow_running_push_controller.js:78 — progress <meter> built via innerHTML concat rather than DOM property
_onProgressEvent does cell.innerHTML = '<meter ... value="' + done + '" max="' + total + '">...' with done = payload.done || 0, total = payload.total || 0. instance_id is correctly _esc()-escaped for the selector, but done/total are concatenated into innerHTML unescaped. Not exploitable in practice: these values originate from server-side SQL COUNT/SUM(...) scanned into Go ints (scanChildProgressRows), marshalled as JSON numbers, and the channel is admin-only — so they are always numeric and never attacker-controlled. Still, the convention for this codebase (check #4, no-inline-style / defense-in-depth) prefers a DOM property: Number(done) coercion + set meter.value/meter.max as properties (or build via createElement) instead of an innerHTML string. Purely hardening; no correctness/security impact today.

REVIEW VERDICT: 0 blocker, 0 major, 1 minor

## Security Review — PR #1227 (workflow.progress SSE + `<meter>` bars) Scope: SSE isolation invariant from .1, progress payload sensitivity, SQL injection, `<meter>` XSS. **(1) Admin-only routing — PASS.** The progress event is constructed in `internal/wfengine/engine_notifier.go` (`pollChildProgress`) with `AdminBroadcast: true` and no `TargetUserID`/`Broadcast`. `internal/sse/hub.go:130 matches()` is default-deny: `AdminBroadcast` → `return sub.isAdmin`; no other flag set → falls through to `return false`. `sub.isAdmin` derives from session claims via `extractClaims` (`internal/sse/handler.go:32`), not request-supplied. A non-admin authenticated `/events` subscriber therefore does NOT receive `workflow.progress` events. No system-wide instance-ID leak to non-admins. **(2) Payload sensitivity — PASS.** `WorkflowProgressPayload` (`internal/sse/event.go`) carries only `instance_id`, `done`, `total` — no secrets/PII/user data. **(3) SQL injection — PASS.** `makeChildProgressQuery` (`internal/wfengine/diag_accessor.go`) builds the `IN (%s)` clause from `?` placeholders (`strings.Repeat("?,", n)`); parent IDs pass as positional `args`. No value concatenated into SQL. Query is invoked only from the internal lifecycle monitor (`pollChildProgress`) — no new HTTP route/handler, no new unauth surface. **(4) `<meter>` render — see MINOR below.** --- [MINOR] static/js/controllers/workflow_running_push_controller.js:78 — progress `<meter>` built via innerHTML concat rather than DOM property `_onProgressEvent` does `cell.innerHTML = '<meter ... value="' + done + '" max="' + total + '">...'` with `done = payload.done || 0`, `total = payload.total || 0`. `instance_id` is correctly `_esc()`-escaped for the selector, but `done`/`total` are concatenated into innerHTML unescaped. Not exploitable in practice: these values originate from server-side SQL `COUNT`/`SUM(...)` scanned into Go `int`s (`scanChildProgressRows`), marshalled as JSON numbers, and the channel is admin-only — so they are always numeric and never attacker-controlled. Still, the convention for this codebase (check #4, no-inline-style / defense-in-depth) prefers a DOM property: `Number(done)` coercion + set `meter.value`/`meter.max` as properties (or build via `createElement`) instead of an innerHTML string. Purely hardening; no correctness/security impact today. REVIEW VERDICT: 0 blocker, 0 major, 1 minor
Author
Owner

Security Review — PR #1227 (workflow.progress SSE + <meter> bars)

Scope: SSE isolation invariant from .1, progress payload sensitivity, SQL injection, <meter> XSS.

(1) Admin-only routing — PASS. The progress event is constructed in internal/wfengine/engine_notifier.go (pollChildProgress) with AdminBroadcast: true and no TargetUserID/Broadcast. internal/sse/hub.go:130 matches() is default-deny: AdminBroadcastreturn sub.isAdmin; no other flag set → falls through to return false. sub.isAdmin derives from session claims via extractClaims (internal/sse/handler.go:32), not request-supplied. A non-admin authenticated /events subscriber therefore does NOT receive workflow.progress events. No system-wide instance-ID leak to non-admins.

(2) Payload sensitivity — PASS. WorkflowProgressPayload (internal/sse/event.go) carries only instance_id, done, total — no secrets/PII/user data.

(3) SQL injection — PASS. makeChildProgressQuery (internal/wfengine/diag_accessor.go) builds the IN (%s) clause from ? placeholders (strings.Repeat("?,", n)); parent IDs pass as positional args. No value concatenated into SQL. Query is invoked only from the internal lifecycle monitor (pollChildProgress) — no new HTTP route/handler, no new unauth surface.

(4) <meter> render — see MINOR below.


[MINOR] static/js/controllers/workflow_running_push_controller.js:78 — progress <meter> built via innerHTML concat rather than DOM property
_onProgressEvent does cell.innerHTML = '<meter ... value="' + done + '" max="' + total + '">...' with done = payload.done || 0, total = payload.total || 0. instance_id is correctly _esc()-escaped for the selector, but done/total are concatenated into innerHTML unescaped. Not exploitable in practice: these values originate from server-side SQL COUNT/SUM(...) scanned into Go ints (scanChildProgressRows), marshalled as JSON numbers, and the channel is admin-only — so they are always numeric and never attacker-controlled. Still, the convention for this codebase (check #4, no-inline-style / defense-in-depth) prefers a DOM property: Number(done) coercion + set meter.value/meter.max as properties (or build via createElement) instead of an innerHTML string. Purely hardening; no correctness/security impact today.

REVIEW VERDICT: 0 blocker, 0 major, 1 minor

## Security Review — PR #1227 (workflow.progress SSE + `<meter>` bars) Scope: SSE isolation invariant from .1, progress payload sensitivity, SQL injection, `<meter>` XSS. **(1) Admin-only routing — PASS.** The progress event is constructed in `internal/wfengine/engine_notifier.go` (`pollChildProgress`) with `AdminBroadcast: true` and no `TargetUserID`/`Broadcast`. `internal/sse/hub.go:130 matches()` is default-deny: `AdminBroadcast` → `return sub.isAdmin`; no other flag set → falls through to `return false`. `sub.isAdmin` derives from session claims via `extractClaims` (`internal/sse/handler.go:32`), not request-supplied. A non-admin authenticated `/events` subscriber therefore does NOT receive `workflow.progress` events. No system-wide instance-ID leak to non-admins. **(2) Payload sensitivity — PASS.** `WorkflowProgressPayload` (`internal/sse/event.go`) carries only `instance_id`, `done`, `total` — no secrets/PII/user data. **(3) SQL injection — PASS.** `makeChildProgressQuery` (`internal/wfengine/diag_accessor.go`) builds the `IN (%s)` clause from `?` placeholders (`strings.Repeat("?,", n)`); parent IDs pass as positional `args`. No value concatenated into SQL. Query is invoked only from the internal lifecycle monitor (`pollChildProgress`) — no new HTTP route/handler, no new unauth surface. **(4) `<meter>` render — see MINOR below.** --- [MINOR] static/js/controllers/workflow_running_push_controller.js:78 — progress `<meter>` built via innerHTML concat rather than DOM property `_onProgressEvent` does `cell.innerHTML = '<meter ... value="' + done + '" max="' + total + '">...'` with `done = payload.done || 0`, `total = payload.total || 0`. `instance_id` is correctly `_esc()`-escaped for the selector, but `done`/`total` are concatenated into innerHTML unescaped. Not exploitable in practice: these values originate from server-side SQL `COUNT`/`SUM(...)` scanned into Go `int`s (`scanChildProgressRows`), marshalled as JSON numbers, and the channel is admin-only — so they are always numeric and never attacker-controlled. Still, the convention for this codebase (check #4, no-inline-style / defense-in-depth) prefers a DOM property: `Number(done)` coercion + set `meter.value`/`meter.max` as properties (or build via `createElement`) instead of an innerHTML string. Purely hardening; no correctness/security impact today. REVIEW VERDICT: 0 blocker, 0 major, 1 minor
Author
Owner

[MINOR] internal/wfengine/engine_notifier_test.go:271–275 — "includes the instance ID in the progress payload" test violates one-Expect-per-It rule
The test executes two Expect calls (checking type assertion ok, then InstanceID value) but should have one assertion per It block. Remove the first Expect(ok).To(BeTrue()) call and let the type assertion panic if it fails (acceptable in tests), or split into two separate It blocks with shared setup. Fix: consolidate to a single Expect by dropping the explicit ok check.

Verification Summary

Replay safety: Progress events are emitted from the monitor goroutine (pollChildProgress), not from any workflow body. No ExecuteActivity or workflow commands added to the sequence.

Query bounded & deterministic: childProgressSQL uses WHERE parent_instance_id IN (...) with a bounded parent-ID list (filtered from currentIDs). Single query per 2s poll tick. ORDER BY includes tiebreaker (id DESC) making it deterministic.

AdminBroadcast routing: Progress events set AdminBroadcast: true matching state events. Hub.Publish enforces default-deny via matches() — only admins receive these events.

JS handler idempotent + CSP-safe: _onProgressEvent() replaces cell content via cell.innerHTML, handles zero/missing totals gracefully, uses <meter> class (no inline style=), and escapes instance_id via existing _esc() function.

Cleanup: knownProgress map is properly deleted when instances terminate (pollLifecycle line 306).

REVIEW VERDICT: 0 blocker, 0 major, 1 minor

[MINOR] internal/wfengine/engine_notifier_test.go:271–275 — "includes the instance ID in the progress payload" test violates one-Expect-per-It rule The test executes two Expect calls (checking type assertion ok, then InstanceID value) but should have one assertion per It block. Remove the first `Expect(ok).To(BeTrue())` call and let the type assertion panic if it fails (acceptable in tests), or split into two separate It blocks with shared setup. Fix: consolidate to a single Expect by dropping the explicit ok check. ## Verification Summary ✓ **Replay safety:** Progress events are emitted from the monitor goroutine (pollChildProgress), not from any workflow body. No ExecuteActivity or workflow commands added to the sequence. ✓ **Query bounded & deterministic:** childProgressSQL uses `WHERE parent_instance_id IN (...)` with a bounded parent-ID list (filtered from currentIDs). Single query per 2s poll tick. ORDER BY includes tiebreaker (id DESC) making it deterministic. ✓ **AdminBroadcast routing:** Progress events set `AdminBroadcast: true` matching state events. Hub.Publish enforces default-deny via matches() — only admins receive these events. ✓ **JS handler idempotent + CSP-safe:** _onProgressEvent() replaces cell content via `cell.innerHTML`, handles zero/missing totals gracefully, uses `<meter>` class (no inline `style=`), and escapes instance_id via existing _esc() function. ✓ **Cleanup:** knownProgress map is properly deleted when instances terminate (pollLifecycle line 306). REVIEW VERDICT: 0 blocker, 0 major, 1 minor
fix(sse): meter accent-color + DOM-property meter update + one-Expect test (bookshelf-t3z2w.2)
All checks were successful
/ E2E API (pull_request) Successful in 2m53s
/ Test Race (pull_request) Successful in 3m5s
/ Coverage (pull_request) Successful in 3m36s
/ Lint (pull_request) Successful in 3m51s
/ JS Unit Tests (pull_request) Successful in 58s
/ Integration (pull_request) Successful in 4m30s
/ E2E Browser (pull_request) Successful in 3m26s
b15d296432
- CSS: add accent-color: var(--accent) to .wf-progress-meter so the
  meter fill uses the app indigo instead of browser-default green
- JS: refactor _onProgressEvent to create-or-reuse <meter> via DOM
  properties (meter.value / meter.max) instead of innerHTML string
  concat; fraction label set via sibling <span> textContent; idempotent
  on repeated events (no element recreation)
- Test: update vitest assertions to read meter.value/meter.max DOM
  properties; add label-textContent and idempotency tests
- Test (Go): consolidate two-Expect It("includes the instance ID...")
  to single Expect by using direct type assertion, per one-Expect-per-It

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

Re-Review: Fix Delta (47241bb9..b15d2964)

Meter DOM-property refactor (static/js/controllers/workflow_running_push_controller.js:68–86): The <meter> element is now created via document.createElement("meter") and updated via .value/.max DOM properties (Number-coerced) with the label set via textContent on a sibling span. No innerHTML sink remains. The meter is idempotently reused on repeat progress events (query-find-or-create pattern). Zero/missing-total handled via cell.textContent = "—". ✓ Security minor resolved.

CSS accent-color (static/css/main.css:4893): Added accent-color: var(--accent); to .wf-progress-meter. Uses the CSS token defined at :root (line 9), no hardcoded color, no inline style attribute. ✓ UI minor resolved.

Go test consolidation (internal/wfengine/engine_notifier_test.go:273): Type assertion now a guard; single Expect per It block asserting p.InstanceID. Idiomatic Go—panic on failed assertion is a valid precondition guard, not an Expect. ✓ Test hygiene minor resolved.

Vitest coverage: Assertions refactored from getAttribute() to DOM properties (meter.value, meter.max). Added test for label text in span.textContent and test for meter-element idempotency across repeated progress events. All coverage maintained; JS unit tests pass.

CI status: All green (Test Race, Coverage, Lint, JS Unit, Integration, E2E API, E2E Browser).

REVIEW VERDICT: 0 blocker, 0 major, 0 minor

## Re-Review: Fix Delta (47241bb9..b15d2964) **Meter DOM-property refactor** (static/js/controllers/workflow_running_push_controller.js:68–86): The `<meter>` element is now created via `document.createElement("meter")` and updated via `.value`/`.max` DOM properties (Number-coerced) with the label set via `textContent` on a sibling span. No `innerHTML` sink remains. The meter is idempotently reused on repeat progress events (query-find-or-create pattern). Zero/missing-total handled via `cell.textContent = "—"`. ✓ Security minor resolved. **CSS accent-color** (static/css/main.css:4893): Added `accent-color: var(--accent);` to `.wf-progress-meter`. Uses the CSS token defined at `:root` (line 9), no hardcoded color, no inline style attribute. ✓ UI minor resolved. **Go test consolidation** (internal/wfengine/engine_notifier_test.go:273): Type assertion now a guard; single `Expect` per `It` block asserting `p.InstanceID`. Idiomatic Go—panic on failed assertion is a valid precondition guard, not an Expect. ✓ Test hygiene minor resolved. **Vitest coverage**: Assertions refactored from `getAttribute()` to DOM properties (`meter.value`, `meter.max`). Added test for label text in `span.textContent` and test for meter-element idempotency across repeated progress events. All coverage maintained; JS unit tests pass. **CI status**: All green (Test Race, Coverage, Lint, JS Unit, Integration, E2E API, E2E Browser). REVIEW VERDICT: 0 blocker, 0 major, 0 minor
zombor merged commit 3ac78b84a0 into main 2026-07-24 20:52:19 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
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
zombor/pergamum!1227
No description provided.