doc/plans/2026-04-07-issue-detail-speed-and-optimistic-inventory.md
Status: Proposed Date: 2026-04-07 Audience: Product and engineering Related:
ui/src/pages/IssueDetail.tsxui/src/components/IssueProperties.tsxui/src/api/issues.tsui/src/lib/queryKeys.tsserver/src/routes/issues.tsserver/src/services/issues.tsThis note inventories the Paperclip issues that point to the same UX class of problem:
The immediate trigger is PAP-1192: the issue detail page now feels very slow.
The issue detail page is not obviously blocked by one pathological endpoint. The main problem is the shape of the page:
IssueDetail fans out into many independent queries on mountLoading... fallback and very little staged or sectional loading UXMeasured against the current assigned issue (PAP-1191) on local dev, the slowest single request was the full company issues list:
GET /api/issues/:id about 18msGET /api/issues/:id/comments|activity|approvals|attachments about 6-8msGET /api/companies/:companyId/agents|projects about 9-11msGET /api/companies/:companyId/issues about 76msThat strongly suggests the current pain is aggregate client fan-out plus over-broad invalidation, not one obviously broken endpoint.
Pattern: the issue page already has a history of needing both optimistic behavior and bounded thread/loading behavior. PAP-1192 is the same family, not a new category.
Pattern: Paperclip already has several places where the right fix was "show intent immediately, then reconcile," not "wait for refetch."
Pattern: the product has recurring pressure to reduce blank/loading states across the app, so the issue-detail work should fit that broader direction.
ui/src/pages/IssueDetail.tsx mounts all of these data sources up front:
This is too much for the initial view of a single issue.
IssueDetail currently does:
issuesApi.list(selectedCompanyId!)parentId === issue.idThat is expensive relative to the need.
Important detail:
parentIdserver/src/services/issues.ts already supports parentIdui/src/api/issues.ts does not expose parentId in the filter typeSo the client is missing an already-supported narrow query path.
server/src/routes/issues.ts and server/src/services/issues.ts already support:
afterorderlimitBut IssueDetail still calls issuesApi.listComments(issueId) with no cursor or limit and then re-invalidates the full thread after common comment actions.
That means we already have the server-side building blocks for incremental comment loading, but the page is not using them.
invalidateIssue() in IssueDetail invalidates:
That is acceptable for correctness, but it is expensive for perceived speed and makes optimistic work feel less stable because the page keeps re-painting from fresh network results.
The page polls both:
issues.liveRuns(issueId) every 3sissues.activeRun(issueId) every 3sThat is duplicate polling for closely related state.
ui/src/components/IssueProperties.tsx fetches:
The page and panel are each doing their own list work instead of sharing a narrower issue-detail data model.
IssueDetail only shows:
Loading... while the main issue query is pendingAfter that, many sub-sections can appear empty or incomplete until their own queries resolve. That makes the page feel slower than the raw request times suggest.
Ship UX changes that make the page feel immediate before deeper backend reshaping:
Loading... state with an issue-detail skeletonWhy first:
Add parentId to the issuesApi.list(...) filter type and switch IssueDetail to:
This is the highest-confidence narrow win because the server path already exists.
Use the existing server support for:
afterlimitSuggested behavior:
load earlier for long threadsThis should address the same performance family as PAP-254.
Tighten the runtime side of the page:
liveRuns and activeRun into one client source if possibleExamples:
If the page is still too chatty after the client fixes, add one tailored bootstrap surface for the issue detail page.
Potential bootstrap payload:
This should happen after the obvious client overfetch fixes, not before.
parentIdThe same standards should apply to:
PAP-1192: issue-detail skeletons and staged loadingparentId support to ui/src/api/issues.ts and switch child-issue fetching to a narrow queryThis inventory is successful if the follow-up implementation makes the issue page behave like this: