Skip to content

Prompt timeline rail for the Agents window#324131

Merged
osortega merged 17 commits into
mainfrom
osortega/prompt-timeline
Jul 7, 2026
Merged

Prompt timeline rail for the Agents window#324131
osortega merged 17 commits into
mainfrom
osortega/prompt-timeline

Conversation

@osortega

@osortega osortega commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Prompt timeline

Adds a right-edge prompt timeline rail to the Agents window that lets users scan and jump between the prompts they have sent in a chat session — adapted from GitHub's "jump to your messages" affordance.

Note

Draft — sharing early to keep history. Interaction/visuals are still being iterated (a "quiet gray-dense rail + single-mark accordion on hover" refinement is planned next).

What's included

  • Self-contained contrib under src/vs/sessions/contrib/promptTimeline, attached per-widget via IChatWidgetContrib (no vs/workbench changes, no import cycle).
  • Recency-bucketed ticks — pure, unit-tested budgetBucketPrompts, capped so each tick keeps a >=24px hit target (WCAG 2.5.8).
  • Dense-at-rest visual that expands on hover/focus, with green/red per-turn diff magnitude and a solid-blue active mark.
  • Interactive hover card — prompt text, clickable +/- diff stats that open the per-request diff, and per-file drill-down rows.
  • Per-request diffs via the provider-agnostic IChatResponseFileChangesService (agent-host server-computed turn changeset) with an editing-session fallback. Diff sides are probed with readFile so a missing agent-host checkpoint blob renders the file as a pure add/delete instead of crashing the multi-diff editor.
  • Keyboard-first commands — Go to Next/Previous Prompt, Go to Prompt… (quick pick), Review Changes for Prompt — with ARIA announcements.
  • Gated behind the new sessions.promptTimeline.enabled setting (experimental, default on) and ChatContextKeys.enabled; the rail is created/disposed reactively as the setting toggles.

Validation

  • npm run typecheck-client — pass
  • npm run valid-layers-check — pass
  • eslint on the contrib — pass
  • npm run precommit (hygiene) — pass (only non-blocking design-token suggestions remain)
  • promptBucketing unit tests

Follow-ups

  • Refine engaged interaction to the quiet gray-dense + single-mark accordion model.
  • Apply design-token suggestions (font-size / radius / spacing) once the visual is locked.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Copilot AI review requested due to automatic review settings July 3, 2026 00:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a self-contained prompt timeline rail to the Agents window (vs/sessions). It overlays a right-edge vertical rail on the chat transcript that lets users scan and jump between the prompts they have sent, showing recency-bucketed ticks with per-turn diff magnitude, an interactive hover card, and keyboard commands. It is implemented as a per-widget IChatWidgetContrib attached via ChatWidget.CONTRIBS (no vs/workbench changes), gated behind a new experimental sessions.promptTimeline.enabled setting, and reuses the provider-agnostic IChatResponseFileChangesService (with an editing-session fallback) for diffs.

Changes:

  • New promptTimeline sessions contrib: pure bucketing (promptBucketing.ts, unit-tested), a reactive model, a presentation rail, four keyboard commands, and CSS.
  • Registration wiring: side-effect import in sessions.common.main.ts, config schema + contrib push + action registration in promptTimeline.contribution.ts.
  • Build metadata: i18n resource entry and new stylelint known CSS variables.
Show a summary per file
File Description
src/vs/sessions/sessions.common.main.ts Adds side-effect import to load the new contribution.
src/vs/sessions/contrib/promptTimeline/common/promptTimeline.ts Shared constants: contrib id, setting key, min prompts, command ids.
src/vs/sessions/contrib/promptTimeline/browser/promptTimeline.contribution.ts Registers the experimental setting, pushes the widget contrib, registers actions.
src/vs/sessions/contrib/promptTimeline/browser/promptTimelineWidgetContrib.ts Per-widget lifecycle: creates/tears down the rail reactively with the setting; navigation API for commands.
src/vs/sessions/contrib/promptTimeline/browser/promptTimelineModel.ts Derives bucketed ticks + active tick from the view model; resolves per-request diffs and opens reviews.
src/vs/sessions/contrib/promptTimeline/browser/promptTimelineRail.ts DOM rail: ticks, magnify, hover card, fit/capacity measurement (flagged: toolbar keyboard nav).
src/vs/sessions/contrib/promptTimeline/browser/promptTimelineActions.ts Four keyboard commands (flagged: keybinding collision with existing chat nav actions).
src/vs/sessions/contrib/promptTimeline/browser/promptBucketing.ts Pure recency-bucketing with a hard MAX_TICKS cap; sampling/expansion helpers.
src/vs/sessions/contrib/promptTimeline/browser/media/promptTimeline.css Rail/card styling (author defers design-token cleanup as follow-up).
src/vs/sessions/contrib/promptTimeline/test/browser/promptBucketing.test.ts Unit tests for the bucketing algorithm.
build/lib/stylelint/vscode-known-variables.json Registers the new custom CSS variables.
build/lib/i18n.resources.json Registers the new module for localization.

