Files
romm/docs/FRONTEND_ARCHITECTURE.md
Georges-Antoine Assi e471efb987 feat(v2): morph gallery cover into the player on Play
Clicking Play at the center of a gallery cover now runs the shared-element
view transition into the /ejs (EmulatorJS) or /ruffle hero, matching the
card → details morph.

- useGameActions gains an optional `coverEl` resolver; `play()` wraps the
  navigation in `morphTransition` (cover → player hero, same `rom-cover-<id>`
  tag the player paints statically) and awaits the push so the snapshot is
  taken after the player renders. GameCard supplies its GameCover box.
- The player heroes only seeded `rom` from `currentRom` (set via GameDetails),
  so a direct gallery→play left `rom` null and the `v-if`-gated hero never
  rendered — nothing to morph into. Seed a lightweight `heroSeed` SimpleRom
  from the gallery store (new `galleryRoms.getRomById`) so the cover paints
  its morph tag immediately; `rom` fills in on mount. Play is disabled until
  the full payload loads.
- Enable hover-motion on both player heroes so the cover spin / hover video
  work there too.
- Arcade systems (arcade / neogeoaes / neogeomvs) skip the cartridge slot-in
  animation (new `isArcadeSystem`).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 12:46:31 -04:00

55 KiB

RomM Frontend Architecture

Comprehensive documentation of the RomM frontend: a Vue 3 single-page application powering the retro gaming platform UI.


Table of Contents

  1. Overview
  2. High-Level Architecture
  3. Directory Structure
  4. Application Lifecycle
  5. Routing & Navigation
  6. State Management (Pinia Stores)
  7. API & Data Layer
  8. Component Architecture
  9. Views & Pages
  10. Console Mode
  11. Emulation Integration
  12. Theming & Styling
  13. Internationalization (i18n)
  14. Real-Time Communication
  15. Caching Strategy
  16. Utilities & Composables
  17. Build & Tooling
  18. Type System

1. Overview

Property Value
Framework Vue 3.4.27 (Composition API, <script setup>)
Build Tool Vite 6.4.2
Language TypeScript 5.7.3 (noImplicitAny: true)
UI Library Vuetify 3.9.2 (Material Design)
CSS Tailwind CSS 4.0.0 + Vuetify themes
State Management Pinia 3.0.1 (18 stores)
Routing Vue Router 4.3.2
HTTP Client Axios 1.15.0
i18n vue-i18n 11.1.10 (17 languages)
Real-time Socket.IO Client 4.7.5
Icons Material Design Icons (MDI) 7.4.47
Node 24 (via .nvmrc)

Total: ~216 Vue components (168 under components/, rest in views/console/layouts), 18 Pinia stores, 17 API service modules, 36 named routes across 3 layouts.


