Files
Marcos Paulo b32650767c
ci / ci (macos-latest) (push) Has been cancelled
ci / ci (ubuntu-latest) (push) Has been cancelled
ci / ci (windows-latest) (push) Has been cancelled
docs: specs and briefs for smart folders, version diff, media timestamps, clean paste, link cards, spaced repetition
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 12:18:00 -03:00

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)

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

  1. 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.
  2. smartfolder.items on a 15 000 note vault, 2 000 matches, first page of 200: < 50 ms warm (ignored perf test).
  3. Deleting a smart folder never touches a note file (test: vault tree identical).
  4. Corrupt smart-folders.json leaves the file byte-identical and the Explorer shows no group.
  5. Collapsed folders cost 0 engine calls per frame (counter test).
  6. Read-only vault refuses save/delete, lists still work.

Open questions

  • Q1: inline yaml vs .base reference as the default for "New smart folder": proposal, inline for quick filters, .base when made from a Base tab.
  • Q2: should .notescraft/smart-folders.json be in the sync include list (vs per-device)? Proposal: synced, it is small user data.

Feature ideas (researched, ranked)

  1. Full-text term filter in the definition (search: "foo") using the search index; needs a base filter function file.matches(".."): ask the Bases owner. Apple Notes and Bear saved searches combine text + tags.
  2. Quick-create from the search panel: "Save search as smart folder" (Bear's saved searches are exactly this).
  3. 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.items as an agent tool.