# Specification: 040-system-alerts

## Status

| Field | Value |
|-------|-------|
| **Created** | 2026-02-27 |
| **Current Phase** | Implementation — T2 Complete, T3 Next |
| **Last Updated** | 2026-02-27 |

## Documents

| Document | Status | Notes |
|----------|--------|-------|
| product-requirements.md | completed | Codex-reviewed, 3 blockers resolved, 2 important fixes, 3 enhancements applied |
| solution-design.md | completed | All sections filled, 6 ADRs confirmed, Codex-reviewed 2026-02-27 |
| implementation-plan.md | completed | 8 phases, 130+ tasks, full PRD/SDD traceability, Codex-reviewed 2026-02-27 |

**Status values**: `pending` | `in_progress` | `completed` | `skipped`

## Codex PRD Review (2026-02-27)

### Blockers Resolved
1. **Conflicting queue rules** — Clarified: max 3 is global combined (banners + toasts). No replacement of visible items — new Critical goes to front of queue.
2. **Acknowledgment on edit vs never-reappear** — Clarified: Edit deletes all ack records, treating edited alert as new version. "Never reappear" applies to current version only.
3. **Targeting semantics** — Clarified: Assignment-based (via `userStoreAssignments`), not current-view-based. Users see alerts for all assigned stores.

### Important Fixes Applied
4. **Bell dropdown click behavior** — Clarified: Click expands alert inline, separate Dismiss button acknowledges.
5. **Subjective acceptance criteria** — Replaced with measurable/testable criteria (specific rich text tags, explicit regression test list, layout push-down behavior).

### Enhancements Applied
6. **Multi-tab deduplication** — Added edge case (Scenario 9).
7. **Store assignment change** — Added edge case (Scenario 10).
8. **Bell + visible alert sync** — Added edge case (Scenario 11).
9. **Success metrics tightened** — Defined "delivery," "acknowledged" in terms of tracking events. Realistic adoption target (80% active admins).
10. **Dependencies section added** — Ably SDK, user-store assignments, permissions, Syncfusion, chat module.
11. **Acknowledgment semantics clarified** — No "viewed" state, only "dismissed." Added to Won't Have.

### Rejected Suggestions
- Reframing Feature 8 (developer story) as non-user-facing: Kept as-is because the chat migration has user-facing acceptance criteria.
- Adding quantifiable evidence to problem statement: The problem is self-evident from the current lack of any in-app broadcast mechanism.

## Codex SDD Review (2026-02-27)

### Blockers Resolved
1. **Toast position mismatch** — SDD specified top-right, PRD requires bottom-right (matching existing chat toasts). Fixed to `bottom-right` with `bottom: 80px; right: 20px`.
2. **Queue ordering incomplete** — Only sorted by severity, missing createdAt tie-breaker. Added `createdAt` comparison within same severity level.
3. **Multi-store dedup bug** — `_isDuplicate` used Ably `message.id` (unique per channel). Users on multiple stores would see duplicates. Changed dedup key to `alertId`-based.
4. **Admin dashboard under-specified** — Missing filters, sorts, summary stats required by PRD. Expanded List Alerts endpoint with severity/store/date filters, sortBy/sortDir params, and summary stats.

### Important Fixes Applied
5. **Performance target mismatch** — SDD said <200ms, PRD says <100ms. Aligned to <100ms.
6. **Bell behavior lacks detail** — Added full "Alert Bell Dropdown Behavior" section with pending/dismissed sections, inline expansion, CTA-click acknowledgment, fresh fetch on open.
7. **Reconnect/backfill missing** — No Ably reconnect strategy. Added reconnection behavior: call `/api/system-alerts/pending` on reconnect, merge with queue deduplicated by alertId.
8. **Delete vs Deactivate ambiguity** — PRD defines deactivate/expire but not delete. Removed DELETE endpoint entirely, deactivate-only lifecycle preserves audit history.

### Enhancements Applied
9. **CTA URL validation underspecified** — Added explicit rules: `https://` or relative path only, no `javascript:`, `data:`, `ftp:` protocols.
10. **Could Have features absent** — Added "Out of Scope for v1" section listing Alert Sound Notification (Feature 10) and Alert Pinning (Feature 11).