Review details

  • Files reviewed: 12/12 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment thread src/vs/sessions/contrib/promptTimeline/browser/promptTimelineActions.ts Outdated
Comment thread src/vs/sessions/contrib/promptTimeline/browser/promptTimelineRail.ts Outdated
osortega and others added 10 commits July 6, 2026 16:24
Introduce a right-edge "prompt timeline" rail in the Agents window that
lets users scan and jump between the prompts they have sent in a chat
session, adapted from GitHub's "jump to your messages" affordance.

- Self-contained contrib under src/vs/sessions/contrib/promptTimeline,
  attached per-widget via IChatWidgetContrib (no vs/workbench changes).
- Recency-bucketed ticks (pure, unit-tested budgetBucketPrompts) capped
  so each tick keeps a >=24px hit target (WCAG 2.5.8).
- Dense marks at rest that expand on hover/focus, with green/red per-turn
  diff magnitude and a solid-blue active mark.
- Interactive hover card: prompt text, clickable +/- diff stats that open
  the per-request diff, and per-file drill-down rows.
- Per-request diffs via provider-agnostic IChatResponseFileChangesService
  (agent-host server-computed turn changeset) with editing-session
  fallback; diff sides are probed so missing checkpoint blobs render as a
  pure add/delete instead of crashing the multi-diff editor.
- Keyboard-first commands: Go to Next/Previous Prompt, Go to Prompt...
  (quick pick), Review Changes for Prompt, with ARIA announcements.
- Gated behind the new sessions.promptTimeline.enabled setting and
  ChatContextKeys.enabled; rail is created/disposed reactively.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the whole-rail expand-on-engage behavior with a per-mark
accordion. The rail now stays quiet, gray and dense at all times; only
the mark under the pointer (or keyboard focus) expands to a >=24px pill
and reveals its green/red diff, so the rail no longer becomes a column
of slim pills with large gaps when interacted with.

- CSS: drop the `.engaged` whole-rail slot expansion and the transform
  fisheye; expand only `:hover`/`:focus-visible` marks (8px -> 24px slot,
  fat pill, diff colors). Active mark stays solid blue in every state.
- Rail: remove the fisheye magnify, the engaged-state tracking, and the
  magnitude width buckets; capacity now reserves headroom for a single
  expanded mark over the dense 8px rhythm, so more prompts fit.
- Drop the now-unused --tick-scale/--tick-scale-y custom properties.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t that file

Clicking a file row in the hover card previously opened a standalone
single-file diff. Route it through the same multi-file diff we already
build for "Review Changes", passing viewState.revealData so it opens
revealed at the clicked file. Per-file and whole-prompt review now share
one multi-diff experience (and open in the modal editor when enabled),
reusing the existing MultiDiffEditorInput without any new UI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…apshot

The per-prompt diff was rendering before-turn vs the live working file, so
it showed the combined changes of all later turns. The per-turn changeset
already carries a frozen "after" snapshot; surface it and diff against it.

- Add optional `modifiedContentURI` to IEditSessionEntryDiff: the frozen
  after-state RHS content, distinct from `modifiedURI` (the live file used
  for identity / go-to-file). Opt-in, so existing consumers (e.g. the chat
  "Changed N files" summary) keep their current behavior unchanged.
- Populate it in AgentHostResponseFileChangesProvider from the changeset's
  after-content; extend its test to assert the mapping.
- PromptTimelineModel now diffs originalURI (frozen before) against the
  frozen after snapshot, so review shows only that turn's changes, while
  go-to-file still opens the live working file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduce a second rail style and let users choose via the new
