notes

MVP ledger

Iterations: 47 (cap extended by the owner’s requests; current batch N17 and N28)

Current handover — 2026-09-29

The requested next two features are N17 backlinks (783c719) and N28 heading outline. Both are implemented and independently reviewed, with mutation tests and inspected desktop/phone light/dark screenshots. Focused results: backlinks 13/13, outline 36/36, preview 15/15, tags 53/53, pins-sync 54/54, wikilinks 47/47 and accessibility 57/57 in both engines. Final merge validation is the full 61-suite CI run on the combined PR. Next by priority is N29 callouts, then N30 recently deleted notes. N34 still needs the owner’s coordinated domain cutover.

Previous batch handover — 2026-09-29 (merged in PR #20)

The requested next four are implemented: N16 image upload (c8f2e48), N33 open tabs (0d1c7ce), N26 shared reader/Recent (9eb3878) and N27 tags. Each has focused Chromium/WebKit checks, a fresh independent review, mutation checks and inspected desktop/phone light/dark screenshots. N27 passes 53/53 in both engines, including real CodeMirror and fallback Undo, unsafe-YAML refusals, BOM/CRLF/draft preservation, bounded scan and scope races. All 59 suites passed in both engines; PR #20 merged as b60a541 and published. Next by priority after this batch is N17 backlinks. N34’s domain/account cutover remains owner-only and outside this batch.

Previous batch handover — 2026-09-29 (merged in PR #19)

The requested four-item batch is N22 note navigation/bookmarks, N23 reading width/text size, N32 editable checklists in Preview, then N25 formatting. The first three are committed as 40fe608, 22359a0, and 15a9c9d. All four are implemented and independently reviewed. The final combined PR runs all 55 suites in both engines before merge. Next by priority is N16, adding images to a note. N34 stays doing until its owner-only name/domain/account cutover; the old URL is intentionally still the working Try it link.

Focused checks cover real CodeMirror and its plain-text fallback. N25 passes 52/52 in each engine, including native Undo, selection boundaries, literal backticks/brackets, initial blank lines, draft recovery and read-only mode. Related keyboard-editor 24/24, quick-switcher 19/19 and axe 45/45 pass in both engines. Desktop/phone light/dark screenshots were inspected for every item. Mutations of navigation scope, checklist read-only/subtree handling and formatting selection replacement all caused the intended assertions to fail.

N32’s old Tasks tests now exercise the retained transition helper or visible Preview/Edit controls; source-save and race assertions remain intact. Two release-races calls previously awaited an intentionally held request, causing a test deadlock; they now launch the operation without awaiting that gate. The complete release-races suite passes 14/14 in both engines. Development used focused suites rather than repeated full runs.

The first full CI run caught a missing refusal message for an unreadable pin; Preview now shows the read error, with stale-read guards. The existing large file overwrite protections still pass (large 20/20 both engines). Two Padgit URL assertions now allow the note bookmark fragment while retaining exact origin/path and OAuth redirect checks (11/11). Hostile content checks open the pin’s Preview and assert sanitised task text and no executable elements, instead of expecting raw HTML to be displayed as literal text (28/28). These three focused suites pass in both engines; the corrected PR repeats full CI. That repeat exposed an error-rendering regression under rate limiting: the shared task capture must be detached before replacing Preview contents, or its recovered text field disappears. The node is now preserved, and the capture-recovery fixture keeps the rate limit active until explicitly reset, instead of depending on completing within one second. Capture recovery 6/6, checklists 20/20 and large files 20/20 pass in both engines; review is clear.

Linux WebKit 1.56 repeatedly crashed on the navigation reload test. Playwright documents the matching regression and its 1.57 fix in upstream issue 37766. The test dependency is upgraded to 1.57.0, retaining every reload assertion. Before updating the lockfile, comparison with Git confirmed the pre-existing local difference was line endings only; its original bytes are backed up in the ignored tests/screens/package-lock-before-browser-update.json. With the fixed browser, all new feature suites passed on Linux. An older auth-renewal sign-out wait dereferenced the Settings dialog while the reload briefly had no document; it now waits for the element to exist and be open. Storage-clearing, sign-in visibility and other-tab sign-out assertions remain.

The original SHOULD list was completed and merged in PR #9. Older handover details below are historical, including the pre-merge S3/S4 wording.

Earlier handover — 2026-09-27

N12 preview and S2 search were merged and published in PR #8, with all 39 suites green. S3 accessibility and S4 repository images are now implemented on codex/accessibility-images; the original feature list is complete. The combined PR’s full two-engine checks remain the final merge gate.

Account setup is complete. The reconciled MVP and faster development workflow were merged into main in PR #5, commit 032f6e6, and the published Pages app matched that commit. All 35 suites passed in Chromium and WebKit on the final PR head e33befe. The owner subsequently confirmed that Android Chrome’s Files → + closes the drawer and leaves the editor ready for typing (2026-09-27). Basic phone save/reload was already confirmed; neither check needs repeating. The owner reported all release tests passed and completed on 2026-09-27. This is owner-reported acceptance; no new independent account evidence is implied. Completed setup and checks do not need repeating.

See RELEASE_CHECKLIST.md for the acceptance record. Earlier dated gauntlet records below are history, including their old WebKit blocks and unfinished deployment instructions. They are not instructions to repeat completed setup.

Development loop — updated at the owner’s request

The owner explicitly requested faster iterations on 2026-09-27. Use npm run test:dev -- <suite> while editing, with --suite <other-suite> for related cases; use the affected engine for browser-specific changes. Do not repeat every suite three times for a small edit. Keep all assertions and run the complete two-engine suite once on the final PR. Reserve three full repeats for the release gate. This supersedes the older per-edit gauntlet workflow below. The three full release runs recorded here cover the earlier application baseline; the sign-out follow-up below uses focused regressions and a final full CI check, not another three-run edit loop.

CI now runs once per PR update instead of twice (push plus PR), cancels superseded runs, and preserves full coverage in both engines. Manual repeat runs have a separate concurrency group from PR validation. The runner’s focused mode rejects unknown, missing and empty selections, preserves selected failures, and labels focused results separately from full results. Per-suite timings expose slow tests without changing their assertions.

Evidence: the new runner tests failed before implementation, then passed 24/24 in each engine; runner plus documentation checks took 10 seconds. Actual development commands took 4.5 seconds for mobile-new (6/6) and 24.6 seconds for drafts (45/45), versus 255–269 seconds for the full local run. Logs: tests/screens/fast-runner-{before,after}.log and tests/screens/dev-{mobile,drafts}-timing.log. Independent review found no blocker or weakened coverage. Full CI on 9586d76 passed twice per engine after the readiness corrections; application code remains unchanged.

The final workflow check exposed a real sign-out race: a late account reply could restore credentials between storage removal and page destruction. The independent reviewer reproduced both tabs regaining their tokens. auth-signout first failed 0/3 in 3.5 seconds. Sign-out now invalidates live tokens immediately, configuration persistence requires sign-in, and pending repository lists are invalidated. The final regression holds the profile and final repository responses, keeps the old document alive through a same-document navigation, and then really reloads; it passes 4/4 in each engine in under three seconds. Removing each of the three safeguards independently fails its corresponding assertion in both engines.

Related checks passed in both engines: auth-renewal 62/62, drafts 45/45, CSP 26/26. The earlier WebKit version of the new regression cancelled its own held request on navigation; the final cross-engine setup fixes that. Logs: tests/screens/signout-{race-before,fix-targeted,race-restored}.log and tests/screens/signout-mutation-*.log. The sign-out UI test now waits for the actual signed-out state instead of a short quiet-network interval. The app is 141,592 bytes with no new runtime dependency or host.

Integration and release review

Items

| ID | Priority | Status | Evidence | |—-|———-|——–|———-| | C1 | 1 | done | tests/drafts.test.mjs 44/44 (written before unload, reload, closed tab, new file, stale draft → conflict + Discard, session-only, mode switch, sign-out, plus six review regressions); screens tests/screens/c1-{desktop,phone}-{light,dark}.png; commit daa2754 | | C2 | 2 | done | tests/autosave.test.mjs 36/36 (pause commits, steady typing does not, hide commits, Save kept, in-flight save not raced, conflict stops autosave and keeps the draft, restored draft and New wait for typing, plus seven review regressions); screens tests/screens/c2-{desktop,phone}-{light,dark}.png; commit 493850a | | C3 | 3 | done | tests/switching.test.mjs 33/33 (no dialog on switch, New or change of repository; commit on leaving; offline and conflict keep a draft; save in flight; untouched template leaves nothing; right repository; reopening during a commit; failed open; two-tab draft; lost reply across repositories); rewritten drafts/autosave tests listed below; commit ec26502 | | G2 | 4 | done | tests/hostile.test.mjs 28/28: source scan for HTML sinks (incl. bracket and split spellings); payloads in file, folder, pin and new-note names, note text, task lines, headings, branch, login, a GitHub error message, a pin read error and the sign-in error in the URL; folders and pins named constructor, __proto__, toString, valueOf, hasOwnProperty; tripwire never set and no payload element created. Screens tests/screens/g2-{desktop,phone}-{light,dark}.png; commit 981fd4d | | G1 | 5 | done | tests/csp.test.mjs 26/26 (runs first in npm test): two policies present and shaped as intended; inline script hash listed; no inline handlers; fetch, image, beacon, WebSocket, form, injected <script> and <base> to a third party all blocked and nothing reached it; a copy filled in exactly as the README says signs in end to end with nothing injected; the broker check agrees with the browser on 13 cases; a mismatched broker is reported and sign-in withheld. All 10 suites pass under the policy. Screens tests/screens/g1-{desktop,phone}-{light,dark}.png; commit 3290031 | | N1 | 5a | done | tests/eol.test.mjs 31/31: CRLF, CR and mixed files open clean (no draft, no autosave, not “restored” after reload); edits keep the file’s endings and every untouched line’s own ending (ties, lone CRs inside and at the end, two edits plus an insertion in one save, a moved line, a second save); pinned CRLF task lists read, tick and capture with CRLF; a BOM is kept; non-UTF-8 text is refused and never written; a lost reply is matched byte for byte; the reviewer’s counterexamples; 20,000 seeded random files and edits all read back exactly as typed; a 30,000-line rewrite takes ~25 ms. Commit 4dec41f | | N2 | 5b | done | tests/auth.test.mjs 78/78, new cases: a return from installing signs nobody in, uses no code, shows a note with “Forget me” ticked, one click signs in session-only; “Choose repositories” opens a new tab and the tab refreshes its list on return, staying session-only; a remembered tab carries on and fetches the list once; an older, slower list never overrides a newer one; a refresh keeps an unsaved pick. Screens tests/screens/n2-{desktop,phone}-{light,dark}.png, tests/screens/n2-settings-*.png; commit 4f5062d | | N3 | 5c | done | tests/auth.test.mjs 97/97, new cases: saving settings, or signing in again, in one tab leaves the others signed in and saving (and they adopt the new token); choosing “Forget me” in one tab signs the others out and leaves nothing on disk; switching back to remembered sticks after a reload; other tabs follow a change of repository (committing their open file to the old one first) and of pins alone, and do not undo it later; a token refresh finishing after another tab signed out or chose “Forget me” writes nothing and sends no empty-token request. Commit f23b584 | | N4 | 5d | done | tests/auth.test.mjs 111/111, new cases: a crafted ?error= or ?code= link neither hides a signed-in person’s notes nor puts its words anywhere on screen; signed out, it shows only fixed wording; an error whose state is not this tab’s is not taken for a cancel; a real cancel is reported in the app’s own words; a sign-in that lost its state says so; the “Forget me” choice survives a cancel and a failed exchange (and defaults to ticked when unknown); a signed-in tab ignores a mismatched code. tests/hostile.test.mjs: text from the address is never shown. The fake’s access_denied redirect now carries state and error_uri, per GitHub’s docs (URL in the harness). Commit 045b181 | | N5 | 5e | done | tests/large.test.mjs 20/20: a file over 1 MB is listed but marked, clicking says why, opening it directly or as a pin is refused and nothing is written; GitHub’s too_large refusal gets its own message; a draft whose file grew past 1 MB opens as name (unsaved copy).md and saves there; a note or a pinned task is never saved past 1 MB (the task text stays in the box); a lost reply followed by an unreadable file is a conflict with Discard; a Git LFS pointer is refused and never written. The fake answers large files as GitHub’s docs describe (URLs in the harness). Screens tests/screens/n5-*.png; commit 42315db | | B1 | 6 | done | tests/empty.test.mjs 19/19: an empty repository is named as such with how to start, no error; the first note and the first pinned task create it (without naming a branch that does not exist yet), and later saves name the branch; the tree updates after a first task; a 409 that is not “empty” is not called empty; a list that failed to load says so and never shows another repository’s files; a vanished branch is followed to the default branch, with a note, and saves go there; a pinned task that fails to save goes back in the box. The fake models empty repositories, branches and the repository’s default branch per GitHub’s docs (URLs in the harness). Screens tests/screens/b1-*.png; commit 800e3ed | | B3 | 7 | done | tests/access.test.mjs 30/30: archived and read-only repositories show a badge with the reason, keep notes readable, lock the editor, hide Save and New, disable the pinned capture and checkboxes, and send nothing (no draft either); a repository archived while someone types locks the open note and sends neither Save nor autosave, keeping the text as a draft; nothing (task or save) is sent before access is known; a late answer about one repository is not applied to another; a repository that is gone locks and says so with what to do; tapping the badge on a phone says why; a writable repository is unaffected. The fake’s repository object carries archived and permissions per GitHub’s docs. Screens tests/screens/b3-*.png; commit d32c97f | | A1 | 8 | done | tests/shared.test.mjs 18/18: someone else signs in to the same deployment and is offered every repository across a personal and 101 organisation installations (253, over several pages of installations and of repositories) and writes a note in an organisation repository; paging survives smaller pages than asked for and a missing total; one installation failing leaves the rest listed, with a note; installations are asked at most four at a time; the repository in use is never swapped silently; Save works before a long list arrives; an install waiting for an organisation owner’s approval says so. The fake pages both endpoints as GitHub documents. Owner step (make the App public) under Needs the owner. Commit 7a955b3 | | D1 | 9 | done | tests/rename.test.mjs 49/49: one commit moves the file to a new path or folder, the editor, tree and pins follow, later saves go there; a failure at each of the five requests leaves the repository as it was and says so; a file changed elsewhere is not moved in its old form; an existing target (known or appeared since) is never overwritten; another commit meanwhile is built on, never forced over; unsaved words go with the file; the note is locked while it moves (a refresh cannot unlock it) and is the same note at its new path at once; another tab follows; a lost reply is recognised; a name without an extension keeps the note’s own; unopenable targets, a path through a file, and links are refused; every GitHub request skips the browser cache; no Rename in a read-only repository. The fake Git database is modelled on GitHub’s docs (trees built from their base, fast-forward-only refs). Screens tests/screens/d1-*.png; commit c77ef15 | | D2 | 10 | done | tests/delete.test.mjs 37/37: Delete asks first, saying how to recover from the history (and that unsaved changes go too); saying no deletes nothing; one commit; the note closes and the list updates; a file changed elsewhere, a failure, a stale draft are not deleted and say why (pointing to Discard); a lost reply and an already-deleted file count as done; a double tap changes nothing; a slow delete is not raced by autosave or by switching apps; it waits for a pinned task being saved; the empty editor takes no typing; a pinned note is unpinned; other tabs close it, and one with unsaved words keeps them as a new, unsaved note (and a draft, even if it never heard); no Delete when read-only. Screens tests/screens/d2-*.png (incl. 320 px); commit d762495 | | E1 | 11 | done | tests/wikilinks.test.mjs 47/47, in CodeMirror and the plain editor: Ctrl-click, Cmd-click and a tap open [[Note]], [[Note|alias]], [[folder/Note]], [[Note#Heading]], any case; shortest path wins (by depth, then length), dot-folders ignored; a plain click and a tap at a link’s edge only place the cursor; an unresolved link asks to create <name>.md (no means nothing, yes then Save creates it) in the folder’s existing spelling; no offer before the list loads, from a partial or stale list, when read-only, or outside the repository. Screens tests/screens/e1-*.png; commit 918ad02 | | B2 | 12 | done | tests/firstrun.test.mjs 32/32: with the app on no repository, only the two steps, Check now and Sign out are on screen (step 1 focused); step 1 opens github.com/new with notes and Private filled in, step 2 the app’s install page, both in a new tab; coming back, one repository is chosen by itself and its first note commits; with several, the usual choice; before one is in use nothing public is chosen for anyone, a private notes is preferred, a public one is named as public; failed or partly failed lists say so with Try again, on the steps or not; a slow list shows “looking”; Check now shows it is checking; fits a 390 px phone. Screens tests/screens/b2-*.png; commit 5bf6436 | | A2 | 14 | done | tests/welcome.test.mjs 19/19: at most three sentences of at most 20 words, above Sign in, saying what Notes is, that it reads and writes files only in repositories the app is installed on (which you choose), that notes stay there and whoever runs the copy’s App can reach them; a Privacy link to PRIVACY.html (GitHub Pages publishes PRIVACY.md there) in a new tab; Sign in has the focus; Forget me promises no more than PRIVACY.md (the privacy test holds the README to that too); fits 320 px. Screens tests/screens/a2-*.png; commit d91783c | | A3 | 13 | done | PRIVACY.md; tests/privacy.test.mjs 40/40: every storage key a real session writes (remembered and Forget me, even for a moment) has its own row in the note and sits where the note says; sign-out leaves nothing; an automatic sign-out keeps drafts, as the note says; the broker only ever receives code, code_verifier and refresh_token, never a note, and its code and deployment keep and log nothing; every host in the page’s policies is named; the revoke pages are GitHub’s documented ones; the note states what sign-out does not do (eight hours, six months), the app owner’s own access and Uninstall, the page and CDN trust, restored and duplicated tabs, and repositories others installed on. Commit 211e75b | | F2 | 15 | done | Combined app: all 34 suites passed three consecutive times in Chromium and WebKit; tests/screens/release-final-{1,2,3}.log (255 s, 269 s, 260 s). Earlier blocked attempt is historical. | | F1 | 16 | done | tests/manifest.test.mjs 20/20; full 22-suite Chromium gauntlet green three consecutive times; origin restriction mutation caught; viewed icon and tests/screens/f1-{desktop,phone}-{light,dark}.png; commit 83c1791 (pushed). | | F3 | 17 | done | tests/keyboard.test.mjs 22/22 using real CodeMirror and the fallback; full 23-suite Chromium gauntlet green three times; five independent mutations caught; tests/screens/f3-{desktop,phone}-{light,dark}.png viewed; commit 3486a8f (pushed). | | G3 | 18 | done | tests/errors.test.mjs 90/90; all 24 Chromium suites green three consecutive runs (tests/screens/g3-gauntlet-2/); 26 safeguards independently removed and caught; fresh review findings fixed or documented below; viewed tests/screens/g3-{desktop,phone}-{light,dark}.png; commit c57046d (pushed). | | H1 | 19 | done | tests/docs.test.mjs 10/10; all 25 Chromium suites green three times (tests/screens/h1-gauntlet-2/); four misleading wording variants caught; reviewed tests/screens/h1-{desktop,phone}-{light,dark}.png; fresh review fixes recorded below; commit a7b60d0 (pushed). | | H2 | 20 | done | .github/ISSUE_TEMPLATE/bug_report.md; tests/docs.test.mjs 20/20, five template mutations caught; all 25 Chromium suites green three times (tests/screens/h2-gauntlet/); fresh review and viewed tests/screens/h2-{desktop,phone}-{light,dark}.png; commit a66ce72 (pushed). | | H3 | 21 | done | CHANGELOG.md; tests/docs.test.mjs 30/30, six misleading wording variants caught; all 25 Chromium suites green three times (tests/screens/h3-gauntlet/); fresh review fix and viewed tests/screens/h3-{desktop,phone}-{light,dark}.png; commit 2a16de0 (pushed). | | N6 | 17a | done | a 32 px PNG tab icon beside the SVG (older Safari); tests/manifest.test.mjs 30/30 checks every tab-icon link (exists, type, real size, served type); run 32, both engines, green. Commit 74c2291; merged in forwardmotionnz/notes#2 | | N7 | 17b | done | a full local run about 300 s -> 73 s (8 suites at a time; three runs green); CI runs the repeats as side-by-side jobs, three over in both engines in 5 min 42 s instead of about 35 min (run 43, https://github.com/forwardmotionnz/notes/actions/runs/36221083063, green). 367 short fixed pauses became H.settle; four missing checks found and added; the review’s hollow check restored. Commits 5574a13, 47b968c, b1a75bd, 66dbef4, ae130fa | | N8 | 17c | done | tests/app.test.mjs 70/70, new cases with GitHub slowed to 0.4-0.8 s a commit: three quick ticks all land with nothing refused and two commits; tick then untick ends as it began; a tick and a quick capture both land; a failed commit sends nothing after it, shows GitHub’s state and the error, and puts the waiting capture back; a change made elsewhere is still a conflict and kept; changing repository mid-commit; saving settings mid-commit; the open note follows each commit. Commit in the log | | N9 | 17d | done | tests/tasks.test.mjs 52/52 (new suite): a remove control per task, named for it; removes that one line in one commit, no question; Undo puts it back exactly and goes after 8 s or once used; Clear done removes every ticked task and only them, in one commit, with Undo; CRLF kept; remove, untick and clear while GitHub is slow all land; a stale row removes nothing; a failed remove keeps the task and offers no Undo; Undo withdrawn on changing repository, switching list, or the repository becoming read-only; Undo refuses when lines were added above (incl. a blank line under each heading), still works after a tick or a capture, stays on offer while it cannot be sent yet, comes back after a refusal, and follows a rename; × always shown on a phone, on hover on a computer. tests/access.test.mjs: no task can be removed read-only. Screens tests/screens/n9-{desktop,phone}-{light,dark}-{list,undo}.png. Commit in the log | | N10 | 21a | done | Today’s daily note; daily-notes 23/23 in both engines; all 36 suites passed both engines in CI 36277448756 on 8e2b0fb; PR #6 merged as 4f4a279. | | N11 | 21b | done | Pin toggle and main-area checklist; pinned-tree 23/23 both engines; all 37 suites passed both engines in CI 36279330993 on 4eb4756; PR #7 merged as 00ed737. | | N12 | 21c | done | 0a655fc, reviewed with ec8d7b5; preview 15/15 both engines, CSP 26/26, hostile 28/28. Screens and mutations below; final CI/merge status in PR #8. | | S1 | 22 | moved | Rendered preview is now MUST N12. | | S2 | 23 | done | ec8d7b5; content-search 13/13 each engine: drafts, incomplete results, retained matches, expired sign-in, stale repo/query responses, bounded reads. Independent re-review clear; final CI/merge status in PR #8. | | S3 | 24 | done | 85a2893 plus attachment contrast fix with S4. axe accessibility 29/29 both engines; zero serious/critical across six views, both widths/themes and plain-editor fallback. Injected audit probe detects violations. | | S4 | 25 | done | Repository Markdown and Obsidian image embeds; preview-images 15/15 both engines, screenshots and mutation evidence below. External/unsupported images visibly refused; bounded reads and stale-result guards. | | N13 | 26 | done | tests/integrity.test.mjs 14/14: six CDN tags, each with a hash and anonymous fetch; the CI check is wired; a changed editor file is refused and the app still opens and saves with the plain editor and its badge; the real editor with a changed stylesheet falls back too; the real files with every hash kept run CodeMirror. CI job CDN files match their integrity hashes: all 6 match cdnjs (it printed the four CodeMirror hashes used). keyboard-editor and accessibility now check they test real CodeMirror. Commits 310c908, cbb4954, 16bddb3 | | N14 | 27 | done | tests/conflict.test.mjs 53/53 (new suite): different lines merged and saved (by Save and by autosave), lines added and removed on both sides, the same change on both sides; the same lines never guessed, Save as copy keeps mine as a new note, never replaces an existing one, survives a failed save and a name taken meanwhile; typing during the fetch kept; CRLF and BOM kept; a restored stale draft never merged; one merge per save; deleted on GitHub (empty or not) never recreated; Undo after a merge cannot drop their lines; the caret stays (CodeMirror and the plain editor); one of several identical lines removed on both sides is not guessed; no typing lost while the copy saves. Screens tests/screens/n14-{desktop,phone}-{light,dark}-{conflict,copied}.png. Commit in the log | | N15 | 28 | done | tests/pins-sync.test.mjs 54/54 (new suite): pinned notes found on a device that never saw them, and only those (not pinned: false, not in the body, not in hidden folders); a second visit reads nothing already read; a pin made elsewhere appears after a refresh, reading only that note; pinning writes pinned: true (with a frontmatter block if none, beside existing properties, inside ... frontmatter), unpinning removes only that line, restoring the note exactly; another device sees both; CRLF/BOM kept; unsaved words saved with the pin; a restored draft is never saved by pinning; a pinned property used for something else is never overwritten (pinned in this browser, and said); browser pins from before carry over; non-Markdown and read-only repositories pin in this browser with nothing sent; typed by hand counts once saved; rename and delete keep the list right; a scan never undoes a pin made meanwhile; the list shown stays shown when the scan adds pins ahead of it; a scan cut short or refreshed keeps what it read; known pins show at once; unreadable notes are not read again; offline, the scan stops rather than trying every note, and carries on later; at most 500 notes a visit, and says so. Commit in the log | | N18 | 29 | done | tests/sidebar.test.mjs 33/33 (new suite): ☰ on a computer hides and shows the list, says so, remembered; a handle resizes it by dragging (from where it is grabbed), arrow keys, Home, and double-click reset, remembered, 180 px to 600 px and at most 60% of the window, keeping room for the note when the window narrows; the handle is its own column, not over the note; the editor lays itself out again; hidden with nothing open, the hint says ☰ brings the list back; phones keep the drawer, with no handle, and the button follows the drawer however it closes. Commit in the log | | N19 | 30 | done | tests/theme.test.mjs 20/20 (new suite): Auto follows the device both ways and is the default; Light and Dark override it at once, with the browser bar colour and form controls; remembered, and in place (with the bar colour) before the app’s script has run; signing out keeps it; Auto again forgets it; storage refused, the app still starts; the privacy note lists notes.theme. Commit in the log | | N20 | 31 | done | Quick switcher 13/13 both engines; switching 33/33 and hostile 28/28 both engines. Independent review’s IME finding fixed; hidden-path and composition mutations caught. Screens tests/screens/n20-{1280,390}-{light,dark}.png viewed. Final combined PR CI follows N31. | | N21 | 32 | done | note-status 14/14 both engines, autosave 22/22, conflict 53/53 and axe 33/33 both engines. Four safeguard mutations caught. Review fixes: replaced drafts withdraw local-copy reassurance; lost save replies after undo remain uncertain; an absent empty note is not called Saved. Screens tests/screens/n21-{1280,390}-{light,dark}.png viewed. | | N34 | 33 | done | Padgit branding; custom-domain sign-in/save tests. The owner reported the move to padgit.com done on 2026-09-30, so the pre-move notice and its migrationFrom/migrationTo settings were removed (padgit: “no address-change notice any more”); README and SECURITY now point at padgit.com. Not verified from here (padgit.com is not reachable through this environment’s proxy). broker/wrangler.toml in the repository still names the old origin; see Needs the owner | | N31 | 34 | done | About 9/9 both engines; version 1.0.0 agrees with changelog, fork links configurable, unsafe links omitted, mutation caught. Independent review clear; 320/390 px keyboard access verified; tests/screens/n31-{1280,390}-{light,dark}.png viewed. | | N22 | 35 | done | Browser/header history and scoped note bookmarks; navigation tests both engines, switching 33/33 both. Review fixed reload history and failed sign-in retry. Scope-guard mutation caught. Desktop/phone light/dark screenshots tests/screens/n22-*.png viewed. | | N23 | 36 | done | Centred editor/Preview and remembered 16/18/20px sizes. Reading 12/12, keyboard-editor 24/24 and app 70/70 both engines; independent real-CodeMirror review clear. Desktop/phone light/dark screenshots tests/screens/n23-*.png viewed. N22 committed as 40fe608; navigation 14/14 and privacy 41/41 both engines. Privacy test now distinguishes the documented tab-only history metadata; all sign-out assertions retained. | | N32 | 37 | done | Shared Preview checklists; checklists 20/20, app 70/70, release-races 14/14, tasks 52/52, preview 15/15, images 15/15 both engines; axe 45/45. Independent review clear after source-map, loose subtree, formatting, conflict refresh, ordered-list and Cancel fixes. Read-only/subtree mutations caught; tests/screens/n32-*.png viewed. N23 commit 22359a0. | | N24 | 37 | moved | Folded into N32 | | N25 | 38 | done | Formatting toolbar and scoped shortcuts; formatting 52/52 both engines, keyboard-editor 24/24, quick-switcher 19/19, axe 45/45; review fixes covered by rendered-Markdown assertions; selection mutation caught; four screenshots inspected. N32 commit 15a9c9d. | | N16 | 39 | done | Images: 39/39 both engines, actual paste/drop and real/fallback editor Undo, scopes, size/type, Obsidian folders; review fixes covered reserved paths, pointer drops and busy feedback. Cross-note insertion mutation caught; n16 desktop/phone light/dark screenshots inspected. | | N33 | 40 | done | Tabs 17/17 both engines; navigation 14/14, reading 12/12, mobile-new 6/6, privacy 41/41, axe 45/45 both. Review: background deletion persisted, closing a loading tab cancels its read, local-storage failure blocks unsafe closing, failed neighbour read restores tab. Unsafe-close mutation caught. n33 four screenshots inspected. N16 commit c8f2e48. Long comments moved to implementation-notes.md with identical executable AST, saving 13 KB. | | N26 | 41 | done | Recent previews/history and shared reader; 18/18 Chromium + WebKit, content-search 13/13 and pins-sync 54/54. Review fixes, mutation and screenshots below. | | N27 | 42 | done | Tags, counts, filtering and source edits; 53/53 Chromium + WebKit, independent review fixed, mutation and screenshots below. | | N17 | 43 | done | Linked from with bounded shared reads, draft priority and Markdown/wiki resolution. 13/13 both engines; wikilinks 47/47, review/mutation/screens below. | | N28 | 44 | done | Heading outline with source/Preview jumps, nested headings and pagination. 36/36 both engines; axe 57/57; review/mutation/screens below. | | N29 | 45 | done | Callouts in Preview: all Obsidian kinds and aliases, custom titles (formatting kept), default titles, -/+ folding, unknown kinds as notes, nesting; built after sanitising by moving the note’s own nodes. tests/callouts.test.mjs 16/16; accessibility now scans every callout colour in both themes (zero serious issues). G-2: folding, default title, title/body split and unknown kinds each fail a named check when reverted. Screenshots tests/screens/callouts-*.png | | N30 | 46 | done | Recently deleted, under Recent: notes deleted in the last 30 commits (newest first; a path’s latest change decides, so one made again or moved is not listed; hidden files and non-notes skipped), with Restore committing the version from just before the deletion and opening it; never overwrites a name taken since; read-only shows no Restore; history read only when expanded. tests/deleted-notes.test.mjs 25/25 (after review). The fake GitHub gained list commits, get a commit (diff entries, moves as renamed) and contents at any commit, checked against github/rest-api-description (repos/list-commits, repos/get-commit; schemas commit, diff-entry). G-2: latest-change-wins, hidden files, the 30-commit limit, read-only and the taken-name refusal each fail a named check when reverted. Commit in the log | | N36 | 44a | done | The header’s ← → buttons removed; tests/navigation.test.mjs 14/14 now drives the browser’s own back and forward (both ways, after a reload and from the middle of history) and checks the header has no arrows. Full suite 61/61 Chromium. Commit in the log | | N37 | 44b | done | Tasks screen, “Add a task” boxes, Edit note / Tasks switch and pinned-only logic removed; a pin opens like any note; Preview checklists keep tick, edit, move, drag, remove, Clear done and Undo. 18 suites that drove the Tasks screen now drive Preview (reasons under “N37: tests changed”). G-1: full suite 61/61 Chromium, three runs. G-2: each safeguard reverted alone fails a test: Undo withdrawn on opening another note (tasks “another note: the Undo is withdrawn”, “a new note: the Undo is not offered there”), cache refilled before Preview’s shortcut (tasks “another list: setup”), Undo refused with unsaved typing (tasks “unsaved typing: Undo sends nothing”), shortcut marked only while open (pinned-tree “a new note: the shortcut is no longer marked”), late checklist reply never rolls the editor back (tasks “late tick reply: the note shows what GitHub has”). G-3: four findings, all fixed with those tests. G-4 tests/screens/n37-*.png. Commits in the log | | N38 | 44c | done | Built to the approved mock-up (https://claude.ai/artifact/AmXpGUFgM5kpXJkZ1awT42): icons, buttons, header with save dot, Edit/Preview switch and ⋯ menu, tabs (chips on phones), icon toolbar, sidebar sections, reading face for headings, property chips, checklist rows with tap-to-edit, grip and ⋯. tests/design.test.mjs 60/60; full suite 62/62 Chromium; axe zero serious issues in every view, both themes, both widths. G-2: each safeguard reverted alone fails a named design check (property values as text; links in tasks; Saving… while a save is on its way; focus back to ⋯; menus close on Tab and on a tap; tabs with one name told apart; Pinned chip follows the pin check; phone title keeps its line in a conflict; one task menu at a time) and the menu observer writing unchanged attributes stops sign-in. G-3: nine findings; eight fixed with those tests; the ninth (no quick-switcher button on phones) kept on purpose, since search is at the top of the Files sheet. Screenshots tests/screens/n38*.png; README screenshot redone | | N35 | 47 | todo | Owner’s request 2026-09-28: GitHub Sponsors | | N39 | 47a | done | Tasks inside a quote or callout work in Preview (was: one quoted task made the whole checklist read-only). tests/quoted-tasks.test.mjs 13/13 (after review): tick, edit, move (numbers kept) and remove inside a callout, a quote and a quote in a quote; a task outside still works; a quote line carried on without > leaves the checklist to Edit and writes nothing. G-2: quote-depth in renumbering and the carried-on-line refusal each fail a named check when reverted. Commit in the log | | N40 | 48 | todo | Owner’s request 2026-09-30: drag and drop notes between folders in Files |

N10: plan

N11: plan — 2026-09-27

N11: verification

N10: verification — 2026-09-27

C1: plan

C2: plan

C3: plan

G2: plan

G1: plan

N1: plan

N2: plan

N3: plan

N4: plan

N5: plan

B1: plan

B3: plan

A1: plan

D1: plan

D2: plan

E1: plan

B2: plan

A3: plan

A2: plan

F2: earlier attempt (historical; superseded by integration below)

F1: gauntlet record

F3: gauntlet record

G3: plan

G3: gauntlet record

H1: plan

H1: gauntlet record

H2: plan

H2: gauntlet record

H3: plan

H3: gauntlet record

After the MVP: the owner’s requests (2026-09-27), in the order agreed

The owner asked for all five; N13 and N14 now, the rest later.

N13: plan: pin every CDN file to its hash (Subresource Integrity)

N14: plan: conflict recovery without copy and Discard

N15: plan: pins that follow you between devices

Owner’s requests (2026-09-27, later), in the order agreed

Asked alongside “sync the pins between devices”, which N15 already does (merged in PR #11): the owner’s screenshot showed the page from before that deploy, so no new item for it. Order: the two small everyday-layout items first (N18, N19), then N16 and N17.

N18: plan: resize and collapse the file sidebar

N19: plan: a light/dark toggle

UX review against a desktop notes app (owner’s screenshot, 2026-09-28), in the order agreed

Each keeps the rules: notes stay plain Markdown files, nothing app-specific is written into the repository, one HTML file. Small, everyday items first; the items that read many notes (N26, N27, N17) come after the shared note reader recommended in the clean-up assessment, which N26 builds first.

N20: plan: quick switcher

N21: plan: save status and word count

N22: plan: back and forward between notes

N23: plan: readable width and text size

N24: folded into N32

N25: plan: formatting toolbar

N26: plan: recent notes with previews (with the shared note reader)

N27: plan: tags

N28: plan: outline of headings

N29: plan: callouts in Preview

N30: plan: recently deleted notes

Left out of the UX review, and why

Owner’s requests (2026-09-28), in the order agreed

N34: plan: rename to Padgit, served at padgit.com

N32: plan: checklists in any note; the Tasks screen folds in

N33: plan: tabs

N40: plan: drag and drop between folders

N35: plan: GitHub Sponsors

N16: plan: add images to a note

Owner’s review of the built app (2026-09-29), in the order agreed

The owner found the app’s look clunky (text buttons, glyphs as icons, three stacked rows of controls), the to-do list still a separate experience, the “Add a task” box on every pinned note, and the ← → buttons confusing (they follow the order notes were visited, which with tabs often reads as reversed). Checked: in a clean history the buttons do go back and forward; the confusion is visit order against the tabs’ left-to-right order.

N36: plan: remove the back and forward buttons

N37: plan: one kind of note

N37: tests changed with the Tasks screen

N38: plan: visual redesign

N38: tests changed with the redesign

Needs the owner

Account setup has already been completed. Do not repeat login, deployment, secret generation or App visibility changes as part of this release.

C1: gauntlet record

C2: gauntlet record

C3: gauntlet record

G2: gauntlet record

G1: gauntlet record

N1: gauntlet record

N2: gauntlet record

N3: gauntlet record

N4: gauntlet record

N5: gauntlet record

B1: gauntlet record

B3: gauntlet record

A1: gauntlet record

D1: gauntlet record

D2: gauntlet record

E1: gauntlet record

B2: gauntlet record

A3: gauntlet record

A2: gauntlet record

N14: gauntlet record

Clean-up assessment after N15 (2026-09-27, asked by the owner)

Decisions

Log

(one line per iteration: date, item, result, commit)

Upstream integration evidence (historical main at 03f7311)

F2: gauntlet record

F1: gauntlet record

F3: gauntlet record

N6: gauntlet record

N7: gauntlet record

N12 and S2 — combined validation, 2026-09-27

The owner requested these two items together, extending the original iteration cap. N12 implementation: 0a655fc. S2 follows in a separate commit, with one full two-engine CI pass for the final combined PR. Setup and owner smoke checks are already complete and are not repeated.

Preview uses pinned Marked 18.0.14 and DOMPurify 3.4.16, SRI, narrow CDN paths and sanitised DOM fragments. Frontmatter, tasks, code, links, drafts and CDN fallback: 15/15 checks in each engine. Images remain S4.

Content search includes paths and scoped drafts, with at most 300 file reads and four workers. Read failures, skipped files and partial lists are visible. 13/13 checks in each engine, including stale query/repo, auth recovery and bounded reads. Both feature suites together take about eight seconds.

Independent review found lost results after selecting a match, and expired sign-in leaving search running. Both fixed with regressions; re-review passed 13/13 and found no residual blocker. Mutations independently caught unsafe preview tags, unsafe attributes, stale results and missing sign-in recovery. The attribute payload was strengthened to use an allowed paragraph, so that check does not accidentally rely on the disallowed-element check.

Screenshots inspected: tests/screens/n12-{1280,390}-{light,dark}.png and s2-{1280,390}-{light,dark}.png. No overflow, readable tables/frontmatter/links. CSP, hostile, privacy, docs, pinned-view and keyboard regressions passed. The keyboard fixture now preserves real preview-library routing.

The app is still one file under 150,000 bytes (148,069 at review), no build. Tabs and shortened duplicate introductory prose leave room while preserving security/data-loss comments. Existing owner package-lock.json edit untouched.

S3 and S4 — 2026-09-27

The owner requested the next two items together. S3 is 85a2893; S4 is the following feature commit on codex/accessibility-images. One combined full CI check follows focused development; no repeated owner setup or release smoke.

Final CI caught two unchanged manifest assertions requiring spaces after CSS colour variables. Restored that formatting; all assertions retained. Focused manifest and axe checks pass in both engines. The app remains under 150 KB.

N26 evidence — 2026-09-29

N27 evidence — 2026-09-29

N17 evidence — 2026-09-29

N28 evidence — 2026-09-29