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:
- lib – Primitive components (e.g.,
RAvatar,RButton) underfrontend/src/v2/lib/ - shared – Feature-level composites under
frontend/src/v2/components/shared/ - 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/andfrontend/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.tsentry 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
uiVersionsetting.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →