Code review of v0.1.3..main — 2026-09-28
A review of everything merged after the v0.1.3 tag: 187 commits, about
21,000 lines changed under lib/. It ran in three passes. The first read
the riskiest logic (sync, WebDAV, files, watcher, template counters, index
rebuild, wikilink suggester); its fixes are in PR #487. The second split
the rest of the diff into four areas (editor, UI and frontmatter, spelling
/ templates / watcher / todo, sync and storage), each read by a separate
reviewer; its fixes are in PR #489. The third read main after #489 in
seven areas, translations and user docs among them.
Line numbers refer to main at 0771606e. Verified means the wrong
result was reproduced by running the code (a throwaway probe or test);
read means it was traced through the code, not run.
First pass — status
| # | Finding | Status |
|---|---|---|
| 1 | Weak ETag: If-Match dropped by the client while the planner and the merge guard still count on it, so a write on Nextcloud could overwrite an edit made during the run |
Fixed, 1c81b470 |
| 2 | Body timeout bounded the whole transfer, so a large file on a slow link never synced | Fixed, 8ffdaff0 |
| 3 | Wikilink suggester wrote the bare name even when another note shares it | Fixed, 45a13a34 |
| 4 | A resumed or switched library opened empty on an index with no rows (never indexed here, or rebuilt after corruption) | Fixed, 40885eff |
| 5 | Counter reservation (file I/O) ran outside the creation flows’ guard | Fixed, 8fb64e50 |
| 6 | A counters file the OS refused to read was taken for empty and rewritten, wiping other counters | Fixed, 73b425be |
| 7 | Stale-temp sweep walks the whole library, .history/ included, on every open |
Open — the periodic rescan already walks the tree every 5 minutes; reducing it is a design choice |
| 8 | Index warm-up awaited on the open path (was overlapped, #315) | Open — after fix 4 the first index query is needed right away; not changed without a measurement |
| 9 | Counter reservation copied in two flows | Fixed with 5 (CounterStore.reserve) |
| 10 | source_view.dart is 6,123 lines with 13 classes, against the “no god classes” rule |
Deferred on purpose |
Known limit left by fix 3: with Notes.md at the root and
Archive/Notes.md, no target names the root note alone, because the
resolver matches any path ending in what is written. Making an exact path
win is a change to link semantics.
Second pass — findings
Ordered by severity inside each group.
Data loss or a corrupted note
- Frontmatter: a text value ending in
:is written unquoted.lib/src/frontmatter/typed_fields.dart:193—_needsQuoteschecks': 'but not a trailing:. SettingtitletoTodo:writestitle: Todo:; the block stops parsing and every field turns into the error panel. Verified. - Frontmatter: list items holding a comma are split on rewrite.
typed_fields.dart:171,ui/frontmatter_field_dialog.dart:79,ui/frontmatter_fields.dart:328— items are emitted as plain flow scalars and only their first character is checked. Fromauthors: ["Doe, J", "Roe, K", x], deletingxwritesauthors: [Doe, J, Roe, K]: four items. The+dialog joins on,and splits on,, the same fault. Verified. - Frontmatter: editing a multi-line value corrupts it.
typed_fields.dart:186withedit.dart:107—description: |over two lines is saved as"line one+ newline +line two"at column 0: the newline is lost, and the next edit of the key leavesline two"behind and the block no longer parses. Verified. - Frontmatter: editing a field whose key is quoted adds a duplicate.
typed_fields.dart:83withedit.dart:104—"due date": 2026-01-01is shown asdue date;_entryRangelooks for the unquoted text, finds nothing and appendsdue date: …; the block fails with “Duplicate mapping key”. Verified. - Frontmatter: a list edit changes items nobody touched.
typed_fields.dart:116-119, 146-153, 171— items round-trip as text:ids: [1, 2, 3]minus2becomes["1", "3"]; booleans and dates are quoted too; nested items are dropped ([{name: A}, B]minusBbecomes[]); deleting one of two equal chips removes both (frontmatter_fields.dart:332). Read. - Invalid UTF-8 is rewritten as U+FFFD by a save and by bulk replace.
lib/src/markdown/note_load.dart:62,70andlib/src/search/replace.dart:386,411— both now decode withallowMalformedand write the whole text back. A Latin-1caffè(byte0xE8) becomesEF BF BDon disk after one keystroke elsewhere in the note, or after a Replace All that matched another word; v0.1.3 refused such a file instead. History keeps the raw bytes only when it is on. One cause, two places. Read. - A local
.niman/*.jsonthat does not parse is still uploaded.lib/src/sync/sync_engine.dart:1385— the #336 guard covers the download only. A hand-editedsettings.jsonwith a syntax error goes up over the good remote copy, and.niman/has no history. Read. - Rename in a case-sensitive Windows folder can replace another note.
lib/src/core/files.dart:347—_excludedEntryreturns early onp.equals, which ignores case on Windows. In a folder made case-sensitive (WSL,fsutil) holdingA.mdanda.md, renamingA.mdtoapicksa.md, and the rename replaces it. Rare setup, data loss. Read. - The merge guard reads the old row’s
remoteUnverified.sync_engine.dart:1257— the row describes the remote version before the change being merged. On a server without ETags, a row recorded as verified skips the hash even when the current listing is in the same second as a second same-size rewrite, which the merge then overwrites (it survives only in the other device’s history). Read.
Certificates and credentials
- The sync settings screen keeps a forgotten certificate trust in
memory.
lib/src/ui/sync/sync_settings_screen.dart:229with:304,:362—_forgetCertificateand_disconnectdo not clear_trustedFingerprint, and_retestprefers it to the stored value. After “Forget certificate” a retest trusts the old certificate, sends Basic auth to it and reports OK while the stored value is null; after disconnect, Test + Save stores it again without the confirmation dialog. Read. - A redirect target’s certificate is shown as the base host’s, and
trusting it never works.
lib/src/sync/webdav/webdav_client.dart:194-202,:750— the callback records the refused fingerprint for any host, the failure namesbaseUrl.host, and the callback accepts it only forbaseUrl.host. A validnas.localredirecting to a self-signednas.lan:5006prompts fornas.localwithnas.lan’s fingerprint, forever. Read.
Crashes and behaviour that is plainly wrong
- The template checker crashes on
{{{title}}}.lib/src/templates/checker.dart:152—_checkBraces(source, 0, 1)sees{{at 0 and callssubstring(2, 1):RangeError. It runs in aTimer, so the error is uncaught and the previous error list stays on screen. Verified. - Desktop reminders fire again on every reconcile for an hour.
lib/src/todo/reminders.dart:426withtodo/reminder_backend_desktop.dart:75-87— scheduling never checks_fired; a reminder fired at 10:00 is still insidereminderGraceat 10:20 andfiresOverdue, so any todo edit or focus regain notifies again, until 11:00. Read. - Ticking a task inside a quote or callout in the read view does
nothing (regression).
lib/src/ui/note_view.dart:1530—taskBoxOffsetruns on the raw line and cannot get past>; the old code used the tokenizer’s box.> - [ ] tasknever ticks. Verified. - The wikilink panel opens whenever the caret enters an existing link,
and takes keys.
lib/src/markdown/render/source_view.dart:3697,:3985— arrowing down ontoSee [[Project plan]]lands inside it and the next Down moves the panel instead of the caret; Enter or Tab there writes[[Project plan]]ect plan]]. Read. - Enter or Tab can accept a stale suggestion of the wrong kind.
source_view.dart:3763,:3868-3891— the old rows stay while the new query loads.[[Pro, then#, then Tab before the headings arrive writes[[Projects/Plan]]and drops the#;[[A#changed to[[B#can insert one of A’s headings into B’s link. Read. - Wrapped table rows break at the wrong places when a cell has hidden
marks.
lib/src/markdown/render/live_tables.dart:384— line boundaries come back in visible-text positions and are added to a source offset, so each break shifts by the hidden characters before it: words split mid-word and the last piece can spill into the next column. Verified. - Setext headings are detected where they are not.
lib/src/markdown/block_scanner.dart:856,:891—---under a table becomes a one-line heading instead of a rule;> quote+lazy+---becomes a heading inside the quote;one+two+---makes onlytwothe heading, while the export makesone twoone<h2>. Verified. - The checklist cascade does nothing below a quote’s first line.
lib/src/markdown/task_cascade.dart:36— a quoted list is one block, soparent.startLine != lineempties the branch for every later item; ticking> - [ ] bleaves> - [ ] b1unticked. Verified. - The template checker misses what the engine leaves unrendered.
checker.dart:299,:304-315againsttemplates/engine.dart:373-382,:413-434—{{title|+1d}},{{date|upper|+1d}},{{title|}},{{counter}}with no name and{{cursor|upper}}all check clean and all stay standing in the note. Verified. - The caret is drawn in the wrong cell on a wrapped row’s separator.
source_view.dart:2460— an offset on|or its padding matches no piece and falls back to the first one; Right from the end of cell 1 shows the caret in cell 1 for two presses. Read. - A tab’s “missing” mark is cleared by an unrelated removal.
lib/src/workspace/workspace.dart:340-345—withMissingtakes the set it gets as complete, but each re-index sends only its own removals: delete A outside the app, then B, and A’s tab no longer shows missing. Read. - Changes made while the watcher restarts are lost until the periodic
rescan.
lib/src/library/file_watcher.dart:187-222— the stream is resubscribed after 1–30 s with no resync of the root, so a save in that window waits up to five minutes. Read. - Switching dictionaries during a spelling pass caches false
“correct” verdicts.
lib/src/spellcheck/editor_spell_check.dart:138-140,:363-364,:518— the running pass keeps the disposed checker, whoseisCorrectanswers true, into the new cache, and nothing clears it when the new engine arrives. Read. - A spelling pass can wait on a superseded load and report the note
clean.
editor_spell_check.dart:517— the panel opens while the first load runs,setDictionariesstarts another, the pass’sengineReadyreturns early and_checkeris still null. Short window. Read. sanitizeNamecan end a name with a dot or a space.lib/src/core/files.dart:56,:274-276—_trailingDotsneeds two dots, and truncation to 200 bytes runs after the trim.Draft.staysDraft.; Windows createsDraftwhile the index holdsDraft., and a folder ending in a space cannot hold notes there. Folders only (notes get.md). Verified (the output).- An escaped pipe at a row’s end is taken for its edge.
live_tables.dart:568—| a | b \|isb \in live mode andb \|in the table model, so widths and edits disagree. Rare. Read.
Lower priority
- Saving a frontmatter field rewrites untouched values in normal form:
1.10→1.1,007→7, and a timestamp with an offset becomes UTC (typed_fields.dart:104, 111, 139). trashFileName/trashDirName(files.dart:487,:505) check only an entry of the same kind: trashing a fileXwhile.trash/Xis a folder fails the delete (nothing lost).- The sync URL field’s listener (
sync_settings_screen.dart:77) fires on a caret move too, so tapping the field drops a loaded trust — the safe direction; the certificate is simply asked again.
Second pass — status
Fixed on fix/review-pass2 (2026-09-29), one commit per fix, each with a
test that failed on the code before it. Hashes are that branch’s.
| # | Status |
|---|---|
| 1 | Fixed, c8f20819 — quoting is decided by reading the plain form back with the parser |
| 2 | Fixed, c8f20819 (the rewrite) and 851c7079 (the list editor reads items as a flow list) |
| 3 | Fixed, c8f20819 (a line break is escaped) and ae45fd29 (an entry’s lines come from the parser’s spans) |
| 4 | Fixed, ae45fd29 |
| 5 | Fixed, 851c7079 — items keep their own YAML, a chip is deleted by its place, a list a chip row cannot show has no row |
| 6 | Fixed, c99369f1 — a byte outside UTF-8 is read as its Windows-1252 character on every path (decided with the user: saving writes UTF-8) |
| 7 | Fixed, 632fa831 |
| 8 | Fixed, 1b0f5760 |
| 9 | Fixed, 0c915947. Left: the server clock is the one at the check, not at the download |
| 10 | Fixed, d5ac9893 |
| 11 | Fixed, f84d4aef — a refusal on another host names that host and offers nothing to trust |
| 12 | Fixed, 196a1df9 |
| 13 | Fixed, 2ba8b38e. Left: the “already shown” record lasts while the app runs, so a restart within the grace hour shows it again |
| 14 | Fixed, 065a241e — one way to find a box on a line, quotes included |
| 15 | Fixed, 9efa5f2f (only typing opens the panel) and 9f71580a (a completion replaces the whole target) |
| 16 | Fixed, 3c590983 |
| 17 | Fixed, 74d889c9 |
| 18 | Fixed, 22a8a56f — all three cases, one + two + --- included. Left: _quoteDepthAfter keeps a quote open over a lazy line, and a list item swallows a --- at the margin |
| 19 | Fixed, 5229f589 |
| 20 | Fixed, d1622d13 — the engine’s filter rules are functions the checker calls. Left: {{include:A\|B}} is reported wrongly and {{include:}} not at all (the rule is in includes.dart); a huge move count still throws when a note is made |
| 21 | Fixed, 080b7ae7; and 7297ce98, found alongside: in source mode a wide table’s caret past its first piece was not drawn |
| 22 | Fixed, 94dd279c |
| 23 | Fixed, 53886af4. Left: the sync queue gets no hints for what changed while the watch was down; a stream error still triggers no rescan |
| 24, 25 | Fixed together, 980e91b3 |
| 26 | Fixed, d6f78071 |
| 27 | Fixed, 89d05651 — live mode uses the table model’s splitter. Left: the read view’s _tableRows still has its own |
| Lower priority | The normalization on save: fixed with 5 (851c7079). The trash names: fixed, 670b2c90 (and the same fault in uniqueFolderName). The URL listener: left, it fails safe |
Found on the way and fixed apart: two export tests that could not run on
a Windows disk (d454132b). Still open: a migration test’s teardown on
Windows (database_migration_test.dart, the database left open).
Checked and found sound
Switch-during-save ordering (#334), the unsaved-notes tracker, saving
before a library switch or forget, the drop service, the tree sort cache,
the replace preview, template_hint, frontmatter head-range math with
CRLF; the certificate column migration, the redirect credential rule, the
certificate callback never landing on a shared client, crash reports kept
out of the library (#383); hunspell over FFI released on dispose, DST in
the date filters, the watcher’s batch caps; a frontmatter fence never read
as a setext underline, the scanner’s incremental rebuild over both setext
lines, checklist lines in fences never ticked, one undo step per cascade,
the shared LineState.initial, surrogate pairs in the tag masker.
Third pass — findings
v0.1.3..main at 327a3008 (PR #489 merged), read in seven areas: the
source editor, Markdown parsing and links, sync and the updater, library
and files, templates / todo / frontmatter, the app shell, translations and
user docs. Nothing listed above — fixed or left open — is repeated.
Read as above; plausible means traced but not certain the failing
path is reached. Line numbers are at 327a3008.
Broken behaviour
- Windows: the caption buttons’ hit areas outlive the title bar.
ui/title_bar.dart:260withwindows/runner/flutter_window.cpp:297—_WindowButtonsStatereports its rectangles and never withdraws them. Below 600 px the narrow layout draws noAppTitleBar, but the runner still answersHTCLOSE/HTMAXBUTTON/HTMINBUTTONat the old right edge: a click on an app-bar action there closes, maximizes or minimizes the window. Nothing sets a minimum window width. Read. - A relative Markdown link no longer resolves.
links/resolver.dart:282— the #330 change keeps the one-candidate shortcut for bare stems only, and nothing normalizes..or a leading/.[x](../Notes/a.md)fromSub/b.mdis unresolved even whena.mdis the only one; the indexer’sresolveBatchshares the tail, so these links also leave the link index. Read. - The index connection leaks when its first query fails for another
reason than damage.
library/library_state.dart:1612—_openIndexrethrows before_indexDbis set, so_teardownfinds nothing to close. A locked file or a full disk leaves the connection and its isolate open; on Windows the file stays locked, and every retry adds one. Read. - The spell-check rows vanish after a dictionary is picked.
ui/settings_editor.dart:695—setDictionariesrestarts the engines,availableis false until the load lands, and the screen listens to nothing, so the switch and the dictionary rows stay gone until it is reopened. Read. - A wrapped table row anchors handles, the suggester and template hints
on its first piece.
markdown/render/source_view.dart:4331(_caretRectAt) and:2316(_templateSpanRect) still use_paragraphAt(line), which for a wrapped live-table row is piece 0; the piece-awarecaretRectfix missed them. Read. - A desktop reminder shows again after its task is edited.
todo/reminder_backend_desktop.dart:88— the “already shown” record is keyed by the reminder id, a hash of the whole description; changing the text (ordue:) inside the grace hour makes a new id and a second notification. Read. [[Aux]]creates_Aux.md, which the link never finds.core/files.dart:292—sanitizeNameprefixes a Windows device stem on every platform (deliberately), but the resolver does not apply the same mapping, so the missing-note offer repeats and makes_Aux_1.md. Read.- A stalled download can throw out of the sync run.
sync/webdav/webdav_client.dart:370withsync/sync_engine.dart:1472— on a body stall the client gives up whilepipemay still hold the sink;IOSink.close()on a bound sink throws synchronously, so.catchErrornever attaches and aStateErrorreplaces the retryable failure. Nothing records the error, and on Windows the temp file stays open. Plausible. - Completing a book form overwrites the number typed after it.
source_view.dart:3934— acceptingpage=reopens the panel for the same book, and Enter or Tab after12writespage=again. Read. - “Close library” in Settings does not save the open notes.
ui/settings_maintenance.dart:212, andui/shell_home_widgets.dart:177(a widget or launch pointing at another library) — the #351 save ran only on the switch screen. Plausible. - “Rebuild index” can run twice at once.
ui/settings_maintenance.dart:68— no busy state; a second tap tears down and deletes the index the first is opening, and on Windows the failed delete closes the library. Plausible. - Replace All rewrites a file the editor refuses as binary.
search/replace.dart:319,:387— nolooksBinarycheck; a UTF-16 note is rewritten as mojibake. Plausible. - A frontmatter edit drops the entry’s indentation, anchor and comment.
frontmatter/edit.dart:55— an indented block stops parsing after an edit,tags: &t [a, b]loses&tand its alias breaks the panel,title: A # keeploses the comment. Read. - The suggester stays open over a swapped buffer.
source_view.dart:765—didUpdateWidgetdoes not close it, and a completion writes at the old offsets. Plausible. - The suggester opens inside code.
source_view.dart:3736—[[in a fence or inline code offers notes and inserts one. Read. - A note name holding
#,|or]]is inserted as a broken link.links/suggester.dart:255—C# tipsis written[[C# tips]], read back as targetc. Read. - Reading positions drop another device’s entry.
reading/reading_positions.dart:145— pruning removes a book whose file has not arrived yet, and the next sync deletes the position. Plausible. - The Windows rename fallback renames again, with no timeout.
library/note_writer.dart:563—copyFileOverends instaged.rename(abs), the call it exists to avoid (#103). Plausible. - A merge the guard stops leaves the fetched temp behind.
sync/sync_engine.dart:1744,:1843—_ChangedDuringSyncafter_fetch;.x.md.niman-tmp-sync-*stays until the next open. Read. - An interrupted update download keeps the installer’s name.
update/update_service.dart(downloadAsset) — only a digest mismatch deletes the file, and the body has no stall timeout. Read. - Cancelling the name dialog burns a counter.
ui/shell_template_flow.dart:164— the reservation now comes before the dialog. Read. - A reminder whose notification failed is never retried.
reminder_backend_desktop.dart:96— shown is recorded beforeshow. Plausible. - A stray
}}is reported as a template error.templates/checker.dart:178— the engine keeps it as text; LaTeX and JSON in a template get a problem mark. Read. - The typewriter row highlight ignores a wrapped row’s piece shift.
source_view.dart:6064. Cosmetic. Read. - The Notion import’s size budget trusts declared sizes.
import/notion.dart:88— entries are inflated withreadBytes()after a check on what the archive claims. Plausible.
Hot paths
- Live tables measure every word of the table on each revision.
markdown/render/live_tables.dart:586—_leastWidthruns one painter per word even when the table fits, and the cache clears on every edit and every reveal change. - Headings are read from disk on every key after
#.links/suggester.dart:285— the whole target note is read, decoded and outlined on the UI isolate per keystroke. - Every line indented four spaces is a definition candidate.
markdown/block_parser.dart:739(mayHold) withmarkdown/source_styler.dart:222— an edit to such a line rescans all of them: O(n) per keystroke in code or verse. - The suggester’s
%q%query sorts every match before its limit.links/suggester.dart:211— a computedORDER BYdefeats the cap. looksBinarywalks the bytes twice in Dart before the native decode.markdown/note_bytes.dart:63— and one U+0000 refuses a note the index and replace accept.- The installer is hashed on the UI isolate.
update/update_service.dart(_verifyDownload) withcore/files.dart:239— a 100 MB file, read synchronously. - A case-only rename lists the whole folder on the UI isolate (Windows,
macOS).
core/files.dart:356.
Translations and user docs
- 43 keys added since v0.1.3 are English in the locales. The properties panel, the source font, the certificate dialog in all 36; ten failure snackbars in all but Italian.
- New UI text is written in English in the code. The wikilink panel
(
wikilink_panel.dart:119,:364), the book-place hints (suggester.dart:302), the template checker’s messages (checker.dart:169,:463). templateProblemssays “1 …” for 21, 31, … inuk,lt,be;hr,bs,srtest onlycount == 1.- The docs name settings sections that no longer exist. “Settings →
About” (
getting-started.md:43,settings.md:168, theCHANGELOG.mdheader shown in the app), “Settings → Library” (organization.md:41,:414), “Settings → Auto-empty trash” (organization.md:9). organization.md:86places the fields panel in the read pane and the live editor; it is above the note in both editors.- Fourteen string keys are never read (
settingsSectionAbout,settingsSectionLibrary,syncDoneSnack, …). historyOffsends the user to “Settings, Library”.- The sidebar tooltip hard-codes Ctrl+B, which can be rebound.
Left out: {{date:YYYY-'W'WW}} gives the calendar year beside an ISO week
at the turn of the year — true, but already so in v0.1.3.
Third pass — checked and found sound
Certificate pinning scope, the redirect credential rule, weak-ETag
guarding, the scheduler’s joined runs, the updater’s asset choice and
digest compare; the app-DB v32 migration, history sha checks, watcher
batching, pair-rename indexing; unsaved edits on a note switch, hunspell
handles, mounted guards in the shell diff; counter serialization, the
checker reading the engine’s own rules, Android alarms skipping overdue
ones; setext rework, cellRangesOf, the streaming decoder, embed_path’s
confinement; interpolation parameters across every locale.
Third pass — issues
Tracked as issues, to be fixed on fix/review-pass3:
| Issue | Findings |
|---|---|
| #490 | 1 |
| #491 | 2, 7, 16, 27, 29 |
| #492 | 3, 17, 18, 25, 32 |
| #493 | 4, 10, 11 |
| #494 | 5, 9, 14, 15, 24, 26 |
| #495 | 8, 19, 20, 31 |
| #496 | 12, 28, 30 |
| #497 | 6, 13, 21, 22, 23 |
| #498 | 33–40 |
Third pass — status
Fixed on fix/review-pass3 (2026-09-29), one commit per finding, each with a
test that failed on the code before it. Hashes are that branch’s.
| # | Status |
|---|---|
| 1 | Fixed, 01a3896d |
| 2 | Fixed, ba6d2e87 |
| 3 | Fixed, 740391a3 |
| 4 | Fixed, e3e2089a |
| 5 | Fixed, 9d9181b3 |
| 6 | Fixed, aad27780 |
| 7 | Fixed, b4b1f162 |
| 8 | Fixed, eda51ebb. Left: the escape needs a response whose socket cannot be detached (the test’s fake models one); with a plain HttpClient the pre-fix code passes |
| 9 | Fixed, ef0ae189 |
| 10 | Fixed, 8e44001d; the widget target’s blocked switch says so, d3ba103e |
| 11 | Fixed, 11ec0038 |
| 12 | Fixed, bd0304dc. Left: the indexer, export and the other readers still accept a file the loader refuses |
| 13 | Fixed, 7535e938 |
| 14 | Fixed, 2e9d6615 |
| 15 | Fixed, bf4bd391 |
| 16 | Fixed, e267184a |
| 17 | Fixed, 5aaa9b40. Left: a book deleted while the index has no row for it (never indexed here, or rebuilt) leaves its position behind, because nothing observes that deletion. No sound fix: on disk the entry looks the same as a position the sync brought for a book whose file has not arrived, and telling them apart would need a device-local record of the files this device knew, which the rebuild that causes the gap also loses. The entry is a few bytes, and it is dropped when the file is deleted with the index intact |
| 18 | Fixed, 9dc5b2c8; the staged file’s delete after the guarded rename has the same timeout, 31810e66 |
| 19 | Fixed, 7f8f3ef5 |
| 20 | Fixed, 627508be |
| 21 | Fixed, 65d1becf |
| 22 | Fixed, 5ddba0d8 |
| 23 | Fixed, 6c8827eb — the problem kind and its key went with it when the branch met #498 (f073ba12) |
| 24 | Fixed, 2f90c8a6 |
| 25 | Fixed, 987da2b3 and c45e42b9 — package:archive does not stop at the declared size, and on dart:io it hands its output to a sink only after inflating the whole stream, so the first fix refused after the allocation; the entry is now inflated in chunks that stop at the budget. An entry with no content is skipped again, d10949a9. Left: a symlink entry is inflated by ZipDecoder.decodeStream itself, before any budget applies |
| 26 | Fixed, 8c6c0266. Left: a revision still clears the measurements — an edit re-measures each table once, not per word |
| 27 | Fixed, ea1a5022. Left: the cache is keyed on the index’s revision, so headings are as of the last index run |
| 28 | Fixed, f32fcf44 |
| 29 | Fixed, 37a51774 |
| 30 | Fixed, 7ff51222. Left: the rule is the loader’s and the replace pass’s, not yet every reader’s |
| 31 | Fixed, f8390862 |
| 32 | Fixed, 2407d53c. Left: the lookup still walks the folder, now off the UI isolate — dart:io has no per-name call that returns the stored case |
| 33 | Fixed, 43d46e58 |
| 34 | Fixed, f073ba12 |
| 35 | Fixed, 9730ce2a |
| 36 | Fixed, 87da485e |
| 37 | Fixed, bbeffe78 |
| 38 | Fixed, 69fa6654 — with a test that fails when a contract key is read nowhere under lib/ |
| 39 | Fixed, c8841d9a |
| 40 | Fixed, 888a4f82 |
Crossings found while merging: #491’s readHeadings seam against #498’s
book-hints test (1a6c05ac), and #497’s removal of the stray-}} report
against #498’s externalization of the checker’s messages — the closingBraces
kind lost its only raiser and went with its key. The analyzer infos the
index-leak and reading fixes left are cleaned in bb86683f.
Third pass — verification
Each fix above was read again against its issue (2026-09-29): whether it reaches the cause, whether its test fails on the code before it, and what it breaks. What that found was fixed on top, one commit each, with a test that fails before it unless said otherwise.
| Found in | What was wrong | Fixed |
|---|---|---|
ba6d2e87 (2) |
.. was read from the library root, not the linking note; a leading / matched any path ending in it; a .. past the root collapsed into a bare name, which the one-candidate rule then resolved to an unrelated note (#330 again) |
a77812d5 — a link is read from the note it is written in; the indexer passes the note’s path |
ba6d2e87 (2) |
Relative links of a note that moves kept the edges resolved from its old folder | 486f68fd. Left: a note that links to a moved note by path keeps its edge until it is next indexed — the index keeps resolved edges, not link text |
| — (pre-existing) | _clean replaced two backslashes, so one \ never separated folders |
64eb642b |
| — (pre-existing) | The indexer took Markdown hrefs undecoded: My%20Note.md was never an edge |
a3a463a3 |
e267184a (16) |
Names the panel cannot write took slots before the cut to 50 | 78a78e9e |
ea1a5022 (27) |
Keys typed while a note was read each started another read | 3d8e6a7a |
aad27780 (6) |
Recorded by moment alone: two tasks at one moment silenced each other | e0bf2024 — per task and moment; an id new at a shown moment is the same task only when a shown id of that moment left the set |
5ddba0d8 (22) |
A slow show could be posted twice; one task’s success hid another’s failure | e05d8b7d |
65d1becf (21) |
A failed createNote still burnt the number (and the journal flow had the same fault) |
2687e372 |
7535e938 (13) |
The kept comment was read from the entry’s last line: a block list item’s comment moved onto the key, it's # c lost its comment |
2657491b — the comment is what follows the value’s parsed end |
987da2b3 (25) |
package:archive inflates a whole entry before handing it to a sink, so the budget refused after the allocation |
c45e42b9, d10949a9 (see 25) |
9dc5b2c8 (18) |
The staged file’s delete had no timeout | 31810e66 |
8e44001d (10) |
A widget target blocked by a failed save did nothing visible | d3ba103e |
eda51ebb (8) |
A non-IO error from the sink left the body copy running | ea107cc4; real-client tests in 45bf33a9 (they pass on the pre-fix code too: with a detachable socket the old code released the sink) |
f32fcf44 (28) |
An edit ending a footnote body dropped the indented lines below it that still cite, so the incremental state left a fresh scan | 9a83bb1d — compared with a fresh scan across edits |
7ff51222 (30) |
Deciding “binary” decoded the note once more; a NUL test named one rule and checked another | dad9944b, ee3cd1f6 |
8c6c0266 (26) |
A revision still re-measured every cell of a table that does not fit; rows measured before the tokens landed stayed stale; a caret move laid out every row | ce6b3314, f3fdb2c4, fca29134 |
2e9d6615 (14), bf4bd391 (15) |
Replace all and undo left the link panel over old offsets; the panel could open in code before a line’s tokens were read | 678784a8, e4324958, docs 4c3589b8 |
2407d53c (32) |
Its test wrote A.md then a.md, which a folder that folds case keeps as one file: it failed on Windows |
b2874220 |
43d46e58 (33) |
The certificate dialog said Niman accepts more than one certificate from the host, in all 36 locales; de monospace was “sans serif”; other words differed from each file’s own |
780c823a, 5cca44e9; four more slips in is, eu, fi, sq fixed with the first |
9730ce2a (35) |
lv, mk, sl kept count == 1; the replace summary did the same in ten languages |
1e175e84, 089903ad |
f073ba12 (34) |
A failed index rebuild showed the raw error | a40b207f |
69fa6654 (38) |
The unread-key test counted comments as reads | 00e9812e |
87da485e (36) |
“Settings → Library → Journal”, and the 0.1.3 changelog’s “Settings → Diagnostics” (already wrong at the tag) | 3a42c3d1 |
Not fixed: a book deleted while the index has no row for it (see 17); a symlink entry inflated by the zip decoder itself (see 25). The new locale sentences have had no native speaker’s review.