# Implementation Plan: Multi-Metric Notes for Compliance Detail Panel ## Overview Extend the compliance notes system so a single note can be associated with multiple metrics in one action. Changes span three layers: a new migration script adding `group_id` to `compliance_notes`, updates to the `POST /notes` endpoint in `backend/routes/compliance.js` to accept `metric_ids` (array) and insert rows transactionally, and frontend changes in `ComplianceDetailPanel.js` to replace the single-select dropdown with a multi-select chip selector and group notes by `group_id` in the display. ## Tasks - [x] 1. Create database migration for `group_id` column - [x] 1.1 Create `backend/migrations/add_compliance_notes_group_id.js` - Add `group_id TEXT` column to `compliance_notes` table via `ALTER TABLE` - Create index `idx_compliance_notes_group` on `compliance_notes(group_id)` - Backfill existing rows: `UPDATE compliance_notes SET group_id = 'legacy-' || id WHERE group_id IS NULL` - Follow the existing migration pattern (sqlite3, serialize, console logging) - _Requirements: 6.1, 6.2, 6.3_ - [x] 2. Update `POST /notes` endpoint to support multi-metric submissions - [x] 2.1 Modify the `POST /notes` handler in `backend/routes/compliance.js` to accept `metric_ids` array - Accept `metric_ids` (array of strings) as an alternative to `metric_id` (single string) - When both are provided, `metric_ids` takes precedence - When neither is provided, return 400 with `"metric_id or metric_ids is required"` - When `metric_ids` is provided but is not an array, return 400 with `"metric_ids must be an array"` - Normalize single `metric_id` into a one-element array internally so the rest of the logic is uniform - _Requirements: 5.1, 5.4, 5.5_ - [x] 2.2 Add validation for `metric_ids` array entries - Validate that `metric_ids` has at least one entry; return 400 with `"At least one metric ID is required"` if empty - Validate each entry is a non-empty string of 50 characters or fewer; return 400 with `"Invalid metric_id at index N"` on failure - Reject the entire request if any entry fails validation (atomic rejection, no partial inserts) - _Requirements: 5.2, 5.6_ - [x] 2.3 Implement transactional multi-row insert with `group_id` - Generate a `group_id` using `crypto.randomUUID()` for each submission (single or multi) - Wrap all inserts in `BEGIN TRANSACTION` / `COMMIT` with `ROLLBACK` on error - Insert one `compliance_notes` row per metric ID, all sharing the same `note`, `group_id`, `created_by`, and `created_at` - _Requirements: 3.1, 3.2, 5.3, 6.1, 6.2_ - [x] 2.4 Update the response to return all created note rows - After commit, query all created rows (joined with `users` for `username`) and return as `{ notes: [...] }` - Each row includes `id`, `hostname`, `metric_id`, `note`, `group_id`, `created_at`, `created_by` - Return HTTP 201 status - _Requirements: 5.7_ - [x] 3. Update `GET /items/:hostname` to include `group_id` in notes response - Add `cn.group_id` to the SELECT in the notes query within the `GET /items/:hostname` handler - The existing query already fetches notes for the hostname; just add the column - No other changes to this endpoint - _Requirements: 6.3, 6.4_ - [x] 4. Checkpoint — Verify backend changes - Ensure all backend changes are syntactically correct, ask the user if questions arise. - [x] 5. Replace single-select dropdown with multi-select MetricChipSelector in `ComplianceDetailPanel.js` - [x] 5.1 Replace `noteMetric` (string) state with `selectedMetrics` (array) state - Initialize `selectedMetrics` with the first active metric's ID when detail loads (matching current default behavior) - When there is exactly one active metric, pre-select it as a non-removable selection - _Requirements: 1.3, 1.4_ - [x] 5.2 Build the multi-select chip-based metric selector UI - Replace the existing `