feat: add favorites section with star button, frontmatter persistence, and drag-to-reorder

Adds FAVORITES sidebar section backed by _favorite and _favorite_index
frontmatter properties. Star button in breadcrumb bar toggles favorite
state. Drag-to-reorder updates _favorite_index on all affected notes.
Section auto-hides when empty.

ADR 0038 documents the decision to use frontmatter for portability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Test
2026-04-02 14:44:23 +02:00
parent 931fed879b
commit 33db64822d
14 changed files with 256 additions and 7 deletions

View File

@@ -59,6 +59,11 @@ pub struct VaultEntry {
pub view: Option<String>,
/// Whether this Type is visible in the sidebar. Defaults to true when absent.
pub visible: Option<bool>,
/// Whether this note is a user favorite (shown in FAVORITES sidebar section).
pub favorite: bool,
/// Display order within the FAVORITES section (lower = higher).
#[serde(rename = "favoriteIndex")]
pub favorite_index: Option<i64>,
/// Word count of the note body (excludes frontmatter and H1 title).
#[serde(rename = "wordCount")]
pub word_count: u32,

View File

@@ -45,6 +45,14 @@ pub(crate) struct Frontmatter {
pub view: Option<StringOrList>,
#[serde(default)]
pub visible: Option<bool>,
#[serde(
rename = "_favorite",
default,
deserialize_with = "deserialize_bool_or_string"
)]
pub favorite: Option<bool>,
#[serde(rename = "_favorite_index", default)]
pub favorite_index: Option<i64>,
}
/// Custom deserializer for boolean fields that may arrive as strings.
@@ -154,6 +162,8 @@ fn parse_frontmatter(data: &HashMap<String, serde_json::Value>) -> Frontmatter {
"notion_id",
"Status",
"status",
"_favorite",
"_favorite_index",
];
let filtered: serde_json::Map<String, serde_json::Value> = data
.iter()
@@ -184,6 +194,8 @@ const SKIP_KEYS: &[&str] = &[
"view",
"visible",
"status",
"_favorite",
"_favorite_index",
];
/// Extract all wikilink-containing fields from raw YAML frontmatter.

View File

@@ -102,6 +102,8 @@ pub fn parse_md_file(path: &Path) -> Result<VaultEntry, String> {
sort: frontmatter.sort.and_then(|v| v.into_scalar()),
view: frontmatter.view.and_then(|v| v.into_scalar()),
visible: frontmatter.visible,
favorite: frontmatter.favorite.unwrap_or(false),
favorite_index: frontmatter.favorite_index,
word_count,
outgoing_links,
properties,