Skip to content

fix(dashboard): UI/UX review remediation — Lucide icons, a11y, code-splitting#47

Merged
Abdulkhalek-1 merged 21 commits into
mainfrom
fix/ui-review-findings
Jul 8, 2026
Merged

fix(dashboard): UI/UX review remediation — Lucide icons, a11y, code-splitting#47
Abdulkhalek-1 merged 21 commits into
mainfrom
fix/ui-review-findings

Conversation

@Abdulkhalek-1

Copy link
Copy Markdown
Member

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) and pnpm build (7/7).

Changes

Icons — unify on Lucide (HIGH)

  • The dashboard mixed a Material Symbols font (shared Icon component, 36 files) with Lucide (15 files) — clashing on the same screen and contradicting the design system.
  • Rewrote Icon.tsx as a Material-Symbol-name → Lucide-component map. Public API (name/filled/size) unchanged, so all call sites are untouched; renders at strokeWidth 1.5.
  • Removed the Material Symbols <link> from index.html (eliminates the ligature-word flash on load), added font preconnects, and deleted the dead .material-symbols-outlined CSS.
  • Fixed latent breakage: CATEGORY_ICONS was feeding Lucide-style names into the Material Symbols font (silently blank).

Accessibility & UX (HIGH / MEDIUM)

  • Heading hierarchy: PageHeader title is now <h1>; sidebar brand demoted from <h1> to a non-heading. Each page now has exactly one correct <h1>.
  • Dead controls: removed the non-functional topbar notifications button, sidebar Docs/Support, and footer placeholder links (no real URLs exist); wired the topbar settings gear to the real settings route.
  • Touch targets: mobile sidebar close button is now a 44×44 target with hover feedback.
  • Refetch feedback: the overview 7d/30d toggle shows an accessible (role="status") spinner during background refetch, reusing an existing translated key.

Landing

  • Demoted the hero's third CTA ("Explore Features") to a subordinate text link (single clear primary action).
  • Hero min-height uses dvh instead of vh (no mobile viewport jump).
  • Added a persistent sticky header (logo + Explore + Open Dashboard).

i18n cleanup

  • Removed header.notifications and sidebar.docs/sidebar.support (unused after the dead-control cleanup) from common.json across all 48 languages.

Performance — route & vendor code-splitting

  • Lazy-load the 19 guild feature pages via React.lazy, each in its own Suspense/PageSkeleton boundary inside the layout Outlet so the shell (sidebar/topbar) stays mounted.
  • Added Vite manualChunks for recharts / react-dom / tanstack / radix / i18n / lucide.
Initial entry chunk Size Gzip
Before 2,225 kB 552 kB
After 181 kB 43 kB

recharts (433 kB) now loads only on chart pages.

Test plan

  • pnpm typecheck — 14/14 tasks pass
  • pnpm build — 7/7 pass, chunks split as intended
  • Manual smoke: navigate guild pages (lazy loading + skeletons), verify icons render, mobile sidebar, landing header

Notes

  • Pre-existing working-tree changes (docker-compose.yml, pnpm-lock.yaml) are intentionally not included.

🤖 Generated with Claude Code

Abdulkhalek-1 and others added 21 commits July 7, 2026 14:08
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>
@Abdulkhalek-1

Copy link
Copy Markdown
Member Author

Superseded by #49, which integrates all 7 open PRs (#42#48) into main in one verified branch — conflicts resolved, typecheck 14/14, and 1053 tests green. This PR's changes are fully included there; it will be closed as merged once #49 lands.

@Abdulkhalek-1 Abdulkhalek-1 merged commit 1df727b into main Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant