RFC-008: Responsibility Segregation and Feature-First Architecture
Status
Proposed
Overview
This RFC defines a new code ownership model for the Photobooth Web App. The current repository structure is organized mostly by framework primitives such as app, providers, components, store, and lib. That structure is no longer providing clear boundaries as the application grows.
The target architecture is feature-first with thin shared layers:
app/for route composition onlyfeatures/for workflow logic, hooks, and feature-owned UIui/for shared presentational componentsdomain/for pure business logic and modelsinfra/for storage, worker, device, and network adaptersserver/for transport, orchestration, business logic, and IO boundaries on the backend
The intent is to reduce coordination cost, stop new behavior from accumulating in lib, and make the largest parts of the app testable without page-level coupling.
Problem Statement
The repository currently has nominal layers, but responsibilities leak across them:
src/apppages own routing, workflow state, side effects, API calls, and persistence orchestrationsrc/providerscombine React context with browser APIs, device interactions, and feature logicsrc/libmixes pure utilities, browser-only code, storage, image processing, worker adapters, and API helperssrc/storeis coupled to UI workflows and persistence formatsrc/serverhas better boundaries than the frontend, but some controllers and use-cases still mix transport concerns with domain logic
This has already created complexity hotspots:
src/app/customization/components/tabs/frame/visual-frame-editor.tsxsrc/app/customization/page.tsxsrc/app/customization/components/live-preview.tsxsrc/lib/image-processing.tssrc/lib/storage.tssrc/app/image-upload/page.tsxsrc/providers/printer-provider.tsx
The issue is not only file size. The main problem is that a single file often owns multiple reasons to change: rendering, mutation logic, side effects, browser/device access, and domain rules.
Current Architecture
The current runtime diagram is accurate at a system level, but it does not describe code ownership boundaries well enough for refactoring.
Current Layering Diagram
Current Responsibility Summary
- Pages act as route components and feature controllers at the same time
- Providers act as state containers and integration layers at the same time
libacts as a shared utility folder and a domain owner at the same time- Domain logic is spread across UI files, providers, stores, and utilities
Target Architecture
The target architecture standardizes feature-first ownership with narrow shared horizontal layers.
Target Layering Diagram
Dependency Direction
appmay depend onfeatures,ui, and route-level framework primitivesfeaturesmay depend onui,domain, andinfrauimay depend on otheruimodules and minimal shared utilitiesdomainmay depend only on other pure domain modulesinframay depend ondomainand external APIs, but must not own business policy- backend controllers may depend on use-cases and transport helpers only
Layer Responsibilities
app/
Allowed:
- route definitions
- page composition
- layout composition
- dynamic imports and route-specific assembly
Not allowed:
- business workflows
- storage orchestration
- direct device/browser integration
- non-trivial API/data orchestration
Rule:
- pages should compose feature entrypoints and remain mostly declarative
features/
Allowed:
- feature-specific hooks
- workflow orchestration
- feature-owned components
- feature-specific API call coordination
- state transitions for one feature area
Not allowed:
- generic shared UI primitives
- cross-feature dumping of unrelated helpers
- direct ownership of persistence or transport implementations
Rule:
- feature modules own behavior, not infrastructure
ui/
Allowed:
- shared visual primitives
- styling
- presentational composition
Not allowed:
fetch- storage access
- device APIs
- business rules
Rule:
- shared UI renders data provided from above and emits user intent upward
domain/
Allowed:
- pure business rules
- data transformations
- frame/layout/image math
- validation and state transitions that do not require IO
Not allowed:
- React
- browser APIs
- fetch/network code
- IndexedDB/localStorage/device access
Rule:
- domain code must be pure and easy to unit test
infra/
Allowed:
- persistence adapters
- worker adapters
- printer/device adapters
- API URL building
- external service wrappers
Not allowed:
- business policy that changes based on product rules
- rendering concerns
Rule:
- infrastructure modules talk to external systems through explicit interfaces
server/
Allowed:
- controllers for request/response handling
- use-cases for orchestration
- services or domain modules for business rules
- repositories/outbound clients for IO
Not allowed:
- controllers owning business policy
- use-cases formatting transport responses directly when a serializer/helper should do it
Rule:
- server controller code should remain transport-only
Mapping Current Layers to Target Layers
| Current location | Current problem | Target location |
|---|---|---|
src/app/customization/* | page and component files own workflow logic | src/features/customization/* plus small route composition in src/app |
src/lib/image-processing.ts | pure transforms, canvas work, and worker coordination are mixed | src/domain/image-processing/* and src/infra/image-processing/* |
src/lib/storage.ts | transport, fallback policy, and migration logic are mixed | src/infra/persistence/* |
src/providers/printer-provider.tsx | provider owns device logic and workflow state | src/features/printing/* and src/infra/printing/* |
src/providers/license-provider.tsx | provider mixes feature behavior with API calls | src/features/license/* and shared API adapters in src/infra/* |
src/server/sharing/* | controller owns too much request-specific behavior | thinner controller/use-case/service boundaries in src/server/* |
src/server/payments/* | orchestration and transport concerns are close together | keep use-case and controller boundaries explicit |
Initial Migration Plan
Migration is incremental and behavior-preserving. This RFC does not authorize a large rewrite.
Track 1: Customization and Editor Decomposition
Introduce:
src/features/customization/editor/*src/features/customization/hooks/*
First extractions:
- selection and drag/resize logic from the visual editor
- preset CRUD logic from the customization page
- customer license lookup from the customization page
- feature-level orchestration from page components
Temporary state:
- routes may continue to import feature modules from
src/appduring migration - existing shared UI can remain in place until its ownership becomes clear
Track 2: Image Processing Split
Introduce:
src/domain/image-processing/*src/infra/image-processing/*
First extractions:
- color adjustment and dithering logic into pure domain modules
- crop/layout math into pure domain modules
- worker integration and browser canvas entrypoints into infra modules
Temporary state:
- one compatibility facade may remain while call sites migrate away from
src/lib/image-processing.ts
Track 3: Storage and Persistence Split
Introduce:
src/infra/persistence/*
First extractions:
- IndexedDB adapter
- localStorage adapter
- reconciliation/dual-write policy
- settings shape migration helpers
Temporary state:
- stores and providers may continue to consume a facade while the persistence boundary is formalized
File and Complexity Guardrails
Use these working limits going forward:
- page or component files should target under 200 LOC
- page or component files above 300 LOC require decomposition or explicit justification
- domain and utility modules should target under 150-200 LOC unless algorithmically dense
- a single file should not combine rendering, side effects, and business rules
- new complex logic should not be added to
src/libwithout a documented reason
These are review guardrails, not hard build rules.
Acceptance Criteria
This RFC is considered successful when the following become true over the next refactor phases:
- new feature work lands in
features/*rather thanapppages orlib - route files in
appbecome composition-only - the customization/editor surface is split into smaller workflow and rendering modules
- image-processing is separated into pure logic and adapter layers
- storage is separated into explicit persistence adapters and migration helpers
- server controllers become thinner where transport and business logic currently mix
Non-Goals
- no visual redesign
- no intentional product behavior changes
- no mandatory one-shot rename of the entire repository
- no backend rewrite beyond boundary cleanup
- no attempt to fully eliminate all existing top-level folders immediately
Testing and Verification
Each migration slice should preserve behavior and move tests with the extracted logic.
- pure domain logic should gain direct unit tests
- feature hooks should gain focused tests where page-level coupling exists today
- shared UI should be testable without feature context where practical
- API behavior should remain stable while server boundary cleanup happens
- each slice should be deployable independently
Default Decisions
This RFC makes these defaults explicit:
- architecture direction: feature-first
libis no longer the default home for complex code- runtime boundaries should be explicit rather than hidden behind convenience layers
- the first implementation slice after the RFC should be the customization/editor area