Archilyzer · Source

archilyzer

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

commit a37d3329a0342ef80938fdd45ea68d798f2648e8
parent 78074df18a6f7bddf017ea394fcea899c0f62ab4
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu,  1 Oct 2026 17:19:13 -0400

editor(e2e), export(e2e): where X posts appear persists beside the login source; a private site saves and every deploy refuses it; a public site built with X private has no Posts box

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Meditor/e2e/sites-crud.spec.ts | 48++++++++++++++++++++++++++++++++++++++++++++++++
Meditor/e2e/x-session.spec.ts | 41+++++++++++++++++++++++++++++++++++++++++
Aexport/e2e/x-posts-private.spec.ts | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 193 insertions(+), 0 deletions(-)

diff --git a/editor/e2e/sites-crud.spec.ts b/editor/e2e/sites-crud.spec.ts @@ -528,3 +528,51 @@ test("brand accent radio group + wordmark lead round-trip to site.json", async ( expect("wordmarkLead" in site).toBe(false); }).toPass({ timeout: 10_000 }); }); + +// Who a site is built for — `site.json` `audience` (release 17 slice XP). A +// private site is the operator's own reading copy: saved from the form, and +// never deployed — the deploy actions refuse it before a job exists, in words +// naming the audience. (The ops routes call the same actions the Publish tab's +// buttons do.) +test("a private site saves its audience, and every deploy refuses it before any job", async ({ + page, + request, +}) => { + await resetData("empty"); + await writeSite("privsite", { siteTitle: "Private Site", cloudflareProject: "never-real" }); + + await page.goto("/sites/privsite"); + const audience = page.getByLabel("Audience", { exact: true }); + await expect(audience).toHaveValue("public"); + await audience.selectOption("private"); + await page.getByRole("button", { name: /save site/i }).click(); + await expect(page.getByRole("status").filter({ hasText: "Saved" })).toBeVisible(); + await expect(async () => { + const site = await readJson<{ audience?: string; cloudflareProject?: string }>( + "test-transcripts/sites/privsite/site.json", + ); + expect(site.audience).toBe("private"); + expect(site.cloudflareProject).toBe("never-real"); + }).toPass({ timeout: 10_000 }); + await page.reload(); + await expect(audience).toHaveValue("private"); + + const refusal = + 'Site "privsite" is private (audience: private): it is built for reading on this machine and is never deployed. Build it without deploying, or set its audience to public on its Settings tab.'; + for (const action of ["build-deploy", "deploy-site"]) { + const res = await request.post(`/api/ops/${action}`, { + headers: { authorization: "Bearer test-worker-token" }, + data: { siteId: "privsite" }, + }); + expect(res.status(), action).toBe(400); + expect(((await res.json()) as { error?: string }).error, action).toBe(refusal); + } + + // Back to public: the key is gone from the file (public is the default). + await audience.selectOption("public"); + await page.getByRole("button", { name: /save site/i }).click(); + await expect(async () => { + const site = await readJson<Record<string, unknown>>("test-transcripts/sites/privsite/site.json"); + expect("audience" in site).toBe(false); + }).toPass({ timeout: 10_000 }); +}); diff --git a/editor/e2e/x-session.spec.ts b/editor/e2e/x-session.spec.ts @@ -76,3 +76,44 @@ test("the login source select persists, and Check shows a status line", async ({ await expect(inUse).toHaveText(`In use: Browser login (${spec}) (automatic)`); expect((await readJson<{ social?: unknown }>("test-settings.json")).social).toEqual({ x: {} }); }); + +// Where X posts appear — `social.x.visibility` (release 17 slice XP). The two X +// choices share one settings block, and saveSettings replaces a nested block +// whole, so each is written over the other: choosing one keeps the other. +test("where X posts appear persists, beside the login source and without it", async ({ page }) => { + await resetData("empty"); + await page.goto("/settings"); + const visibility = page.getByLabel("Where X posts appear"); + const source = page.getByLabel("x cookie source", { exact: true }); + const social = async () => + (await readJson<{ social?: unknown }>("test-settings.json")).social; + + await expect(visibility).toHaveValue("public"); + await expect(page.locator("[data-x-posts-visibility]")).toContainText( + "Sites already published change on their next build and deploy.", + ); + + await source.selectOption("profile"); + await expect(page.getByLabel("x cookie source in use")).toHaveText("In use: Connected profile"); + + await visibility.selectOption("private"); + await expect(page.getByLabel("x posts visibility saved")).toHaveText( + "Saved. Published sites change on their next build and deploy.", + ); + expect(await social()).toEqual({ x: { cookieSource: "profile", visibility: "private" } }); + + await page.reload(); + await expect(visibility).toHaveValue("private"); + + // The login source back to automatic keeps the visibility… + await source.selectOption("auto"); + await expect(page.getByLabel("x cookie source in use")).toHaveText( + "In use: Connected profile (automatic)", + ); + expect(await social()).toEqual({ x: { visibility: "private" } }); + + // …and public is the default, written as no key. + await visibility.selectOption("public"); + await expect(page.getByLabel("x posts visibility saved")).toBeVisible(); + expect(await social()).toEqual({ x: {} }); +}); diff --git a/export/e2e/x-posts-private.spec.ts b/export/e2e/x-posts-private.spec.ts @@ -0,0 +1,104 @@ +import { expect, test, type Page } from "@playwright/test"; +import { + POST_CHANNEL, + POST_CHANNEL_SLUG, + POST_REPLY_ID, + POST_ROOT_ID, + postsPage, +} from "./fixtures/data"; +import { installRoutes, openFilters } from "./helpers"; + +// X posts are private (release 17 slice XP): with `social.x.visibility` +// "private", a PUBLIC site's build carries no X channel and a PRIVATE site's +// build carries all of it. The build itself — the index build's per-site posts +// manifest and compose's posts tree — is pinned through the real code by +// common/bin/compose-site.postsVisibility.test.ts: this suite's data is +// route-mocked, never built. Here the two posts manifests that build writes +// are served, and the visitor's side is checked: the Search in row's Posts box +// and the post hits come and go with the manifest, with nothing special-cased. +// +// The fixture posts say "kappa" (two of them); no video does. + +const X_POST_SLUGS = [ + `${POST_CHANNEL_SLUG}/${POST_ROOT_ID}`, + `${POST_CHANNEL_SLUG}/${POST_REPLY_ID}`, +]; + +const fulfillJson = (body: unknown) => ({ + status: 200, + contentType: "application/json", + body: JSON.stringify(body), +}); + +// The site posts manifest compose writes: the X channel listed (a private +// site's build), or no channel at all (a public site whose only posts were X +// posts). Routed after installRoutes, so these answers win. +async function servePostsManifest(page: Page, built: "public" | "private") { + await page.route("**/posts/manifest.json", (route) => + route.fulfill( + fulfillJson({ + version: 1, + channels: + built === "private" + ? [{ name: POST_CHANNEL, slug: POST_CHANNEL_SLUG, postCount: 4, platform: "twitter" }] + : [], + totalCount: built === "private" ? 4 : 0, + generatedAt: new Date().toISOString(), + }), + ), + ); + await page.route(/\/posts\/[^/]+\/page-\d+\.json$/, (route) => + route.fulfill( + fulfillJson( + postsPage().map((p) => ({ + ...p, + platform: "twitter", + url: `https://x.com/tester/status/${p.id}`, + })), + ), + ), + ); +} + +const postsBox = (page: Page) => + page.getByTestId("search-in-row").getByRole("checkbox", { name: "Posts", exact: true }); + +async function search(page: Page, q: string) { + await page.locator('input[data-testid^="leaf-query-"]').first().fill(q); + await page.getByTestId("search-submit").click(); +} + +test.describe("X posts private", () => { + test.beforeEach(async ({ page }) => { + await installRoutes(page); + }); + + test("a public site built with X posts private has no Posts box and finds no X post", async ({ + page, + }) => { + await servePostsManifest(page, "public"); + await page.goto("/"); + await openFilters(page); + await expect(page.getByRole("checkbox", { name: "Transcripts", exact: true })).toBeVisible(); + await expect(postsBox(page)).toHaveCount(0); + await search(page, "kappa"); + await expect(page.getByText(/^searched \d+\/\d+$/)).toBeVisible({ timeout: 15_000 }); + await expect(page.getByTestId("results-summary")).toHaveText("Matching videos (0)"); + await expect(page.locator(`[data-result-slug^="${POST_CHANNEL_SLUG}/"]`)).toHaveCount(0); + }); + + test("a private site's build shows the X posts", async ({ page }) => { + await servePostsManifest(page, "private"); + await page.goto("/"); + await openFilters(page); + await expect(postsBox(page)).toBeChecked(); + await search(page, "kappa"); + const cards = page.locator("[data-card-header]"); + await expect(async () => { + const got = await cards.evaluateAll((els) => + els.map((e) => e.getAttribute("data-result-slug") ?? ""), + ); + expect(got.slice().sort()).toEqual(X_POST_SLUGS.slice().sort()); + }).toPass({ timeout: 15_000 }); + }); +});