Architectural Differences Between RomM's v1 Legacy and v2 Rewrite Frontends

RomM maintains two parallel frontend stacks: a frozen v1 legacy UI using flat component hierarchies and Vuetify theming, and an active v2 rewrite under frontend/src/v2/ featuring a three-tier component architecture, token-driven design systems, and universal input handling for gamepad, mouse, and keyboard.

RomM ships as a single-page Vue 3 application within the rommapp/romm repository, but internally it houses two distinct architectural approaches. While the legacy v1 frontend remains frozen and receives only critical bug fixes, the v2 rewrite represents the active development path for all new features and redesigns. Understanding these architectural differences is essential for contributors and developers extending the application.

Project Structure and Entry Points

Both frontend versions share the same entry point at frontend/src/main.ts, but they diverge immediately in how they bootstrap the application.

The v1 legacy stack lives directly under frontend/src/views/, frontend/src/components/, and frontend/src/layouts/. These directories contain a flat organization of hundreds of components mixed together without strict tier separation.

The v2 rewrite lives under frontend/src/v2/ and is gated by the user setting uiVersion. When activated, main.ts loads the v2 router from frontend/src/v2/router/routes.ts, which registers legacy routes as named views while adding the new v2 routes. This allows both stacks to coexist during the migration period.

Component Architecture: Flat vs. Three-Tier

The most significant structural difference lies in component organization.

v1 (Legacy) employs a flat folder hierarchy. Components reside in a single components/ directory with no clear separation between primitives and page-specific widgets. For example, importing a user avatar in a settings view looks like this:

// frontend/src/views/Settings/UserProfile.vue
import UserAvatar from '@/components/common/UserAvatar.vue';

v2 (Rewrite) implements a three-tier feature model:

  1. lib – Primitive components (e.g., RAvatar, RButton) under frontend/src/v2/lib/
  2. shared – Feature-level composites under frontend/src/v2/components/shared/
  3. feature – Page-specific components under frontend/src/v2/views/

This enforces strict boundaries between design primitives and business logic:

// frontend/src/v2/views/Settings/UserProfile.vue
import RAvatar from '@/v2/lib/RAvatar/RAvatar.vue';

Design System and Theming

The v1 frontend relies on Vuetify's theme system with custom CSS in frontend/src/styles/. Colors are hard-coded using Vuetify's palette or raw hex values:

<v-btn color="deep-purple accent-4">Legacy Button</v-btn>

The v2 frontend introduces a token-driven design system. Tokens are defined in frontend/src/v2/tokens/ and compiled to tokens.css via the npm run build:tokens command. The UI consumes CSS variables instead of hard-coded values:

<v-btn class="bg-primary text-on-primary">New Button</v-btn>

Here, bg-primary resolves to var(--color-primary), ensuring consistent theming across the application and eliminating raw hex literals in component code.

Routing Strategy

The legacy router in frontend/src/router.ts defines 36 static component imports with simple path-to-component mappings:

// frontend/src/router.ts
{
  path: '/gallery/:platform',
  component: () => import('@/views/Gallery/Platform.vue')
}

The v2 router in frontend/src/v2/router/routes.ts adds named-view placeholders (home, activity, etc.) and lazy-loads components using defineAsyncComponent:

// frontend/src/v2/router/routes.ts
{
  path: '/gallery/:platform',
  component: () => import('@/v2/views/Gallery/Platform.vue')
}

This architecture enables frontend/src/v2/layouts/AppLayout.vue to host both v1 and v2 views simultaneously during the transition period.

Input Handling and Responsiveness

The v1 frontend supports mouse and keyboard only, with gamepad functionality living in a separate console/ branch. Responsive layouts rely on media queries scattered across component-scoped <style> blocks.

The v2 frontend implements a universal input layer through a stack-based input bus in frontend/src/v2/input/. This provides mouse, touch, keyboard, and gamepad events to every component. The useGamepad composable in frontend/src/v2/composables/useGamepad/index.ts enables gamepad interactions through custom directives:

// v1: Mouse-only
<button @click="onClick">Click me</button>

// v2: Gamepad + Mouse
<button @click="onClick" v-gamepad:confirm="onConfirm">Press A or Click</button>

For responsive design, v2 uses the useBreakpoint and useResponsiveColumns composables to drive layout logic consistently across the application, replacing scattered media queries.

Summary

  • RomM v1 is frozen under frontend/src/views/ and frontend/src/components/ with flat hierarchies, Vuetify theming, and mouse-only input.
  • RomM v2 is active under frontend/src/v2/ with a three-tier component model (lib, shared, feature), token-driven CSS variables, and universal gamepad support.
  • Both stacks share the same main.ts entry point, but v2 registers legacy routes as named views while introducing lazy-loaded v2 routes.
  • New features and UI bugs must be implemented in the v2 stack, accessed via the uiVersion setting.

Frequently Asked Questions

How do I enable the v2 frontend in RomM?

Set the uiVersion configuration option to activate the v2 experience. When enabled, frontend/src/main.ts loads the v2 router from frontend/src/v2/router/routes.ts instead of defaulting to the legacy v1 routes.

Can I contribute new features to the v1 frontend?

No. The v1 legacy frontend is frozen and only accepts critical bug fixes. All new features, redesigns, and UI-only bug fixes must be implemented in the v2 stack under frontend/src/v2/ according to the architecture guide in docs/FRONTEND_ARCHITECTURE.md.

What is the three-tier component architecture in RomM v2?

The v2 architecture organizes components into three strict tiers: lib for primitives like RAvatar, shared for composite UI elements like PageHeader, and feature for page-specific views under frontend/src/v2/views/. This replaces the flat folder structure of v1.

How does RomM v2 handle gamepad input differently from v1?

While v1 only supports mouse and keyboard, v2 implements a universal input bus in frontend/src/v2/input/ that normalizes gamepad, keyboard, and touch events. Components consume these through composables like useGamepad and directives like v-gamepad, allowing gamepad navigation throughout the interface.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →