The user reported having to click Jump-to-latest ~10 times before the
viewport actually pinned to the latest comment on long threads (e.g.
PAP-2536). The virtualizer estimates 220px per unmeasured row, so
totalSize is hugely underestimated until rows render and get measured;
a single smooth scroll lands above the real bottom and each subsequent
click walks closer as more rows mount and expand.
- IssueChatThread.handleJumpToLatest now awaits an optional
onRefreshLatestComments before resolving the latest target so any
comment that arrived after the initial load (or was dropped on a
live-update reconnect) is in the loaded set first.
- scrollToLatestCommentWithSettle issues the initial smooth scroll,
then on an 80 ms tick re-issues scrollIntoView({ block: "end" }) on
the latest comment row for up to 4 s, exiting once the row's bottom
matches the scroll container's bottom (within 4 px) and scrollTop +
scrollHeight have been stable for 3 ticks. If the row hasn't been
mounted into the virtualizer buffer, scrollToIndex is used to nudge
the offset until it is.
- IssueDetail destructures `refetch` from the comments useInfiniteQuery
and threads refetchLatestComments through to IssueChatThread.
QA verified on PAP-2720: single-click convergence in 327-407 ms across
5 successive trials, comment-fa353646 lands flush with the viewport
bottom (-0.44 px sub-pixel), stopped-scroll stability holds.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Suppress bounded run-liveness continuation wakes when an active pause hold
covers the issue, and cancel due scheduled retries with errorCode `issue_paused`
instead of promoting them only to be cancelled at claim time. Adds regression
coverage for both gates plus the no-pause-hold passthrough invariant.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
StatusIcon adds an AlertTriangle overlay on the red-ring blocked variant
when the read model reports recovery_needed; tooltip and aria-label
spell out the liveness break and which leaf is invalid.
IssueBlockedNotice gets a rose container variant with two copy
templates: parent-perspective ("paused at a liveness break in [leaf]")
and leaf-perspective ("productive run that exited without queueing a
continuation"). The sub-row label flips to "Liveness break at" and an
owner pill renders with the next-action verb derived from the
nextActionHint.
IssueRow surfaces "liveness break at {leaf}" in the meta line for
recovery_needed parents so inbox/MyIssues rows show the actionable leaf
without opening the parent.
The status-language Storybook matrix gains the four new state/reason
combinations and parent/leaf-perspective notice variants. Targeted
vitest cases cover both the icon overlay and notice rendering.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
A blocked parent like PAP-2089 currently reports a generic
needs_attention chain even when its leaf is the productive-run-stopped
shape from PAP-2674's plan. Extend listIssueBlockerAttentionMap to
fetch each candidate leaf's latest issue-linked succeeded run plus its
productivity-review state, classify the leaf as recovery_needed when it
matches the shape, and propagate the leaf identifier, next-action
owner, and CTA hint up to the root. The leaf itself also surfaces
recovery_needed on its own attention so the leaf-level notice can
render when an inbox wake lands directly on it.
Adds two integration cases (productive_run_stopped and
continuation_exhausted) to the embedded-Postgres regression suite.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Phase 5 of the liveness plan needs the blocker read model to distinguish
a "productive run stopped without continuation" leaf from generic
attention or stalled-review chains. Extend the shared type so server and
UI share one vocabulary for the new surface (state, reasons, owner,
hint).
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The Phase 3 virtualizer (PAP-2645) used useWindowVirtualizer, but on the
real issue page the chat lives inside `<main id="main-content"
overflow-auto>`. Window scroll never moves on desktop, so the virtualizer
only ever mounted the first 7 rows and the chat area went blank past the
first viewport. Detect the actual scroll container at mount via DOM walk
and rekey the inner virtualizer to use element-mode observers/scroll-fn
when an overflow ancestor exists; fall back to window mode for mobile and
the auth-free perf fixture, where the document scrolls.
Adds a regression test that mounts IssueChatThread inside an overflow-auto
ancestor and asserts jump-to-latest drives that element's scrollTo, not
window.scrollTo.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
If the issue reply composer would post without any assignee, the first
Send press now surfaces a warning toast instead of submitting. A second
press confirms the user wants to proceed unassigned, and changing the
assignee resets the confirmation.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Add @tanstack/react-virtual and gate IssueChatThread on a 150-row
threshold. Thread shorter than the threshold still uses the direct
render path; longer threads window the merged feed via
useWindowVirtualizer with dynamic measurement and overscan, so the
document scroll continues to represent the full conversation height
without introducing a nested scrollbar.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
UX feedback split the badge by role: on the source issue the linked pill
describes a state ("Under review"), while on the productivity-review issue
itself the static pill identifies the kind of issue ("Productivity review").
Tooltip and aria-label still surface the formal name on hover.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Adds a productivityReview lookup to single-issue and list responses, plus
a ProductivityReviewBadge component that surfaces the open productivity
review on the source issue's header (with trigger/no-comment-streak
detail in the tooltip), and a compact eye glyph next to the StatusIcon
on IssueRow so operators can spot yellow tasks in any list. The review
issue itself shows a static "Productivity review" pill in the header
when its originKind matches.
Also extends the shared IssueOriginKind union with the recovery-side
kinds (harness_liveness_escalation, issue_productivity_review,
stranded_issue_recovery) so UI checks against issue.originKind type-check.
Wires the new helper through GET /issues/:id, GET /companies/:id/issues
(via issueService.list), and GET /issues/:id/heartbeat-context. Adds a
status-language Storybook story matrix for UX review.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Reviewer follow-up — pills sat slightly low after the baseline-to-middle
switch. position: relative + top: -1px raises them to optically center
with the surrounding cap height.
Inline-flex baseline behavior is browser-dependent, which made skill/agent/issue
chips in MarkdownBody and the MDXEditor hang below the surrounding text. Switch
to vertical-align: middle and drop the vertical padding so the pill sits
centered on the line with the prose around it.
Adds a skill-chip line to the MarkdownBody storybook fixture so this regresses
loudly next time.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Wrap the variables editor in a single rounded container so the
"Variables" header and the per-variable cards share one border with
internal dividers, instead of stacking as two visually separate
containers when expanded.
Add a help button to the variable instructions hint that opens a modal
explaining custom {{variable_name}} placeholders and the built-in
{{date}} and {{timestamp}} variables.
Co-Authored-By: Paperclip <noreply@paperclip.ing>