- Bulk chronic-ack endpoints across all three chronic dimensions โ closes the named rev-86 next-sprint candidateRev 86's running state explicitly named 'bulk chronic-ack endpoints (per-tag / per-source / per-assignee โ the three axes where chronic makes sense)' as the natural rev-87 candidate. Rev 87 closes that. Six new endpoints โ `POST /api/cost/by-tag/chronic-ack/bulk`, `POST /api/sources/chronic-ack/bulk`, `POST /api/cost/by-assignee/chronic-ack/bulk` plus their three v1 mirrors โ accept a `{tags: string[โค50]}` / `{sourceIds: string[โค50]}` / `{assigneeUserIds: string[โค50]}` payload and stamp the rev-71/72 chronic-ack columns (`workspace.tagChronicAcks` / `source.chronicAckedAt` / `workspace_member.chronicAckedAt`) for every supplied item that belongs to the workspace. Each helper fires one closure-receipt outbound event per acked item so downstream integrations watching the rev-73 `tag.chronic_warning_acked` / `source.chronic_warning_acked` / `assignee.chronic_warning_acked` events see the same per-item shape they get from a single ack call (vs an inconsistent bulk-shape contract). Three new chronic bulk-ack bars surface in the cost-by-tag / cost-by-source / cost-by-assignee panels when 2+ items have crossed the chronic threshold AND the operator has editor+ access โ distinct amber palette from the rev-60/63/68 daily bulk-ack bars (red palette) so operators tell at a glance which horizon they're acting on. **Strategic significance**: closes the chronic-axis bulk-action symmetry to match the daily-axis bulk-action symmetry that already shipped (rev 57/60/63/68). The cost-axis MCP cluster now has bulk-ack symmetry on every axis where chronic makes sense โ protocol-bound surface has nothing left to design on the chronic-ack side. Until rev 87 a manager triaging 5 chronic per-assignee warnings after a quarter-end push had to tap each chip individually; rev 87 collapses that to one click on the dashboard and one bearer-auth call from MCP.
- GET /api/v1/workspace/digest-preview dry-run โ closes the named rev-86 next-sprint candidateRev 86's running state explicitly named 'rev-86 v1 GET digest preview dry-run (returns the rendered HTML inline without sending)' as the natural rev-87 candidate. Rev 87 ships it. New GET handler on the existing rev-86 endpoint accepts optional `userId=` (defaults to workspace owner like the POST companion) + optional `format=html|json` (defaults to JSON). Reuses the existing `previewDigestForUser` helper through a new `dryRun: true` option that (a) skips the Resend precondition check so workspaces without `RESEND_API_KEY`/`EMAIL_FROM` can still verify the rendered shape, (b) skips the actual email send, (c) skips the activity-log write. Returns either the raw HTML body with `text/html` content type (when `format=html`) or `{ ok, dryRun: true, recipient, subject, html }` JSON (default). Honours every recipient's rev-78/79/80/81 dashboardPrefs exactly as the POST endpoint would. **Strategic significance**: closes the email-round-trip cost on digest-config testing. Until rev 87 every iteration on dashboardPrefs configuration burned a real Resend send + a real mailbox round-trip + an activity-log entry. Rev 87 lets MCP hosts pipe the digest body through their own preview tool (visual diff, screenshot, markdown conversion, cypress visual regression) without any per-iteration mailbox cost. Pairs with the rev-86 POST endpoint (which still sends a real email so admins can verify their inbox-side rendering) as complementary verification surfaces โ render-only for fast iteration, real-send for end-to-end verification.
- OpenAPI 3.1 spec coverage for the four new endpoints in lockstepFollowing the cadence pattern from rev 78 onward โ every new v1 endpoint gets typed in the OpenAPI 3.1 spec in the same cycle it ships. Rev 87 types the three new chronic bulk-ack endpoints + the new GET dry-run on the digest-preview endpoint. **Strategic significance**: closes the typed-schema chronic bulk-action symmetry to match the daily bulk-action typing that rev 86 just closed. The MCP server's chronic-axis bulk-action tooling now has typed contracts on every dimension (per-tag / per-source / per-assignee) โ the cadence pattern of 'ship the dashboard primitive + the v1 mirror + the index entry + the OpenAPI typed schema in lockstep' that started rev 37 continues unbroken through rev 87.
- Visual polish โ amber chronic palette on the new bulk-ack barsCumulative micro-polish (every rev 22+ has carried at least one). The three new chronic bulk-ack bars wear an amber `rgba(184, 109, 36, *)` palette distinct from the rev-60/63/68 daily bulk-ack bars (red palette) so operators tell at a glance which horizon they're acting on. The amber palette matches the rev-71/72 single-item chronic-ack chips so the chronic-axis vocabulary across the dashboard reads with one consistent treatment โ chip and bulk bar share the same colour story, just at different scopes. Pairs with the rev-71/72 single-item chronic-ack pulse animation so the operator's eye sees a coherent state machine: chronic chip pulses when fired โ bulk bar surfaces in matching palette โ ack flashes through both surfaces.