sessions.promptTimeline.style setting ('compact' | 'overview').

- Extract IPromptTimelineRail as the seam the contrib consumes, and a
  shared PromptTimelineCard (hover preview) used by both styles.
- Rename the existing rail to PromptTimelinePillRail (dense pills).
- Add PromptTimelineRulerRail: the session compressed into the rail like
  the editor overview ruler — proportional marks at their transcript
  scroll positions, a scrollbar-slider you-are-here thumb, one green
  "changed code" signal (editorOverviewRuler.addedForeground), focusBorder
  active, >=24px hit targets. Detail stays in the shared hover card.
- Model exposes getScrollLayout()/onDidChangeScrollLayout for proportional
  positions (per-request offsets via currentRenderedHeight + scrollTop).
- Contrib picks the rail class reactively and reuses the enablement swap
  pattern, wiring setScrollLayout only for rails that support it.

All colors come from real theme tokens. Validated: typecheck, eslint,
layers, hygiene, unit tests (12 passing).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…odel

The overview-ruler rail placed each mark by accumulating chat items'
`currentRenderedHeight`, which the virtualized tree only sets for rows it has
actually rendered. Off-screen prompts reported `undefined` (treated as 0), so
every not-yet-rendered mark collapsed onto the same position and only spread
out as the user scrolled and rows rendered.

Read each prompt's top from the list's layout height model instead, the same
virtualization-safe source the scrollbar uses (all items have a height via the
delegate's `currentRenderedHeight ?? defaultElementHeight`). Threaded a
read-only accessor through the layers, all in the list's content-pixel space so
the viewport thumb stays aligned:

- AbstractTree.getElementTop(location): absolute top (works off-screen), sibling
  of getRelativeTop.
- ChatListWidget.getElementTop / ChatWidget.getElementTop + ChatWidget.scrollHeight.
- PromptTimelineModel.getScrollLayout() and _updateActive() now use these instead
  of accumulating currentRenderedHeight.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ruler marks were a single green "edited" signal, so a delete-heavy turn still
showed green. Render the mark as a two-tone bar instead (a green added segment
and a red removed segment sized by flexGrow from the turn's diff stat), reusing
the pill rail's seg-add/seg-del pattern. Only the non-zero side is appended, so
a pure-add turn is fully green and a pure-delete turn fully red, and a 1px
min-width keeps the minority color visible on a lopsided split. Segments go
transparent while the mark is active so the focusBorder you-are-here color wins.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nreadable

Per-turn review diffed the frozen before/after turn-checkpoint snapshots so only
that turn's changes show. But those checkpoint blobs can be absent (an added
file's original, or a pruned/restored session where whole turn checkpoints are
gone). When both sides were unreadable every item was dropped and reviewChanges
opened nothing at all.

_readableSides now still prefers the frozen snapshots but falls back to the live
working file for the modified side when the checkpoint blob is unreadable, so
review always opens with the best available fidelity. An unreadable side is
still dropped so the file renders as a clean add/delete instead of crashing the
diff editor.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The rail shipped with two selectable styles (dense pills vs overview ruler)
behind sessions.promptTimeline.style. We're standardizing on the overview-ruler
UX, so remove the pill style and the setting entirely (the on/off
sessions.promptTimeline.enabled setting stays).

- Delete promptTimelinePillRail.ts and the PROMPT_TIMELINE_STYLE_SETTING /
  PromptTimelineStyle enum.
- The contribution always builds the ruler rail; drop the style-based swap, the
  capacity->display-budget wiring, and the optional setScrollLayout indirection
  (now required on the interface).
- Model no longer tracks a dynamic display budget; ticks bucket against the
  constant MAX_TICKS (still caps very long sessions).
- Remove the pill-only CSS; the overflow rule now hides the ruler marks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ad probes

Two fixes from a council review:

- getRequestFiles: when a grouped tick edits the same file in more than one
  prompt, advance the merged entry's diffModifiedURI to the later prompt's
  after-snapshot (keeping the earliest originalURI) so the opened multi-diff
  spans the whole tick instead of only the first edit.
- _canRead: probe availability with readFile(resource, { length: 1 }) instead of
  reading whole (potentially large) file contents just to test whether a diff
  side is readable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@osortega osortega force-pushed the osortega/prompt-timeline branch from 3aa0cd7 to ffb5344 Compare July 7, 2026 17:09
Comment-only cleanup after removing the pill style and the display-budget
mechanism: drop the stale "or budget", "(like the pill rail)", and "visual
density" references so the docs match the single overview-ruler implementation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread src/vs/base/browser/ui/tree/abstractTree.ts
Comment thread src/vs/sessions/contrib/promptTimeline/browser/promptTimelineActions.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

  • Files reviewed: 20/20 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment thread src/vs/sessions/contrib/promptTimeline/browser/promptTimelineModel.ts Outdated
Comment thread src/vs/sessions/contrib/promptTimeline/browser/promptTimelineWidgetContrib.ts Outdated
osortega and others added 4 commits July 7, 2026 11:49
Put sessions.promptTimeline.enabled behind the experimentation service via the
`experiment: { mode: 'startup' }` property (the sanctioned way for core
TS-registered settings; the registry auto-adds the onExP tag). Default is now
false so the rail is off until the experiment (or the user) turns it on.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…order

Review fixes:
- Remove the redundant GoToNext/GoToPrevious commands: they duplicated and
  collided with the built-in chat nextUserPrompt/previousUserPrompt keybindings
  (same chord, weight, when). Drop the two actions, their command ids,
  contrib.navigate(), and model.getSiblingTick(). GoToPrompt and ReviewChanges
  stay.
- Fix the active-mark fallback: when scrolled above the oldest prompt, highlight
  the oldest tick (ticks.at(0)), not the newest (ticks.at(-1)).
- Replace the getComputedStyle probe + unreverted host `position` mutation with a
  lifecycle-scoped `.prompt-timeline-host` class removed on teardown.
- Give the overview-ruler toolbar a proper keyboard model: aria-orientation,
  roving tabindex (a single Tab stop) and Arrow/Home/End navigation between marks.

Also assign `_sessionResource` in the constructor instead of a field initializer:
it reads `this.widget`, which under useDefineForClassFields is not yet assigned
when field initializers run (fixes the define-class-fields-check / TS2729).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Agent-host sessions don't record per-turn timestamps; the transcript is
reconstructed via ChatModel.addRequest(), which stamps Date.now(), so every
prompt showed ~the current time. Since the real send times aren't available to
recover, drop the absolute time from the hover card and the "Go to Prompt"
picker. Grouped ticks still show their prompt count; the prompt text and diff
stats (both reliable) are unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e modifiedContentURI)

