5.5 KiB
Smart folders: saved-query virtual folders in the Explorer (agy gap 9, roadmap 26)
Part of specs/plugins-parity/. Built on the Bases query engine (crates/bases, engine base.query); no new crate.
Research (2026-10-21; agy claims checked)
- Apple Notes: native Smart Folders (criteria: tags, mentions, checklists, dates); a smart folder holds references, not notes. https://support.apple.com/en-gb/guide/notes/apd58edc7964/4.13/mac/27
- Bear: agy listed Bear as having them. WRONG: Bear has no smart folders, only a saved-searches section that users ask to extend. https://community.bear.app/t/will-bear-2-0-have-smart-folders/9964 and https://community.bear.app/t/allow-customizing-of-saved-searches-in-the-notes-section-of-the-sidebar/12298
- Joplin: agy claim NOT verified (search returned nothing); dropped.
- Obsidian: Dataview/Bases embeds are the workaround; saved searches are in core Search. Demand shows in https://noteappsfeatures.featureupvote.com/suggestions/581809/smart-folders-or-saved-search
- agy said "BM25 queries": NotesCraft has two engines (search index and Bases). We use Bases filters only (typed, property aware); full-text term as a filter is Feature idea 1.
Problem
A note lives in one folder; "all notes tagged #urgent in Projects edited this week" needs a query tab each time. Users want it as a clickable node in the tree.
Goal
A smart folder is a named, saved Bases query shown in the Explorer under a "Smart folders" group. Expanding it lists matching notes as virtual rows (no files copied). Rows open notes; drag/rename/delete of a row acts on the real note.
Non-goals (v1)
Nesting smart folders; editing results inline; sorting beyond the base view's sort; sync of the definitions between devices (the JSON file syncs like any .notescraft/ file only if the sync feature already includes it: Q2).
Design decisions
D1 Storage
.notescraft/smart-folders.json, written ONLY with vault.write_private (atomic). Shape:
{"version":1,"folders":[{"id":"sf_ab12","name":"Urgent","icon":"flame","yaml":"filters:\n and:\n - file.hasTag(\"urgent\")\nviews:\n - type: table\n name: x\n","limit":500}]}
yaml is an inline base (exactly what base.query param yaml takes), or path (a .base file + view). Exactly one of the two. Limits: 200 folders, name 1..80 chars, yaml 16 KiB, ids sf_ + 4..12 [a-z0-9]. Invalid file: ignored with a toast, never rewritten.
D2 Engine (crates/engine/src/smart_folders/{mod,model,store,commands}.rs)
| id | params | result | writes |
|---|---|---|---|
smartfolder.list |
none | {folders:[{id,name,icon,count}]} count via base.query limit 1 total |
no |
smartfolder.items |
{id, offset?, limit?} (default 200, max 2000) |
{items:[{path,name}], total} |
no |
smartfolder.save |
{id?, name, yaml? , path?, view?, icon?} |
{id} |
yes |
smartfolder.delete |
{id} |
{id} |
yes |
items calls the same code as base.query (reuse crate::bases::commands internals via a pub(crate) fn run_query, add if missing) and keeps only the file column paths. Add the four ids to ai/assistant_gate.rs (list/items READ, save/delete WRITE) and tests/core_commands.rs CORE_IDS. |
D3 UI
panels/tree.rs gets a group header row "Smart folders" above the vault root rows when at least one exists; rows are built in panels/tree/rows.rs::flatten as a second FlatRow kind (virtual: true). Counts and items come from ONE smartfolder.list snapshot refreshed on index generation change (copy the decor snapshot pattern of icons-banners D5); items are fetched on expansion only. Context menu on the group/row: New smart folder... (name + pick a .base/inline filter template), Edit filter (opens the base tab with the yaml), Rename, Delete (ConfirmModal). Command "Save this view as smart folder" on a Base tab menu. Strings in [ui.smart_folders] appended at END of en.toml.
D4 Safety
Hostile yaml reuses Bases parser limits. A folder whose query errors shows a danger-coloured row with the error in a tooltip; no panic, no retry per frame.
Acceptance criteria
- Saving a folder with filter
file.hasTag("urgent")shows it in the Explorer; expanding lists exactly the notes with that tag; a new tagged note appears after the next index generation. smartfolder.itemson a 15 000 note vault, 2 000 matches, first page of 200: < 50 ms warm (ignored perf test).- Deleting a smart folder never touches a note file (test: vault tree identical).
- Corrupt
smart-folders.jsonleaves the file byte-identical and the Explorer shows no group. - Collapsed folders cost 0 engine calls per frame (counter test).
- Read-only vault refuses save/delete, lists still work.
Open questions
- Q1: inline yaml vs
.basereference as the default for "New smart folder": proposal, inline for quick filters,.basewhen made from a Base tab. - Q2: should
.notescraft/smart-folders.jsonbe in the sync include list (vs per-device)? Proposal: synced, it is small user data.
Feature ideas (researched, ranked)
- Full-text term filter in the definition (
search: "foo") using the search index; needs abasefilter functionfile.matches(".."): ask the Bases owner. Apple Notes and Bear saved searches combine text + tags. - Quick-create from the search panel: "Save search as smart folder" (Bear's saved searches are exactly this).
- Count badge refresh + pin to top of the sidebar; 4. Smart folder as a Homepage target (roadmap 15); 5. Icon from the icons-banners picker; 6. MCP exposure:
smartfolder.itemsas an agent tool.