b562eeafde
The Visual view's block editor now shares the Source view's task toggle (keys::on_toggle_task). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
17 KiB
17 KiB
Tasks: Obsidian basics
Order = suggested order (value over size). Every task ends with the crate's tests, clippy -D warnings, fmt; cargo xtask i18n for UI strings. Pure logic first, UI second. "Owner" = needs a decision before work starts.
- 1. Find and replace in the open note (engine
find.*pure matcher inblocksoreditor, UI bar with highlight, Next/Previous, Replace, Replace all = one undo step; plain, case, whole word; regex waits on task 11's decision, plain first) - 2. Back/forward navigation history (stack of path, scroll, caret; Cmd-[ / Cmd-], menu items, commands
nav.back/nav.forward) - 3. Auto-pair brackets and quotes (
editor/src/keys.rs: pair, step over, Backspace on empty pair, skip in code fences and after a word character for quotes) - 4. Footnotes: inline
^[..]scanner and a Footnote item innotescraft-markdown, rendering as superscript with jump and back-link in the rendered view - 5. Explorer sort orders (name, modified, created, reverse; setting
ui.explorer_sort, remembered) - 6. Heading folding by click in the rendered view (done; LIST item folding still open) (fold model shared with callouts and vim
za) - 7. Search operators
section:,block:,task:and match-case switch innotescraft-index(+ results UI switch) - 8. Vault-wide search and replace: engine
search.replacedry run + apply (onelint_commitper file, content-hash re-check like batch), review view reuse - 9. Quick switcher heading and block search (
note#heading,note^block) from the existing index - 10. Link hover page preview (400 ms, read-only card, heading/block targets show that part)
- 11. Regex search
/…/and regex in find-and-replace (own linear-time engine innotescraft-regex, so no third-party dependency) - 12. Explorer drag and drop to move files and folders (drop targets,
note.rename, link update, refuse into itself) - 13. Tab pinning and drag reorder
- 14. Settings batch: default view mode for new tabs, editor font size, readable line length, line numbers, strict line breaks, default location for new notes, link format, deleted files behaviour (each only shown when code reads it). DONE: default view, font size, readable line length, new-note folder. line numbers, link format and path (Copy link, linking a mention), deleted files. strict line breaks, show hidden files
- 15. Context menu and commands: Open in default app, Copy Obsidian URL, Outgoing links pane, Open random note, Unique note creator. DONE: random, unique, Open in default app, Copy Obsidian URL. Outgoing links section in the Backlinks tab
- 16. Split view: two panes, per-pane tabs; the second pane is a read-only rendered view, editing stays in the main pane (
view.split, tab menu "Open in split view") - 17. PDF, audio and video embeds as file cards with an Open button (PDF pages render through the system tool, audio plays through the system player, video plays in the note through ffmpeg when installed (else opens in the system player); no decoder library linked)
- 18. Spellcheck without a dictionary: the
spellinglint rule (off by default,session.lint.spelling) flags words the vault uses in fewer than two notes and suggests the closest known word
Round 2: gaps found after round 1 (checked 2026-10-19; none built yet)
Confirmed missing by grep: no query fence handling, no RTL handling. Pop-out and stacked tabs are listed as missing in audit.md.
- 19. Embedded search queries: a ```query fence renders the live results of a vault search (same operators as the search panel), refreshes on index change
Done when: a driven test shows a note with a
queryfence listing the matching notes, and the list changes after a matching note is added. DONE:blocks/query/mod.rs, driven testa_query_block_lists_the_matching_notes_and_follows_new_ones; answers refresh every second, rows open the note (a note holding the query text lists itself, as in Obsidian) - 20. RTL in the reading view. Owner approved
unicode-bidi(2026-10-19). DONE:views/note_view/direction/orders a row whose first strong letter is right-to-left (whole line at once, styles kept, inline math and footnote marks stay whole) andpainting.rsputs the row against the right edge; the UI fonts get the system's Hebrew/Arabic faces as fallbacks (design/fonts.rs); testsdirection::tests(8) anda_hebrew_paragraph_sits_against_the_right_edge_and_an_english_one_stays_left; rendered offscreen and viewed. Limits: Arabic stays in isolated letter forms (egui does not join), Source mode and the caret keep logical order, a wrapped right-to-left paragraph keeps its lines left-aligned inside its block, selection and copy on such rows are not mapped back to the source Done when: a unit test resolves direction for Arabic, Hebrew and Latin lines; an offscreen render shows an Arabic paragraph right-aligned - 21. Stacked tabs: a tab-group mode where tabs stack as collapsed headers
Done when: a driven test toggles stacked mode and the active tab body shows under the other tabs' headers. DONE:
view.stacked_tabs(View menu),widgets/stacked.rs(earlier tabs fold left, later ones right, title written upward, click unfolds), saved in the UI state; teststacked_tabs_fold_the_others_into_strips_and_a_click_unfolds_one. The tab bar stays; no stacking with the split view on - 22. Pop-out window: move a tab to its own OS window (needs a second egui viewport sharing the session)
Done when: a driven test moves a tab to a pop-out and the main window no longer lists it; closing the pop-out returns it. DONE:
view.pop_out(View menu),shell/app/popout.rs: an immediate egui viewport (a native window under eframe on desktop) shows the rendered note read-only through the split pane's drawing; links open in the main window; closing the window returns the note as a tab. Where eframe embeds viewports (web, the offscreen harness) the note goes straight back to its tab. NOT checked in a real native window (the live window never paints in the agent session); the driven test covers the tab hand-off only - 23. Editor follow-ups. DONE: checkboxes inside embeds (
ticking_a_task_inside_an_embed_changes_the_embedded_note), text on an embed's line drawn around it (text_on_the_line_of_an_embed_is_drawn_around_it), embeds follow an edit made in the embedded note's own tab (an_embed_follows_an_edit_made_to_the_embedded_note_in_its_own_tab). block menu by keyboard (Cmd/Ctrl-Alt-B,the_block_menu_opens_from_the_keyboard_at_the_caret; before it was reachable only from the palette) Done when: one driven or unit test per item passes - 24. (DMG checked 2026-10-19 without running the UI: mounts,
codesign --verify --deep --strictpasses, arm64, version 0.1.0, containsview.pop_outandview.stacked_tabs; the window screenshot, a native pop-out window and the Windows run are still open) Hand check of the packaged app: open the DMG build, run the dev hook screenshot and a Windows run of audio/video playback (needs the owner's desktop and a Windows machine) Done when: a PNG of the real window and a Windows result are attached to this file
Round 3: gaps still listed as missing or partial in audit.md (re-audited 2026-10-19; recheck each row before building, some may have been covered by 1-18)
- 25. (UI DONE too: a selection-toolbar button "Extract to a new note" opens a name prompt (first line offered) and moves the passage through the open editor with a link left behind; Explorer menu "Merge into…" opens the note picker and folds the note into the pick through the open editor, refusing a note with unsaved changes; tests
extracting_a_selection_asks_for_a_name_then_moves_it_into_a_new_note_and_links_itandmerging_a_note_into_another_goes_through_the_open_editor_and_a_dirty_note_is_refused; ENGINE:vault/composer/mod.rs, commandsnote.extractandnote.merge, testsengine/tests/note_composer.rs(4); open: the UI, a name prompt for extract on the selection toolbar and a note picker for merge, since the palette cannot take required parameters; links to a merged note are not rewritten) Note composer:note.merge(append a note to another, fix links, trash the source) andnote.extract(move a text range into a new note, leave a[[link]]) Done when: engine integration tests for both commands pass, and each is in the palette - 26. (UI DONE too: a Trash tab, View > Open the trash (
view.trash),views/trash_view/, list with age and origin, Restore per item, Empty asking twice, driven testthe_trash_tab_lists_a_deleted_note_restores_it_and_empties_on_the_second_click; ENGINE: vaulttrash.rsrecords where an item came from (<name>.frombeside it),Vault::trash_list/restore_from_trash/empty_trash, enginetrash.list,trash.restore,trash.empty, tests invault/tests/files.rsandengine/tests/note_composer.rs; an item whose old place is taken stays in the trash; items trashed before this change have no recorded origin and cannot be restored; open: the UI, a Trash list with Restore and Empty) Trash restore and empty: list.notescraft/trash, restore a note to its place, empty the trash Done when: engine command tests and a driven test restoring a deleted note - 27. Folder notes: a folder opens its note (
folder/folder.md) when one exists. DONE:open_folder_or_its_note(double-click on a folder in the Explorer; the context menu's "Open folder view" still opens the view), driven testa_folder_with_a_folder_note_opens_that_note_and_one_without_opens_the_folder_viewDone when: a driven test clicking a folder with a folder note opens the note - 28. (DONE too: result order (relevance, A to Z, Z to A) and collapse to titles, two pills in the results heading, test
search_results_can_be_ordered_by_name_and_collapsed_to_titles; DONE earlier: excluded folders, settingsession.files.excluded_folders, applied to plain and/regex/search, testexcluded_folders_hide_notes_from_plain_and_regex_searchand config unit test, no Settings control yet;#tagin a note click-to-search, driven testclicking_a_tag_in_a_note_searches_for_it; open: result sort order and collapse) Search: excluded files setting, result sort order, collapse results,#tagin a note body click-to-search Done when: one test per item - 29. (DONE both: reading mode
view.reading_mode(View menu; per-tab flagTabEdit.reading, testreading_mode_shows_the_note_but_a_click_and_typing_do_not_change_it); INDENT: settingseditor.indent_with_tabsandeditor.indent_width(1-8), Settings > Editor controls,keys::Indent, tests ineditor/tests/keys.rsandeditor_settings.rs; open: reading-only open) Reading-only open: "Open in reading mode" (no editing) and an indent-with-tab-or-spaces setting Done when: a driven test shows a reading tab ignoring a click-to-edit; a unit test for the indent setting - 30. (ALL BUILT; heading folding in Source mode: a chevron in the left margin beside each heading that has a section; a folded section is laid out at 0.1 pt and transparent, so the text stays one buffer and edits, carets and selections keep working;
editor/src/fold.rs,highlight::layout_job_folded,views/center/source_fold.rs, testsfold::tests,hidden_ranges_keep_every_byte_in_the_job_and_shrink_only_those_bytes, drivena_heading_folds_its_section_in_the_source_view_from_the_margin_chevron; folds are per open tab, keyed by the heading line, and not saved; DONE: "Move to…" in the Explorer menu opens a folder picker (palette kindFolders, the vault root included) and moves a note or folder, refusing a folder into itself, testmove_to_opens_the_folder_picker_and_moves_a_file_or_folder_but_never_into_itself. STRUCK: spellcheck language (the checker is the vault's own vocabulary, it has no language). DONE too: table helpers ("+ row", "− row", "+ column", "− column" under an editable table,Table::add_rowetc. inblocks/table/edit.rs, driven testa_table_gets_a_row_and_a_column_from_the_controls_under_it); user themes (<settings folder>/themes/*.toml,Session::user_themes/theme_named, listed in Settings and pickable, tests inengine/tests/user_themes.rs). DONE too: graph timelapse (a Timelapse toggle in the graph card; notes appear by file creation time over a 12 s run, with play/pause, the date reached and a slider;graph_view/timelapse.rs, tests there andevery_note_has_a_creation_time; undated notes always show). - see above) Smaller rows from the audit: heading folding in Source mode, table editing helpers, "Move file to…" folder picker, graph timelapse, user themes in Settings, spellcheck language Done when: each checked against the code first; built or struck out with the reason
Round 4: basic Obsidian features still missing after round 3 (checked 2026-10-19 against the code; none built)
- 31. (ALREADY BUILT, missed by the round 4 check: Source sidebar tab > History for the open note lists its commits (
git.logwith the path), a click shows the diff to the current text (panels/source_panel/history.rs,diff_view) and Restore asks first and runsgit.restore_version; it needs a git repository (git.initfrom the same panel). OPEN as 31b: snapshots for a vault without git (a local version list every few minutes, Obsidian's File recovery)) Note history (Obsidian's File recovery): per-note snapshots or git history shown as a list with a diff and Restore.git.log/git.showexist in the engine; there is no per-note history view and no snapshots for vaults without git. Done when: a driven test lists the versions of a note, shows the diff to the current text and restores one as one undoable edit - 32. (DONE:
view.save_workspace(name prompt) andview.load_workspace(picker, palette kindWorkspaces) in the View menu; a layout is the tabs in order, the active one, pins, panel widths, panel visibility and the sidebar tab; engineworkspace.list/save/load/delete, files in.notescraft/workspaces/, testsengine/tests/workspaces.rsanda_saved_workspace_brings_the_tabs_and_the_active_one_back_and_skips_notes_that_are_gone; not saved: the split pane and per-note view mode; no delete control in the UI, only the command) Workspaces: save and load the layout (open tabs, split, sidebar tab, panel widths) by name. Done when: a test saves a layout, changes it and loads it back - 33. (DONE: commands
editor.copy_block_link(adds the^idonce) andeditor.copy_heading_link, and "Copy link to this heading" in the Outline row's context menu;blocks::ensure_block_id,FilesConfig::link_text_to(wiki or Markdown links), tests inblocks/src/ids.rsandcopying_a_block_link_adds_the_id_once_and_copying_a_heading_link_needs_no_edit) Copy a heading or block link from the editor ([[Note#Heading]],[[Note#^id]]) and a command to add a block id. Done when: a test produces the link text for a heading and for a block and adds a missing^idonce - 34. (DEFERRED by the owner 2026-10-19: a click keeps opening the whole block as source) Live Preview editing in the Visual view for inline syntax (the Visual view opens a whole block as source on click; Obsidian edits the rendered line in place).
Done when: decided with the owner first (large change to
note_view) - 35. Hand checks that need a real window: task 24 plus the Linux and Windows items in ROADMAP "Linux and Windows feedback".
- 31b. (DONE: every save of a changed note keeps a snapshot at most every 5 minutes, newest 20 in
.notescraft/history/(vault/snapshots/, hooked intosave_buffer), enginehistory.listandhistory.read; View > Note versions opens a Versions tab listing them with Compare (the diff page) and Restore (one undoable edit); tests insnapshots,engine/tests/workspaces.rsandthe_versions_tab_lists_a_snapshot_and_restores_it_over_the_current_text; the Source panel's History still needs git) Local snapshots of a note for a vault without git: saved on write when the text changed and the last one is a few minutes old, newest 20 kept in.notescraft/history/, listed with a diff and Restore. Done when: engine tests for snapshot, list and restore (one undoable edit) pass and the Source panel's History lists them when there is no repository
Round 5: small basics found by reading the key handling (2026-10-19)
- 36. Cmd/Ctrl+Enter toggles a task on the caret line in the Source view: ticks or unticks a box, adds a box to a list item, makes a plain or empty line a
- [ ]task (keys::on_toggle_task, testcmd_enter_ticks_unticks_and_makes_tasks). - 37. (DONE:
read_keysinnote_view/edit.rscalls the sameon_toggle_task; driven testcmd_enter_in_an_open_visual_block_ticks_the_task) The same key in the Visual view's block editor (it has its own key handling innote_view/edit, so the Source version does not reach it). Done when: a driven test presses Cmd/Ctrl+Enter in an open block and the task box flips