From 82fd9975eb944d9b347d655b81b3f52f8a42ba77 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 06:45:25 +0000 Subject: [PATCH 01/18] feat(admin): migrate admin surfaces onto edition-view role tokens Scopes the admin layout root with EditionViewRoot and migrates the non-portal hardcoded color sites (text-white, bg-white/10, border-white/20, bg-purple-600) onto the role tokens from #363, with no visual change since each token carries today's exact value. Colors inside Radix Dialog/AlertDialog portals are left as-is since those portals render outside the edition-view CSS variable scope. --- .../ArtistsManagement/ArchiveArtistDialog.tsx | 2 +- .../components/BulkEditorHeader.tsx | 2 +- src/pages/admin/festivals/EditionNavLink.tsx | 6 +-- .../festivals/FestivalEditionManagement.tsx | 2 +- .../festivals/FestivalManagementSection.tsx | 2 +- .../StageManagement/CreateStageDialog.tsx | 2 +- src/routes/admin.tsx | 21 ++++----- src/routes/admin/analytics.tsx | 44 +++++++++---------- .../$festivalSlug/editions/$editionSlug.tsx | 2 +- .../editions/$editionSlug/sets.tsx | 2 +- 10 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/pages/admin/ArtistsManagement/ArchiveArtistDialog.tsx b/src/pages/admin/ArtistsManagement/ArchiveArtistDialog.tsx index f83abae43..7ee82420e 100644 --- a/src/pages/admin/ArtistsManagement/ArchiveArtistDialog.tsx +++ b/src/pages/admin/ArtistsManagement/ArchiveArtistDialog.tsx @@ -49,7 +49,7 @@ export function ArchiveArtistDialog({ diff --git a/src/pages/admin/ArtistsManagement/components/BulkEditorHeader.tsx b/src/pages/admin/ArtistsManagement/components/BulkEditorHeader.tsx index b4434652d..f5609fbf7 100644 --- a/src/pages/admin/ArtistsManagement/components/BulkEditorHeader.tsx +++ b/src/pages/admin/ArtistsManagement/components/BulkEditorHeader.tsx @@ -33,7 +33,7 @@ export function BulkEditorHeader({ onAddArtist }: BulkEditorHeaderProps) { diff --git a/src/routes/admin.tsx b/src/routes/admin.tsx index aefcd2399..74218256c 100644 --- a/src/routes/admin.tsx +++ b/src/routes/admin.tsx @@ -7,6 +7,7 @@ import { useUserPermissionsQuery } from "@/api/auth/useUserPermissions"; import { useEffect } from "react"; import { Music, Calendar, BarChart3, UserPlus } from "lucide-react"; import { pageMeta } from "@/lib/pageHead"; +import { EditionViewRoot } from "@/pages/EditionView/EditionViewRoot"; export const Route = createFileRoute("/admin")({ component: AdminLayout, @@ -76,16 +77,16 @@ function AdminLayout() { if (isLoadingPermissions || authLoading || isLoadingSuperAdmin) { return ( -
-
Loading...
-
+ +
Loading...
+
); } if (!canEdit) return null; return ( -
+
@@ -96,18 +97,18 @@ function AdminLayout() { className="w-full" > Artists Festival Management @@ -116,14 +117,14 @@ function AdminLayout() { <> Analytics Admin Roles @@ -138,6 +139,6 @@ function AdminLayout() {
- + ); } diff --git a/src/routes/admin/analytics.tsx b/src/routes/admin/analytics.tsx index a5e7dd401..9fb18d7cb 100644 --- a/src/routes/admin/analytics.tsx +++ b/src/routes/admin/analytics.tsx @@ -37,35 +37,35 @@ function AdminAnalytics() { return (
- + - + Groups Analytics - + Groups with member counts - - Group Name - Members - Created + + Group Name + Members + Created {groupAnalytics.map((group) => ( - + {group.name} - + {group.member_count} - + {new Date(group.created_at).toLocaleDateString()} @@ -75,39 +75,39 @@ function AdminAnalytics() { - + - + Users Analytics - + Users with vote counts
- - Username - Email - Votes - Joined + + Username + Email + Votes + Joined {userAnalytics.map((user) => ( - + {user.username || "No username"} - + {user.email || "No email"} - + {user.vote_count} - + {new Date(user.created_at).toLocaleDateString()} diff --git a/src/routes/admin/festivals/$festivalSlug/editions/$editionSlug.tsx b/src/routes/admin/festivals/$festivalSlug/editions/$editionSlug.tsx index 4124018a9..d2d958a96 100644 --- a/src/routes/admin/festivals/$festivalSlug/editions/$editionSlug.tsx +++ b/src/routes/admin/festivals/$festivalSlug/editions/$editionSlug.tsx @@ -75,7 +75,7 @@ function FestivalEdition() { return (
-
+
- + Group Name Members Created @@ -75,7 +75,7 @@ function AdminAnalytics() { - + @@ -88,7 +88,7 @@ function AdminAnalytics() {
- + Username Email Votes From 96c021272654acce0817f333544b916b5c9c51d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 07:12:00 +0000 Subject: [PATCH 03/18] =?UTF-8?q?fix(admin):=20address=20PR=20review=20?= =?UTF-8?q?=E2=80=94=20move=20role=20tokens=20to=20:root,=20pair=20accent?= =?UTF-8?q?=20states?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review feedback: moves the edition-view role tokens from the .edition-view scope class onto :root so the whole app shares one set of values instead of admin (or any other unmigrated surface) keeping stale shadcn defaults for the same variable names — this also fixes Radix portal content never seeing the override, since :root is the ultimate ancestor of a portal too. Adds a real accent-hover token (purple-700) so CTAs stop mixing a token fill with a literal hover color, pairs every bg-accent use with text-accent-foreground, and migrates the remaining non-portal literals flagged in review (analytics.tsx borders via border-foreground/NN, LinkWizardTable via bg-accent/20) instead of leaving them hardcoded. --- docs/design/edition-color-vocabulary.md | 7 +- src/index.css | 94 +++++++++---------- src/pages/EditionView/EditionViewRoot.tsx | 7 +- .../ArtistsManagement/ArchiveArtistDialog.tsx | 2 +- .../components/BulkEditorHeader.tsx | 2 +- src/pages/admin/festivals/EditionNavLink.tsx | 2 +- .../festivals/FestivalEditionManagement.tsx | 2 +- .../festivals/FestivalManagementSection.tsx | 2 +- .../festivals/LinkWizard/LinkWizardTable.tsx | 4 +- .../StageManagement/CreateStageDialog.tsx | 2 +- src/routes/admin.tsx | 8 +- src/routes/admin/analytics.tsx | 12 +-- .../editions/$editionSlug/sets.tsx | 2 +- tailwind.config.ts | 1 + 14 files changed, 73 insertions(+), 74 deletions(-) diff --git a/docs/design/edition-color-vocabulary.md b/docs/design/edition-color-vocabulary.md index a0f818fcc..150e6ec56 100644 --- a/docs/design/edition-color-vocabulary.md +++ b/docs/design/edition-color-vocabulary.md @@ -9,7 +9,7 @@ This names the **roles** colors play in the voter-facing edition views, independ - **Reuse and retheme the existing shadcn token names** where a role already exists (`background`, `foreground`, `muted-foreground`, `border`, `ring`, `popover`, `accent`, `destructive`). New names are added only for roles shadcn doesn't cover. No `ed-*` namespace. - **No numeric ramps.** Each role is one value per theme; "lighter/darker" variants are expressed as alpha on the base token (Tailwind `/NN` over an HSL var) or as an explicit `-soft` companion, not as `-100…-900` scales. - **`/NN` opacity modifiers work only on opaque roles** (`foreground`, `accent`, `live`, `vote-must`, …). The inherently translucent roles (`surface`/`surface-raised`/`surface-active`, `accent-soft`, `border`/`border-strong`, `*-soft`) carry their alpha inside the CSS variable, so `bg-surface/50` expands to a double-alpha `hsl(… / 0.05 / .5)` — silently invalid CSS. Use the next step in the role family (`surface` → `surface-raised` → `surface-active`) instead of a modifier. -- **Portals escape the scope.** The variables live on the edition root (`EditionViewRoot`), but Radix `Select`/`Popover`/`Sheet` content portals under `document.body` and resolves the global values there. Before tokenizing portaled content, give it the scope (portal `container` inside the edition root, or the scope class on the content element). +- **The tokens live on `:root`, not a scope class (#367).** They used to sit under a `.edition-view` class so only voter-facing pages picked them up, but that meant every other surface (admin included) kept the old shadcn light-theme defaults for the same variable names — so a partial migration elsewhere (like admin's) recolored whatever hadn't been touched yet, and Radix portal content (which renders under `document.body`, outside any wrapper's scope) never picked up the override at all. The whole app renders on the same dark identity today, so there's one set of values for the whole app and no scope class to remember. `EditionViewRoot` still supplies the `bg-app-gradient` ground and is where the flip (#359) will attach `data-edition-theme="light"`. - Values below reference today's hardcoded classes only to define the mapping; the actual palette is chosen in #320/#321. ## Token set @@ -40,11 +40,14 @@ This names the **roles** colors play in the voter-facing edition views, independ | `border-strong` **(new)** | Emphasis / selected outlines | `border-purple-400/40`–`/50`, `border-purple-400`, `border-white/50`–`/80` | | `ring` | Focus/selection ring | `ring-purple-400`, `ring-white` | +A hairline drawn in `border-white/NN` (not purple) doesn't fit either border role — reach for `border-foreground/NN` instead: `foreground` is opaque and already carries today's white value, so the opacity modifier reproduces the exact color without borrowing a fill role (`surface`) for a border. + ### Brand accent (interactive) | Token | Role | Absorbs today | | ------------------------------ | ------------------------------------------------ | -------------------------------------------------- | -| `accent` / `accent-foreground` | Primary interactive fill: selected toggles, CTAs | `bg-purple-600`, `bg-purple-700`, `bg-purple-400` | +| `accent` / `accent-foreground` | Primary interactive fill: selected toggles, CTAs | `bg-purple-600`, `bg-purple-400` | +| `accent-hover` **(new)** | Hover state for the accent fill | `hover:bg-purple-700` | | `accent-soft` **(new)** | Translucent accent wash for selected/hover chips | `bg-purple-600/30`–`/60`, `bg-purple-400/10`–`/30` | | `text-accent` | Accent-colored text/icons | `text-purple-400` | diff --git a/src/index.css b/src/index.css index dd383d647..160cd1055 100644 --- a/src/index.css +++ b/src/index.css @@ -7,13 +7,13 @@ @layer base { :root { --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; + --foreground: 0 0% 100%; /* text-white */ --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; + --popover: 215 27.9% 16.9%; /* gray-800 */ + --popover-foreground: 268.7 100% 95.5%; /* purple-100 */ --primary: 222.2 47.4% 11.2%; --primary-foreground: 210 40% 98%; @@ -22,17 +22,40 @@ --secondary-foreground: 222.2 47.4% 11.2%; --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; + --muted-foreground: 268.6 100% 91.8%; /* purple-200 */ + --subtle-foreground: 269.2 97.4% 85.1%; /* purple-300 */ - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; + --surface: 0 0% 100% / 0.05; /* bg-white/5 */ + --surface-raised: 0 0% 100% / 0.1; /* bg-white/10 */ + --surface-active: 0 0% 100% / 0.2; /* bg-white/20 */ - --destructive: 0 84.2% 60.2%; + --accent: 271.5 81.3% 55.9%; /* purple-600 */ + --accent-foreground: 0 0% 100%; + --accent-hover: 272.1 71.7% 47.1%; /* purple-700 */ + --accent-soft: 271.5 81.3% 55.9% / 0.4; /* bg-purple-600/40 */ + + --destructive: 0 90.6% 70.8%; /* red-400 */ --destructive-foreground: 210 40% 98%; - --border: 214.3 31.8% 91.4%; + --border: 270 95.2% 75.3% / 0.3; /* border-purple-400/30 */ + --border-strong: 270 95.2% 75.3% / 0.5; /* border-purple-400/50 */ --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; + --ring: 270 95.2% 75.3%; /* purple-400 */ + + --live: 292 91.4% 72.5%; /* fuchsia-400 */ + --live-foreground: 288.3 95.8% 90.6%; /* fuchsia-200 */ + --notice: 43.3 96.4% 56.3%; /* amber-400 */ + --notice-foreground: 20.9 91.7% 14.1%; /* amber-950 */ + + --vote-must: 20.5 90.2% 48.2%; /* orange-600 */ + --vote-must-foreground: 27 96% 61%; /* orange-400 */ + --vote-must-soft: 15.3 74.6% 27.8% / 0.2; /* orange-900/20 */ + --vote-interested: 221.2 83.2% 53.3%; /* blue-600 */ + --vote-interested-foreground: 213.1 93.9% 67.8%; /* blue-400 */ + --vote-interested-soft: 224.4 64.3% 32.9% / 0.2; /* blue-900/20 */ + --vote-skip: 215 13.8% 34.1%; /* gray-600 */ + --vote-skip-foreground: 217.9 10.6% 64.9%; /* gray-400 */ + --vote-skip-soft: 220.9 39.3% 11% / 0.2; /* gray-900/20 */ --radius: 0.5rem; @@ -92,56 +115,29 @@ --app-button-hover: 195 85% 40%; } - /* Edition-view identity tokens (#317 / #355). + /* Edition-view identity tokens (#317 / #355 / #367). Role vocabulary: docs/design/edition-color-vocabulary.md Target values: docs/design/edition-identity-spec.md Current values are TODAY's rendered colors so migrating hardcoded classes onto these tokens is a zero-visual-change operation; the Festival v2 values land in the flip (#359). + These used to live under a `.edition-view` scope class so only + voter-facing pages picked them up, but that meant every other surface + (admin included) still carried the old shadcn light-theme defaults for + the same variable names, which made partial migrations (like #367's + admin sweep) recolor whatever hadn't been touched yet. The whole app + renders on the same dark identity today, so the tokens now live + directly on `:root` — one set of values for the whole app, no scope + class required. Ground stays bg-app-gradient until the flip, so --background and --app-gradient-from/to are not overridden here. Translucent roles carry their alpha inside the variable (hsl H S% L% / A form), consumed via the same hsl(var(--x)) plumbing. */ - .edition-view { - --foreground: 0 0% 100%; /* text-white */ - --muted-foreground: 268.6 100% 91.8%; /* purple-200 */ - --subtle-foreground: 269.2 97.4% 85.1%; /* purple-300 */ - - --surface: 0 0% 100% / 0.05; /* bg-white/5 */ - --surface-raised: 0 0% 100% / 0.1; /* bg-white/10 */ - --surface-active: 0 0% 100% / 0.2; /* bg-white/20 */ - --popover: 215 27.9% 16.9%; /* gray-800 */ - --popover-foreground: 268.7 100% 95.5%; /* purple-100 */ - - --border: 270 95.2% 75.3% / 0.3; /* border-purple-400/30 */ - --border-strong: 270 95.2% 75.3% / 0.5; /* border-purple-400/50 */ - --ring: 270 95.2% 75.3%; /* purple-400 */ - - --accent: 271.5 81.3% 55.9%; /* purple-600 */ - --accent-foreground: 0 0% 100%; - --accent-soft: 271.5 81.3% 55.9% / 0.4; /* bg-purple-600/40 */ - - --live: 292 91.4% 72.5%; /* fuchsia-400 */ - --live-foreground: 288.3 95.8% 90.6%; /* fuchsia-200 */ - --notice: 43.3 96.4% 56.3%; /* amber-400 */ - --notice-foreground: 20.9 91.7% 14.1%; /* amber-950 */ - --destructive: 0 90.6% 70.8%; /* red-400 */ - - --vote-must: 20.5 90.2% 48.2%; /* orange-600 */ - --vote-must-foreground: 27 96% 61%; /* orange-400 */ - --vote-must-soft: 15.3 74.6% 27.8% / 0.2; /* orange-900/20 */ - --vote-interested: 221.2 83.2% 53.3%; /* blue-600 */ - --vote-interested-foreground: 213.1 93.9% 67.8%; /* blue-400 */ - --vote-interested-soft: 224.4 64.3% 32.9% / 0.2; /* blue-900/20 */ - --vote-skip: 215 13.8% 34.1%; /* gray-600 */ - --vote-skip-foreground: 217.9 10.6% 64.9%; /* gray-400 */ - --vote-skip-soft: 220.9 39.3% 11% / 0.2; /* gray-900/20 */ - } - /* Light-theme scope stub per the CSS contract in the identity spec: - light values apply under an explicit data-edition-theme="light" on the - edition root. Values land with the flip (#359). */ - .edition-view[data-edition-theme="light"] { + /* Light-theme stub per the CSS contract in the identity spec: light values + apply under an explicit data-edition-theme="light" on the document root. + Values land with the flip (#359). */ + :root[data-edition-theme="light"] { } .dark { diff --git a/src/pages/EditionView/EditionViewRoot.tsx b/src/pages/EditionView/EditionViewRoot.tsx index 6ea40aae0..a4ff7578f 100644 --- a/src/pages/EditionView/EditionViewRoot.tsx +++ b/src/pages/EditionView/EditionViewRoot.tsx @@ -1,9 +1,10 @@ import { ReactNode } from "react"; import { cn } from "@/lib/utils"; -// The identity scope for the voter-facing edition views: `edition-view` -// anchors the CSS variables defined in src/index.css, and the flip (#359) -// attaches data-edition-theme="light" to this same node per +// The identity root for the voter-facing edition views: supplies the +// bg-app-gradient ground (the role-token CSS variables themselves live on +// :root per docs/design/edition-color-vocabulary.md, not on this class), and +// the flip (#359) attaches data-edition-theme="light" to this same node per // docs/design/edition-identity-spec.md. export function EditionViewRoot({ className, diff --git a/src/pages/admin/ArtistsManagement/ArchiveArtistDialog.tsx b/src/pages/admin/ArtistsManagement/ArchiveArtistDialog.tsx index 7ee82420e..471759941 100644 --- a/src/pages/admin/ArtistsManagement/ArchiveArtistDialog.tsx +++ b/src/pages/admin/ArtistsManagement/ArchiveArtistDialog.tsx @@ -49,7 +49,7 @@ export function ArchiveArtistDialog({ diff --git a/src/pages/admin/ArtistsManagement/components/BulkEditorHeader.tsx b/src/pages/admin/ArtistsManagement/components/BulkEditorHeader.tsx index f5609fbf7..29656c3a6 100644 --- a/src/pages/admin/ArtistsManagement/components/BulkEditorHeader.tsx +++ b/src/pages/admin/ArtistsManagement/components/BulkEditorHeader.tsx @@ -33,7 +33,7 @@ export function BulkEditorHeader({ onAddArtist }: BulkEditorHeaderProps) { diff --git a/src/routes/admin.tsx b/src/routes/admin.tsx index 74218256c..ac9d44a74 100644 --- a/src/routes/admin.tsx +++ b/src/routes/admin.tsx @@ -101,14 +101,14 @@ function AdminLayout() { > Artists Festival Management @@ -117,14 +117,14 @@ function AdminLayout() { <> Analytics Admin Roles diff --git a/src/routes/admin/analytics.tsx b/src/routes/admin/analytics.tsx index 936f73944..cd40b0b16 100644 --- a/src/routes/admin/analytics.tsx +++ b/src/routes/admin/analytics.tsx @@ -37,7 +37,7 @@ function AdminAnalytics() { return (
- + @@ -50,7 +50,7 @@ function AdminAnalytics() {
- + Group Name Members Created @@ -58,7 +58,7 @@ function AdminAnalytics() { {groupAnalytics.map((group) => ( - + {group.name} @@ -75,7 +75,7 @@ function AdminAnalytics() { - + @@ -88,7 +88,7 @@ function AdminAnalytics() {
- + Username Email Votes @@ -97,7 +97,7 @@ function AdminAnalytics() { {userAnalytics.map((user) => ( - + {user.username || "No username"} diff --git a/src/routes/admin/festivals/$festivalSlug/editions/$editionSlug/sets.tsx b/src/routes/admin/festivals/$festivalSlug/editions/$editionSlug/sets.tsx index b14156b5c..63ca1ac46 100644 --- a/src/routes/admin/festivals/$festivalSlug/editions/$editionSlug/sets.tsx +++ b/src/routes/admin/festivals/$festivalSlug/editions/$editionSlug/sets.tsx @@ -92,7 +92,7 @@ function FestivalSets() {
- + ); } From 7093b355cf6bf01c1c69d92e6a944387ff3442ff Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 07:28:17 +0000 Subject: [PATCH 05/18] fix: restore legible text/borders on light-background surfaces after :root token migration The AuthDialog, toast notifications, and admin Artists table (Bulk Editor and generic Table primitives) render on white backgrounds but were picking up the dark edition-view identity's now-global muted-foreground/ foreground values, washing out text, placeholders, and the input focus ring. Add explicit gray/black overrides at these light-surface call sites and shared table defaults, without reverting the :root migration. Also removes the now-vestigial EditionViewRoot wrapper (it had shrunk to a single div once its token-scoping job moved to :root) and the stale CSS comment block in index.css. --- docs/design/edition-color-vocabulary.md | 2 +- src/components/AuthDialog/AuthDialog.tsx | 4 ++-- src/components/AuthDialog/EmailStep.tsx | 1 + src/components/ui/table.tsx | 9 +++++--- src/components/ui/toaster.tsx | 6 ++++-- src/pages/EditionView/EditionViewRoot.tsx | 21 ------------------- .../BulkEditor/GenresCell.tsx | 4 +--- .../ArtistsManagement/BulkEditor/TextCell.tsx | 2 +- .../BulkEditor/TextareaCell.tsx | 2 +- .../ArtistsManagement/BulkEditor/UrlCell.tsx | 2 +- .../$festivalSlug/editions/$editionSlug.tsx | 9 ++++---- 11 files changed, 22 insertions(+), 40 deletions(-) delete mode 100644 src/pages/EditionView/EditionViewRoot.tsx diff --git a/docs/design/edition-color-vocabulary.md b/docs/design/edition-color-vocabulary.md index 150e6ec56..d5345dfa4 100644 --- a/docs/design/edition-color-vocabulary.md +++ b/docs/design/edition-color-vocabulary.md @@ -9,7 +9,7 @@ This names the **roles** colors play in the voter-facing edition views, independ - **Reuse and retheme the existing shadcn token names** where a role already exists (`background`, `foreground`, `muted-foreground`, `border`, `ring`, `popover`, `accent`, `destructive`). New names are added only for roles shadcn doesn't cover. No `ed-*` namespace. - **No numeric ramps.** Each role is one value per theme; "lighter/darker" variants are expressed as alpha on the base token (Tailwind `/NN` over an HSL var) or as an explicit `-soft` companion, not as `-100…-900` scales. - **`/NN` opacity modifiers work only on opaque roles** (`foreground`, `accent`, `live`, `vote-must`, …). The inherently translucent roles (`surface`/`surface-raised`/`surface-active`, `accent-soft`, `border`/`border-strong`, `*-soft`) carry their alpha inside the CSS variable, so `bg-surface/50` expands to a double-alpha `hsl(… / 0.05 / .5)` — silently invalid CSS. Use the next step in the role family (`surface` → `surface-raised` → `surface-active`) instead of a modifier. -- **The tokens live on `:root`, not a scope class (#367).** They used to sit under a `.edition-view` class so only voter-facing pages picked them up, but that meant every other surface (admin included) kept the old shadcn light-theme defaults for the same variable names — so a partial migration elsewhere (like admin's) recolored whatever hadn't been touched yet, and Radix portal content (which renders under `document.body`, outside any wrapper's scope) never picked up the override at all. The whole app renders on the same dark identity today, so there's one set of values for the whole app and no scope class to remember. `EditionViewRoot` still supplies the `bg-app-gradient` ground and is where the flip (#359) will attach `data-edition-theme="light"`. +- **The tokens live on `:root`, not a scope class (#367).** They used to sit under a `.edition-view` class so only voter-facing pages picked them up, but that meant every other surface (admin included) kept the old shadcn light-theme defaults for the same variable names — so a partial migration elsewhere (like admin's) recolored whatever hadn't been touched yet, and Radix portal content (which renders under `document.body`, outside any wrapper's scope) never picked up the override at all. The whole app renders on the same dark identity today, so there's one set of values for the whole app and no scope class to remember. Each root layout applies `bg-app-gradient` directly; the flip (#359) will attach `data-edition-theme="light"` to the document root. - Values below reference today's hardcoded classes only to define the mapping; the actual palette is chosen in #320/#321. ## Token set diff --git a/src/components/AuthDialog/AuthDialog.tsx b/src/components/AuthDialog/AuthDialog.tsx index bc9819de7..6663378bf 100644 --- a/src/components/AuthDialog/AuthDialog.tsx +++ b/src/components/AuthDialog/AuthDialog.tsx @@ -42,7 +42,7 @@ export function AuthDialog({ - + {step === "otp" && (
(({ className, ...props }, ref) => ( )); @@ -99,7 +102,7 @@ const TableCaption = React.forwardRef< >(({ className, ...props }, ref) => (
)); diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx index 5887f0804..c374b05f3 100644 --- a/src/components/ui/toaster.tsx +++ b/src/components/ui/toaster.tsx @@ -15,11 +15,13 @@ export function Toaster() { {toasts.map(function ({ id, title, description, action, ...props }) { return ( - +
{title && {title}} {description && ( - {description} + + {description} + )}
{action} diff --git a/src/pages/EditionView/EditionViewRoot.tsx b/src/pages/EditionView/EditionViewRoot.tsx deleted file mode 100644 index a4ff7578f..000000000 --- a/src/pages/EditionView/EditionViewRoot.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { ReactNode } from "react"; -import { cn } from "@/lib/utils"; - -// The identity root for the voter-facing edition views: supplies the -// bg-app-gradient ground (the role-token CSS variables themselves live on -// :root per docs/design/edition-color-vocabulary.md, not on this class), and -// the flip (#359) attaches data-edition-theme="light" to this same node per -// docs/design/edition-identity-spec.md. -export function EditionViewRoot({ - className, - children, -}: { - className?: string; - children: ReactNode; -}) { - return ( -
- {children} -
- ); -} diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/GenresCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/GenresCell.tsx index 38d53967c..58724af64 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/GenresCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/GenresCell.tsx @@ -96,9 +96,7 @@ export function GenresCell({ value, onSave }: GenresCellProps) { ))} ) : ( - - Click to add genres... - + Click to add genres... )} ); diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/TextCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/TextCell.tsx index 2c909d9c8..316aa75c7 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/TextCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/TextCell.tsx @@ -79,7 +79,7 @@ export function TextCell({ title="Click to edit" > {value || ( - + {placeholder || "Click to add..."} )} diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/TextareaCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/TextareaCell.tsx index 833192856..a15ad7195 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/TextareaCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/TextareaCell.tsx @@ -74,7 +74,7 @@ export function TextareaCell({ {value ? ( ) : ( - + {placeholder || "Click to add..."} )} diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/UrlCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/UrlCell.tsx index 26a0ecbda..af4be402d 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/UrlCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/UrlCell.tsx @@ -96,7 +96,7 @@ export function UrlCell({ value, placeholder, onSave }: UrlCellProps) { title="Click to edit" > {displayValue || ( - + {placeholder || "Click to add..."} )} diff --git a/src/routes/festivals/$festivalSlug/editions/$editionSlug.tsx b/src/routes/festivals/$festivalSlug/editions/$editionSlug.tsx index 7a3e67600..181dea939 100644 --- a/src/routes/festivals/$festivalSlug/editions/$editionSlug.tsx +++ b/src/routes/festivals/$festivalSlug/editions/$editionSlug.tsx @@ -16,7 +16,6 @@ import { getDefaultTab } from "@/pages/EditionView/TabNavigation/defaultTab"; import { tabRoutes } from "@/pages/EditionView/TabNavigation/tabRoutes"; import { pageMeta } from "@/lib/pageHead"; import { SupabaseNotFoundError } from "@/lib/supabaseErrors"; -import { EditionViewRoot } from "@/pages/EditionView/EditionViewRoot"; export const Route = createFileRoute( "/festivals/$festivalSlug/editions/$editionSlug", @@ -83,7 +82,7 @@ function EditionLayout() { const { festival, edition } = Route.useRouteContext(); return ( - +
-
+ ); } @@ -107,7 +106,7 @@ function EditionNotFound() { const { festivalSlug } = Route.useParams(); return ( - +

Edition not found

@@ -122,6 +121,6 @@ function EditionNotFound() { Back to festival

- +
); } From a630a2a9b06b64a5f384f16dd79dec5249f20487 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 07:59:04 +0000 Subject: [PATCH 06/18] fix: restore legible placeholder text color in ImageCell --- src/pages/admin/ArtistsManagement/BulkEditor/ImageCell.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/ImageCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/ImageCell.tsx index cd8de51a7..32035f833 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/ImageCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/ImageCell.tsx @@ -26,7 +26,7 @@ export function ImageCell({ }} /> ) : ( - add image... + add image... ); return ( From 1ed193b669bf865470be2b5b656b0dba6dddc86b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 08:01:22 +0000 Subject: [PATCH 07/18] refactor: tokenize light-surface text/border colors instead of literal grays Introduces surface-foreground/surface-muted-foreground/surface-border tokens for white-background surfaces (dialogs, toasts, admin tables), replacing the hardcoded gray-900/gray-500/gray-300 classes. --- src/components/AuthDialog/AuthDialog.tsx | 4 ++-- src/components/AuthDialog/EmailStep.tsx | 2 +- src/components/ui/table.tsx | 6 +++--- src/components/ui/toaster.tsx | 4 ++-- src/index.css | 5 +++++ src/pages/admin/ArtistsManagement/BulkEditor/GenresCell.tsx | 4 +++- src/pages/admin/ArtistsManagement/BulkEditor/ImageCell.tsx | 4 +++- src/pages/admin/ArtistsManagement/BulkEditor/TextCell.tsx | 2 +- .../admin/ArtistsManagement/BulkEditor/TextareaCell.tsx | 2 +- src/pages/admin/ArtistsManagement/BulkEditor/UrlCell.tsx | 2 +- tailwind.config.ts | 3 +++ 11 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/components/AuthDialog/AuthDialog.tsx b/src/components/AuthDialog/AuthDialog.tsx index 6663378bf..49442b910 100644 --- a/src/components/AuthDialog/AuthDialog.tsx +++ b/src/components/AuthDialog/AuthDialog.tsx @@ -42,7 +42,7 @@ export function AuthDialog({ - + {step === "otp" && (
(({ className, ...props }, ref) => (
)); diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx index c374b05f3..b24feb7dc 100644 --- a/src/components/ui/toaster.tsx +++ b/src/components/ui/toaster.tsx @@ -15,11 +15,11 @@ export function Toaster() { {toasts.map(function ({ id, title, description, action, ...props }) { return ( - +
{title && {title}} {description && ( - + {description} )} diff --git a/src/index.css b/src/index.css index 742e00c6f..cff421aa2 100644 --- a/src/index.css +++ b/src/index.css @@ -29,6 +29,11 @@ --surface-raised: 0 0% 100% / 0.1; /* bg-white/10 */ --surface-active: 0 0% 100% / 0.2; /* bg-white/20 */ + /* Text/border roles for light (white-background) surfaces: dialogs, toasts, admin tables */ + --surface-foreground: 221 39% 11%; /* gray-900 */ + --surface-muted-foreground: 220 9% 46%; /* gray-500 */ + --surface-border: 216 12% 84%; /* gray-300 */ + --accent: 271.5 81.3% 55.9%; /* purple-600 */ --accent-foreground: 0 0% 100%; --accent-hover: 272.1 71.7% 47.1%; /* purple-700 */ diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/GenresCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/GenresCell.tsx index 58724af64..c34561ef5 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/GenresCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/GenresCell.tsx @@ -96,7 +96,9 @@ export function GenresCell({ value, onSave }: GenresCellProps) { ))}
) : ( - Click to add genres... + + Click to add genres... + )} ); diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/ImageCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/ImageCell.tsx index 32035f833..8b74bd75a 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/ImageCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/ImageCell.tsx @@ -26,7 +26,9 @@ export function ImageCell({ }} /> ) : ( - add image... + + add image... + ); return ( diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/TextCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/TextCell.tsx index 316aa75c7..906e8bcef 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/TextCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/TextCell.tsx @@ -79,7 +79,7 @@ export function TextCell({ title="Click to edit" > {value || ( - + {placeholder || "Click to add..."} )} diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/TextareaCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/TextareaCell.tsx index a15ad7195..099667f4a 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/TextareaCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/TextareaCell.tsx @@ -74,7 +74,7 @@ export function TextareaCell({ {value ? ( ) : ( - + {placeholder || "Click to add..."} )} diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/UrlCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/UrlCell.tsx index af4be402d..1905e0900 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/UrlCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/UrlCell.tsx @@ -96,7 +96,7 @@ export function UrlCell({ value, placeholder, onSave }: UrlCellProps) { title="Click to edit" > {displayValue || ( - + {placeholder || "Click to add..."} )} diff --git a/tailwind.config.ts b/tailwind.config.ts index acb444a3e..b778a91db 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -30,6 +30,9 @@ export default { DEFAULT: "hsl(var(--surface))", raised: "hsl(var(--surface-raised))", active: "hsl(var(--surface-active))", + foreground: "hsl(var(--surface-foreground))", + "muted-foreground": "hsl(var(--surface-muted-foreground))", + border: "hsl(var(--surface-border))", }, live: { DEFAULT: "hsl(var(--live))", From 0dbefd8d7ea3ccdb9d4a16e1b420a9bb4a23ae64 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 08:32:36 +0000 Subject: [PATCH 08/18] fix: restore legible text on OTP slots and shared Input primitive InputOTPSlot char/caret and Input's text/placeholder inherited the white/purple dark-identity foreground tokens, making them invisible or off-color on white surfaces (login dialog). Use the surface-* tokens instead, since both primitives are only ever used on white-background surfaces. --- src/components/ui/input-otp.tsx | 4 ++-- src/components/ui/input.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ui/input-otp.tsx b/src/components/ui/input-otp.tsx index 1475c7297..537c6960e 100644 --- a/src/components/ui/input-otp.tsx +++ b/src/components/ui/input-otp.tsx @@ -39,7 +39,7 @@ const InputOTPSlot = React.forwardRef<
-
+
)}
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index 09700f605..b4c962581 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -8,7 +8,7 @@ const Input = React.forwardRef>( Date: Sun, 23 Aug 2026 09:55:05 +0000 Subject: [PATCH 09/18] docs: mark surface-* tokens as a pre-theming stopgap Documents that surface-foreground/muted-foreground/border are hardcoded grays for shared primitives on white backgrounds, and should fold into foreground/muted-foreground/border once the light/dark theme flip (#359) lands. --- docs/design/edition-color-vocabulary.md | 2 ++ src/index.css | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/design/edition-color-vocabulary.md b/docs/design/edition-color-vocabulary.md index d5345dfa4..a36f7253c 100644 --- a/docs/design/edition-color-vocabulary.md +++ b/docs/design/edition-color-vocabulary.md @@ -32,6 +32,8 @@ This names the **roles** colors play in the voter-facing edition views, independ | `surface-active` **(new)** | Hover/pressed/selected layer | `bg-white/20`, `bg-white/30` | | `popover` (existing) | Solid floating sheets, dropdowns, drawers | `bg-gray-800`, `bg-gray-900`, `bg-gray-900/95` | +`surface-foreground` / `surface-muted-foreground` / `surface-border` **(new, admin/auth only)** are a stopgap for shared shadcn primitives (`Input`, `InputOTP`, `Table`, toasts, `AuthDialog`) that render on a plain white background outside the edition views and were left invisible when `foreground`/`muted-foreground`/`border` were repointed to the dark identity. They're hardcoded grays today, not theme-aware. Once the light/dark flip (#359) lands, fold them into `foreground`/`muted-foreground`/`border` (scoped to the light theme value) and delete this family. + ### Borders & focus | Token | Role | Absorbs today | diff --git a/src/index.css b/src/index.css index cff421aa2..7b20cbeb1 100644 --- a/src/index.css +++ b/src/index.css @@ -29,7 +29,12 @@ --surface-raised: 0 0% 100% / 0.1; /* bg-white/10 */ --surface-active: 0 0% 100% / 0.2; /* bg-white/20 */ - /* Text/border roles for light (white-background) surfaces: dialogs, toasts, admin tables */ + /* + * Text/border roles for light (white-background) surfaces: dialogs, toasts, admin tables. + * TODO(#359): these are hardcoded, not theme-aware. Once light/dark theming lands + * (data-edition-theme), fold these into --foreground/--muted-foreground/--border + * instead so these surfaces follow the theme, and remove this token family. + */ --surface-foreground: 221 39% 11%; /* gray-900 */ --surface-muted-foreground: 220 9% 46%; /* gray-500 */ --surface-border: 216 12% 84%; /* gray-300 */ From 84bc215756c0842f00e63e2a5f5734794ad4f04e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 16:54:41 +0000 Subject: [PATCH 10/18] refactor: rename surface-* light-chrome tokens to paper-* Avoids a name collision with the pre-existing surface/surface-raised/ surface-active overlay-fill tokens, which are translucent layers on the dark ground and semantically unrelated to the opaque light-chrome background/text/border roles used by Dialog, Input, InputOTP, Table, toasts, and AuthDialog. Also adds paper-background and applies it to DialogContent, replacing the coincidentally-white bg-background. --- docs/design/edition-color-vocabulary.md | 2 +- src/components/AuthDialog/AuthDialog.tsx | 4 ++-- src/components/AuthDialog/EmailStep.tsx | 2 +- src/components/ui/dialog.tsx | 2 +- src/components/ui/input-otp.tsx | 4 ++-- src/components/ui/input.tsx | 2 +- src/components/ui/table.tsx | 6 +++--- src/components/ui/toaster.tsx | 4 ++-- src/index.css | 15 +++++++++------ .../ArtistsManagement/BulkEditor/GenresCell.tsx | 2 +- .../ArtistsManagement/BulkEditor/ImageCell.tsx | 2 +- .../ArtistsManagement/BulkEditor/TextCell.tsx | 2 +- .../ArtistsManagement/BulkEditor/TextareaCell.tsx | 2 +- .../ArtistsManagement/BulkEditor/UrlCell.tsx | 2 +- tailwind.config.ts | 9 ++++++--- 15 files changed, 33 insertions(+), 27 deletions(-) diff --git a/docs/design/edition-color-vocabulary.md b/docs/design/edition-color-vocabulary.md index a36f7253c..327ee68ec 100644 --- a/docs/design/edition-color-vocabulary.md +++ b/docs/design/edition-color-vocabulary.md @@ -32,7 +32,7 @@ This names the **roles** colors play in the voter-facing edition views, independ | `surface-active` **(new)** | Hover/pressed/selected layer | `bg-white/20`, `bg-white/30` | | `popover` (existing) | Solid floating sheets, dropdowns, drawers | `bg-gray-800`, `bg-gray-900`, `bg-gray-900/95` | -`surface-foreground` / `surface-muted-foreground` / `surface-border` **(new, admin/auth only)** are a stopgap for shared shadcn primitives (`Input`, `InputOTP`, `Table`, toasts, `AuthDialog`) that render on a plain white background outside the edition views and were left invisible when `foreground`/`muted-foreground`/`border` were repointed to the dark identity. They're hardcoded grays today, not theme-aware. Once the light/dark flip (#359) lands, fold them into `foreground`/`muted-foreground`/`border` (scoped to the light theme value) and delete this family. +`paper-background` / `paper-foreground` / `paper-muted-foreground` / `paper-border` **(new, admin/auth only)** are a stopgap for shared shadcn primitives (`Dialog`, `Input`, `InputOTP`, `Table`, toasts, `AuthDialog`) that render on a plain white chrome surface outside the edition views and were left invisible when `background`/`foreground`/`muted-foreground`/`border` were repointed to the dark identity. Named `paper-*` (not `surface-*`) specifically to avoid colliding with the `surface`/`surface-raised`/`surface-active` overlay-fill family above — those are translucent layers on the dark ground, `paper-*` is an opaque light page/dialog background, and the two are unrelated roles that happen to share a domain vocabulary. They're hardcoded grays/white today, not theme-aware. Once the light/dark flip (#359) lands, fold them into `background`/`foreground`/`muted-foreground`/`border` (scoped to the light theme value) and delete this family. ### Borders & focus diff --git a/src/components/AuthDialog/AuthDialog.tsx b/src/components/AuthDialog/AuthDialog.tsx index 49442b910..b8aae8bc3 100644 --- a/src/components/AuthDialog/AuthDialog.tsx +++ b/src/components/AuthDialog/AuthDialog.tsx @@ -42,7 +42,7 @@ export function AuthDialog({ - + {step === "otp" && (
)); diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx index b24feb7dc..76a6a2c29 100644 --- a/src/components/ui/toaster.tsx +++ b/src/components/ui/toaster.tsx @@ -15,11 +15,11 @@ export function Toaster() { {toasts.map(function ({ id, title, description, action, ...props }) { return ( - +
{title && {title}} {description && ( - + {description} )} diff --git a/src/index.css b/src/index.css index 7b20cbeb1..508d0187b 100644 --- a/src/index.css +++ b/src/index.css @@ -30,14 +30,17 @@ --surface-active: 0 0% 100% / 0.2; /* bg-white/20 */ /* - * Text/border roles for light (white-background) surfaces: dialogs, toasts, admin tables. + * Background/text/border roles for light (white) chrome surfaces: dialogs, toasts, + * admin tables. Distinct from the --surface/-raised/-active overlay tokens above, + * which are translucent fills on the dark ground, not opaque page backgrounds. * TODO(#359): these are hardcoded, not theme-aware. Once light/dark theming lands - * (data-edition-theme), fold these into --foreground/--muted-foreground/--border - * instead so these surfaces follow the theme, and remove this token family. + * (data-edition-theme), fold these into --background/--foreground/--muted-foreground/ + * --border instead so these surfaces follow the theme, and remove this token family. */ - --surface-foreground: 221 39% 11%; /* gray-900 */ - --surface-muted-foreground: 220 9% 46%; /* gray-500 */ - --surface-border: 216 12% 84%; /* gray-300 */ + --paper-background: 0 0% 100%; /* white */ + --paper-foreground: 221 39% 11%; /* gray-900 */ + --paper-muted-foreground: 220 9% 46%; /* gray-500 */ + --paper-border: 216 12% 84%; /* gray-300 */ --accent: 271.5 81.3% 55.9%; /* purple-600 */ --accent-foreground: 0 0% 100%; diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/GenresCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/GenresCell.tsx index c34561ef5..9cc988501 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/GenresCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/GenresCell.tsx @@ -96,7 +96,7 @@ export function GenresCell({ value, onSave }: GenresCellProps) { ))}
) : ( - + Click to add genres... )} diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/ImageCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/ImageCell.tsx index 8b74bd75a..1d74e174c 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/ImageCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/ImageCell.tsx @@ -26,7 +26,7 @@ export function ImageCell({ }} /> ) : ( - + add image... ); diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/TextCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/TextCell.tsx index 906e8bcef..28d6d8abd 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/TextCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/TextCell.tsx @@ -79,7 +79,7 @@ export function TextCell({ title="Click to edit" > {value || ( - + {placeholder || "Click to add..."} )} diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/TextareaCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/TextareaCell.tsx index 099667f4a..3b73402e9 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/TextareaCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/TextareaCell.tsx @@ -74,7 +74,7 @@ export function TextareaCell({ {value ? ( ) : ( - + {placeholder || "Click to add..."} )} diff --git a/src/pages/admin/ArtistsManagement/BulkEditor/UrlCell.tsx b/src/pages/admin/ArtistsManagement/BulkEditor/UrlCell.tsx index 1905e0900..90adf09aa 100644 --- a/src/pages/admin/ArtistsManagement/BulkEditor/UrlCell.tsx +++ b/src/pages/admin/ArtistsManagement/BulkEditor/UrlCell.tsx @@ -96,7 +96,7 @@ export function UrlCell({ value, placeholder, onSave }: UrlCellProps) { title="Click to edit" > {displayValue || ( - + {placeholder || "Click to add..."} )} diff --git a/tailwind.config.ts b/tailwind.config.ts index b778a91db..0b747f06b 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -30,9 +30,12 @@ export default { DEFAULT: "hsl(var(--surface))", raised: "hsl(var(--surface-raised))", active: "hsl(var(--surface-active))", - foreground: "hsl(var(--surface-foreground))", - "muted-foreground": "hsl(var(--surface-muted-foreground))", - border: "hsl(var(--surface-border))", + }, + paper: { + background: "hsl(var(--paper-background))", + foreground: "hsl(var(--paper-foreground))", + "muted-foreground": "hsl(var(--paper-muted-foreground))", + border: "hsl(var(--paper-border))", }, live: { DEFAULT: "hsl(var(--live))", From 7d1421b95fe26775013e8aec201d0be2bc511402 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 17:44:37 +0000 Subject: [PATCH 11/18] docs(design): trim :root note, fix stale scope language; fix(toast): color destructive text via variants Condenses the :root ground-rule bullet, updates edition-identity-spec's stale .edition-view scope claims, and moves paper-* toast text colors into toastVariants so destructive toasts no longer lose their contrast. --- docs/design/edition-color-vocabulary.md | 2 +- docs/design/edition-identity-spec.md | 6 +++--- src/components/ui/toast.tsx | 7 +++++-- src/components/ui/toaster.tsx | 6 ++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/design/edition-color-vocabulary.md b/docs/design/edition-color-vocabulary.md index 327ee68ec..952bce803 100644 --- a/docs/design/edition-color-vocabulary.md +++ b/docs/design/edition-color-vocabulary.md @@ -9,7 +9,7 @@ This names the **roles** colors play in the voter-facing edition views, independ - **Reuse and retheme the existing shadcn token names** where a role already exists (`background`, `foreground`, `muted-foreground`, `border`, `ring`, `popover`, `accent`, `destructive`). New names are added only for roles shadcn doesn't cover. No `ed-*` namespace. - **No numeric ramps.** Each role is one value per theme; "lighter/darker" variants are expressed as alpha on the base token (Tailwind `/NN` over an HSL var) or as an explicit `-soft` companion, not as `-100…-900` scales. - **`/NN` opacity modifiers work only on opaque roles** (`foreground`, `accent`, `live`, `vote-must`, …). The inherently translucent roles (`surface`/`surface-raised`/`surface-active`, `accent-soft`, `border`/`border-strong`, `*-soft`) carry their alpha inside the CSS variable, so `bg-surface/50` expands to a double-alpha `hsl(… / 0.05 / .5)` — silently invalid CSS. Use the next step in the role family (`surface` → `surface-raised` → `surface-active`) instead of a modifier. -- **The tokens live on `:root`, not a scope class (#367).** They used to sit under a `.edition-view` class so only voter-facing pages picked them up, but that meant every other surface (admin included) kept the old shadcn light-theme defaults for the same variable names — so a partial migration elsewhere (like admin's) recolored whatever hadn't been touched yet, and Radix portal content (which renders under `document.body`, outside any wrapper's scope) never picked up the override at all. The whole app renders on the same dark identity today, so there's one set of values for the whole app and no scope class to remember. Each root layout applies `bg-app-gradient` directly; the flip (#359) will attach `data-edition-theme="light"` to the document root. +- **The tokens live on `:root`, not a scope class (#367).** The whole app renders on the same dark identity today, so there's one set of values for the whole app — including admin and Radix portal content — and no scope class to remember. The flip (#359) will attach `data-edition-theme="light"` to the document root. - Values below reference today's hardcoded classes only to define the mapping; the actual palette is chosen in #320/#321. ## Token set diff --git a/docs/design/edition-identity-spec.md b/docs/design/edition-identity-spec.md index a5b9d822a..0483153b9 100644 --- a/docs/design/edition-identity-spec.md +++ b/docs/design/edition-identity-spec.md @@ -2,7 +2,7 @@ Resolution of [#321](https://github.com/chiptus/UpLine/issues/321), part of the visual-identity wayfinder map [#317](https://github.com/chiptus/UpLine/issues/317). Written in the semantic token vocabulary from [`edition-color-vocabulary.md`](./edition-color-vocabulary.md); implementation tickets are cut from this document. -**Scope:** the voter-facing edition views only (EditionView hero/tabs, schedule Now/Timeline/List, artists, explore, set details). Admin, groups/settings, and landing/auth keep the old skin. +**Scope:** the voter-facing edition views (EditionView hero/tabs, schedule Now/Timeline/List, artists, explore, set details). The tokens themselves live on `:root` globally (#367), so admin and Radix portal content pick them up too — but the palette and component work described here targets the edition views. **Provenance:** direction decided in [#320](https://github.com/chiptus/UpLine/issues/320) (Festival v2 winner, soft-border light graft), confirmed against real screens by the in-app prototype pass on PR #354 (`src/pages/EditionView/prototype/`, deleted after rollout). Values below are the prototype's confirmed values, extended to the full #319 role set. @@ -12,7 +12,7 @@ One skeleton, two color themes. **Dark is the default**: a violet poster ground ## Theme mechanics (CSS contract) -- Token values are CSS variables scoped to the edition-view root. Dark values are the default; light values apply under an explicit `data-edition-theme="light"` scope on that root. +- Token values are CSS variables on `:root` (#367). Dark values are the default; light values apply under an explicit `data-edition-theme="light"` attribute on the document root. - Both themes ship. How the theme is chosen (system preference vs in-app toggle) and persisted is a rollout decision — [#322](https://github.com/chiptus/UpLine/issues/322)'s territory. The CSS contract above is what it plugs into. - Values are stored as HSL channels to match the existing shadcn `hsl(var(--…))` plumbing; hex given here for legibility. Opaque roles are plain triples (`H S% L%`); the inherently translucent roles (`surface*`, `accent-soft`, `border*`, `*-soft`) carry their alpha inside the variable (`H S% L% / A`) — see the vocabulary's ground rules for why those roles must not take Tailwind `/NN` modifiers. @@ -107,7 +107,7 @@ Same in both themes. ## What implementation consumes -1. Token values above land in `src/index.css` under the edition scope, replacing the roles named in `edition-color-vocabulary.md`'s "absorbs today" mapping (~350 occurrences / 60 files per the #319 audit). +1. Token values above land in `src/index.css` under `:root`, replacing the roles named in `edition-color-vocabulary.md`'s "absorbs today" mapping (~350 occurrences / 60 files per the #319 audit). 2. `tailwind.config.ts` gains the new role names (`surface*`, `subtle-foreground`, `border-strong`, `accent-soft`, `live*`, `notice*`, `vote-*`). 3. Font families wired once at the edition-view root; display styling via the heading rules above. 4. The prototype directory `src/pages/EditionView/prototype/` and its wiring are deleted once the real implementation lands. diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx index 2bc23c1f4..b02a1240f 100644 --- a/src/components/ui/toast.tsx +++ b/src/components/ui/toast.tsx @@ -27,7 +27,7 @@ const toastVariants = cva( { variants: { variant: { - default: "border bg-background text-foreground", + default: "border bg-background text-paper-foreground", destructive: "destructive group border-destructive bg-destructive text-destructive-foreground", }, @@ -104,7 +104,10 @@ const ToastDescription = React.forwardRef< >(({ className, ...props }, ref) => ( )); diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx index 76a6a2c29..5887f0804 100644 --- a/src/components/ui/toaster.tsx +++ b/src/components/ui/toaster.tsx @@ -15,13 +15,11 @@ export function Toaster() { {toasts.map(function ({ id, title, description, action, ...props }) { return ( - +
{title && {title}} {description && ( - - {description} - + {description} )}
{action} From 15372cf8fa42a0426de86a9fdcb93aa665573945 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 17:48:10 +0000 Subject: [PATCH 12/18] fix(theme): apply paper roles to dialog/sonner, fix destructive contrast Dialog and Sonner toast content now use paper-foreground/border instead of inheriting the global dark foreground, fixing invisible text on the white paper chrome. Darkens --destructive-foreground since red-400 is too light for white text (WCAG AA). Also fixes ArchiveArtistDialog's hover text color to the exact white it had before the token migration. --- src/components/ui/dialog.tsx | 6 +++--- src/components/ui/sonner.tsx | 4 ++-- src/index.css | 2 +- src/pages/admin/ArtistsManagement/ArchiveArtistDialog.tsx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index 128c77366..05fab5150 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -36,13 +36,13 @@ const DialogContent = React.forwardRef< {children} - + Close @@ -104,7 +104,7 @@ const DialogDescription = React.forwardRef< >(({ className, ...props }, ref) => ( )); diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx index b38250eab..47b1ccac8 100644 --- a/src/components/ui/sonner.tsx +++ b/src/components/ui/sonner.tsx @@ -13,8 +13,8 @@ function Toaster({ ...props }: ToasterProps) { toastOptions={{ classNames: { toast: - "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg", - description: "group-[.toast]:text-muted-foreground", + "group toast group-[.toaster]:bg-paper-background group-[.toaster]:text-paper-foreground group-[.toaster]:border-paper-border group-[.toaster]:shadow-lg", + description: "group-[.toast]:text-paper-muted-foreground", actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground", cancelButton: diff --git a/src/index.css b/src/index.css index 508d0187b..d6e06ebfc 100644 --- a/src/index.css +++ b/src/index.css @@ -48,7 +48,7 @@ --accent-soft: 271.5 81.3% 55.9% / 0.4; /* bg-purple-600/40 */ --destructive: 0 90.6% 70.8%; /* red-400 */ - --destructive-foreground: 210 40% 98%; + --destructive-foreground: 221 39% 11%; /* gray-900 — red-400 is too light for a white foreground */ --border: 270 95.2% 75.3% / 0.3; /* border-purple-400/30 */ --border-strong: 270 95.2% 75.3% / 0.5; /* border-purple-400/50 */ diff --git a/src/pages/admin/ArtistsManagement/ArchiveArtistDialog.tsx b/src/pages/admin/ArtistsManagement/ArchiveArtistDialog.tsx index 471759941..f83abae43 100644 --- a/src/pages/admin/ArtistsManagement/ArchiveArtistDialog.tsx +++ b/src/pages/admin/ArtistsManagement/ArchiveArtistDialog.tsx @@ -49,7 +49,7 @@ export function ArchiveArtistDialog({ From 9d917fb976e3ffa3e6cf0b700c0d084640e4fcda Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 18:06:10 +0000 Subject: [PATCH 13/18] Remove redundant paper-token overrides now handled by primitives DialogTitle/DialogDescription in AuthDialog and the Input override in EmailStep duplicated styling the Dialog/Input primitives already apply. Also drops the stale "(new)" marker from the accent-hover doc row. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01546kmFCrpafJ2ykLQKjYvb --- docs/design/edition-color-vocabulary.md | 2 +- src/components/AuthDialog/AuthDialog.tsx | 4 ++-- src/components/AuthDialog/EmailStep.tsx | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/design/edition-color-vocabulary.md b/docs/design/edition-color-vocabulary.md index 952bce803..0377741d5 100644 --- a/docs/design/edition-color-vocabulary.md +++ b/docs/design/edition-color-vocabulary.md @@ -49,7 +49,7 @@ A hairline drawn in `border-white/NN` (not purple) doesn't fit either border rol | Token | Role | Absorbs today | | ------------------------------ | ------------------------------------------------ | -------------------------------------------------- | | `accent` / `accent-foreground` | Primary interactive fill: selected toggles, CTAs | `bg-purple-600`, `bg-purple-400` | -| `accent-hover` **(new)** | Hover state for the accent fill | `hover:bg-purple-700` | +| `accent-hover` | Hover state for the accent fill | `hover:bg-purple-700` | | `accent-soft` **(new)** | Translucent accent wash for selected/hover chips | `bg-purple-600/30`–`/60`, `bg-purple-400/10`–`/30` | | `text-accent` | Accent-colored text/icons | `text-purple-400` | diff --git a/src/components/AuthDialog/AuthDialog.tsx b/src/components/AuthDialog/AuthDialog.tsx index b8aae8bc3..bc9819de7 100644 --- a/src/components/AuthDialog/AuthDialog.tsx +++ b/src/components/AuthDialog/AuthDialog.tsx @@ -42,7 +42,7 @@ export function AuthDialog({ - + {step === "otp" && (