feat(app): add composition root

This commit is contained in:
syntaxbullet
2026-07-03 09:36:04 +02:00
parent d7406e803a
commit 9422494458
6 changed files with 47 additions and 17 deletions

View File

@@ -4,6 +4,7 @@ Follow these rules for the whole repository. More specific `AGENTS.md` files ove
## Boundaries
- `core/`: pure domain models only. See `core/AGENTS.md`.
- `app/`: composition root for wiring stores, command registries, input, renderer, and view adapters.
- `commands/`: the only write path for application state.
- `editor/`: transient editor/app state types and app store, e.g. viewport/camera and selection.
- `input/`: keyboard, pointer, mouse, touch, pen, and wheel resolution; global consumer first, command fallback second.
@@ -45,6 +46,7 @@ Follow these rules for the whole repository. More specific `AGENTS.md` files ove
- Avoid ad-hoc component-local shortcuts/gestures unless they are purely local UI behavior and cannot affect app/editor/document state.
## Imports
- `app/` may import from all layers; lower layers must not import from `app/`.
- `core/` imports nothing from app layers.
- `commands/` may import `core/` and `editor/`; avoid importing React or renderer backend APIs.
- `editor/` may import `core/` types and command dispatch infrastructure; it must not import React, renderer, storage, or backend APIs.

7
app/AGENTS.md Normal file
View File

@@ -0,0 +1,7 @@
# App Composition Rules
- `app/` is the composition root for runtime wiring.
- Create registries, stores, command sets, input adapters, and renderer/view adapters here.
- Do not implement domain rules, command behavior, rendering backend logic, or React UI here.
- App composition may import from all layers, but lower layers must not import from `app/`.
- Keep factories testable; avoid module-level mutable singleton state unless explicitly required by the runtime entrypoint.

16
app/app.ts Normal file
View File

@@ -0,0 +1,16 @@
import { createCommandRegistry } from "@commands/registry";
import { viewportCommands } from "@commands/viewport";
import { createInitialAppState } from "@editor/initial-state";
import { createAppStore } from "@editor/store";
export type ImageStudioApp = ReturnType<typeof createImageStudioApp>;
export function createImageStudioApp(options?: { documentName?: string }) {
const registry = createCommandRegistry([...viewportCommands]);
const store = createAppStore(createInitialAppState(options?.documentName), registry);
return {
registry,
store,
};
}

2
app/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export type { ImageStudioApp } from "./app";
export { createImageStudioApp } from "./app";

View File

@@ -2,11 +2,13 @@ import js from "@eslint/js";
import tseslint from "typescript-eslint";
const appLayerImports = [
"@app/*",
"@view/*",
"@renderer/*",
"@commands/*",
"@editor/*",
"@input/*",
"../app/*",
"../view/*",
"../renderer/*",
"../commands/*",
@@ -30,12 +32,7 @@ export default tseslint.config(
{
files: ["core/**/*.{ts,tsx}"],
rules: {
"no-restricted-imports": [
"error",
{
patterns: appLayerImports,
},
],
"no-restricted-imports": ["error", { patterns: appLayerImports }],
},
},
{
@@ -43,9 +40,7 @@ export default tseslint.config(
rules: {
"no-restricted-imports": [
"error",
{
patterns: ["@view/*", "@renderer/*", "../view/*", "../renderer/*", "react", "react-dom"],
},
{ patterns: ["@app/*", "@view/*", "@renderer/*", "../app/*", "../view/*", "../renderer/*", "react", "react-dom"] },
],
},
},
@@ -54,9 +49,7 @@ export default tseslint.config(
rules: {
"no-restricted-imports": [
"error",
{
patterns: ["@view/*", "@renderer/*", "../view/*", "../renderer/*", "react", "react-dom"],
},
{ patterns: ["@app/*", "@view/*", "@renderer/*", "../app/*", "../view/*", "../renderer/*", "react", "react-dom"] },
],
},
},
@@ -65,9 +58,7 @@ export default tseslint.config(
rules: {
"no-restricted-imports": [
"error",
{
patterns: ["@view/*", "../view/*", "react", "react-dom"],
},
{ patterns: ["@app/*", "@view/*", "../app/*", "../view/*", "react", "react-dom"] },
],
},
},
@@ -77,7 +68,18 @@ export default tseslint.config(
"no-restricted-imports": [
"error",
{
patterns: ["@view/*", "@renderer/*", "@editor/*", "../view/*", "../renderer/*", "../editor/*", "react", "react-dom"],
patterns: [
"@app/*",
"@view/*",
"@renderer/*",
"@editor/*",
"../app/*",
"../view/*",
"../renderer/*",
"../editor/*",
"react",
"react-dom",
],
},
],
},

View File

@@ -29,7 +29,8 @@
"@renderer/*": ["./renderer/*"],
"@commands/*": ["./commands/*"],
"@editor/*": ["./editor/*"],
"@input/*": ["./input/*"]
"@input/*": ["./input/*"],
"@app/*": ["./app/*"]
},
// Some stricter flags (disabled by default)