commit b0f8d2b65d196f89dbff133c639c811bb68b0812
parent 46c1b05cb44875d4d6ff68f5fc0dbc74770df92e
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sat, 12 Sep 2026 02:58:38 -0400
common, export: one shipsPwa(), and the comment about it is now true
The predicate "does this build ship an installable PWA" existed twice —
compose-site.ts and export/app/lib/mode.ts — each carrying a "keep in sync
with the other" comment, which is the shape of a bug that has not happened
yet. S1 put the one copy in lib/archive/contract.ts; this deletes both.
export/app/lib/mode.ts loses its hub short-circuit with it. It was true
twice over: currentSite() in hub mode returns hubSite(), which sets
`pwa: true`, and the contract predicate reads INSTANCE_MODE itself. Same
answer in both modes, one call.
The kept caller stays SERVER-side, deliberately. contract.ts's own comment
claimed Next inlines `process.env.INSTANCE_MODE` into the client bundle and
that a `typeof process` guard would therefore break hub mode; the S1 review
checked and that is false — INSTANCE_MODE is neither NEXT_PUBLIC_ nor in a
next.config `env:` block, so a client bundle would read undefined. The bare
read is fine for the real reason: nothing in a browser evaluates it. Its
callers are a build script and a server component (and currentSite() reads
the sites dir, so mode.ts could not be client-side anyway). The comment now
says that, and says what a future client caller would owe.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
3 files changed, 33 insertions(+), 22 deletions(-)
diff --git a/common/bin/compose-site.ts b/common/bin/compose-site.ts
@@ -32,6 +32,7 @@ import type { Manifest, SubsManifest } from "../lib/manifest";
import type { PostsManifest } from "../lib/posts";
import type { DigestsManifest } from "../lib/digests";
import { buildSiteDescriptor, type PublicSiteDescriptor } from "../lib/siteDescriptor";
+import { shipsPwa } from "../lib/archive/contract";
import { effectiveSiteAliases } from "../lib/aliasesStore";
import {
buildSiteCorpus,
@@ -201,13 +202,6 @@ async function emitAiFiles(paths: ReturnType<typeof getPaths>): Promise<void> {
}
}
-// Whether this build ships an installable PWA. Resolved from the site's `pwa`
-// config flag (site builds are dumb instances by default) or forced on in hub
-// mode. Keep in sync with export/app/lib/mode.ts shipsPwa().
-function shipsPwa(site: Site): boolean {
- return site.pwa === true || process.env.INSTANCE_MODE === "hub";
-}
-
// Compose the service worker into the served public dir ONLY when this instance
// ships a PWA. The SW source lives outside public/ (export/service-worker/) so
// a dumb instance emits no /sw.js at all — not just an unregistered one. Mirrors
diff --git a/common/lib/archive/contract.ts b/common/lib/archive/contract.ts
@@ -143,10 +143,18 @@ export function pageUrl(
// installable) and opts in per site via the `pwa` config flag.
//
// Was two copies with "keep in sync" comments on each (compose-site.ts and
-// export/app/lib/mode.ts); this is the one. `process.env.INSTANCE_MODE` is read
-// here exactly as both copies read it — Next inlines that member expression
-// into the client bundle at build time, so a `typeof process` guard around it
-// would turn hub mode OFF in the browser rather than make it safer.
+// export/app/lib/mode.ts); S2c deleted both, and this is the one.
+//
+// `process.env.INSTANCE_MODE` is read bare, exactly as both copies read it, and
+// the reason that is safe is NOT that Next inlines it — it does not:
+// INSTANCE_MODE is neither `NEXT_PUBLIC_` nor listed in a next.config `env:`
+// block, so a client bundle would read `undefined` here. (An earlier draft of
+// this comment claimed the opposite; the S1 review checked.) It is safe because
+// no browser evaluates it: the only caller in the export app is
+// export/app/lib/mode.ts, whose only caller is export/app/layout.tsx, a SERVER
+// component, and the other caller is compose-site.ts, a build script. If this
+// ever becomes reachable from a client module it needs a
+// `typeof process !== "undefined"` guard AND an env var the client can see.
export function shipsPwa(site: { pwa?: boolean }): boolean {
return site.pwa === true || process.env.INSTANCE_MODE === "hub";
}
diff --git a/export/app/lib/mode.ts b/export/app/lib/mode.ts
@@ -1,4 +1,5 @@
import { currentSite } from "./site";
+import { shipsPwa as contractShipsPwa } from "yt-dlp-transcript-common/lib/archive/contract";
// The export app renders one of two shells, selected at build time:
// site (default): a single-site archive, exactly as before.
@@ -13,17 +14,25 @@ export function instanceMode(): InstanceMode {
}
// Whether this build ships an installable PWA — the "dangerous permissions"
-// surface: a service worker, a web manifest, and installability. This is an
-// axis INDEPENDENT of the shell:
-// - hub mode always ships the PWA (Archilyzer IS the installable app);
-// - site mode is a dumb instance by default (federatable JSON only, not
-// installable) and opts in per site via the `pwa` config flag.
+// surface: a service worker, a web manifest, and installability. See
+// lib/archive/contract.ts shipsPwa() for what the axis means; this wrapper only
+// supplies the site, which the export app resolves at build/render time.
//
-// Resolved purely from config that is already available at build/render time
-// (INSTANCE_MODE env + the selected site's `pwa`), so the app shell and the
-// compose step agree without threading an extra env var. Keep this in sync with
-// compose-site.ts's shipsPwa().
+// Was a second copy of the predicate with a "keep in sync" comment pointing at
+// compose-site.ts's copy. Both are gone; there is one.
+//
+// Hub mode needs no branch of its own any more: currentSite() already returns
+// hubSite(), which sets `pwa: true` (export/app/lib/site.ts), and the contract
+// predicate reads INSTANCE_MODE itself — so the old
+// `if (instanceMode() === "hub") return true` short-circuit was true twice over.
+//
+// SERVER-ONLY, deliberately: the contract predicate reads
+// `process.env.INSTANCE_MODE`, which is not a `NEXT_PUBLIC_` var and is not in a
+// next.config `env:` block, so Next does NOT inline it into a client bundle.
+// The only caller is export/app/layout.tsx, a server component — and
+// currentSite() is server-only regardless, since it reads the sites dir.
+// Calling this from a client component would read `undefined` and silently turn
+// hub mode off; that would need a guard there, not an assumption here.
export function shipsPwa(): boolean {
- if (instanceMode() === "hub") return true;
- return currentSite().pwa === true;
+ return contractShipsPwa(currentSite());
}