A deletes 1808-line legacy/parser.rs and adds a fully integrated parser (server/src/parser.rs + parser_action.rs + parser_render.rs + ui_html.rs changes + JS event wiring + integration tests) that wires the Reddit graph into live /ui morphs and primary_completion handling. B only appends one 40-line test in ranking.rs exercising an already-working spanning-tree scenario. The refactor+integration in A produces durable structure and behavior; B is incremental verification.
constitution · epochs · watch · epoch 3
comparison
c_b3cb2762569d (tommy-mor) vs c_b7cc67300477 (tommy-mor)
download prompt · raw event · cmp_f3150c8ff691a7
council reasoning
sides
A — c_b3cb2762569d (tommy-mor)
message
[604a14ad] nice
diff preview
diff --git a/.gitignore b/.gitignore
index 16de5edb7185b04ef5bc64512814d7dfe2c1f50c..73e8f22cf0d39c706e7cdce5e39f1903a0f9181b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@
/.lsp/
*.swp
.DS_Store
+data/
+repomix-output.xml
diff --git a/legacy/parser.rs b/legacy/parser.rs
deleted file mode 100644
index 2b87b974f8d1dd93bee35681d87668a94e4ef349..0000000000000000000000000000000000000000
--- a/legacy/parser.rs
+++ /dev/null
@@ -1,1808 +0,0 @@
-use std::collections::HashMap;
-use std::rc::Rc;
-use std::cell::RefCell;
-use crate::ui::action::UIAction;
-use crate::ui::types::{Suggestion, GuideOption, ScrollingSuggestion};
-
-// --- Core Abstractions ---
-
-/// Unique identifier for nodes in the graph
-type NodeId = &'static str;
-
-/// Pattern matching for edges
-#[derive(Debug, Clone)]
-pub enum EdgePattern {
- /// Matches exact literal string
- Literal(&'static str),
-
- /// Matches any prefix of a string and suggests the full string
- /// e.g., PrefixOf("reddit.com") matches "r", "re", "red", "reddit", "reddit.com"
- PrefixOf(&'static str),
-
- /// Captures a variable segment (e.g., subreddit name, username)
- Variable(&'static str),
-
- /// Matches any string (wildcard)
- Any,
-}
-
-impl EdgePattern {
- /// Try to match this pattern against input, return (consumed_chars, captured_value)
- fn matches(&self, input: &str) -> Option<(usize, Option<String>)> {
- match self {
- EdgePattern::Literal(lit) => {
- if input.starts_with(lit) {
- Some((lit.len(), None))
- } else {
- None
- }
- }
- EdgePattern::PrefixOf(target) => {
- // Check if input is a prefix of target
- if target.starts_with(input) && !input.is_empty() {
- // It's a valid prefix
- Some((input.len(), None))
- } else if input.starts_with(target) {
- // Full match
- Some((target.len(), None))
- } else {
- None
- }
- }
- EdgePattern::Variable(var_name) => {
- // Consume until next '/' or end of string
- let end = input.find('/').unwrap_or(input.len());
- if end > 0 {
- let captured = input[..end].to_string();
- // Validate based on variable type
- if is_valid_variable(var_name, &captured) {
- Some((end, Some(captured)))
- } else {
- None
- }
- } else {
- None
- }
- }
- EdgePattern::Any => {
- // Match everything until next '/' or end
- let end = input.find('/').unwrap_or(input.len());
- if end > 0 {
- Some((end, Some(input[..end].to_string())))
- } else {
- None
- }
- }
- }
- }
-
- /// Get the completion suggestion for this pattern
- fn completion(&self, partial: &str) -> Option<String> {
- match self {
- EdgePattern::PrefixOf(target) => {
- if target.starts_with(partial) && partial != *target {
- Some(target.to_string())
- } else {
- None
- }
- }
- _ => None,
- }
- }
-}
-
-/// Edge in the graph
-pub struct Edge {
- pattern: EdgePattern,
- target: NodeId,
- /// Optional description for autocomplete
- description: Option<&'static str>,
-}
-
-/// Handler function for generating UI actions
-type Handler = Box<dyn Fn(&str, &str, &HashMap<String, String>) -> UIAction>;
-
-/// Node in the graph
-pub struct Node {
- #[allow(dead_code)]
- id: NodeId,
- edges: Vec<Edge>,
- handler: Option<Handler>,
-}
-
-/// The composable parser graph
-pub struct Graph {
- nodes: HashMap<NodeId, Rc<RefCell<Node>>>,
- root: NodeId,
-}
-
-// --- Graph Builder (Fluent API) ---
-
-pub struct GraphBuilder {
- nodes: HashMap<NodeId, Rc<RefCell<Node>>>,
- current_node: Option<NodeId>,
- root: NodeId,
-}
-
-impl GraphBuilder {
- pub fn new() -> Self {
- let mut nodes = HashMap::new();
- let root_node = Rc::new(RefCell::new(Node {
- id: "root",
- edges: Vec::new(),
- handler: None,
- }));
- nodes.insert("root", root_node);
-
- GraphBuilder {
- nodes,
- current_node: Some("root"),
- root: "root",
- }
- }
-
- /// Select a node to add edges to
- pub fn at(mut self, node_id: NodeId) -> Self {
- // Create node if it doesn't exist
- if !self.nodes.contains_key(node_id) {
- let node = Rc::new(RefCell::new(Node {
- id: node_id,
- edges: Vec::new(),
- handler: None,
- }));
- self.nodes.insert(node_id, node);
- }
- self.current_node = Some(node_id);
- self
- }
-
- /// Add an edge from the current node
- pub fn edge(self, pattern: EdgePattern, target: NodeId) -> Self {
- self.edge_with_desc(pattern, target, None)
- }
-
- /// Add an edge with description
- pub fn edge_with_desc(mut self, pattern: EdgePattern, target: NodeId, desc: Option<&'static str>) -> Self {
- let current = self.current_node.expect("No current node selected");
-
- // Create target node if it doesn't exist
- if !self.nodes.contains_key(target) {
- let node = Rc::new(RefCell::new(Node {
- id: target,
- edges: Vec::new(),
- handler: None,
- }));
- self.nodes.insert(target, node);
- }
-
- // Add edge to current node
- if let Some(node) = self.nodes.get(current) {
- node.borrow_mut().edges.push(Edge {
- pattern,
- target,
- description: desc,
- });
- }
-
- self
- }
-
- /// Set handler for current node
- pub fn handler<F>(self, handler: F) -> Self
- where
- F: Fn(&str, &str, &HashMap<String, String>) -> UIAction + 'static
- {
- let current = self.current_node.expect("No current node selected");
- if let Some(node) = self.nodes.get(current) {
- node.borrow_mut().handler = Some(Box::new(handler));
- }
- self
- }
-
- /// Build the final graph
- pub fn build(self) -> Graph {
- Graph {
- nodes: self.nodes,
- root: self.root,
- }
- }
-}
-
-// --- Parser Implementation ---
-
-impl Graph {
- pub fn parse(&self, input: &str) -> UIAction {
- let normalized = input.trim().to_lowercase();
- let mut state = ParserState {
- input: &normalized,
- cursor: 0,
- current_node_id: self.root,
- context: HashMap::new(),
- original_query: input.to_string(),
- current_prefix: String::new(),
- };
-
- self.parse_recursive(&mut state)
- }
-
- fn parse_recursive(&self, state: &mut ParserState) -> UIAction {
- let node = self.nodes.get(state.current_node_id)
- .expect("Node not found in graph");
- let node_ref = node.borrow();
-
- // If we've consumed all input, check for handler or suggestions
- if state.cursor >= state.input.len() {
- if let Some(handler) = &node_ref.handler {
- return handler(&state.original_query, &state.current_prefix, &state.context);
- }
-
- // No handler, try to suggest based on available edges
- return self.suggest_from_edges(&node_ref, state);
- }
-
- let remaining = &state.input[state.cursor..];
-
- // Try to match each edge
- for edge in &node_ref.edges {
- if let Some((consumed, captured)) = edge.pattern.matches(remaining) {
- // Save state for potential backtracking
- let saved_cursor = state.cursor;
- let saved_node = state.current_node_id;
- let saved_prefix = state.current_prefix.clone();
-
- // Update state
- state.cursor += consumed;
- state.current_node_id = edge.target;
- state.current_prefix.push_str(&remaining[..consumed]);
-
- // Store captured variable if any
- if let Some(value) = captured {
- if let EdgePattern::Variable(var_name) = &edge.pattern {
- state.context.insert(var_name.to_string(), value);
- }
- }
-
- // Check if this is a partial match that needs completion
- if state.cursor == state.input.len() {
- if let Some(completion_suffix) = edge.pattern.completion(remaining) {
- // Use the current_prefix plus the completion suffix
- let full_completion = format!("{}{}",
- state.current_prefix,
- completion_suffix.strip_prefix(remaining).unwrap_or(&completion_suffix)
- );
- return UIAction::suggest(
- state.original_query.clone(),
- Some(Suggestion {
- text: full_completion.clone(),
- completion: full_completion,
- description: edge.description.map(|d| d.to_string()),
- score: 1.0,
- })
- );
- }
- }
-
- // Continue parsing from the target node
- let result = self.parse_recursive(state);
-
- // If we got a valid response, return it
- if !matches!(result, UIAction::ShowError(_)) {
- return result;
- }
-
- // Otherwise, restore state and try next edge
- state.cursor = saved_cursor;
- state.current_node_id = saved_node;
- state.current_prefix = saved_prefix;
- }
- }
-
- // No edges matched - try to provide suggestions
- self.suggest_from_edges(&node_ref, state)
- }
-
- fn suggest_from_edges(&self, node: &Node, state: &ParserState) -> UIAction {
- let remaining = &state.input[state.cursor..];
-
- // Find edges that could match with more input
- for edge in &node.edges {
- match &edge.pattern {
- EdgePattern::PrefixOf(target) => {
- if target.starts_with(remaining) && !remaining.is_empty() {
- // Use current_prefix instead of rebuilding from input
- let full_completion = format!("{}{}", state.current_prefix, target);
- return UIAction::suggest(
- state.original_query.clone(),
- Some(Suggestion {
- text: full_completion.clone(),
- completion: full_completion,
- description: edge.description.map(|d| d.to_string()),
- score: 1.0,
- })
- );
- }
- }
- EdgePattern::Literal(lit) => {
-
… preview truncated; 152,463 characters omittedB — c_b7cc67300477 (tommy-mor)
message
[9e3d989b] Add test that 25 spanning-tree votes with perfect ratios sort the alphabet. Demonstrates rank centrality recovers the true order from a random connected comparison graph when each vote encodes item strength as (idx+1). Co-authored-by: Cursor <cursoragent@cursor.com>
diff preview
diff --git a/server/src/ranking.rs b/server/src/ranking.rs
index c9b4d5d89d2f22cf53a5b43eb979e19a11bc89a1..93cb4c9f5887a1e598cdc9d648751055f618adcc 100644
--- a/server/src/ranking.rs
+++ b/server/src/ranking.rs
@@ -369,6 +369,48 @@ mod tests {
assert_eq!(comp1, vec!["c", "d"]);
}
+ /// A random spanning tree over 26 items needs only n−1 = 25 pairwise votes.
+ /// When each vote uses the "perfect" ratio (strength left : strength right =
+ /// (idx_left+1) : (idx_right+1)), rank centrality recovers the true order.
+ /// See `rank-eric.py` (Eric's demo of Negahban–Oh–Shah rank centrality).
+ #[test]
+ fn twenty_five_random_votes_perfect_ratios_sort_alphabet() {
+ use rand::seq::SliceRandom;
+
+ const N: usize = 26;
+ let letters: Vec<char> = (0..N).map(|i| char::from(b'a' + i as u8)).collect();
+
+ let mut rng = rand::thread_rng();
+ let mut perm: Vec<usize> = (0..N).collect();
+ perm.shuffle(&mut rng);
+
+ let mut g = mk_group();
+ for k in 1..N {
+ let i = *perm[..k].choose(&mut rng).unwrap();
+ let j = perm[k];
+ let (a, b) = (letters[i], letters[j]);
+ g.apply_vote(vote(
+ k as i64,
+ &a.to_string(),
+ &b.to_string(),
+ (i + 1) as i32,
+ (j + 1) as i32,
+ ));
+ }
+
+ let ranked = ranked_items(&g);
+ assert_eq!(ranked.len(), N);
+ for (rank, item) in ranked.iter().enumerate() {
+ let expected = char::from(b'a' + (N - 1 - rank) as u8);
+ assert_eq!(
+ item.item.as_str(),
+ expected.to_string(),
+ "rank {rank}: expected '{expected}', got '{}'",
+ item.item.as_str()
+ );
+ }
+ }
+
#[test]
fn subset_ranking_ranks_within_component_only() {
let mut g = mk_group();
Hardlinks — judgments / attempts / prompt
judgments
attempts
Prompt text is loaded only by the download route.