- Remove the ReviewPromptChangesAction command: per-prompt review is already
  available from the hover card, so the palette/keyboard entry point was
  redundant. Drops the command id and the now-unused contrib.getActiveTick() /
  contrib.reviewChanges() bridge (model.reviewChanges stays, wired to the card).
- Rename IEditSessionEntryDiff.modifiedContentURI -> modifiedSnapshotURI: it's the
  frozen after-turn snapshot content, and "snapshot" reads clearer next to
  modifiedURI (the live file) than "content".
- Clarify in comments that this field is distinct from the agent-host checkpoint-
  ref readability fix (#323932): that made the snapshot blobs readable; this
  carries which snapshot to diff against so a per-turn review shows only that
  turn's changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@osortega osortega marked this pull request as ready for review July 7, 2026 21:05
@vs-code-engineering

Copy link
Copy Markdown
Contributor

📬 CODENOTIFY

The following users are being notified based on files changed in this PR:

@benibenj

Matched files:

  • src/vs/base/browser/ui/tree/abstractTree.ts

@osortega osortega enabled auto-merge (squash) July 7, 2026 21:07
@roblourens

Copy link
Copy Markdown
Member

Why is this done as an agents window-specific contrib, rather than a generic piece of UI that the editor window could also use?

@osortega

osortega commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Why is this done as an agents window-specific contrib, rather than a generic piece of UI that the editor window could also use?

That is the idea, but for now I want to scope it small until it's in a very polished state to integrate it into core

@osortega osortega merged commit 04c9ab5 into main Jul 7, 2026
29 checks passed
@osortega osortega deleted the osortega/prompt-timeline branch July 7, 2026 22:17
@vs-code-engineering vs-code-engineering Bot added this to the 1.129.0 milestone Jul 7, 2026
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.

3 participants