commit 8bde3a944771f807bf78419c885d58afc3b4ba43
parent 0f7fa1d1db2b072ddcf26c27141a4c1cc510f9ea
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sun, 27 Sep 2026 00:19:22 -0400
hub: an official instance's named accent is painted per base
officialInstances (common) now resolves a named accent to its swatch, so the
shelf card, the scope chip, the result edge and the channel group wear the
accent's value on the base in force rather than its on-dark hex on every
base. Comments updated; official-instances.spec checks a Brass member's stripe
on dark, light and sepia.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
3 files changed, 48 insertions(+), 8 deletions(-)
diff --git a/export/app/components/hub/ArchiveShelf.tsx b/export/app/components/hub/ArchiveShelf.tsx
@@ -15,10 +15,11 @@
// descriptor gave. The live federation total is HubStats, under the shelf.
//
// The official cards come in the homepage's order (the summary's `sites`, the
-// order its ArchiveCards and growth chart draw in), and each wears its
-// archive's OWN accent when the site sets one (the tool has no colour, the
-// archives do); one that sets none wears seriesColor() at its index in the
-// summary, the colour of its homepage card. Order and colour are useHubSites'
+// order its ArchiveCards and growth chart draw in), and each wears the colour
+// its homepage card wears (lib/siteColor.ts): its archive's OWN accent when the
+// site sets one (the tool has no colour, the archives do) — a named accent at
+// its value on the base in force — and seriesColor() at its index in the
+// summary when it sets none. Order and colour are useHubSites'
// (officialInstances), shared with the scope chips, the results' stripes and
// /ask, so an archive is one colour on every surface. With no summary the order
// is hub-sites.json's and the colour the card's place. An added archive with
diff --git a/export/app/components/hub/useHubSites.ts b/export/app/components/hub/useHubSites.ts
@@ -4,9 +4,9 @@
// the scope chips, the search (its result stripes, chip dots and channel groups)
// and /ask: ONE order and ONE colour per archive, decided here.
//
-// The official instances come in the homepage's order, in the homepage's
-// colours unless a site sets its own accent (officialInstances,
-// common/lib/hubSummary.ts). That order is /hub-summary.json's, so they are
+// The official instances come in the homepage's order, each in the colour its
+// homepage card wears — its own accent (a named one per base), else the chart
+// palette's at its place (officialInstances, common/lib/hubSummary.ts). That order is /hub-summary.json's, so they are
// listed only once that file has settled — found, missing or unreadable —
// rather than in hub-sites.json's order first and reordered a moment later.
// Both files are small, same-origin and requested together, so the wait is the
diff --git a/export/e2e-hub/official-instances.spec.ts b/export/e2e-hub/official-instances.spec.ts
@@ -1,4 +1,5 @@
import { expect, test, type Page, type Route } from "@playwright/test";
+import { ACCENTS } from "../../common/lib/brand";
// The hub's "Official Instances": one card per built-in member
// (hub-sites.json), carrying the homepage's own figures for it when the hub was
@@ -21,13 +22,14 @@ async function fulfillJson(route: Route, body: unknown) {
});
}
-async function stubMember(page: Page) {
+async function stubMember(page: Page, { accent }: { accent?: string } = {}) {
await page.route("**/hub-sites.json", (r) =>
fulfillJson(r, [
{
siteId: "member",
siteTitle: "Member Archive",
siteUrl: MEMBER,
+ ...(accent ? { accent } : {}),
pwa: false,
contract: 1,
},
@@ -148,4 +150,41 @@ test.describe("hub official instances", () => {
await expect(card.getByText("Data-only")).toBeVisible();
await expect(card.locator("dl")).toHaveCount(0);
});
+
+ // Release 10: a NAMED accent is painted at its value on the base in force.
+ // hub-sites.json carries only its on-dark hex; hub-summary.json names the
+ // accent (`accentId`), and the card wears `var(--swatch-<id>)`.
+ test("a named accent wears its own value on each base", async ({ page }) => {
+ await stubMember(page, { accent: ACCENTS.brass.onDark });
+ await page.route("**/hub-summary.json", (r) =>
+ fulfillJson(r, {
+ ...SUMMARY,
+ sites: [
+ { ...SUMMARY.sites[0], accent: ACCENTS.brass.onDark, accentId: "brass" },
+ ],
+ }),
+ );
+ const stripe = page
+ .getByTestId("shelf-spine")
+ .filter({ hasText: "Member Archive" })
+ .locator(":scope > span[aria-hidden='true']");
+ const colour = () =>
+ stripe.evaluate((el) => getComputedStyle(el).backgroundColor);
+ const rgb = (hex: string) =>
+ `rgb(${[1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16)).join(", ")})`;
+
+ // The hub opens dark.
+ await page.goto("/");
+ await expect(page.locator("html")).toHaveAttribute("data-base", "dark");
+ await expect.poll(colour).toBe(rgb(ACCENTS.brass.onDark));
+
+ for (const base of ["light", "sepia"] as const) {
+ await page.evaluate((b) => localStorage.setItem("ytdlp-tb:base", b), base);
+ await page.reload();
+ await expect(page.locator("html")).toHaveAttribute("data-base", base);
+ await expect
+ .poll(colour)
+ .toBe(rgb(base === "light" ? ACCENTS.brass.onLight : ACCENTS.brass.onSepia));
+ }
+ });
});