16 KiB
Frontend Overhaul — IMPHNEN Dashboard
Date: 2026-07-05 Status: Spec (approved design, pre-implementation)
1. Goals
- Visual redesign — Elevate the dashboard from "functional prototype" to "premium monitoring tool" using the Neuform-inspired dark design language (AeroNet, Nexus Capital, Summit references from
design/). - Code restructure — Split oversized files, modularize CSS, eliminate inline styles, and add consistent state handling across all components.
2. Scope
Two sequential phases, both targeting the Leptos 0.9.0-alpha WASM frontend at services/frontend/frontend/.
| Phase | Focus | Deliverables |
|---|---|---|
| Fase 1 | Code Restructure | File splits, CSS modules, inline→classes, state consistency |
| Fase 2 | Visual Redesign | Palette, layout, all panels, UI primitives |
3. File Splitting (Fase 1)
3.1 message_card.rs (453 lines → 4 files)
| New file | Content | Lines (est.) |
|---|---|---|
message_card.rs |
Main component — card layout orchestration | ~100 |
message_embed.rs |
Discord embed rendering (title, fields, images, footer) | ~120 |
message_meta.rs |
Author row, timestamp, channel badge, AI status chip | ~100 |
message_actions.rs |
Reanalyze button, moderate action, expand/collapse | ~80 |
3.2 messages/mod.rs (397 lines → 3 files)
| New file | Content | Lines (est.) |
|---|---|---|
mod.rs |
Panel orchestrator — wires FilterBar + MessageList | ~60 |
filter_bar.rs |
Channel picker, search input, status filter chips, live count | ~150 |
message_list.rs |
Infinite scroll logic, pagination, skeleton/empty/error states | ~180 |
3.3 ws/socket.rs (227 lines → 2 files)
| New file | Content | Lines (est.) |
|---|---|---|
connection.rs |
WebSocket lifecycle (connect, reconnect, heartbeat, close) | ~120 |
handlers.rs |
Event dispatch — maps WS event types to signal updates | ~100 |
3.4 Dashboard (dashboard/mod.rs, 276 lines)
Already well-split (stats_overview.rs, channel_summary_list.rs, user_summary_list.rs are separate). Extract orchestrator wiring from mod.rs into a focused panel component. No further splitting needed.
4. CSS Architecture (Fase 1)
4.1 File Structure
src/styles/
├── main.css @import hub — imported by index.html
├── tokens.css CSS custom properties (dark + light)
├── reset.css Reset, base elements, scrollbar, selection
├── utilities.css Utility classes (flex, gap, grid, w-full, etc.)
├── layout.css App shell, sidebar, topbar, content area, mobile tab
├── ui.css Button, card, input, select, modal, badge,
│ skeleton, tabs, scroll area, toast, status badge
├── messages.css Message card, feed, embed, image grid, filter bar
├── live.css Voice connection, speakers, visualizer, mic meter,
│ music player, screen share, recordings, waveform
├── dashboard.css Stats overview, channel/user summary lists
└── polish.css Particle background, theme toggle, mascot chatbot
4.2 Bundling
index.html links to src/styles/main.css. Trunk resolves @import statements and bundles into a single CSS file in the build output. Ordering is guaranteed by @import sequence.
4.3 Design Tokens (tokens.css)
Dark theme (default — :root without attribute selector)
:root {
--surface-base: #050510;
--surface-raised: #0a0a1a;
--surface-overlay: #12122a;
--surface-container: #1a1a35;
--surface-border: rgba(255, 255, 255, 0.06);
--surface-hover: rgba(59, 130, 246, 0.06);
--surface-glass: rgba(5, 5, 16, 0.78);
--text-primary: #f1f1f9;
--text-secondary: #9d9db5;
--text-tertiary: #5c5c78;
--text-inverse: #050510;
--color-primary: #3b82f6;
--color-primary-hover: #60a5fa;
--color-primary-active: #2563eb;
--color-primary-muted: rgba(59, 130, 246, 0.12);
--gradient-primary: linear-gradient(135deg, #3b82f6 0%, #6366f1 100%);
--gradient-brand: linear-gradient(135deg, #3b82f6 0%, #5865f2 50%, #6366f1 100%);
--color-success: #10b981;
--color-warning: #f59e0b;
--color-error: #ef4444;
--color-info: #3b82f6;
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.35);
--shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.4);
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 23px;
--radius-pill: 9999px;
--sidebar-width: 240px;
--header-height: 0px; /* header dihapus */
}
Light theme ([data-theme="light"])
[data-theme="light"] {
--surface-base: #f4f6fb;
--surface-raised: #ffffff;
--surface-overlay: #eeeff4;
--surface-container: #dde0e8;
--surface-border: rgba(0, 0, 0, 0.06);
--surface-hover: rgba(35, 161, 235, 0.05);
--surface-glass: rgba(255, 255, 255, 0.72);
--text-primary: #0f172a;
--text-secondary: #475569;
--text-tertiary: #94a3b8;
--text-inverse: #ffffff;
--color-primary: #2563eb;
--color-primary-hover: #3b82f6;
--color-primary-active: #1d4ed8;
--color-primary-muted: rgba(37, 99, 235, 0.1);
--gradient-primary: linear-gradient(135deg, #2563eb 0%, #6366f1 100%);
--gradient-brand: linear-gradient(135deg, #2563eb 0%, #5865f2 50%, #6366f1 100%);
/* reuse same semantic tokens */
--color-success: #10b981;
--color-warning: #f59e0b;
--color-error: #ef4444;
--color-info: #2563eb;
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.08);
}
5. Layout Architecture (Fase 2)
5.1 Current → Proposed
// Current (app.rs)
app-shell
├── app-header (brand + ThemeToggle + WS status — redundant)
├── app-main
│ ├── app-sidebar (TabButton × 3 — no icons, inline styles)
│ └── app-content (tab panel)
// Proposed
app-shell
├── app-sidebar
│ ├── brand (◉ IMPHNEN + "Guild Watcher" subtitle)
│ ├── nav (3 items with lucide icons + active indicator)
│ └── footer (WS status dot + compact ThemeToggle)
└── app-content
└── tab-panel (no header wrapper — full height)
5.2 Sidebar Specifics
- Width: 240px, full viewport height, sticky/fixed
- Brand area: 60px tall, logo placeholder (◉) + "IMPHNEN" bold + subtitle "Guild Watcher" (JetBrains Mono 11px)
- Nav items: icon (lucide-leptos) + label, 40px tall, border-radius 10px on hover/active
- Active state: 3px left border indicator +
var(--surface-overlay)background
- Active state: 3px left border indicator +
- Footer: separated by a subtle divider, WS dot + status text + ThemeToggle icon-button
- Mobile: sidebar collapses or slides overlay, MobileTabBar at bottom
5.3 Content Area
- Full remaining width, scrollable, padding 24px (desktop)
- Max-width 1400px for readability
- No header at top — brand is in sidebar
6. Components Redesign (Fase 2)
6.1 Messages Panel
FilterBar:
- Channel picker:
<select>redesigned — card-style dropdown with active channel name displayed, chevron icon - Search: input with magnifying glass icon (lucide), compact height (36px)
- Status chips: pill-style toggle buttons — "All", "Clean", "Warn", "Flagged", "Error" — active chip gets primary background
- Live count:
--text-tertiarylabel "X,XXX pesan" next to heading
MessageCard:
┌─────────────────────────────────────────────┐
│ @username · #channel · 12:34 ◉ │ ← meta row
├─────────────────────────────────────────────┤
│ Message content — Inter 14px, │
│ line-height 1.5, wraps naturally │
│ │
│ [Embed card-in-card] │
│ ┌───────────────────────────────────────┐ │
│ │ Embed title (semibold) │ │
│ │ Embed description │ │
│ │ [thumbnail] │ │
│ └───────────────────────────────────────┘ │
├─────────────────────────────────────────────┤
│ ● Clean 98% [⟳] [⚙] │ ← actions bar
└─────────────────────────────────────────────┘
- Card:
surface-raised, border1px solid border, radius--radius-md - Meta row: JetBrains Mono 12px,
text-secondary - AI badge: chip dengan soft background (
--color-primary-mutedatau--color-successvariant), 6px radius - Content: Inter 14px, max 6 lines with fade gradient overflow
- Actions bar: icon-only buttons, visible always (not on hover)
- Timestamp: relative (baru dimodalin — "2m ago", "1h ago")
- Shadow:
--shadow-sm
States:
| State | Component |
|---|---|
| Loading | 3-5 skeleton cards, each with pulse animation (gradient shimmer) |
| Empty | Icon + "Belum ada pesan" + "Coba ubah filter atau channel" |
| Error | Error icon + error message + "Coba lagi" button |
| Success | Normal message list |
6.2 Live Panel (Bento Grid)
2-column grid with gap: --space-4:
┌──────────────────────┬──────────────────────┐
│ VoiceConnectionCard │ ActiveSpeakers │
├──────────────────────┼──────────────────────┤
│ AudioVisualizer │ MicLevelMeter │
├──────────────────────┴──────────────────────┤
│ MusicPlayer (NowPlaying + controls) │
├──────────────────────┬──────────────────────┤
│ ScreenSubPanel │ RecordingsSubPanel │
└──────────────────────┴──────────────────────┘
VoiceConnectionCard:
- Shows channel name, connection status (dot + text), connect/disconnect button
- States: disconnected (picker CTA), connecting (pulse), connected (info)
AudioVisualizer + MicLevelMeter:
- Canvas 2D with glass card background
- Waveform rendering yang lebih smooth
NowPlaying / MusicPlayer:
- Track info (thumbnail/icon + title + artist)
- Progress bar + time
- Controls: prev/play-pause/next + volume + stop
States:
| State | Display |
|---|---|
| No voice connected | CTA card: "Connect to a voice channel" with guild/channel picker |
| Connecting | Animated pulse on status indicator |
| Connected, no speakers | Empty state: "Waiting for speakers..." |
| Music idle | "No track playing" + queue button |
| All normal | Full grid |
6.3 Dashboard Panel
StatsOverview (4-column grid):
┌──────────┬──────────┬──────────┬──────────┐
│ Metrics │ Metrics │ Metrics │ Metrics │
│ 12,458 │ 3,201 │ 89 │ 24 │
│ Messages │ Users │ Channels │ Flagged │
│ +12% │ Active │ Total │ ↑3 │
└──────────┴──────────┴──────────┴──────────┘
- Each stat card: 140px tall, number in JetBrains Mono 28px bold, label in Inter 12px secondary, trend line in 11px
- Trend: green for positive/neutral, red for negative
ChannelSummaryList / UserSummaryList:
- Row cards (border-bottom yang subtle, hover background)
- Channel:
#icon + name + message count + last active tooltip - User: avatar initial circle + username + message count + trust score badge
- Loading: 5 skeleton rows
- Empty: "Belum ada data"
7. UI Primitives Upgrade (Fase 2)
| Component | Upgrade |
|---|---|
| Button | Pill radius (--radius-pill), icon slot via children, loading spinner overlay, hover lift (translateY -1px + shadow-md), variants: primary/ghost/destructive/icon |
| Card | --radius-md, border 1px solid border, shadow-sm, optional interactive hover (shadow-md + border-primary) |
| Badge | --radius-sm, soft background (opacity variant), compact padding 4px 8px |
| Skeleton | Gradient shimmer (base → highlight → base sliding), --radius-sm |
| Modal | Backdrop blur (8px), enter transition (scale 0.95→1 + fade), exit reverse |
| Input | --radius-sm, focus ring 2px --color-primary + subtle glow, border 1px solid border → primary on focus |
| Select | Same as input, custom chevron icon |
| Toast | Fixed bottom-right, enter slide-in right, progress bar for auto-dismiss, variants: success/error/info |
| Tabs | Underline indicator (2px primary), no fill |
8. State Handling Convention
Every data-fetching component MUST handle these four states:
// Pattern (pseudocode)
match loading, data, error {
(true, _, _) => render_skeleton(),
(false, Some(data), _) => render_success(data),
(false, None, Some(err)) => render_error(err, retry_fn),
(false, None, None) => render_empty(empty_msg, icon),
}
List of components requiring state audit:
MessageList(messages) — skeleton needed, empty/error exist?VoiceConnectionCard(live) — exists partiallyActiveSpeakers(live) — empty state neededRecordingsSubPanel(live) — loading/empty neededMusicSubPanel(live) — idle/loading neededStatsOverview(dashboard) — skeleton neededChannelSummaryList(dashboard) — loading/empty neededUserSummaryList(dashboard) — loading/empty neededImageGrid(messages) — loading/empty neededMascotChatbot(polish) — loading needed
9. Inline Styles → CSS Classes (Fase 1)
Files with style= props that must be extracted:
| File | Inline styles | Action |
|---|---|---|
sidebar.rs |
style:width, style:background, style:color on NavItem |
Extract to .sidebar, .nav-item, .nav-item.is-active |
header.rs (being removed) |
style: for header layout, status dot |
Style moves to layout.css |
mobile_tab_bar.rs |
style:color, flex on buttons |
Extract to .mobile-tab-bar, .mobile-tab-item |
auth.rs |
Inline button style, close button | Extract to .auth-box, .auth-close-btn |
10. Implementation Order
Fase 1 — Code Restructure (dilakukan duluan biar Fase 2 punya fondasi bersih)
- CSS modular: buat
src/styles/dan pindahkan CSS dariapp.css - File splitting:
message_card.rs→ 4 files,messages/mod.rs→ 3 files,ws/socket.rs→ 2 files - Inline style → CSS classes: sidebar, mobile_tab_bar, auth
- State audit: tambah loading/empty/error states ke komponen yang kurang
- Dead code cleanup, verify build still works
Fase 2 — Visual Redesign
- Update design tokens (
tokens.css) — new palette, dark default - Layout restructure: hapus header, rebuild sidebar as primary nav
- UI primitives upgrade (button, card, modal, etc.)
- Messages Panel redesign (FilterBar, MessageCard)
- Live Panel redesign (bento grid)
- Dashboard Panel redesign (stat cards, row lists)
- Polish: transitions, micro-interactions, responsive
11. Out of Scope
- Leptos version upgrade (staying on 0.9.0-alpha)
- New features or panels (pure overhaul of existing surface)
- Backend changes
- Performance optimization beyond CSS/rendering
- Test coverage (separate effort)