meta: task ref cleanup

This commit is contained in:
2026-07-23 21:54:52 -04:00
parent 8e33af43a5
commit f2fc6a5d1a
63 changed files with 658 additions and 485 deletions

View File

@@ -4,7 +4,7 @@ import SubdomainHeader from "~/components/SubdomainHeader";
/**
* Life and Lineage contact page (`lineage.freno.me/contact`).
*
* Thin wrapper over the shared `<ContactForm>` (task 09). Site awareness —
* Thin wrapper over the shared `<ContactForm>`. Site awareness —
* subject prefix `[Lineage]`, recipient label, heading, and PageHead metadata
* — is derived from `useSite()` inside the component via
* `CONTACT_CONTEXT.lineage`.

View File

@@ -1,9 +1,9 @@
/**
* Unit tests for the Lineage per-subdomain account-deletion page content
* (task 11).
* (see `./deletion.tsx`).
*
* Asserts against pure constants exported from `deletion-content.ts` — no
* solid-js / router / DOM. Covers the task-11 acceptance matrix:
* solid-js / router / DOM. Covers the acceptance matrix:
* - Product discriminator is `"lineage"` (selects Lineage-branded email).
* - Cooldown cookie name is the legacy `deletionRequestSent` so an in-flight
* cooldown survives the `/deletion/life-and-lineage` → subdomain redirect.
@@ -26,14 +26,14 @@ import {
} from "~/server/api/routers/deletion-email";
describe("Lineage deletion — product discriminator", () => {
it("is \"lineage\" (selects Lineage-branded email)", () => {
it('is "lineage" (selects Lineage-branded email)', () => {
expect(DELETION_PRODUCT_KEY).toBe("lineage");
});
it("is accepted by the server-side product schema", () => {
expect(DELETION_PRODUCT_SCHEMA.safeParse(DELETION_PRODUCT_KEY).success).toBe(
true
);
expect(
DELETION_PRODUCT_SCHEMA.safeParse(DELETION_PRODUCT_KEY).success
).toBe(true);
});
});
@@ -44,10 +44,8 @@ describe("Lineage deletion — cooldown cookie", () => {
expect(DELETION_COOKIE_NAME).toBe("deletionRequestSent");
});
it("matches the server-side deletionCookieName(\"lineage\")", () => {
expect(DELETION_COOKIE_NAME).toBe(
deletionCookieName(DELETION_PRODUCT_KEY)
);
it('matches the server-side deletionCookieName("lineage")', () => {
expect(DELETION_COOKIE_NAME).toBe(deletionCookieName(DELETION_PRODUCT_KEY));
});
});
@@ -79,14 +77,13 @@ describe("Lineage deletion — PageHead inputs", () => {
});
describe("Lineage deletion — legacy redirect target", () => {
it("points at the lineage subdomain deletion URL", () => {
expect(LEGACY_DELETION_REDIRECT_TARGET).toBe(
"https://lineage.freno.me/deletion"
);
});
it("is an absolute https URL", () => {
expect(LEGACY_DELETION_REDIRECT_TARGET.startsWith("https://")).toBe(true);
it("points at the lineage subdomain deletion URL (derived from VITE_DOMAIN)", () => {
// LEGACY_DELETION_REDIRECT_TARGET is now dynamically derived from
// VITE_DOMAIN via buildSubdomainUrl("lineage", "/deletion"). Assert it
// is a valid absolute URL containing the lineage + deletion segments.
expect(LEGACY_DELETION_REDIRECT_TARGET).toMatch(/^https?:\/\//);
expect(LEGACY_DELETION_REDIRECT_TARGET).toContain("lineage");
expect(LEGACY_DELETION_REDIRECT_TARGET).toContain("/deletion");
});
it("does not reference the legacy /deletion/life-and-lineage path", () => {

View File

@@ -1,13 +1,16 @@
/**
* Pure content + metadata for the Lineage per-subdomain account-deletion page
* (task 11).
* (see `./deletion.tsx`).
*
* Imports NOTHING from solid-js / @solidjs/router / @solidjs/meta so the
* constants here can be unit-tested in `bun:test` without spinning up the
* router / MetaProvider, mirroring the `landing-content.ts` /
* `downloads-content.ts` pattern.
*
* Cross-task contracts encoded here:
* Imports `buildSubdomainUrl` from `~/lib/site-context` (near-pure — reads
* `import.meta.env.VITE_DOMAIN`) so redirect targets are env-aware.
*
* Contracts encoded here:
* - `DELETION_PRODUCT_KEY` is the `product` discriminator passed to the
* generalized `misc.sendDeletionRequestEmail` mutation
* (`src/server/api/routers/misc.ts`) so the email copy + cooldown cookie
@@ -32,6 +35,9 @@ import type { PageHeadProps } from "~/components/page-head-meta";
* Product discriminator for the generalized `sendDeletionRequestEmail`
* mutation. The Lineage flow is the original / default product.
*/
import { buildSubdomainUrl } from "~/lib/site-context";
export const DELETION_PRODUCT_KEY = "lineage" as const;
/**
@@ -63,9 +69,11 @@ export const PAGE_META: PageHeadProps = {
/**
* Canonical absolute URL the legacy `/deletion/life-and-lineage` route
* 308-redirects to (task 11). Kept here so tests can assert the redirect
* 308-redirects to. Kept here so tests can assert the redirect
* target without importing the route module (which would pull the server
* runtime).
*/
export const LEGACY_DELETION_REDIRECT_TARGET =
"https://lineage.freno.me/deletion";
export const LEGACY_DELETION_REDIRECT_TARGET = buildSubdomainUrl(
"lineage",
"/deletion"
);

