Browse pages
Frontend Structure
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.
Stack: any Layer: frontend-ui
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#
ruleruleguidancelitespec/frontend/structure#organize-a-frontend-into-frame-routes-and-modulesA frontend MUST organize its code into a frame, routes, and use-case modules.
Applies toartifact: any
Why
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.
{frontend}/
frame/ client configuration, session, language, permissions, formatting, shell data
routes/ one entry per address
modules/ one folder per backend module, aggregate, and use caseChecka reviewer answers
Reviewed forWhich frontend files sit outside the frame, routes, and use-case modules split? Look in the frontend folder roots.
Marked guidance: a reviewer answers this question.
Mirror the Backend Use Case Path in Kebab Case#
ruleruleguidancelitespec/frontend/structure#mirror-the-backend-use-case-path-in-kebab-caseA 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.
Applies toartifact: any
Why
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.
<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.
<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.
Checka reviewer answers
Reviewed forWhich 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.
Marked guidance: a reviewer answers this question.
Keep a Use Case Folder Flat#
ruleruleguidancelitespec/frontend/structure#keep-a-use-case-folder-flatA folder named for one use case MUST hold files and no subfolder.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forWhich use-case folders hold a subfolder? Look in the module tree.
Marked guidance: a reviewer answers this question.
Place a Shared File in the Folder That Owns It#
ruleruleguidancelitespec/frontend/structure#place-a-shared-file-in-the-folder-that-owns-itA file read by several use cases of one aggregate MUST sit flat in that aggregate's folder.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forWhich files read by several use cases sit outside the narrowest folder holding every reader? Look in the aggregate and module folders.
Marked guidance: a reviewer answers this question.
Reject a Folder Name That Owns Nothing#
ruleruleguidancelitespec/frontend/structure#reject-a-folder-name-that-owns-nothingA folder under an application's src MUST NOT be named shared, common, utils, util, helpers, misc, components, hooks, types, or services.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forWhich folders under an application's src carry a refused name? Look in the application folder tree.
Marked guidance: a reviewer answers this question.
Keep a Route Entry Thin#
ruleruleguidancelitespec/frontend/structure#keep-a-route-entry-thinA route entry MUST hold its input, its load, and its component, with no reusable logic and no reference to another route entry.
Applies toartifact: route
Why
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.
Checka reviewer answers
Reviewed forWhich route entries hold reusable logic or reference another route entry? Look in the route entries.
Marked guidance: a reviewer answers this question.
Mark Each Route with Its Screen#
rulerulecheckablelitespec/frontend/structure#mark-each-route-with-its-screenA route entry MUST carry one mark naming the screen it renders, in the form its stack page states.
Applies toartifact: route
Why
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, React Stack, Blazor Stack, and Android Stack 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;.
Checkchecked by a tool
Checked bylitespec 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.
Marked checkable: the tool named here checks it.
Order Imports from Routes to Shared Packages#
ruleruleguidancelitespec/frontend/structure#order-imports-from-routes-to-shared-packagesAn import MUST point from a route to a module to the frame to a shared package, and never in the reverse direction.
Applies toartifact: any
Why
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.
Routes -> modules -> frame -> shared packagesThe 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.
Checka reviewer answers
Reviewed forWhich imports point against the route to module to frame to shared package direction? Look in the import statements.
Marked guidance: a reviewer answers this question.
Keep Applications Independent#
ruleruleguidancelitespec/frontend/structure#keep-applications-independentA frontend MUST NOT import source from another frontend.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forWhich frontends import source from another frontend? Look in the import statements.
Marked guidance: a reviewer answers this question.
Keep Shared Packages Non Application Specific#
ruleruleguidancelitespec/frontend/structure#keep-shared-packages-non-application-specificA shared package MUST NOT contain page composition, feature state, authentication policy, or application components.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forWhich shared packages hold page composition, feature state, authentication policy, or application components? Look in the shared package sources.
Marked guidance: a reviewer answers this question.
Locate Cross-Route Values in the Frame#
ruleruleguidancelitespec/frontend/structure#locate-cross-route-values-in-the-frameA frontend MUST place cross-route values in its frame: client configuration, session read, language choice, permissions, formatting, and shell data.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forWhich client configuration, session read, language choice, permissions, formatting, shell data, or browser uses sit outside the frame? Look in the frame.
Marked guidance: a reviewer answers this question.
Conventions#
Use Explicit Public Entry Points for Shared Packages#
conventiondefaultguidancelitespec/frontend/structure#use-explicit-public-entry-points-for-shared-packagesExpose a shared package only through its documented public entry points.
ReplacementA consumer can replace this default with an explicit local convention.
Applies toartifact: any
Why
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.
Checka reviewer answers
Reviewed forWhich application imports reach past the documented public entry points of shared packages? Look in the application imports and the package manifests.
Marked guidance: a reviewer answers this question.
Place Tests at Their Ownership Boundary#
conventiondefaultguidancelitespec/frontend/structure#place-tests-at-their-ownership-boundaryPlace a logic test beside its module, and keep browser suites under one tests root per frontend.
ReplacementA consumer can replace this default with an explicit local convention.
Applies toartifact: test
Why
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.
Checka reviewer answers
Reviewed forWhich 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.
Marked guidance: a reviewer answers this question.
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.