Skip to content

feat: add a privacy-safe built-in repository demo - #2

Merged
renrenmimi merged 5 commits into
mainfrom
feat/privacy-safe-built-in-demo
Aug 25, 2026
Merged

feat: add a privacy-safe built-in repository demo#2
renrenmimi merged 5 commits into
mainfrom
feat/privacy-safe-built-in-demo

Conversation

@renrenmimi

@renrenmimi renrenmimi commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Why

The demo list named four of the author's own repositories, and those names had
spread into the fixtures, the tests, the scripts, the README and an error
message. None of that was needed for the product to work.

This replaces them with one curated synthetic history that ships with the
application. Nothing else about the product changes: the same input, playback,
tree, commit detail, milestones, statistics, responsive behaviour, accessibility,
caching and server-side GitHub architecture.

The built-in demo

demo/learning-platform — visible title Learning Platform, badged
Built-in demo, described as "A curated 16-commit history showing a learning
product grow from a shell into a guided, accessible experience."
with the
disclosure "This is a curated, synthetic history — not a live GitHub
repository."

Sixteen commits, in this order:

# Subject
1 chore: scaffold the application shell
2 feat: establish design tokens and layout primitives
3 feat: define lesson and exercise models
4 feat: add progress state and local persistence
5 feat: publish the first guided lesson
6 feat: expand the learning library
7 feat: generate navigation and search indexes
8 feat: add guided practice sessions
9 feat: introduce the coding workspace
10 feat: add assessment and review modes
11 feat: introduce guided learning paths
12 feat: make progress and continuation plan-aware
13 refactor: unify navigation and next actions
14 feat: add dashboard progress views
15 fix: align responsive layouts and accessibility
16 fix: lock background scrolling behind the mobile drawer

The fixture is data, not sixteen special cases: src/lib/builtin/learning-platform.ts
holds literal object ids, timestamps, subjects and per-file line counts, and
provider.ts replays them into the same domain objects the GitHub adapter
produces. The projector, statistics, milestone rules and every panel run
unchanged — nothing about the charts or the tree is hard-coded.

It goes from 6 files to 90, adds and removes files, renames one, carries two
release tags, and produces fifteen milestones including the initial commit, the
first README, manifest, lockfile, TypeScript config, framework config, test, CI
definition, deploy descriptor and licence, a structural change, an unusually
large change, and a second language appearing. A full tree is stored for all
sixteen commits, so every timeline position reads exact rather than
rebuilt.

How it avoids GitHub entirely

src/lib/github/service.ts resolves the exact reference before anything else, in
every environment:

if (isBuiltinRef(ref)) return builtinRepoMeta();
if (fixtureModeEnabled()) { /* tests only */ }
// …only then does a GitHub request get built
  • It runs in production. It does not depend on RTM_FIXTURE_MODE, and the
    test fixtures remain unreachable unless that variable is explicitly set.
  • It is scoped to that one slug. demo/learning, demo/sample-app,
    demo/anything fall through to the ordinary GitHub path and get the ordinary
    not-found result.
  • No token is ever attached, because no request is ever made.

RepoMeta.dataSource is builtin or github, set by the server. The interface
switches on that field, never on a visible label. Built-in metadata and commits
carry a null htmlUrl, so there is no fake GitHub link to construct.

Measured on a production build with no token:

View GitHub requests Requests to this app's own routes
Landing page 0 0
Built-in demo, fully loaded 0 45 (repo 1, commits 1, commit 16, tree 16, tags 1, probe 10)
Live octocat/Hello-World (3 commits) 8 8

Built-in data is also exempt from the GitHub request budgets, which exist to
protect a quota it does not consume — so the demo loads completely for every
visitor, token or not.

Making the distinction unmissable

  • Header badge Built-in demo · 0 GitHub requests, with a glyph as well as
    colour; the GitHub quota meter is hidden, so no anonymous quota is shown as
    if the demo had spent it.
  • A source bar names the current mode and, in live mode, offers the demo in one
    click.
  • Built-in commits show the sha as plain dashed text — there is no "view on
    GitHub" affordance to mislead anyone.
  • The insights panel repeats the synthetic-history disclosure.
  • Sharing still works, on the stable internal reference:
    /?repo=demo%2Flearning-platform&c=<sha>.

Rate-limit resilience

A rate-limited live repository keeps its existing explanation, and now also
offers Open the built-in demo, with a footnote saying the demo is synthetic
and "not the repository you asked for". The requested repository is never
silently replaced.

Request efficiency

Audited without changing what the product means:

  • The first usable screen is now the selected commit — metadata, commit list,
    then that commit's tree and diff concurrently. Tags, remaining checkpoints,
    background diffs and milestone probes all queue behind it.
  • Trees are only read where diffs cannot reach. If every diff in the range is
    going to load anyway, a baseline tree plus the selected one already makes every
    position exact. A five-commit repository now costs 2 tree reads instead of 5.
  • Stale-load cancellation on repository switch is unchanged.

Four tests pin this, including one that fails if a five-commit repository ever
goes back to reading a tree per commit.

Privacy audit

Privacy audit: the current tree contains none of the removed personal
repository names. The permanent CI privacy job reconstructs protected patterns
internally so the workflow does not match itself.

$ git grep -nI 'github.com/renrenmimi/' -- .
  e2e/helpers.ts        → github.com/renrenmimi/RepoTimeMachine  (allow-list)
  src/app/layout.tsx    → github.com/renrenmimi                  (author profile)
  src/app/opengraph-image.tsx → github.com/renrenmimi/RepoTimeMachine
  src/components/TimeMachine.tsx → github.com/renrenmimi/RepoTimeMachine

Removed: the four-item demo list, the README demo table, both recorded fixture
bundles and their index entries, the screenshot and live-smoke defaults, the
owner/repository error example, and every test and E2E reference.

fixtures/ is now entirely synthetic — a five-commit and a twelve-commit
stand-in, a one-commit repository, an empty repository, a 640-commit history, and
the failure directives. Recording a real repository is opt-in and must name it on
the command line. The fixture set dropped from 1.5 MB to 688 kB.

The regression guards are structural rather than name-based, so the removed names
do not have to live on in the tests: the E2E suite asserts that the interface
links to no GitHub destination other than this project, and the unit suite asserts
the fixture contains no github.com, no URL, no e-mail and no issue reference.
CI runs the name-based audit as a separate job.

This removes the references from the current tree. Earlier public commits still
contain them; no history was rewritten and nothing was force-pushed.

Token canary

Built with GITHUB_TOKEN set to a deliberately fake value, then verified:

PASS response /
PASS response /?repo=demo%2Flearning-platform
PASS response /api/gh/repo?owner=demo&repo=learning-platform
PASS response /api/gh/commits?owner=demo&repo=learning-platform&page=1
PASS response /api/gh/tags?owner=demo&repo=learning-platform
PASS response /api/gh/repo?owner=rtm-fixtures&repo=sample-app
PASS 11 browser JS/CSS chunks

exposed metadata only: tokenConfigured=true dataSource=builtin rateLimit=null
payload keys: meta, tokenConfigured, data, rateLimit, rateLimitAgeMs

Also absent from .next/static and from the whole build output. Kept as
npm run token-canary so it can be re-run. No real token was used or printed.

Verification

Check Result
tsc --noEmit clean
eslint . clean
vitest run 397 passed, no network
playwright test 138 passed against a production build, no network, no token
next build clean

New tests cover: exactly sixteen commits with the prescribed subjects in order;
sixteen unique valid 40-character object ids, deterministic across rebuilds;
chronological timestamps and a parent chain from a parentless first commit; a
detail record and an exact tree for every commit; a last tree more than five times
the first and a commit that removes files; every position projectable as exact;
statistics derived through the normal buildGrowthSeries, estimateLanguages and
buildActivityMap; no fetch call anywhere in building or serving the demo;
demo/* and rtm-fixtures/* unreachable in production configuration; live
repositories still hitting https://api.github.com/repos/<owner>/<repo>; the
rate-limit path offering the demo; exactly one H1 in every state.

Visual QA at 1440 / 390 / 360px, both modes, no horizontal overflow and no console
errors. Production behaviour with no token confirmed: landing makes zero GitHub
requests, the demo loads completely and reports zero, live mode reports the
anonymous quota honestly (51/60, quota · anon).

One accessibility gap found and fixed on the way: once a timeline was open the
page had no H1 at all. There is now exactly one in every state.

Notes on wording

The badge reads 0 GitHub requests rather than 0 API requests. The demo does
call this application's own routes, so the more specific phrasing is the one that
is actually true.

🤖 Generated with Claude Code

renrenmimi and others added 3 commits August 25, 2026 01:59
The landing page previously offered four of the author's own repositories. It
now offers one curated synthetic history instead, so the tool can demonstrate
itself without reading anybody's account and without spending GitHub quota.

- `src/lib/builtin/learning-platform.ts` holds the fixture: sixteen commits
  with literal object ids, timestamps, subjects and per-file line counts.
- `src/lib/builtin/provider.ts` replays it into the same domain objects the
  GitHub adapter produces, so the projector, statistics, milestones and panels
  all run unchanged. Trees are materialised at every commit, so no position on
  the timeline is ever an approximation.
- The service layer resolves `demo/learning-platform` before anything else, in
  every environment. Nothing else is special: an arbitrary `demo/*` falls
  through to the ordinary GitHub path and gets the ordinary not-found result.
- `RepoMeta.dataSource` is `builtin` or `github`, set by the server, so the UI
  never has to infer which mode it is in. Built-in metadata and commits carry a
  null `htmlUrl`, because there is no repository to link to.

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

The landing page now offers one built-in demo and one neutral repository field
instead of four named projects. Which data source is in use is stated rather
than implied:

- a badge reading "Built-in demo · 0 GitHub requests" replaces the quota meter
  when the data is built in, so no anonymous GitHub quota is shown as if the
  demo had spent it;
- a source bar under the header names the current mode and, in live mode,
  offers the demo as a one-click alternative;
- a built-in commit shows its sha as plain text, because there is no repository
  to open, and the insights panel repeats the synthetic-history disclosure;
- the built-in marker is a glyph plus words, so it does not rely on colour.

Built-in data is also exempt from the GitHub request budgets, which exist to
protect a quota it does not consume: the demo now loads all sixteen diffs and
all sixteen trees for every visitor, with or without a token, so every timeline
position reads "exact".

The quota meter gained an accessible name, which it needed anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The README described four personal repositories as the demo set. It now covers
the built-in demo, why it replaced them, and live GitHub mode as a separate
path, plus the `meta.dataSource` field, the new tree-budget rule, the
Vercel instructions for the optional server-only token, and the fact that the
demo needs neither a token nor any quota.

Adds `npm run token-canary`, which builds with a deliberately fake token and
verifies it appears in no page response, no response header, none of the
JavaScript or CSS chunks the browser actually loads, and no API payload.

Adds a CI workflow. Beyond the usual checks it runs a privacy audit that fails
the build if a removed repository name reappears in the tree, if a
`github.com/renrenmimi/...` link points anywhere other than this project, or if
the token variable is ever given a `NEXT_PUBLIC_` prefix.

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

vercel Bot commented Aug 25, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
repo-time-machine Ready Ready Preview Aug 25, 2026 3:58pm

Spelling the removed names out in the workflow put them back into the tree, so
the audit found itself and failed. The patterns are now assembled from halves at
run time, which keeps the literals out of every tracked file while still
searching for the whole name.

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

@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: 631dba1336

ℹ️ 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 thread .github/workflows/ci.yml Outdated
# The demo is synthetic; no personal repository may reappear in the tree.
- name: No personal repository references in tracked files
run: |
if git grep -nIi -e 'renren-across-tabs' -e 'DataData' -e 'DrillLab' -e 'PetNote' -- .; then

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 Badge Exclude the workflow from its own privacy searches

In the new privacy-audit job, both negative searches always find their own pattern literals in this workflow: this command matches line 68 itself, and the token search similarly matches line 84, so every push and pull request exits 1 even when the rest of the tree is clean. The checked git grep -h usage is git grep ... [[--] <path>...]; supplying -- . includes .github/workflows/ci.yml, as confirmed by running both commands locally. Exclude this workflow (or store the patterns without spelling them literally in a searched file).

Useful? React with 👍 / 👎.

Comment on lines 441 to 443
void this.#loadTags(generation, ref);
void this.#ensureCheckpointTrees(generation, ref);
this.#queueDensify(generation, ref);

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 Fetch the oldest baseline before quota-gated checkpoints

When an anonymous live load succeeds with rateLimit.remaining <= 12, this newly delegated checkpoint task immediately returns from #ensureCheckpointTrees because #quotaExhausted() is true. The preceding Promise.all has loaded only the selected commit (normally the newest), and densification and ensureExactTree are gated by the same quota check, so every older position projects from no earlier snapshot and appears as an empty/partial tree. The previous load path fetched the oldest baseline before applying the background quota gate; preserve that baseline fetch so low-quota users can still inspect the loaded history.

Useful? React with 👍 / 👎.

Comment on lines +513 to +514
const directory = repo.toLowerCase();
await writeBundle(directory, bundle);

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 Keep opt-in recordings out of synthetic fixture directories

When the opt-in repository has a basename such as sample-app, tiny-app, or one-commit, deriving the directory from only repo.toLowerCase() overwrites the synthetic bundle generated immediately above while the fixed rtm-fixtures/* index entry continues pointing to that directory. Running the documented npm run fixtures -- owner/sample-app can therefore silently replace deterministic test data with real repository metadata and cause it to be committed under a supposedly synthetic fixture. Use a collision-free owner/repository directory or reject reserved fixture names.

Useful? React with 👍 / 👎.

The last audit step had the same self-match problem as the first: spelling the
forbidden variable name out put it into the tree, so the grep found this file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@renrenmimi
renrenmimi merged commit 62ca38a into main Aug 25, 2026
4 checks passed
@renrenmimi
renrenmimi deleted the feat/privacy-safe-built-in-demo branch August 25, 2026 16:02
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.

1 participant