# Frontend Structure

**Stack:** any
**Layer:** frontend-ui

## Intent

A LiteSpec frontend holds three parts: a frame holding what every route shares, routes for the addresses it serves, and one module folder per backend use case it calls, with the module tree mirroring the backend module folders in kebab case. This page states those parts, the import direction between them, and where a file shared by several use cases belongs. Frontend developers and agents apply it whenever they add or move frontend code, so a reader holding a use-case specification finds the frontend code that serves it without a second map.

## Concepts

### The Three Parts

The frame holds everything cross-cutting: client configuration, session read, language choice, permissions, formatting, and shell data. Routes hold one entry per address the frontend serves. Modules hold one folder per backend use case the frontend calls. Each frontend is one entry-point record, owning its routes, environment, session handling, words, tests, and deployment.

### The Module Mirror

The module tree mirrors the backend module tree in kebab case: `{module}/{aggregate}/{use-case}/`, where module, aggregate, and use case are the backend's three grouping names. A reader holding a use case specification translates the three backend names to lowercase folders and lands on the frontend code that serves it, with no second map to learn.

### The Import Direction

Imports point one way: routes import modules, modules import the frame, and the frame and modules import shared packages. The direction keeps addresses, use-case code, cross-route values, and shared code in separate layers, with each layer building only on the layers inside it.

## Standards

### Organize a Frontend into Frame, Routes, and Modules

**ID:** `litespec/frontend/structure#organize-a-frontend-into-frame-routes-and-modules`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** A frontend MUST organize its code into a frame, routes, and use-case modules.

**Rationale:** Three parts give every file one home by answering one question: is it cross-cutting, is it an address, or does it serve one use case. Without the split, shared values hide inside routes, use-case code hides inside shared folders, and no author can tell where a new file belongs. The three parts make the answer mechanical.

**Example:** Each stack names these three parts for its own toolchain.

```text
{frontend}/
  frame/     client configuration, session, language, permissions, formatting, shell data
  routes/    one entry per address
  modules/   one folder per backend module, aggregate, and use case
```

**Reviewed for:** Which frontend files sit outside the frame, routes, and use-case modules split? Look in the frontend folder roots.

### Mirror the Backend Use Case Path in Kebab Case

**ID:** `litespec/frontend/structure#mirror-the-backend-use-case-path-in-kebab-case`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** A use-case folder MUST sit under the modules root at `{module}/{aggregate}/{use-case}/` in kebab case, or at `{module}/use-cases/{use-case}/` for a query describing no one aggregate, resolving to the one backend use case folder of the same names inside its `UseCases/` folder.

**Rationale:** A reader holding a specification page finds the frontend code by translating three names to lowercase folders. No second map, index, or naming register stands between the specification and the code, so an agent given a use case id resolves its frontend folder by rule. The backend holds one folder per use case, so the mirror names one folder. The frontend structure test holds the mirror on every build under `litespec/frontend/testing#prove-the-module-mirror-in-a-structure-test`.

**Example:** One backend folder and its frontend counterpart are the same path in two spellings.

```text
<backend>/Orders/Orders/UseCases/SearchMerchantOrders/
<frontend>/modules/orders/orders/search-merchant-orders/
```

For example, the Claimwick product lists expenses through `use-case/spending.list-expenses`, implemented in the backend under `Spending/Expenses/UseCases/ListExpenses`. The web frontend serves it from the same path in kebab case: `lib/modules/spending/expenses/list-expenses` in the Claimwick example, beside the screen record `screen/claimwick-app.expense-list`.

```text
<backend>/Spending/Expenses/UseCases/ListExpenses/
<frontend>/modules/spending/expenses/list-expenses/
```

The three backend names become three lowercase folder names, so a reader holding the use case finds the frontend code by translating Spending, Expenses, and ListExpenses. The `UseCases/` segment carries no frontend spelling.

A query that describes no one aggregate has its record home under `use-cases/` beside its module, so its folder mirrors that home with two names instead of three: a query named `use-case/spending.read-spending-summary` would live at `<frontend>/modules/spending/use-cases/read-spending-summary/` and `<backend>/Spending/UseCases/ReadSpendingSummary/`. The running Claimwick example holds no such module-level query.

The diagram shows the backend path beside its frontend mirror, with one arrow per translated segment.

![The backend folder Spending, Expenses, SubmitExpense beside the frontend folders spending, expenses, submit-expense, with one arrow mapping each backend segment to its kebab-case spelling.](../assets/diagrams/frontend-use-case-path.svg)

**Reviewed for:** Which use-case folders sit outside `{module}/{aggregate}/{use-case}/` in kebab case, or resolve to no backend folder of the same names? Look in the module tree and the structure test proving the mirror.

### Keep a Use Case Folder Flat

**ID:** `litespec/frontend/structure#keep-a-use-case-folder-flat`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** A folder named for one use case MUST hold files and no subfolder.

**Rationale:** A subfolder inside a use case folder names a grouping the backend does not have, so the one-to-one mirror between the two trees stops at that point. Past the subfolder, the reader can no longer translate names by rule, and nothing reports where the translation broke. Files directly in the use case folder keep the mirror checkable to the bottom.

**Reviewed for:** Which use-case folders hold a subfolder? Look in the module tree.

### Place a Shared File in the Folder That Owns It

**ID:** `litespec/frontend/structure#place-a-shared-file-in-the-folder-that-owns-it`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** A file read by several use cases of one aggregate MUST sit flat in that aggregate's folder.

**Rationale:** A file read by several use cases of one aggregate belongs flat in that aggregate's folder: the lowest folder holding every reader. There it sits beside its consumers, found by browsing rather than through an index, and moving it changes nothing outside the aggregate. A file read by several aggregates rises one more level, to the module folder, by the same rule.

**Example:** Three expense queries share one tab strip, so the tab strip sits flat in the aggregate folder. A file read by several aggregates sits flat in the module folder.

**Reviewed for:** Which files read by several use cases sit outside the narrowest folder holding every reader? Look in the aggregate and module folders.

### Reject a Folder Name That Owns Nothing

**ID:** `litespec/frontend/structure#reject-a-folder-name-that-owns-nothing`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** A folder under an application's `src` MUST NOT be named `shared`, `common`, `utils`, `util`, `helpers`, `misc`, `components`, `hooks`, `types`, or `services`.

**Rationale:** A folder named `shared`, `utils`, or `components` can refuse nothing: every author with a file of unclear ownership drops it there. The folder collects unrelated code that escapes review against the use cases it serves, and finding a file means searching a pile instead of translating a use case path. The refused names force each file into the folder that owns it. The rule covers an application's `src`, not a shared package's internals, which the package owns.

**Example:** A shared query three use cases read sits flat in the aggregate folder, not in a `shared/` folder beside them.

**Reviewed for:** Which folders under an application's `src` carry a refused name? Look in the application folder tree.

### Keep a Route Entry Thin

**ID:** `litespec/frontend/structure#keep-a-route-entry-thin`

**Strength:** rule

**Applies to:** artifact: route

**Statement:** A route entry MUST hold its input, its load, and its component, with no reusable logic and no reference to another route entry.

**Rationale:** A route entry serves one address: its input, its load, and its component. A column definition or validation rule written there is unreachable from the second route that needs it, so the second route writes its own copy. A reference from one route entry to another makes one address depend on another, so changing the first breaks the second. Reusable logic belongs in modules, where both routes import it.

**Example:** An expense list route validates its search, prefetches its read, and composes one screen from module components.

**Reviewed for:** Which route entries hold reusable logic or reference another route entry? Look in the route entries.

### Mark Each Route with Its Screen

**ID:** `litespec/frontend/structure#mark-each-route-with-its-screen`

**Strength:** rule

**Applies to:** artifact: route

**Statement:** A route entry MUST carry one mark naming the screen it renders, in the form its stack page states.

**Rationale:** The route is where a screen record becomes code, and its folder or file name only resembles the screen. One mark makes the link written and checkable: a web route exports the same typed `litespec` constant a use-case module exports, so a wrong identifier fails the consumer's type check against the generated union, and `litespec model check` reports it as `mark-unknown` where no type check ran, and a Blazor component or an Android destination carries the .NET mark the .NET reader already reads. Each stack page states its route entry, the file or class that carries the mark, and that layouts, error pages, and route groups are not route entries: the [Svelte Stack](https://litespec.dev/4.0.0/frontend/web/svelte/stack.md), [React Stack](https://litespec.dev/4.0.0/frontend/web/react/stack.md), [Blazor Stack](https://litespec.dev/4.0.0/frontend/blazor/stack.md), and [Android Stack](https://litespec.dev/4.0.0/frontend/mobile/android/stack.md) pages state theirs. The use cases a screen offers are not marked on the route; the builder computes them from its calls.

**Example:** The `<script module>` of the Claimwick expense list page exports `export const litespec = { implements: "screen/claimwick-app.expense-list" } as const satisfies LiteSpecMark;`.

**Checked by:** `litespec model check` reports `mark-missing` (level S) for a route entry with no mark, `mark-count-invalid` (level S) for one with more than one, and `mark-unknown` (level S) for a mark naming no screen of the route's entry point.