### Rejected Suggestions
- None — all 10 findings were accepted and applied.

## Codex PLAN Review (2026-02-27)

### Blockers Resolved
1. **Ably token auth missing `alerts:*` capability** — Clients couldn't subscribe to alert channels. Added T3.3 (Ably Token Auth Update) to modify `StaffChatApiController` to include `alerts:global` and `alerts:{typeNum}` capabilities in token generation.
2. **T2 parallelization hidden dependency** — T2.1 (Entity Models) was marked `[parallel: true]` but T2.2/T2.3 depend on model classes. Removed parallel tag, added prerequisite note.

### Important Fixes Applied
3. **Missing PendingAlert DTO** — SDD specifies PendingAlert DTO (lines 583-593) but plan omitted it. Added T2.1.5 with full field list and factory method.
4. **No risk section** — Added Risks & Mitigations table with 8 identified risks (Ably auth, Syncfusion init, chat regression, rate limits, XSS, multi-tab races, PDO params).
5. **RTE toolbar mismatch** — Plan only listed bold/italic/links/lists. SDD also requires underline + undo/redo. Fixed T5.3.1.
6. **Accessibility not planned** — Added T4.6 (Banner/Toast accessibility) and T6.5 (Bell accessibility) with ARIA attributes, keyboard navigation, focus management.
7. **No telemetry/tracking tasks** — PRD defines 10 tracking events but plan had no instrumentation tasks. Added T8.6 (Tracking Event Instrumentation) covering all 10 events.
8. **Store list data source unspecified** — MultiSelect for store targeting had no data source. Specified: `kiosk_buykiosk.stores` with `UPPER(typeNum)` as display name (stores table has no name column).

### Enhancements Applied
9. **Vague T6.2.5** — Admin layout bell icon task was too vague ("if different from workspace"). Expanded with specific investigation steps and dedup guidance.
10. **Phase DoD checklists** — Added global Phase Definition of Done checklist (tests pass, PHPStan, CSS build, console errors, SDD compliance, smoke test).

### Rejected Suggestions
- **Automated JS tests for frontend queue logic**: Deferred — no JS test framework in codebase currently. Manual testing covers queue behavior in T4/T6/T8 validation steps.
- **Template file tasks (notification-banner.html, alert-bell.html)**: Not needed — renderers create DOM directly via JS (matching existing chat-notifications.js pattern). No separate Twig templates for notifications.

## Decisions Log

| Date | Decision | Rationale |
|------|----------|-----------|
| 2026-02-27 | Both global + per-store targeting | Flexibility to broadcast everywhere or target specific stores |
| 2026-02-27 | 4 severity types: Info/Warning/Critical/Chat | Chat type enables unified toast framework for existing chat notifications |
| 2026-02-27 | DB is source of truth, show on page load | Ensures no alerts are missed even if Ably delivery fails |
| 2026-02-27 | Banner OR toast per alert (not both) | Simpler UX, clear intent per alert |
| 2026-02-27 | Optional expiry date | Admins can set auto-expiry or let alerts live until dismissed |
| 2026-02-27 | Full acknowledgment tracking dashboard | Compliance-grade visibility into who saw what and when |
| 2026-02-27 | Edit resets all acknowledgments | Ensures everyone sees updated content |
| 2026-02-27 | BuyerKiosk > System Alerts (new page) | Clean separation, dedicated admin experience |
| 2026-02-27 | Max 3 visible globally (combined), queue the rest | Prevents alert overload while ensuring all get seen |
| 2026-02-27 | Alert bell with history in navbar | Central notification hub for all users |
| 2026-02-27 | Above page header (global position) | Consistent banner placement across admin and workbook |
| 2026-02-27 | Toasts stay until manually closed | Ensures staff engagement, no auto-dismiss |
| 2026-02-27 | No scheduling, publish immediately | Keep scope tight for v1 |
| 2026-02-27 | Optional CTA button with link | Enables actionable alerts (link to policies, docs, etc.) |
| 2026-02-27 | Dedicated Ably alerts channels | Clean separation from existing store channels |
| 2026-02-27 | Basic rich text via Syncfusion RTE | Formatted messages without full HTML complexity |
| 2026-02-27 | Migrate chat toasts to unified system | One notification rendering engine for all toast types |
| 2026-02-27 | Chat = new severity type, preserves all behaviors | Unified rendering but chat keeps click-to-thread, consolidation, sound |
| 2026-02-27 | No DB tracking for chat toasts | Chat notifications are ephemeral, not compliance-tracked |
| 2026-02-27 | Assignment-based targeting (not view-based) | Users see alerts for all assigned stores, not just current page |
| 2026-02-27 | No replacement of visible notifications | New Critical goes to front of queue, doesn't bump visible items |
| 2026-02-27 | Edit = new version (exception to never-reappear) | Acknowledgment reset on edit is explicit exception, not contradiction |
| 2026-02-27 | Toast position: bottom-right | Matches existing chat toast position per PRD |
| 2026-02-27 | Dedup on alertId, not Ably message.id | Prevents duplicates for multi-store users |
| 2026-02-27 | Deactivate-only lifecycle (no hard delete) | Preserves audit trail, aligns with PRD |
| 2026-02-27 | CTA URLs: https or relative only | Security: block javascript/data/ftp protocols |
| 2026-02-27 | Ably reconnect backfills from DB | Ensures no missed alerts after connectivity loss |
| 2026-02-27 | Namespace `BuyerKiosk\SystemAlerts` (not `Feature\SystemAlerts`) | Matches codebase convention — flat namespaces under `src/BuyerKiosk/` |
| 2026-02-27 | T2.4.4: Ack logic stays in SystemAlertService | No separate AcknowledgmentService needed — ack operations are simple delegations to AcknowledgmentRepository |

