feat: add a privacy-safe built-in repository demo - #2
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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>
There was a problem hiding this comment.
💡 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".
| # 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 |
There was a problem hiding this comment.
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 👍 / 👎.
| void this.#loadTags(generation, ref); | ||
| void this.#ensureCheckpointTrees(generation, ref); | ||
| this.#queueDensify(generation, ref); |
There was a problem hiding this comment.
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 👍 / 👎.
| const directory = repo.toLowerCase(); | ||
| await writeBundle(directory, bundle); |
There was a problem hiding this comment.
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>
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, badgedBuilt-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:
chore: scaffold the application shellfeat: establish design tokens and layout primitivesfeat: define lesson and exercise modelsfeat: add progress state and local persistencefeat: publish the first guided lessonfeat: expand the learning libraryfeat: generate navigation and search indexesfeat: add guided practice sessionsfeat: introduce the coding workspacefeat: add assessment and review modesfeat: introduce guided learning pathsfeat: make progress and continuation plan-awarerefactor: unify navigation and next actionsfeat: add dashboard progress viewsfix: align responsive layouts and accessibilityfix: lock background scrolling behind the mobile drawerThe fixture is data, not sixteen special cases:
src/lib/builtin/learning-platform.tsholds literal object ids, timestamps, subjects and per-file line counts, and
provider.tsreplays them into the same domain objects the GitHub adapterproduces. 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
exactrather thanrebuilt.
How it avoids GitHub entirely
src/lib/github/service.tsresolves the exact reference before anything else, inevery environment:
RTM_FIXTURE_MODE, and thetest fixtures remain unreachable unless that variable is explicitly set.
demo/learning,demo/sample-app,demo/anythingfall through to the ordinary GitHub path and get the ordinarynot-found result.
RepoMeta.dataSourceisbuiltinorgithub, set by the server. The interfaceswitches 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:
repo1,commits1,commit16,tree16,tags1,probe10)octocat/Hello-World(3 commits)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
Built-in demo · 0 GitHub requests, with a glyph as well ascolour; the GitHub quota meter is hidden, so no anonymous quota is shown as
if the demo had spent it.
click.
GitHub" affordance to mislead anyone.
/?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:
then that commit's tree and diff concurrently. Tags, remaining checkpoints,
background diffs and milestone probes all queue behind it.
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.
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.
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/repositoryerror example, and every test and E2E reference.fixtures/is now entirely synthetic — a five-commit and a twelve-commitstand-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_TOKENset to a deliberately fake value, then verified:Also absent from
.next/staticand from the whole build output. Kept asnpm run token-canaryso it can be re-run. No real token was used or printed.Verification
tsc --noEmiteslint .vitest runplaywright testnext buildNew 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,estimateLanguagesandbuildActivityMap; nofetchcall anywhere in building or serving the demo;demo/*andrtm-fixtures/*unreachable in production configuration; liverepositories still hitting
https://api.github.com/repos/<owner>/<repo>; therate-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 requestsrather than0 API requests. The demo doescall this application's own routes, so the more specific phrasing is the one that
is actually true.
🤖 Generated with Claude Code