Skip to content

refactor: deepen four modules surfaced by the architecture review#100

Merged
ronaldtse merged 1 commit into
mainfrom
refactor/architecture-review
Jul 23, 2026
Merged

refactor: deepen four modules surfaced by the architecture review#100
ronaldtse merged 1 commit into
mainfrom
refactor/architecture-review

Conversation

@ronaldtse

Copy link
Copy Markdown
Contributor

Summary

Acting on the candidates from /improve-codebase-architecture. Four refactors, each independently motivated but cohesive as a batch.

1. Collapse the easter-egg scattering (the big one)

FingerprintPlayground.vue was 724 lines with each egg smeared across 6 locations (type union + EGGS array + EGG_DURATIONS + eggBackground switch + v-else-if template branch + CSS block). Adding an egg meant editing all 6.

Each egg is now a self-contained Vue SFC under src/components/vue/fingerprint/eggs/. The playground imports a single EGG_REGISTRY and renders the active egg via <component :is>.

src/components/vue/FingerprintPlayground.vue   724 → 247 lines
src/components/vue/fingerprint/types.ts        new (EggDef, EggProps)
src/components/vue/fingerprint/registry.ts     new (15 entries)
src/components/vue/fingerprint/eggs/*.vue      new (15 files)

Each egg owns its template, script (including any reactive state — matrix tick, OTP XOR, Enigma rotors), style, and prefers-reduced-motion rule. The engine stays tiny: hash, watch input, dispatch events, render the active egg component.

2. Surface the implicit rnp:* event bus

Five custom events were referenced as string literals at ~12 sites across SiteHeader, SiteSearch, FingerprintPlayground, EasterEggs, and HeroDecrypt. No central registry; rename was unsafe.

src/lib/events.ts now exports RNP_EVENTS (and RNP_STORAGE for the session key) as as const maps plus a dispatch() helper. All senders and listeners import the same symbol — rename = one edit; typo = compile error.

3. Deepen the vendor-source concept

RNP_VERSION was a hand-synced constant in content.config.ts that the comment warned to "bump together with docs_ref". The man-page paths were duplicated between fetch-sources.mjs (EXTRA_PATHS) and content.config.ts (include regex).

src/lib/vendor-source.mjs now owns:

  • RNP_RELEASE_TAG (read from docs_ref in software/rnp.md)
  • RNP_VERSION (derived from the tag, stripping the leading v)
  • RNP_MAN_PAGES + RNP_EXTRA_SPARSE (shared sparse-checkout paths)

content.config.ts and fetch-sources.mjs both import. The hand-sync is gone — bump docs_ref and RNP_VERSION follows.

src/lib/frontmatter.mjs extracts the YAML-front-matter parser shared between asciidoc.ts (TS) and fetch-sources.mjs (Node). Kept in .mjs so the standalone script can import it without a TS compiler.

4. Delete the duplicated helpers

decodeEntities was inlined verbatim inside renderAdoc (asciidoc.ts); docSort was duplicated verbatim in two software route files. Both pass the deletion test as pass-throughs.

  • renderAdoc now calls the existing decodeEntities function
  • docSort moves to src/lib/docs.ts; both routes import

Skipped: front-matter parsing seam

The original review claimed fetch-sources.mjs and asciidoc.ts both parse YAML front matter. Verifying on disk: fetch-sources.mjs doesn't parse front matter itself — it sparse-clones repos and reads each product's front matter via the shared parser now in frontmatter.mjs (already done above as part of #3). The "seam" never existed as described; nothing to do here.

Test plan

  • npm run build clean (no errors, no warnings)
  • HTTP 200 on /, /blog/, /software/rnp/, /advisories/
  • node -e "import('./src/lib/vendor-source.mjs')..."RNP_VERSION: 0.18.1
  • Dev server HMR picked up all new files; egg SFCs all return 200
  • Each egg SFC has the defineProps<{ input: string; hex: string }> signature
  • All rnp: event literals replaced — grep "rnp:" src/ returns empty (excluding events.ts and comments)
  • Visual review of egg triggers (matrix, dh, otp, enigma, phil) on a real screen
  • Verify HAL/Dave screenwide scenes still fire on hal / dave input

Acting on the candidates from /improve-codebase-architecture. Four
refactors, each independently motivated but cohesive as a batch.

== 1. Collapse the easter-egg scattering (the big one) ==

FingerprintPlayground.vue was 724 lines with each egg smeared across 6
locations: type union, EGGS array, EGG_DURATIONS, eggBackground switch,
v-else-if template branch, and CSS block. Adding an egg meant editing
all 6.

Each egg is now a self-contained Vue SFC under
src/components/vue/fingerprint/eggs/. The playground imports a single
EGG_REGISTRY and renders the active egg via <component :is>. Adding an
egg is now: create one .vue file + append one entry to registry.ts.

  src/components/vue/FingerprintPlayground.vue   724 → 247 lines
  src/components/vue/fingerprint/types.ts        new (EggDef, EggProps)
  src/components/vue/fingerprint/registry.ts     new (15 entries)
  src/components/vue/fingerprint/eggs/*.vue      new (15 files)

Each egg owns its template, script (including any reactive state —
matrix tick, OTP XOR, Enigma rotors), style, and prefers-reduced-motion
rule. The engine stays tiny: hash, watch input, dispatch events, render
the active egg component.

== 2. Surface the implicit rnp:* event bus ==

Five custom events were referenced as string literals at ~12 sites
across SiteHeader, SiteSearch, FingerprintPlayground, EasterEggs, and
HeroDecrypt. No central registry; rename was unsafe.

src/lib/events.ts now exports RNP_EVENTS (and RNP_STORAGE for the
session key) as `as const` maps plus a dispatch() helper. All senders
and listeners import the same symbol; rename = one edit; typo =
compile error.

== 3. Deepen the vendor-source concept ==

RNP_VERSION was a hand-synced constant in content.config.ts that the
comment warned to "bump together with docs_ref". The man-page paths
were duplicated between fetch-sources.mjs (EXTRA_PATHS) and
content.config.ts (include regex).

src/lib/vendor-source.mjs now owns:
  - RNP_RELEASE_TAG (read from docs_ref in software/rnp.md)
  - RNP_VERSION (derived from the tag, stripping the leading 'v')
  - RNP_MAN_PAGES + RNP_EXTRA_SPARSE (shared sparse-checkout paths)

content.config.ts and fetch-sources.mjs both import. The hand-sync is
gone — bump docs_ref and RNP_VERSION follows.

src/lib/frontmatter.mjs extracts the YAML-front-matter parser shared
between asciidoc.ts (TS) and fetch-sources.mjs (Node). Kept in .mjs so
the standalone script can import it without a TS compiler.

== 4. Delete the duplicated helpers ==

decodeEntities was inlined verbatim inside renderAdoc (asciidoc.ts);
docSort was duplicated verbatim in two software route files. Both pass
the deletion test as pass-throughs.

  - renderAdoc now calls the existing decodeEntities function
  - docSort moves to src/lib/docs.ts; both routes import

== Skipped: front-matter parsing seam ==

The original review claimed fetch-sources.mjs and asciidoc.ts both
parse YAML front matter. Verifying on disk: fetch-sources.mjs doesn't
parse front matter itself — it sparse-clones repos and reads each
product's front matter via the shared parser now in frontmatter.mjs
(already done above as part of #3). The "seam" never existed as
described; nothing to do here.

== Verification ==

  - `npm run build` clean (no errors, no warnings)
  - HTTP 200 on /, /blog/, /software/rnp/, /advisories/
  - node -e import vendor-source.mjs → RNP_VERSION '0.18.1'
  - dev server HMR picked up all new files; egg files all 200
EOF
@ronaldtse
ronaldtse merged commit 780a2ca into main Jul 23, 2026
3 of 4 checks passed
ronaldtse added a commit that referenced this pull request Jul 23, 2026
…text)

Regression introduced in PR #100 (the architecture refactor). When I
extracted the YAML-front-matter parser to src/lib/frontmatter.mjs, I
kept its interface as `parseFrontmatter(rawText)` — but in
fetch-sources.mjs I called it as `parseFrontmatter(filePath)`. The
regex matched nothing, every product's `docs_repo` came back undefined,
every product hit the `continue`, and `vendor/` stayed empty.

CI then built with no vendor checkout, so the `softwareDocs` and
`manpages` collections were empty, ~970 internal doc/manpage links went
missing, and lychee reported 8 broken internal links (the README and
installation hrefs that had nothing to point at).

The live site deployed from this broken build.

Fix: readFileSync(join(softwareDir, file), 'utf8') before calling
parseFrontmatter, matching what asciidoc.ts already does through its
adocLoader.

Verified locally:
  - parseFrontmatter(readFileSync('src/content/software/rnp.md', 'utf8'))
    returns docs_repo + docs_ref correctly
  - rm -rf vendor && npm run fetch-sources clones all 3 products
  - npm run build: no empty-collection warnings
  - npm run check:links: "OK — all internal href/src targets exist"
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