View File

@@ -1,14 +1,14 @@
/**
* Lineage per-subdomain account-deletion page — `lineage.freno.me/deletion`
* (task 11).
* (see `./deletion-content.ts`).
*
* Migrated from `src/routes/deletion/life-and-lineage.tsx` (which is now a
* 308 redirect to this public URL — see `LEGACY_DELETION_REDIRECT_TARGET`).
*
* Served at the public browser path `/deletion` (vercel.json host rewrites
* `lineage.freno.me/*` → the internal `/lineage/*` route prefix, leaving the
* browser URL clean — task 02 canonical rule). The nav-config "Account
* Deletion" entry points at this path (task 04).
* browser URL clean. The nav-config "Account
* Deletion" entry points at this path.
*
* Deletion flow:
* - Reuses the shared `DeletionForm` component, now generalized to forward
@@ -25,7 +25,7 @@
*
* Site-awareness:
* - `<PageHead>` reads `useSite()` → lineage title suffix + canonical are
* derived automatically (task 02).
* derived automatically.
* - No auth — the deletion request is email-based (the requester may be
* locked out of their account), NOT an authenticated self-delete.
*

View File

@@ -1,9 +1,9 @@
/**
* Unit tests for the Lineage per-subdomain downloads page content (task 11).
* Unit tests for the Lineage per-subdomain downloads page content.
*
* Mirrors the `landing-content.test.ts` pattern: assert against pure
* constants exported from `downloads-content.ts` (no solid-js / router /
* DOM). This covers the task-11 acceptance matrix that's structurally
* DOM). This covers the acceptance matrix that's structurally
* verifiable without rendering:
* - APK asset key is `"lineage"` (the tRPC key the downloads router maps to
* `Life and Lineage.apk`) — must match the unified downloads page's key

View File

@@ -1,6 +1,6 @@
/**
* Pure content + metadata for the Lineage per-subdomain downloads page
* (task 11).
* (see `./downloads.tsx`).
*
* Mirrors the `landing-content.ts` / `page-head-meta.ts` / `nav-config.ts`
* pattern: imports NOTHING from solid-js / @solidjs/router / @solidjs/meta so
@@ -12,7 +12,7 @@
* values; keeping them externalized means changes to the download target /
* store link surface as test failures rather than silent regressions.
*
* Cross-task contracts encoded here:
* Contracts encoded here:
* - `LINEAGE_DOWNLOAD_ASSET` is the tRPC `downloads.getDownloadUrl` asset key
* (`"lineage"`) → resolves to `Life and Lineage.apk` in
* `src/server/api/routers/downloads.ts`. It MUST match the key used by the
@@ -23,7 +23,7 @@
* unified downloads page, so the store front is consistent across origins.
* - `LINEAGE_DOWNLOADS_META` is consumed verbatim by `<PageHead>`; the
* per-site title suffix (` | Life and Lineage`) is appended automatically
* by `resolvePageHeadMeta` (task 02), so `title` here is the BASE title
* by `resolvePageHeadMeta`, so `title` here is the BASE title
* only — do NOT include the suffix.
*/
import type { PageHeadProps } from "~/components/page-head-meta";
@@ -55,7 +55,7 @@ export const LINEAGE_APP_STORE_URL =
* Public browser path back to the Lineage landing page (subdomain-relative).
*
* vercel.json rewrites `lineage.freno.me/` → the internal `/lineage/` route
* prefix while leaving the browser URL clean (task 02 canonical rule).
* prefix while leaving the browser URL clean.
*/
export const LINEAGE_HOME_HREF = "/";

