fix(dashboard): UI/UX review remediation — Lucide icons, a11y, code-splitting#47
Merged
Conversation
Rewrite shared Icon.tsx as a Material-Symbol-name to Lucide component map (same public API, strokeWidth 1.5, size default 20, filled fill). Remove the Material Symbols font link from index.html (eliminates ligature-word FOUT) and add font preconnects; delete the now-dead .material-symbols-outlined CSS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…anding) Heading hierarchy: PageHeader title is now h1, sidebar brand demoted to a non-heading. Remove dead controls: topbar notifications button, sidebar docs/support and footer placeholder links (no real URLs); wire the topbar settings gear to the settings route. Demote the hero's tertiary CTA to a text link. Give the mobile sidebar close button a 44x44 target. Hero min-height vh -> dvh. Add an accessible refetch spinner to the overview day toggle. Add a persistent landing header (logo + login). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop header.notifications and sidebar.docs/support (no longer referenced after the dead-control cleanup) from common.json across all 48 languages. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lazy-load the 19 guild feature pages via React.lazy, each wrapped in its own Suspense/PageSkeleton inside the layout Outlet so the sidebar stays mounted. Add Vite manualChunks for recharts/react-dom/tanstack/radix/i18n/lucide. Initial entry chunk drops from 2,225 kB to 181 kB (gzip 552 -> 43 kB). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
discord-select/discord-multi-select: raise option contrast (text/80 -> full text) and fix RTL chevron (ml-2 -> ms-2). Add Volume2 to the Icon map (volume_up) for voice-channel indicators. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nodes get role/aria-label, larger handle hit targets, and Yes/No branch titles on the condition node; canvas region is labeled. ActionFields/NodeDetailPanel: raw <input> -> shadcn Input, label/id association, aria-required + non-color-only required markers, emoji channel labels -> Icon, reduced-motion guard on the detail panel. Remove dead files: RuleForm, ActionRow, VariableHelper, AddActionNode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Icon-only buttons get aria-labels; destructive actions gain ConfirmDialogs; bare empty states use the shared EmptyState; section headings fixed (no h1->h3 skips); form labels associated (htmlFor/id); inline validation near fields; color-not-only cues added. Page-specific: security raw <select> -> shadcn Select + load skeleton; commands -> DiscordMultiSelect + Switch toggle + unsaved-changes guard + fixed mis-namespaced common: i18n keys; settings non-functional notification/danger controls removed (unsupported by API); permissions matrix labeled + audit log -> Table; scheduled browser-timezone default + cron validation; logs category icons + filter aria-labels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…obile inputs) Address three findings from the Obsidian Engine design-system audit: - Discord blue (#5865F2) is 4.19:1 as text on #0e0e10 (fails WCAG AA). Document --color-discord as fill-only and add --color-discord-text (#8b94ff, 7.14:1 AAA) for on-dark text/icons. The verbatim Discord button palette in the reaction-roles preview is intentional brand fidelity (style name shown alongside, so not color-only) — annotated. - Add a Title-LG (2rem) tier between Headline-SM and Display-LG so hierarchy holds on dense pages. - Render form inputs at 16px below the md breakpoint (14px from md up) to prevent iOS Safari focus auto-zoom. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Postgres 18 initializes its data directory at /var/lib/postgresql (not the legacy /var/lib/postgresql/data), so mount the named volume there to persist data across `pnpm dev` restarts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…irection Regenerate pnpm-lock.yaml to record already-declared dependencies: @playwright/test ^1.49.0 and @radix-ui/react-direction ^1.1.1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract remaining hardcoded UI strings (automation/rules, welcome, overview, permissions, tempvoice, tickets, music, logs) into i18n, and fix pre-existing i18n gaps found during the sweep: - missing/broken keys that rendered raw or showed English in every locale (confirmDelete dialogs, starboard/suggestions/moderation/ warnings, common time.weeks/limit/options) - an incorrect i18next plural (editor.issues.one/.other -> _one/_other) Repair 53 broken locale JSON files (41 empty, 12 with unescaped quotes) and bring all 48 languages to full key parity (1477 keys, zero [NEEDS TRANSLATION] markers). Add real translations for the major languages (es, fr, de, pt, ru, ja, zh-CN, ar, ur); remaining locales use English placeholders pending native review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Taller, reliably scrollable list so all 48 languages are reachable, with an on-brand scrollbar using design tokens - Render the language list LTR with <bdi>-isolated native names so RTL locales no longer mirror or reorder the parentheses - Add translate="no" so browser auto-translation stops rewriting the native names (e.g. Japanese, Chinese) into English - Fix the hover state: hover:bg-surface-container-high referenced a non-existent token; use surface-hover with a transition and keyboard-focus parity Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ings From an adversarially-verified RTL/icon/focus audit of the dashboard: - Icon.tsx: stop applying fill="currentColor" to active icons. Lucide glyphs are stroke-based outlines; filling them collapsed detailed icons (Hand/Shield/Zap) into solid blobs — the "Welcome icon corrupted when active" and "icons all filled" reports. Active state now uses a heavier stroke (2.25 vs 1.5), per design.md's "outlined, not filled" rule. - Sidebar.tsx: nav had pe-2 only, so Link items sat flush against the ScrollArea's overflow-hidden edge and the :focus-visible ring was clipped on the inline-start side. px-2 gives the ring clearance. - RuleList.tsx: flow-strip arrowheads used physical border-r, so they pointed backwards against the auto-reversed chip order in RTL. border-e. - NodeDetailPanel.tsx: slide-in-from-end-4 is not a real utility and never animated; replaced with slide-in-from-right-4 + rtl:slide-in-from-left-4 so the panel enters from its end-0 dock edge in both directions. - dialog.tsx: centered modal carries no data-side, so its physical slide-in-from-left never flipped; added rtl: right counterparts. - HeroSection.tsx: decorative glow used physical right-1/4 with no translate, so it didn't mirror; end-1/4. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dashboard used animate-in / slide-in-from-* / fade-in / zoom-in classes (Radix dialogs, dropdowns, selects, tooltips, popovers, and the automation detail panel) but no plugin provided them — Tailwind 4 core ships none of these, so every enter/exit animation was a silent no-op app-wide. Add tw-animate-css (the Tailwind-4-native successor to tailwindcss-animate) and import it in styles.css. Verified in-browser: animate-in now resolves to the "enter" keyframes, and physical slide utilities stay physical (no RTL auto-flip), so the explicit rtl: slide variants added in the prior commit resolve correctly in both directions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The detail panel overlays the React Flow canvas, which does not mirror (nodes stay in LTR pixel space). Using logical end-0 correctly mirrored the panel to the LEFT edge in RTL — but that dropped it on top of the trigger/start node and read as broken. Pin it to the physical right in both directions (border-l, slide-in-from-right, no rtl variant) so it stays consistent with the unmirrored canvas; the panel's content stays RTL via inherited dir. Also switch the React Flow control panel's inset from logical me-2 to physical mr-2 to match its bottom-right position. Verified in-browser: right-0 keeps the panel on the right edge in both LTR and RTL, where end-0 flipped it to the left in RTL. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause of the "empty automation canvas in RTL" report: React Flow does not support RTL — under dir="rtl" its transform/coordinate math breaks and nodes render off-screen, leaving a blank canvas. Force the canvas container (relative flex-1, holding ReactFlow) to dir="ltr" so the graph renders normally, and set the NodeDetailPanel root back to dir="rtl" so its content stays Arabic/RTL. The panel is already pinned to the physical right (prior commit), which is correct for an overlay on the unmirrored canvas. Verified live in the authenticated dashboard (Arabic/RTL): the trigger + action nodes now render, a Member Join trigger sets correctly, and the detail panel docks right with RTL content. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Radix injects a `display:table; min-width:100%` viewport wrapper for horizontal-scroll measurement. We only scroll vertically, and that table wrapper sized 16px wider than its viewport and overflowed, pushing its inline-start padding outside the clip box — the node detail panel showed 0 padding on one side and 16px on the other (most visible in RTL). Force the wrapper to display:block so it fills the viewport width exactly; padding is now symmetric. No component uses horizontal ScrollArea. Verified live: panel content insets are now 16px on both sides. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Autocomplete, inline highlighting, searchable variable browser, and a
Discord-style live preview (hybrid data) for all {variable} template fields.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
requireGuildAdmin authorized off the cached OAuth session snapshot, so admin access revoked on Discord kept working (up to 30 min) — a privilege-escalation hole. Authorization now derives from a live bot-token check (guildAuthz.isUserGuildAdmin: owner / Administrator / Manage Server computed from current guild roles), surfaced through resolveUserPermissions. Revocation is honored within the ~60s cache. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a refresh control on the servers page backed by POST /api/guilds/refresh, which force-refreshes the user's guilds from Discord and invalidates stale server-side guild/permission caches. The manageable-guild filter now also includes guilds the user owns (OAuth `owner` flag), not just Manage Server / Administrator. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Stop hook derived repo-relative test paths but checked them against the current working directory, so from a subdirectory it falsely flagged every changed source file. Anchor existence checks to the git root and accept the repo's <feature>.test.ts convention (not just a 1:1 mirror). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4 tasks
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Remediation of a UI/UX review of the admin dashboard. Groups: icon-system unification, accessibility, dead-control cleanup, landing polish, i18n cleanup, and route code-splitting. Verified with
pnpm typecheck(14/14) andpnpm build(7/7).Changes
Icons — unify on Lucide (HIGH)
Iconcomponent, 36 files) with Lucide (15 files) — clashing on the same screen and contradicting the design system.Icon.tsxas a Material-Symbol-name → Lucide-component map. Public API (name/filled/size) unchanged, so all call sites are untouched; renders atstrokeWidth 1.5.<link>fromindex.html(eliminates the ligature-word flash on load), added fontpreconnects, and deleted the dead.material-symbols-outlinedCSS.CATEGORY_ICONSwas feeding Lucide-style names into the Material Symbols font (silently blank).Accessibility & UX (HIGH / MEDIUM)
PageHeadertitle is now<h1>; sidebar brand demoted from<h1>to a non-heading. Each page now has exactly one correct<h1>.role="status") spinner during background refetch, reusing an existing translated key.Landing
min-heightusesdvhinstead ofvh(no mobile viewport jump).i18n cleanup
header.notificationsandsidebar.docs/sidebar.support(unused after the dead-control cleanup) fromcommon.jsonacross all 48 languages.Performance — route & vendor code-splitting
React.lazy, each in its ownSuspense/PageSkeletonboundary inside the layout Outlet so the shell (sidebar/topbar) stays mounted.manualChunksfor recharts / react-dom / tanstack / radix / i18n / lucide.recharts (433 kB) now loads only on chart pages.
Test plan
pnpm typecheck— 14/14 tasks passpnpm build— 7/7 pass, chunks split as intendedNotes
docker-compose.yml,pnpm-lock.yaml) are intentionally not included.🤖 Generated with Claude Code