design-system.md 11 KB

MixBoard Design System

Design Intent & Feel

This app should feel like a well-organized DJ booth — everything within reach, nothing hidden, both decks visible at once. Pro density with craft and care.

Feel Attributes

  1. Professional — information-dense, respects the user's expertise
  2. Unhurried — generous padding, no cramped toolbars, content breathes
  3. Grounded — playlist is always visible, never yanked away by navigation
  4. Responsive — immediate feedback on interactions, micro-animations on state changes
  5. Tactile — hover states, spatial grouping, controls feel like physical objects

Anti-Goals

  • NOT a streaming app (no oversized artwork cards dominating the screen)
  • NOT a spreadsheet (no raw data grids without typographic hierarchy)
  • NOT retro-first (skins change colors, not layout structure)

Motion Preferences

  • Panel slide: 200ms ease-out
  • State transitions (play/pause, selection): 150ms ease-in-out
  • Toast messages: 300ms ease-in-out with opacity
  • Reduced-motion: all animations replaced with instant cuts

Token Registry (Designer-managed)

Spacing

Token Value Usage
spacing.xs 4pt Inline icon gaps
spacing.sm 8pt Between related elements
spacing.md 12pt Section padding, control groups
spacing.lg 16pt Panel margins, major separators
spacing.xl 24pt Section breaks, panel insets

Typography

Token Spec Usage
type.trackTitle .system(size: 13, weight: .medium) Track title in list
type.trackMeta .system(size: 11) + .secondary Artist, album in list
type.dataPill .system(size: 10, design: .monospaced) BPM, key, duration
type.sectionHeader .system(size: 11, weight: .semibold) + .tertiary UPPERCASE sidebar/list sections
type.playerTitle .system(size: 13, weight: .semibold) Now-playing track in player bar
type.playerArtist .system(size: 11) + .secondary Now-playing artist in player bar
type.time .system(size: 11, design: .monospaced) Elapsed / remaining time

Sizing

Token Value Usage
playerBar.height 64pt Bottom player bar
playerBar.artwork 44pt Artwork thumbnail in player bar
waveform.height 48pt Waveform display
browsePanel.minWidth 280pt Cloud browser panel minimum
browsePanel.idealWidth 340pt Cloud browser panel default
browsePanel.maxWidth 420pt Cloud browser panel maximum
trackRow.height 28pt Playlist track row
sidebar.minWidth 180pt Sidebar minimum

Corner Radii

Token Value Usage
radius.sm 4pt Buttons, pills, metadata badges
radius.md 8pt Cards, panels, popovers
radius.lg 12pt Artwork, modal sheets

Colors

Managed by AppTheme skin system. No hardcoded colors in views — always reference theme.* properties.

Navigation & Flow

Layout Architecture

Three-region layout with slide-out panel:

┌─────────┬──────────────────────────┬──────────────────┐
│ Sidebar │ Detail (always playlist) │ Browse Panel     │
│ 180-300 │ (flex)                   │ 280-420 (toggle) │
│         │                          │                  │
│         │                          │                  │
│         ├──────────────────────────┴──────────────────┤
│         │ Player Bar (64pt, full width)               │
└─────────┴─────────────────────────────────────────────┘

Panel Behavior

  • Primary toggle: cloud.fill button in playlist header toolbar, rightmost position, larger than other toolbar buttons (20pt icon, prominent style)
  • No sidebar Cloud entry — removed entirely. Panel is toggled only from the toolbar button or ⌘B.
  • Sidebar "Queue" click → opens browse panel with Queue tab selected (kept in sidebar)
  • ⌘B → toggles browse panel
  • Panel persists state — scroll position, navigation depth preserved when hidden
  • Small windows (<1200px) → panel overlays as sheet instead of inline
  • Panel slides from trailing edge, 200ms ease-out

Chad Music Icon

  • Toolbar button: cloud.fill SF Symbol at 20pt, accent-colored when panel is open, secondary when closed
  • Boar asset (chadmusic-boar): kept in Assets.xcassets for future use (e.g. panel header branding), not used in toolbar
  • Toolbar button style: Larger than neighboring buttons (Add, Group, Settings, Export). Should visually stand out — use .controlSize(.regular) or explicit frame while other buttons use .controlSize(.small).

Sidebar Decisions

  • No "Chad Music" row — removed from sidebar. Cloud browsing is accessed via toolbar button only.
  • Clicking a playlist always shows it in the detail area
  • Active sidebar item gets rounded-rect highlight background

Player Bar Decisions

  • No vertical dividers — spatial grouping only
  • Three zones: Transport (left) | Track Info with artwork (center, flex) | Time + Volume (right)
  • Waveform sits above player bar at 48pt height
  • Small artwork thumbnail (44pt) in player bar when track is playing

Decision Log (append-only)

2026-03-17 — UI Revamp Direction

  • Options: (A) Modern Streaming, (B) Pro Player Refined, (C) Hybrid density modes
  • Choice: B — Pro Player, Refined
  • Reason: MixBoard is a music tool (mix targets, cue points, BPM, DAW export). Stripping density would betray power users. Instead: better typography, spatial hierarchy, subtle animation, eliminate visual noise.
  • Learned pattern: Density is the feature, not the problem. The craft applied to the density is what was missing.

