Repository navigation
Conversation
The Phase 4 editor parses OpenNote Markdown (CommonMark plus extensions, SPEC 7.1) with markdown-it, as the Phase 4 design chooses. markdown-it 15 ships its own types. @tiptap/core and @tiptap/pm move from devDependencies to dependencies, because the editor schema ships in the app. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The schema is built with Tiptap's getSchema from extensions, so the editor can mount the same extension list later. It holds exactly what OpenNote Markdown 1 can write (SPEC 7.2 to 7.4): paragraphs, headings, lists with task state on the item, quotes, callouts with a title node, code blocks, math atoms, images, and the marks in SPEC 7.7's nesting order. Element metadata (SPEC 6.6) lives in node attributes that the Markdown never carries. Parse rules read tags and data attributes only, so they form the paste allowlist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
parseTextBlock reads the whole CommonMark syntax with markdown-it, plus the dialect of SPEC 7: == highlights, task items, callouts with a fold and a title, math atoms, and the allowed HTML tags, which become marks only when they pair and nest. Setext headings, indented code, reference links, and other HTML map to their canonical equivalents, and any string gives a valid document. serializeTextBlock writes SPEC 7.7's canonical form. Text is escaped as one line of code points (SPEC 7.6). Marks nest in the canonical order, close and reopen on overlap, and move edge whitespace outside. A delimiter that would not open or close under CommonMark's flanking rules becomes an HTML tag. The writer also checks each paragraph against the reader and falls back to tags when CommonMark's matching rules would pair the delimiters differently. Two lists of one kind side by side would read back as one list, so the writer joins them, and joinListsDeep does the same for a document. Fixtures, SPEC B.5 escape pairs, and fast-check properties cover the round trips in both directions and the stability of writing any input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SPEC 6.6 keeps element data in the text block's ids, tags, styles, and checked fields, in document order, and the Markdown never holds it. A document holds it as node attributes. applyElementData reads the fields into attributes: extra IDs are ignored and later elements stay without one. extractElementData writes them back, mints IDs for elements that have none or repeat one, and orders the maps by key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SPEC 2.4 IDs are 26 lowercase Crockford base32 characters: a 48-bit millisecond time and 80 random bits. The editor needs them for text element IDs, table rows and columns, and the tables that a paste makes. The time and the random source are parameters so tests can fix them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parser tests and the paste tests both compare documents as compact outlines, so the helper moves to its own file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sanitizePaste turns one clipboard paste into pieces for the editor: text documents and table blocks. It is pure. It reads no clipboard, loads no image, and never touches the page. HTML is parsed into an inert document and classified by its source. Normalizers then rebuild what each program hides in styles. Word's mso-list paragraphs become nested lists and its character styles become marks. OneNote's To Do images become task items, and Google Docs' wrapper and sibling lists are fixed. Web pages lose page chrome, hidden text, scripts, colors, and fonts, and relative addresses are resolved against the source page. Only what the editor schema's parse rules allow survives, so script, data, and vscript links and local file images never reach the page. Tables become table blocks with new IDs and inline Markdown cells. Images become asset requests, and resolveImages swaps each source for its asset, or for a link or the description when an import fails. Plain text is read as Markdown when it looks like it, with GFM tables, or else as a paragraph for each line. Text from a PDF gets a separate joined version. HTML over 5 MB falls back to plain text. Small captured clipboard samples live in paste/fixtures and are skipped by CHECKS, because real clipboard HTML has long lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parallel packages build against these decisions from day one, so they land as Proposed before the code: the typed notes editor, highlighting with lowlight, the page viewport, the keyboard model, and text box layout. ADRs 0023 and 0025 wait for their spikes, as the plan says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tiptap packages declare exact peer versions of each other, so all of them move to dependencies at one exact pin, with the table, subscript, superscript, extensions, and suggestion packages the editor needs. markdown-it is pinned, and lowlight and highlight.js arrive for the lazy highlighting chunk, as ARCHITECTURE.md section 3.1 lists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
editor/ never imports state, services, features, or the shell; it gets what it needs through EditorHost, like ui/ does through props. A fixture proves the rule fails. A feature's flags.ts also becomes public like its index.ts, because flags load at start-up, before the feature's chunk. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The early core lived in features/editor; the parallel packages look for editor/schema, editor/markdown, and features/page/paste, so the files move there with their history. On top of it, the editor skeleton of PLAN.md section 3.8 lands: - schema: the table schema, the specs as data, and static DOM in slices - markdown/index.ts: the fixed API, with the cache, the splice, the range re-parse, and the worker as stubs that WP1 replaces - meta.ts and host.ts: the transaction meta keys and the EditorHost - extensions/kit.ts: one factory per extension file, each a stub that its owner replaces, and the editorExtensions registry A test builds each kit's schema and requires it to equal textSchema and tableSchema. The serializer takes nodes from any schema instance, since each editor builds its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parallel packages build against these from day one (PLAN.md sections 3.5 and 3.12, change P2-10): - services/pages: the PageService types, a memory implementation with per-client undo, Phase 3's typing groups, and frames for other clients, the shared contract suite, deterministic page fixtures, and the Tauri adapter over the core's page commands - core/client.ts: the client Phase 3 planned for the app bridge, with invoke injected from platform/tauri (P3-8), one request in flight per page, and the envelope and frame decoders - platform: the pages, spelling, clipboard, images, and speech clients replace Phase 2's placeholders, each a Tauri wrapper and a web fake; ?fixture=sampler makes every web page the sampler Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PLAN.md section 4.1's page skeleton: every seam of sections 3.6 to 3.10 exists with a working stub, so each package's tests run from day one. - flags.ts: Phase 4's flags, joined to Phase 2's list at start-up - keys.ts: every Phase 4 command's keys in both shortcut sets, checked against Phase 2's rules; registerPageCommand reads them - registries.ts, register.ts, and one registrations file per package - seams/index.ts: the only path Phase 5 imports - a static viewport, a block layer, a pool that mounts every text block at open, and a sync queue that sends setText after each change - PageView.tsx mounts them behind page.editor: an empty page opens with a text box that joins page.json when typed in, and the empty state has a small illustration of a window at dusk with a candle and plant - one strings namespace per package Page zoom moves into registrations/page.ts, and its reset command takes the plan's ID, page.zoom100, with Ctrl+Alt+= and its neighbors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PLAN.md section 3.15's helpers, so each package tests its feature end to end from day one: - builders.ts: document builders over textSchema, and md(), which reads a selection marked with brackets out of Markdown - harness.ts: renderPage mounts the real page assembly over the memory service, types with real keys in browser mode, and reads back what the service holds; expectCommand runs a command on a marked selection - fixtures.ts: the sampler, the short note, ADR 0005's 20-page note in three shapes, eight freeform boxes, and the 500-block budget page Smoke tests in each job: the unit harness test, the PageView component test that types into an empty page and finds it in the page service, and the Playwright spec that does the same in the production build and opens ?fixture=sampler. ESLint treats the helpers as test code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 3 built the core but not the app bridge it planned, so typed text had nowhere to go in the desktop app. core_bridge.rs starts the core on the first page_open and serves page_open, page_apply, page_undo, page_redo, page_save_now, and page_close, and flushes and stops the core on exit. Until the storage-backed notes service lands, the interface's pages live in one notebook of the bridge's own, mapped by the interface's page ID. page_handle serves the image commands (P3-8). Every other command of PLAN.md section 3.13 is a stub that answers notImplemented: clipboard facts and file tokens, image import and renditions, spelling, and speech. Each is listed in lib.rs, the command list, and the capability file, and the windows crate gains the features of ARCHITECTURE.md section 3.2. A test restarts the core and finds the text still there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mounting the page pulled Tiptap, ProseMirror, and markdown-it into the start-up bundle, which doubled it. The page body now loads lazily, and the sync registration reaches the shown queue through a module that needs no editor code, so start-up stays at 160 KB gzipped (P2-11). The bundle budget gains ARCHITECTURE.md section 24.4's chunks: the page at 170 KB, and the Markdown worker, highlighting core, each grammar, history, read aloud, text styles, and reading order. The specific entries come first, since the check takes the first key that matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ctrl+/ shows every key a person can rely on, and Phase 4 adds the keys the browser and ProseMirror own (P2-12): Tab in a text box, Escape and then Tab to leave it, Enter and Shift+Enter, the clipboard keys, plain paste, the slash menu, and the arrows, Shift, Space, Enter, and F2 with boxes selected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The typing gate needs a home from day one (ARCHITECTURE.md section 24.5). tests/perf/typing measures keydown to the next frame, as ADR 0005's spike did, with 100 keys 120 ms apart after 10 warm-up keys, on the short fixture in the test build. The perf-typing CI job runs it and keeps the numbers; WP8 adds the conditions, throttling, and the gate. Here it reads 7.4 ms at the median and 9.5 ms at the 95th percentile. Its own preview server and port keep it apart from Playwright runs in other worktrees. The WebView2 harness in spikes/harness stays there until WP8 ports it to the real app. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PLAN.md section 4.4 asks that text typed into a page in the desktop app come back after a restart, through Phase 3's core. The new smoke spec types into Mitosis, closes the app, starts it again on the same profile, and finds the text. It runs in the e2e-smoke job, where the drivers are installed, and skips elsewhere like the other specs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sampler read as one gray column: every highlight was honey, colored text and sizes were ignored, and callouts were plain paragraphs. Text now takes BRAND.md's palette, quietly: highlighters are the pens' colors washed out on paper, pen colors and sizes apply, bullets are clay and checked items moss, and each callout is a soft card tinted by its type with the color on its edge and beside its title. The title names the type in words, so color is never the only signal, and forced colors draw them in CanvasText. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The serializer now keeps an entry for every node it visits: the text its parent joins, the text without its own prefixes, and where each child starts and ends. ProseMirror shares unchanged nodes between states, so a flush after a keystroke writes one paragraph and re-joins cached strings along one path, and renumbering a list rewrites only items whose marker width changed. The spans feed the range re-parse that comes next. Types are compared by name and the read-back check builds its expected nodes in textSchema, so editor and table schemas no longer go through a JSON copy first. warmCache now fills the cache. A property test checks cached and uncached text are equal across random edit sequences. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A table block stores SPEC 6.3's data (columns with IDs and widths, rows whose cells are keyed by column ID), while its editor holds one table document. mapping.ts converts between the two, keeps IDs by position, memoizes each cell's Markdown by paragraph node so a flush serializes only edited cells, and normalizes untrusted data from the core so every row has a cell for every column and every ID is unique. The table commands are pure changes to that data, so a moved row keeps its ID and new rows and columns get fresh ones. Property tests cover the round trip, the normalizer, and random command sequences. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The page now scrolls in its own viewport: a sizer that makes the scroll bars match the zoomed world, the world in page units scaled by --page-zoom, and the underlay Phase 5 draws into. The camera keeps scroll and zoom without layout reads, a ResizeObserver keeps the viewport rectangle, and listeners run once per frame during gestures and once when they settle. Ctrl+wheel and touchpad pinch zoom around the pointer with data-gesture set until 150 ms after the last input, the three zoom commands step through the fixed zoom list, and Fit page to width joins them. The world grows to fit the flow column and never shrinks while the page is open. Each page's scroll and zoom are saved in device state and restored when it opens again. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The contract suite now holds real cases in contractCases.ts, free of any test framework, so the same cases can run in Vitest and inside the real app against Phase 3's core: edits with order keys and preconditions, UTF-8 splice offsets, whole-or-nothing batches, locks, page fields, undo grouping by Phase 3's rules, frames to other clients, failed undo checks, and versions. The memory service now follows the core: each open page is a client with its own undo stacks, grouped by the rules of crates/core/src/ops/undo/group.rs, steps that refuse to undo blocks another client changed since, frames for the other clients, read-only pages, changes on disk, and versions kept per save. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Task items get a native checkbox named by the item's text, Enter starts an unchecked task, and Backspace drops the box before leaving the list. Adjacent lists of one kind join after each local edit, since canonical Markdown can't keep them apart. Callouts are notes named by type and title, with a type menu and a real fold button that changes the Markdown. Math atoms show their source and edit it in a small field. Inline images show a chip and never load an outside source. A custom text color is lightened on dark pages. Adds the shared command helpers and the editor test helpers the later WP4 commits build on. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Undo, redo, and frames from other windows hand a text block its new Markdown. reparseRange now finds the splice, walks down through the lists, items, quotes, and callouts that hold all of it using each cache entry's child spans, and parses only the children it touches, widened by one sibling. Writing the result again and comparing it with the new Markdown proves it equals a full parse; when a level can't prove it, the level above tries, and otherwise the caller parses the whole block. Unchanged children at either end keep their identity and cache entries. A property test checks the range result against a full parse for random documents and random splices, and that adding a word to any paragraph never needs a full parse. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
parseInWorker now starts one module worker on first need and resolves with the document it sends back as JSON. Where there are no workers, or the worker fails to start or to parse, the block is parsed on the main thread instead, so callers always get a document. An aborted signal rejects with its reason. MAIN_THREAD_PARSE_LIMIT (64 KB) tells callers when a full parse belongs in the worker. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The page never decodes an image to learn its size (Phase 4 ARCHITECTURE.md section 12.2). probe.rs reads each format's header for the pixel size and EXIF orientation (orientations 5 to 8 swap the sides, so the stored size is the shown size), SVG sizes come from width, height, or viewBox, and on Windows a WIC header read confirms the size. image_import and image_import_clip check the declared type and the 50 MB cap, then store the file through the core's PageHandle::import_asset on a blocking thread. HEIC and TIFF are refused unless page.heicImport is on, when convert.rs turns them into PNG or JPEG with WIC, upright. The opennote-asset scheme serves an open page's assets through PageHandle::asset_bytes, so the WebView never opens an asset file itself. Fixtures cover every format and all eight orientations. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
image_import_url saves an image from a web paste into the page (Phase 4 ARCHITECTURE.md sections 12.2 and 15.5). It uses ureq with Windows' TLS stack, as the updater does: 20 seconds, 50 MB, image/* answers only, at most 3 redirects and never to another scheme, no cookies, no Referer, and no proxy. Loopback, link-local, private, and other non-public addresses are refused when the name resolves, so a pasted page can't reach into the local network. Tests run against a local server. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Each key in a paragraph with squiggles dropped its ranges from the spelling highlight and added equal ones. Chromium repaints everything with a custom highlight when a highlight's ranges change: 17 ms of paint a key with 200 squiggles in view. Typing already moves the live ranges with the text, so show() now keeps the ranges when they match. Paint falls to about 8 ms; the rest is Chromium revalidating every highlight marker after any DOM change, recorded in docs/perf/phase-4.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The typing benchmark ran one key loop on the short note. It now runs every typing condition of ARCHITECTURE.md section 24.2 that a browser can reach: the 20-page note at its start, middle, and end; the outline; the callout; the freeform page; a table cell and a code block after the note; 200 squiggles in view; bursts at 30 keys a second; the screen reader flag with Chromium's accessibility tree on; and 50% and 200% zoom. Each reports painted p50, p95, and maximum, script per key, and long animation frames. The motion mode pans and zooms the freeform page by wheel, Ctrl+wheel, touch drag, and pinch, and counts dropped frames against 60 Hz. OPENNOTE_TYPING_GATE=1 holds painted p95 to 16 ms, a baseline file holds it to main's within 10%, and calibrated throttling matches Phase 2's. CI keeps recording the numbers, with time for the longer run. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ARCHITECTURE.md section 24.1 gives Phase 4's plugins 1.5 ms of each key, checked on every pull request. On the 20-page note and the 20-page outline, the new unit test times a key's transaction against the full editor state and against a state with no plugins, and holds the difference to that budget. It also times the serializer cold, a flush (serialize, splice, and the request's JSON), and the range re-parse that applies undo frames. A fixed benchmark scales the budgets on slower machines, as Phase 2's perf tests do. The outline is one top-level list, so its undo frames fall back to a full parse; the test holds that to undo's whole 50 ms budget and docs/perf/phase-4.md lists it as a follow-up. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ARCHITECTURE.md section 26.2 lists prose with blank lines and a single URL among the plain-text sources. Prose keeps its paragraphs and its inline address as text; a lone URL stays text rather than being parsed as Markdown, as section 13 requires. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Typing an address straight after Ctrl+K put its first letter into the note, over the selected words, because the link popover loads and takes focus a few tasks later. Walking the app on the web build turned it up: "Visit site", Ctrl+K, "example.com" became "Visit e" with a link to xample.com. The command now holds printable keys, Backspace, and Enter from the moment Ctrl+K runs, and the popover starts its address field with them. Holding stops when the popover opens, fails, or after two seconds. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Static text renders math as an empty element with its source in an attribute, so display and inline math were invisible, and silent to screen readers, until the block's editor mounted. Generated content now shows the same $$ fences and source, in the math view's style. A new accessibility spec compares the sampler's tree static and mounted. Apart from the editor's own controls the two now read the same. Two gaps remain for the schema's owner and sit in an expected-failure test: static task items have no checkbox and static callouts have no name. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
tests/ui/screens/page.ts registers the sampler page, static and with its editor mounted, so the cross-screen axe suite checks every node, mark, callout type, table, code block, and image at each size class in both themes. All 12 runs pass. page.walk types with Markdown shortcuts, italicizes with the formatting bar, pastes HTML, adds a code block, an image from the file picker, and a table, then undoes and redoes, checking the Markdown the page service holds at each step. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Keys typed at full speed after Ctrl+K still lost their first letters: the popover added the held letters through state, and the next key's change event read the field's old, empty value and replaced them. The held letters now go straight into the field, then into state. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The keyboard specs of ARCHITECTURE.md section 26.3 on the web build: every Markdown shortcut, with undo right after one keeping the typed characters; then marks, a link typed straight after Ctrl+K, heading levels and Normal, an outline move, the date helper, and AutoCorrect with its one-step undo. Each checks the Markdown the page service holds. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A one-finger pan moved the page by its first step only. Touch and pen pointers start captured by the element they pressed; when the gesture tool moved the capture to the viewport, that element got lostpointercapture, and the router, listening on the viewport in the capture phase, took it as a cancel and ended the gesture. Synthetic events in the component tests never capture, so they missed it; touch sent through the DevTools Protocol showed it. The router now cancels only when the viewport itself loses the capture. A component test fires lostpointercapture on a pressed child mid-pan. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
page.touch-pen sends real touch and pen input through the DevTools Protocol: a tap places the caret in a text box, a one-finger drag pans without making a box, a pinch zooms, and a pen drag on empty page draws a marquee without scrolling. Long presses need Windows' gesture recognizer and stay with the desktop spec and the manual checklist. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ARCHITECTURE.md section 30 asks for both as Phase 4 documentation. The help page explains the keys between blocks: arrows across a document's blocks, selecting blocks, Escape then Tab out of a text box, Tab inside text, outline moves, and page zoom. The checklist holds what needs a person: Narrator and NVDA, touch and pen on a Surface, IMEs and other text entry, AltGr layouts, and contrast themes, with a table for each run. Phase 4's run has not happened yet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Kept both content security policy changes: main's connect-src for Tauri's IPC over fetch, and Phase 4's opennote-asset localhost image source. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
docs/perf/phase-4.md holds the exit gate run on the Surface Laptop Studio 2: every typing condition the web build can reach, two runs (one at 99% processor load from other jobs), the motion gestures, and the microbenchmarks. The 20-page note types at 13.8 to 14.4 ms painted p95, inside the 16 ms gate. The page lists what the run fixed, what is still over budget with the cause of each (the outline, squiggles in view, outline undo, script per key), and what still needs the reference laptop or the desktop app. ADRs 0020 and 0022 now point at the measured numbers in their status. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The walk read the page service right after typing in a table cell, but the words arrive in the next flush, 150 to 300 ms later, and later still on a busy machine. It now polls for them, before undo and after redo. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The status table now marks typed notes done with this pull request's link, shows Phases 5 to 14 as in progress and being wired into the app, and notes the first beta build on branch beta. A new section lists what the app does today, the quick start lists the test commands in package.json, and the repository map covers the editor, the page view, the page service, the typing benchmark, docs/perf, and ADRs 0020 to 0026. docs/README.md now links the Phase 4 performance record, the keyboard and screen reader checklist, and the block-navigation help page. The ADR index lists the seven Phase 4 records it was missing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The WIC agreement test decoded every fixture and compared its size with the header reader's. Windows ships the WebP decoder as an optional extension, which the CI runner lacks, so WIC answered None for the WebP fixtures and the test failed there. With no codec there is no answer to compare, so the test now skips only a missing answer for a WebP file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The popover's chunk loaded on the first Ctrl+K. On a slow runner that took longer than the keys typed after it, which were all held for the address field, so the link never saved. The chunk now loads in idle time with the formatting commands, and the popover mounts synchronously once loaded, so the held keys reach its field before the next key arrives. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The page holds an image before the view has finished with it. The view then selects the image and takes the focus, so a click and the slash menu's keys sent in between were lost on a slow runner. The walk now waits for the image's toolbar, which shows once the view is done. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The benchmark pressed Enter at the end of the 20-page note and typed at once. If the Enter had not landed, a block shortcut such as the code fence could not apply, and the run waited two minutes for a code block. It now waits until the caret is in an empty paragraph, and both waits fail within 15 seconds and say where the caret was. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CodeQL flagged the corpus test's table cell helper, which escaped a pipe but not a backslash, so a backslash before a pipe would read as the pipe's own escape. The helper now escapes both in one pass. No expected table in the corpus holds a backslash, so no expected file changes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The spec ended the WebDriver session right after the text showed in the box. That stops the app at once, before the page's flush to the core, which is 150 to 300 ms after the last key, so the text was lost on restart as in a crash. The spec now closes the window as the close button does. Rust holds that close until the interface has flushed the page, so the process leaves only after the text is with the core. The assertion also says what the page held. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The typing benchmark focused the text box after it had collected the box's text nodes. Focus can mount the editor, which replaces those nodes, so the caret fell back to the page title and the next keys went there. It now focuses first. The language picker tests typed through real key events into a popover that was still settling, and timed out at 15 s on loaded runners. They now set the filter's value directly, as the arrow and Enter steps already drove their keys. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A command used before its chunk loads runs after the keys typed behind it, so on a slow runner a strike-through, a checkbox toggle, and a list move came out wrong in the keyboard spec. The idle warm-up now loads the formatting commands, the link popover, the slash menu, folds, and the merge command together, and sets data-page-commands on the root when all are in. The keyboard and walk specs wait for it before they type. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The code condition failed on CI with the caret still in a heading after Enter. A text box exists as an empty wrapper until its text is drawn in idle time, and the old search for the longest contenteditable text box then found only the page title, so the caret went into the title. Delaying idle time by 3 s reproduced the exact CI message. openFixture now waits for the page's commands (data-page-commands) and for every text block to be drawn. longestBox searches the text blocks, not the title. placeCaret waits for the press to mount the box's editor and says so when the caret and focus are not in the box afterwards. The caret context also reports whether the caret is in the title. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
later() started its module load with void load(), so a load that rejected was an unhandled rejection. In a unit test that finishes before the idle load does, the load runs after the environment is torn down. Vitest then fails the run with an EnvironmentTeardownError even though every test passed, as the ubuntu checks job did on code/commands.test.ts. The loads are optional warm-ups: the chunk that needs the module first imports it too. A failed one is now ignored, as the editor's own warm-up already does. A test covers a load that rejects. Co-Authored-By: Claude Fable 5.1 <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.
Summary
Phase 4 of DEVELOPMENT.md: typed notes. Text boxes sit anywhere on a freeform page or stack in a document flow, with rich formatting, Markdown shortcuts, images, tables, code, paste from other apps, and spell check. This branch also brings in Phase 3 (document model and storage) from main, so main gets both.
Outcome: a 20-page note types at 13.8 to 14.4 ms painted p95 on the Surface Laptop Studio 2, inside the 16 ms gate. The outline and squiggle-heavy conditions are still over budget; see the follow-ups.
What shipped
Exit-gate results
npm run perf:typing, 14 conditions, 100 keys each), recorded indocs/perf/phase-4.md: the 20-page note at its start, middle, and end is 14.3, 14.4, and 13.8 ms p95. The callout, freeform page, table cell, code block, and 50% zoom also pass.Fixed at the gate
contain: paint, and an:nth-child(of)rule restyled every block (17.5 to 14 ms p95)tauri.conf.jsonReview fixes
Four high findings from the pre-merge review, merged from
p4-review-fixesin 3b69571:pages.jsonas empty and could replace it; it never replaces an unreadable page map now, and it checks page argumentsThe same branch also reads a link's scheme as a browser does before checking it (f1bc893), keeps a clicked link from navigating the window (d9b98b2), and encodes the page and asset IDs in an image's address (276acd8).
Known follow-ups
docs/testing/keyboard-and-screen-reader.md), page screenshot baselines, and spikes S3 and S5 on hardware.🤖 Generated with Claude Code