Estimate: 16m
The route + data spine the rest of the Story hangs off. A Server Component at app/(authed)/issues/[key]/page.tsx resolves the active project (getActiveProject()) and reads the work item BY ITS PROJECT-SCOPED IDENTIFIER (e.g. PROD-42) — not by raw id — since that's what the URL carries and what every link in the app produces.
Service (one aggregate read). Add workItemsService.getIssueDetail(identifier, ctx) returning a single IssueDetailDto that bundles what the page needs in one round-trip: the work item, its parent (id + identifier + kind + title + status, for the breadcrumb), its direct children (same shape, for the child list), its links (blocked-by / blocks), and its workflow context (the project's statuses + legal next-transitions for the current status). It composes existing repo reads behind ONE service method (CLAUDE.md: the page calls one service method, not five repos). Tenant gate FIRST: a cross-workspace or non-existent identifier collapses to a uniform 404 (no existence leak) — reuse WorkItemNotFoundError. getWorkItemByIdentifier is the underlying repo lookup (@@unique([projectId, identifier])); if 2.3.6 already added it, reuse it — do not add a second copy.
Page shell. The two-column layout every later subtask fills: a header row (type icon from 2.1.1 · PROD-N identifier · title · a status pill placeholder 2.4.4 upgrades to the inline control), a main column (description/explanation — 2.4.2, child list — 2.4.3), and a metadata sidebar (core fields — 2.4.2; relationships — 2.4.5). An "Edit" button links to /issues/[key]/edit (2.3.6). The layout reserves clearly-commented Epic-5 extension slots (comments · attachments · custom fields · activity) as empty regions, so those land as fills rather than a re-layout. Renders inside the Story-1.5 authed shell; unauthenticated → sign-in redirect; non-member / cross-workspace → 404.
PROD-N; cross-workspace / unknown identifier → 404 (no title/existence leak); unauthenticated → sign-in redirect.workItemsService.getIssueDetail returns ONE IssueDetailDto (work item + parent + children + links + workflow context); the page makes exactly one service call. Explicit workspaceId gate (finding #26).getWorkItemByIdentifier is the single shared identifier lookup (reused from 2.3.6 if present, else added here — never duplicated).getIssueDetail: happy path shape, cross-workspace 404, parent/children/links populated correctly. The STRICT shell-a11y sweep is extended to the detail route (added here, asserted across later subtasks).lib/services/workItemsService.ts + lib/repositories/workItemRepository.ts (1.4) — getWorkItem/listChildren/findById/getWorkItemByIdentifier + the workspaceId-filter conventionapp/(authed)/issues/[key]/edit/ (2.3.6) — the sibling route shape + getActiveProject() resolution (finding #50)lib/dto/workItems.ts + lib/mappers/workItemMappers.ts — DTO + mapper conventions (no @prisma/client leak)motir-core/CLAUDE.md — 4-layer, one-service-call-per-page, entity-naming