Type: bug · UI / design-system token (visibility · colour-contrast)
Parent: Epic 6 (Search, reporting & admin) — Story 6.2 Saved filters
Discovered in: manual dogfooding of the /filters directory — the per-row ⋯ actions menu. The user reported "delete option of saved filter is not visible"; debugging confirmed the item renders and is clickable — its STYLE makes it invisible, not a permission/gating bug.
Symptom: In the default theme, the Delete item in a saved filter's ⋯ row-actions menu (icon + label) is invisible — white text + white icon on the white popover surface. Only the hover:bg-(--el-muted) (#f3f4f6) state faintly reveals it. The permission gating is correct (owner/admin see the item; the component test finds it in the DOM) — the defect is purely the colour token.
Root cause (verified):
app/(authed)/filters/_components/FilterRowActionsMenu.tsx:93-101 — the Delete role="menuitem" button is styled text-(--el-danger-text), and its <Trash2> icon (line 99) has no explicit colour, so it inherits the same token.bg-(--el-page-bg) (packages/design-system/src/components/ui/Popover.tsx:94); --el-page-bg: var(--color-background) (packages/design-system/theme.css:2222).--color-background: #ffffff (theme.css:86); --el-danger-text: var(--color-destructive-foreground) (theme.css:2264) and --color-destructive-foreground: #ffffff (theme.css:125). ⇒ white text + white icon on a white popover = invisible.--el-danger-text is the ink placed ON a bright danger FILL — its correct use is Button variant danger: bg-(--el-danger) text-(--el-danger-text) (packages/design-system/src/components/ui/Button.tsx:43), where it's the label colour sitting on the red fill (so it's #ffffff / dark-ink to clear AA against the fill). It is not a danger-coloured text token for a plain surface. Danger-coloured LABEL text on a surface must use --el-danger (the red hue, #e03131) — exactly what the design mock uses for the icon: .opt.danger .og { color: var(--el-danger) } (design/work-items/saved-filters.mock.html:613)..opt.danger { color: var(--el-danger-text) } (saved-filters.mock.html:610). The implementation copied that wrong label token and additionally dropped the mock's red icon colour, so in the shipped component both the icon and the text vanish (the mock at least kept a red trash icon).Blast radius (fix ALL in the same PR): the identical pattern recurs at app/(authed)/settings/project/automation/_components/AutomationRuleActionsMenu.tsx:78-82 — the automation-rule Delete menuitem, same text-(--el-danger-text) + uncoloured <Trash2> → same white-on-white. Grep for role="menuitem" + text-(--el-danger-text) on a popover surface. (Also worth auditing other text-(--el-danger-text) uses that are NOT on a danger fill — e.g. DangerZoneCard heading, WorkItemNode danger chip on --el-danger-surface — but those are different surfaces; scope this fix to the invisible row-menu Deletes.)
Fix direction:
text-(--el-danger) (red on the surface) in both FilterRowActionsMenu.tsx and AutomationRuleActionsMenu.tsx. Verify ≥ AA (4.5:1) against BOTH --el-page-bg and the --el-muted hover state, in BOTH light and dark themes — if --el-danger (#e03131 on #ffffff ≈ 4:1) doesn't clear AA for small text, add/use an AA-safe danger-on-surface token (the finding-#35 pattern) rather than a raw darkened hex..opt.danger label token from --el-danger-text → --el-danger (and re-export the .png).tests/components/filters-directory.test.tsx asserts the Delete item's PRESENCE and passed the whole time the item was invisible; a presence check can't catch this.Reproduce: default light theme (no data-palette / data-theme override) → /filters → own a saved filter → open its ⋯ menu → the Delete row is present in the DOM (and filters-directory.test.tsx finds it) but paints white-on-white, so it's invisible. Same at Settings → Project → Automation → a rule's ⋯ menu.
Resolution: (open)