Skip to content

Commit 5a3c6ee

Browse files
authored
Merge pull request #222 from pajoma/fix/221-attachement-typo
fix(model): rename JournalPageType.attachement → attachment (#221)
2 parents 6a49535 + 8dbcb57 commit 5a3c6ee

11 files changed

Lines changed: 301 additions & 16 deletions
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Plan: Decouple inferType from Raw Positional Arguments (#199)
2+
3+
## Reference spec
4+
[docs/specs/2026-05-18-199-infer-type-context-object.md](../specs/2026-05-18-199-infer-type-context-object.md)
5+
6+
## Approach
7+
8+
Introduce `InferTypeContext` in `src/journal/paths.ts` (colocated with `inferType` — approved in spec review). Change the function signature from `(entry, extension: string)` to `(entry, ctx: InferTypeContext)`. Update both call sites in `scan-entries.ts`. No logic changes — pure structural refactor.
9+
10+
Trade-off: keeping the interface in `paths.ts` rather than `src/model/interfaces.ts` maximises local cohesion at the cost of discoverability. Correct per YAGNI until a second consumer appears.
11+
12+
**Amendment (post-review):** Unit tests must be written against existing behavior before touching the signature, to lock down the contract empirically. All future fields in `InferTypeContext` must be optional (`?:`) — required fields would reintroduce shotgun surgery on every addition. The `|` literal in the existing regex (`/^[\d|\-|_]+$/`) is a pre-existing quirk (pipes illegal in Windows filenames, unused on Unix); it is documented in the test but not fixed in this PR.
13+
14+
## Steps
15+
16+
### 0 — Write unit tests for current `inferType` behavior (`src/test/suite/infer-type.test.ts`)
17+
18+
`inferType` has no VS Code dependencies — safe to run inside Extension Host suite without special plumbing. Lock down all three classification branches before touching the signature:
19+
20+
- attachment: extension mismatch → `JournalPageType.attachment`
21+
- entry: `extension` matches AND name matches `/^[\d|\-|_]+$/``JournalPageType.entry`
22+
- note: `extension` matches AND name is alphanumeric → `JournalPageType.note`
23+
24+
Include a test that confirms the current (pre-fix) behavior of `2026|05|18.md``entry`, so the regex fix in Step 0b is verifiable.
25+
26+
### 0b — Fix regex in `inferType` (`src/journal/paths.ts`)
27+
28+
Separate commit. Change `/^[\d|\-|_]+$/gm``/^[\d\-_]+$/`:
29+
30+
- Remove `|` literal from character class (was unintentionally included; pipe is valid on macOS/Linux)
31+
- Remove `gm` flags (unnecessary for a single filename string match)
32+
33+
No classification semantics change for real-world filenames. Update the test from Step 0 to reflect corrected behavior: `2026|05|18.md``JournalPageType.note` after fix.
34+
35+
### 1 — Add `InferTypeContext` and update `inferType` (`src/journal/paths.ts`)
36+
37+
Define the interface immediately above the function:
38+
39+
```typescript
40+
export interface InferTypeContext {
41+
extension: string;
42+
// all future fields must be optional (?: ) to prevent shotgun surgery
43+
}
44+
```
45+
46+
Change signature:
47+
48+
```typescript
49+
export function inferType(entry: Path.ParsedPath, ctx: InferTypeContext): J.Model.JournalPageType
50+
```
51+
52+
Replace `extension` references inside the body with `ctx.extension`.
53+
54+
### 2Update call sites (`src/features/entries/scan-entries.ts`)
55+
56+
Two occurrences (lines 68 and 140 at time of writing):
57+
58+
```typescript
59+
// before
60+
entry.type = J.Journal.inferType(Path.parse(entry.path), this.config.getFileExtension());
61+
// after
62+
entry.type = J.Journal.inferType(Path.parse(entry.path), { extension: this.config.getFileExtension() });
63+
```
64+
65+
### 3Verify compile
66+
67+
```bash
68+
npm run compile # esbuild — extension bundle
69+
npm run compile-tests # tsc — catches type errors in tests
70+
```
71+
72+
### 4Run tests
73+
74+
```bash
75+
npm test
76+
```
77+
78+
All existing tests plus the new unit tests must pass.
79+
80+
## Test scenarios
81+
82+
- **Compile clean:** `npm run compile` and `npm run compile-tests` both exit 0, no TS errors
83+
- **Regressionattachment:** file with non-matching extension`JournalPageType.attachment`
84+
- **Regressionentry:** matching extension + digits/dashes/underscores name`JournalPageType.entry`
85+
- **Regressionnote:** matching extension + alphanumeric name`JournalPageType.note`
86+
- **Regex fix verified:** `2026|05|18.md``JournalPageType.note` after Step 0b (pipe no longer in character class)
87+
- **Extensibility proof:** adding `weeklyFilePattern?: string` to `InferTypeContext` requires touching only `paths.ts`
88+
89+
## Dependencies
90+
91+
None. Self-contained structural change; no other open PR touches `inferType` or its call sites.
92+
93+
## Risk
94+
95+
Low. Pure signature refactorno logic change, no new code paths. Covered entirely by the existing test suite plus compile checks.
96+
97+
## Rollback
98+
99+
`git revert <commit>`single commit, no data or config side effects.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
issue: 221
3+
slug: fix-attachement-typo
4+
date: 2026-05-18
5+
status: plan
6+
spec: docs/specs/2026-05-18-221-fix-attachement-typo.md
7+
---
8+
9+
# Plan: Fix `attachement` typo — enum, method, and all consumers
10+
11+
## Approach
12+
13+
Single-commit rename across all affected files. Pure text substitution — no logic changes, no behaviour change. TypeScript compiler enforces completeness: any missed reference fails to compile. One commit keeps `git bisect` attribution clean.
14+
15+
## Steps
16+
17+
1. **Rename enum member** in `src/model/config.ts:4`
18+
`attachement``attachment`
19+
Foundation; compiler immediately flags all unresolved consumers.
20+
21+
2. **Fix enum consumers** in `src/journal/paths.ts`, `src/vscode/dialogues.ts`
22+
`JournalPageType.attachement``JournalPageType.attachment` at all 5 sites.
23+
24+
3. **Rename method + fix log strings** in `src/features/sync/sync-note-links.ts`
25+
Method signature line 21, log strings lines 22/36/41: `injectAttachementLinks``injectAttachmentLinks`.
26+
27+
4. **Fix call site** in `src/vscode/startup.ts:101`
28+
`injectAttachementLinks``injectAttachmentLinks`.
29+
30+
5. **Fix comments** in `src/features/entries/scan-entries.ts:57,92`
31+
Inline comment spelling only — no logic change.
32+
33+
6. **Fix plan prose** in `docs/plans/2026-05-18-199-infer-type-context-object.md:20,83`
34+
Prevents copy-paste regression in future tasks.
35+
36+
7. **Verify**`tsc --noEmit` must pass; `grep -rn "attachement" src/` must return zero hits.
37+
38+
8. **Commit + PR** — single commit, PR description links issue, spec, and plan.
39+
40+
## Test Scenarios
41+
42+
| Scenario | Type | Coverage |
43+
|----------|------|----------|
44+
| Compilation succeeds with zero TS errors | compile-time | Proves all enum and method references resolved |
45+
| `grep -rn "attachement" src/` returns zero hits | static analysis | Proves no remaining typo in source |
46+
| Existing unit tests pass unchanged | unit | Proves no behavioural regression (tests in active worktrees will assert `attachment` after rebase) |
47+
48+
No new tests needed — this is a pure rename with no logic change.
49+
50+
## Dependencies
51+
52+
None. Self-contained rename; no other PRs must land first.
53+
54+
## Risk
55+
56+
- **Worktrees diverge temporarily** — worktrees `feat+199`, `feat+209`, `feat+210`, `feat+211` each reference `attachement`. They must rebase on develop after this lands. Low risk: compiler will surface conflicts immediately on rebase.
57+
- **Missed occurrence** — mitigated by grep verification step before commit.
58+
59+
## Rollback
60+
61+
`git revert <commit>` — pure rename reverts cleanly with zero side-effects.
62+
63+
## Reference Spec
64+
65+
[docs/specs/2026-05-18-221-fix-attachement-typo.md](../specs/2026-05-18-221-fix-attachement-typo.md)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Spec: Decouple inferType from Raw Positional Arguments (#199)
2+
3+
## Goal
4+
5+
Change `inferType` in `src/journal/paths.ts` to accept a context object instead of positional raw-value arguments, so future parameter additions don't require shotgun surgery across call sites.
6+
7+
## Why Now
8+
9+
A prior change (adding `weeklyFilePatternRaw`) forced updates to every call site of `inferType` — two in `scan-entries.ts` and the function itself in `paths.ts`. The function is a stable utility but its positional signature makes it rigid: each new classification criterion forces a signature change and cascading edits. A context object breaks that coupling.
10+
11+
## In Scope
12+
13+
- Introduce `InferTypeContext` interface in `src/journal/paths.ts` (or `src/model/interfaces.ts` if it becomes a shared type — see open questions)
14+
- Change `inferType` signature from `(entry: Path.ParsedPath, extension: string)` to `(entry: Path.ParsedPath, ctx: InferTypeContext)`
15+
- Update both call sites in `src/features/entries/scan-entries.ts` to pass `{ extension: this.config.getFileExtension() }`
16+
- Update `src/journal/index.ts` re-export if the interface is defined there
17+
18+
## Out of Scope
19+
20+
- Adding new classification logic (e.g. weekly-note detection via pattern) — that is a follow-up
21+
- Changing `JournalPageType` enum members
22+
- Any changes to `getWeekFromURIAndConfig` or `getDateFromURIAndConfig`
23+
24+
## Acceptance Criteria
25+
26+
- `inferType` signature takes `(entry: Path.ParsedPath, ctx: InferTypeContext)` where `InferTypeContext` has at minimum `{ extension: string }`
27+
- Both call sites in `scan-entries.ts` compile and pass `ctx` as an object literal
28+
- `npm run compile` and `npm run compile-tests` succeed (no type errors)
29+
- `npm test` passes (all existing tests green)
30+
- Adding a future field to `InferTypeContext` requires touching only `paths.ts` and the struct definition — not the call sites (verified by design)
31+
32+
## Entities / Contracts
33+
34+
```typescript
35+
// src/journal/paths.ts (or src/model/interfaces.ts)
36+
export interface InferTypeContext {
37+
extension: string;
38+
// future fields: weeklyFilePattern?: string, etc.
39+
}
40+
41+
// updated signature
42+
export function inferType(entry: Path.ParsedPath, ctx: InferTypeContext): J.Model.JournalPageType
43+
```
44+
45+
Call site (no change to call-site logic):
46+
```typescript
47+
// scan-entries.ts — both occurrences
48+
entry.type = J.Journal.inferType(Path.parse(entry.path), { extension: this.config.getFileExtension() });
49+
```
50+
51+
## Constraints
52+
53+
- `InferTypeContext` must have zero `vscode` imports`paths.ts` is domain code
54+
- Keep interface definition in `src/journal/paths.ts` unless a second consumer in a different module also needs it; move to `src/model/interfaces.ts` only when that second consumer appears (YAGNI)
55+
56+
## Open Questions
57+
58+
- Should `InferTypeContext` live in `src/journal/paths.ts` or `src/model/interfaces.ts`? Recommend `paths.ts` until a second consumer exists.
59+
- Is there a planned follow-up to add weekly detection inside `inferType`? If so, the `weeklyFilePattern?: string` field should be stubbed in the interface now to validate the design.
60+
61+
## Related Issues
62+
63+
- Part of the ongoing domain-layer decoupling work (cf. #209, #212)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
issue: 221
3+
slug: fix-attachement-typo
4+
date: 2026-05-18
5+
status: spec
6+
---
7+
8+
# Spec: Fix `attachement` typo — enum, method, and all consumers
9+
10+
## Goal
11+
12+
Eliminate every occurrence of the misspelled token `attachement` across enum member, method name, log strings, comments, and documentation prose.
13+
14+
## Why Now
15+
16+
Identified during review of #199/#220. The typo spans the core domain model enum AND a public method (`injectAttachementLinks`). Fixing now — before more consumers land — minimises blast radius. All current sites are known and enumerable.
17+
18+
## In Scope
19+
20+
Rename in main-branch source files only (worktrees are ephemeral and will rebase on develop):
21+
22+
| File | Lines | Change |
23+
|------|-------|--------|
24+
| `src/model/config.ts` | 4 | Enum member: `attachement``attachment` |
25+
| `src/journal/paths.ts` | 179 | Return value + inline comment |
26+
| `src/features/entries/scan-entries.ts` | 57, 92 | Comments only |
27+
| `src/vscode/dialogues.ts` | 129, 130, 460 | Three enum references |
28+
| `src/features/sync/sync-note-links.ts` | 21, 22, 36, 41 | Method rename + log strings: `injectAttachementLinks``injectAttachmentLinks` |
29+
| `src/vscode/startup.ts` | 101 | Call site: `injectAttachementLinks``injectAttachmentLinks` |
30+
| `docs/plans/2026-05-18-199-infer-type-context-object.md` | 20, 83 | Plan prose mentions |
31+
32+
Test files on active branches (updated by their branch on rebase):
33+
- `feat+199-infer-type-context-object`: `src/test/suite/infer-type.test.ts:10,12,15,17`
34+
35+
## Out of Scope
36+
37+
- Other worktrees — they rebase or merge develop after this lands
38+
- Runtime / serialised data (the enum is not persisted to disk or config files by value; used purely in-process)
39+
- No config schema changes required
40+
41+
## Acceptance Criteria
42+
43+
1. `grep -rn "attachement" src/` returns zero hits (excluding worktrees)
44+
2. TypeScript compilation succeeds (`npm run compile` or `tsc --noEmit`)
45+
3. All existing tests pass
46+
47+
## Constraints
48+
49+
- Enum member rename is a pure rename — no behavioural change
50+
- Must stay on a single commit for clean `git bisect` attribution
51+
52+
## Open Questions
53+
54+
None.
55+
56+
## Related
57+
58+
- Identified during: #199, #220

src/features/entries/scan-entries.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class ScanEntries {
5454
}
5555

5656
// go into base directory, find all files changed within the last X days (see config)
57-
// for each file, check if it is an entry, a note or an attachement
57+
// for each file, check if it is an entry, a note or an attachment
5858
for (const directory of directories) {
5959
try {
6060
await this.fs.stat(directory.path);
@@ -89,7 +89,7 @@ export class ScanEntries {
8989
public async getPreviouslyAccessedFiles(thresholdInMs: number, callback: Function, picker: any, type: J.Model.JournalPageType, directories: Set<J.Model.ScopeDirectory>): Promise<void> {
9090

9191
// go into base directory, find all files changed within the last 40 days
92-
// for each file, check if it is an entry, a note or an attachement
92+
// for each file, check if it is an entry, a note or an attachment
9393

9494

9595
this.logger.trace("Entering getPreviouslyAccessedFiles() in actions/reader.ts and number of directories to scan: ", directories.size);

src/features/sync/sync-note-links.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ export class SyncNoteLinks {
1818
*
1919
* @param doc
2020
*/
21-
public async injectAttachementLinks(doc: vscode.TextDocument, date: Date): Promise<vscode.TextDocument> {
22-
this.ctrl.logger.trace("Entering injectAttachementLinks() in features/sync-note-links for date: ", date);
21+
public async injectAttachmentLinks(doc: vscode.TextDocument, date: Date): Promise<vscode.TextDocument> {
22+
this.ctrl.logger.trace("Entering injectAttachmentLinks() in features/sync-note-links for date: ", date);
2323

2424
try {
2525
await this.ctrl.ui.saveDocument(doc);
@@ -33,12 +33,12 @@ export class SyncNoteLinks {
3333
const promises: Promise<J.Model.InlineString>[] = foundFiles
3434
.filter(file => J.Util.isNullOrUndefined(referencedFiles.find(match => match.fsPath === file.fsPath)))
3535
.map(file => {
36-
this.ctrl.logger.debug("injectAttachementLinks() - File link not present in entry: ", file);
36+
this.ctrl.logger.debug("injectAttachmentLinks() - File link not present in entry: ", file);
3737
return this.buildReference(doc, file);
3838
});
3939

4040
const inlineStrings = await Promise.all(promises);
41-
this.ctrl.logger.trace("injectAttachementLinks() - Number of references to synchronize: ", inlineStrings.length);
41+
this.ctrl.logger.trace("injectAttachmentLinks() - Number of references to synchronize: ", inlineStrings.length);
4242

4343
if (inlineStrings.length > 0) {
4444
this.ctrl.inject.injectInlineString(inlineStrings[0], ...inlineStrings.splice(1))

src/journal/paths.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ export interface InferTypeContext {
176176
export function inferType(entry: Path.ParsedPath, ctx: InferTypeContext): J.Model.JournalPageType {
177177

178178
if (!entry.ext.endsWith(ctx.extension)) {
179-
return J.Model.JournalPageType.attachement;
179+
return J.Model.JournalPageType.attachment;
180180
} else if (entry.name.match(/^[\d\-_]+$/)) {
181181
return J.Model.JournalPageType.entry;
182182
} else {

src/model/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export enum JournalPageType {
22
note,
33
entry,
4-
attachement
4+
attachment
55
}
66

77
export interface ScopedTemplate {

src/test/suite/infer-type.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ suite('inferType — classification', () => {
77
const ctx: InferTypeContext = { extension: '.md' };
88

99
suite('attachment', () => {
10-
test('extension mismatch → attachement', () => {
10+
test('extension mismatch → attachment', () => {
1111
const entry = Path.parse('/base/2026/05/2026-05-18.txt');
12-
assert.strictEqual(inferType(entry, ctx), JournalPageType.attachement);
12+
assert.strictEqual(inferType(entry, ctx), JournalPageType.attachment);
1313
});
1414

15-
test('no extension → attachement', () => {
15+
test('no extension → attachment', () => {
1616
const entry = Path.parse('/base/2026/05/2026-05-18');
17-
assert.strictEqual(inferType(entry, ctx), JournalPageType.attachement);
17+
assert.strictEqual(inferType(entry, ctx), JournalPageType.attachment);
1818
});
1919
});
2020

src/vscode/dialogues.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ export class Dialogues {
126126
this.pickItem(JournalPageType.note).then(selected => {
127127
resolve(selected);
128128
});
129-
} else if (isNotNullOrUndefined(selected.pickItem) && selected.pickItem === JournalPageType.attachement) {
130-
this.pickItem(JournalPageType.attachement).then(selected => {
129+
} else if (isNotNullOrUndefined(selected.pickItem) && selected.pickItem === JournalPageType.attachment) {
130+
this.pickItem(JournalPageType.attachment).then(selected => {
131131
resolve(selected);
132132
});
133133
} else {
@@ -457,7 +457,7 @@ function addItemToPickList(entries: FileEntry[], input: TimedQuickPick, type: Jo
457457
else { displayName = `$(circle-large-filled) ${displayName}`; break; }
458458
}
459459
case JournalPageType.entry: displayName = `$(clock) ${displayName}`; break;
460-
case JournalPageType.attachement: displayName = `$(package) ${displayName}`; break;
460+
case JournalPageType.attachment: displayName = `$(package) ${displayName}`; break;
461461
}
462462

463463

0 commit comments

Comments
 (0)