feat: remove active knowledge support

This commit is contained in:
2026-07-16 07:30:00 +09:00
parent f2106407be
commit f786e01997
54 changed files with 197 additions and 1591 deletions
+1 -25
View File
@@ -595,7 +595,6 @@ impl App {
self.input_history.cancel_browse();
match kind {
CompletionKind::File => self.input.replace_with_file_ref(start, value),
CompletionKind::Knowledge => self.input.replace_with_knowledge_ref(start, value),
}
self.completion = None;
true
@@ -607,7 +606,6 @@ impl App {
/// suggestion" — partial typing like `@README.` followed by
/// Enter should chip when the popup is on `README.md`.
///
/// Files and Knowledge entries chipify here. Directory file entries return `false`
/// so the caller can fall through to `apply_completion_text`
/// for drill-in — chip-ifying a directory on Enter would strand
/// the user with no way to inspect children.
@@ -628,7 +626,6 @@ impl App {
self.input_history.cancel_browse();
match kind {
CompletionKind::File => self.input.replace_with_file_ref(start, value),
CompletionKind::Knowledge => self.input.replace_with_knowledge_ref(start, value),
}
self.completion = None;
true
@@ -2166,13 +2163,13 @@ impl App {
self.blocks.push(Block::WorkerEvent { event });
}
session_store::SystemItem::FileAttachment { body, .. }
| session_store::SystemItem::Knowledge { body, .. }
| session_store::SystemItem::TaskReminder { body, .. }
| session_store::SystemItem::Interrupt { body } => {
self.task_store.apply_system_message_text(&body);
self.blocks.push(Block::SystemMessage { text: body });
}
session_store::SystemItem::LegacyIgnored { .. } => {}
session_store::SystemItem::LegacyKnowledgeIgnored { .. } => {}
}
}
@@ -2969,24 +2966,6 @@ mod completion_flow_tests {
assert!(app.completion.is_some());
}
#[test]
fn outdated_completions_event_is_dropped() {
let mut app = App::new("test".into());
for c in "@x".chars() {
app.insert_char(c);
}
let _ = app.refresh_completion();
// Reply for a different kind shouldn't overwrite state.
app.handle_worker_event(Event::Completions {
kind: CompletionKind::Knowledge,
entries: vec![CompletionEntry {
value: "stale".into(),
is_dir: false,
}],
});
assert!(app.completion.as_ref().unwrap().entries.is_empty());
}
#[test]
fn committed_user_message_survives_fresh_segment_rotation() {
let mut app = App::new("test".into());
@@ -3697,9 +3676,6 @@ mod completion_flow_tests {
Segment::Text {
content: " and ".into(),
},
Segment::KnowledgeRef {
slug: "design-note".into(),
},
Segment::Paste {
id: 1,
chars: 13,
+6 -46
View File
@@ -46,24 +46,11 @@ impl FileRefAtom {
}
}
/// `#<slug>` chip — confirmed completion of a Knowledge reference.
#[derive(Debug, Clone)]
pub struct KnowledgeRefAtom {
pub slug: String,
}
impl KnowledgeRefAtom {
pub fn label(&self) -> String {
format!("#{}", self.slug)
}
}
#[derive(Debug, Clone)]
pub enum Atom {
Char(char),
Paste(PasteRef),
FileRef(FileRefAtom),
KnowledgeRef(KnowledgeRefAtom),
}
impl Atom {
@@ -74,7 +61,6 @@ impl Atom {
Atom::Char(_) => None,
Atom::Paste(p) => Some((Style::default().fg(Color::Magenta), p.label())),
Atom::FileRef(r) => Some((Style::default().fg(Color::Cyan), r.label())),
Atom::KnowledgeRef(r) => Some((Style::default().fg(Color::Green), r.label())),
}
}
}
@@ -83,7 +69,7 @@ impl Atom {
enum AtomClass {
Word(WordKind),
Sep,
/// Indivisible chip — paste / file ref / knowledge ref. Word motion treats one chip as one unit; deletion
/// Indivisible chip — paste / file ref. Word motion treats one chip as one unit; deletion
/// removes the whole atom.
Chip,
}
@@ -103,7 +89,7 @@ enum WordKind {
fn atom_class(atom: &Atom) -> AtomClass {
match atom {
Atom::Char(c) => char_class(*c),
Atom::Paste(_) | Atom::FileRef(_) | Atom::KnowledgeRef(_) => AtomClass::Chip,
Atom::Paste(_) | Atom::FileRef(_) => AtomClass::Chip,
}
}
@@ -195,10 +181,6 @@ impl InputBuffer {
self.atoms
.push(Atom::FileRef(FileRefAtom { path: path.clone() }));
}
protocol::Segment::KnowledgeRef { slug } => {
self.atoms
.push(Atom::KnowledgeRef(KnowledgeRefAtom { slug: slug.clone() }));
}
protocol::Segment::Unknown => {
self.atoms
.extend("[unknown input segment]".chars().map(Atom::Char));
@@ -226,7 +208,6 @@ impl InputBuffer {
Atom::Char(c) => text.push(*c),
Atom::Paste(paste) => text.push_str(&paste.content),
Atom::FileRef(file) => text.push_str(&file.path),
Atom::KnowledgeRef(knowledge) => text.push_str(&knowledge.slug),
}
}
text
@@ -264,13 +245,6 @@ impl InputBuffer {
self.cursor = start + 1;
}
pub fn replace_with_knowledge_ref(&mut self, start: usize, slug: String) {
self.atoms.drain(start..self.cursor);
self.atoms
.insert(start, Atom::KnowledgeRef(KnowledgeRefAtom { slug }));
self.cursor = start + 1;
}
/// Replace `atoms[start..self.cursor]` with the chars of `text`,
/// leaving cursor at the end of the inserted run. Used by the Tab
/// completion path: the popup-selected entry is inserted as raw
@@ -286,7 +260,7 @@ impl InputBuffer {
self.cursor = idx;
}
/// If the cursor is currently inside a `@<typed>` / `#<typed>` /
/// If the cursor is currently inside a `@<typed>` /
/// `/<typed>` token that satisfies the trigger rules, return the
/// kind, the index of the leading sigil atom, and the typed text
/// after the sigil (sigil itself excluded).
@@ -311,7 +285,7 @@ impl InputBuffer {
}
let kind = match c {
'@' => Some(protocol::CompletionKind::File),
'#' => Some(protocol::CompletionKind::Knowledge),
_ => None,
};
if let Some(k) = kind {
@@ -480,7 +454,7 @@ impl InputBuffer {
/// Build the typed `Vec<Segment>` sent over the protocol. Adjacent
/// `Atom::Char`s are concatenated into a single `Segment::Text`; each
/// chip atom (`Paste` / `FileRef` / `KnowledgeRef` )
/// chip atom (`Paste` / `FileRef`)
/// becomes a standalone `Segment` so that clients re-rendering an
/// `Event::UserMessage` see the same indivisible chip rather than a
/// flattened string.
@@ -510,12 +484,6 @@ impl InputBuffer {
path: r.path.clone(),
});
}
Atom::KnowledgeRef(r) => {
flush_text(&mut buf, &mut out);
out.push(protocol::Segment::KnowledgeRef {
slug: r.slug.clone(),
});
}
}
}
if !buf.is_empty() {
@@ -1028,14 +996,6 @@ mod completion_prefix_tests {
assert_eq!(prefix, "sr");
}
#[test]
fn hash_sigil_triggers_knowledge_completion() {
let buf = buf_from("#abc");
let (kind, _, prefix) = buf.pending_completion_prefix().unwrap();
assert_eq!(kind, CompletionKind::Knowledge);
assert_eq!(prefix, "abc");
}
#[test]
fn newline_before_cursor_invalidates_trigger() {
let buf = buf_from("@a\nbc");
@@ -1234,7 +1194,7 @@ mod word_motion_tests {
for a in &buf.atoms {
match a {
Atom::Char(c) => out.push(*c),
Atom::Paste(_) | Atom::FileRef(_) | Atom::KnowledgeRef(_) => out.push_str("<P>"),
Atom::Paste(_) | Atom::FileRef(_) => out.push_str("<P>"),
}
}
out
+1 -3
View File
@@ -965,7 +965,7 @@ fn push_padded_lines(lines: &mut Vec<Line<'static>>, text: &str, kind: MessageKi
/// Render `Block::UserMessage` from typed segments. Each non-text
/// segment renders as a one-piece chip whose colour matches the input
/// area's chip presentation (paste = magenta, `@` file = cyan,
/// `#` knowledge = green, `/` workflow = yellow), so the user
/// `/` workflow = yellow), so the user
/// recognises their own typed atoms in the scrollback.
fn render_user_message(
lines: &mut Vec<Line<'static>>,
@@ -1097,7 +1097,6 @@ fn chip_span_for(seg: &Segment, fallback: Style) -> (Style, String) {
format!("[Clipboard #{id} | {chars} chars, {line_count} lines]"),
),
Segment::FileRef { path } => (Style::default().fg(Color::Cyan), format!("@{path}")),
Segment::KnowledgeRef { slug } => (Style::default().fg(Color::Green), format!("#{slug}")),
Segment::Unknown => (fallback, "[unknown segment]".to_owned()),
}
}
@@ -1112,7 +1111,6 @@ fn segment_display_text(seg: &Segment) -> String {
id, chars, lines, ..
} => format!("[Clipboard #{id} | {chars} chars, {lines} lines]"),
Segment::FileRef { path } => format!("@{path}"),
Segment::KnowledgeRef { slug } => format!("#{slug}"),
Segment::Unknown => "[unknown segment]".to_owned(),
}
}