syncAll() ran each enabled type's sync in sequence with no per-type
error handling, so one type throwing (e.g. its vault folder missing)
aborted the whole run and skipped libraryLastSync -- every other
enabled type silently never synced. Each type's runSync is now wrapped
individually: failures are logged (and surfaced via Notice unless
quiet) and the loop continues; libraryLastSync still updates after the
loop regardless.
Also: dry-run sync previews now route spec-level flip notifications
(the "chapter/issue out" Notices) through the log instead of a real
Notice, since a dry run never actually writes the change it's
describing.
- comic/manga sync(): a note's first-ever observed issue/chapter number
(no prior latest_issue/latest_chapter) now seeds the baseline without
flipping read_status or notifying. Previously `prev === null` counted
as "newer", so a Read note with no stored baseline flipped to Unread
on its very first real sync. Flips now require a prior value AND a
strictly greater candidate AND read_status Read, mirroring the
watchlist build.ts `prevLast &&` guard.
- manga rss/last_chapter_date: quotedOrNull renders an unset value as
the literal `null` token; on the next parse that string came back
truthy, so an unset rss field was fetched as the URL "null" and
isActive treated it as "has rss -> active" forever. Sentinel is now
stripped back to '' everywhere it's read from frontmatter.
- manga resolve(): best-effort MangaDex id lookup by title (unique-exact
match against attributes.title + altTitles) runs alongside the
existing MAL lookup when the note has no mangadex_id yet. Any failure
(no match, ambiguous, network error) is logged and skipped, leaving
the mal_id patch intact.
isActive() for book/game/comic only checked for a resolved id, so a
note that had run resolve() but never a real sync() (no read_status/
play_status/status field yet) was treated as static and never
enriched on subsequent runs. Now also requires the status-analog
field canonical render always writes; missing it means sync() hasn't
actually produced output yet.
- render **Rating:** line in manga/book/game/comic body (after read/play
status, gated on rating != '0' + stars non-empty), matching watchlist's
render.ts convention
- manga finish-flip now notifies user (final chapters out), matching
chapter-flip's existing notify path
- game renderGame emits ## Links (Steam by appid, RAWG by url) — fixes
latent owned-heading strip where user-added Links sections silently
vanished since games never re-rendered the heading
- move duplicated deriveReadStatus/deriveRating/parseNumOrNull into
library/convert.ts, import across specs; zero behavior change
TMDB's origin_country returns ISO codes (KR), not full names, causing
a vault regression from South Korea to KR. buildRecord now prefers
production_countries[0].name, falling back to the raw origin_country
code only when absent.
TMDB also now returns vote_average at full precision (6.537), causing
mass diff noise against the vault's 1-decimal convention and spurious
rewrites on precision drift alone. tmdb_rating is now rounded to 1
decimal place.
Loki fixture updated to realistic TMDB shapes (ISO country code +
production_countries) to catch this regression going forward.
renderNote wiped any ## heading not owned by the renderer (Synopsis,
Cast, Links, My Notes) on every sync, destroying user content like
## Collection graph-link sections. extractCustomSections now pulls
non-owned sections out of the existing body before rewrite, and
renderNote re-emits them verbatim, in original order, between Links
and My Notes.
package.json author/description now match manifest.json, release
workflow plugin name and repo-automation.config.json repo ref no
longer point at the upstream project.
- SyncDeps.listNotes now returns paths only; new readNote(path) reads
content immediately before parse/diff/write per note, closing the
window where an edit made between the initial scan and a later write
got silently overwritten. resolveMissingIds updated to match.
- WatchlistController gains a syncing flag shared by syncNow and
resolveMissingIds so overlapping invocations short-circuit instead
of racing on the same vault notes.
- main.ts registers cleanup for the 30s startup catch-up setTimeout so
it doesn't fire after unload.
- syncNow takes a quiet flag (used by the scheduled catch-up path) so
zero-change scheduled syncs don't spam a Notice; manual commands are
unaffected.
quotedOrNull renders an empty notion_url as the literal string `null`.
buildRecord read that back verbatim on the next sync pass and re-quoted
it as "null", breaking diff-on-write (every re-sync of a note with no
notion_url appeared changed). Treat the `null` sentinel as empty when
reading prev frontmatter.
Pop up modal when legacy keys are found in data.json
The keys are hidden by default but the user can show them or just copy them. The old plaintext keys should be deleted for safety reasons