|
| 1 | +# Plan — Issue #239: vscode-free domain + `shared/editor` adapter |
| 2 | + |
| 3 | +> **Spec:** [docs/specs/2026-06-02-239-vscode-free-domain.md](../specs/2026-06-02-239-vscode-free-domain.md) |
| 4 | +> **Issue:** [pajoma/vscode-journal#239](https://github.com/pajoma/vscode-journal/issues/239) |
| 5 | +> **Branch:** `refactor/239-vscode-free-domain` (off `develop`) |
| 6 | +> **Created:** 2026-06-02 |
| 7 | +
|
| 8 | +## Approach |
| 9 | + |
| 10 | +Five staged steps, each test-green and independently shippable. Edges first (cheap, unblocks reasoning about ownership), then the editor adapter, then domain return-type changes, then tests. Keep `IEditor` on the `JournalController` so commands reach it via the interface (consistent with Phase 2 DI). |
| 11 | + |
| 12 | +## Steps |
| 13 | + |
| 14 | +### Step 1 — Remove cross-feature edges |
| 15 | +- **smart-input → entries:** move `Dialogues` to `shared/` (it is a generic UI service, already used broadly) OR move `ScanEntries` to `shared/`. Prefer moving `Dialogues` → `shared/dialogues/` since it implements `IDialogues` (a shared interface). Re-point `Container`. |
| 16 | +- **navigation → entries:** move `AbstractLoadEntryForDateCommand` to `shared/commands/` (or `features/entries` export consumed only via a shared base). Navigation extends the shared base. |
| 17 | +- **entries → notes:** in `loadPageForInput`, replace the direct `new LoadNotes(...)` branch with a note-input handler resolved from the `JournalController` (register a `noteLoader` in the container, or move the `NoteInput` branch up into the command-registration layer). Entries no longer imports `notes`. |
| 18 | +- Verify: cross-feature grep (the #234 Phase-4 script) returns zero edges; suite green. |
| 19 | + |
| 20 | +### Step 2 — `shared/editor/` adapter |
| 21 | +- Add `IEditor` to `shared/model/interfaces.ts`: `open(path)`, `show(doc)`, `save(doc)`, `createAndOpen(path, content)`, `applyEdit(doc, edits)` / `insert(doc, position, text)`. |
| 22 | +- Implement `EditorAdapter` in `shared/editor/`, moving the bodies from `Dialogues.openDocument/showDocument/saveDocument`, `Writer.createSaveLoadTextDocument`, and `Inject.injectString/injectInlineString`. |
| 23 | +- Add `editor: IEditor` to `JournalController` + `Container`. |
| 24 | +- Verify: suite green (behavior identical; just relocated). |
| 25 | + |
| 26 | +### Step 3 — Writer / Reader return data |
| 27 | +- `Writer.createEntryForPath/createWeeklyForPath` → return the resolved path + content (or a small `CreatedFile` record); the command/reader calls `editor.createAndOpen`. |
| 28 | +- `Reader.loadEntryFor*` → return `{ path, created: boolean }` (plain data, no `vscode`); the command opens via `editor.open`. The `onNotesInjected`/`entryOpened` event still fires from the command layer. |
| 29 | +- Drop `import * as vscode` from `reader.ts` / `writer.ts`. |
| 30 | +- Verify: `grep "vscode" reader.ts writer.ts` empty; suite green. |
| 31 | + |
| 32 | +### Step 4 — Inject split |
| 33 | +- Keep pure: `buildInlineString` (returns `InlineString`), position computation → return a plain `{ line, character }` instead of `vscode.Position`. |
| 34 | +- Move apply (`injectString`, `injectInlineString`, `injectInput`'s edit application) to `EditorAdapter`. |
| 35 | +- `inject.ts` pure half drops `vscode` import; the editor adapter owns `WorkspaceEdit`. |
| 36 | +- Verify: pure half has no `vscode`; suite green. |
| 37 | + |
| 38 | +### Step 5 — Node-only unit tests |
| 39 | +- Add `src/test/unit/` (plain node, not Extension Host) covering: path resolution (`PathResolver`), `buildInlineString`, position computation, `inferType`. |
| 40 | +- Wire into the test pipeline (separate `node --test` or extend compile-tests + a node runner). Document the command in AGENTS.md. |
| 41 | +- Verify: node-only tests pass without a display; full Extension-Host suite still green. |
| 42 | + |
| 43 | +## Test scenarios |
| 44 | + |
| 45 | +| Scenario | Check | |
| 46 | +|----------|-------| |
| 47 | +| Each step | `npm run check` green | |
| 48 | +| Step 1 | cross-feature edge grep → 0; commands-prev-next, commands-note, scan/quickpick tests green | |
| 49 | +| Step 2 | open/show/save + create-and-open behavior unchanged (commands-entry real-fs, issue-51 remote) | |
| 50 | +| Step 3 | `grep vscode reader.ts writer.ts` empty; entry/weekly create+open tests green | |
| 51 | +| Step 4 | `grep vscode` on Inject pure half empty; commands-inject (memo/task insertion) green | |
| 52 | +| Step 5 | node-only unit tests pass with no Extension Host | |
| 53 | +| Regression | full suite (238+) green after every step | |
| 54 | + |
| 55 | +## Risks |
| 56 | + |
| 57 | +| Risk | Mitigation | |
| 58 | +|------|-----------| |
| 59 | +| Return-type change ripples to many call sites | Stage 3/4 isolated; `IEditor` on `JournalController` keeps call sites uniform | |
| 60 | +| Remote-path edge cases (local-vs-remote open) regress | issue-51 + remote tests cover; keep `getResolvedEntryPathForLocalOpen` flow intact | |
| 61 | +| Inject apply semantics (WorkspaceEdit ordering) change | Move bodies verbatim into adapter; commands-inject asserts inserted text/position | |
| 62 | +| Moving `Dialogues` to shared churns imports | Mechanical; `Container` is the only constructor site | |
| 63 | +| Note-dispatch indirection adds complexity | Keep it minimal (a `noteLoader` callback or command-layer branch), not a generic plugin system | |
| 64 | + |
| 65 | +## Rollback |
| 66 | + |
| 67 | +Each step is a separate PR. Revert the offending PR; earlier steps remain valid. No settings/schema/data migration. |
| 68 | + |
| 69 | +## Reference spec |
| 70 | + |
| 71 | +[docs/specs/2026-06-02-239-vscode-free-domain.md](../specs/2026-06-02-239-vscode-free-domain.md) |
0 commit comments