From 9422494458e6279fc72f9f03bf3eaab595a9396f Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Fri, 3 Jul 2026 09:36:04 +0200 Subject: [PATCH] feat(app): add composition root --- AGENTS.md | 2 ++ app/AGENTS.md | 7 +++++++ app/app.ts | 16 ++++++++++++++++ app/index.ts | 2 ++ eslint.config.js | 34 ++++++++++++++++++---------------- tsconfig.json | 3 ++- 6 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 app/AGENTS.md create mode 100644 app/app.ts create mode 100644 app/index.ts diff --git a/AGENTS.md b/AGENTS.md index f87d742..a4b664d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/app/AGENTS.md b/app/AGENTS.md new file mode 100644 index 0000000..8889cab --- /dev/null +++ b/app/AGENTS.md @@ -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. diff --git a/app/app.ts b/app/app.ts new file mode 100644 index 0000000..9d65b59 --- /dev/null +++ b/app/app.ts @@ -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; + +export function createImageStudioApp(options?: { documentName?: string }) { + const registry = createCommandRegistry([...viewportCommands]); + const store = createAppStore(createInitialAppState(options?.documentName), registry); + + return { + registry, + store, + }; +} diff --git a/app/index.ts b/app/index.ts new file mode 100644 index 0000000..a410163 --- /dev/null +++ b/app/index.ts @@ -0,0 +1,2 @@ +export type { ImageStudioApp } from "./app"; +export { createImageStudioApp } from "./app"; diff --git a/eslint.config.js b/eslint.config.js index ecfadea..44394c1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -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", + ], }, ], }, diff --git a/tsconfig.json b/tsconfig.json index 3b695ca..cbf7f6e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,8 @@ "@renderer/*": ["./renderer/*"], "@commands/*": ["./commands/*"], "@editor/*": ["./editor/*"], - "@input/*": ["./input/*"] + "@input/*": ["./input/*"], + "@app/*": ["./app/*"] }, // Some stricter flags (disabled by default)