diff --git a/admin-frontend/src/components/Layout.tsx b/admin-frontend/src/components/Layout.tsx
index 9fdd921..0d2fe2c 100644
--- a/admin-frontend/src/components/Layout.tsx
+++ b/admin-frontend/src/components/Layout.tsx
@@ -97,8 +97,8 @@ export default function Layout() {
}}
className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
isActive
- ? 'bg-sidebar-active text-sidebar-accent-foreground ring-1 ring-gold/50'
- : 'text-sidebar-foreground/85 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground'
+ ? 'bg-sidebar-active text-sidebar-accent-foreground ring-1 ring-[color:var(--gold)]'
+ : 'text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground'
} ${extraClasses}`}
>
{item.label}
diff --git a/docs/ADMIN_UX_REVIEW.md b/docs/ADMIN_UX_REVIEW.md
index cb8d2ed..9ec8816 100644
--- a/docs/ADMIN_UX_REVIEW.md
+++ b/docs/ADMIN_UX_REVIEW.md
@@ -21,6 +21,7 @@ Snapshot after Chabad Blue chrome parity (navy sidebar, theme toggle, shared tok
| P2 | iOS hamburger only — no desktop collapse | Optional collapsed icon rail for large monitors |
| P3 | Viewer filter comboboxes lack accessible names (axe `button-name`) | Label each Select trigger; then restore full-page axe on gallery home |
| Done | Admin Home was a SaaS marketing splash (orange/blue hero, emoji placeholders) | Quiet ops home: welcome + workflow links + recent processed strip |
+| Done | Sidebar primary links invisible (Tailwind `/85` on CSS-var colors → transparent) | Solid `text-sidebar-foreground`; e2e asserts Scan/Process/People visible |
| P3 | Emoji leftovers in Help / some toasts (remove over time) | Plain language + lucide/SVG icons over time |
## Accessibility status
diff --git a/e2e/tests/admin.sidebar-nav.spec.ts b/e2e/tests/admin.sidebar-nav.spec.ts
new file mode 100644
index 0000000..e698f43
--- /dev/null
+++ b/e2e/tests/admin.sidebar-nav.spec.ts
@@ -0,0 +1,60 @@
+import { z } from 'zod';
+import { test, expect } from '../fixtures';
+import { DEFAULT_ADMIN_BASE_URL } from '../env-defaults';
+
+const TokenResponse = z.object({
+ access_token: z.string().min(1),
+ refresh_token: z.string().min(1),
+});
+
+/**
+ * Admin sidebar primary nav must be visible after Chabad chrome (CSS-var
+ * opacity modifiers made inactive links transparent — #104).
+ */
+test.describe('admin sidebar nav @smoke', () => {
+ const adminBaseUrl = process.env.PLAYKIT_ADMIN_BASE_URL || DEFAULT_ADMIN_BASE_URL;
+
+ test('primary workflow links are visible in the sidebar', async ({ page, api }) => {
+ const username = process.env.E2E_API_USERNAME || '';
+ const password = process.env.E2E_API_PASSWORD || '';
+ test.skip(!username || !password, 'E2E_API_USERNAME/PASSWORD required for admin UI');
+
+ const tokens = await api.post>('/api/v1/auth/login', {
+ body: { username, password },
+ expectedStatus: 200,
+ schema: TokenResponse,
+ });
+
+ await page.goto(`${adminBaseUrl}/login`);
+ await page.evaluate(
+ ({ access, refresh }) => {
+ localStorage.setItem('access_token', access);
+ localStorage.setItem('refresh_token', refresh);
+ },
+ {
+ access: tokens.data.access_token,
+ refresh: tokens.data.refresh_token,
+ },
+ );
+ await page.goto(`${adminBaseUrl}/`);
+ await expect(page.getByRole('button', { name: /Log\s*out/i })).toBeVisible({
+ timeout: 30_000,
+ });
+
+ const nav = page.getByRole('navigation', { name: 'Primary' });
+ await expect(nav).toBeVisible();
+
+ for (const label of ['Scan', 'Process', 'Search photos', 'People', 'Tag photos']) {
+ const link = nav.getByRole('link', { name: label, exact: true });
+ await expect(link, `sidebar missing visible link: ${label}`).toBeVisible();
+ // Guard against transparent text (CSS-var + /opacity bug)
+ await expect(link).toHaveCSS('opacity', '1');
+ const color = await link.evaluate((el) => getComputedStyle(el).color);
+ expect(color, `${label} must not be transparent`).not.toBe('rgba(0, 0, 0, 0)');
+ expect(color).not.toMatch(/rgba?\(\s*0,\s*0,\s*0,\s*0\s*\)/);
+ }
+
+ await expect(nav.getByRole('button', { name: /Maintenance/i })).toBeVisible();
+ await expect(nav.getByRole('link', { name: 'Help', exact: true })).toBeVisible();
+ });
+});