feat(capture): hierarchical capture/* tags + schema-ensure + option (d) + path-collision fix (0.3.15-rc.8) - #132
Merged
Merged
Conversation
…d) + path-collision fix (0.3.15-rc.8) Closes notes#126 (reshaped scope per Aaron's design conversation). Builds on rc.7's `quickPath()` pre-fill (PR #130) with the confirmed `capture/*` classification model plus three reviewer follow-ups bundled. ## What landed **Hierarchical capture tags (notes#126 reshape):** - New file `src/lib/vault/schema.ts` declaring `NOTES_REQUIRED_SCHEMA`: - `capture` (parent, no parent_names) - `capture/text` (parent_names: ["capture"]) - `capture/voice` (parent_names: ["capture"]) First instance of patterns#57 (surface-declares-required-schema). - `DEFAULT_TAG_ROLES.captureText` → `"capture/text"`, `DEFAULT_TAG_ROLES.captureVoice` → `"capture/voice"`. Existing vaults preserve their stored values — no force-migrate. **Idempotent schema-ensure:** - New `VaultClient.updateTag(name, { description, parent_names })` — PUT /api/tags/:name, field-merged vault-side. - New `src/lib/vault/schema-ensure.ts` exposes `ensureNotesSchema()`. Per-vault per-session module-level ref guard so repeated captures don't hammer the vault. Failure rolls back the guard so next capture retries; success is silent (it's plumbing, not user- actionable — notes#129 will add the audit UI). - Capture wires `ensureNotesSchema(activeVault.id, client)` as a fire-and-forget call inside save(). Doesn't block the save or surface failures. **Option (d) bundled** (closed PR #131 absorbed here): - `pathOverride.trim() || generatedPathRef.current` resolves to the mount-time generated value when input is empty. Never falls back to vault-picks. The rc.6 `memoPath()` audio-only fallback is unreachable and dropped — one canonical Notes-side rule. - Placeholder text updated: "(blank → vault picks)" → "(blank → uses generated path)". **Path-collision fix** (raised in #130 review): - `reset()` after successful save now regenerates `quickPath()` and updates the input — but only when the operator hasn't manually edited (`pathEditedRef`). User-typed paths survive across captures on the same mount; auto-generated paths roll forward in time. ## Behavior changes - Text captures: default tag is `capture/text` instead of `quick`. - Voice captures: default tag is `capture/voice` instead of `voice`. - Existing vaults with stored values are NOT migrated — their `quick`/`voice` choices stand. Schema-ensure still writes the parent + parent_names rows so hierarchy queries work either way. - Audio captures with cleared path no longer go to `Memos/` — they land under `Notes/` like text captures (single canonical rule). - Two captures within the same wall-clock second no longer collide on the auto-generated path. ## Tests - 6 new in `schema-ensure.test.ts`: declaration-order PUTs, parent-before-children, per-session per-vault idempotence, multi-vault independence, retry-after-failure, swallow-doesn't- throw. - 2 new in `Capture.test.tsx`: regen-on-reset-when-unedited (with fake-timer `setSystemTime` + shouldAdvanceTime for waitFor), preserve-user-edit-across-reset. - 4 existing tests flipped for the default rename + option (d): - `"quick"`/`"voice"` → `"capture/text"`/`"capture/voice"` in tag assertions. - "Empty path → omits path" → "Empty path reverts to generated". - "Audio-only cleared path → memoPath" → "Audio-only cleared path → generated path". - `Capture.test.tsx` adds `vi.mock("@/lib/vault/schema-ensure")` so capture tests don't hit the real PUT (covered by the dedicated schema-ensure tests). ## Not in scope - Full Settings audit UI + connect-time banner → notes#129. - Per-vault path-template customization → notes#128. Gates: 82 test files / 749 tests pass (+8 from rc.7's 741). typecheck clean, lint clean, build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(capture): hierarchical capture/* tags + schema-ensure + option (d) + path-collision fix (0.3.15-rc.8)
Closes notes#126 (reshaped scope per Aaron's design conversation).
Builds on rc.7's
quickPath()pre-fill (PR #130) with the confirmedcapture/*classification model plus three reviewer follow-ups bundled.What landed
Hierarchical capture tags (notes#126 reshape):
src/lib/vault/schema.tsdeclaringNOTES_REQUIRED_SCHEMA:capture(parent, no parent_names)capture/text(parent_names: ["capture"])capture/voice(parent_names: ["capture"])First instance of patterns#57 (surface-declares-required-schema).
DEFAULT_TAG_ROLES.captureText→"capture/text",DEFAULT_TAG_ROLES.captureVoice→"capture/voice". Existingvaults preserve their stored values — no force-migrate.
Idempotent schema-ensure:
VaultClient.updateTag(name, { description, parent_names })—PUT /api/tags/:name, field-merged vault-side.
src/lib/vault/schema-ensure.tsexposesensureNotesSchema().Per-vault per-session module-level ref guard so repeated captures
don't hammer the vault. Failure rolls back the guard so next
capture retries; success is silent (it's plumbing, not user-
actionable — notes#129 will add the audit UI).
ensureNotesSchema(activeVault.id, client)as afire-and-forget call inside save(). Doesn't block the save or
surface failures.
Option (d) bundled (closed PR #131 absorbed here):
pathOverride.trim() || generatedPathRef.currentresolves tothe mount-time generated value when input is empty. Never falls
back to vault-picks. The rc.6
memoPath()audio-only fallbackis unreachable and dropped — one canonical Notes-side rule.
uses generated path)".
Path-collision fix (raised in #130 review):
reset()after successful save now regeneratesquickPath()andupdates the input — but only when the operator hasn't manually
edited (
pathEditedRef). User-typed paths survive across captureson the same mount; auto-generated paths roll forward in time.
Behavior changes
capture/textinstead ofquick.capture/voiceinstead ofvoice.quick/voicechoices stand. Schema-ensure still writes theparent + parent_names rows so hierarchy queries work either way.
Memos/— theyland under
Notes/like text captures (single canonical rule).on the auto-generated path.
Tests
schema-ensure.test.ts: declaration-order PUTs,parent-before-children, per-session per-vault idempotence,
multi-vault independence, retry-after-failure, swallow-doesn't-
throw.
Capture.test.tsx: regen-on-reset-when-unedited (withfake-timer
setSystemTime+ shouldAdvanceTime for waitFor),preserve-user-edit-across-reset.
"quick"/"voice"→"capture/text"/"capture/voice"in tagassertions.
path → generated path".
Capture.test.tsxaddsvi.mock("@/lib/vault/schema-ensure")socapture tests don't hit the real PUT (covered by the dedicated
schema-ensure tests).
Not in scope
Gates: 82 test files / 749 tests pass (+8 from rc.7's 741). typecheck
clean, lint clean, build green.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com