Files
kandev/docs/plans/pr-task-status-hover-hydration/plan.md
T
2026-08-26 15:54:59 +00:00

181 lines
8.7 KiB
Markdown

---
created: 2026-08-26
status: complete
requirements:
- REQ-UI-PR-TASK-STATUS-SUMMARY-001
system_design:
- ../../specs/ui/system-design/pr-task-status-summary.md
legacy_specs: []
---
# Implementation Plan: PR Task Status Hover Hydration
## Overview
Make every GitHub task PR indicator disclose details consistently. First add a
task-scoped hydration path for compact indicators. Then add the author identity
to the shared summary and the existing mobile drawer.
This order makes the data available before the presentation and end-to-end
work depends on it.
## Scope
### In scope
- Open a tooltip for compact GitHub PR indicators on mouse hover and keyboard
focus.
- Load persisted full PR records for only the disclosed task.
- Deduplicate concurrent loads, reuse store data, guard HTTP and WebSocket
races, and permit retry after an error.
- Preserve workspace ownership for live TaskPR updates. The browser must reject
missing or foreign workspace IDs, and typed backend events must route by their
owning workspace without falling back to a global broadcast.
- Show the GitHub author login in the task summary.
- Show the same author login in the existing coarse-pointer PR-status drawer.
- Add focused component, desktop Playwright, and mobile Playwright coverage.
### Out of scope
- Upstream GitHub refreshes from the task-row indicator.
- Workspace-wide PR loading for the sidebar.
- Changes to task-summary persistence or projection fields.
- New task-row touch targets, drawers, navigation, or provider associations.
- Author support for GitLab or registered provider summaries.
## Technical approach
### Task-scoped disclosure hydration
Add a hook under `hooks/domains/github` that uses `listTaskPRs([taskId])` when
`taskPRs.byTaskId[taskId]` is empty for the active workspace context. Scope the
in-flight registry to the Zustand store and key it by workspace, workspace
context generation, and task. Preserve deletion tombstones so a late response
cannot restore an association removed while it was pending.
Update `PRTaskIcon` and `TaskContributionIcons` so compact and full data use one
tooltip trigger. Compact content shows localized loading or unavailable text.
Successful data updates replace the content without unmounting the trigger or
closing the tooltip.
Before response application, compare the active workspace and current store
records. Preserve matching records that arrived from WebSocket events. Add only
missing PR identities from the HTTP result.
### Workspace-scoped live PR updates
Treat `TaskPR.workspace_id` as the authoritative owner of a live update. The
frontend handler applies an update only when that ID is present and matches the
active workspace, preserving the current scoped cache for missing or foreign
payloads. The backend exposes `TaskPR.WorkspaceID` to the notification
broadcaster and routes typed PR updates and detachments through the fail-closed
workspace path when authentication is enforced, so an unattributed event is
dropped instead of broadcast globally.
### Author presentation
Add optional author data to `ChangeRequestTaskStatusSummaryData`. Derive the
value from `TaskPR.author_login` and show localized `task:byAuthor` copy below
the title.
Extend `ChangeRequestPopoverHeader` with an optional author value. Pass the
GitHub author from `PRCIPopover` so desktop status content and the existing
mobile drawer use the same text.
### Localization
Add localized loading and unavailable copy to the GitHub namespace in English,
Portuguese, Simplified Chinese, Hong Kong Chinese, and Taiwan Chinese. Reuse
the existing localized `task:byAuthor` key for author presentation.
## Tests
| Acceptance criteria | Evidence |
| --- | --- |
| `AC-UI-PR-TASK-STATUS-SUMMARY-001.13` to `.16` | A new hydration-hook test covers task scope, one in-flight request, cache reuse, workspace and task-context changes, deletion during an HTTP request, empty/error retry, and HTTP/WS ordering. Render tests cover mouse and keyboard disclosure states, including focus continuity. |
| `AC-UI-PR-TASK-STATUS-SUMMARY-001.17` | Summary derivation and shared render tests cover a present and missing author login. |
| Existing `.1` to `.12` | Existing PR icon and shared summary suites remain green. |
| `AC-UI-PR-TASK-STATUS-SUMMARY-001.18` | Existing status-chip component tests and mobile Playwright coverage prove drawer author visibility and unchanged task-row navigation. |
| `AC-UI-PR-TASK-STATUS-SUMMARY-001.19` | `github.test.ts` proves missing and foreign live updates leave the active scoped cache unchanged; `task_notifications_test.go` proves typed events reach only the owning workspace and unattributed events are dropped when auth is enforced; the singleton panel test preserves taskPR scope metadata during task switching. |
## E2E tests
- `apps/web/e2e/tests/pr/pr-sidebar-hover-hydration.spec.ts` uses the `chromium`
project. It opens an unrelated task, hovers an inactive task PR indicator,
observes the task-scoped request, and checks the author and structured rows.
- `apps/web/e2e/tests/task/mobile-task-status-summary.spec.ts` uses the
`mobile-chrome` project. It taps the row, opens the current PR-status drawer,
checks the author, and checks for horizontal overflow.
## Mobile design contract
- **Desktop outcome:** Hover or keyboard focus opens and hydrates the PR task
summary.
- **Mobile entry:** The task row remains the entry point. The user opens the
task and then uses the existing PR-status chip.
- **Exemplars:** `session-task-switcher-sheet.tsx` owns task navigation.
`pr-status-chip.tsx` owns the coarse-pointer drawer.
- **Hierarchy:** The task row stays primary. The PR author is secondary content
below the title in the existing drawer.
- **Presentation:** Reuse the inset PR-status drawer. Do not add another drawer
or a compact icon hit target.
- **Scroll and geometry:** Keep the drawer's existing internal scroll owner,
safe-area behavior, and viewport containment.
- **Shared state:** Desktop and mobile read the same `TaskPR.author_login`.
- **Proof:** The mobile Playwright scenario checks navigation, drawer content,
and horizontal containment.
## Work orders
- [x] [Task 01: Hydrate PR details on disclosure](task-01-hydrate-pr-details-on-disclosure.md)
- [x] [Task 02: Show PR author identity](task-02-show-pr-author-identity.md)
Task 02 depends on Task 01. The work orders are sequential and do not authorize
subagents.
## Verification results
- Focused web unit suite: 163 tests passed across 10 files after the review
fixup, including the workspace-context generation regression test.
- Web typecheck passed.
- Web i18n check and ratchet passed.
- Full web lint passed.
- Desktop Playwright hydration scenario passed, including task-scoped request
deduplication, structured details, author identity, and cache reuse.
- Mobile Chrome Playwright scenario passed, including task navigation, the
existing PR drawer, author identity, and horizontal containment.
- Web E2E build passed.
- Fresh desktop and mobile captures were generated, inspected, compressed,
and validated through `apps/web/.pr-assets/manifest.json`.
- The repository-wide E2E sleep-policy lint remains baseline-red with 188
unrelated errors. Both changed E2E files pass the same lint directly.
- PR review fixup coverage also verifies task-id and workspace-context races,
deletion tombstones, and keyboard focus continuity during hydration.
- Workspace-routing review fixup on 2026-08-26 added the `workspace_id` frontend
contract, missing/foreign event guards, typed backend workspace extraction, and
fail-closed notification tests. The focused frontend suite passed 95 tests
across 6 files and the backend gateway/GitHub suites passed 2,176 tests. The
full frontend Vitest run was stopped after the requested 15-minute local
window without a failure result or terminal summary; the PR checks remain the
authoritative broad-suite validation.
## Documentation impact
No public documentation change is required. This package updates the durable UI
requirement and adds its system design.
## Risks
- A late HTTP result can replace newer WebSocket data unless current store
identities win during response application.
- One hook instance per row can produce duplicate requests unless the in-flight
registry is scoped above each component.
- A request that starts on pointer entry can finish after pointer leave. The
result must remain cached without reopening the tooltip.
- Radix can keep hidden tooltip portals mounted. E2E locators must target the
active tooltip content.
- Adding author text can increase summary height and can wrap in translated
layouts. Desktop and mobile tests must retain viewport containment.
- A missing or mismatched workspace ID must never be replaced with the active
workspace or reach a global notification broadcast.