Skip to content

refactor: make apps.json a source-only registry with one install path - #81

Merged
oto-macenauer-absa merged 1 commit into
chore/kb-docs-contractfrom
chore/source-only-registry
Sep 4, 2026
Merged

refactor: make apps.json a source-only registry with one install path#81
oto-macenauer-absa merged 1 commit into
chore/kb-docs-contractfrom
chore/source-only-registry

Conversation

@oto-macenauer-absa

Copy link
Copy Markdown
Collaborator

Closes #75. Part of #73.

Stacked on #80 (chore/kb-docs-contract), which is stacked on #79. Review in that order; this PR's base is #80's branch, so the diff here is only the registry work.

The duplication was worse than "duplicated"

apps.json restated everything the docs repo already declared. But the resolution never worked as documented: fetch-apps.js merged the manifest over the registry entry ("manifest wins"), build-vite.js discarded that result for packaged apps, and Astro's loadRegistry read apps.json alone.

Two consequences, both live on master:

  • A catalog card showed whatever the onboarding PR typed, not what the repo published.
  • pages never reached getAppPages. The Array.isArray(app.pages) branch in apps.js was dead code, and every packaged app was filesystem-crawled no matter what its manifest said.

A registry entry now names a source and nothing else

[
  { "repo": "AbsaOSS/my-docs", "version": "latest" },
  { "localPath": "../my-docs" },
  { "prebuilt": "tests/fixtures/my-docs.kb-docs.tar.gz" }
]

Display fields are rejected, not ignored. An onboarding PR that sets one has a mistaken idea of where metadata lives, and silently dropping it would leave the author waiting for a card that never changes.

One install path replaces two

stageEntry puts an artifact on disk — download, copy, or pack a local checkout — and is the only place the three sources differ. installArtifact then reads the manifest, validates it, copies one directory per declared app into apps/{slug}/, and checks the headless marker, identically for all of them. fetch-apps.js is now purely a downloader, and src/utils/single-page.js becomes src/utils/registry.js, generalised from bundles to every artifact.

Things that follow from having one path:

pages works And is authoritative when present: the directory is not crawled, so nothing it happens to contain becomes a route the publisher did not ask for
The marker check runs everywhere It used to run only on the GitHub path, so the prebuilt fixtures CI actually uses were never checked
No single-page registry type A markdown bundle and a docs site are both artifacts with a manifest. The reading-column layout is inferred from the artifact (one HTML file, no pages) rather than declared in the registry
localPath can pack non-Node repos Per-entry pack command, default npm run pack:kb, replacing a hard-coded npm run build:headless that could not run in the Python example repo at all
KB_REGISTRY Selects the registry file, so a deployment repo owns its own list without forking this one (#78 needs this)
Size budget enforced The numbers agreed in #74 are now checked on download
Unclaimed members reported An archive directory no app declares is named in a warning rather than silently ignored

The fixture now tests the feature

The vendored artifact is one kb-docs.tar.gz whose manifest declares two apps from one tree:

  • user-guide — no pages, so crawled. Six routes, including the admin page the inline-script hoisting tests need.
  • guide-mirror — a pages list of four, deliberately omitting docs/some-new-page/index.html, which is present on disk.

That asymmetry is the test: the same file is a route under one app and not under the other. It also drops the artifact from 6.5 MB to 4.2 MB, by not duplicating the 4.8 MB vendored CMS bundle into the mirror.

Its HTML is now stamped with data-kb-headless="true". The old fixture had no marker anywhere — a gap nothing could catch, because nothing checked prebuilt artifacts. A fixture that does not meet the contract cannot verify the contract.

Verification

Suite Result
npm test (embedded) 118 passed
playwright.config.ci.js (standalone) 22 passed
npm run build:headless 5 apps, 14 pages

The route list is the readable proof: guide-mirror emits exactly its four manifest pages, user-guide emits all six crawled ones.

Not run locally: test:container (needs Docker) — CI's image job covers it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PYtmxSeSXFg4wEXRcKCmAi

apps.json duplicated everything the docs repo already declared — slug, name,
description, icon, tags — and the duplication was not even resolved the way the
docs claimed. fetch-apps.js merged the manifest over the registry entry
("manifest wins"), build-vite.js discarded that result for packaged apps, and
Astro's loadRegistry read apps.json alone. So a catalog card showed whatever the
onboarding PR typed rather than what the repo published, and the `pages`
navigation manifest never reached getAppPages at all: the Array.isArray(app.pages)
branch in apps.js was dead code and every packaged app was filesystem-crawled
regardless of what its manifest said.

A registry entry now names a source and nothing else:

    [{ "repo": "AbsaOSS/my-docs", "version": "latest" }]

Display fields are rejected rather than ignored. An onboarding PR that sets one
has a mistaken idea of where metadata lives, and silently dropping it would
leave the author waiting for a card that never changes.

One install path replaces two. stageEntry puts an artifact on disk — download,
copy, or pack a local checkout — and is the only place the three sources differ.
installArtifact then reads the manifest, validates it, copies one directory per
declared app into apps/{slug}/ and checks the headless marker, identically for
all of them. fetch-apps.js is now purely a downloader; src/utils/single-page.js
becomes src/utils/registry.js and generalises from bundles to every artifact.

Consequences worth naming:

  * `pages` is honoured, and is authoritative when present — the directory is
    not crawled, so nothing it happens to contain becomes a route the publisher
    did not ask for.
  * The headless-marker check runs for every source. It used to run only on the
    GitHub path, so the prebuilt fixtures CI actually uses were never checked —
    and the vendored one turned out to carry no marker at all.
  * The single-page type is gone as a registry concept. A markdown bundle and a
    docs site are both artifacts with a manifest; the reading-column layout is
    inferred from the artifact (one HTML file, no `pages`) rather than declared.
  * localPath runs a per-entry `pack` command (default `npm run pack:kb`)
    instead of a hard-coded `npm run build:headless`, which could not run in the
    Python example repo at all.
  * KB_REGISTRY selects the registry file, so a deployment repo can own its own
    list without forking this one.
  * The artifact size budget from contract/ARTIFACT.md is enforced on download,
    and archive members no app claims are reported rather than silently ignored.

Fixtures rebuilt for the v1 layout. The vendored artifact is now one
kb-docs.tar.gz whose manifest declares two apps: user-guide, crawled and
complete, and guide-mirror, which carries a `pages` list of four and omits a
fifth page that exists on disk. That asymmetry is what tests the feature, and it
drops the artifact from 6.5 MB to 4.2 MB by not duplicating the vendored CMS
bundle. Its HTML is now stamped with the headless marker, because a fixture that
does not meet the contract cannot verify it.

Suites green: 118 embedded (up 3 — the pages manifest had no coverage before),
22 standalone.

Part of #73. Closes #75.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PYtmxSeSXFg4wEXRcKCmAi
@oto-macenauer-absa
oto-macenauer-absa force-pushed the chore/source-only-registry branch from 2e055e3 to dfd5caf Compare September 4, 2026 13:36
@oto-macenauer-absa
oto-macenauer-absa merged commit 88a2f7d into master Sep 4, 2026
14 checks passed
@oto-macenauer-absa
oto-macenauer-absa deleted the chore/source-only-registry branch September 4, 2026 14:13
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.

apps.json becomes a source-only registry; kb-docs.json is the source of truth for name, description, slug and pages

1 participant