### Order Imports from Routes to Shared Packages

**ID:** `litespec/frontend/structure#order-imports-from-routes-to-shared-packages`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** An import MUST point from a route to a module to the frame to a shared package, and never in the reverse direction.

**Rationale:** Imports pointing from routes to modules to the frame to shared packages keep addresses, use-case code, cross-route values, and shared code in separate layers. A reverse import collapses two layers: a module importing a route ties use-case code to one address, and a shared package importing an application ties every application to one application's choices. One direction keeps each layer usable without the layers above it.

**Example:** This direction shows the allowed dependency path.

```text
Routes -> modules -> frame -> shared packages
```

The direction refuses three imports: a module reaching a route, a shared package reaching an application, and a frame module reaching into one module's use-case folder for a second module's data.

**Reviewed for:** Which imports point against the route to module to frame to shared package direction? Look in the import statements.

### Keep Applications Independent

**ID:** `litespec/frontend/structure#keep-applications-independent`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** A frontend MUST NOT import source from another frontend.

**Rationale:** Each application owns its routes, its environment, its session handling, its words, its tests, and its deployment, so it can change, test, and ship on its own. An import from another application's source ties the two together: a change to one breaks or redeploys the other. Code two applications genuinely share moves down into a shared package, which names neither application.

**Reviewed for:** Which frontends import source from another frontend? Look in the import statements.

### Keep Shared Packages Non Application Specific

**ID:** `litespec/frontend/structure#keep-shared-packages-non-application-specific`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** A shared package MUST NOT contain page composition, feature state, authentication policy, or application components.

**Rationale:** Shared packages hold what serves every frontend and names none of them: the generated client, the shared UI package, the locale dictionaries, and the frontend tooling. Page composition, feature state, authentication policy, or application components kept there would bind every application to one application's choices. Application-specific code stays in the application, where it can change without touching the rest.

**Reviewed for:** Which shared packages hold page composition, feature state, authentication policy, or application components? Look in the shared package sources.

### Locate Cross-Route Values in the Frame

**ID:** `litespec/frontend/structure#locate-cross-route-values-in-the-frame`

**Strength:** rule

**Applies to:** artifact: any

**Statement:** A frontend MUST place cross-route values in its frame: client configuration, session read, language choice, permissions, formatting, and shell data.

**Rationale:** Client configuration, session read, language choice, permissions, formatting, and shell data cross every route. A route holding one of them holds a copy beside every other route's copy, with nothing keeping the copies equal. The frame holds each once, where every route reaches it. A browser API a use-case module needs belongs in the frame too, because a module renders and reads data rather than touching the device.

**Reviewed for:** Which client configuration, session read, language choice, permissions, formatting, shell data, or browser uses sit outside the frame? Look in the frame.

## Conventions

### Use Explicit Public Entry Points for Shared Packages

**ID:** `litespec/frontend/structure#use-explicit-public-entry-points-for-shared-packages`

**Strength:** default

**Applies to:** artifact: any

**Default:** Expose a shared package only through its documented public entry points.

**Replacement:** A consumer can replace this default with an explicit local convention.

**Rationale:** A shared package is free to rearrange its internals between releases. An application importing an internal path couples itself to that layout, so the next rearrangement breaks the application. Documented public entry points are the stable surface the package keeps.

**Reviewed for:** Which application imports reach past the documented public entry points of shared packages? Look in the application imports and the package manifests.

### Place Tests at Their Ownership Boundary

**ID:** `litespec/frontend/structure#place-tests-at-their-ownership-boundary`

**Strength:** default

**Applies to:** artifact: test

**Default:** Place a logic test beside its module, and keep browser suites under one tests root per frontend.

**Replacement:** A consumer can replace this default with an explicit local convention.

**Rationale:** A logic test beside its module is found from the code it covers and runs with it. Browser suites instead share one fixture set and one deployment per frontend, so scattering them beside modules would split the fixtures and the runs. One tests root per frontend keeps the browser suites together.

**Reviewed for:** Which logic tests sit outside their module, and which browser suites sit outside the one tests root per frontend? Look in the module folders and the tests roots.

## Reference Example

This informative example demonstrates `litespec/frontend/structure#mirror-the-backend-use-case-path-in-kebab-case`, `litespec/frontend/structure#place-a-shared-file-in-the-folder-that-owns-it`, and `litespec/frontend/structure#reject-a-folder-name-that-owns-nothing`.

A search use case folder holds its query definition and its table. The aggregate folder holds the tab strip its three sibling use cases share. Nothing sits in a `shared/` folder, because that name owns nothing.
