refactor(app-store): read app data from the metadata API, not local JSON - #221
Merged
Conversation
The site and the Alpha management console both render an app store, and each built it from its own copy of the data. This script assembled records from app-overrides.json + app-methods.json + app-demos.json; the console embedded a snapshot pasted across from here by hand. They drifted — 25 of 27 apps carried different summary copy, and one app's console blurb had been taken from a different paragraph of the description entirely. Both now read one served document. Editing an app means editing one file in pilot-protocol/app-template under appstore-meta/data/apps/ and redeploying the API; nothing about an app's copy is decided in this repo any more, so the three local JSON sources are removed rather than left to rot as a second truth. Which glyph a mask icon is cut from now travels in the document as icon.mark, so the id-to-glyph table this script used to hold is gone too. That table was the last piece of app data still hardcoded here. A static build must not depend on a live service. The fetched document is written to src/data/app-metadata.json and committed; if the API is unreachable the build uses that snapshot and carries on, one release behind at worst. An empty or malformed response counts as unreachable rather than as an empty store. Only a missing API *and* a missing snapshot is fatal, and --offline skips the fetch outright. Verification ------------ - apps.ts regenerated from the API is semantically identical to the committed one: same 27 apps in the same order, same categories, same featured order, and no field differing on any app. The textual diff is explicit nulls where optional keys used to be omitted. - The exported TypeScript shape is unchanged, so every page reading apps.ts is untouched. - npm run build: 407 pages, clean. /app-store and /apps/<id> render their shelves, methods and demos as before.
Contributor
|
🚀 Preview deployed to Cloudflare Pages
|
added 3 commits
August 25, 2026 15:26
appstore-meta.pilotprotocol.network now resolves and serves over TLS, so the snapshot and the generated banner record that rather than the VM's IP. Every app record is byte-identical to the copy generated from the IP — only the recorded source URL changes.
Auditing the app-store surface against the served schema turned up four data-model faults. All predate the API switch, but the switch is what makes them worth fixing now: the app list is no longer local, so a reference to a field or an id that does not exist can no longer be checked by reading this repo. `astro check` reported all four; none is caught by `astro build`, which does not type-check. - **`app.pricing` was dead.** The app page carried a whole Pricing section guarded on a field that is not on the `App` type and that no generator has ever produced, so the guard was always false and the section never rendered. Removed, with its now-unreferenced markup. Cost is already shown from `productDemo.cost`, which is real data. - **`app.isNew` was dead.** The card rendered a "New" badge on a field that does not exist, so the badge never appeared. Removed. `publishedAt` is now served for 13 apps, so the badge could be revived off real data — that is a visible change and left as a deliberate decision rather than smuggled in here. - **The "New & Updated" shelf leaned on `filter(Boolean)`**, which strips undefined at runtime but leaves the type `(App | undefined)[]`. Its pinned ids are curated in this file while the app list is served, so an id can now disappear from under it and the shelf would quietly shrink. Narrowed with a real type guard, and a missing id is now reported at build time. - **The description blocks were not a discriminated union**, so `p.type === 'ul'` did not narrow and `p.items.map` type-checked as possibly-undefined. Typed properly. Also hardened the generator against demo data that would crash a build rather than degrade: `quickstart` is always a whole step, because the app page dereferences `demo.quickstart.command` directly and the TypeScript shape says it is non-nullable; a demo with no quickstart command is dropped with a warning instead of rendering an empty code block; and example steps with no command are filtered out. Verification ------------ - The eight app-store data-model errors are gone (`astro check`: 254 -> 244). The remainder are pre-existing implicit-any and possibly-null findings in inline `<script>` blocks across the whole site, plus `worker/test` needing dev deps that are not installed — all untouched here. - **Zero rendered output changed.** All 31 app-store pages — `/app-store`, its plain twin, `/docs/app-store`, `/publish` and all 27 `/apps/<id>` — are byte-identical to the dev mirror at 7b88a265. That is what confirms the removed code really was dead. - `npm run build` clean at 407 pages; `check:plain` and `check:site` pass. - Regenerating `apps.ts` after the generator hardening produces a zero-line diff: every shipped demo already has a quickstart command, so the guards are for future data. The plain twin of `app-store.astro` carries a provenance hash of its source. The edit here is frontmatter only — the twin does not reference the shelf that changed, and its rendered output is byte-identical to the mirror — so the stamp is updated in place rather than regenerating a page whose content did not change (regen needs a GEMINI_API_KEY that is not available here).
The app was renamed to io.pilot.smol; "smolmachines" is the retired id, and
the app's name is Smol Machines. Assets and copy still carried the old id.
Removed (orphaned — nothing referenced them, and io.pilot.smol ships its own
complete icon set):
- public/appicons/io.pilot.smolmachines.png
- public/appicons/optimized/io.pilot.smolmachines-{240,96}.webp
Renamed, because the homepage tile still pointed at it, and the brand
directory is keyed by short app name (aegis, duckdb, miren, …):
- public/brand/apps/smolmachines.png -> smol.png
- public/brand/apps/optimized/smolmachines.webp -> smol.webp
Prose in two learn pages and three blog posts introduced the app as
"smolmachines", which is now a name no app has. They read "Smol Machines".
Kept deliberately
-----------------
public/_redirects still maps /apps/io.pilot.smolmachines -> /apps/io.pilot.smol
(301). That rule exists *because* of this rename: it is what stops old
bookmarks and inbound links 404ing, and removing it would turn a working
redirect into a dead URL. The retired id has to survive in exactly this one
place for the rename to stay invisible to anyone who followed an old link.
Untouched: https://smolmachines.com is the vendor's live domain, not the app
id, and remains the app's vendor_url and homepage.
Verification
------------
- npm run build clean at 407 pages; check:plain and check:site pass.
- No smolmachines-named asset ships: `find dist -iname '*smolmachines*'` is
empty. The only occurrences left in the built site are the vendor domain on
the app page and the redirect rule itself.
- The homepage tile resolves: dist/brand/apps/optimized/smol.webp is present
and referenced once.
The plain twin of index.astro carries a provenance hash of its source. The
edit here is a single image src; the twin has no image tiles at all, so the
stamp is updated in place rather than regenerating a page whose content did
not change (regen needs a GEMINI_API_KEY that is not available here).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The site and the Alpha management console both render an app store, and each
built it from its own copy of the data.
scripts/gen-apps.mjsassembledrecords from
app-overrides.json+app-methods.json+app-demos.json;the console embedded a snapshot pasted across from here by hand.
They drifted — 25 of 27 apps carried different summary copy, and one
app's console blurb had been taken from a different paragraph of the
description entirely.
What changes
Both surfaces now read one served document
(app-template#101).
Editing an app means editing one file in
pilot-protocol/app-templateunderappstore-meta/data/apps/and redeploying the API. Nothing about an app'scopy is decided in this repo any more, so the three local JSON sources are
removed rather than left to rot as a second truth.
Which glyph a mask icon is cut from now travels in the document as
icon.mark, so the id-to-glyph table this script used to hold is gone too.That table was the last piece of app data still hardcoded here.
The build must not depend on a live service
The fetched document is written to
src/data/app-metadata.jsonandcommitted. If the API is unreachable the build uses that snapshot and carries
on, one release behind at worst. An empty or malformed response counts as
unreachable rather than as an empty store. Only a missing API and a
missing snapshot is fatal, and
--offlineskips the fetch outright.Verification
apps.tsregenerated from the API is semantically identical to thecommitted one: same 27 apps in the same order, same categories, same
featured order, and no field differing on any app. The textual diff is
explicit
nulls where optional keys used to be omitted.apps.tsis untouched.
npm run build: 407 pages, clean./app-storeand/apps/<id>rendertheir shelves, methods and demos as before.
Note for reviewers
The
src/data/apps.tsdiff is large but mechanical — it is the explicit-nullserialisation above. The semantic comparison is the thing to trust; the
generator is the file worth reading.