Skip to content

feat(agents): hover details on tick beats + arrow-key navigation between snapshots - #267

Open
cardosofede wants to merge 6 commits into
mainfrom
feat/agent-review-ux
Open

cardosofede wants to merge 6 commits into
mainfrom
feat/agent-review-ux

Conversation

@cardosofede

Copy link
Copy Markdown
Contributor

Summary

Two review-ergonomics improvements to the agent run screen:

Tick strip hover card — hovering (or keyboard-focusing) a beat in the tick strip now opens a card with:

  • tick number, timestamp and state (Actions ran / Action failed / No actions / Not logged)
  • the journal's summary line
  • every deed with ✓/✗ and its error text (capped at 6, "+N more")

The card is portalled and fixed-positioned so the strip's horizontal scroller can't clip it, and it clamps/flips to stay in the viewport. Replaces the native title tooltip (kept as aria-label).

Arrow keys between snapshots — the tick overlay now has ‹ › buttons, an N/M position counter, and ←/→ keyboard navigation through the session's ticks.

  • Steps follow journal order (skips gaps in numbering), disabled at either end
  • Ignored while typing in inputs or with modifier keys held
  • Steps replace history, so Back still closes the overlay in one press (WorkspaceUrlAdapter.set gained an optional { replace } override)
  • Neighbouring snapshots are prefetched so a step renders instantly

Test plan

  • New tests: TickSpine.test.tsx (hover card content, deed cap, pre-log ticks, focus), TickOverlay.test.tsx (adjacentTicks, arrows, modifiers/inputs, buttons, close)
  • Full frontend suite: 2273 passed; tsc -b, lint:ci, build green
  • Verified live on brigado / PMM King BTC-BRL S5 (55 ticks): card renders on failed and edge beats, arrows step the overlay, Back closes it

🤖 Generated with Claude Code

…hots

- TickSpine: hovering or focusing a beat opens a portalled card with the
  tick's time, state, journal line and every deed (errors in red), capped
  at 6 with '+N more'. Replaces the native title tooltip.
- TickOverlay: the tick view gets prev/next buttons, an N/M counter and
  ArrowLeft/ArrowRight navigation (ignored while typing or with modifiers).
  Steps replace history so Back still closes the overlay in one press, and
  neighbouring snapshots are prefetched.
- WorkspaceUrlAdapter.set takes an optional { replace } override.
@greptile-apps

greptile-apps Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[Medium risk] Adds hover cards and keyboard navigation to tick timeline.

The PR is not yet safe to merge because navigation can still lead to a journal tick without a snapshot.

Findings

  1. P1 Journal ticks can lack snapshots ▶
  2. P2 Keyboard focus becomes invisible ▶
  3. P2 Ring overlaps another tick ▶
  4. P2 Focused card covers snapshot ▶
  5. P2 Tall cards can leave viewport ▶
  6. P2 Gap clicks do nothing ▶
  7. P2 Focused card disappears ▶

Summary

The PR adds tick detail cards and snapshot navigation with adjacent prefetching and history replacement. Since the previous review, it changes the tick strip to animate visuals inside fixed hit targets and adds pointer handling for wrapped rows. Two new interaction mismatches remain in the strip.

Reviews (5) · Last reviewed commit: "Merge pull request #269 from mlguys/feat..."

Comment on lines +52 to +57
const ticks = useMemo(
() =>
journalContent ? parseJournal(journalContent).ticks.map((t) => t.tick) : [],
[journalContent],
);
const { prev, next, index } = adjacentTicks(ticks, tick);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Journal ticks can lack snapshots

When a run is blocked, the engine records a journal tick but does not write a snapshot. Arrow navigation includes that tick anyway. Selecting it makes the snapshot request return 404, so the overlay shows “Select a snapshot to view details” instead of a snapshot. Navigation should use ticks with available snapshots or explain journal-only ticks.

Knowledge Base Used: Frontend application

Comment on lines 179 to +183
onClick={() => onSelectTick(entry.tick)}
onMouseEnter={(e) => showCard(entry.tick, e.currentTarget)}
onMouseLeave={hideCard}
onFocus={(e) => showCard(entry.tick, e.currentTarget)}
onBlur={hideCard}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Focused card covers snapshot

Selecting a keyboard-focused beat does not blur it, so its hover card stays open when the tick overlay appears. The card has a higher z-index than the overlay and can cover part of the snapshot until focus moves elsewhere. Dismiss the card when the beat is selected or the overlay opens.

Comment on lines +261 to +264
const above = vh - anchor.bottom < CARD_MIN_ROOM && anchor.top > vh - anchor.bottom;
const position = above
? { left, bottom: vh - anchor.top + CARD_GAP }
: { left, top: anchor.bottom + CARD_GAP };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Tall cards can leave viewport

The card flips above a beat only when less than 240 pixels remain below it, but six deeds with summaries and error text can make the card much taller. On a short viewport, it can open below the beat and clip the details. Use the card’s measured height for placement or constrain its height and allow scrolling.

@cardosofede
cardosofede requested a review from mlguys October 3, 2026 06:47
The beat under the pointer (or focus) scales to 2x width and 1.2x height,
covering the gaps beside it. A transform rather than a width change, so
neighbours do not reflow under the cursor.
Each beat is now a fixed 16px button with the bar drawn inside it, so the
targets sit edge to edge while the bars get an 8px gap. Hovering grows
only the inner bar and gives it a 2px contour (the selected beat keeps its
primary ring), without reflowing the neighbours.
Comment thread frontend/src/components/agent/lab/TickSpine.tsx Outdated
The hovered beat's real width and height animate (8x20 -> 14x26, 200ms
ease-out), so its neighbours slide over and the 8px gap stays fixed,
instead of a fixed slot with the bar growing inside it. Hover is tracked
on the strip: a pointer in a gap keeps the nearest beat, so sliding along
the row never flickers. Respects prefers-reduced-motion.
}
className={`flex items-center gap-1 ${
// The vertical padding is room for the selected beat's ring, which an
className={`flex items-center gap-2 ${

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Gap clicks do nothing

The pointer tracker shows the nearest beat’s card while the pointer is in the new 8px gap, but only the beat buttons handle clicks. Clicking where the card says “Click to open this tick” therefore does nothing. Keep the clickable targets contiguous or make a gap click select the beat shown on the card.

ref={stripRef}
data-testid="tick-spine"
onPointerMove={trackPointer}
onPointerLeave={hideCard}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Focused card disappears

When a beat has keyboard focus, leaving the strip with the pointer clears its card. The beat remains focused, so onFocus does not fire again and its details stay unavailable until focus moves away and back. Preserve the focused beat’s card when pointer hover ends.

@mlguys

mlguys commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Hey @cardosofede, I added a fix for hovering effect to make it better here #269

@mlguys

mlguys commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

LGTM!

onBlur={hideCard}
data-beat-hovered={isHovered || undefined}
data-beat-selected={isSelected || undefined}
className="relative h-5 w-2 shrink-0 rounded-sm outline-none"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Keyboard focus becomes invisible

When a beat has keyboard focus, moving the pointer to another beat or out of the strip moves or clears its visible ring. The focused button also has outline-none, so users can no longer see which beat Enter will open.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +210 to +212
const offset = hoveredIndex < 0 || index === hoveredIndex
? 0
: index < hoveredIndex ? -3 : 3;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Ring overlaps another tick

When a selected beat sits next to the hovered beat, the new offset can move its ring over the preceding beat’s fixed button. Clicking that visible part of the selected ring opens the preceding tick instead, making the strip misleading to use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants