Skip to content

CPLAT-11489: daily Produced pane reads as a timeline, tabbed by kind - #157

Merged
gavin-jeong merged 1 commit into
masterfrom
CPLAT-11489-daypane-tabs
Aug 14, 2026
Merged

gavin-jeong merged 1 commit into
masterfrom
CPLAT-11489-daypane-tabs

Conversation

@gavin-jeong

Copy link
Copy Markdown
Collaborator

JIRA: https://sendbird.atlassian.net/browse/CPLAT-11489

Follow-up to #156. The daily view's "Produced" pane grouped every output into kind sections — Pull Requests, then Jira Issues, then Artifacts, then Plans. Two things are wrong with that on a real day.

1. Sections are not how a day happened

The pane answers "what came out of this day", but grouping destroys the one ordering a day actually has. "What happened after that PR went up" is unanswerable from four stacked lists.

Rows now come out in the order the outputs first appeared, each stamped with that time, kinds interleaved:

  [All 570]  PRs 424   Jira 393   Artifacts 7   Plans 7
Produced (570)  ↵:jump to first mention  o:open  y:copy  x:actions
> ~12:00  ◆ daily-view-plan            a1b2c3 · ccx
   13:00  ○ CPLAT-11459                a1b2c3 · ccx
   14:00  ◆ artifact:abcd  published   d4e5f6 · ops-k8s
   15:00  ● sendbird/ccx#155  OPEN     a1b2c3 · ccx

Chronological is not the natural insertion order. A session's Refs are sorted first-seen descending (session.SortRefs), so without an explicit sort the pane reads backwards inside every session — dropping the kind sort alone would not have produced a timeline.

Outputs with no recorded entry — plan slugs, which have no transcript entry at all, and refs extracted before FirstSeen existed — fall back to the producing session's time. A zero timestamp would sink them to the bottom of the day and read as "produced last", which is a claim the row cannot support. Those stamps are marked ~ so the row does not assert a minute it does not know. A first mention landing on another date (a long-lived session carrying a ref in) spells the date out rather than showing a bare time that belongs to the wrong day.

2. The later sections were unreachable

Measured on a real day: 570 outputs — 424 PRs, 393 Jira, 7 artifacts, 7 plans. With kinds stacked as sections, Plans sits ~500 lines below the fold, reachable only by scrolling past everything else. Sections are the wrong container at that scale.

Kinds are now tabs: All plus one per kind the scope produced, each carrying its count so the bar doubles as the day's rollup — you can see there were 424 PRs without opening that tab.

tab / shift+tab switches. Those are the keys that already rotate preview modes on a session row, and a day row has none to rotate — rowSupportsPreviewModes() already said so, which is why the number keys are suppressed there too. As a side effect, tab on a day row no longer silently rotates sessPreviewMode; that was invisible state corruption that only surfaced after moving back to a session row.

Every tab keeps the one chronology, and the selected tab is sticky as you walk between dates, so "what PRs did each day produce" stays one keypress per day. A day with none of that kind says so rather than silently falling back to All — a silent fallback would break exactly the comparison the stickiness exists for.

Three traps this had to get right

  • a.dayOutputRows holds the filtered slice, because that is what dayOutputsCursor indexes. Rendering a filter while indexing the full list would leave enter/o/y/x acting on a different output than the highlighted one. The cursor resets on a tab switch for the same reason.
  • The preview cache key carries the tab. cycleDayOutputTab clears CacheKey and renders, but the next updateSessionPreview() — any navigation, any refresh tick — recomputes it. Without the tab in the key that call sees a match, returns early, and the pane silently reverts to the previous tab's content.
  • The active tab stays in the bar even on a day that produced none of it. dayOutputTabsFor otherwise drops it and the index lookup falls back to All.

Testing

gofmt and go vet clean; full suite green on a clean rebase onto master.

Every fix was verified by disabling it individually and confirming the corresponding test fails:

