diff --git a/workplans/IHUB-WP-0002-ihf-phase2-structured-feedback-and-triage.md b/workplans/IHUB-WP-0002-ihf-phase2-structured-feedback-and-triage.md index c294913..93a4a18 100644 --- a/workplans/IHUB-WP-0002-ihf-phase2-structured-feedback-and-triage.md +++ b/workplans/IHUB-WP-0002-ihf-phase2-structured-feedback-and-triage.md @@ -2,11 +2,11 @@ id: IHUB-WP-0002 type: workplan title: "IHF Phase 2 — Structured Feedback and Triage" -domain: inter_hub +domain: custodian repo: inter-hub status: active owner: custodian -topic_slug: inter_hub +topic_slug: custodian created: "2026-03-28" updated: "2026-03-28" state_hub_workstream_id: "25d4c92c-b213-4c33-9404-822192899de7" @@ -16,41 +16,39 @@ state_hub_workstream_id: "25d4c92c-b213-4c33-9404-822192899de7" ## Goal -Transform raw annotations into structured, operable feedback. Phase 1 established the -capture layer (widgets, events, annotations). Phase 2 makes that feedback governable: -grouping related observations, escalating them into Requirement Candidates, tracking -triage status and reviewer ownership, and surfacing the triage queue to operators in -a live dashboard. +Transform raw annotations into structured, operable feedback. Introduces +AnnotationThread (grouping), RequirementCandidate (escalation), TriageState +(lifecycle), and ReviewerAssignment (ownership). Operator triage dashboard +with AutoRefresh. ## Background -Phase 1 (IHUB-WP-0001) delivered the Minimal Interaction Core — widget registry, -interaction event capture, annotation system, and hub operator dashboard. All Phase 1 -exit criteria are met. +Phase 1 delivered the minimal interaction core: widget registry, interaction +event capture, annotation system, and hub-level operator dashboard. Phase 2 +closes the gap between raw observation and structured, actionable feedback — +enabling operators to group annotations, escalate them into requirement +candidates, and drive them through a triage lifecycle. -Phase 2 is the second of eight phases in the IHF specification -(`specs/InteractionHubFrameworkSpecification_v0.1.md`). It extends the Phase 1 -annotation model with severity markers, threading/grouping, escalation to Requirement -Candidates, a triage lifecycle, and reviewer assignment. - -**Technology stack:** IHP v1.5 (Haskell, Nix), PostgreSQL, AutoRefresh (triage -dashboard), HTMX (escalation and triage actions), IHP forms (CRUD). +**Entry criteria:** Phase 1 complete (T01–T12 all done, WP-0001 status=done). Reference: `docs/ihp-overview.md`, `docs/ihp-data-and-queries.md`, -`docs/ihp-controllers-views-forms.md`, `docs/ihp-realtime.md`. +`docs/ihp-controllers-views-forms.md`, `docs/ihp-realtime.md`, +`docs/ihp-ihf-mapping.md`. -## Phase 2 Exit Criteria (from IHF spec §14 Phase 2) +## Phase 2 Exit Criteria -- Feedback volume can be triaged rather than merely stored -- Multiple related comments can converge into a structured candidate -- Reviewers can track status and ownership +- Annotations can be grouped into AnnotationThreads +- Annotations can be escalated into RequirementCandidates +- RequirementCandidates have a full triage lifecycle (open → in_review → accepted/rejected/deferred) +- Reviewers can be assigned to candidates +- Operator triage dashboard live-updates via AutoRefresh +- All integration tests passing +- SCOPE.md updated to reflect Phase 2 completion ## Data Artifacts Introduced (Phase 2) `AnnotationThread`, `RequirementCandidate`, `TriageState`, `ReviewerAssignment` -Also extends: `Annotation` (adds `severity`, `thread_id`) - --- ## Tasks @@ -64,64 +62,17 @@ priority: high state_hub_task_id: "eb267a9e-7e80-4913-b7a3-7f5adb04a0f2" ``` -Add Phase 2 tables to `Application/Schema.sql` and write migration: +Add Phase 2 tables to `Application/Schema.sql` and write migration. Add +severity/intensity field to annotations table. Define: -```sql --- Extend annotations with severity and thread grouping -ALTER TABLE annotations ADD COLUMN severity TEXT NOT NULL DEFAULT 'medium'; -ALTER TABLE annotations ADD COLUMN thread_id UUID REFERENCES annotation_threads(id) ON DELETE SET NULL; +- `AnnotationThread` — grouping of related annotations +- `RequirementCandidate` — escalated feedback record +- `TriageState` — per-candidate lifecycle row +- `ReviewerAssignment` — ownership record -CREATE TABLE annotation_threads ( - id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, - widget_id UUID NOT NULL REFERENCES widgets(id) ON DELETE CASCADE, - title TEXT NOT NULL, - description TEXT, - created_by UUID REFERENCES users(id), - created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL -); +Generate IHP types via the IDE code generator. -CREATE TABLE requirement_candidates ( - id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, - title TEXT NOT NULL, - description TEXT NOT NULL, - source_widget_id UUID NOT NULL REFERENCES widgets(id) ON DELETE RESTRICT, - source_thread_id UUID REFERENCES annotation_threads(id) ON DELETE SET NULL, - source_annotation_id UUID REFERENCES annotations(id) ON DELETE SET NULL, - category TEXT NOT NULL DEFAULT 'friction', - status TEXT NOT NULL DEFAULT 'open', - created_by UUID REFERENCES users(id), - created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL -); - -CREATE INDEX requirement_candidates_widget_id_idx ON requirement_candidates (source_widget_id); -CREATE INDEX requirement_candidates_status_idx ON requirement_candidates (status); - -CREATE TABLE triage_states ( - id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, - candidate_id UUID NOT NULL REFERENCES requirement_candidates(id) ON DELETE CASCADE, - status TEXT NOT NULL, - notes TEXT, - changed_by UUID REFERENCES users(id), - changed_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL -); - -CREATE INDEX triage_states_candidate_id_idx ON triage_states (candidate_id); - -CREATE TABLE reviewer_assignments ( - id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, - candidate_id UUID NOT NULL REFERENCES requirement_candidates(id) ON DELETE CASCADE, - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - assigned_by UUID REFERENCES users(id), - assigned_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, - UNIQUE (candidate_id) -); -``` - -- Valid `severity` values: `low`, `medium`, `high`, `critical` -- Valid `requirement_candidates.status` values: `open`, `in_review`, `accepted`, `rejected`, `deferred` -- Verify Haskell types are generated correctly - -**Exit criteria:** `migrate` runs cleanly; all Phase 2 types available in GHCi. +**Exit criteria:** `migrate` succeeds; IHP generated types compile cleanly. --- @@ -134,15 +85,13 @@ priority: high state_hub_task_id: "fdcbf823-484e-4f0f-a0ca-28f9222520af" ``` -1. Update `CreateAnnotationAction` to bind and validate `severity` (low/medium/high/critical, default medium) -2. Update annotation form: add severity select field -3. Update annotation list and show views to display severity with Tailwind color cues: - - `low` → muted/secondary - - `medium` → info/neutral - - `high` → warning - - `critical` → error/destructive (per `specs/TailwindForInteractionHubs_v0.2.md` color roles) +Add severity field (low/medium/high/critical) to annotations table via +migration. Update `AnnotationsController` `CreateAnnotationAction` to accept +and validate severity. Update annotation form and list/show views to display +severity with appropriate visual cues (Tailwind color roles per spec). -**Exit criteria:** New annotations carry severity; existing annotations default to `medium`; severity is visible in all annotation views. +**Exit criteria:** Annotations can be created with a severity; severity renders +with correct color coding in list and show views. --- @@ -155,14 +104,13 @@ priority: high state_hub_task_id: "35b989a0-5e2a-4300-990b-f43d67de0727" ``` -1. Scaffold `AnnotationThreadsController` scoped to a widget: `/widgets/:widgetId/threads/` -2. Actions: `IndexAnnotationThreadsAction`, `ShowAnnotationThreadAction`, `NewAnnotationThreadAction`, `CreateAnnotationThreadAction` -3. `CreateAnnotationThreadAction`: creates thread with `title`, `description`, `widgetId` -4. Add `AssignAnnotationToThreadAction` (HTMX POST): sets `annotation.thread_id` -5. Thread show view: list of member annotations with severity distribution bar and dominant category badge -6. Thread list view: thread title, annotation count, severity breakdown, created_at +Scaffold `AnnotationThreadsController`. Allow operator to create a named thread +and manually assign annotations to it (many-to-one via `annotation.thread_id` +FK). Thread list view shows member annotation count, severity distribution, and +dominant category. Enables duplicate/similar-observation grouping. -**Exit criteria:** Operator can create a thread, assign annotations to it, and view the grouped observations. +**Exit criteria:** Threads can be created; annotations can be assigned to a +thread; thread list shows per-thread aggregates. --- @@ -175,13 +123,14 @@ priority: high state_hub_task_id: "4eb2a51c-1b3f-4b36-b945-6bfb14c2e680" ``` -1. Scaffold `RequirementCandidatesController` -2. Actions: index, show, new, create, edit, update (no delete) -3. Fields: `title`, `description`, `sourceWidgetId` (select), `sourceThreadId` (optional select), `category` (select), initial status `open` -4. Index view: table with status badge, widget, category, reviewer name, created_at; filterable by status -5. Show view: full detail + linked source (annotation or thread) + triage history log + current reviewer +Scaffold `RequirementCandidatesController`. CRUD: index, show, new/create, +edit/update (no delete). Fields: title, description, source_widget_id, +source_thread_id (optional), category, status (open). Index: list with status, +widget, category, reviewer. Show: full detail + linked annotations + triage +history. -**Exit criteria:** Requirement candidates can be created manually, listed, filtered by status, and viewed with full context. +**Exit criteria:** Candidates can be created manually; index and show pages +render correctly. --- @@ -194,16 +143,13 @@ priority: high state_hub_task_id: "5c3a154b-38e0-4e40-9e97-57aae1dbc95d" ``` -1. Add "Escalate to Candidate" button on annotation show page (HTMX POST to `EscalateAnnotationAction { annotationId }`) -2. Action pre-populates and creates a `RequirementCandidate` with: - - `title` derived from annotation body (first 80 chars) - - `category` copied from annotation category - - `sourceAnnotationId` set - - `sourceWidgetId` from annotation's widget -3. Respond with HTMX swap: replace escalate button with "Escalated →" link to the candidate -4. Add visual indicator on escalated annotations in annotation lists +Add `EscalateAnnotationAction` (HTMX POST) on the annotation show/list page. +Pre-populates a new RequirementCandidate form with the annotation body and +category. On submit, creates the candidate and records `source_annotation_id`. +Add a visual indicator on escalated annotations. -**Exit criteria:** Single-click escalation creates a linked candidate; escalated annotations are visually distinct; no duplicate escalation possible (button replaced after action). +**Exit criteria:** Operator can escalate an annotation to a candidate in one +action; escalated annotations show a visual marker. --- @@ -216,17 +162,13 @@ priority: high state_hub_task_id: "cd8c3ef1-e0f7-435f-ae20-e0760df5da83" ``` -1. `UpdateTriageStatusAction { candidateId, newStatus, notes }` (HTMX POST) -2. Validate allowed transitions: - - `open` → `in_review` - - `in_review` → `accepted`, `rejected`, `deferred` - - `deferred` → `in_review` - - All others rejected with 422 -3. On valid transition: insert `TriageState` row, update `requirement_candidates.status` -4. Show page: render full triage history as an audit trail (status, changed_by, changed_at, notes) -5. Status badge in index updates live via AutoRefresh +Implement TriageState table (candidate_id, status, changed_by, changed_at, +notes). Status lifecycle: open → in_review → accepted | rejected | deferred. +Add `UpdateTriageStatusAction` (HTMX). Show full triage history on +RequirementCandidate show page. Validate allowed transitions. -**Exit criteria:** Triage lifecycle enforced; invalid transitions rejected; full audit trail visible on show page. +**Exit criteria:** Status transitions work; invalid transitions are rejected; +full history visible on candidate show page. --- @@ -239,13 +181,13 @@ priority: medium state_hub_task_id: "3dc9bfdb-06d0-48a5-8973-2e39c6e0f78a" ``` -1. `AssignReviewerAction { candidateId, userId }` (HTMX POST from candidate show page) -2. Upsert `ReviewerAssignment` (one reviewer per candidate; replacing previous assignment) -3. Show current reviewer name on candidate index and show pages -4. Add "My Queue" view: `MyQueueAction` — lists all open/in_review candidates assigned to `currentUser` -5. Index view: filter by "Unassigned" and "Assigned to me" +Implement ReviewerAssignment (candidate_id, user_id, assigned_by, assigned_at). +Add `AssignReviewerAction`. Show current reviewer on candidate index and show +pages. Filter index by assignee. Reviewer can see their open candidates via a +"My Queue" view under their session. -**Exit criteria:** Candidates can be assigned to reviewers; reviewer sees their queue; unassigned candidates are identifiable. +**Exit criteria:** Reviewer can be assigned; "My Queue" shows open candidates +for the logged-in reviewer. --- @@ -258,16 +200,13 @@ priority: high state_hub_task_id: "82498422-1626-4479-9daa-3d7c7e088d8e" ``` -1. Add `TriageDashboardAction` (or extend `ShowHubAction`) wrapped with `autoRefresh do` -2. Dashboard panels: - - KPI row: open / in_review / accepted / rejected / deferred counts - - Triage queue: open candidates sorted oldest-first (title, widget, category, age) - - Recent escalations: last 20 with widget and source annotation snippet - - Category breakdown: annotation count grouped by category across all hub widgets -3. Layout includes `{autoRefreshMeta}`, `morphdom.js`, `ihp-auto-refresh.js` -4. Test: insert a new candidate via the UI → dashboard updates within ~1s without reload +Extend or add a dedicated triage dashboard action wrapped with `autoRefresh do`. +Shows: open RequirementCandidates (count by status), triage queue (oldest open +first), recent escalations (last 20), candidates by category breakdown. +Live-updates on any candidate/triage state change. -**Exit criteria:** Dashboard live-updates on candidate/triage changes. WebSocket frames visible in DevTools. +**Exit criteria:** Dashboard renders; live-updates on candidate creation and +triage status change without page reload. --- @@ -280,44 +219,11 @@ priority: high state_hub_task_id: "935de4d7-867f-49aa-bddf-6ff9435215de" ``` -1. **Integration tests** (`Test/`): - - AnnotationThread create + assign annotation - - Escalation: annotation → candidate, duplicate escalation blocked - - Triage transitions: valid path (open → in_review → accepted); invalid transition rejected (422) - - ReviewerAssignment: assign, reassign, my-queue filter - - Triage dashboard: `autoRefresh` wrapper present -2. **Consistency sync:** - ```bash - cd ~/the-custodian && make fix-consistency REPO=inter-hub - ``` -3. **Documentation updates:** - - Update `SCOPE.md` current state section: Phase 2 complete - - Write `docs/phase2-summary.md`: what was built, known limitations, Phase 3 readiness -4. **Smoke test checklist:** - - `devenv up` → clean start - - Annotate a widget, set severity high - - Create a thread, assign two annotations to it - - Escalate an annotation to a candidate - - Run triage: open → in_review → accepted - - Assign reviewer, check My Queue - - Confirm triage dashboard auto-updates +Integration tests: AnnotationThread CRUD; escalation happy path; triage state +transitions (valid + invalid); reviewer assignment; dashboard AutoRefresh +wrapper present. Consistency sync. Update `SCOPE.md` current state. Write +`docs/phase2-summary.md`. Smoke test: create thread, escalate annotation, triage +candidate, confirm dashboard updates. -**Exit criteria:** All tests pass; consistency sync reports no errors; smoke test completed; SCOPE.md updated. - ---- - -## Phase 2 Dependencies - -- Phase 1 schema stable (T01 depends on widgets, annotations, users from Phase 1) -- `annotation_threads` before `annotations` FK extension (T01 ordering: create thread table before altering annotations) -- Schema (T01) before all controller work (T02–T08) -- RequirementCandidate (T04) before Escalation (T05), TriageState (T06), ReviewerAssignment (T07) -- All feature tasks (T01–T08) before gate (T09) - -## Notes - -- **Severity on Annotation is a Phase 1 schema extension**, not a new table — it's a simple migration with a safe default. -- **No automated duplicate detection in Phase 2.** Grouping is operator-driven via `AnnotationThread`. Automated similarity is Phase 3+ scope. -- **TriageState is append-only.** Like `InteractionEvent`, never update/delete triage rows — only insert new status transitions. -- **HTMX for in-place actions** (escalate, triage status, assign reviewer) keeps the operator flow fluid without full page reloads. -- **No `RequirementCandidate` delete.** Candidates can be rejected or archived, never deleted, to preserve audit trail. +**Exit criteria:** All integration tests pass; `SCOPE.md` reflects Phase 2 +completion; consistency check passes.