Skip to content

feat(web-ui): task → PRD requirement traceability badges (#468)#490

Merged
frankbria merged 3 commits intomainfrom
feat/issue-468-task-requirement-traceability
Mar 24, 2026
Merged

feat(web-ui): task → PRD requirement traceability badges (#468)#490
frankbria merged 3 commits intomainfrom
feat/issue-468-task-requirement-traceability

Conversation

@frankbria
Copy link
Owner

@frankbria frankbria commented Mar 24, 2026

Summary

  • Adds requirement_ids field to the Task model (backend → API → frontend) so tasks can be linked to PROOF9 requirements
  • New useRequirementsLookup SWR hook fetches all requirements once per workspace (shared cache, no N+1 calls)
  • TaskCard shows a compact requirement badge (first req + glitch type + overflow count) linked to /proof/{reqId}
  • TaskDetailModal shows a full Requirements section with ID, title, and glitch_type for each linked requirement

Changes

Backend (codeframe/core/tasks.py, workspace.py, ui/routers/tasks_v2.py):

  • requirement_ids: list[str] field on Task dataclass, stored as JSON in SQLite
  • Migration guard (ALTER TABLE ... ADD COLUMN) for existing workspaces
  • update_requirement_ids() function
  • TaskResponse Pydantic model updated at all 3 response sites

Frontend:

  • Task TypeScript interface: requirement_ids?: string[]
  • useRequirementsLookup hook: SWR-cached Map for badge lookups
  • TaskCard, TaskColumn, TaskBoardContent, TaskBoardView, TaskDetailModal: requirement display

Tests: 8 new v2 tests for create/get/list/persist/update round-trips

Test plan

  • uv run pytest tests/core/test_task_requirement_ids.py — 8 tests pass
  • npm run build in web-ui/ — clean TypeScript compile
  • Existing task-related tests (test_task_dependencies.py, test_tasks_edge_cases.py) still pass
  • Tasks with no requirement_ids show no badge (graceful empty state)
  • Tasks with multiple requirements show first badge + "+N" overflow

Closes #468

Summary by CodeRabbit

  • New Features

    • Tasks can be associated with multiple requirements; requirement data surfaces across board, columns, cards, and detail view
    • Requirement badges on task cards link to requirement pages, show first requirement (with type) and “+N” overflow
    • Detail view shows a Requirements section with titles, metadata, links, and a loading spinner while fetching
    • Frontend hook added to fetch and provide requirement lookup data
  • Tests

    • Comprehensive tests for association, persistence, migration, and updates
  • Bug Fixes

    • Prevented badge clicks from triggering card selection/keyboard handlers

Adds requirement_ids linkage between tasks and PROOF9 requirements,
enabling users to trace tasks back to the business requirements they implement.

Backend:
- Add `requirement_ids: list[str]` field to Task dataclass
- Persist as JSON column in SQLite with migration guard for existing workspaces
- Update all SELECT queries and _row_to_task() parser (index 18)
- Add `update_requirement_ids()` function
- Expose field in TaskResponse Pydantic model (all 3 response sites)

Frontend:
- Add `requirement_ids?: string[]` to Task TypeScript interface
- New `useRequirementsLookup` SWR hook: fetches requirements once per workspace,
  returns Map<id, ProofRequirement> for O(1) card-level lookup (no N+1 calls)
- TaskCard: shows first requirement as outlined badge with glitch_type indicator,
  "+N" overflow count, links to /proof/{reqId}; hidden when no requirements
- TaskDetailModal: Requirements section with full ID (linked), title, and
  glitch_type caption for each linked requirement; hidden when empty
- Map threaded down: TaskBoardView → TaskBoardContent → TaskColumn → TaskCard

Tests: 8 new v2 tests covering create/get/list/persist/update round-trips
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: afbfd377-de36-41ce-8613-b245a7a512d9

📥 Commits

Reviewing files that changed from the base of the PR and between de6bcef and 56cf5de.

📒 Files selected for processing (1)
  • tests/core/test_task_requirement_ids.py
✅ Files skipped from review due to trivial changes (1)
  • tests/core/test_task_requirement_ids.py

Walkthrough

Adds a requirement_ids list to Task model and DB, surfaces it in API responses, adds update_requirement_ids() mutation, introduces a client hook to fetch requirements, threads requirement lookups through board/column/card components, and renders clickable requirement badges plus a Requirements section in task detail views.

Changes

Cohort / File(s) Summary
Backend Core Model & Persistence
codeframe/core/tasks.py, codeframe/core/workspace.py
Add Task.requirement_ids: list[str], persist as tasks.requirement_ids (TEXT JSON default '[]'), update create, get, list_tasks, _row_to_task, add update_requirement_ids(), and ensure DB schema upgrade adds the column when missing.
Backend API Response
codeframe/ui/routers/tasks_v2.py
Add requirement_ids to TaskResponse and include it in list_tasks, get_task, and update_task responses.
Backend Tests
tests/core/test_task_requirement_ids.py
New integration tests covering default value, creation with values, list/get persistence, update/clear via update_requirement_ids, and legacy-schema migration compatibility.
Frontend Types & Hook
web-ui/src/types/index.ts, web-ui/src/hooks/useRequirementsLookup.ts, web-ui/src/hooks/index.ts
Add optional requirement_ids?: string[] to Task type; new useRequirementsLookup(workspacePath) hook (SWR) returning requirementsMap: Map<string,ProofRequirement> plus loading/error; re-export added to hooks index.
Frontend Data Propagation
web-ui/src/components/tasks/TaskBoardView.tsx, web-ui/src/components/tasks/TaskBoardContent.tsx, web-ui/src/components/tasks/TaskColumn.tsx
Fetch requirementsMap in board view and pass it through props: board → content → column → cards.
Frontend UI Display
web-ui/src/components/tasks/TaskCard.tsx, web-ui/src/components/tasks/TaskDetailModal.tsx
Render clickable requirement badge(s) linking to /proof/[id] with glitch_type/title when available; add Requirements section in detail modal; add event handlers to prevent badge clicks from triggering card selection/navigation.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Browser / UI
  participant Hook as useRequirementsLookup
  participant API as Backend API (tasks_v2 / proofApi)
  participant DB as SQLite (workspace)

  Browser->>Hook: mount(workspacePath)
  Hook->>API: GET /proof/requirements (SWR key ['/proof/requirements', workspacePath])
  API->>DB: query requirements table
  DB-->>API: requirements[]
  API-->>Hook: requirements[]
  Hook-->>Browser: requirementsMap (Map<id,Requirement>)

  Browser->>API: GET /tasks or GET /tasks/{id}
  API->>DB: query tasks (include requirement_ids column)
  DB-->>API: task rows (requirement_ids JSON/text)
  API-->>Browser: task(s) with requirement_ids[]
  Browser->>Browser: render TaskBoard / TaskCard / TaskDetail (badges link to /proof/{id})
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇 I hopped through rows and JSON fields with care,

Pinned tiny badges here and links to there,
Maps and hooks I carried in my pack,
So tasks can point back to the source they track.
— CodeRabbit

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: adding task-to-PRD requirement traceability badges in the web-UI, which is the primary change across multiple component and backend modifications.
Linked Issues check ✅ Passed All coding requirements from issue #468 are met: requirement_ids field added to Task model, requirement badges displayed on TaskCard with clickable links to /proof/{reqId}, requirement info included in TaskDetailModal, multi-requirement support implemented, and graceful handling when no requirements are linked.
Out of Scope Changes check ✅ Passed All changes are in-scope and directly support the requirement traceability feature: backend Task model updates, database schema migration, API/TypeScript type updates, React hooks for data fetching, and UI components for displaying requirement badges.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #468

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-468-task-requirement-traceability

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link

claude bot commented Mar 24, 2026

Review: feat(web-ui): task → PRD requirement traceability badges

This is a clean, well-scoped PR. The full-stack slice (DB → core → API → hooks → components) is consistent, the SWR deduplication strategy is correct, and the 8 tests cover the important persistence and round-trip cases. A few things worth addressing before merge:


Bug: connection leak in update_requirement_ids() (tasks.py)

Unlike every other write function in this file (e.g. create(), update_depends_on()), the new function does not use try/finally to guarantee the connection is closed:

conn = get_db_connection(workspace)
cursor = conn.cursor()
cursor.execute(...)
conn.commit()
conn.close()   # not reached if execute() or commit() raises

Pattern used elsewhere:

conn = get_db_connection(workspace)
try:
    cursor = conn.cursor()
    cursor.execute(...)
    conn.commit()
finally:
    conn.close()

Performance: requirementsMap recreated on every render

In useRequirementsLookup.ts, the Map is built unconditionally outside any memo:

const requirementsMap = new Map<string, ProofRequirement>(
  (data?.requirements ?? []).map((r) => [r.id, r]),
);

This creates a new Map object on every render even when data has not changed, which breaks reference equality for the TaskCard / TaskColumn props and may cause avoidable re-renders across the entire board. Wrapping in useMemo fixes it:

const requirementsMap = useMemo(
  () => new Map<string, ProofRequirement>((data?.requirements ?? []).map((r) => [r.id, r])),
  [data],
);

Question: how do requirement IDs get set?

update_requirement_ids() exists in core but there is no API endpoint that calls it — the update_task endpoint does not accept requirement_ids in the request body, and there is no CLI command to link tasks to requirements. The badges will always show empty in practice until a write path is added.

Is this intentional (display infrastructure first, linking mechanism in a follow-up)? If so, a note in the PR or a follow-up issue would help. If not, the endpoint needs to be wired up.


Minor: reqIds[0].slice(0, 10) appears twice in TaskCard.tsx

Once inside the firstReq branch and once in the fallback. Could be extracted to a variable at the top of the badge block.


Minor: the legacy-DB test is vacuous

test_task_without_requirement_ids_in_existing_db just creates a fresh task and asserts hasattr — it does not simulate reading a row that predates the migration (a row where the column is absent or NULL). The real protection is the len(row) > 18 guard in _row_to_task(). Consider either renaming the test to reflect what it actually checks, or adding a raw-SQL fixture that inserts a row without the column to exercise that guard directly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
web-ui/src/hooks/useRequirementsLookup.ts (1)

17-19: Consider memoizing the requirementsMap to avoid recreating it on every render.

The Map is reconstructed on each render. While SWR caches the underlying data, memoizing the Map would prevent unnecessary object identity changes that could trigger re-renders in consuming components.

♻️ Proposed refactor using useMemo
+'use client';
+
+import { useMemo } from 'react';
 import useSWR from 'swr';
 import { proofApi } from '@/lib/api';
 import type { ProofRequirement } from '@/types';
 
 export function useRequirementsLookup(workspacePath: string | null | undefined) {
   const { data, isLoading, error } = useSWR(
     workspacePath ? ['/proof/requirements', workspacePath] : null,
     ([, path]: [string, string]) => proofApi.listRequirements(path),
   );
 
-  const requirementsMap = new Map<string, ProofRequirement>(
-    (data?.requirements ?? []).map((r) => [r.id, r]),
-  );
+  const requirementsMap = useMemo(
+    () => new Map<string, ProofRequirement>(
+      (data?.requirements ?? []).map((r) => [r.id, r]),
+    ),
+    [data?.requirements],
+  );
 
   return { requirementsMap, isLoading, error };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/hooks/useRequirementsLookup.ts` around lines 17 - 19, The Map
construction for requirementsMap is recreated on every render; wrap it in
React's useMemo inside useRequirementsLookup (memoize the Map created from
(data?.requirements ?? []) so identity only changes when data?.requirements
changes) to prevent unnecessary re-renders in consumers—use the dependency on
data?.requirements (or data) and keep the Map signature new Map<string,
ProofRequirement>(...) as currently implemented.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/core/test_task_requirement_ids.py`:
- Around line 76-82: The test name and docstring claim to verify behavior for
tasks created before the migration (no requirement_ids column) but the current
test uses a fresh current-schema workspace and never simulates a pre-migration
tasks table; update test_task_without_requirement_ids_in_existing_db to
explicitly create a workspace DB/schema that lacks the requirement_ids column
before exercising the migration guard: use the workspace fixture to obtain a DB
connection (or create a new test DB file), execute raw SQL to create a tasks
table without the requirement_ids column (or drop/rename that column), then
initialize or open the workspace so the migration guard path runs and finally
call tasks.create(...) and assert hasattr(task, "requirement_ids") and
task.requirement_ids == []; reference the test function name
test_task_without_requirement_ids_in_existing_db and the tasks.create and
workspace initialization code paths when making this change.

In `@web-ui/src/components/tasks/TaskCard.tsx`:
- Around line 118-122: The card's global keyboard handler (onKeyDown in the
TaskCard component) is intercepting Enter on nested controls like the Badge/Link
for requirements (firstReq, reqIds, Link, Badge), causing the card to open
instead of following the link; fix by isolating key events: add onKeyDown={(e)
=> e.stopPropagation()} to the Badge/Link container (the div wrapping
BookOpen01Icon/Badge) so focused keyboard events don't bubble, and modify the
card's onKeyDown handler to only act when the event originates on the card
itself (compare event.target to event.currentTarget or use a cardRef and require
event.target === cardRef.current) before calling preventDefault/open logic so
nested controls can handle Enter/Space normally.
- Around line 120-133: The badge for reqIds[0] should always be wrapped in a
Link so navigation works even when requirementsMap lookup (firstReq) is missing;
update the JSX in TaskCard.tsx to render Link
href={`/proof/${encodeURIComponent(reqIds[0])}`} around the Badge in both cases
(use the existing firstReq conditional only for displaying
firstReq.glitch_type), ensuring you preserve the Badge variants, classes, and
the font-mono slice display while keeping the glitch_type span rendered only
when firstReq?.glitch_type exists.

---

Nitpick comments:
In `@web-ui/src/hooks/useRequirementsLookup.ts`:
- Around line 17-19: The Map construction for requirementsMap is recreated on
every render; wrap it in React's useMemo inside useRequirementsLookup (memoize
the Map created from (data?.requirements ?? []) so identity only changes when
data?.requirements changes) to prevent unnecessary re-renders in consumers—use
the dependency on data?.requirements (or data) and keep the Map signature new
Map<string, ProofRequirement>(...) as currently implemented.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 890f8377-001d-46a3-a2b6-d92bf094607d

📥 Commits

Reviewing files that changed from the base of the PR and between 39c1f8b and 4185952.

📒 Files selected for processing (12)
  • codeframe/core/tasks.py
  • codeframe/core/workspace.py
  • codeframe/ui/routers/tasks_v2.py
  • tests/core/test_task_requirement_ids.py
  • web-ui/src/components/tasks/TaskBoardContent.tsx
  • web-ui/src/components/tasks/TaskBoardView.tsx
  • web-ui/src/components/tasks/TaskCard.tsx
  • web-ui/src/components/tasks/TaskColumn.tsx
  • web-ui/src/components/tasks/TaskDetailModal.tsx
  • web-ui/src/hooks/index.ts
  • web-ui/src/hooks/useRequirementsLookup.ts
  • web-ui/src/types/index.ts

Test User added 2 commits March 23, 2026 20:09
- useRequirementsLookup: memoize Map with useMemo to prevent unnecessary
  re-renders when requirements data hasn't changed
- TaskCard: gate card keyboard handler with e.target !== e.currentTarget
  so Enter/Space on nested links (req badges) doesn't open task details
- TaskCard: always wrap requirement badge in Link — glitch_type is optional
  but navigation should work even when requirementsMap is still loading
- TaskCard: add onKeyDown stopPropagation to requirement badges container
- test_task_requirement_ids: replace placeholder migration test with a real
  simulation — drops requirement_ids column from a live SQLite DB, then
  re-opens the workspace to exercise the ALTER TABLE migration guard
@claude
Copy link

claude bot commented Mar 24, 2026

Follow-up Review

Good progress on this iteration. A few notes.

Outstanding: Connection leak in update_requirement_ids() (tasks.py)

My previous review flagged this and it still has not been addressed. The function calls conn.close() directly without a try/finally guard. If cursor.execute() or conn.commit() raises, the connection leaks. Every other write function in this file wraps the DB work in try/finally. Please apply that pattern here before merge.

Fixed: Keyboard event guard on TaskCard

The guard added to the card's onKeyDown handler (checking that the event target matches the current target) is the right fix. It prevents the requirement badge link keyboard events from bubbling up and triggering the card open action.

New: No API endpoint for updating requirement_ids

update_requirement_ids() exists in core but there is no REST endpoint that calls it. tasks_v2.py only adds requirement_ids to TaskResponse — the existing PATCH tasks endpoint does not accept or apply it. Requirement IDs are currently read-only from the API perspective.

If the intent is that requirement IDs are managed exclusively from the PROOF9 side (requirements link to tasks, not the other way around), document that with a comment. If the task board should support editing the links, a PATCH body extension or a dedicated sub-resource endpoint is needed.

Minor: ID truncation

reqIds[0].slice(0, 10) in TaskCard clips the badge label to 10 chars. Worth exposing as a named constant so it is easy to tune.

The migration test (test_task_without_requirement_ids_in_existing_db) is solid. Recreating the pre-migration schema via raw SQL is exactly the right way to validate a migration guard. No concerns there.

@claude
Copy link

claude bot commented Mar 24, 2026

Good iteration — most of the previous feedback has been addressed.

Fixed

  • requirementsMap is now memoized with useMemo([data?.requirements]) in useRequirementsLookup
  • Keyboard event propagation: onKeyDown guard (e.target !== e.currentTarget) added to card; badge div has onKeyDown stopPropagation
  • Badge is now always wrapped in Link (regardless of whether firstReq resolves)
  • Migration test rewritten: the new test actually drops and recreates the tasks table without requirement_ids, inserts a legacy row, then re-opens the workspace to trigger _ensure_schema_upgrades(). This genuinely exercises the migration guard.
  • The duplicate reqIds[0].slice(0, 10) is gone; now appears exactly once.

Still needs fixing: connection leak in update_requirement_ids()

The previous feedback on this was not addressed. Every other write function in tasks.py (e.g., create(), update_depends_on()) wraps the connection in try/finally to guarantee conn.close() is called even when execute() or commit() raises. update_requirement_ids() still does not — conn.close() is only reached on the happy path.

The fix is the same pattern used everywhere else in the file: wrap the cursor.execute / conn.commit block in try/finally: conn.close().


Still open: no write path for requirement IDs

update_requirement_ids() exists in core but nothing calls it from the API layer. The update_task endpoint request body does not accept requirement_ids, and there is no dedicated endpoint. The traceability badges will always render empty in practice.

If this is intentional (display layer now, linking mechanism as follow-up), please open a tracking issue and reference it here so the incomplete state is visible.

@frankbria frankbria merged commit e673154 into main Mar 24, 2026
10 checks passed
@frankbria frankbria deleted the feat/issue-468-task-requirement-traceability branch March 24, 2026 03:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UX: Link tasks back to PRD requirements for traceability

1 participant