2026-03-17 — Cloud Browser Navigation

  • Options: (1) Tabs at top of detail, (2) Slide-out right panel, (3) Dual split panes
  • Choice: 2 — Slide-out browse panel
  • Reason: The browse workflow is asymmetric — user spends most time in playlists, occasionally dips into cloud. A panel respects this asymmetry. Playlist never disappears. Direct drag-and-drop between panel and playlist.
  • Learned pattern: Asymmetric workflows need asymmetric layouts. Don't give equal space to unequal tasks.

Compromises

(None yet)

2026-03-18 — Panel Toggle Spatial Fix

  • Problem: Sidebar button (left) opens panel (right) — spatially disconnected, unintuitive
  • Options: (A) Toggle button in playlist header toolbar right-aligned, (B) Move panel to left side, (C) Animation cue from sidebar to panel
  • Choice: A — Boar icon button in playlist toolbar, right-aligned
  • Reason: Trigger and effect become spatially adjacent. Sidebar "Chad Music" stays as secondary entry point for discovery. Button uses кабанчик (boar) logo matching Chad Music branding.
  • Learned pattern: Trigger and effect must be spatially connected. If UI action X opens panel Y, the button for X should be adjacent to Y.

2026-03-18 — Chad Music Icon

  • Choice: Replace cloud.fill with custom кабанчик (boar) image asset
  • Reason: Brand identity — Chad Music has its own logo, generic cloud icon doesn't convey what it is
  • Revised 2026-03-18: Reverted to cloud.fill for toolbar button. Boar asset kept for future panel branding.

2026-03-18 — Remove Sidebar Cloud Entry

  • Choice: Remove "Chad Music" from sidebar entirely. Panel toggle lives only in playlist toolbar (rightmost, larger button) + ⌘B.
  • Reason: Sidebar button was spatially disconnected from the panel (left→right). Having the toggle in the toolbar right next to where the panel appears is more intuitive. Also fixes the SwiftUI List deselection bug (clicking non-tagged rows inside List(selection:) clears selectedPlaylist).
  • Learned pattern: If a UI element's only purpose is toggling a panel, put the toggle where the panel is — not in a navigation list.

2026-03-19 — Upload/Download Action Buttons (Apple Music-inspired)

  • Options: (A) Make existing status icons clickable at all levels, (B) Add dedicated action buttons at group header + playlist toolbar, keep track rows status-only, (C) Full hover-action rows with revealed buttons on every track
  • Choice: B — Prominent Header Buttons
  • Reason: Apple Music's core insight is that batch actions at the container level (album, playlist) are the primary interaction. Per-track is secondary and suits macOS right-click conventions. Track rows at 28pt with 11pt icons are too tight for reliable hover targets. Direction B gives the biggest discoverability win (actions visible without right-click) with the least disruption.
  • Scope:
    • PlaylistUploadButton — mirrors existing PlaylistDownloadButton, placed after it in toolbar
    • Group header cloud action button — arrow.up.to.cloud / arrow.down.to.line / stop.circle, placed after upload summary indicator
    • Track row status icons remain read-only (no change)
  • Icon vocabulary: filled = status/completed, outlined/cloud = actionable buttons
  • Color vocabulary: .secondary at rest, theme.accentColor on hover, .orange in progress, .green completed, .red error (all existing tokens)
  • Learned pattern: Batch-level buttons > per-item buttons for discoverability in dense lists. Status and action are separate concerns — don't overload one icon with both.

Components

PlaylistUploadButton

  • Purpose: Playlist-toolbar-level upload action, mirrors PlaylistDownloadButton
  • Placement: After PlaylistDownloadButton, before Export button in PlaylistHeader toolbar
  • Visibility: Only when playlist has local tracks AND Chad Music is configured
  • Label: ↑ N/M (uploaded count / total local tracks)
  • Icon: arrow.up.circle
  • States: idle → click uploads eligible tracks; uploading → click cancels
  • Typography: Matches PlaylistDownloadButton (inherits .controlSize(.small))

Group Header Cloud Action Button

  • Purpose: One-click upload/download for group (album) batches
  • Placement: After uploadSummary in GroupHeaderView HStack, before Spacer()
  • Icon mapping: arrow.up.to.cloud (upload), arrow.down.to.line (download), stop.circle (cancel)
  • Size: 11pt icon, 20×20pt hit target frame
  • Hover: .secondarytheme.accentColor, 150ms ease-in-out (instant on reduced-motion)
  • Priority: Upload > Download when both directions have actionable tracks
  • Hidden when: All tracks are synced (no actionable state)

Backlog

Drop cloud tracks between playlist rows (positional insert)

  • Current: Cloud tracks/albums can only be dropped onto the playlist header bar — appends to end
  • Wanted: Drop between specific tracks in the track list to insert at a position (like reordering, but from an external source)
  • Interaction: Drag from Cloud panel → hover between rows in playlist → drop indicator line appears → release inserts at that position
  • Complexity: Medium — requires per-row drop targets with insertion index calculation, not just a single playlist-level drop zone