## Implementation Progress

### T1 Phase 1: Database Foundation (Completed 2026-02-27)
- 2 migration files: `040_001_system_alerts_tables.json`, `040_002_system_alerts_column_fixes.json`
- Tables: `systemAlerts`, `systemAlertStoreTargets`, `systemAlertAcknowledgments`

### T2 Phase 2: Backend Domain Layer (Completed 2026-02-27)
- **90 tests, 425 assertions** — all passing
- **PHPStan: 0 errors**
- Files created:
  - `src/BuyerKiosk/SystemAlerts/Models/SystemAlert.php` — Core entity with targeting, expiry, serialization
  - `src/BuyerKiosk/SystemAlerts/Models/AlertAcknowledgment.php` — Simple DTO
  - `src/BuyerKiosk/SystemAlerts/Models/PendingAlert.php` — Staff-facing DTO with factory
  - `src/BuyerKiosk/SystemAlerts/Repositories/SystemAlertRepository.php` — CRUD + targeting resolution + admin listing
  - `src/BuyerKiosk/SystemAlerts/Repositories/AcknowledgmentRepository.php` — Ack tracking + dashboard stats
  - `src/BuyerKiosk/SystemAlerts/Events/AlertAblyPublisher.php` — Realtime push with channel targeting + throttle
  - `src/BuyerKiosk/SystemAlerts/Services/SystemAlertService.php` — Business logic orchestrator with validation + sanitization
- Test coverage: models, repositories (PDO mocks), publisher (testable subclass), service (mock dependencies)
- **Codex review completed**: 6 fixes applied (2 critical, 3 important, 1 nice-to-have)
  - Critical: SQL injection via sortBy/sortDir — added whitelist
  - Critical: Target-wipe on update — preserved existing targetType
  - Important: CTA URL now HTTPS-only per SDD
  - Important: getAlertHistory now includes dismissedAt
  - Important: Cross-DB query fixed — uses proper usersDb connection
  - Nice-to-have: HTML sanitizer blocks vbscript:/file: in body links
- Post-review: **97 tests, 449 assertions, PHPStan 0 errors**

## Context

System alert module for BuyerKiosk admins to push alert messages to the top of admin pages and/or workbook. Features include:
- Admin-only alert creation from BuyerKiosk admin panel (uri_bkadmin permission)
- Banner and toast notification display types with severity levels
- Per-user acknowledgment tracking with full analytics dashboard
- Ably realtime integration for live push without page reload
- Targeting: all stores or specific stores (assignment-based)
- Unified toast framework absorbing existing chat notifications
- Notification bell with history dropdown in navbar

---
*This file is managed by the specification-management skill.*
