================================================================================ QA PROOF — Redmine #40 "Implement parent-child ticket relationships in ticketing module" Project: Pipeliner Client (id 9) | Tracker: Feature | Status entering QA: QA Branch: feat/40-parent-child-tickets | PR #22 -> dev HEAD: 0a59ec3 fix(ticketing): address #40 review — dedup subtitle, derive sort order, ICU plural, parent fallback Date: 2026-06-11 | Agent: manual QA (#25) Scope: Frontend-only (Next.js 16, Atomic Design, next-intl). No backend change. ================================================================================ SMOKE TEST STRATEGY ------------------- A live authenticated screenshot of the ticketing module was attempted via the gstack browse binary. The pipeliner login page (https://pipeliner.omdev.tech/login) was reachable (HTTP 200) but rendered blank/unauthenticated (no test credentials, flapping auth, no guaranteed project carrying parent-child tickets). Per the QA fallback policy, the component/render test suites that exercise the feature are used as the smoke proof. These tests render the real TaskStageGroup, TaskRow, TaskDetailPane and ticketing page components and assert the exact user-visible behaviour described by each acceptance criterion. FULL FRONTEND GATE (project CLAUDE.md) -------------------------------------- npx vitest run -> 83 files, 424 tests PASSED npx tsc --noEmit -> exit 0 (clean) npx eslint . -> exit 0 (clean) npm run build -> exit 0 (success; all routes emitted incl. /projects/[id]/ticketing) The single "ENVIRONMENT_FALLBACK" log on /admin prerender is the documented pre-existing, non-fatal log — build still completes. AC-RELEVANT SUITES (35 tests, all PASSED) ----------------------------------------- src/features/tasks/hierarchy.test.ts ............ 9 tests src/components/molecules/TaskStageGroup.test.tsx 15 tests src/components/organisms/TaskDetailPane.test.tsx . 9 tests app/projects/[id]/ticketing/page.test.tsx ....... 2 tests ACCEPTANCE CRITERIA -> EVIDENCE -------------------------------- [OK] List View: parent shows expandable arrow; expanding reveals indented subtickets (title/status/preview) - TaskStageGroup > shows an expand toggle on a parent with subtickets - TaskStageGroup > hides subtickets until the parent is expanded - TaskStageGroup > reveals indented subtickets (with status preview, not a duplicate subject) when expanded [OK] List View: clicking a subticket opens its detail view with a "Back to parent" button - TaskStageGroup > opens a subticket's detail via onSelect when its row is clicked - TaskStageGroup > calls onToggleExpand (not onSelect) when the chevron is clicked - Ticketing page > shows a Back-to-parent affordance for a deep-linked subticket whose parent isn't on the loaded board (#40) [OK] Detail View: clickable "Parent Ticket" section when a parent exists, navigates to parent - TaskDetailPane > shows a clickable Parent ticket section that navigates to the parent - hierarchy > parentSummary resolves the parent TaskSummary when present - hierarchy > parentSummary returns null for a top-level task and for an orphan parent_id [OK] Detail View: collapsible "Subtickets" section (title/status/preview) when subtickets exist - TaskDetailPane > lists subtickets in a collapsible section and opens one on click - TaskDetailPane > collapses the subtickets section via its toggle - TaskDetailPane > renders nothing parent/subtickets-related when there are none [OK] Detail View: clicking a subticket replaces the view + shows "Back to parent" - TaskDetailPane > lists subtickets in a collapsible section and opens one on click - Ticketing page > shows a Back-to-parent affordance for a deep-linked subticket (#40) [OK] Sorting: subtickets sorted by status (e.g. "In Development" first) - hierarchy > sortByStatus orders by pipeline rank with in_development first - hierarchy > sortByStatus is stable for equal statuses (preserves input order) [OK] Nesting: only one level of subtickets shown (no subtickets of subtickets) - hierarchy > enforces one level of nesting: a child's own children are not nested - TaskStageGroup > renders a child that is itself a parent with NO expand chevron at depth 1 (one-level guarantee enforced at render) [OK] Visual Design: expandable arrows, status tags, sections match existing styling - TaskStageGroup > renders rows with an accessible name of subject + status - TaskStageGroup > derives the stage label from the catalog (status_id), not server status_name - TaskStageGroup > falls back to status_name for an unknown status id - Reuses existing StatusPill/Badge (no new status color logic per spec_ref) [OK] Navigation: "Back to parent" returns user to the parent ticket's detail view - Ticketing page > shows a Back-to-parent affordance for a deep-linked subticket whose parent isn't on the loaded board (#40) - TaskDetailPane > shows a clickable Parent ticket section that navigates to the parent EDGE CASES / ROBUSTNESS (covered by tests) ------------------------------------------ [OK] Orphan parent_id (parent not on the loaded board) renders as a normal top-level row (no crash) - hierarchy > treats an orphan parent_id as a top-level row - TaskStageGroup > renders an orphan parent_id as a normal top-level row [OK] Dedup: a task appearing in two stage groups produces no duplicate child rows - hierarchy > dedups a task that appears in two stage groups [OK] A real child is NOT rendered as a standalone row in its own stage - TaskStageGroup > does NOT render a child as a standalone row in its own stage VERBATIM TEST RESULTS (ANSI-stripped; no secrets) ------------------------------------------------- ✓ src/features/tasks/hierarchy.test.ts > buildTaskHierarchy > groups children under their parent across stage groups ✓ src/features/tasks/hierarchy.test.ts > buildTaskHierarchy > treats an orphan parent_id (parent not in the board) as a top-level row ✓ src/features/tasks/hierarchy.test.ts > buildTaskHierarchy > marks a real child as NOT a top-level row ✓ src/features/tasks/hierarchy.test.ts > buildTaskHierarchy > dedups a task that appears in two stage groups (no duplicate child rows) ✓ src/features/tasks/hierarchy.test.ts > buildTaskHierarchy > enforces one level of nesting: a child's own children are not nested ✓ src/features/tasks/hierarchy.test.ts > sortByStatus > orders by pipeline rank with in_development first ✓ src/features/tasks/hierarchy.test.ts > sortByStatus > is stable for equal statuses (preserves input order) ✓ src/features/tasks/hierarchy.test.ts > parentSummary > resolves the parent TaskSummary when present ✓ src/features/tasks/hierarchy.test.ts > parentSummary > returns null for a top-level task and for an orphan parent_id ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > renders the stage header, count, and a labelled group ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > renders rows with an accessible name of subject + status ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > marks the selected row with aria-current ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > fires onSelect with the issue id when a row is clicked ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > renders the empty-stage line for a stage with no tasks ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > derives the stage label from the catalog (status_id), not the server status_name ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > falls back to status_name for an unknown status id ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > parent/child (#40) > shows an expand toggle on a parent with subtickets ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > parent/child (#40) > hides subtickets until the parent is expanded ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > parent/child (#40) > reveals indented subtickets (with status preview, not a duplicate subject) when expanded ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > parent/child (#40) > does NOT render a child as a standalone row in its own stage ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > parent/child (#40) > calls onToggleExpand (not onSelect) when the chevron is clicked ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > parent/child (#40) > opens a subticket's detail via onSelect when its row is clicked ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > parent/child (#40) > renders a child that is itself a parent with NO expand chevron at depth 1 (one-level guarantee enforced at render) ✓ src/components/molecules/TaskStageGroup.test.tsx > TaskStageGroup > parent/child (#40) > renders an orphan parent_id as a normal top-level row ✓ src/components/organisms/TaskDetailPane.test.tsx > TaskDetailPane — read-only status (no client transitions) > renders the status as a read-only Badge with no transition control ✓ src/components/organisms/TaskDetailPane.test.tsx > TaskDetailPane — gated edit / delete / attachments > shows Edit + Delete when mutable (status 7) and reveals the edit form ✓ app/projects/[id]/ticketing/page.test.tsx > Ticketing workspace (/projects/[id]/ticketing) > renders the assistant composer and the project context (active module tab + name) ✓ src/components/organisms/TaskDetailPane.test.tsx > TaskDetailPane — gated edit / delete / attachments > saves an edit via PATCH ✓ app/projects/[id]/ticketing/page.test.tsx > Ticketing workspace (/projects/[id]/ticketing) > shows a Back-to-parent affordance for a deep-linked subticket whose parent isn't on the loaded board (#40) ✓ src/components/organisms/TaskDetailPane.test.tsx > TaskDetailPane — gated edit / delete / attachments > deletes via the confirm dialog and navigates back ✓ src/components/organisms/TaskDetailPane.test.tsx > TaskDetailPane — gated edit / delete / attachments > renders nothing parent/subtickets-related when there are none ✓ src/components/organisms/TaskDetailPane.test.tsx > TaskDetailPane — gated edit / delete / attachments > locks edit/delete/add and shows lock notes when not mutable (status 9) ✓ src/components/organisms/TaskDetailPane.test.tsx > TaskDetailPane — parent/child relationships (#40) > shows a clickable Parent ticket section that navigates to the parent ✓ src/components/organisms/TaskDetailPane.test.tsx > TaskDetailPane — parent/child relationships (#40) > lists subtickets in a collapsible section and opens one on click ✓ src/components/organisms/TaskDetailPane.test.tsx > TaskDetailPane — parent/child relationships (#40) > collapses the subtickets section via its toggle Test Files 4 passed (4) Tests 35 passed (35) ================================================================================ VERDICT: PASS — all 9 acceptance criteria OK. Ticket stays in QA (preprod promotion is a separate agent's responsibility). SECRETS: none included (no env values, tokens, DB URLs, cookies, or bearer tokens). ================================================================================