Rebuild the interface around Replay, Compare and Insights - #7
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 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".
| (index: number, options: { push?: boolean } = {}) => { | ||
| if (options.push) pushNext.current = true; | ||
| dispatch({ type: 'seek', index }); |
There was a problem hiding this comment.
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>
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.
The structure now
Replayis the history list plus one main column. The commit's identity, aone-line summary of what it changed, and the player sit above
Repository(thefile tree at that commit) and
Changes(the diff). Both sub-views share thatheading 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
Latestis one click away.Dark theme — its own palette, not an inversion (its reading surface is lighter
than its page):
1280×720, where the player and the file tree have to be on screen together:
390px — the history becomes a named drawer, and the current step stays visible:
The rate-limited error state and a live-fixture repository, for the states that
are not the happy path:
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
302to anyone not signedin 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 allThe suggested path, in order:
Explore the demo. You land on commit 1 of 16, paused.Nexta few times. The tree fills in and the change markers move with it.Play, thenPause. Note the player and the tree are on screen together.Diff. It opens that file's patch inChanges, onthe same commit, and playback stops so the evidence cannot move.
Compare. From/To are proposed and run once. Move either selector — theold result is put away, not relabelled — then press
Compare.Insights→ anyInspect commit. Back in Replay at that commit.Dark, thenLight. Nothing reloads. Reload the page: no flash.Nothing above touches
github.com. For a live repository, dropRTM_FIXTURE_MODEand open any public one.Where everything went
Nothing was cut to make room. Every feature and every caveat has a new address.
Change repositoryin the top bar afterwardsStart overChange repository → Start over from the home screenGitHub usagedisclosure beside itWhat exactly happenedReplay's compact player (78px, was 108px)Replay's history column; alsoInsightsReplay → RepositoryReplay's heading +Replay → Changesexact/rebuilt/≈ n gapsbadgeSnapshot/Reconstructed/Approximate, with the count and a jump to the source commitInsights, as a page;Inspect commitreturns to ReplayInsightsCompare'sFrom/To+Swap, thenCompareCompare → File changes/Commits/Repository statsrepo/c/base/head, plusview; old links unchangedBehaviour 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, andLatestis one click.Choosing a comparison no longer spends requests. Selecting an end updates a
draft;
Compareruns it. Previously each pick and each swap fired a request, andthe previous result was left sitting under the new endpoints. Measured: two
selector changes plus a swap went from +2 requests to +0.
Opening
Compareat the first commit proposes the whole loaded range, not acommit 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 actionin
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.
Insightsnow opens withAbout 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
probeat 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
mainexactlyagain, and two tests pin it.
Honesty, moved to where it is read
Loading source…until the server'sdataSourcearrives — no announcing alive repository before anything has been fetched.
Built-in demo · 0 GitHub requests, in one place. Not "0 APIrequests": the browser does call this application's own routes.
Unknown, never0; an undated reading saysAge unknown, never "just now".not-foundno longer claims the repository does not exist — GitHub answersidentically for a private one.
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.
behindcomparison with no ahead commits is not "no difference".and the player answer different questions.
Verification
Run on this branch, against a production build, with no token and no network:
npm run typechecknpm run lintnpm testnpm run buildnpm run test:e2enpm run token-canaryThe one pre-existing e2e failure on
mainwas a strict-mode ambiguity incompare.spec.ts(the same commit subject in both the picker and the commitlist); it passed in isolation. The replacement scopes its locator to the picker.
No job, assertion or
skipwas added to make this pass. Test counts went up.Requests for one session
Browser network log, production build, built-in demo.
0requests reachgithub.laiyagushi.com.main, endpoint for endpointInsights, two theme changesCompare(runs the proposal once)SwapCompareLayout, 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
Compareand inInsights. The same holds at720×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
Repository statstable scrolls sideways below 900px. It is a table ofnumbers in its own named container, which seemed better than truncating them.
in the browser. It does not search file contents, and says so where it finds
nothing. Adding that would be a request per commit.
npm run test:livereaches real repositories, andthis deployment has no token, so it would spend the 60/hour anonymous budget to
re-check code paths the fixtures already cover.
covered by tests; I have not tested with VoiceOver or NVDA.
200% but is not literally the same as pressing ⌘+.
docs/screenshots/review-*.jpgadds ~1.1MB to the repository. They are therecord 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 tovercel.json, the environment variables or the production token. No token wascreated, read or displayed. No dependency was added — runtime dependencies are
still
next,react,react-domandserver-only, and the interface nowdownloads no webfont at all.
🤖 Generated with Claude Code