Editor previews (colour swatches, arrows, embeds), the 18 obsidian-basics tasks (find and replace with regex, back/forward, auto-pair, footnotes, folding, search operators, split view, PDF/audio/ video embeds, spelling, drag and drop, pinned tabs, settings) and round 2: ```query blocks, stacked tabs, pop-out windows, tasks ticked inside embeds, text around an embed, embeds that follow edits, block menu shortcut. New crates: notescraft-regex (linear-time engine) and notescraft-syntax. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
9.0 KiB
Code blocks, language grammars and batch formatting
Problem
Fenced code in notes is drawn as flat monospace text: no language awareness, no syntax colors, no copy button, an odd background shared with inline code, and diagrams or folder trees written in fences are not rendered. JSON is not recognized, so it cannot be formatted, validated or copied easily. Languages cannot be added, and there is no way to review or fix many notes at once. Evidence: owner testing on real notes in the project folder.
Goal
Code in notes is readable, copyable and formattable in place, new languages can be added safely when needed, and formatting or linting a whole scope is reviewed file by file before anything is written.
Non-Goals
- A full language server, autocomplete or refactoring for code blocks.
- Running or executing any code.
- Community plugins or an extension marketplace.
- Formatters for languages other than JSON in the first release (YAML and TOML follow when their parsers allow it).
- Inline diagram rendering and color previews (see the editor previews spec).
User Stories
- As a writer, I want code blocks drawn as rounded cards with a language label, so that code is easy to see.
- As a writer, I want one-click copy and a wrap toggle on every code block, so that I can reuse code quickly.
- As a writer, I want syntax colors for common languages, so that code is easy to read.
- As a writer, I want a typo in a language name to suggest the right one, so that I get highlighting without checking spelling.
- As a writer, I want to install a missing language from a repository, or from my own link after it is checked, so that my languages work.
- As a writer, I want JSON detected, validated and formattable, so that I can fix and copy it easily.
- As a writer, I want to be offered formatting when a block or a lone string looks like JSON, so that I do not have to know the label.
- As a maintainer of many notes, I want a batch format and lint run with a review step, so that no file changes without my approval.
Acceptance Criteria
Design: PaperCraft page "Large vaults and code", artboards "Editor / Code blocks and previews", "Editor / Language picker and grammars" and "Batch / Format and lint review".
Code blocks
- Given a fenced block, when it is drawn, then it is a rounded card with an uppercase language label and a header toolbar that shows Copy (and Wrap) on hover or focus.
- Given the user presses Copy, when it completes, then the block text without the fence lines is on the clipboard and the button confirms with a short state change.
- Given inline code, when it is drawn, then it is a small chip with rounded corners and a calm background that has at least 4.5:1 contrast with its text, in both themes.
- Given a fence with a built-in language (JSON, YAML, TOML, Rust, JavaScript, TypeScript, shell, CSS, HTML, Markdown, Python, diff), when it is drawn, then keywords, strings, numbers, comments and types use the syntax color tokens.
- Given a very long block (over 1,000 lines), when it is drawn, then only the visible lines are highlighted and scrolling stays smooth.
- Given an unknown or missing language, when the block is drawn, then it falls back to plain monospace without errors.
Language picker and grammars
- Given the user types a language name after the fence that is not installed, when the picker opens, then it lists the closest installed or available names and a "keep this name and get more info" item.
- Given a close match exists, when the user picks it, then the fence label is replaced with the chosen name.
- Given the user asks for more info on a language found in the grammar repository, when the panel opens, then it shows name, size, format and license, with Download and Cancel.
- Given the user presses Download, when the grammar is fetched, then it is checked (https only, size cap, parses as a grammar, sample highlights without error) before it is saved to the app config folder.
- Given the language is not in the repository, when the user asks for more info, then the app asks for a link to a grammar file.
- Given the user enters a link, when it is not https, is too large, does not load, or is not a usable grammar, then the matching error is shown inline and the install button stays disabled.
- Given a downloaded grammar fails the check, when the dialog closes, then nothing is saved and the app state is unchanged.
- Given an installed grammar, when the user removes it in settings, then blocks of that language fall back to plain monospace.
- Given no network, when the user asks to download, then a clear offline message appears and the app keeps working.
JSON
- Given a block labeled json, when it is drawn, then a chip shows Valid or the first error with its line, and the error line is marked.
- Given valid JSON, when the user presses Format, then the block is rewritten with two-space indentation and the edit is one undo step.
- Given valid JSON, when the user presses Minify, then the block is rewritten on one line.
- Given a block with no label, or a note body that is one template string, that parses as a JSON object or array, when it is drawn, then an offer card proposes formatting, and nothing changes until the user accepts.
- Given the user dismisses an offer, when the note is reopened, then the offer for the same block does not return.
- Given invalid JSON, when the user presses Format, then the text is not changed.
Batch format and lint
- Given the command palette or Tools menu, when the user opens Batch format and lint, then they choose a scope (current note, folder, whole vault) and the checks to run.
- Given the user presses Run dry run, when it finishes, then nothing has been written and a summary shows counts for Would change, Needs review, Not edited and Already clean.
- Given the run, when a file cannot be processed (invalid JSON, unreadable, locked), then it is listed under Not edited with the reason, never silently skipped.
- Given a file with an uncertain change, when the run ends, then it is listed under Needs review and is not applied by default.
- Given the review screen, when the user selects a file, then every proposed change shows as a before and after diff with Accept and Skip per change, and Accept all in file.
- Given the user moves through the list, when they press Next or Previous, then the review order follows the list and progress shows "Reviewed n of N".
- Given the user presses Apply, when it runs, then only accepted changes are written, each file atomically, and the result is undoable per file.
- Given a file changed on disk after the dry run, when Apply reaches it, then it is skipped and reported instead of overwritten.
- Given the scope is the whole vault, when the run takes more than a second, then it shows progress and can be cancelled without writing anything.
General
- Given any path above, when it runs on hostile input, then it never panics.
- Given a failure to write, when it happens, then the error is shown and the original file is intact.
Constraints
- Stack: Rust only, egui and eframe. The highlighter and formatter live below the UI layer and must not use UI types.
- Performance: highlighting stays off the UI thread for large blocks; opening a note with 50 code blocks adds under 50 ms; grammar load is lazy.
- Security: downloads are https only, size capped, parsed as data and never executed; the user-supplied link is validated before any request; no grammar can run code; writes use the shared atomic-write crate and never touch reserved vault folders.
- Compat: macOS, Windows and Linux. The highlighter uses the approved pure-Rust regex engine, no C library. Grammar format support is confirmed by a prototype before the plan.
- Grammar source: the built-in source is the public NotesCraft registry repository on the owner's Gitea (it also holds plugins, themes and templates). The app reads its index file, then downloads one grammar over https from that host only; each entry's size and checksum are verified. A user-entered link is used only after validation.
- Dependencies: the highlighting crate and its pure-Rust regex backend are approved by the owner; nothing else is added.
- Strings: every label, error and offer goes through the translation tables.
- Design: existing tokens plus eight syntax color tokens in dark and light; 28 pt toolbar buttons on desktop.
Open Questions
- Does the chosen highlighting crate read the grammar file format the registry serves, or is a converter needed? A prototype with one real, licensed grammar answers it; the registry has none yet.
- Is the JSON indent fixed at two spaces, or a setting?
- Should unlabeled blocks that look like JSON be offered formatting in every note, or only in notes the user has opened this session?
- Where does Batch format and lint live (command palette only, or also a menu entry and the folder context menu)?
- What rule set does Apply lint fixes include in the first release, and are table alignment fixes in scope?
- Should the app keep a backup of changed files before Apply, or is per-file undo enough?