- Template fetch retired: Create/Download/Paste no longer fetch or write the
legacy PluginFiles/Templates + Handlebars sets (nothing reads them — every
note write is frontmatter via writeElement). includeLegacyTemplates defaults
false; the two Create* template commands kept as a reversible margin.
- Read-only keys need no PIN: WorldDownloadModal detects ow_r_ / demo keys and
greys out the PIN field; validation accepts a blank PIN and sends "". Write
keys and private-world write keys still require the 4-digit PIN (unit-checked:
read keys skip, write keys don't — no auth hole). pin type: number -> string.
- Docs: README read-key note + all commands current; release notes reversioned
2.4.0 -> 3.0.0 (the frontmatter era: format change + folder bridge + span
retirement = a major).
Gate: tsc clean, build clean, 60/60 tests + pinless-detection unit check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- ValidateWorld: skip frontmatter notes instead of showing false 'missing
Id/Name' errors on migrated worlds (the span linter can't judge them; a
frontmatter-aware validator is future work). The user-facing lie is the
actual harm; this removes it without a full rewrite.
- README: frontmatter format + clickable [[wikilink]] fields, the Migrate
command + note-format/migration section, folder Export/Import commands,
folder-bridge in the ecosystem section. Voice preserved (Captain's), facts
brought to 2.4.0.
- 2.4.0 release-notes draft (the GitHub release body + Captain's gate).
Gate: tsc clean, build clean, 60/60 tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The download id->name map sanitizes wikilink targets with WorldService's
sanitizeFileName (strips control chars + trailing dot/space, 'unnamed'
fallback), but writeElement named the actual file with a WEAKER regex
(colon/slash class only). A name with a trailing dot or control char would
name the file one way and the [[link]] another -> dangling link. Both now
use sanitizeFileName, so the wikilink always matches the on-disk basename.
Also files the download-resolver brief.
Gate: tsc clean, build clean, npm test 60/60 (orchestrator hands).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CORE-SMOKE.md: the 10-min pass. Records what Skeld verified by execution
(migration correctness on Captain's real MessWorld run — 22/22, 0 residue;
extension-field round-trip incl. nested objects, both via the plugin's own
transform) vs the human-only pass (download/save/migrate/folder-bridge).
atlas-handoff-design.md: why the plugin can't auto-detect the Atlas root
(browser FS-Access sandbox, opaque IDB handles, no shared path — confirmed
against Atlas source in the Forge), and that a destination picker is the
chosen smoothing (executor building it).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two additive commands making the plugin the folder format's fourth
implementation (after Atlas, Magelet, azgaar-converter):
- Export as OnlyWorlds folder: walk a world's notes -> conformant folder
under OW-folder-export/ (vault-internal). world.json id+name, spatial
bucketing, body type/local_updated_at/created_at stamp, <slug>--<tail>
filenames. [[wikilink]] -> ow://type/uuid where names resolve.
- Import OnlyWorlds folder: read a world folder placed in the vault, mint
frontmatter notes via Phase B's writeElement. ow:// -> [[wikilink]],
skip-existing-id safety, abort on world-id mismatch, verbatim extension
fields.
Pure serializers in vault/folder-format.ts (no Obsidian imports), 13 new
tests: conformance pins for all four Atlas-ingest facts + in-memory
round-trip (object equality on ids/links/extension keys/story). Phase B's
21 tests stay green. Smoke section G appended.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
readElement returns id OUTSIDE .fields; the Phase B guards in Export and Copy
returned parsed.fields bare, so on a fully-migrated world Upload World would
silently skip EVERY element (the sweep's if-no-id-continue) while reporting
success, and Copy World would emit id-less JSON that breaks paste dedupe.
Both guards now return { id, ...fields }. Smoke step 17 strengthened to check
the uploaded count and spot-check ids in copied JSON, so this class stays dead
at the user layer too.
Gate: tsc clean, build clean, npm test 21/21 (orchestrator hands).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mint authentic old-format span fixtures via the REAL writer (real handlebars lib
+ verbatim upstream templates + linkify helper + [[id]]->[[Name]] rewrite) rather
than hand-written strings, per the R9 addendum. test/span-writer.ts (minter, with
disclosure that template STRINGS are transcribed but the RENDER is genuine) +
test/migration-roundtrip.test.ts (5 cases: Character multi-links, Narrative
body->story, apostrophe/HTML-entity, comma-in-linked-name, extension-field
round-trip).
The writer-minted apostrophe fixture caught a REAL bug hand-written fixtures
missed: the migration path did NOT decode HTML entities, so a pre-2.2.2 escaped
note ("The Kid's Family") would migrate with the corruption preserved. Fixed:
element-transform.ts parseSpanNote now decodes via the pure decodeHtmlEntities
(matching SaveElement/ExportWorld). Propagates to every span-read path (migration,
readElement span fallback). 21/21 tests pass.
SMOKE-CHECKLIST.md: added the copy/throwaway-vault-first caution as the first line.
Also folds in a working-tree safety change to MigrateWorldCommand + main.ts that I
did NOT author (a teammate added it mid-flight): migration takes auto-sync's
markSelfWrite guard so a user with auto-sync on doesn't mass-PATCH migrated notes
to the server (which would break backup-based reversibility). Sound change; added
the null-guard (this.autoSync?.) it was missing so tsc passes. Flagged to lead.
Gate: tsc clean, esbuild build succeeds, npm test 21/21.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wake the dormant vault/element-file.ts write path and make YAML frontmatter
the note format for new writes, with read-tolerance for legacy span notes.
Pure logic (Obsidian-free, unit-tested under `npm test`):
- vault/element-transform.ts: frontmatter<->payload, span parse, span->frontmatter,
link normalization (single string / multi list, never comma-joined), extension-key
passthrough (atlas_/shadow_/x_), body<->description|story mapping, read-before-PATCH diff.
- test/element-transform.test.ts + tsconfig.test.json: 16 node:test cases incl. a real
span-format Character fixture. Wired to `npm test` (pretest compiles to test-dist/).
Wiring:
- element-file.ts: readElement preserves extension fields + maps body to story for
Narrative (R3/R5); frontmatter-first with legacy span fallback; writeElement round-trips
extension keys and respects count-suffixed folders.
- DownloadWorldCommand + CreateElementCommand: emit frontmatter via writeElement (no more
Handlebars element templates).
- NoteLinker: reworked onto metadataCache + SDK FIELD_SCHEMA; writes link IDs into
frontmatter (new FieldSelectionModal picks the field). NameChanger syncs frontmatter name.
- SaveElementCommand: read-before-PATCH (GET, diff, PATCH only changed fields) (R7).
- ExportWorldCommand + CopyWorldCommand: read frontmatter via readElement, span as fallback
(mixed worlds upload/copy correctly).
- MigrateWorldCommand: "Migrate world notes to frontmatter" — backup-first (abort on failure),
idempotent, end report modal (converted/skipped/failed).
Deferred (see docs report): PasteWorld still emits span; ValidateWorld is still a span linter
(diagnostic only, no writes). docs/SMOKE-CHECKLIST.md is the manual release gate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- All sync paths on /api/v2: Download World = /changes cursor walk (incremental
re-downloads, rewind detection, honest delete reporting); Upload World =
create/update sweep via /bulk + PATCH (server-only elements never deleted);
Save/auto-sync via shared V2Client. Legacy worldsync routes fully retired.
- Filename sanitization: Windows-illegal chars (':' etc.) no longer mangle
notes into NTFS streams; wikilinks use sanitized targets, names stay exact.
- Link safety: unresolvable links skip the whole field (server values kept,
surfaced in a toast); uuid-shaped wikilinks pass through as ids.
- Delete Element command: typed-name confirm -> server DELETE -> note to trash.
- Progress toasts for download/upload; README rewritten (v2 semantics +
ecosystem section).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>