2. High-Level Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        Browser / PWA                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌─────────────┐   ┌──────────────┐   ┌───────────────────┐   │
│   │  Vue Router  │   │  Pinia Stores │   │   Mitt Emitter    │   │
│   │  (36 routes) │   │  (18 stores)  │   │  (80+ events)     │   │
│   └──────┬──────┘   └──────┬───────┘   └────────┬──────────┘   │
│          │                  │                     │              │
│   ┌──────v──────────────────v─────────────────────v──────────┐  │
│   │                    Components (~216)                       │  │
│   │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐ │  │
│   │  │  Gallery  │  │ Details  │  │ Settings │  │ Console  │ │  │
│   │  │  Mode     │  │  Page    │  │  Pages   │  │  Mode    │ │  │
│   │  └──────────┘  └──────────┘  └──────────┘  └──────────┘ │  │
│   └──────────────────────┬───────────────────────────────────┘  │
│                          │                                       │
│   ┌──────────────────────v───────────────────────────────────┐  │
│   │                   Service Layer                           │  │
│   │  ┌─────────┐  ┌───────────┐  ┌──────────┐  ┌─────────┐ │  │
│   │  │  Axios   │  │ Socket.IO │  │  Cache   │  │ Compose │ │  │
│   │  │  API     │  │  Client   │  │ Service  │  │ -ables  │ │  │
│   │  └────┬────┘  └─────┬─────┘  └────┬─────┘  └─────────┘ │  │
│   └───────┼──────────────┼─────────────┼─────────────────────┘  │
│           │              │             │                         │
├───────────┼──────────────┼─────────────┼─────────────────────────┤
│           v              v             v                         │
│     Backend API    WebSocket /ws   Browser Cache API             │
│     (/api/*)       (/ws/socket.io)  (IndexedDB)                 │
└─────────────────────────────────────────────────────────────────┘

Layered Architecture

┌─────────────────────────────────────────────────────────┐
│                    PRESENTATION LAYER                     │
│  views/          Page-level route components              │
│  layouts/        Auth, Main, Console layouts              │
│  components/     Feature & common components              │
│  console/        TV/gamepad-optimized UI                  │
├─────────────────────────────────────────────────────────┤
│                    STATE LAYER                            │
│  stores/         18 Pinia stores (auth, roms, config...) │
│  composables/    Reusable stateful logic                  │
├─────────────────────────────────────────────────────────┤
│                    DATA LAYER                             │
│  services/api/   17 Axios-based API modules               │
│  services/cache/ Browser Cache API wrapper                │
│  services/socket Socket.IO client                         │
├─────────────────────────────────────────────────────────┤
│                    INFRASTRUCTURE LAYER                   │
│  plugins/        Vuetify, Pinia, i18n, Router             │
│  styles/         Themes, global CSS                       │
│  locales/        17 language packs                        │
│  types/          TypeScript definitions                   │
│  utils/          Helpers (formatting, emulation, covers)  │
│  __generated__/  OpenAPI-generated types                  │
└─────────────────────────────────────────────────────────┘

3. Directory Structure

frontend/
├── index.html                     # HTML entry point (<div id="app">)
├── package.json                   # Dependencies & scripts
├── vite.config.js                 # Vite build config with plugins
├── tsconfig.json                  # TypeScript configuration
├── eslint.config.js               # ESLint flat config
├── .nvmrc                         # Node 24
│
├── assets/                        # Static assets (logos, platform icons)
├── public/                        # Public static files
│
└── src/
    ├── main.ts                    # Entry point: app creation & plugin init
    ├── RomM.vue                   # Root component
    │
    ├── plugins/                   # Vue plugin setup
    │   ├── index.ts               # Plugin registration (Vuetify, Pinia, i18n, Mitt)
    │   ├── router.ts              # Vue Router (36 routes, guards, permissions)
    │   ├── vuetify.ts             # Vuetify instance (themes, icons)
    │   ├── pinia.ts               # Pinia store with router injection
    │   ├── pinia.d.ts             # Pinia type augmentation ($router)
    │   ├── mdeditor.ts            # Markdown editor with XSS plugin
    │   └── transition/            # View Transitions API polyfill
    │
    ├── layouts/                   # Layout wrappers
    │   ├── Auth.vue               # Authentication pages layout
    │   └── Main.vue               # Authenticated pages layout (+ all dialogs)
    │
    ├── views/                     # Page-level components (routes)
    │   ├── Auth/                  # Setup, Login, ResetPassword, Register
    │   ├── Gallery/               # Platform, Search, Collection variants
    │   ├── Player/                # EmulatorJS, RuffleRS
    │   ├── Settings/              # Profile, UI, Library, Metadata, Admin, Stats
    │   ├── Home.vue               # Dashboard
    │   ├── GameDetails.vue        # ROM detail page (8 tabs)
    │   ├── Scan.vue               # Library scan
    │   ├── Patcher.vue            # ROM patcher
    │   ├── Pair.vue               # Device pairing
    │   └── 404.vue                # Not found
    │
    ├── components/                # ~168 Vue components
    │   ├── common/                # Shared, reusable
    │   │   ├── Collection/        # Collection cards, dialogs (9 components)
    │   │   ├── Dialog/            # LoadingDialog, SearchCover
    │   │   ├── EmptyStates/       # 8 empty state variants
    │   │   ├── Game/              # Game cards, dialogs, controls (48+ components)
    │   │   ├── Navigation/        # AppBar, drawers, nav buttons (13 components)
    │   │   ├── Notifications/     # Snackbar, upload progress
    │   │   └── Platform/          # Platform cards, dialogs (8 components)
    │   ├── Details/               # Game detail sub-components (14+)
    │   ├── Gallery/               # Gallery app bar, filters, skeleton
    │   ├── Home/                  # Dashboard sections (8 components)
    │   ├── Scan/                  # Scan platform component
    │   └── Settings/              # Settings sub-components (25+)
    │       ├── Administration/    # Users, tokens, tasks
    │       ├── ClientApiTokens/   # API token list, create, pair
    │       ├── LibraryManagement/ # Platform bindings, exclusions
    │       ├── MetadataSources/   # Provider config & priority
    │       ├── ServerStats/       # Library stats widgets
    │       ├── UserInterface/     # Theme, view, locale
    │       └── UserProfile/       # Profile, password, avatar
    │
    ├── console/                   # Console mode (TV/gamepad UI)
    │   ├── Layout.vue             # Console layout with input bus
    │   ├── index.css              # Console-specific styles
    │   ├── views/                 # Console pages
    │   │   ├── Home.vue           # Platform grid, collections
    │   │   ├── GamesList.vue      # ROM grid for platform/collection
    │   │   ├── Game.vue           # Game details with spatial nav
    │   │   └── Play.vue           # Emulator in console mode
    │   ├── components/            # Console-specific components (12)
    │   │   ├── GameCard.vue, SystemCard.vue, CollectionCard.vue
    │   │   ├── BackButton.vue, NavigationHint.vue
    │   │   ├── ScreenshotLightbox.vue, SettingsModal.vue
    │   │   └── ArrowKeysIcon.vue, DPadIcon.vue, FaceButtons.vue
    │   ├── composables/           # Console-specific composables
    │   │   ├── useConsoleTheme.ts # Theme management
    │   │   ├── useThemeAssets.ts  # Asset path resolution
    │   │   ├── useBackgroundArt.ts # Double-buffered backgrounds
    │   │   ├── useSpatialNav.ts   # Grid navigation
    │   │   ├── useElementRegistry.ts # Focus management
    │   │   ├── useInputScope.ts   # Scoped input handling
    │   │   └── useRovingDom.ts    # ARIA roving tabindex
    │   ├── input/                 # Input system
    │   │   ├── bus.ts             # Stack-based input scope manager
    │   │   ├── actions.ts         # 12 input actions
    │   │   ├── config.ts          # Keyboard + gamepad mappings
    │   │   ├── keyboard.ts        # Keyboard listener
    │   │   └── gamepad.ts         # Gamepad polling (rAF)
    │   ├── constants/             # Console constants (sizes, timings, themes)
    │   └── utils/                 # Console helpers
    │       ├── sfx.ts             # Procedural Web Audio SFX
    │       └── assetResolver.ts   # Theme-aware asset loading
    │
    ├── stores/                    # 18 Pinia stores
    │   ├── auth.ts                # Current user & scopes
    │   ├── roms.ts                # ROM library (largest store, 400+ lines)
    │   ├── platforms.ts           # Platform catalog
    │   ├── collections.ts         # Regular, virtual, smart collections
    │   ├── config.ts              # Server configuration
    │   ├── heartbeat.ts           # Server status & feature flags
    │   ├── galleryFilter.ts       # 13+ filter types
    │   ├── galleryView.ts         # View mode, aspect ratio
    │   ├── navigation.ts          # Drawer & nav state
    │   ├── console.ts             # Console mode navigation indices
    │   ├── scanning.ts            # Scan progress
    │   ├── tasks.ts               # Background task status
    │   ├── upload.ts              # Upload progress tracking
    │   ├── download.ts            # Download queue
    │   ├── playing.ts             # Emulator state
    │   ├── language.ts            # Locale selection
    │   ├── notifications.ts       # Toast queue
    │   └── users.ts               # User list (admin)
    │
    ├── services/                  # Data fetching & communication
    │   ├── api/                   # 16 Axios API modules (+ index.ts client)
    │   │   ├── index.ts           # Axios instance (CSRF, interceptors)
    │   │   ├── rom.ts             # ROM CRUD, upload, download
    │   │   ├── platform.ts        # Platform CRUD
    │   │   ├── collection.ts      # Collection operations
    │   │   ├── user.ts            # User management
    │   │   ├── identity.ts        # Login, logout, password reset
    │   │   ├── config.ts          # Backend configuration
    │   │   ├── task.ts            # Task monitoring
    │   │   ├── firmware.ts        # Firmware uploads
    │   │   ├── save.ts            # Game saves
    │   │   ├── state.ts           # Save states
    │   │   ├── screenshot.ts      # Screenshots
    │   │   ├── setup.ts           # Setup wizard
    │   │   ├── sgdb.ts            # SteamGridDB covers
    │   │   ├── export.ts          # Gamelist.xml + Pegasus exports
    │   │   ├── play-session.ts    # Play session tracking
    │   │   └── client-token.ts    # API token management
    │   ├── cache/                 # Experimental response cache
    │   │   ├── index.ts           # Browser Cache API wrapper
    │   │   └── api.ts             # Cached API service
    │   └── socket.ts              # Socket.IO client
    │
    ├── composables/               # Vue 3 composition utilities
    │   ├── useUISettings.ts       # Settings sync (localStorage ↔ backend)
    │   ├── useFavoriteToggle.ts   # Favorites collection management
    │   ├── useGameAnimation.ts    # CD spin, cartridge load, video hover
    │   └── useAutoScroll.ts       # Auto-scroll on content change
    │
    ├── styles/                    # Global styles
    │   ├── themes.ts              # Dark/light theme definitions
    │   ├── common.css             # Utility classes
    │   ├── fonts.css              # Font definitions
    │   └── scrollbar.css          # Custom scrollbar
    │
    ├── locales/                   # i18n translations
    │   ├── index.ts               # Loader with dynamic imports
    │   ├── en_US/                 # English (default)
    │   ├── en_GB/, fr_FR/, de_DE/, es_ES/, it_IT/, ja_JP/
    │   ├── ko_KR/, pt_BR/, pl_PL/, ro_RO/, ru_RU/
    │   ├── zh_CN/, zh_TW/, cs_CZ/, hu_HU/, bg_BG/
    │   └── (each has: collection, common, console, detail,
    │         emulator, gallery, home, library, login,
    │         navigation, patcher, platform, scan, settings, task)
    │
    ├── types/                     # TypeScript definitions
    │   ├── emitter.d.ts           # 80+ event types
    │   ├── main.d.ts              # Global augmentations
    │   ├── rom.d.ts               # ROM selection types
    │   ├── user.d.ts              # User form types
    │   ├── ruffle.d.ts            # Flash emulator types
    │   ├── rompatcher.d.ts        # ROM patcher types
    │   └── index.ts               # Utility types
    │
    ├── utils/                     # Helper functions
    │   ├── index.ts               # ~825 lines of utilities
    │   ├── covers.ts              # Procedural SVG cover generation
    │   ├── formData.ts            # FormData builder
    │   ├── tasks.ts               # Task status maps
    │   └── indexdb-monitor.ts     # IndexedDB change detection
    │
    └── __generated__/             # OpenAPI codegen output
        └── models/                # Generated TypeScript interfaces

4. Application Lifecycle

Startup Sequence

index.html
  └── <script type="module" src="src/main.ts">
        │
        ├── Create Vue app with RomM.vue as root
        ├── Register plugins (Vuetify, Pinia, i18n, Mitt, MD Editor)
        ├── Install Vue Router
        │
        ├── Initialize critical stores (before mount):
        │   ├── authStore.fetchCurrentUser()
        │   ├── configStore.fetchConfig()
        │   ├── heartbeatStore.fetchHeartbeat()
        │   └── tasksStore.fetchTasks()
        │
        └── app.mount("#app")

Plugin Registration Order

1. Vuetify     : Material Design components, themes, icons
2. Pinia       : State management (with router injection)
3. vue-i18n    : Internationalization (17 locales)
4. Mitt        : Event emitter (provided as 'emitter')
5. MD Editor   : Markdown editor with XSS plugin
6. Vue Router  : Navigation with guards

Request Lifecycle

Component Action
    │
    ├─ Store Action (e.g., romsStore.fetchRoms())
    │   │
    │   ├─ Cache check (if experimental cache enabled)
    │   │   ├─ Cache hit → return cached, fire background update
    │   │   └─ Cache miss → continue to API
    │   │
    │   ├─ API Service call (e.g., romApi.getRoms(params))
    │   │   │
    │   │   ├─ Axios request interceptor:
    │   │   │   ├─ Add CSRF token (x-csrftoken from cookie)
    │   │   │   └─ Track in inflight set
    │   │   │
    │   │   ├─ HTTP request to /api/*
    │   │   │
    │   │   └─ Axios response interceptor:
    │   │       ├─ Remove from inflight set
    │   │       ├─ 403 → clear session, redirect to login
    │   │       └─ Emit 'network-quiesced' when all requests complete
    │   │
    │   └─ Store mutation (update reactive state)
    │
    └─ Component reacts via reactive refs/getters

5. Routing & Navigation

Route Map

/ (root)
│
├── Auth Layout (public)
│   ├── /setup                    → Setup wizard (3 steps)
│   ├── /login                    → Login (password + OIDC)
│   ├── /reset-password           → Password reset
│   └── /register                 → Invite-based registration
│
├── /pair                         → Device pairing (standalone, no layout)
│
├── Main Layout (authenticated)
│   ├── /                         → Home dashboard
│   ├── /search                   → Global ROM search
│   ├── /platform/:platform       → Platform gallery
│   ├── /collection/:collection   → Regular collection
│   ├── /collection/virtual/:id   → Virtual collection
│   ├── /collection/smart/:id     → Smart collection
│   ├── /rom/:rom                 → Game details (8 tabs)
│   ├── /rom/:rom/ejs             → EmulatorJS player
│   ├── /rom/:rom/ruffle          → Ruffle Flash player
│   ├── /april-fools              → April Fools easter egg (toggleable)
│   ├── /scan                     → Library scanner [platforms.write]
│   ├── /patcher                  → ROM patcher
│   ├── /user/:user               → User profile
│   ├── /user-interface           → UI settings
│   ├── /library-management       → Library config [platforms.write]
│   ├── /metadata-sources         → Metadata provider status
│   ├── /client-api-tokens        → API token management [me.write]
│   ├── /administration           → User & task admin [users.write]
│   ├── /server-stats             → Library statistics
│   └── /*                        → 404
│
└── Console Layout (authenticated, TV/gamepad)
    ├── /console                  → Console home
    ├── /console/platform/:id     → Console game list
    ├── /console/collection/:id   → Console collection
    ├── /console/collection/smart/:id
    ├── /console/collection/virtual/:id
    ├── /console/rom/:rom         → Console game details
    └── /console/rom/:rom/play    → Console emulator

Route Guards

Guard Type Behavior
Global beforeEach Navigation Setup wizard redirect, auth check, scope validation
ROM beforeEnter Per-route Pre-fetches ROM data before rendering
Global beforeResolve Navigation View Transitions API animation
Scroll behavior Router config Restores saved scroll position on back/forward

Permission-Protected Routes

Route Required Scope
/scan platforms.write
/library-management platforms.write
/client-api-tokens me.write
/administration users.write

6. State Management (Pinia Stores)

Store Overview

┌─────────────────────────────────────────────────────┐
│                   PINIA STORES                       │
├──────────────┬──────────────────────────────────────┤
│ Core Data    │ roms, platforms, collections, users   │
├──────────────┼──────────────────────────────────────┤
│ Auth & Config│ auth, config, heartbeat               │
├──────────────┼──────────────────────────────────────┤
│ UI State     │ navigation, galleryFilter, galleryView│
│              │ language, notifications, console       │
├──────────────┼──────────────────────────────────────┤
│ Operations   │ scanning, tasks, upload, download,    │
│              │ playing                                │
└──────────────┴──────────────────────────────────────┘

Key Stores in Detail

roms (largest store, ~400 lines)

State Type Description
_allRoms SimpleRom[] Current page of ROMs
currentPlatform Platform | null Active platform filter
currentCollection Collection | null Active collection filter
currentRom DetailedRom | null Selected ROM details
recentRoms SimpleRom[] Recently added
continuePlayingRoms SimpleRom[] Recently played
selectedIDs Set<number> Multi-select state
fetchOffset / fetchTotalRoms number Pagination cursor
orderBy / orderDir string Sort (persisted to localStorage)
characterIndex Record<string, number> A-Z jump index

Key actions: fetchRoms(), fetchRecentRoms(), fetchContinuePlayingRoms(), add(), update(), remove(), resetPagination()

galleryFilter

Manages 13+ filter dimensions with logic operators:

Filter Type Logic
Genres string[] any / all / none
Franchises string[] any / all / none
Collections string[] any / all / none
Companies string[] any / all / none
Age Ratings string[] any / all / none
Regions string[] any / all / none
Languages string[] any / all / none
Player Counts string[] any / all / none
Statuses string[] any / all / none
Matched boolean | null toggle
Favorites boolean | null toggle
Duplicates boolean | null toggle
Playable boolean | null toggle
RetroAchievements boolean | null toggle
Missing boolean | null toggle
Verified boolean | null toggle

collections

Manages three collection types:

Type State Description
Regular allCollections User-created collections
Virtual virtualCollections Auto-generated by platform/genre
Smart smartCollections Filter-criteria based
Favorite favoriteCollection Special favorite collection

heartbeat

Server capability flags used throughout the UI:

METADATA_SOURCES: { IGDB, SS, MOBY, RA, STEAMGRIDDB, LAUNCHBOX, ... }
EMULATION: { DISABLE_EMULATOR_JS, DISABLE_RUFFLE_RS }
FRONTEND: { DISABLE_USERPASS_LOGIN, DISABLE_LOGS_VIEWER, YOUTUBE_BASE_URL }
OIDC: { ENABLED, AUTOLOGIN, PROVIDER, RP_INITIATED_LOGOUT }
TASKS: { scheduled task configurations }

Persistence Strategy

Storage What Examples
localStorage UI preferences View mode, sort order, theme, drawer state, boxart style
Backend (user.ui_settings) Synced preferences Same as localStorage, synced via useUISettings composable
In-memory (Pinia) Session data ROMs, platforms, collections, auth state
Browser Cache API API responses Optional experimental cache with background updates

7. API & Data Layer

Axios Client Setup

Location: services/api/index.ts

const api = axios.create({
  baseURL: "/api",
  timeout: 120000, // 2 minutes
});

Request Interceptor:

  • Injects CSRF token from romm_csrftoken cookie as x-csrftoken header
  • Tracks inflight requests in a Set

Response Interceptor:

  • On 403: clears session cookie, refetches CSRF, redirects to /login
  • Fires network-quiesced custom event when all requests complete (250ms debounce)

API Service Modules

Module Key Endpoints
rom.ts CRUD, chunked upload, download, search, notes
collection.ts CRUD for regular/smart/virtual + ROM association
platform.ts CRUD, supported list
user.ts CRUD, profile, RA refresh, invite links
identity.ts Login, logout, forgot/reset password
config.ts Platform bindings, versions, exclusions
task.ts List, status, run
firmware.ts Upload, list, delete
save.ts Upload, update, delete
state.ts Upload, update, delete
screenshot.ts Upload, update
setup.ts Library structure, platform creation
sgdb.ts Cover art search
export.ts Gamelist.xml export, Pegasus export
play-session.ts Play session ingestion & listing
client-token.ts Token CRUD, pair, exchange

Chunked Upload System (rom.ts)

1. POST /roms/upload/start
   Headers: X-Upload-Filename, X-Upload-Total-Size, X-Upload-Total-Chunks
   → Returns upload_id

2. PUT /roms/upload/{upload_id}  (per 10MB chunk)
   Headers: X-Chunk-Number, X-Chunk-Size
   → Retry: 3 attempts with exponential backoff

3. POST /roms/upload/{upload_id}/complete
   → 10-minute timeout for assembly

On failure: POST /roms/upload/{upload_id}/cancel

Key Data Flows

ROM Gallery Loading:

Component mount → romsStore.fetchRoms()
  → cachedApiService.getRoms(params, onBackgroundUpdate)
    → Cache hit? Return cached + background refresh
    → API call: GET /api/roms?platform_id=...&limit=72&offset=0&...
  → _postFetchRoms(): update ROMs, pagination, character index, filter values
  → Components react via reactive getters

Filter & Search:

User sets filter → galleryFilterStore.setSelected*()
  → Component detects change → romsStore.fetchRoms()
    → _buildRequestParams() merges all 13+ filter dimensions
    → API returns filtered paginated results
    → _postFetchRoms() updates available filter values from response

Settings Sync:

User changes setting → localStorage updated
  → useUISettings watcher fires
    → PUT /api/users/{id} with ui_settings JSON
      → Backend returns updated user
        → authStore.setCurrentUser(data)
          → On next login: user.ui_settings hydrates localStorage

8. Component Architecture

Organization Pattern

Feature-based hybrid with three tiers:

Tier 1: Common (shared, reusable)
├── Collection/     Cards, list items, 6 dialogs
├── Dialog/         Loading, SearchCover
├── EmptyStates/    8 variants (game, platform, collection, firmware, saves...)
├── Game/           Cards, 14 dialogs, PlayBtn, FavBtn, VirtualTable (48+)
├── Navigation/     AppBar, 3 drawers, 10 nav buttons
├── Platform/       Cards, PlatformIcon, 3 dialogs
└── Notifications/  Snackbar, upload progress

Tier 2: Feature-specific
├── Details/        Game detail tabs (14+ sub-components)
├── Gallery/        AppBar variants, filters, skeleton
├── Home/           Dashboard sections (8 components)
├── Scan/           Scan platform component
└── Settings/       25+ settings sub-components

Tier 3: Console Mode
└── console/        12 components + 7 composables + input system

Component Communication

┌─────────────────┐     props/emit     ┌─────────────────┐
│  Parent          │ ←───────────────→ │  Child           │
│  Component       │                    │  Component       │
└────────┬────────┘                    └────────┬────────┘
         │                                       │
    store refs                              store refs
         │                                       │
         v                                       v
┌─────────────────────────────────────────────────────────┐
│                    Pinia Stores                          │
└─────────────────────────────────────────────────────────┘
         │
    mitt events (80+ types)
         │
         v
┌─────────────────────────────────────────────────────────┐
│              Cross-Component Events                      │
│  showEditRomDialog, snackbarShow, playGame, etc.        │
└─────────────────────────────────────────────────────────┘

Patterns used:

  • Props/emit for parent-child communication
  • Pinia stores for shared state across components
  • Mitt emitter for loosely-coupled cross-component events (dialog triggers, notifications)
  • Provide/inject for console input scoping

Dialog System

All dialogs use Vuetify's v-dialog wrapped in a custom RDialog component:

RDialog (wrapper)
├── Header slot (title + close button)
├── Toolbar slot (optional)
├── Prepend slot
├── Content slot (scrollable)
├── Append slot
└── Footer slot (actions)

15 game dialogs: EditRom (with 4 sub-components), UploadRom, DeleteRom, MatchRom, NoteDialog, ShowQRCode, CopyDownloadLink, SelectSave, UploadSaves, DeleteSaves, SelectState, UploadStates, DeleteStates

All triggered via Mitt events, rendered in Main.vue layout.


9. Views & Pages

Home Dashboard (/)

Section Data Source Toggleable
Stats cards GET /api/stats Yes (localStorage)
Recently added romsStore.fetchRecentRoms() Yes
Continue playing romsStore.fetchContinuePlayingRoms() Yes
Platforms grid platformsStore Yes
Collections collectionsStore Yes
Smart collections collectionsStore Yes
Virtual collections collectionsStore Yes
  • Grid or table view (3 sizes + list)
  • Infinite scroll pagination (72 per page)
  • Multi-select for bulk operations
  • 3D tilt effect on cards (vanilla-tilt)
  • Virtual table for list mode performance

Game Details (/rom/:rom)

8-tab interface:

Tab Content
Details File info + game metadata
Manual PDF viewer (if available)
Save Data Save file management
Personal Notes, rating, play time, status
How Long To Beat Playtime estimates (if HLTB data)
Additional Content DLC/expansions (mobile)
Related Games Remakes/remasters (mobile)
Screenshots Screenshot gallery

Scan (/scan)

  • Platform multi-select
  • Metadata source selection with priority ordering
  • Real-time progress via Socket.IO
  • Log auto-scroll
  • Hash calculation toggle

ROM Patcher (/patcher)

Supports: .ips, .ups, .bps, .ppf, .rup, .aps, .bdf, .pmsr, .vcdiff

  • Drag-and-drop ROM + patch files
  • Platform selection for output
  • Save locally or upload to RomM

10. Console Mode

A complete TV/gamepad-optimized interface under /console/.

Architecture

Console Layout
├── Input Bus (keyboard + gamepad → actions)
├── Theme System (CSS variables per theme)
├── Spatial Navigation (grid-based focus)
├── Sound Effects (Web Audio synthesis)
│
├── Home View
│   ├── Platform cards (spatial nav)
│   ├── Continue playing
│   └── Collections grid
│
├── Games List View
│   ├── Game cards with lazy loading
│   └── Virtual scrolling
│
├── Game Detail View
│   ├── Description, metadata, screenshots
│   ├── Save state management
│   └── Play button → Emulator
│
└── Play View
    └── EmulatorJS with save/state/BIOS selection

Input System

Hardware Input (keyboard / gamepad)
    │
    ├── Keyboard Listener (keydown → action mapping)
    │   └── Ignores when focused on INPUT/TEXTAREA
    │
    ├── Gamepad Poller (requestAnimationFrame loop)
    │   ├── Button press detection (with repeat delay)
    │   └── Analog stick threshold (0.2)
    │
    └── Input Bus (stack-based scope manager)
        ├── Global shortcuts (always active)
        ├── Scoped listeners (context-dependent)
        └── Action dispatch with SFX feedback

12 Input Actions: moveUp, moveDown, moveLeft, moveRight, confirm, back, menu, delete, tabNext, tabPrev, toggleFavorite

Repeat Timing: 350ms initial delay, 120ms repeat

Procedural Sound Effects (Web Audio API)

Sound Frequency Duration When
move 860Hz 20ms Navigation
confirm 680→880Hz sweep 19ms Selection
back 300Hz 85ms Return
error 180Hz + 140Hz 180ms Failure
delete 260Hz + 180Hz 120ms Destructive action
favorite 600Hz + 950Hz Dual burst Toggle

All synthesized with sine/noise blend, exponential envelopes, low-pass filter, and waveshaper saturation.

Console Composables

Composable Purpose
useSpatialNav Grid navigation with boundary enforcement
useConsoleTheme Theme CSS variable injection
useThemeAssets Format-aware asset resolution (SVG > PNG > JPG)
useBackgroundArt Double-buffered background transitions
useElementRegistry Focus element tracking per section
useInputScope Dependency-injected input subscription
useRovingDom ARIA roving tabindex with auto-scroll

11. Emulation Integration

EmulatorJS

Location: views/Player/EmulatorJS/

Feature Details
Core selection Platform-specific core mapping (40+ platforms)
BIOS/firmware Selectable from uploaded firmware
Save management Upload, download, delete saves & states
Multi-disc Disc selection for multi-file games
Cache IndexedDB cache for game data
Fullscreen With keyboard lock
Netplay Socket.IO-based multiplayer
Controls Per-core configurable via config.yml

Ruffle (Flash)

Location: views/Player/RuffleRS/

  • SWF/Flash game emulation via Ruffle 0.2.0-nightly
  • Fullscreen support
  • Background color customization

Platform Detection

utils/index.ts provides:

  • getSupportedEJSCores(platform): maps platforms to EmulatorJS cores
  • isEJSEmulationSupported(rom): checks WebGL + server config
  • isRuffleEmulationSupported(rom): checks Flash platform
  • isCDBasedSystem(platform): 31 CD-based platforms for animation logic

12. Theming & Styling

Theme System

Location: styles/themes.ts

Theme Background Primary Accent
Dark #0D1117 #8B74E8 #E1A38D
Light #F2F4F8 #371f69 #E1A38D

Detection priority: settings.theme localStorage → prefers-color-scheme media query → dark default

Vuetify handles theme switching. Additional shared brand colors: romm-red, romm-green, romm-blue, romm-gold.

CSS Stack

Layer Technology Scope
Component Vuetify classes + scoped <style> Per-component
Utility Tailwind CSS 4.0 Inline utility classes
Global styles/common.css App-wide utilities
Scrollbar styles/scrollbar.css Custom scrollbar
Console console/index.css Console mode only

Procedural Cover Generation

utils/covers.ts generates SVG covers with:

  • Hash-based deterministic gradients (consistent per game)
  • Collection covers with multi-image grid
  • Favorite covers with star icon
  • Missing/unmatched covers with icons
  • Aspect-ratio-aware empty placeholders

13. Internationalization (i18n)

Setup

  • Library: vue-i18n 11.1.10 (Composition API mode)
  • Locale loading: Dynamic glob import from locales/{lang}/*.json
  • Default: en_US
  • Fallback: en_US

Supported Languages (17)

Code Language
en_US English (US, default)
en_GB English (UK)
fr_FR French
de_DE German
es_ES Spanish
it_IT Italian
ja_JP Japanese
ko_KR Korean
pt_BR Portuguese (Brazil)
pl_PL Polish
ro_RO Romanian
ru_RU Russian
zh_CN Chinese (Simplified)
zh_TW Chinese (Traditional)
cs_CZ Czech (custom plural rules)
hu_HU Hungarian
bg_BG Bulgarian

Namespace Organization

Each locale directory contains translation files per feature: collection, common, console, detail, emulator, gallery, home, library, login, navigation, patcher, platform, scan, settings, task


14. Real-Time Communication

Socket.IO Client

Location: services/socket.ts

io({
  path: "/ws/socket.io/",
  transports: ["websocket", "polling"],
  autoConnect: false,
});

Usage: Manually connected during upload and scan operations.

Events consumed:

  • scan:update_stats: live scan progress (platform/ROM counts)
  • scan:log: scan log messages
  • scan:stop: scan completion

Dev proxy: Vite proxies /ws to backend with WebSocket upgrade support.


15. Caching Strategy

Experimental Browser Cache

Location: services/cache/

Opt-in: localStorage.settings.enableExperimentalCache

Request Flow with Cache:
┌──────────┐    cache hit    ┌──────────┐
│  Component├───────────────→│  Cached   │ → Immediate render
│           │                │  Response │
│           │    meanwhile   │          │
│           │◄───────────────│  Background│ → API fetch
│           │  onBackgroundUpdate       │ → Update if different
└──────────┘                └──────────┘

Features:

  • Browser Cache API (requires HTTPS)
  • Request deduplication (concurrent identical requests share promise)
  • Background update callbacks (stale-while-revalidate pattern)
  • Pattern-based cache clearing
  • Used for ROM lists and recent/continue playing data

16. Utilities & Composables

Global Composables

Composable Purpose Key Features
useUISettings Settings persistence Singleton, localStorage ↔ backend bidirectional sync, 25+ settings
useFavoriteToggle Favorites management Auto-creates Favorites collection, toggle with notifications
useGameAnimation Card animations CD spin (5000 deg/s), cartridge load, video hover (1.5s delay)
useAutoScroll Scroll management Throttled (50ms), mutation observer, respects user scroll

Utility Functions (utils/index.ts, ~825 lines)

Display:

  • formatBytes(): human-readable sizes (B through PB)
  • formatTimestamp(): locale-aware dates
  • formatRelativeDate(): relative time strings

Emojis & Localization:

  • regionToEmoji(): 50+ region codes → country flags
  • languageToEmoji(): 40+ language codes → country flags

Emulation Support:

  • getSupportedEJSCores(): platform → EmulatorJS core mapping
  • isEJSEmulationSupported(): WebGL + config check
  • isCDBasedSystem(): 31 CD-based platforms
  • isArcadeSystem(): 3 arcade platforms

Game Status:

  • romStatusMap: 8 statuses with emoji, text, i18n keys
  • Status enum: unplayed, now_playing, backlogged, paused, completed, 100%, retired, never_playing

Layout:

  • views: 3 view modes with responsive grid configurations
  • calculateMainLayoutWidth(): dynamic width based on drawer state

Task Display:

  • convertCronExpression(): human-readable cron (via cronstrue)
  • Task status/type maps with colors and icons

Cover Generation (utils/covers.ts)

Procedural SVG generation for:

  • Collection covers (multi-image grid with deterministic gradients)
  • Favorite covers (star icon themed)
  • Missing covers (question mark icon)
  • Unmatched covers (warning icon)
  • Empty placeholders (aspect-ratio-aware)

17. Build & Tooling

Vite Configuration

Feature Config
Target ESNext
Dev port 3000 (8443 with HTTPS)
Backend proxy /api/*http://127.0.0.1:5000
WebSocket proxy /ws, /netplay → backend with upgrade
Allowed hosts localhost, 127.0.0.1, romm.dev

Plugins:

  1. Tailwind CSS (@tailwindcss/vite)
  2. Vue 3 (@vitejs/plugin-vue)
  3. Vuetify auto-import (vite-plugin-vuetify, 57 pre-optimized components)
  4. PWA (vite-plugin-pwa, service worker, installable)
  5. HTTPS (vite-plugin-mkcert, optional dev HTTPS)
  6. Static copy (ROM patcher JS assets)

Scripts

Script Command Purpose
dev vite --host Development server
build vite build Production build
preview vite preview Preview production build
typecheck vue-tsc TypeScript validation
generate openapi-typescript-codegen Generate types from backend OpenAPI
lint eslint Lint .vue, .js, .ts files

OpenAPI Code Generation

npm run generate
# Fetches http://127.0.0.1:3000/openapi.json
# Generates TypeScript interfaces in __generated__/models/

Generated types used throughout stores and API services for type-safe backend communication.

ESLint Configuration

  • Flat config (eslint.config.js)
  • Vue plugin with essential rules
  • TypeScript-ESLint integration
  • Vue accessibility plugin (eslint-plugin-vuejs-accessibility)

18. Type System

Generated Types (__generated__/models/)

Auto-generated from backend OpenAPI schema:

Type Description
SimpleRomSchema ROM in list view (covers, metadata IDs, user data)
DetailedRomSchema Full ROM with all relationships
SearchRomSchema Minimal search result
PlatformSchema Platform with ROM count
UserSchema User with role and settings
CollectionSchema Collection with ROM IDs
VirtualCollectionSchema Auto-generated collection
SmartCollectionSchema Filter-based collection
SaveSchema / StateSchema / ScreenshotSchema Asset types
FirmwareSchema BIOS file info
HeartbeatResponse Server status and capabilities
ConfigResponse Full server configuration
ScanStats Scan progress counters
TaskInfo / TaskStatusResponse Background task data
GetRomsResponse Paginated ROM list with filter values

Custom Types

File Types
emitter.d.ts SnackbarStatus, Events (80+ event signatures)
rom.d.ts RomSelectEvent
user.d.ts UserItem (extends User with password + avatar)
ruffle.d.ts RufflePlayerElement, RuffleSourceAPI
rompatcher.d.ts ROM patching library interfaces
main.d.ts Global augmentations
index.ts isKeyof<T>, ExtractPiniaStoreType<D>, ValueOf<T>

Path Alias

"@/*"  "./src/*"

Used throughout: import { ... } from "@/stores/roms".


Appendix: Key Design Patterns

Pattern Where Purpose
Composition API All components <script setup> with reactive refs
Pinia stores stores/ Centralized state with actions/getters
Mitt event bus Cross-component Loosely-coupled dialog/notification triggers
Composables composables/ Reusable stateful logic (singleton where needed)
Stale-while-revalidate services/cache/ Return cached, update in background
Chunked upload services/api/rom.ts 10MB chunks with retry
Spatial navigation console/ Grid-based focus for gamepad/keyboard
Input scoping console/input/bus.ts Stack-based context for input handling
Procedural audio console/utils/sfx.ts Web Audio API synthesis
Double buffering useBackgroundArt Smooth background transitions
View Transitions plugins/transition/ CSS View Transitions API
OpenAPI codegen __generated__/ Type-safe API communication
Feature flags heartbeatStore Server-driven UI feature toggling