JST documentation

Architecture

JST provides boundaries, not a framework inside a framework. Add only the layers a use case needs, keep effectful code replaceable, and make ownership obvious from the file tree.

Dependency direction

app → pages → modules → widgets → features → entities → shared

Ownership follows the decision, not the data. Entities own reusable domain state and domain-wide operations. Application services own use cases that remain meaningful without the current UI. Feature view models own interaction state and presentation orchestration: selection, filters, pending state, optimistic projection, rollback/undo presentation, and outcome messages. A feature must not be a thin proxy over workflow logic hidden in an entity store, and a view model must not become a hidden application-service layer.

Dependencies point downward. Slices and modules cannot import siblings directly; compose them from a higher layer, inject a narrow consumer-owned port/facade, or publish a typed completed fact for genuine fan-out. Tests follow the same public APIs and isolation as production code. Keep a test double with its consumer or expose an intentional testing contract instead of deep-importing another slice’s private __tests__ directory.

Every entity, feature, and widget exposes its supported surface through <slice>.public.ts. Other slices and pages import that file instead of deep-importing private implementation. create:slice generates the public API automatically.

A complete vertical slice

HTTP client
  → repository adapter
  → runtime DTO validation
  → domain mapper
  → use-case service
  → Reatom store
  → feature entry
  → props-driven view

This is a menu, not mandatory boilerplate. Static content and trivial local controls do not need repositories, services, stores, or DI.

SOLID in React

Dependency injection

Import stable lower-layer code directly: types, pure models, mappers, and components. Use DI for effectful or replaceable boundaries, request-scoped services, or dependencies tests must substitute.

useService is restricted to app and pages. Lower layers receive dependencies through constructors or feature injectors. A feature view never imports a container, store, service, repository, or adapter.

Do not add an interface for a single pure implementation, inject plain data, or use DI to hide unclear ownership.

Choose the narrowest valid lifetime:

Mutable feature state must not become an application singleton merely to simplify access.

Data boundaries

Use generated clients or schema libraries when an API contract justifies them. The showcase uses a small manual parser because it has one endpoint and no OpenAPI source.

State and MVVM

Reatom stores are view models for non-trivial async or interactive flows. They own user actions and expose explicit loading, refreshing, error, empty, and ready states. Feature entries observe stores and translate them into narrow view props.

Local presentation state stays local. Do not turn a component hook into a hidden service/store/repository stack.

Styling

Tests and enforcement

The executable rules come from @jst-stack/eslint-plugin: its flat-config preset runs in editors and its jst-lint CLI validates cross-file architecture and stylesheet ownership. Stylelint handles CSS syntax. The concise coding-agent contract lives in skills/frontend-architecture/SKILL.md.

The standard is strict but replaceable. Use jst.createConfig({ files: { testSuffixes: ['spec'] } }) in eslint.config.js to override a deliberate convention without editing or forking rule code. The same policy API exposes focused files, imports, effects, ui, and limits overrides; unspecified values retain JST defaults.

Enforced source contract

These rules are errors in the editor through ESLint, in npm run lint, and at commit time:

Create a compliant empty slice instead of assembling folders by hand:

npm run create:slice -- entity account
npm run create:slice -- feature signIn
npm run create:slice -- module projectManagement --stateful
npm run create:slice -- widget accountSummary

The generator prints the public entry to compose from an owning page or higher layer. Knip deliberately rejects a newly generated but unreachable slice, so connect that public entry before running the complete quality gate.

Add --stateful to an entity or feature when it needs a Reatom view model. A stateful feature scaffold includes the store, reactive entry, props-driven view, public API, and required Reatom packages:

npm run create:slice -- feature checkout --stateful

The limits are defaults, not permission to disable rules inline. If a real module cannot fit them, split responsibilities first; change a limit only through review with a concrete counterexample.

Growth path

Use the smallest boundary that matches demonstrated complexity:

page-local → vertical slices → bounded-context module → workspace package → microfrontend

npm run create:package -- orderOperations creates an opt-in workspace package, one public entry, and a required extraction ADR. Use --kind microfrontend only when the capability has an autonomous owner and independent deployment; the command then requires owner, host contract, and fallback metadata. See Architecture evolution.