Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit cf2e17f66890986156e6d231964ff3e418915803
parent 82f10323cbc619c506a07f2d473f7ed840954ef4
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu,  2 Jul 2026 02:12:19 -0400

Phase 6d: hub-parent indicator + editor pwa/hubUrl fields

Finish Phase 6's hub-parent axis:

- Header "Hub" backlink now resolves via resolveHubUrl(site) (honors a per-site
  hubUrl override, not just the family homepageUrl default), renders only in
  site mode (a hub never links to itself), and hides when the parent is this
  deployment.
- Editor site form gains a "Hub URL" field (validated absolute http(s), else
  inherit the family default) and a "Ship as an installable PWA" checkbox
  (default off). saveSiteAction reads both and writeSite round-trips them
  (serializeSite already persisted pwa/hubUrl). Nothing to add to the settings
  form — the family default reuses the existing SiteSettings.homepageUrl.

Site mode stays inert unless a site opts in; profile-row e2e green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Diffstat:
Meditor/app/sites/actions.ts | 13+++++++++++++
Meditor/app/sites/components/SiteForm.tsx | 20++++++++++++++++++++
Mexport/app/components/Header.tsx | 17+++++++++++++++--
3 files changed, 48 insertions(+), 2 deletions(-)

diff --git a/editor/app/sites/actions.ts b/editor/app/sites/actions.ts @@ -61,6 +61,17 @@ export async function saveSiteAction( } const siteUrl = parseSiteUrl(siteUrlRaw); + // Hub parent (per-site override of the family default) + PWA opt-in. + const hubUrlRaw = String(formData.get("hubUrl") ?? "").trim(); + if (hubUrlRaw && !parseSiteUrl(hubUrlRaw)) { + return { + ok: false, + error: "Hub URL must be an absolute http(s):// URL, or left blank.", + }; + } + const hubUrl = parseSiteUrl(hubUrlRaw); + const pwa = formData.get("pwa") === "on"; + let relatedSitesInput: unknown; try { relatedSitesInput = JSON.parse( @@ -149,6 +160,8 @@ export async function saveSiteAction( ...(parseAccent(accentRaw) ? { accent: parseAccent(accentRaw) } : {}), ...(cloudflareProject ? { cloudflareProject } : {}), ...(siteUrl ? { siteUrl } : {}), + ...(hubUrl ? { hubUrl } : {}), + ...(pwa ? { pwa: true } : {}), ...(relatedSites.length > 0 ? { relatedSites } : {}), }; try { diff --git a/editor/app/sites/components/SiteForm.tsx b/editor/app/sites/components/SiteForm.tsx @@ -239,6 +239,26 @@ export function SiteForm({ initial, channels, allSites, isNew }: Props) { defaultValue={initial.siteUrl ?? ""} hint="Absolute URL this site is served at (e.g. https://jeralyzer.com). Used so other sites can link to it in their footer. Leave blank to omit this site from cross-site lists." /> + <Field + label="Hub URL" + name="hubUrl" + defaultValue={initial.hubUrl ?? ""} + hint="The hub this site belongs under (e.g. https://archilyzer.com). Shows a 'Hub' backlink and lets the hub recognize this site as a member. Leave blank to inherit the family default from Settings." + /> + <label className="flex items-center gap-2 text-sm"> + <input + type="checkbox" + name="pwa" + defaultChecked={initial.pwa === true} + className="accent-brand" + /> + Ship as an installable PWA + </label> + <p className="-mt-2 text-xs text-muted-foreground"> + Off by default: the site serves its data for the hub to read but isn&apos;t + independently installable and ships no service worker. Turn on to make + this site installable with offline support. + </p> <fieldset className="flex flex-col gap-3 border border-border rounded p-3"> <legend className="px-1 text-sm font-medium">Channels</legend> diff --git a/export/app/components/Header.tsx b/export/app/components/Header.tsx @@ -1,10 +1,15 @@ import Link from "next/link"; import { ArrowUpLeft } from "lucide-react"; import { getSettings } from "yt-dlp-transcript-common/lib/settings"; -import { listSites, resolveRelatedSites } from "yt-dlp-transcript-common/lib/site"; +import { + listSites, + resolveHubUrl, + resolveRelatedSites, +} from "yt-dlp-transcript-common/lib/site"; import { ThemeToggle } from "yt-dlp-transcript-common/components/ThemeToggle"; import { ThemeMenu } from "yt-dlp-transcript-common/components/ThemeMenu"; import { currentSite } from "../lib/site"; +import { instanceMode } from "../lib/mode"; import SiblingSwitcher from "./SiblingSwitcher"; // The export site's masthead: a brand-colored mark + wordmark, calm sans nav, @@ -15,7 +20,15 @@ import SiblingSwitcher from "./SiblingSwitcher"; export default function Header() { const site = currentSite(); const settings = getSettings(); - const hubUrl = settings.homepageUrl; + // The hub this site points visitors toward: its own hubUrl override, else the + // family default (resolveHubUrl). Only a plain site shows the backlink — the + // hub itself never links to itself — and never when the parent is this very + // deployment. + const resolvedHub = resolveHubUrl(site, settings); + const hubUrl = + instanceMode() === "site" && resolvedHub && resolvedHub !== site.siteUrl + ? resolvedHub + : undefined; const related = resolveRelatedSites(site, listSites()); return (