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

> Explore RomM's frontend evolution from v1 legacy's flat hierarchy to v2's three-tier architecture. Discover token-driven design systems and universal input handling.

- Repository: [The RomM Project/romm](https://github.com/rommapp/romm)
- Tags: architecture
- Published: 2026-07-06

---

**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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/main.ts) loads the v2 router from [`frontend/src/v2/router/routes.ts`](https://github.com/rommapp/romm/blob/main/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:

```typescript
// 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:

```typescript
// 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:

```vue
<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`](https://github.com/rommapp/romm/blob/main/tokens.css) via the `npm run build:tokens` command. The UI consumes CSS variables instead of hard-coded values:

```vue
<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`](https://github.com/rommapp/romm/blob/main/frontend/src/router.ts) defines 36 static component imports with simple path-to-component mappings:

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

```

The v2 router in [`frontend/src/v2/router/routes.ts`](https://github.com/rommapp/romm/blob/main/frontend/src/v2/router/routes.ts) adds **named-view** placeholders (`home`, `activity`, etc.) and lazy-loads components using `defineAsyncComponent`:

```typescript
// 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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/frontend/src/v2/composables/useGamepad/index.ts) enables gamepad interactions through custom directives:

```typescript
// 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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/frontend/src/main.ts) loads the v2 router from [`frontend/src/v2/router/routes.ts`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/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.