Skip to content

Rebuild the interface around Replay, Compare and Insights - #7

Merged
renrenmimi merged 6 commits into
mainfrom
feat/task-oriented-ui
Sep 3, 2026
Merged

renrenmimi merged 6 commits into
mainfrom
feat/task-oriented-ui

Conversation

@renrenmimi

@renrenmimi renrenmimi commented Sep 3, 2026 •

Copy link
Copy Markdown
Owner

Turns the three-column dashboard into three named tasks — Replay, Compare,
Insights — without removing anything it used to tell you.

The old screen showed the working tree, the commit detail and the insights panel
all at once, with a global search field, a source bar, a notice strip and a 108px
control area around them. Everything the application knows was on screen
simultaneously, which meant nothing on screen was the point of it. It also opened
on commit 16 of 16, so a first-time visitor arrived at the finished repository
rather than at something they could step through.

Not merged. Nothing about the deployment, the environment variables or the
production token has been touched.

The structure now

View The question it answers
Replay What did this repository look like at this commit, and what did this commit change?
Compare What is different between these two points?
Insights How did the size and the structure change across the loaded history?

Replay is the history list plus one main column. The commit's identity, a
one-line summary of what it changed, and the player sit above Repository (the
file tree at that commit) and Changes (the diff). Both sub-views share that
heading and that player, so moving between the tree and the diff changes only the
content underneath.

Arriving now puts the playhead on the earliest commit in the loaded range,
paused. A link that names a commit still wins, and Latest is one click away.

Home

Replay at the first step

Replay at commit 10 of 16

A real diff in Changes

Compare

Insights

Dark theme — its own palette, not an inversion (its reading surface is lighter
than its page):

Replay, dark

Compare, dark

1280×720, where the player and the file tree have to be on screen together:

1280x720

390px — the history becomes a named drawer, and the current step stays visible:

Replay at 390px History drawer at 390px Compare at 390px

The rate-limited error state and a live-fixture repository, for the states that
are not the happy path:

Rate limited

Live repository

Try it

Preview: https://repo-time-machine-1m2j26xf9-renrenmimis-projects.vercel.app
— built automatically by the existing Vercel integration for this branch. It sits
behind Vercel's deployment protection, so it answers 302 to anyone not signed
in to the account; I have not changed that setting. Locally there is no such
gate:

npm ci
RTM_FIXTURE_MODE=1 npm run dev     # fixtures, no GitHub requests at all

The suggested path, in order:

  1. Home → Explore the demo. You land on commit 1 of 16, paused.
  2. Next a few times. The tree fills in and the change markers move with it.
  3. Play, then Pause. Note the player and the tree are on screen together.
  4. Hover a changed file → Diff. It opens that file's patch in Changes, on
    the same commit, and playback stops so the evidence cannot move.
  5. Compare. From/To are proposed and run once. Move either selector — the
    old result is put away, not relabelled — then press Compare.
  6. Insights → any Inspect commit. Back in Replay at that commit.
  7. Browser Back twice. You are where you were.
  8. Dark, then Light. Nothing reloads. Reload the page: no flash.

Nothing above touches github.com. For a live repository, drop
RTM_FIXTURE_MODE and open any public one.

Where everything went

Nothing was cut to make room. Every feature and every caveat has a new address.

Before Now
Landing input + validation + URL loading Home screen field; Change repository in the top bar afterwards
Start over Change repository → Start over from the home screen
Synthetic demo, source, GitHub quota One source line under the repository name; GitHub usage disclosure beside it
Loading / retry / error / empty states Same states, per stage, with the technical detail behind What exactly happened
Commit selection, play/pause, speed, keyboard, position slider Replay's compact player (78px, was 108px)
Commit list, load older history Replay's history column; also Insights
Working tree, expansion, filter, selection, change markers Replay → Repository
Commit meta, file changes, patches, renames, GitHub links Replay's heading + Replay → Changes
exact / rebuilt / ≈ n gaps badge A line above the tree: Snapshot / Reconstructed / Approximate, with the count and a jump to the source commit
Growth, file-type mix, milestones, commit table Insights, as a page; Inspect commit returns to Replay
Milestone cards above the file list One line on the commit, evidence behind a disclosure; the full list in Insights
Tag/commit pickers, swap, relation Compare's From/To + Swap, then Compare
Net file changes, patches, both ends' structure, commits in range Compare → File changes / Commits / Repository stats
Share URL, Back/Forward Same repo/c/base/head, plus view; old links unchanged
Reduced motion, virtualisation, cache, stale cancellation Unchanged; see the tests below

Behaviour changes worth arguing about

Arriving at the first commit rather than the last. Stepping forward from the
beginning is the point of the tool. ?c= still wins, and Latest is one click.

Choosing a comparison no longer spends requests. Selecting an end updates a
draft; Compare runs it. Previously each pick and each swap fired a request, and
the previous result was left sitting under the new endpoints. Measured: two
selector changes plus a swap went from +2 requests to +0.

Opening Compare at the first commit proposes the whole loaded range, not a
commit compared with itself — which would answer nothing.

Milestone markers on the player's track are no longer clickable. They were
tabindex="-1" buttons, so mouse-only. Jumping to a milestone is a named action
in Insights (Inspect commit) and in the history list.

Two things this dropped, and put back

Writing the "where everything went" table above is what found them, which is
the argument for writing it.

The repository's own metadata. The old insights panel showed the
repository's description, creation date and star count, and the link to it on
GitHub. The link mattered most — it was the only route from here to the actual
repository. Insights now opens with About this repository: description,
commit total, created and last-pushed dates, stars, forks, licence, GitHub's own
guess at the language (labelled as GitHub's), archived/fork flags, and the link
out. Built-in data gets no star counts, because they would be invented, and no
link, because there is nothing to open.

The disclosure, said twice. Restoring that block put the
"curated, synthetic history" sentence on the page a second time, next to the
source line that already carries it. Repeating a disclosure does not make it
more true. The block now says what the source line does not — that there is
nothing to open on GitHub, and why — and a test pins the sentence to exactly one
occurrence per page.

A regression this caught

The milestone path probes read the newest tree in range out of a snapshot map and
gave up if it was empty. That map happened to be populated when the playhead
defaulted to the newest commit. Starting at the earliest commit made it empty, so
the probes silently stopped running and every milestone lost the precision
they buy. Found by diffing the request profile against main: 45 requests → 35,
with probe at 10 → 0.

They now await that tree, which the checkpoint plan was going to fetch anyway and
the tree cache shares rather than duplicates. The profile matches main exactly
again, and two tests pin it.

Honesty, moved to where it is read

  • Loading source… until the server's dataSource arrives — no announcing a
    live repository before anything has been fetched.
  • Built-in reads Built-in demo · 0 GitHub requests, in one place. Not "0 API
    requests": the browser does call this application's own routes.
  • An unknown quota reads Unknown, never 0; an undated reading says
    Age unknown, never "just now".
  • A failed tag request is told apart from a repository with no tags.
  • not-found no longer claims the repository does not exist — GitHub answers
    identically for a private one.
  • A missing patch says it is not the same as an unchanged file. An aggregated
    total says it is a total, not the size of a net diff, and that this is a limit
    of the data rather than of Git.
  • A behind comparison with no ahead commits is not "no difference".
  • The loaded range is stated twice, in two different sentences, because the list
    and the player answer different questions.
  • Milestones are counted as rules: "15 milestones across 9 commits".
  • Chart axes say they are spaced by commit order, not by elapsed time.

Verification

Run on this branch, against a production build, with no token and no network:

Check Result
npm run typecheck pass
npm run lint pass, no rule disabled or suppressed
npm test 622 pass (was 533)
npm run build pass
npm run test:e2e 235 pass across desktop, mobile and reduced-motion (was 158 pass + 1 failure)
npm run token-canary pass — fake token absent from every page, header, chunk and payload
CI privacy audit (all three greps) pass

The one pre-existing e2e failure on main was a strict-mode ambiguity in
compare.spec.ts (the same commit subject in both the picker and the commit
list); it passed in isolation. The replacement scopes its locator to the picker.

No job, assertion or skip was added to make this pass. Test counts went up.

Requests for one session

Browser network log, production build, built-in demo. 0 requests reach
github.com.

Step Requests to this application
Initial load, fully settled 45 — identical to main, endpoint for endpoint
Sub-views, opening a diff, Insights, two theme changes +0
Opening Compare (runs the proposal once) +1
Moving both selectors and pressing Swap +0 (was +2)
Pressing Compare +1

Layout, measured

At 1280×720: chrome above the content is 148px (bar 56 + title row + view
tabs), the player ends at 395px and the file tree starts at 427px — both on
screen together. The brief asked for ~140px; 148 is what it costs without
shrinking any text below 12px, so I left it there rather than compress the type.

No horizontal document overflow at 1440, 1280, 1024, 390 or 360px, asserted at
each width with a diff open, in Compare and in Insights. The same holds at
720×450 and 640×400 — the equivalents of 200% and 320% browser zoom — where the
commit, the player, the history and the view tabs all remain present.

Contrast, measured

Every text role against every surface it is used on, in both themes: 24 pairs,
all passing
(4.5:1 text, 3:1 non-text). Tightest is the control border at
3.11:1 against the page. Computed from the live tokens rather than from the
design table.

Known limitations

  • The 148px of chrome is 8px over the target, as above.
  • The Repository stats table scrolls sideways below 900px. It is a table of
    numbers in its own named container, which seemed better than truncating them.
  • History filtering matches commit subject, author and sha — the metadata already
    in the browser. It does not search file contents, and says so where it finds
    nothing. Adding that would be a request per commit.
  • No live smoke test was run. npm run test:live reaches real repositories, and
    this deployment has no token, so it would spend the 60/hour anonymous budget to
    re-check code paths the fixtures already cover.
  • Contrast was computed, not audited with a screen reader. Keyboard paths are
    covered by tests; I have not tested with VoiceOver or NVDA.
  • Zoom was checked by shrinking the viewport, which is how a browser reflows at
    200% but is not literally the same as pressing ⌘+.
  • docs/screenshots/review-*.jpg adds ~1.1MB to the repository. They are the
    record of this review; the generated PNG set stays out of git.

Not done, deliberately

No merge, no push to main, no force-push, no history rewrite. No change to
vercel.json, the environment variables or the production token. No token was
created, read or displayed. No dependency was added — runtime dependencies are
still next, react, react-dom and server-only, and the interface now
downloads no webfont at all.

🤖 Generated with Claude Code

renrenmimi and others added 3 commits September 3, 2026 01:48
The interface was one screen with three permanent columns — working tree,
commit detail, insights — plus a global search field, a source bar, a notice
strip and a 108px control area at the foot. Everything the application knows
was on screen at once, which meant nothing on screen was the point of it.

Three questions, one at a time
------------------------------

`Replay`, `Compare` and `Insights` are now top-level views, named for what
somebody is trying to find out. Replay is two columns: the commit history, and
the repository at the selected commit. Its `Repository` and `Changes` sub-views
share the commit heading and the player above them, so moving between the tree
and the diff changes only the content underneath and never loses the place.

Arriving on a repository now puts the playhead on the *earliest* commit in the
loaded range, paused. Stepping forward from the beginning is the whole point of
the tool; arriving at the finished repository showed a result instead. A link
that names a commit still wins over that default, and `Latest` is one click.

The commit's identity, a one-line summary of what it changed, and the player
now sit above the content they describe, in that order. Milestones are one
compact line with the evidence behind a disclosure, rather than four explanation
cards standing in front of the changes they annotate.

Choosing a comparison no longer spends requests
-----------------------------------------------

The two ends are `From` and `To`, equally weighted, with `base` and `head` in
small print. Selecting an end updates a draft; pressing `Compare` is what runs
it. Previously every pick and every swap fired a request, so a visitor adjusting
both ends paid for a comparison nobody wanted to see — and the old result was
left sitting under the new endpoints. Now, whenever the selection and the loaded
result disagree, the result is put away rather than relabelled.

Opening it from the replay proposes a pair and runs it once: the step before the
current commit and the current commit. At the first commit there is no step
before it, so the proposal is the whole loaded range — never a commit compared
with itself, which would answer nothing.

Honesty, moved to where it is read
----------------------------------

Nothing was removed to make room. The reconstruction state moved from a badge
under the file list to a line above it, where it qualifies the thing it
describes; the source and the GitHub quota moved into one place under the
repository's name; the loaded range is stated in the history list and under the
player in two different sentences, because they answer two different questions.

`Loading source…` is now shown until the server's `dataSource` arrives, instead
of announcing a live repository before anything has been fetched. A failed tag
request is told apart from a repository with no tags. An unknown quota reads
`Unknown`, never `0`. A missing patch says it is not the same as an unchanged
file. A `not-found` no longer claims the repository does not exist, because
GitHub answers identically for a private one.

Also fixed here: the milestone path probes read the newest tree in range from a
snapshot map and gave up if it was empty. That happened to be populated when the
playhead defaulted to the newest commit. Starting at the earliest commit made it
empty, so the probes silently stopped running and every milestone lost the
precision they buy. They now await that tree, which the checkpoint plan was
going to fetch anyway and the tree cache shares rather than duplicates.

Structure and mechanics
-----------------------

- `view` joins `repo`, `c`, `base` and `head` in the URL. A pair of endpoints is
  still what makes a link a comparison, named or not, so every link handed out
  before now opens exactly as it did; an unrecognised value falls back safely.
- The view lives in the controller, so switching it cannot discard loaded
  history and can pause playback in the same update. Leaving the replay pauses
  it; returning finds the same commit and the same speed, still paused. Opening
  a diff or an explanation pauses too, so evidence cannot move out from under
  the reader.
- Every button, tab, field and panel composes from one `controls.module.css`,
  and one `Overlay` supplies the shared modal behaviour — Escape, focus trap,
  scroll lock without losing the scroll position, and focus returned to the
  trigger.
- Light-first palette with a dark theme built as its own scale rather than an
  inversion, and Light/Dark/System applied before the first paint. Charts read
  their colours from the theme tokens instead of hard-coded hexes.
- Below 900px the history becomes a drawer; below 768px the global actions move
  into a menu rather than shrinking their labels.

Virtualisation, caching, request budgeting, stale-response cancellation, the
checkpoint-plus-diff reconstruction, the patch and file-list caps and the
server-only token boundary are all unchanged. Interaction that reads
already-loaded data — switching view, switching sub-view, opening a diff,
changing the theme — issues no request at all.

The interface, the state it needs and the tests that pin it are one change: the
tests address the same components by their new roles and copy, so splitting them
apart would produce commits that do not build or do not pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…uest counts

The README described a single screen with a scrubbable timeline along the
bottom, which is no longer what the application is. It now opens with the three
views and the question each one answers, and the hero image is the built-in demo
at commit 10 of 16 in the new layout.

Added or corrected:

- how the loaded range is worded, and why the history list and the player say it
  differently — they answer different questions;
- the compare contract: choosing the ends and reading the answer are two steps,
  and what that saves;
- a table of the URL parameters, including `view`, and why a pair of endpoints
  is still what makes a link a comparison;
- measured request counts for one session, endpoint by endpoint, against a
  production build — including the two steps that now cost nothing;
- measured contrast: 24 text-on-surface pairs in both themes, with the tightest
  named rather than a claim that it is all fine;
- the theme section, and why dark is its own palette;
- the milestone count is a count of rules, not of commits or of phases;
- "GitHub requests" rather than "API requests" for the demo, because the browser
  does call this application's own routes.

The screenshot script now covers each view at five widths in both themes. It
still reads only the built-in demo and the fixtures, so it never touches a real
repository.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fourteen JPEGs of the new structure — the home screen, the replay at its first
and a middle step, a real diff, the comparison, the insights page, both themes,
1280x720, and 390px including the history drawer — plus the rate-limited error
state and a live-fixture repository.

They are committed as `.jpg` because `docs/screenshots/*.png` is the generated
set that stays out of git, and these are the record of what the change looked
like when it was reviewed. All of them come from the built-in demo or the
fixtures; none reads a real repository.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 3, 2026 08:53
@vercel

vercel Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
repo-time-machine Ready Ready Preview Sep 3, 2026 9:07am UTC

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02d24f4ac7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +143 to +145
(index: number, options: { push?: boolean } = {}) => {
if (options.push) pushNext.current = true;
dispatch({ type: 'seek', index });

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 Badge Arm history pushes only when a seek changes position

When a push-enabled seek targets the already selected commit—for example, clicking the active row in the History column—the reducer returns without notifying React, so this flag remains set. The next operation that actually changes the commit, including an ordinary playback tick that should replace the current URL, consequently calls pushState; Back then stops at a playback-generated entry. Check that the target differs from the current index before arming the push, or clear the flag when dispatch produces no state change.

Useful? React with 👍 / 👎.

200% browser zoom on a 1440x900 screen leaves about 720x450 CSS pixels; 320%
leaves about 640x400. Both were checked by hand while building this, which is
exactly the kind of thing that quietly stops being true.

At either size the commit, the player, the way into the history and the view
tabs are all still present, and nothing drags the document sideways — including
with a diff open and on the comparison's statistics table, which is the one
element allowed its own horizontal scroll.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rebuilding the interface dropped what the old insights panel said about the
repository itself: its description, its creation date, its star count, and the
link to it on GitHub. The link mattered most — it was the only way from here to
the actual repository, and losing it was not a decision anybody made.

`Insights` now opens with `About this repository`: the description, the commit
total on the default branch, created and last-pushed dates, stars and forks,
the licence, GitHub's own guess at the primary language (labelled as GitHub's),
whether it is archived or a fork, and the link out.

Kept apart from everything below it, because these describe the repository while
the rest of the page describes only the loaded range — which the block under it
then says explicitly.

Nothing is stated twice: the section is named for what it is rather than
repeating the repository's name from the H1 above it, and the default branch is
left to the source line that already names it. Built-in data gets no star or
fork counts, because they would be invented, and no link, because there is no
repository to open — it gets the synthetic-history disclosure instead. An
unknown commit total reads `Unknown`, not `0`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Restoring the repository metadata put the "curated, synthetic history — not a
live GitHub repository" sentence into `Insights` as well, where the source line
under the repository's name already carries it word for word. Repeating a
disclosure does not make it more true; it makes the page longer and trains
people to skip it.

The `About this repository` block now says the thing the source line does not:
that there is nothing to open on GitHub, and why. A test pins the sentence to
exactly one occurrence per page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@renrenmimi
renrenmimi merged commit 9e4001f into main Sep 3, 2026
4 checks passed
@renrenmimi
renrenmimi deleted the feat/task-oriented-ui branch September 3, 2026 17:54

This branch was successfully deployed

1 active deployment
Preview — 8cbc5add Deployed Sep 3, 2026 by vercel[bot]
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