disabled test failure
the selectedOwnsDayPane() branch in km.Session.Preview tab after one press = "", want the PR tab + sessPreviewMode = 1, want it untouched
the explicit chronological sort timeline order = [middle late early], want [early middle late]
the session-time fallback untimed ref sunk to the bottom instead of sitting at its session's time
the cursor reset on tab switch cursor = 2, want 0
the tab in the cache key cache key is unchanged across a tab switch — the pane will not repaint
keeping the active tab in the bar empty kind tab fell back to All

Two notes on the test work itself:

  • TestDayPreviewTabKeySwitchesKind drives app.Update(tea.KeyTab), not handleDayPreviewKeys directly. tab is consumed by km.Session.Preview long before the focused-preview handlers run, so a handler-level test would pass while the actual keypress does nothing. The existing TestDayPreviewKeysIgnoreUnrelatedKeys asserts the day pane does not handle tab, which is what forced the switch into the right place.
  • The cursor-reset assertion initially passed even with the reset removed — the render's range clamp hid it. The fixture now gives the target tab more rows than the stale index, so a missing reset leaves the cursor on a real but different PR and the test fails.

The day pane grouped every output into kind sections, so a day read as four
stacked lists instead of as the day it was — and on a real day (570 outputs)
the later sections sat hundreds of lines below the fold, reachable only by
scrolling past everything else.

Rows now come out in the order the outputs first appeared, each stamped with
that time. Kinds became TABS: All plus one per kind the scope produced, each
carrying its count so the bar doubles as the day's rollup. tab/shift+tab
switches — the keys that rotate preview modes on a session row, which a day
row has none of (rowSupportsPreviewModes already said so). As a bonus, tab
on a day row no longer silently rotates sessPreviewMode.

Chronological is NOT the natural insertion order: a session's Refs are
sorted first-seen DESCENDING, so the timeline only exists because of the
explicit sort. Outputs with no recorded entry (plan slugs; refs extracted
before FirstSeen existed) fall back to the producing session's time rather
than a zero that would sink them to the bottom and read as "produced last" —
those stamps are marked `~` so the row does not assert a minute it does not
know. A first mention on another date spells the date out.

Three traps this had to get right:
  - a.dayOutputRows holds the FILTERED slice, because that is what
    dayOutputsCursor indexes — rendering a filter while indexing the full
    list would make enter/o/y/x act on a different row than the highlighted
    one. The cursor resets on a tab switch for the same reason.
  - the preview cache key carries the tab; without it the next
    updateSessionPreview() sees a matching key and the pane reverts.
  - the active tab stays in the bar even on a day that produced none of it,
    so walking dates under one kind filter keeps comparing like with like.
@Kairo-Kim Kairo-Kim added the auto-review/approved Auto-approved by the Slack auto-reviewer bot label Aug 14, 2026

@jinsekim jinsekim left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM!

@upwind-code-us

upwind-code-us Bot commented Aug 14, 2026

Copy link
Copy Markdown

Upwind Upwind Code Scan - ✅ Proceed with Deployment

0 newly introduced vulnerabilities · 0 resolved · 2 total in this PR vs master

🔶 1 High | 🟢 1 Low

View full analysis in Upwind Console

Scan completed in 9m 24s

Scan history (1 scan)
Commit Scanned at New Resolved Net
2afa7ae < 2026-08-14 07:50 UTC 0 0 0

Last scanned: 2afa7ae · 2026-08-14 07:50 UTC

@upwind-code-us

upwind-code-us Bot commented Aug 14, 2026

Copy link
Copy Markdown

Upwind Upwind IaC Scan - ✅ Proceed with Deployment

0 newly introduced misconfigurations · 0 resolved · 0 total in this PR vs main

View full analysis in Upwind Console →

Scan completed in 8m 55s

Scan history (2 scans)
Commit Scanned at New Resolved Net
2afa7ae 2026-08-14 07:50 UTC
2afa7ae < 2026-08-14 07:50 UTC 0 0 0

Last scanned: 2afa7ae · 2026-08-14 07:50 UTC

@gavin-jeong
gavin-jeong merged commit 96f6159 into master Aug 14, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-review/approved Auto-approved by the Slack auto-reviewer bot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants