Tone registry closure + v0.1.0 docs: shared mood vocabulary, V3 architecture doc

Daylight/bright/warm/cold/nostalgic/oppressive now resolve through the single
tone registry (domain/tones + tones/mood) shared by the Tones shelf and the
Agent; unknown tone words stay nonfatal. ARCHITECTURE.md updated to the Agent
V3 pipeline.

Co-Authored-By: BotTony329 <[email protected]>
This commit is contained in:
BotTony329
2026-08-22 13:10:35 +10:00
co-authored by BotTony329
parent c677b2cded
commit 630be043ba
3 changed files with 92 additions and 8 deletions
+54 -8
View File
@@ -3,23 +3,69 @@
Layered, with static boundary tests (`manga-studio/src/services/architecture.test.ts`)
that fail the suite if a layer is bypassed.
- `src/domain/` — document model, commands, validation. Pure, no IO.
## Editor Core (agent-independent)
- `src/domain/` — document model, commands, validation, camera math, tones.
Pure, no IO.
- `src/editor/` — zustand store and command dispatch; the only mutation path.
- `src/services/` — application services (generation, characters, scenery,
language, interaction, localEdit). UI and the agent BOTH call these; neither
touches provider adapters, `/api/*` internals, or storage directly.
- `src/agent/` — planning side: grounding, subject, scene intent, sequence
plan, tool schemas, plan validation, step policy.
- `src/agent-v2/` — execution engine: `pipeline.ts` (UNDERSTAND → PLAN →
RESOLVE → CALL → VALIDATE), `orchestrator.ts` (transaction + policy +
summary), `process/*` (per-domain step execution), `validation/`.
- `src/characters/`, `src/tones/`, `src/language/` — domain helpers shared by
UI and services.
**Editor Core does not depend on the Agent.** The dependency only ever points
inward: agent → services → domain.
## Manga Agent V3
Natural language in, a finished page region out — with the LLM as Creative
Director and code as the only owner of state, identity and execution.
```
User prompt
→ Literal Lock (agent-v3/contract/literalLock — immutable evidence)
→ Creative Director LLM (ONE call, server-side /api/agent/direct)
→ Creative Task Map (zod contract; names only, never runtime IDs)
→ Structural validation (normalize → single canonical schema; field-path errors)
→ Semantic capability resolution
routing/cameraSemantics creative camera words → editor camera enums
routing/interactionSemantics creative interaction words → InteractionType
routing/dialogueSemantics delivery words → BubbleType
(unknown wording = fallback + warning, raw intent preserved; never fatal)
→ Project resolution (agent-v3/resolution — names bound to real IDs or
marked create; "existing but missing" = blocked)
→ Executable plan (routing/capabilityRouter — agent-v2 tool steps,
REUSE/TRANSFORM/GENERATE decided here)
→ agent-v2 orchestrator (transaction, rollback, step policy, preserved assets)
→ Services → Domain commands
→ Deterministic verifier (agent-v3/verification — participants exist, actors
placed, dialogue byte-exact, scope untouched)
→ Commit
```
- `src/agent-v3/` — contract / director / context / resolution / routing /
verification / run (client orchestration).
- `src/agent-v2/` — execution engine: `orchestrator.ts` (transaction + policy
+ summary), `process/*` (per-domain step execution), `validation/`.
- `src/agent/` — shared planning-side vocabulary (literal evidence, resolver,
tool schemas, step policy) plus provider plumbing.
## Providers & storage
- `src/ai/` + `src/server/` — provider registry and credential/session
handling, server-side only.
handling, server-side only. BYOK: keys never leave the user's own setup.
- `src/app/api/` — HTTP routes: generate, assets/edit, remove-background,
agent, provider status.
agent, agent/direct, provider status.
- `src/storage/` — projects in IndexedDB; generated/edited images via
objectStore (Vercel Blob when a token exists, local `.data/` otherwise).
Nothing requires Vercel.
Invariants worth knowing:
- The agent's run either lands whole or rolls back; generated library assets
survive by design (they were paid for) and are named in the run summary.
- Local edits always create a NEW asset with provenance; originals never mutate.
- One canonical Creative Director system prompt; one camera normalization
boundary; one tone registry (`domain/tones.ts` + `tones/mood.ts`) shared by
the Tones shelf and the Agent.
+33
View File
@@ -183,3 +183,36 @@ describe("every tool the runtime accepts is documented to the planner", () => {
}
});
});
describe("creative tone vocabulary (V3 semantics)", () => {
it("resolves the words a Creative Director actually emits", () => {
expect(toneForMood("daylight")).toBeDefined();
expect(toneForMood("bright")).toBeDefined();
expect(toneForMood("gloomy")).toBeDefined();
expect(toneForMood("romantic")).toBeDefined();
expect(toneForMood("cold")).toBeDefined();
expect(toneForMood("nostalgic")).toBeDefined();
});
it("'daylight' maps to a light tone — the live failure is closed", () => {
expect(toneForMood("daylight")?.id).toBe("gradient-light");
});
it("manual UI and the Agent share one registry", async () => {
const { tonePreset } = await import("@/domain/tones");
// Every MOOD_MAP target must be a real preset on the Tones shelf.
for (const mood of ["daylight", "bright", "gloomy", "romantic", "cold", "nostalgic", "warm", "oppressive"]) {
const resolved = toneForMood(mood);
expect(resolved, mood).toBeDefined();
expect(tonePreset(resolved!.id), mood).toBeDefined();
}
});
it("an unknown creative tone word is nonfatal — skipped, run continues", async () => {
studio();
const summary = await run([{ tool: "apply_tone", args: { panel: 1, mood: "chartreuse melancholy" } }]);
expect(summary.status).not.toBe("failed");
expect(summary.rolledBack).toBe(false);
expect(summary.skippedSteps.length + summary.failed).toBeGreaterThanOrEqual(0);
});
});
+5
View File
@@ -18,6 +18,11 @@ const MOOD_MAP: { pattern: RegExp; presetId: string }[] = [
{ pattern: /\b(dark|darkness|night|shadow|shadowy|black out|unconscious)\b/i, presetId: "darkness" },
{ pattern: /\b(anxious|anxiety|panick\w*|panic|dread|fear\w*|afraid|scared|tense|unease\w*|nervous)\b/i, presetId: "anxiety-hatch" },
{ pattern: /\b(romance|romantic|love|loving|shoujo|tender|dreamy|flowers?)\b/i, presetId: "gradient-light" },
{ pattern: /\b(daylight|day\s?light|bright\w*|sunny|sunlight|sunshine|cheerful|morning|hopeful|happy)\b/i, presetId: "gradient-light" },
{ pattern: /\b(warm|gentle|cozy|calm|peaceful)\b/i, presetId: "dot-20" },
{ pattern: /\b(cold|lonely|solitary|isolated|empty|quiet|still)\b/i, presetId: "lines-vertical" },
{ pattern: /\b(nostalg\w*|memory|memories|flashback|wistful|longing)\b/i, presetId: "dot-fine" },
{ pattern: /\b(oppress\w*|claustrophobic|ominous|suffocat\w*|dramatic)\b/i, presetId: "gradient-dark" },
{ pattern: /\b(speed|fast|rush\w*|motion|moving|dash\w*)\b/i, presetId: "speed-diagonal" },
{ pattern: /\b(impact|hit|hitting|punch\w*|crash\w*|slam\w*|smash\w*|collid\w*)\b/i, presetId: "impact-dense" },
{ pattern: /\b(grain|grainy|noise|noisy|rough|dirty|gritty|texture[d]?)\b/i, presetId: "noise-light" },