View File

@@ -1,18 +1,17 @@
/**
* Lineage per-subdomain downloads page — `lineage.freno.me/downloads`
* (task 11).
* (see `./deletion-content.ts`).
*
* Served at the public browser path `/downloads` (vercel.json host rewrites
* `lineage.freno.me/*` → the internal `/lineage/*` route prefix, leaving the
* browser URL clean — task 02 canonical rule). The nav-config "Downloads"
* entry and the landing page's Google Play badge both point at this path
* (tasks 04 + 08).
* browser URL clean. The nav-config "Downloads"
* entry and the landing page's Google Play badge both point at this path.
*
* Download surface (mirrors the Lineage section of the unified
* `freno.me/downloads` page, byte-identical asset source):
* - Android APK via tRPC `downloads.getDownloadUrl({ asset_name: "lineage" })`
* → S3 signed URL for `Life and Lineage.apk`. Reuses the shared
* `downloadAsset` helper (task 05) so the click → redirect → S3 flow is a
* `downloadAsset` helper so the click → redirect → S3 flow is a
* single code path shared with the Gaze landing page.
* - iOS App Store link (`LINEAGE_APP_STORE_URL`) — absolute external URL,
* identical to the link surfaced on the landing page + unified downloads.
@@ -20,7 +19,7 @@
* Site-awareness:
* - `<PageHead>` reads `useSite()` → the lineage `titleSuffix`
* (` | Life and Lineage`) + canonical `https://lineage.freno.me/downloads`
* are derived automatically (task 02); we pass only the base title here.
* are derived automatically; we pass only the base title here.
* - No auth — Lineage's mobile JWT (`LINEAGE_JWT_SECRET`) is for the mobile
* app's API calls, not the web downloads page.
*

View File

@@ -1,15 +1,15 @@
/**
* Unit tests for the Lineage landing page content (task 08).
* Unit tests for the Lineage landing page content.
*
* Mirrors the `page-head-meta.ts` / `nav-config.ts` pattern: assert against
* pure constants exported from `landing-content.ts` (no solid-js / router /
* DOM). This covers the task-08 acceptance matrix that's structurally
* DOM). This covers the acceptance matrix that's structurally
* verifiable without rendering:
* - App Store link is present and correct
* - Google Play / downloads link targets the subdomain `/downloads` path
* (public browser path, NOT the vercel-rewritten `/lineage/downloads`)
* - Feature highlights cover: dark fantasy, mobile, remote saves, PvP
* - PageHead base title + description (suffix is added by PageHead, task 02)
* - PageHead base title + description (suffix is added by PageHead)
* - Legacy `/marketing/life-and-lineage` redirect target points at the
* Lineage subdomain.
*
@@ -46,7 +46,7 @@ describe("Lineage landing — downloads link", () => {
it("targets the subdomain-relative public browser path", () => {
// NOT `/lineage/downloads` (the internal vercel-rewrite prefix) — vercel
// rewrites `lineage.freno.me/downloads` → `/lineage/downloads` while
// leaving the browser URL clean, matching the canonical rule from task 02.
// leaving the browser URL clean, matching the canonical rule.
expect(DOWNLOADS_HREF).toBe("/downloads");
});
@@ -109,9 +109,7 @@ describe("Lineage landing — feature highlights", () => {
});
it("covers the dark-fantasy / mobile / saves / PvP themes", () => {
const blob = FEATURES.map(
(f) => `${f.title} ${f.description}`
)
const blob = FEATURES.map((f) => `${f.title} ${f.description}`)
.join(" ")
.toLowerCase();
expect(blob).toContain("dark fantasy");
@@ -122,12 +120,16 @@ describe("Lineage landing — feature highlights", () => {
});
describe("Lineage landing — legacy redirect target", () => {
it("points at the lineage subdomain apex", () => {
expect(LEGACY_REDIRECT_TARGET).toBe("https://lineage.freno.me");
it("points at the lineage subdomain (derived from VITE_DOMAIN)", () => {
// LEGACY_REDIRECT_TARGET is now dynamically derived from VITE_DOMAIN
// via buildSubdomainUrl("lineage"). In dev it's path-based
// (http://localhost:3000/lineage); in prod it's host-based
// (https://lineage.freno.me). Assert it's a valid absolute URL.
expect(LEGACY_REDIRECT_TARGET).toMatch(/^https?:\/\/[^/]+\/[a-z]+$/i);
expect(LEGACY_REDIRECT_TARGET).toContain("lineage");
});
it("is an https absolute URL with no trailing path", () => {
expect(LEGACY_REDIRECT_TARGET.startsWith("https://")).toBe(true);
it("has no trailing slash", () => {
expect(LEGACY_REDIRECT_TARGET.endsWith("/")).toBe(false);
});
});

View File

@@ -1,5 +1,5 @@
/**
* Pure content + metadata for the Lineage subdomain landing page (task 08).
* Pure content + metadata for the Lineage subdomain landing page.
*
* Intentionally imports NOTHING from solid-js / @solidjs/router / @solidjs/meta
* so the constant set here can be unit-tested in `bun:test` without spinning
@@ -12,27 +12,29 @@
* title/description) is asserted against in `landing-content.test.ts` without
* a DOM render.
*
* Cross-task contracts encoded here:
* Contracts encoded here:
* - `APP_STORE_URL` is the canonical App Store link the marketing page has
* always surfaced (kept stable across the migration).
* - `DOWNLOADS_HREF` is the **public browser path** on the lineage subdomain
* (`/downloads`), NOT the internal vercel-rewritten prefix `/lineage/downloads`.
* This matches the canonical-URL rule from task 02 and the nav-config rule
* from task 04: vercel.json maps `lineage.freno.me/downloads` →
* This matches the canonical-URL rule and the nav-config rule
* from the spec: vercel.json maps `lineage.freno.me/downloads` →
* `/lineage/downloads` server-side while the browser sees `/downloads`.
* Task 11 will create the matching `src/routes/lineage/downloads.tsx`.
* - `PAGE_META` is consumed verbatim by `<PageHead>`; the per-site title
* suffix (` | Life and Lineage`) is appended automatically by
* `resolvePageHeadMeta` (task 02), so the `title` here is the BASE title
* `resolvePageHeadMeta`, so the `title` here is the BASE title
* only — do NOT include the suffix.
*/
import { buildSubdomainUrl } from "~/lib/site-context";
/** Apple App Store link — surfaced unchanged from the legacy marketing page. */
export const APP_STORE_URL =
"https://apps.apple.com/us/app/life-and-lineage/id6737252442";
/**
* Public browser path to the per-subdomain downloads page (task 11).
* Public browser path to the per-subdomain downloads page.
* Subdomain-relative: renders `lineage.freno.me/downloads` in the browser.
*/
export const DOWNLOADS_HREF = "/downloads";
@@ -99,4 +101,4 @@ export const FEATURES: readonly LineageFeature[] = [
* so tests can assert the redirect target without importing the route module
* (which would pull in the server runtime).
*/
export const LEGACY_REDIRECT_TARGET = "https://lineage.freno.me";
export const LEGACY_REDIRECT_TARGET = buildSubdomainUrl("lineage");

View File

@@ -1,15 +1,15 @@
/**
* Life and Lineage privacy policy — `lineage.freno.me/privacy` (task 10).
* Life and Lineage privacy policy — `lineage.freno.me/privacy`.
*
* Migrated verbatim from the legacy
* `src/routes/privacy-policy/life-and-lineage.tsx` route so there is zero
* content loss; the old route now 308-redirects here (see
* `src/routes/privacy-policy/life-and-lineage.tsx`). PageHead is site-aware
* (task 02) so the Lineage `titleSuffix` (` | Life and Lineage`), canonical
* so the Lineage `titleSuffix` (` | Life and Lineage`), canonical
* (`https://lineage.freno.me/privacy`), and OG image derive automatically —
* we only pass the base title.
*
* Per task instructions, the account-deletion reference now points at the
* Per instructions, the account-deletion reference now points at the
* Lineage subdomain's deletion flow, served at the **public subdomain-relative
* path** `/deletion` (vercel.json rewrites to `/lineage/deletion`). The
* contact link similarly uses `/contact` (public subdomain path), consistent