From d7588e21654a1d4828861933c941d3bf19abc52d Mon Sep 17 00:00:00 2001 From: nhyiramante1 <196460314+nhyiramante1@users.noreply.github.com> Date: Sun, 4 Oct 2026 10:00:26 -0400 Subject: [PATCH 1/4] docs: spec the src folder refactor Co-Authored-By: Claude Opus 5.5 --- docs/src-folder-refactor-pass.md | 203 +++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 docs/src-folder-refactor-pass.md diff --git a/docs/src-folder-refactor-pass.md b/docs/src-folder-refactor-pass.md new file mode 100644 index 0000000..d3e7d86 --- /dev/null +++ b/docs/src-folder-refactor-pass.md @@ -0,0 +1,203 @@ +# `src/` Folder Refactor — Implementation Spec + +Status: **SPEC READY — decided 2026-10-04 (Nhyira).** Audience: implementer (Codex). +Branch: `refactor/src-folders` (off `main` at `ae55265`). This spec is the first +commit on that branch. + +## Goal + +`src/` is one flat list of 41 modules plus their tests. Group them into folders +by responsibility so a reader can find the coach, the grounding checks, the map +state and the UI without opening every file. + +**This is a move-only change.** After it, the app, the prompt, the validator, +the tests and the build behave exactly as before. Reviewers should be able to +confirm that from the diff alone. + +## Non-goals (do not do any of these) + +- Do not rename any file. Only its folder changes. +- Do not change, add or remove any export, function, type, comment or string. +- Do not delete the deprecated shims `controller.ts` and `map-commands.ts`. + Move them like any other file. +- Do not fix the `types.ts` ↔ `assistance-contract.ts` import cycle, or the + type-only import from `session-persistence.ts` into `Map.tsx`. Both are known + and out of scope. +- **Do not edit any regular expression**, anywhere. That includes the one in + `ui-strings.ts` and the ones in `ui-locale.test.ts`; see "Traps" below. +- Do not reformat, reorder imports, or touch lines other than import paths. +- Do not add a path alias (`@/…`), barrel `index.ts` files, or tsconfig/vite + changes. + +## Target layout + +Every module moves with its co-located test (`x.ts` → same folder as +`x.test.ts`). Files not listed stay where they are. + +| Folder | Responsibility | Modules (tests move with them) | +| --- | --- | --- | +| `src/` (root) | entry point and shared definitions | `main.tsx`, `types.ts`, `config.ts`, `assistance-contract.ts` | +| `src/coach/` | one model turn: prompt, transport, response parsing, turn loop | `stage1-loop.ts`, `controller.ts`, `api.ts`, `provider-tools.ts`, `llm-contract.ts`, `assistant-response.ts`, `turn-shape.ts`, `language-context.ts` | +| `src/grounding/` | code checks on AI output | `validator.ts`, `normalize.ts`, `unicode-scripts.ts`, `action-gateway.ts`, `map-commands.ts` | +| `src/map/` | map, Source Bank and proposal state; provenance | `map-store.ts`, `store.ts`, `proposal-store.ts`, `map-layout.ts`, `map-export.ts`, `provenance-summary.ts`, `suggestion-adoption.ts` | +| `src/session/` | persistence, logging, diagnostics | `session-persistence.ts`, `event-ledger.ts`, `trace.ts`, `understanding.ts` | +| `src/platform/` | Writing Tools sign-in | `platform-session.ts`, `PlatformBootstrap.tsx`, `platform.css` | +| `src/ui/` | React components, UI copy, DOM helpers | `App.tsx`, `ControlRoom.tsx`, `Map.tsx`, `reader-view.tsx`, `reader-translation.ts`, `mutation-policy.tsx`, `useSpeechToText.ts`, `draft-anchor.ts`, `ui-locale.tsx`, `ui-strings.ts` | +| `src/i18n/` | translation dictionaries (unchanged) | existing `*.json` and `README.md` stay exactly where they are | + +Tests whose names don't match a module: + +| Test | Goes to | +| --- | --- | +| `candidate-store.test.ts` (tests `store.ts`) | `src/map/` | +| `proposal-fuzz.test.ts` | `src/map/` | +| `provider-tools.integration.test.ts` | `src/coach/` | +| `App.test.ts` | `src/ui/` | +| `useSpeechToText.test.tsx` | `src/ui/` | + +`assistance-contract.ts` stays at the root because nearly every folder imports +it and it shares the import cycle with `types.ts`. Keeping both at the root +stops that cycle from crossing folders. + +## Mechanics + +1. Use `git mv` for every move so `git log --follow` and rename detection keep + each file's history. +2. Fix every relative path that the moves break. There are five kinds; search + for all of them, not only `from "./…"`: + - static imports and re-exports: `import … from "./x"`, `export … from "./x"` + - inline type imports: `import("./x").SomeType` (present in `action-gateway.ts`, + `api.ts`, `stage1-loop.ts`, `types.ts`, `App.tsx`, `Map.tsx`, + `stage1-loop.test.ts`; check with `grep -rn 'import("\.' src` after moving) + - Vitest mocks: `vi.mock("./api", …)` in `reader-view.test.ts` + - side-effect imports: `import "./platform.css"` in `main.tsx` becomes + `import "./platform/platform.css"` + - the Vite glob in `ui-strings.ts` (see Traps) +3. Fix imports outside `src/`: `eval/run.ts`, `eval/reporting.ts` and + `eval/scenarios/*.ts` import `../src/` / `../../src/`. Point them + at the new folders. +4. Do **not** change: `index.html` (still loads `/src/main.tsx`, which stays at + the root), `scripts/generate-i18n.mjs` (it writes to `src/i18n/`, which does + not move), `tsconfig*.json`, `vite.config.ts`, `.github/workflows/*`. +5. If `tsc -b` complains about stale paths, delete `tsconfig.tsbuildinfo` (a + build cache) and rebuild. Do not commit it if it is untracked. + +## Traps + +**1. `ui-strings.ts` translation glob (silent failure).** It loads dictionaries +with + +```ts +const DICTIONARIES = import.meta.glob>("./i18n/*.json", { … }); +… +const code = /\.\/i18n\/([\w-]+)\.json$/.exec(path)?.[1]; +``` + +After moving to `src/ui/`, change **only** the glob string to +`"../i18n/*.json"`. Leave the regex untouched. The glob keys become +`"../i18n/zh.json"`, and the regex is not anchored at the start, so it still +matches `./i18n/zh.json` inside that key and extracts `zh`. If the glob is +wrong, every language silently falls back to English with no error. That is +why the guard test below is required. + +**2. `ui-locale.test.ts` reads source files by relative URL.** It calls +`new URL("./App.tsx" | "./Map.tsx" | "./PlatformBootstrap.tsx", import.meta.url)` +and `new URL("./i18n/source.json", import.meta.url)`. After it moves to +`src/ui/`, `App.tsx` and `Map.tsx` stay as `./…`. Change the other two to +`"../platform/PlatformBootstrap.tsx"` and `"../i18n/source.json"`. Change only +the path strings, not the regexes in that test. + +**3. Comment references.** Doc comments that mention a sibling path (e.g. +`ui-strings.ts`: "See ./i18n/README.md") may be updated to the new relative +path (`../i18n/README.md`). This is the only comment change allowed. + +## Guard test (the one new test) + +Add to `src/ui/ui-locale.test.ts`: + +```ts +it("loads a dictionary for every translation file in src/i18n", () => { + const files = readdirSync(new URL("../i18n/", import.meta.url)) + .filter((name) => name.endsWith(".json") && name !== "source.json") + .map((name) => name.slice(0, -".json".length)) + .sort(); + expect(files.length).toBeGreaterThan(0); + expect(translatedLanguages()).toEqual(files); +}); +``` + +(Import `readdirSync` from `node:fs` and `translatedLanguages` from +`./ui-strings`, matching how the file already imports.) This fails if the glob +or the code extraction breaks, which today's tests only catch for Chinese. + +## Docs + +Update `src/` path mentions in these docs to the new folders: +- `docs/login-bootstrap-spec.md:38` (`src/platform-session.ts`) +- `docs/multilingual-grounding-pass.md:40` (`src/ui-strings.ts`) +- `docs/polish-sweeps-pass.md:62` (`src/ui-strings.ts`) + +Leave the other path mentions alone. They name files that no longer exist +(`src/language.ts`, `src/translate.ts`, …) or `prototype-mindmap/src/…` in the +old repo, so they are historical. + +Add a short "Source layout" section to `README.md` with the folder table above +(folder + one-line responsibility only). + +## Commits + +1. `docs: spec the src folder refactor` (this file; already on the branch) +2. `refactor: group src modules into folders`: all `git mv`s plus import-path + fixes in **one** commit, so no commit leaves the tree broken +3. `test(i18n): guard that every dictionary file loads` +4. `docs: point path references at the new folders` + +Do not push. Report back when done. + +## Verification (all must pass; paste the output) + +```bash +npm test +npx tsc --noEmit +npm run eval:typecheck +npm run build:pages +npm run verify:pages +``` + +- Baseline on `main` (`ae55265`): **37 test files, 393 tests**. After: 37 files, + **394 tests** (the guard test is the only addition). +- Optional if Playwright browsers are installed: `npm run test:e2e`. + +**Move-only proof.** Rename detection must show every module as a rename: + +```bash +git diff -M --summary main..HEAD -- src | grep -c "rename" +``` + +The count should equal the number of moved files (modules plus tests plus +`platform.css`). + +Every changed line must be an import path, the glob string, a test-file path +string, the guard test, or the allowed comment path. This command should print +only the guard test's lines and the allowed comment/path-string lines: + +```bash +git diff -M main..HEAD -- src eval | grep -E '^[+-]' | grep -vE '^(\+\+\+|---)' \ + | grep -vE 'from "|import\(|vi\.mock\(|import\.meta\.glob|^[+-]\s*$' +``` + +**Picture.** If `codemap` is available: + +```bash +codemap C:\Users\nhyir\Linux_Folder\mindmap-extract --name mindmap-folders +``` + +The Folders tab should show the seven folders. The only circular import should +still be `types.ts` ↔ `assistance-contract.ts`. + +## Report format + +- the commands above with their output +- the rename count, and the full output of the move-only grep +- any file you could not place by this table, and where you put it +- anything in this spec that turned out to be wrong From 79f2241f52d037ce1a2fc16c74c6497389e36ce8 Mon Sep 17 00:00:00 2001 From: nhyiramante1 <196460314+nhyiramante1@users.noreply.github.com> Date: Sun, 4 Oct 2026 10:09:49 -0400 Subject: [PATCH 2/4] refactor: group src modules into folders --- eval/reporting.ts | 2 +- eval/run.ts | 12 +++--- eval/scenarios/index.test.ts | 2 +- eval/scenarios/recall.test.ts | 2 +- eval/scenarios/types.ts | 2 +- src/assistance-contract.test.ts | 2 +- src/{ => coach}/api.test.ts | 6 +-- src/{ => coach}/api.ts | 10 ++--- src/{ => coach}/assistant-response.ts | 10 ++--- src/{ => coach}/controller.ts | 0 src/{ => coach}/language-context.test.ts | 0 src/{ => coach}/language-context.ts | 2 +- src/{ => coach}/llm-contract.ts | 4 +- .../provider-tools.integration.test.ts | 8 ++-- src/{ => coach}/provider-tools.test.ts | 0 src/{ => coach}/provider-tools.ts | 0 src/{ => coach}/stage1-loop.test.ts | 10 ++--- src/{ => coach}/stage1-loop.ts | 22 +++++----- src/{ => coach}/turn-shape.test.ts | 2 +- src/{ => coach}/turn-shape.ts | 6 +-- src/{ => grounding}/action-gateway.test.ts | 4 +- src/{ => grounding}/action-gateway.ts | 14 +++---- src/{ => grounding}/map-commands.test.ts | 6 +-- src/{ => grounding}/map-commands.ts | 0 src/{ => grounding}/normalize.test.ts | 0 src/{ => grounding}/normalize.ts | 0 src/{ => grounding}/unicode-scripts.ts | 0 src/{ => grounding}/validator.test.ts | 4 +- src/{ => grounding}/validator.ts | 4 +- src/main.tsx | 4 +- src/{ => map}/candidate-store.test.ts | 2 +- src/{ => map}/map-export.test.ts | 2 +- src/{ => map}/map-export.ts | 2 +- src/{ => map}/map-layout.test.ts | 2 +- src/{ => map}/map-layout.ts | 2 +- src/{ => map}/map-store.test.ts | 2 +- src/{ => map}/map-store.ts | 6 +-- src/{ => map}/proposal-fuzz.test.ts | 2 +- src/{ => map}/proposal-store.test.ts | 0 src/{ => map}/proposal-store.ts | 6 +-- src/{ => map}/provenance-summary.test.ts | 2 +- src/{ => map}/provenance-summary.ts | 2 +- src/{ => map}/store.ts | 4 +- src/{ => map}/suggestion-adoption.test.ts | 2 +- src/{ => map}/suggestion-adoption.ts | 4 +- src/{ => platform}/PlatformBootstrap.test.ts | 2 +- src/{ => platform}/PlatformBootstrap.tsx | 12 +++--- src/{ => platform}/platform-session.test.ts | 0 src/{ => platform}/platform-session.ts | 0 src/{ => platform}/platform.css | 0 src/{ => session}/event-ledger.test.ts | 0 src/{ => session}/event-ledger.ts | 2 +- src/{ => session}/session-persistence.test.ts | 0 src/{ => session}/session-persistence.ts | 14 +++---- src/{ => session}/trace.ts | 2 +- src/{ => session}/understanding.test.ts | 0 src/{ => session}/understanding.ts | 4 +- src/{ => ui}/App.test.ts | 12 +++--- src/{ => ui}/App.tsx | 42 +++++++++---------- src/{ => ui}/ControlRoom.tsx | 6 +-- src/{ => ui}/Map.test.ts | 4 +- src/{ => ui}/Map.tsx | 14 +++---- src/{ => ui}/draft-anchor.test.ts | 0 src/{ => ui}/draft-anchor.ts | 0 src/{ => ui}/mutation-policy.test.ts | 0 src/{ => ui}/mutation-policy.tsx | 0 src/{ => ui}/reader-translation.test.ts | 0 src/{ => ui}/reader-translation.ts | 0 src/{ => ui}/reader-view.test.ts | 4 +- src/{ => ui}/reader-view.tsx | 2 +- src/{ => ui}/ui-locale.test.ts | 4 +- src/{ => ui}/ui-locale.tsx | 0 src/{ => ui}/ui-strings.ts | 4 +- src/{ => ui}/useSpeechToText.test.tsx | 0 src/{ => ui}/useSpeechToText.ts | 0 75 files changed, 153 insertions(+), 153 deletions(-) rename src/{ => coach}/api.test.ts (99%) rename src/{ => coach}/api.ts (99%) rename src/{ => coach}/assistant-response.ts (93%) rename src/{ => coach}/controller.ts (100%) rename src/{ => coach}/language-context.test.ts (100%) rename src/{ => coach}/language-context.ts (97%) rename src/{ => coach}/llm-contract.ts (95%) rename src/{ => coach}/provider-tools.integration.test.ts (95%) rename src/{ => coach}/provider-tools.test.ts (100%) rename src/{ => coach}/provider-tools.ts (100%) rename src/{ => coach}/stage1-loop.test.ts (99%) rename src/{ => coach}/stage1-loop.ts (98%) rename src/{ => coach}/turn-shape.test.ts (98%) rename src/{ => coach}/turn-shape.ts (84%) rename src/{ => grounding}/action-gateway.test.ts (99%) rename src/{ => grounding}/action-gateway.ts (97%) rename src/{ => grounding}/map-commands.test.ts (98%) rename src/{ => grounding}/map-commands.ts (100%) rename src/{ => grounding}/normalize.test.ts (100%) rename src/{ => grounding}/normalize.ts (100%) rename src/{ => grounding}/unicode-scripts.ts (100%) rename src/{ => grounding}/validator.test.ts (99%) rename src/{ => grounding}/validator.ts (99%) rename src/{ => map}/candidate-store.test.ts (97%) rename src/{ => map}/map-export.test.ts (98%) rename src/{ => map}/map-export.ts (98%) rename src/{ => map}/map-layout.test.ts (99%) rename src/{ => map}/map-layout.ts (99%) rename src/{ => map}/map-store.test.ts (99%) rename src/{ => map}/map-store.ts (99%) rename src/{ => map}/proposal-fuzz.test.ts (98%) rename src/{ => map}/proposal-store.test.ts (100%) rename src/{ => map}/proposal-store.ts (95%) rename src/{ => map}/provenance-summary.test.ts (96%) rename src/{ => map}/provenance-summary.ts (96%) rename src/{ => map}/store.ts (99%) rename src/{ => map}/suggestion-adoption.test.ts (99%) rename src/{ => map}/suggestion-adoption.ts (96%) rename src/{ => platform}/PlatformBootstrap.test.ts (98%) rename src/{ => platform}/PlatformBootstrap.tsx (96%) rename src/{ => platform}/platform-session.test.ts (100%) rename src/{ => platform}/platform-session.ts (100%) rename src/{ => platform}/platform.css (100%) rename src/{ => session}/event-ledger.test.ts (100%) rename src/{ => session}/event-ledger.ts (98%) rename src/{ => session}/session-persistence.test.ts (100%) rename src/{ => session}/session-persistence.ts (93%) rename src/{ => session}/trace.ts (91%) rename src/{ => session}/understanding.test.ts (100%) rename src/{ => session}/understanding.ts (97%) rename src/{ => ui}/App.test.ts (98%) rename src/{ => ui}/App.tsx (99%) rename src/{ => ui}/ControlRoom.tsx (98%) rename src/{ => ui}/Map.test.ts (97%) rename src/{ => ui}/Map.tsx (99%) rename src/{ => ui}/draft-anchor.test.ts (100%) rename src/{ => ui}/draft-anchor.ts (100%) rename src/{ => ui}/mutation-policy.test.ts (100%) rename src/{ => ui}/mutation-policy.tsx (100%) rename src/{ => ui}/reader-translation.test.ts (100%) rename src/{ => ui}/reader-translation.ts (100%) rename src/{ => ui}/reader-view.test.ts (98%) rename src/{ => ui}/reader-view.tsx (99%) rename src/{ => ui}/ui-locale.test.ts (95%) rename src/{ => ui}/ui-locale.tsx (100%) rename src/{ => ui}/ui-strings.ts (97%) rename src/{ => ui}/useSpeechToText.test.tsx (100%) rename src/{ => ui}/useSpeechToText.ts (100%) diff --git a/eval/reporting.ts b/eval/reporting.ts index 6989e37..dc9190c 100644 --- a/eval/reporting.ts +++ b/eval/reporting.ts @@ -1,4 +1,4 @@ -import type { AssistantResponse } from "../src/assistant-response"; +import type { AssistantResponse } from "../src/coach/assistant-response"; export interface CsvRow { [column: string]: string; diff --git a/eval/run.ts b/eval/run.ts index 3e40251..f1d99a2 100644 --- a/eval/run.ts +++ b/eval/run.ts @@ -2,13 +2,13 @@ import { mkdir, writeFile } from "node:fs/promises"; import { execFileSync } from "node:child_process"; import { resolve } from "node:path"; import { contractForLevel, type AssistanceLevel } from "../src/assistance-contract"; -import { historyForCurrentTurn, makeLLM, type ConversationMessage, type ProviderTrace } from "../src/api"; -import type { AssistantResponse, TurnProgressStage } from "../src/assistant-response"; +import { historyForCurrentTurn, makeLLM, type ConversationMessage, type ProviderTrace } from "../src/coach/api"; +import type { AssistantResponse, TurnProgressStage } from "../src/coach/assistant-response"; import { defaultConfig } from "../src/config"; -import { ThoughtUnitStore } from "../src/map-store"; -import { containsWholePhrase, contentTokens, stem, stemSet } from "../src/normalize"; -import type { ProviderTransport } from "../src/provider-tools"; -import { createConversationState, processTurn } from "../src/stage1-loop"; +import { ThoughtUnitStore } from "../src/map/map-store"; +import { containsWholePhrase, contentTokens, stem, stemSet } from "../src/grounding/normalize"; +import type { ProviderTransport } from "../src/coach/provider-tools"; +import { createConversationState, processTurn } from "../src/coach/stage1-loop"; import { EVAL_SCENARIOS, MANIPULATION_CHECK_SCENARIOS, type EvalMemoryEvent, type EvalScenario } from "./scenarios"; import { encodeCsv, visibleResponseText } from "./reporting"; diff --git a/eval/scenarios/index.test.ts b/eval/scenarios/index.test.ts index fa4df4b..bee5992 100644 --- a/eval/scenarios/index.test.ts +++ b/eval/scenarios/index.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { containsWholePhrase } from "../../src/normalize"; +import { containsWholePhrase } from "../../src/grounding/normalize"; import { EVAL_SCENARIOS, MANIPULATION_CHECK_SCENARIOS } from "./index"; describe("combined evaluation scenario set", () => { diff --git a/eval/scenarios/recall.test.ts b/eval/scenarios/recall.test.ts index af88932..1e56046 100644 --- a/eval/scenarios/recall.test.ts +++ b/eval/scenarios/recall.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { containsWholePhrase } from "../../src/normalize"; +import { containsWholePhrase } from "../../src/grounding/normalize"; import { RECALL_EVAL_SCENARIOS } from "./recall"; describe("recall eval scenario fixtures", () => { diff --git a/eval/scenarios/types.ts b/eval/scenarios/types.ts index df7911b..f9bed74 100644 --- a/eval/scenarios/types.ts +++ b/eval/scenarios/types.ts @@ -1,5 +1,5 @@ import type { AssistanceLevel } from "../../src/assistance-contract"; -import type { ConversationMessage } from "../../src/api"; +import type { ConversationMessage } from "../../src/coach/api"; import type { CandidateTarget } from "../../src/types"; export interface EvalMemoryEvent { diff --git a/src/assistance-contract.test.ts b/src/assistance-contract.test.ts index 1728979..0d881ae 100644 --- a/src/assistance-contract.test.ts +++ b/src/assistance-contract.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { ASSISTANCE_CONTRACTS, normalizeInfluenceTrace, snapshotContract } from "./assistance-contract"; -import { parseAssistantResponse, renderContext } from "./api"; +import { parseAssistantResponse, renderContext } from "./coach/api"; import { defaultConfig } from "./config"; describe("assistance contracts", () => { diff --git a/src/api.test.ts b/src/coach/api.test.ts similarity index 99% rename from src/api.test.ts rename to src/coach/api.test.ts index aead4c1..1116d98 100644 --- a/src/api.test.ts +++ b/src/coach/api.test.ts @@ -1,10 +1,10 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { historyForCurrentTurn, makeLLM, parseAssistantResponse, PROVIDER_TRANSPORT, renderContext, translateReaderText } from "./api"; -import { defaultConfig } from "./config"; +import { defaultConfig } from "../config"; import type { LLMContext } from "./llm-contract"; -import { ThoughtUnitStore } from "./map-store"; +import { ThoughtUnitStore } from "../map/map-store"; import { createConversationState, processTurn } from "./stage1-loop"; -import { contractForLevel, snapshotContract } from "./assistance-contract"; +import { contractForLevel, snapshotContract } from "../assistance-contract"; const context: LLMContext = { language: { uiLocale: "en", latestUserLanguagePattern: "single" }, diff --git a/src/api.ts b/src/coach/api.ts similarity index 99% rename from src/api.ts rename to src/coach/api.ts index fb40689..26d0f57 100644 --- a/src/api.ts +++ b/src/coach/api.ts @@ -1,7 +1,7 @@ /// -import type { MindmapConfig } from "./config"; -import { defaultConfig } from "./config"; +import type { MindmapConfig } from "../config"; +import { defaultConfig } from "../config"; import type { LLMContext } from "./llm-contract"; import { ModelResponseValidationError, @@ -9,9 +9,9 @@ import { type AssistantResponseEnvelope, type StructuredRejection, } from "./assistant-response"; -import type { ProposedAction, ProposedRef } from "./action-gateway"; -import type { GroundedClaim, GroundedRecap, MirrorClaim, MirrorReflection, SourceSpan, TranslationEvidencePhrase } from "./types"; -import type { SourceBackedOption } from "./assistance-contract"; +import type { ProposedAction, ProposedRef } from "../grounding/action-gateway"; +import type { GroundedClaim, GroundedRecap, MirrorClaim, MirrorReflection, SourceSpan, TranslationEvidencePhrase } from "../types"; +import type { SourceBackedOption } from "../assistance-contract"; import { CONVERSATIONAL_TEXT_FORMAT, MINDMAP_PROVIDER_TOOLS, diff --git a/src/assistant-response.ts b/src/coach/assistant-response.ts similarity index 93% rename from src/assistant-response.ts rename to src/coach/assistant-response.ts index a2f3e3d..f2e5fb1 100644 --- a/src/assistant-response.ts +++ b/src/coach/assistant-response.ts @@ -1,9 +1,9 @@ import type { LLMContext, QuestionStance } from "./llm-contract"; -import type { CandidateStore, SourceBank } from "./store"; -import type { ProposedAction } from "./action-gateway"; -import type { GroundedRecap, MirrorReflection, TranslationEvidencePhrase } from "./types"; -import type { Proposal } from "./proposal-store"; -import type { SourceBackedOption } from "./assistance-contract"; +import type { CandidateStore, SourceBank } from "../map/store"; +import type { ProposedAction } from "../grounding/action-gateway"; +import type { GroundedRecap, MirrorReflection, TranslationEvidencePhrase } from "../types"; +import type { Proposal } from "../map/proposal-store"; +import type { SourceBackedOption } from "../assistance-contract"; import type { LatestUserLanguagePattern } from "./language-context"; export interface RecallAnnotation { diff --git a/src/controller.ts b/src/coach/controller.ts similarity index 100% rename from src/controller.ts rename to src/coach/controller.ts diff --git a/src/language-context.test.ts b/src/coach/language-context.test.ts similarity index 100% rename from src/language-context.test.ts rename to src/coach/language-context.test.ts diff --git a/src/language-context.ts b/src/coach/language-context.ts similarity index 97% rename from src/language-context.ts rename to src/coach/language-context.ts index ca1fcc9..7825c4a 100644 --- a/src/language-context.ts +++ b/src/coach/language-context.ts @@ -1,4 +1,4 @@ -import { CJK_SCRIPT_RE } from "./unicode-scripts"; +import { CJK_SCRIPT_RE } from "../grounding/unicode-scripts"; /** * Conversational language hints are deliberately separate from evidence and diff --git a/src/llm-contract.ts b/src/coach/llm-contract.ts similarity index 95% rename from src/llm-contract.ts rename to src/coach/llm-contract.ts index 889edea..bb8452d 100644 --- a/src/llm-contract.ts +++ b/src/coach/llm-contract.ts @@ -1,6 +1,6 @@ import type { TurnShape } from "./turn-shape"; -import type { CandidateStatus, CandidateTarget, SourceUtterance, ThoughtUnit, ThoughtUnitRole } from "./types"; -import type { AssistanceContractSnapshot } from "./assistance-contract"; +import type { CandidateStatus, CandidateTarget, SourceUtterance, ThoughtUnit, ThoughtUnitRole } from "../types"; +import type { AssistanceContractSnapshot } from "../assistance-contract"; import type { LanguageContext } from "./language-context"; export type { LanguageContext } from "./language-context"; diff --git a/src/provider-tools.integration.test.ts b/src/coach/provider-tools.integration.test.ts similarity index 95% rename from src/provider-tools.integration.test.ts rename to src/coach/provider-tools.integration.test.ts index eab4828..5319ef4 100644 --- a/src/provider-tools.integration.test.ts +++ b/src/coach/provider-tools.integration.test.ts @@ -1,10 +1,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { makeLLM } from "./api"; -import { ASSISTANCE_CONTRACTS } from "./assistance-contract"; -import { defaultConfig } from "./config"; -import { ThoughtUnitStore } from "./map-store"; +import { ASSISTANCE_CONTRACTS } from "../assistance-contract"; +import { defaultConfig } from "../config"; +import { ThoughtUnitStore } from "../map/map-store"; import { createConversationState, processTurn } from "./stage1-loop"; -import { resetIdCounter } from "./store"; +import { resetIdCounter } from "../map/store"; afterEach(() => vi.unstubAllGlobals()); beforeEach(() => resetIdCounter()); diff --git a/src/provider-tools.test.ts b/src/coach/provider-tools.test.ts similarity index 100% rename from src/provider-tools.test.ts rename to src/coach/provider-tools.test.ts diff --git a/src/provider-tools.ts b/src/coach/provider-tools.ts similarity index 100% rename from src/provider-tools.ts rename to src/coach/provider-tools.ts diff --git a/src/stage1-loop.test.ts b/src/coach/stage1-loop.test.ts similarity index 99% rename from src/stage1-loop.test.ts rename to src/coach/stage1-loop.test.ts index 75eae4e..c9a3cbd 100644 --- a/src/stage1-loop.test.ts +++ b/src/coach/stage1-loop.test.ts @@ -1,11 +1,11 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -import { defaultConfig, withQuestionIntentBias } from "./config"; -import { ThoughtUnitStore } from "./map-store"; +import { defaultConfig, withQuestionIntentBias } from "../config"; +import { ThoughtUnitStore } from "../map/map-store"; import { buildContext, createConversationState, deriveClaimAttribution, MAX_MODEL_CALLS_PER_TURN, MAX_REFLECTION_ATTEMPTS, processTurn } from "./stage1-loop"; -import { cardRef, resetIdCounter } from "./store"; -import { ASSISTANCE_CONTRACTS } from "./assistance-contract"; +import { cardRef, resetIdCounter } from "../map/store"; +import { ASSISTANCE_CONTRACTS } from "../assistance-contract"; import { ModelResponseValidationError } from "./assistant-response"; -import { POLARITY_REPAIR_NOTE } from "./validator"; +import { POLARITY_REPAIR_NOTE } from "../grounding/validator"; beforeEach(() => resetIdCounter()); diff --git a/src/stage1-loop.ts b/src/coach/stage1-loop.ts similarity index 98% rename from src/stage1-loop.ts rename to src/coach/stage1-loop.ts index b8ddc96..8ae7e16 100644 --- a/src/stage1-loop.ts +++ b/src/coach/stage1-loop.ts @@ -1,7 +1,7 @@ -import type { MindmapConfig } from "./config"; -import { containsWholePhrase } from "./normalize"; +import type { MindmapConfig } from "../config"; +import { containsWholePhrase } from "../grounding/normalize"; import type { LLMContext, LLMMapContext, ProposalOutcomeContext, SelectedFocusContext, UserRequestedMode } from "./llm-contract"; -import { inspectAction } from "./action-gateway"; +import { inspectAction } from "../grounding/action-gateway"; import type { AssistantModel, AssistantResponseEnvelope, @@ -14,15 +14,15 @@ import type { VerifiedRecall, } from "./assistant-response"; import { ModelResponseValidationError } from "./assistant-response"; -import type { ThoughtUnitStore } from "./map-store"; -import type { Proposal, ProposalAttribution } from "./proposal-store"; -import type { AssistanceContract, AssistanceContractSnapshot, InfluenceTrace } from "./assistance-contract"; -import { DEFAULT_ASSISTANCE_CONTRACT, snapshotContract } from "./assistance-contract"; -import { CandidateStore, SourceBank } from "./store"; +import type { ThoughtUnitStore } from "../map/map-store"; +import type { Proposal, ProposalAttribution } from "../map/proposal-store"; +import type { AssistanceContract, AssistanceContractSnapshot, InfluenceTrace } from "../assistance-contract"; +import { DEFAULT_ASSISTANCE_CONTRACT, snapshotContract } from "../assistance-contract"; +import { CandidateStore, SourceBank } from "../map/store"; import { detectTurnShape } from "./turn-shape"; import { detectLatestUserLanguagePattern, type LanguageContext } from "./language-context"; -import type { CandidateThought, CandidateTarget, GroundedClaim, SourceUtterance } from "./types"; -import { POLARITY_REPAIR_NOTE, validateGroundedClaims, validateMirror } from "./validator"; +import type { CandidateThought, CandidateTarget, GroundedClaim, SourceUtterance } from "../types"; +import { POLARITY_REPAIR_NOTE, validateGroundedClaims, validateMirror } from "../grounding/validator"; export interface ProcessTurnOptions { mapRevision: number; @@ -194,7 +194,7 @@ function validateRecall(envelope: AssistantResponseEnvelope, state: Conversation return { recall: { ...annotation, ageInTurns: state.candidates.ageInTurns(candidate.id, state.currentUserTurn) ?? 0 } }; } -function targetForAction(kind: import("./action-gateway").ProposedAction["kind"]): CandidateTarget { +function targetForAction(kind: import("../grounding/action-gateway").ProposedAction["kind"]): CandidateTarget { if (kind === "nest_card") return "hierarchy"; if (kind === "connect_cards") return "connection"; return "idea"; diff --git a/src/turn-shape.test.ts b/src/coach/turn-shape.test.ts similarity index 98% rename from src/turn-shape.test.ts rename to src/coach/turn-shape.test.ts index 2a2d1b0..7aa76f5 100644 --- a/src/turn-shape.test.ts +++ b/src/coach/turn-shape.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { detectTurnShape } from "./turn-shape"; -import type { SourceUtterance } from "./types"; +import type { SourceUtterance } from "../types"; function u(text: string, index: number): SourceUtterance { return { diff --git a/src/turn-shape.ts b/src/coach/turn-shape.ts similarity index 84% rename from src/turn-shape.ts rename to src/coach/turn-shape.ts index 66e05d3..cf068ce 100644 --- a/src/turn-shape.ts +++ b/src/coach/turn-shape.ts @@ -1,6 +1,6 @@ -import { contentTokens } from "./normalize"; -import { defaultConfig, type TurnShapeConfig } from "./config"; -import type { SourceUtterance } from "./types"; +import { contentTokens } from "../grounding/normalize"; +import { defaultConfig, type TurnShapeConfig } from "../config"; +import type { SourceUtterance } from "../types"; export type TurnShapeKind = "compact" | "large"; diff --git a/src/action-gateway.test.ts b/src/grounding/action-gateway.test.ts similarity index 99% rename from src/action-gateway.test.ts rename to src/grounding/action-gateway.test.ts index 357f4e8..987e6c3 100644 --- a/src/action-gateway.test.ts +++ b/src/grounding/action-gateway.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it } from "vitest"; import { applyConfirmedReflection, applyGatewayActions, executeCanvasAction, inspectAction } from "./action-gateway"; -import { ThoughtUnitStore } from "./map-store"; -import { cardRef, resetIdCounter, SourceBank } from "./store"; +import { ThoughtUnitStore } from "../map/map-store"; +import { cardRef, resetIdCounter, SourceBank } from "../map/store"; beforeEach(() => resetIdCounter()); diff --git a/src/action-gateway.ts b/src/grounding/action-gateway.ts similarity index 97% rename from src/action-gateway.ts rename to src/grounding/action-gateway.ts index 3c06774..aca11ad 100644 --- a/src/action-gateway.ts +++ b/src/grounding/action-gateway.ts @@ -1,9 +1,9 @@ import { containsWholePhrase, normalize } from "./normalize"; -import type { ConnectionLayoutDirection, ThoughtUnitStore, ThoughtUnitStoreSnapshot, XYPosition, XYSize } from "./map-store"; -import { cardRef, type SourceBank } from "./store"; -import type { ConfirmedReflection, ThoughtUnit, ThoughtUnitRole } from "./types"; -import type { ContributionOrigin } from "./assistance-contract"; -import type { InteractionMode } from "./mutation-policy"; +import type { ConnectionLayoutDirection, ThoughtUnitStore, ThoughtUnitStoreSnapshot, XYPosition, XYSize } from "../map/map-store"; +import { cardRef, type SourceBank } from "../map/store"; +import type { ConfirmedReflection, ThoughtUnit, ThoughtUnitRole } from "../types"; +import type { ContributionOrigin } from "../assistance-contract"; +import type { InteractionMode } from "../ui/mutation-policy"; export type GatewayActor = "ai_proposal" | "user_canvas"; export type ProposedActionKind = "create_card" | "edit_card" | "nest_card" | "connect_cards"; @@ -316,7 +316,7 @@ function ensureExecutableCard( ref: ExecutableRef, store: ThoughtUnitStore, bank: SourceBank, - provenance?: { origin: ContributionOrigin; contract?: import("./assistance-contract").AssistanceContractSnapshot; relationshipProvenance?: { pairingOrigin: ContributionOrigin; labelOrigin: ContributionOrigin } }, + provenance?: { origin: ContributionOrigin; contract?: import("../assistance-contract").AssistanceContractSnapshot; relationshipProvenance?: { pairingOrigin: ContributionOrigin; labelOrigin: ContributionOrigin } }, ): ThoughtUnit | undefined { if ("id" in ref) return store.get(ref.id); const existing = store.getAll().find((unit) => unit.role !== "connection_label" && normalize(unit.text) === normalize(ref.text)); @@ -333,7 +333,7 @@ export function applyGatewayActions( actions: ExecutableAction[], store: ThoughtUnitStore, bank: SourceBank, - provenance?: { origin: ContributionOrigin; contract?: import("./assistance-contract").AssistanceContractSnapshot; relationshipProvenance?: { pairingOrigin: ContributionOrigin; labelOrigin: ContributionOrigin } }, + provenance?: { origin: ContributionOrigin; contract?: import("../assistance-contract").AssistanceContractSnapshot; relationshipProvenance?: { pairingOrigin: ContributionOrigin; labelOrigin: ContributionOrigin } }, ): ThoughtUnit[] { const changed: ThoughtUnit[] = []; for (const action of actions) { diff --git a/src/map-commands.test.ts b/src/grounding/map-commands.test.ts similarity index 98% rename from src/map-commands.test.ts rename to src/grounding/map-commands.test.ts index 344a17e..a9841f9 100644 --- a/src/map-commands.test.ts +++ b/src/grounding/map-commands.test.ts @@ -1,8 +1,8 @@ import { beforeEach, describe, expect, it } from "vitest"; import { applyAcceptedMapCommands } from "./map-commands"; -import { ThoughtUnitStore } from "./map-store"; -import { resetIdCounter, SourceBank } from "./store"; -import type { ThoughtUnit } from "./types"; +import { ThoughtUnitStore } from "../map/map-store"; +import { resetIdCounter, SourceBank } from "../map/store"; +import type { ThoughtUnit } from "../types"; beforeEach(() => { resetIdCounter(); diff --git a/src/map-commands.ts b/src/grounding/map-commands.ts similarity index 100% rename from src/map-commands.ts rename to src/grounding/map-commands.ts diff --git a/src/normalize.test.ts b/src/grounding/normalize.test.ts similarity index 100% rename from src/normalize.test.ts rename to src/grounding/normalize.test.ts diff --git a/src/normalize.ts b/src/grounding/normalize.ts similarity index 100% rename from src/normalize.ts rename to src/grounding/normalize.ts diff --git a/src/unicode-scripts.ts b/src/grounding/unicode-scripts.ts similarity index 100% rename from src/unicode-scripts.ts rename to src/grounding/unicode-scripts.ts diff --git a/src/validator.test.ts b/src/grounding/validator.test.ts similarity index 99% rename from src/validator.test.ts rename to src/grounding/validator.test.ts index 025725e..d71cdc7 100644 --- a/src/validator.test.ts +++ b/src/grounding/validator.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { defaultConfig } from "./config"; -import type { MirrorClaim, SourceUtterance } from "./types"; +import { defaultConfig } from "../config"; +import type { MirrorClaim, SourceUtterance } from "../types"; import { validateMirror } from "./validator"; let n = 0; diff --git a/src/validator.ts b/src/grounding/validator.ts similarity index 99% rename from src/validator.ts rename to src/grounding/validator.ts index 9023a1d..28677ce 100644 --- a/src/validator.ts +++ b/src/grounding/validator.ts @@ -22,7 +22,7 @@ * knows exactly which span to ask about when one fails. */ -import type { MindmapConfig } from "./config"; +import type { MindmapConfig } from "../config"; import { containsWholePhrase, contentTokens, @@ -42,7 +42,7 @@ import type { MirrorValidationResult, SourceSpan, SourceUtterance, -} from "./types"; +} from "../types"; function ratio(part: number, whole: number): number { return whole === 0 ? 0 : part / whole; diff --git a/src/main.tsx b/src/main.tsx index 700ba4a..8f1b484 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,7 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import PlatformBootstrap from "./PlatformBootstrap"; -import "./platform.css"; +import PlatformBootstrap from "./platform/PlatformBootstrap"; +import "./platform/platform.css"; createRoot(document.getElementById("root")!).render( diff --git a/src/candidate-store.test.ts b/src/map/candidate-store.test.ts similarity index 97% rename from src/candidate-store.test.ts rename to src/map/candidate-store.test.ts index 87510a9..d981442 100644 --- a/src/candidate-store.test.ts +++ b/src/map/candidate-store.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { CandidateStore } from "./store"; -import type { CandidateThought } from "./types"; +import type { CandidateThought } from "../types"; function candidate(overrides: Partial = {}): CandidateThought { return { diff --git a/src/map-export.test.ts b/src/map/map-export.test.ts similarity index 98% rename from src/map-export.test.ts rename to src/map/map-export.test.ts index eb7a4d4..b53d1e4 100644 --- a/src/map-export.test.ts +++ b/src/map/map-export.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { formatMapAsMarkdown } from "./map-export"; import type { ThoughtUnitStoreSnapshot } from "./map-store"; -import type { ThoughtUnit } from "./types"; +import type { ThoughtUnit } from "../types"; function unit(id: string, text: string, parentId?: string, role: ThoughtUnit["role"] = "node"): ThoughtUnit { return { diff --git a/src/map-export.ts b/src/map/map-export.ts similarity index 98% rename from src/map-export.ts rename to src/map/map-export.ts index 53af482..cac5008 100644 --- a/src/map-export.ts +++ b/src/map/map-export.ts @@ -1,5 +1,5 @@ import type { ThoughtConnection, ThoughtUnitStoreSnapshot } from "./map-store"; -import type { ThoughtUnit } from "./types"; +import type { ThoughtUnit } from "../types"; const EMPTY_CARD = "(empty card)"; diff --git a/src/map-layout.test.ts b/src/map/map-layout.test.ts similarity index 99% rename from src/map-layout.test.ts rename to src/map/map-layout.test.ts index 328000b..f0d6edc 100644 --- a/src/map-layout.test.ts +++ b/src/map/map-layout.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { computeAutoCleanPositions, computeConnectionHandles } from "./map-layout"; import type { ThoughtConnection, XYPosition, XYSize } from "./map-store"; -import type { ThoughtUnit } from "./types"; +import type { ThoughtUnit } from "../types"; const defaultSize: XYSize = { w: 200, h: 100 }; diff --git a/src/map-layout.ts b/src/map/map-layout.ts similarity index 99% rename from src/map-layout.ts rename to src/map/map-layout.ts index ee65ec1..33e325b 100644 --- a/src/map-layout.ts +++ b/src/map/map-layout.ts @@ -1,5 +1,5 @@ import type { ThoughtConnection, XYPosition, XYSize } from "./map-store"; -import type { ThoughtUnit } from "./types"; +import type { ThoughtUnit } from "../types"; export interface AutoCleanLayoutInput { units: ThoughtUnit[]; diff --git a/src/map-store.test.ts b/src/map/map-store.test.ts similarity index 99% rename from src/map-store.test.ts rename to src/map/map-store.test.ts index 2f70b82..c902cdf 100644 --- a/src/map-store.test.ts +++ b/src/map/map-store.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it } from "vitest"; import { ThoughtUnitStore } from "./map-store"; import { resetIdCounter, SourceBank } from "./store"; -import type { ConfirmedReflection, SourceUtterance, ThoughtUnit, ThoughtUnitRole } from "./types"; +import type { ConfirmedReflection, SourceUtterance, ThoughtUnit, ThoughtUnitRole } from "../types"; beforeEach(() => { resetIdCounter(); diff --git a/src/map-store.ts b/src/map/map-store.ts similarity index 99% rename from src/map-store.ts rename to src/map/map-store.ts index 9cbe050..4d04166 100644 --- a/src/map-store.ts +++ b/src/map/map-store.ts @@ -1,4 +1,4 @@ -import type { LLMMapContext, LLMMapConnection } from "./llm-contract"; +import type { LLMMapContext, LLMMapConnection } from "../coach/llm-contract"; import { nextId, primeIdCounters } from "./store"; import type { SourceBank } from "./store"; import type { @@ -6,8 +6,8 @@ import type { SourceUtterance, ThoughtUnit, ThoughtUnitRole, -} from "./types"; -import type { AssistanceContractSnapshot, ContributionOrigin } from "./assistance-contract"; +} from "../types"; +import type { AssistanceContractSnapshot, ContributionOrigin } from "../assistance-contract"; export interface XYPosition { x: number; diff --git a/src/proposal-fuzz.test.ts b/src/map/proposal-fuzz.test.ts similarity index 98% rename from src/proposal-fuzz.test.ts rename to src/map/proposal-fuzz.test.ts index b608fe4..3af89e0 100644 --- a/src/proposal-fuzz.test.ts +++ b/src/map/proposal-fuzz.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { applyGatewayActions, executeCanvasAction, inspectAction } from "./action-gateway"; +import { applyGatewayActions, executeCanvasAction, inspectAction } from "../grounding/action-gateway"; import { ThoughtUnitStore } from "./map-store"; import { activeProposals, createProposalStore, resolveProposal, updateProposal, type Proposal } from "./proposal-store"; import { SourceBank } from "./store"; diff --git a/src/proposal-store.test.ts b/src/map/proposal-store.test.ts similarity index 100% rename from src/proposal-store.test.ts rename to src/map/proposal-store.test.ts diff --git a/src/proposal-store.ts b/src/map/proposal-store.ts similarity index 95% rename from src/proposal-store.ts rename to src/map/proposal-store.ts index 1562982..20b24bc 100644 --- a/src/proposal-store.ts +++ b/src/map/proposal-store.ts @@ -1,6 +1,6 @@ -import type { ExecutableAction, ProposedAction, VerifiedPairingProof } from "./action-gateway"; -import type { ClaimValidation, MirrorReflection } from "./types"; -import type { AssistanceContractSnapshot, ContributionOrigin, InfluenceTrace } from "./assistance-contract"; +import type { ExecutableAction, ProposedAction, VerifiedPairingProof } from "../grounding/action-gateway"; +import type { ClaimValidation, MirrorReflection } from "../types"; +import type { AssistanceContractSnapshot, ContributionOrigin, InfluenceTrace } from "../assistance-contract"; export type ProposalState = | "shown" diff --git a/src/provenance-summary.test.ts b/src/map/provenance-summary.test.ts similarity index 96% rename from src/provenance-summary.test.ts rename to src/map/provenance-summary.test.ts index dd26bf5..9c5ad39 100644 --- a/src/provenance-summary.test.ts +++ b/src/map/provenance-summary.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { provenanceTotals } from "./provenance-summary"; -import type { ThoughtUnit } from "./types"; +import type { ThoughtUnit } from "../types"; import type { ThoughtConnection } from "./map-store"; const unit = (id: string, origin: ThoughtUnit["source"]["origin"], parentId?: string, parentOrigin?: ThoughtUnit["parentProvenance"]): ThoughtUnit => ({ diff --git a/src/provenance-summary.ts b/src/map/provenance-summary.ts similarity index 96% rename from src/provenance-summary.ts rename to src/map/provenance-summary.ts index 767a65e..de3f176 100644 --- a/src/provenance-summary.ts +++ b/src/map/provenance-summary.ts @@ -1,5 +1,5 @@ import type { ThoughtConnection } from "./map-store"; -import type { ThoughtUnit } from "./types"; +import type { ThoughtUnit } from "../types"; export interface ProvenanceTotals { userAuthored: number; diff --git a/src/store.ts b/src/map/store.ts similarity index 99% rename from src/store.ts rename to src/map/store.ts index 3a89c2e..5a1955c 100644 --- a/src/store.ts +++ b/src/map/store.ts @@ -5,13 +5,13 @@ * CandidateStore: LLM-maintained working hypotheses, never shown raw to the user. */ -import { segment } from "./normalize"; +import { segment } from "../grounding/normalize"; import type { CandidateThought, CandidateStatus, SourceUtterance, UtteranceOrigin, -} from "./types"; +} from "../types"; let _nextId = 0; let _nextTurn = 0; diff --git a/src/suggestion-adoption.test.ts b/src/map/suggestion-adoption.test.ts similarity index 99% rename from src/suggestion-adoption.test.ts rename to src/map/suggestion-adoption.test.ts index cb2cdcc..224489d 100644 --- a/src/suggestion-adoption.test.ts +++ b/src/map/suggestion-adoption.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { bestSuggestionMatch, reconcileStoreSuggestionAdoption, reconcileSuggestionAdoption } from "./suggestion-adoption"; -import type { ThoughtUnit } from "./types"; +import type { ThoughtUnit } from "../types"; import { ThoughtUnitStore } from "./map-store"; function unit(text: string, origin: ThoughtUnit["source"]["origin"] = "user_canvas"): ThoughtUnit { diff --git a/src/suggestion-adoption.ts b/src/map/suggestion-adoption.ts similarity index 96% rename from src/suggestion-adoption.ts rename to src/map/suggestion-adoption.ts index 357e7a8..6734a4f 100644 --- a/src/suggestion-adoption.ts +++ b/src/map/suggestion-adoption.ts @@ -1,5 +1,5 @@ -import { contentTokens, stem } from "./normalize"; -import type { SuggestionAdoptionTrace, ThoughtUnit } from "./types"; +import { contentTokens, stem } from "../grounding/normalize"; +import type { SuggestionAdoptionTrace, ThoughtUnit } from "../types"; import type { ThoughtUnitStore } from "./map-store"; export const SUGGESTION_ADOPTION_THRESHOLD = 0.5; diff --git a/src/PlatformBootstrap.test.ts b/src/platform/PlatformBootstrap.test.ts similarity index 98% rename from src/PlatformBootstrap.test.ts rename to src/platform/PlatformBootstrap.test.ts index 5fa885c..8014a79 100644 --- a/src/PlatformBootstrap.test.ts +++ b/src/platform/PlatformBootstrap.test.ts @@ -3,7 +3,7 @@ import { act, createElement, type ReactNode } from "react"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { AppErrorBoundary, browserStorage, initialBootState } from "./PlatformBootstrap"; -import { SESSION_STORAGE_KEY } from "./session-persistence"; +import { SESSION_STORAGE_KEY } from "../session/session-persistence"; import { OAUTH_SESSION_STORAGE_KEY, } from "./platform-session"; diff --git a/src/PlatformBootstrap.tsx b/src/platform/PlatformBootstrap.tsx similarity index 96% rename from src/PlatformBootstrap.tsx rename to src/platform/PlatformBootstrap.tsx index eca401a..9bd53ee 100644 --- a/src/PlatformBootstrap.tsx +++ b/src/platform/PlatformBootstrap.tsx @@ -1,8 +1,8 @@ import { Component, useCallback, useEffect, useMemo, useRef, useState, type ErrorInfo, type ReactNode } from "react"; -import App from "./App"; -import type { ProviderRuntimeConfig } from "./api"; -import { MutationPolicyProvider } from "./mutation-policy"; -import { SESSION_STORAGE_KEY } from "./session-persistence"; +import App from "../ui/App"; +import type { ProviderRuntimeConfig } from "../coach/api"; +import { MutationPolicyProvider } from "../ui/mutation-policy"; +import { SESSION_STORAGE_KEY } from "../session/session-persistence"; import { OAUTH_RESOURCE, OAUTH_SESSION_STORAGE_KEY, @@ -19,8 +19,8 @@ import { type OAuthCallback, type OAuthSession, } from "./platform-session"; -import { ReaderViewProvider, useReaderView } from "./reader-view"; -import { UiLocaleProvider, useUiLocale } from "./ui-locale"; +import { ReaderViewProvider, useReaderView } from "../ui/reader-view"; +import { UiLocaleProvider, useUiLocale } from "../ui/ui-locale"; type BootState = | { kind: "connecting" } diff --git a/src/platform-session.test.ts b/src/platform/platform-session.test.ts similarity index 100% rename from src/platform-session.test.ts rename to src/platform/platform-session.test.ts diff --git a/src/platform-session.ts b/src/platform/platform-session.ts similarity index 100% rename from src/platform-session.ts rename to src/platform/platform-session.ts diff --git a/src/platform.css b/src/platform/platform.css similarity index 100% rename from src/platform.css rename to src/platform/platform.css diff --git a/src/event-ledger.test.ts b/src/session/event-ledger.test.ts similarity index 100% rename from src/event-ledger.test.ts rename to src/session/event-ledger.test.ts diff --git a/src/event-ledger.ts b/src/session/event-ledger.ts similarity index 98% rename from src/event-ledger.ts rename to src/session/event-ledger.ts index bd85bbc..e56a177 100644 --- a/src/event-ledger.ts +++ b/src/session/event-ledger.ts @@ -1,4 +1,4 @@ -import type { AssistanceContractSnapshot, ContributionOrigin, InfluenceTrace } from "./assistance-contract"; +import type { AssistanceContractSnapshot, ContributionOrigin, InfluenceTrace } from "../assistance-contract"; export type LedgerEventKind = | "contract_initialized" | "contract_selected" | "contract_changed" diff --git a/src/session-persistence.test.ts b/src/session/session-persistence.test.ts similarity index 100% rename from src/session-persistence.test.ts rename to src/session/session-persistence.test.ts diff --git a/src/session-persistence.ts b/src/session/session-persistence.ts similarity index 93% rename from src/session-persistence.ts rename to src/session/session-persistence.ts index 4fed423..7ce503b 100644 --- a/src/session-persistence.ts +++ b/src/session/session-persistence.ts @@ -10,13 +10,13 @@ * `migrateStoredProposals`, `migrateCandidateMemory` stay in `App.tsx`) — those operate * on the running `ThoughtUnitStore` / bank, not on storage. */ -import type { AssistanceLevel } from "./assistance-contract"; -import type { AssistantResponse, ConversationState, DiagnosticEvent, RepairFailureTerminal } from "./assistant-response"; -import type { QuestionStance } from "./llm-contract"; -import type { CoachDebugInfo } from "./Map"; -import type { ThoughtUnitStoreSnapshot } from "./map-store"; -import type { Proposal } from "./proposal-store"; -import type { ClaimValidation, ConfirmedReflection, MirrorReflection } from "./types"; +import type { AssistanceLevel } from "../assistance-contract"; +import type { AssistantResponse, ConversationState, DiagnosticEvent, RepairFailureTerminal } from "../coach/assistant-response"; +import type { QuestionStance } from "../coach/llm-contract"; +import type { CoachDebugInfo } from "../ui/Map"; +import type { ThoughtUnitStoreSnapshot } from "../map/map-store"; +import type { Proposal } from "../map/proposal-store"; +import type { ClaimValidation, ConfirmedReflection, MirrorReflection } from "../types"; import type { UnderstandingSnapshot } from "./understanding"; /** Unchanged since v1 despite the schema reaching version 7 — renaming it would strand diff --git a/src/trace.ts b/src/session/trace.ts similarity index 91% rename from src/trace.ts rename to src/session/trace.ts index 35a8b1d..1caea3f 100644 --- a/src/trace.ts +++ b/src/session/trace.ts @@ -1,4 +1,4 @@ -import type { DiagnosticEvent } from "./assistant-response"; +import type { DiagnosticEvent } from "../coach/assistant-response"; export type TraceLevel = "quiet" | "notice" | "held"; export interface TraceEvent { diff --git a/src/understanding.test.ts b/src/session/understanding.test.ts similarity index 100% rename from src/understanding.test.ts rename to src/session/understanding.test.ts diff --git a/src/understanding.ts b/src/session/understanding.ts similarity index 97% rename from src/understanding.ts rename to src/session/understanding.ts index 44c6806..41e98f0 100644 --- a/src/understanding.ts +++ b/src/session/understanding.ts @@ -1,5 +1,5 @@ -import type { DiagnosticEvent } from "./assistant-response"; -import type { CandidateStatus, CandidateTarget, CandidateThought, SourceUtterance } from "./types"; +import type { DiagnosticEvent } from "../coach/assistant-response"; +import type { CandidateStatus, CandidateTarget, CandidateThought, SourceUtterance } from "../types"; import { diagnosticTrace, type TraceEvent } from "./trace"; export const THESIS_BANNER = "Nothing here changes your map. Typed responses, proposals, validation, and applied actions are logged separately."; diff --git a/src/App.test.ts b/src/ui/App.test.ts similarity index 98% rename from src/App.test.ts rename to src/ui/App.test.ts index 2249526..a2990be 100644 --- a/src/App.test.ts +++ b/src/ui/App.test.ts @@ -4,13 +4,13 @@ import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { AssistantResponseKindBadge, buildConversationHistory, copyTextToClipboard, deriveCurrentUserTurn, draftHtmlToPlainText, InfluenceBadge, MapActionProposalCard, migrateCandidateMemory, migrateLegacyMirrors, migrateStoredProposals, normalizeDraftPasteHtml, recoverFailedTurn, resolveMirrorDecision, restoreDraftHtml, restoreFailedMessageToComposer, TURN_PROGRESS_COPY } from "./App"; -import { hasPersistableWork } from "./session-persistence"; +import { hasPersistableWork } from "../session/session-persistence"; import { UnderTheHoodPanel } from "./ControlRoom"; -import { ASSISTANCE_CONTRACTS, snapshotContract } from "./assistance-contract"; -import { ThoughtUnitStore } from "./map-store"; -import type { Proposal } from "./proposal-store"; -import { createConversationState } from "./stage1-loop"; -import type { UnderstandingSnapshot } from "./understanding"; +import { ASSISTANCE_CONTRACTS, snapshotContract } from "../assistance-contract"; +import { ThoughtUnitStore } from "../map/map-store"; +import type { Proposal } from "../map/proposal-store"; +import { createConversationState } from "../coach/stage1-loop"; +import type { UnderstandingSnapshot } from "../session/understanding"; import { MutationPolicyProvider } from "./mutation-policy"; import { UiLocaleProvider } from "./ui-locale"; diff --git a/src/App.tsx b/src/ui/App.tsx similarity index 99% rename from src/App.tsx rename to src/ui/App.tsx index 8ccd8ca..32d2e81 100644 --- a/src/App.tsx +++ b/src/ui/App.tsx @@ -1,25 +1,25 @@ import { useCallback, useEffect, useMemo, useRef, useState, type ClipboardEvent, type FormEvent } from "react"; -import { historyForCurrentTurn, makeLLM, type ConversationMessage, type ProviderRuntimeConfig } from "./api"; -import { defaultConfig, withQuestionIntentBias, type MindmapConfig } from "./config"; -import type { AssistantResponse, ConversationState, DiagnosticEvent, TurnProgressStage, TurnResult } from "./assistant-response"; -import type { ProposalOutcomeContext, SelectedFocusContext, UserRequestedMode } from "./llm-contract"; +import { historyForCurrentTurn, makeLLM, type ConversationMessage, type ProviderRuntimeConfig } from "../coach/api"; +import { defaultConfig, withQuestionIntentBias, type MindmapConfig } from "../config"; +import type { AssistantResponse, ConversationState, DiagnosticEvent, TurnProgressStage, TurnResult } from "../coach/assistant-response"; +import type { ProposalOutcomeContext, SelectedFocusContext, UserRequestedMode } from "../coach/llm-contract"; import { pruneContextSelection, ThoughtMap, toggleContextSelection, type CoachDebugInfo, type MapCommandAcknowledgement } from "./Map"; -import { ThoughtUnitStore, type ThoughtUnitStoreSnapshot } from "./map-store"; -import { applyConfirmedReflection, applyGatewayActions, executeCanvasAction, inspectAction, type ProposedAction } from "./action-gateway"; -import { createProposalStore, resolveProposal, updateProposal, type Proposal } from "./proposal-store"; -import { cloneConversationState, createConversationState, mergeConversationBank, processTurn } from "./stage1-loop"; -import { cardRef } from "./store"; -import type { CandidateThought, ThoughtUnit, ThoughtUnitRole } from "./types"; -import type { ConfirmedReflection } from "./types"; -import { validateMirror } from "./validator"; -import { buildDiagnosticSnapshot, type UnderstandingSnapshot } from "./understanding"; +import { ThoughtUnitStore, type ThoughtUnitStoreSnapshot } from "../map/map-store"; +import { applyConfirmedReflection, applyGatewayActions, executeCanvasAction, inspectAction, type ProposedAction } from "../grounding/action-gateway"; +import { createProposalStore, resolveProposal, updateProposal, type Proposal } from "../map/proposal-store"; +import { cloneConversationState, createConversationState, mergeConversationBank, processTurn } from "../coach/stage1-loop"; +import { cardRef } from "../map/store"; +import type { CandidateThought, ThoughtUnit, ThoughtUnitRole } from "../types"; +import type { ConfirmedReflection } from "../types"; +import { validateMirror } from "../grounding/validator"; +import { buildDiagnosticSnapshot, type UnderstandingSnapshot } from "../session/understanding"; import { useSpeechToText } from "./useSpeechToText"; import { measureDraftAnchorRects, scrollDraftAnchorIntoView, type DraftAnchorRect } from "./draft-anchor"; -import { ASSISTANCE_CONTRACTS, contractForLevel, DEFAULT_ASSISTANCE_CONTRACT, normalizeInfluenceTrace, snapshotContract, type AssistanceLevel } from "./assistance-contract"; -import { EventLedger, type LedgerEventKind } from "./event-ledger"; -import { reconcileStoreSuggestionAdoption, type VisibleSuggestion } from "./suggestion-adoption"; -import { provenanceTotals } from "./provenance-summary"; -import { formatMapAsMarkdown } from "./map-export"; +import { ASSISTANCE_CONTRACTS, contractForLevel, DEFAULT_ASSISTANCE_CONTRACT, normalizeInfluenceTrace, snapshotContract, type AssistanceLevel } from "../assistance-contract"; +import { EventLedger, type LedgerEventKind } from "../session/event-ledger"; +import { reconcileStoreSuggestionAdoption, type VisibleSuggestion } from "../map/suggestion-adoption"; +import { provenanceTotals } from "../map/provenance-summary"; +import { formatMapAsMarkdown } from "../map/map-export"; import { useMutationAccess } from "./mutation-policy"; import { useUiLocale } from "./ui-locale"; import { useReaderView } from "./reader-view"; @@ -34,7 +34,7 @@ import { type DraftSelectionFocus, type PersistedPendingMirror, type PersistedSession, -} from "./session-persistence"; +} from "../session/session-persistence"; // --------------------------------------------------------------------------- // Types @@ -2823,7 +2823,7 @@ export default function App({ providerRuntime, aiAccessDenied = false }: AppProp const ledgerRef = useRef(new EventLedger(initialSessionId)); const contract = contractForLevel(assistanceLevel); - const recordEvent = useCallback(async (kind: LedgerEventKind, detail?: unknown, extra?: { origin?: import("./assistance-contract").ContributionOrigin; responseKind?: string; outcome?: string; code?: string; durationMs?: number; contract?: import("./assistance-contract").AssistanceContractSnapshot; providerTransport?: "chat_json" | "responses_tools"; toolName?: "propose_reflection_v1" | "propose_map_action_v1"; repairCount?: number; candidateStatus?: "active" | "parked" | "ignored" | "promoted"; ageInTurns?: number; currentPercentage?: number; peakPercentage?: number }) => { + const recordEvent = useCallback(async (kind: LedgerEventKind, detail?: unknown, extra?: { origin?: import("../assistance-contract").ContributionOrigin; responseKind?: string; outcome?: string; code?: string; durationMs?: number; contract?: import("../assistance-contract").AssistanceContractSnapshot; providerTransport?: "chat_json" | "responses_tools"; toolName?: "propose_reflection_v1" | "propose_map_action_v1"; repairCount?: number; candidateStatus?: "active" | "parked" | "ignored" | "promoted"; ageInTurns?: number; currentPercentage?: number; peakPercentage?: number }) => { await ledgerRef.current.record(kind, detail, { ...extra, contract: extra?.contract ?? snapshotContract(contract) }); setLedgerAvailable(ledgerRef.current.isAvailable); }, [contract]); @@ -4456,7 +4456,7 @@ export default function App({ providerRuntime, aiAccessDenied = false }: AppProp * is the share of the proposal's cited phrases that came back verbatim from the * coach, and the tooltip lists them. */ -export function InfluenceBadge({ influence }: { influence?: import("./assistance-contract").InfluenceTrace }) { +export function InfluenceBadge({ influence }: { influence?: import("../assistance-contract").InfluenceTrace }) { if (!influence || !influence.exactOverlapPhrases.length) return null; const pct = Number.isFinite(influence.overlapRatio) ? Math.round(influence.overlapRatio! * 100) : undefined; // Echo is an influence signal, not an authorship claim — this trace also diff --git a/src/ControlRoom.tsx b/src/ui/ControlRoom.tsx similarity index 98% rename from src/ControlRoom.tsx rename to src/ui/ControlRoom.tsx index 1448f38..4893460 100644 --- a/src/ControlRoom.tsx +++ b/src/ui/ControlRoom.tsx @@ -10,11 +10,11 @@ // --------------------------------------------------------------------------- import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react"; -import type { UserRequestedMode } from "./llm-contract"; -import type { ProvenanceTotals } from "./provenance-summary"; +import type { UserRequestedMode } from "../coach/llm-contract"; +import type { ProvenanceTotals } from "../map/provenance-summary"; import { useMutationAccess } from "./mutation-policy"; import { useUiLocale } from "./ui-locale"; -import type { SafetyCheck, TrackedIdea, UnderhoodEvent, UnderstandingSnapshot } from "./understanding"; +import type { SafetyCheck, TrackedIdea, UnderhoodEvent, UnderstandingSnapshot } from "../session/understanding"; function targetLabel(target: TrackedIdea["target"]): string { return target === "idea" ? "idea" : target === "hierarchy" ? "nesting" : "connection"; diff --git a/src/Map.test.ts b/src/ui/Map.test.ts similarity index 97% rename from src/Map.test.ts rename to src/ui/Map.test.ts index f492d1a..7a222c0 100644 --- a/src/Map.test.ts +++ b/src/ui/Map.test.ts @@ -5,8 +5,8 @@ import { describe, expect, it } from "vitest"; import { groupDragPositions, pruneContextSelection, proxyAnchorSpecs, renderedEndpointCenter, resyncFlowNodes, suggestionBadge, toggleContextSelection } from "./Map"; -import { ThoughtUnitStore } from "./map-store"; -import type { ThoughtUnit, ThoughtUnitRole } from "./types"; +import { ThoughtUnitStore } from "../map/map-store"; +import type { ThoughtUnit, ThoughtUnitRole } from "../types"; function unit(id: string, parentId?: string, role: ThoughtUnitRole = "node"): ThoughtUnit { return { diff --git a/src/Map.tsx b/src/ui/Map.tsx similarity index 99% rename from src/Map.tsx rename to src/ui/Map.tsx index 470d529..0eb3624 100644 --- a/src/Map.tsx +++ b/src/ui/Map.tsx @@ -25,12 +25,12 @@ import { useUpdateNodeInternals, } from "@xyflow/react"; import "@xyflow/react/dist/style.css"; -import { computeAutoCleanPositions, computeConnectionHandles } from "./map-layout"; -import { executeCanvasAction, type CanvasAction } from "./action-gateway"; -import type { ConnectionLayoutDirection, ThoughtUnitStore, XYBounds, XYPosition, XYSize } from "./map-store"; -import type { SourceBank } from "./store"; -import { cardRef } from "./store"; -import type { ConfirmedReflection, ThoughtUnit } from "./types"; +import { computeAutoCleanPositions, computeConnectionHandles } from "../map/map-layout"; +import { executeCanvasAction, type CanvasAction } from "../grounding/action-gateway"; +import type { ConnectionLayoutDirection, ThoughtUnitStore, XYBounds, XYPosition, XYSize } from "../map/map-store"; +import type { SourceBank } from "../map/store"; +import { cardRef } from "../map/store"; +import type { ConfirmedReflection, ThoughtUnit } from "../types"; import { useMutationAccess, type MutationIntent } from "./mutation-policy"; import { useUiLocale } from "./ui-locale"; import { useReaderView } from "./reader-view"; @@ -89,7 +89,7 @@ interface ConnectionEdgeData extends Record { targetRef?: string; sourceText?: string; targetText?: string; - origin?: import("./assistance-contract").ContributionOrigin | "user_canvas"; + origin?: import("../assistance-contract").ContributionOrigin | "user_canvas"; } /** Id-based actions shared by a card and every card embedded inside it. */ diff --git a/src/draft-anchor.test.ts b/src/ui/draft-anchor.test.ts similarity index 100% rename from src/draft-anchor.test.ts rename to src/ui/draft-anchor.test.ts diff --git a/src/draft-anchor.ts b/src/ui/draft-anchor.ts similarity index 100% rename from src/draft-anchor.ts rename to src/ui/draft-anchor.ts diff --git a/src/mutation-policy.test.ts b/src/ui/mutation-policy.test.ts similarity index 100% rename from src/mutation-policy.test.ts rename to src/ui/mutation-policy.test.ts diff --git a/src/mutation-policy.tsx b/src/ui/mutation-policy.tsx similarity index 100% rename from src/mutation-policy.tsx rename to src/ui/mutation-policy.tsx diff --git a/src/reader-translation.test.ts b/src/ui/reader-translation.test.ts similarity index 100% rename from src/reader-translation.test.ts rename to src/ui/reader-translation.test.ts diff --git a/src/reader-translation.ts b/src/ui/reader-translation.ts similarity index 100% rename from src/reader-translation.ts rename to src/ui/reader-translation.ts diff --git a/src/reader-view.test.ts b/src/ui/reader-view.test.ts similarity index 98% rename from src/reader-view.test.ts rename to src/ui/reader-view.test.ts index 97704a3..2e1db53 100644 --- a/src/reader-view.test.ts +++ b/src/ui/reader-view.test.ts @@ -3,8 +3,8 @@ import { StrictMode, createElement, useEffect } from "react"; import { act } from "react"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -vi.mock("./api", () => ({ translateReaderText: vi.fn() })); -import { translateReaderText } from "./api"; +vi.mock("../coach/api", () => ({ translateReaderText: vi.fn() })); +import { translateReaderText } from "../coach/api"; import { appendReaderDisplayCache, clearPersistedReaderDisplayCache, diff --git a/src/reader-view.tsx b/src/ui/reader-view.tsx similarity index 99% rename from src/reader-view.tsx rename to src/ui/reader-view.tsx index 5a0ca26..c2d4779 100644 --- a/src/reader-view.tsx +++ b/src/ui/reader-view.tsx @@ -1,5 +1,5 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; -import { translateReaderText, type ProviderRuntimeConfig } from "./api"; +import { translateReaderText, type ProviderRuntimeConfig } from "../coach/api"; import { mapWithConcurrency, protectReaderText, readerCacheKey } from "./reader-translation"; import { isSupportedUiLocale, normalizeUiLocale, uiLocaleOptions, type UiLocaleOption } from "./ui-locale"; diff --git a/src/ui-locale.test.ts b/src/ui/ui-locale.test.ts similarity index 95% rename from src/ui-locale.test.ts rename to src/ui/ui-locale.test.ts index aab9da8..7add3f1 100644 --- a/src/ui-locale.test.ts +++ b/src/ui/ui-locale.test.ts @@ -68,7 +68,7 @@ describe("UI locale", () => { }); it("registers every localized literal in the canonical English source", () => { - const sources = ["App.tsx", "Map.tsx", "PlatformBootstrap.tsx"].map((file) => + const sources = ["App.tsx", "Map.tsx", "../platform/PlatformBootstrap.tsx"].map((file) => readFileSync(new URL(`./${file}`, import.meta.url), "utf8"), ); const keys = new Set(); @@ -83,7 +83,7 @@ describe("UI locale", () => { "I couldn’t complete that response reliably. You can rephrase or try again.", ].forEach((key) => keys.add(key)); const canonical = new Set(JSON.parse( - readFileSync(new URL("./i18n/source.json", import.meta.url), "utf8"), + readFileSync(new URL("../i18n/source.json", import.meta.url), "utf8"), ) as string[]); const missing = [...keys].filter((key) => !canonical.has(key)); expect(missing).toEqual([]); diff --git a/src/ui-locale.tsx b/src/ui/ui-locale.tsx similarity index 100% rename from src/ui-locale.tsx rename to src/ui/ui-locale.tsx diff --git a/src/ui-strings.ts b/src/ui/ui-strings.ts similarity index 97% rename from src/ui-strings.ts rename to src/ui/ui-strings.ts index c450d8a..35fe89d 100644 --- a/src/ui-strings.ts +++ b/src/ui/ui-strings.ts @@ -12,10 +12,10 @@ * text is never passed here. A missing entry renders in English, so a partial * dictionary degrades in coverage rather than correctness. * - * See ./i18n/README.md for how a dictionary is produced. + * See ../i18n/README.md for how a dictionary is produced. */ -const DICTIONARIES = import.meta.glob>("./i18n/*.json", { +const DICTIONARIES = import.meta.glob>("../i18n/*.json", { eager: true, import: "default", }); diff --git a/src/useSpeechToText.test.tsx b/src/ui/useSpeechToText.test.tsx similarity index 100% rename from src/useSpeechToText.test.tsx rename to src/ui/useSpeechToText.test.tsx diff --git a/src/useSpeechToText.ts b/src/ui/useSpeechToText.ts similarity index 100% rename from src/useSpeechToText.ts rename to src/ui/useSpeechToText.ts From 27b344224e3fc008a5ca730281973e04b7fb9784 Mon Sep 17 00:00:00 2001 From: nhyiramante1 <196460314+nhyiramante1@users.noreply.github.com> Date: Sun, 4 Oct 2026 10:10:12 -0400 Subject: [PATCH 3/4] test(i18n): guard that every dictionary file loads --- src/ui/ui-locale.test.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ui/ui-locale.test.ts b/src/ui/ui-locale.test.ts index 7add3f1..1457540 100644 --- a/src/ui/ui-locale.test.ts +++ b/src/ui/ui-locale.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { readFileSync } from "node:fs"; +import { readFileSync, readdirSync } from "node:fs"; import { UI_LOCALE_STORAGE_KEY, applyDocumentLocale, @@ -11,7 +11,7 @@ import { uiDirection, uiLocaleOptions, } from "./ui-locale"; -import { uiString } from "./ui-strings"; +import { translatedLanguages, uiString } from "./ui-strings"; describe("UI locale", () => { it("normalizes supported browser locales and falls back to English", () => { @@ -52,6 +52,15 @@ describe("UI locale", () => { expect(options.some((option) => option.code === "source")).toBe(false); }); + it("loads a dictionary for every translation file in src/i18n", () => { + const files = readdirSync(new URL("../i18n/", import.meta.url)) + .filter((name) => name.endsWith(".json") && name !== "source.json") + .map((name) => name.slice(0, -".json".length)) + .sort(); + expect(files.length).toBeGreaterThan(0); + expect(translatedLanguages()).toEqual(files); + }); + it("uses Chinese static copy and safely falls back for missing UI strings", () => { expect(uiString("Clear map", "zh")).toBeTypeOf("string"); expect(uiString("Clear map", "zh")).not.toBe("Clear map"); From b7510ce2e2be58ef19da52f66ecebc9040f63bce Mon Sep 17 00:00:00 2001 From: nhyiramante1 <196460314+nhyiramante1@users.noreply.github.com> Date: Sun, 4 Oct 2026 10:10:43 -0400 Subject: [PATCH 4/4] docs: point path references at the new folders --- README.md | 13 +++++++++++++ docs/login-bootstrap-spec.md | 2 +- docs/multilingual-grounding-pass.md | 2 +- docs/polish-sweeps-pass.md | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c71e241..584266b 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,19 @@ MINDMAP_OAUTH_REDIRECT_URIS=https://mindmap.thoughtful-ai.com/ Restart the backend after setting them so startup provisioning creates or updates the fixed client. Do not register localhost on the production server. +## Source layout + +| Folder | Responsibility | +| --- | --- | +| `src/` | Entry point and shared definitions | +| `src/coach/` | One model turn: prompt, transport, response parsing, and turn loop | +| `src/grounding/` | Code checks on AI output | +| `src/map/` | Map, Source Bank, proposal state, and provenance | +| `src/session/` | Persistence, logging, and diagnostics | +| `src/platform/` | Writing Tools sign-in | +| `src/ui/` | React components, UI copy, and DOM helpers | +| `src/i18n/` | Translation dictionaries | + ## Verification ```sh diff --git a/docs/login-bootstrap-spec.md b/docs/login-bootstrap-spec.md index 38814de..e57a4d3 100644 --- a/docs/login-bootstrap-spec.md +++ b/docs/login-bootstrap-spec.md @@ -35,7 +35,7 @@ automatic document reading. There is no `doc:read` scope any more. ### 1. Replace the launch gate with a login gate -Today `launchRequired()` in `src/platform-session.ts` refuses to start unless +Today `launchRequired()` in `src/platform/platform-session.ts` refuses to start unless Writing Tools launched the app. Replace it with `loginRequired()` **keeping the exact same shape**: diff --git a/docs/multilingual-grounding-pass.md b/docs/multilingual-grounding-pass.md index 5c1483e..9696c0b 100644 --- a/docs/multilingual-grounding-pass.md +++ b/docs/multilingual-grounding-pass.md @@ -37,7 +37,7 @@ rebuilding; these donor modules already encode the right philosophy: Reuse (near) as-is: - `src/i18n/*.json` — **34 static UI dictionaries including `zh.json`**, keyed by English source string. Chinese UI works day one. -- `src/ui-strings.ts` — static dictionary lookup with case-folded keys; +- `src/ui/ui-strings.ts` — static dictionary lookup with case-folded keys; partial dictionary degrades in coverage, never correctness. - `src/language.ts` — the write/view language split. Its own doc comment states a view-language translation is "never written back, never harvested into the diff --git a/docs/polish-sweeps-pass.md b/docs/polish-sweeps-pass.md index 4bdd035..6ad1195 100644 --- a/docs/polish-sweeps-pass.md +++ b/docs/polish-sweeps-pass.md @@ -59,7 +59,7 @@ sync with no tool to regenerate. Port the donor's generator. module does **not exist on `mindmap-main`** (the donor `language.ts` is on the explicit-skip list) and there is **no `LANGUAGE_CODES` constant anywhere** — `mindmap-main` discovers locales at build time via - `import.meta.glob("./i18n/*.json")` in `src/ui-strings.ts`. So the donor + `import.meta.glob("./i18n/*.json")` in `src/ui/ui-strings.ts`. So the donor script throws immediately. **Fix:** rewrite `supportedLanguages()` to enumerate the existing `src/i18n/*.json` filenames (excluding `source.json`), e.g. read the `i18n` dir and strip `.json`. This keeps the