From 0c2d5614c102f2c35989875efec2010436b99378 Mon Sep 17 00:00:00 2001 From: Jean Mertz Date: Thu, 25 Jun 2026 14:25:09 +0200 Subject: [PATCH 1/2] feat(attachment_bear_note): Add `exclude_archived` search param Bear's `search` attachment URI now accepts an `exclude_archived=true` query parameter. When set, archived notes are dropped from the results before their content is fetched, so the assistant never sees stale or retired notes even when they match the tag or query. Archived notes still count as matches under the hood; they're simply not returned. This is useful for tag-based searches where archived notes accumulate over time and would otherwise pollute context with outdated content. The `_bear-note` justfile recipe now appends `&exclude_archived=true` to every Bear search URI it constructs, making this the default behaviour for all project-tooling lookups. Signed-off-by: Jean Mertz --- crates/jp_attachment_bear_note/README.md | 7 +++ crates/jp_attachment_bear_note/src/lib.rs | 62 ++++++++++++++----- .../jp_attachment_bear_note/src/lib_tests.rs | 32 ++++++++++ justfile | 7 ++- 4 files changed, 91 insertions(+), 17 deletions(-) diff --git a/crates/jp_attachment_bear_note/README.md b/crates/jp_attachment_bear_note/README.md index 73b9917e..7a3a2043 100644 --- a/crates/jp_attachment_bear_note/README.md +++ b/crates/jp_attachment_bear_note/README.md @@ -36,6 +36,13 @@ Fetch a list of notes tagged with a specific tag: jp attachment add "bear://search/?tag=project/my-project" ``` +Exclude archived notes from a search. Archived notes still match, but their +content is left out of the attachment: + +```sh +jp attachment add "bear://search/?tag=project/my-project&exclude_archived=true" +``` + List all added URIs: ```sh diff --git a/crates/jp_attachment_bear_note/src/lib.rs b/crates/jp_attachment_bear_note/src/lib.rs index 51ee8f78..805884f6 100644 --- a/crates/jp_attachment_bear_note/src/lib.rs +++ b/crates/jp_attachment_bear_note/src/lib.rs @@ -27,14 +27,20 @@ impl BearNotes { fn query_to_uri(&self, query: &Query) -> Result> { let (host, path, query_pairs) = match query { Query::Get(path) => ("get", path, vec![]), - Query::Search { query, tags } => ( - "search", + Query::Search { query, - tags.clone() + tags, + exclude_archived, + } => { + let mut pairs: Vec<(String, String)> = tags .iter() .map(|t| ("tag".to_owned(), t.to_owned())) - .collect::>(), - ), + .collect(); + if *exclude_archived { + pairs.push(("exclude_archived".to_owned(), "true".to_owned())); + } + ("search", query, pairs) + } }; let query_pairs = query_pairs @@ -64,7 +70,16 @@ enum Query { Get(String), /// Search for a note by its title or content, optionally filtering by tags. - Search { query: String, tags: Vec }, + Search { + query: String, + tags: Vec, + + /// Drop archived notes from the results instead of attaching their full + /// content. + /// Archived notes still match; they're just not returned. + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + exclude_archived: bool, + }, } /// A note from the Bear note-taking app, formatted for attachment XML output. @@ -179,12 +194,21 @@ fn uri_to_query(uri: &Url) -> Result> { .map(Query::Get)?, Some("get") => Query::Get(path), Some("search") => { - let tags = query_pairs - .into_iter() - .filter_map(|(k, v)| if k == "tag" { Some(v) } else { None }) - .collect(); + let mut tags = vec![]; + let mut exclude_archived = false; + for (k, v) in query_pairs { + match k.as_str() { + "tag" => tags.push(v), + "exclude_archived" => exclude_archived = v != "false" && v != "0", + _ => {} + } + } - Query::Search { query: path, tags } + Query::Search { + query: path, + tags, + exclude_archived, + } } // Shorthand: `bear:NOTE_ID`. Truly opaque form (no `//`, no // leading `/`) — the path holds the note id directly. Gated on @@ -207,7 +231,11 @@ fn get_notes(query: &Query, db: &BearDb) -> Result, Box { + Query::Search { + query, + tags, + exclude_archived, + } => { let matches = db .search(&SearchParams { queries: vec![query.clone()], @@ -216,8 +244,14 @@ fn get_notes(query: &Query, db: &BearDb) -> Result, Box = matches.iter().map(|m| m.note_id.as_str()).collect(); + // Archived notes match like any other. When excluded, drop them + // before fetching content so their (often large) bodies never + // reach the assistant. + let ids: Vec<_> = matches + .iter() + .filter(|m| !*exclude_archived || !m.is_archived) + .map(|m| m.note_id.as_str()) + .collect(); db.get_notes(&ids) .map_err(|e| e.to_string())? .into_iter() diff --git a/crates/jp_attachment_bear_note/src/lib_tests.rs b/crates/jp_attachment_bear_note/src/lib_tests.rs index 8cbeb275..ea5fb429 100644 --- a/crates/jp_attachment_bear_note/src/lib_tests.rs +++ b/crates/jp_attachment_bear_note/src/lib_tests.rs @@ -53,6 +53,7 @@ fn test_uri_to_query() { Ok(Query::Search { query: "tag #1".to_string(), tags: vec![], + exclude_archived: false, }), ), ( @@ -60,6 +61,7 @@ fn test_uri_to_query() { Ok(Query::Search { query: "tag #1".to_string(), tags: vec!["tag #2".to_string()], + exclude_archived: false, }), ), ( @@ -67,6 +69,23 @@ fn test_uri_to_query() { Ok(Query::Search { query: "tag #1".to_string(), tags: vec!["tag #2".to_string(), "tag #3".to_string()], + exclude_archived: false, + }), + ), + ( + "bear://search/?tag=foo&exclude_archived=true", + Ok(Query::Search { + query: String::new(), + tags: vec!["foo".to_string()], + exclude_archived: true, + }), + ), + ( + "bear://search/?tag=foo&exclude_archived=false", + Ok(Query::Search { + query: String::new(), + tags: vec!["foo".to_string()], + exclude_archived: false, }), ), ( @@ -81,3 +100,16 @@ fn test_uri_to_query() { assert_eq!(query, expected); } } + +#[test] +fn test_exclude_archived_round_trips() { + let handler = BearNotes::default(); + let query = Query::Search { + query: String::new(), + tags: vec!["rfd/D46/review".to_string()], + exclude_archived: true, + }; + + let uri = handler.query_to_uri(&query).unwrap(); + assert_eq!(uri_to_query(&uri).unwrap(), query); +} diff --git a/justfile b/justfile index 3ec4ad17..4b0bd322 100644 --- a/justfile +++ b/justfile @@ -1926,8 +1926,9 @@ _resolve-conversation TITLE: # Internal: look up a Bear note (or notes) by tag. # -# Resolves `bear://search/?tag=TAG` against the local Bear database. Outputs -# one of: +# Resolves `bear://search/?tag=TAG` against the local Bear database. Archived +# notes are excluded: they're kept for reference, not for feeding into a +# session. Outputs one of: # # FOUND - at least one note matched; caller should attach URI # EDIT - no notes matched; caller should add `--edit` @@ -1941,7 +1942,7 @@ _bear-note TAG: #!/usr/bin/env sh set -eu - uri="bear://search/?tag={{TAG}}" + uri="bear://search/?tag={{TAG}}&exclude_archived=true" if jp attachment print "$uri" 2>/dev/null | grep -q .; then echo "FOUND $uri" exit 0 From 2268334e2b6cf10e2f514bf3e3ae743a06da18f9 Mon Sep 17 00:00:00 2001 From: Jean Mertz Date: Thu, 25 Jun 2026 14:31:40 +0200 Subject: [PATCH 2/2] fixup! feat(attachment_bear_note): Add `exclude_archived` search param Signed-off-by: Jean Mertz --- crates/jp_attachment_bear_note/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/jp_attachment_bear_note/README.md b/crates/jp_attachment_bear_note/README.md index 7a3a2043..3fd9f6bb 100644 --- a/crates/jp_attachment_bear_note/README.md +++ b/crates/jp_attachment_bear_note/README.md @@ -36,8 +36,8 @@ Fetch a list of notes tagged with a specific tag: jp attachment add "bear://search/?tag=project/my-project" ``` -Exclude archived notes from a search. Archived notes still match, but their -content is left out of the attachment: +Exclude archived notes from a search. +Archived notes still match, but their content is left out of the attachment: ```sh jp attachment add "bear://search/?tag=project/my-project&exclude_archived=true"