Side B introduces a genuinely new, composable URL-canonicalization subsystem (url_rules module with engine + registry, domain-specific rules for reddit/youtube, breadcrumb/parent derivation) that fixes real correctness issues (schemeless IDs, non-canonical hosts, youtube variants) and is exercised by substantial new tests. Side A is a solid internal refactor (GroupState -> ScopeVotes, deriving edges on demand) that improves clarity but is mostly mechanical renaming/restructuring of existing logic without adding new capability or fixing an external-facing bug, making B's contribution somewhat more impactful in scope though both are reasonable quality refactors.
constitution · epochs · watch · epoch 3
c_ef71be9831cc (tommy-mor) vs c_77729db919ab (tommy-mor)
download prompt · raw event · cmp_70795f3c6df4ed
council reasoning
A removes incremental edge/index caches from GroupState (ensure_item, rollback_weights, voted_pairs) in favor of ScopeVotes that keep only uuid_votes/recent_votes and rebuild edges at rank time—a lasting single-source-of-truth fix across reducer, ranking, storage, and pair logic. B adds a real url_rules engine and full-URL ItemIds, but much of the diff is mechanical https:// string churn in tests and call sites rather than equally deep design consolidation.
Side A makes a substantial architectural change by replacing cached `GroupState` with `ScopeVotes`, deriving ranking edges and connected components on demand from deduplicated votes. It updates ranking, storage, reducers, UI, and tests consistently, simplifying persisted state while preserving behavior. Side B mainly introduces URL canonicalization infrastructure and migrates many tests and IDs to `https://` forms, which is useful but much of the patch is mechanical conversion rather than a core data model improvement.
sides
A — c_ef71be9831cc (tommy-mor)
message
[af73743d] Replace GroupState with ScopeVotes and derive edges at ranking time. Store only uuid_votes and recent_votes per scope; rank centrality and pair logic rebuild edge weights on demand instead of maintaining cached state. Co-authored-by: Cursor <cursoragent@cursor.com>
diff preview
diff --git a/server/src/events.rs b/server/src/events.rs
index 8a166d49b4f26835fbc2b58cb1f4bdbf002763b8..015208311f6c5c23a0e8aab068d002a69c89c4e1 100644
--- a/server/src/events.rs
+++ b/server/src/events.rs
@@ -43,7 +43,7 @@ pub enum ViewEvent {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Event {
- /// Pairwise comparison vote (replayed into the parent node's [`crate::reducer::GroupState`] on boot).
+ /// Pairwise comparison vote (replayed into the parent node's [`crate::reducer::ScopeVotes`] on boot).
/// `scope` is the parent [`crate::path_types::ItemId`] string; empty string is the tree root.
VoteRecorded {
ts: i64,
diff --git a/server/src/html/mod.rs b/server/src/html/mod.rs
index 4eff2e19ed4d303ff8e80c1eabd8a15b4990e643..1e2e7a06856d8a62378741aaf5ed94a4ffed337e 100644
--- a/server/src/html/mod.rs
+++ b/server/src/html/mod.rs
@@ -13,7 +13,7 @@ use crate::{
form_template::template_json_compact,
path_types::ItemId,
ranking::{
- connected_components_from_voted_pairs, ranked_items_subset, RankedItem, MAX_ITERS, TOL,
+ ranked_items_subset, scope_components, RankedItem, MAX_ITERS, TOL,
},
reducer::{GlobalTree, NodeState},
state::AppState,
@@ -397,10 +397,9 @@ pub fn ranking_panel_with_highlights(
tree: &GlobalTree,
highlighted: &HashSet<ItemId>,
) -> Markup {
- let group = &node.local_ranking;
- let n = group.idx_to_item.len();
- let (comps, _isolates) =
- connected_components_from_voted_pairs(n, group.voted_pairs.iter().copied());
+ let scope = &node.votes;
+ let (comps, _isolates, _) =
+ scope_components(scope);
// Each connected component of voted items is its own ranking; isolated and
// never-voted children fall into the "unranked" bucket below.
@@ -410,7 +409,7 @@ pub fn ranking_panel_with_highlights(
if comp.len() < 2 {
continue;
}
- let ranked = ranked_items_subset(group, comp, MAX_ITERS, TOL);
+ let ranked = ranked_items_subset(scope, comp, MAX_ITERS, TOL);
for r in &ranked {
ranked_ids.insert(r.item.clone());
}
diff --git a/server/src/html/vote.rs b/server/src/html/vote.rs
index bf82aef3ad9c5f4e9e877c47dab27beb29a80b8f..3aa00c417c89a9cab3417c650b50ed7c73f08e20 100644
--- a/server/src/html/vote.rs
+++ b/server/src/html/vote.rs
@@ -14,7 +14,7 @@ use crate::{
html::{ranking_panel_with_highlights, scope_theme_style, JsBuilder},
pair::{children_of, resolve_pair, suggest_next_pair_in_pool},
path_types::ItemId,
- reducer::{GlobalTree, GroupState, NodeState, VoteData},
+ reducer::{GlobalTree, NodeState, ScopeVotes, VoteData},
state::{parse_item_param, AppState},
ui_action::UI_RPC_FIELD,
};
@@ -68,8 +68,8 @@ fn ratios_for_page(v: &VoteData, page_left: &ItemId, page_right: &ItemId) -> (i3
}
}
-fn edge_votes(group: &GroupState, left: &ItemId, right: &ItemId) -> Vec<VoteData> {
- group
+fn edge_votes(scope: &ScopeVotes, left: &ItemId, right: &ItemId) -> Vec<VoteData> {
+ scope
.recent_votes
.iter()
.filter(|v| {
@@ -113,11 +113,11 @@ fn slider_value_from_ratios(r_left: i32, r_right: i32) -> i32 {
fn vote_edge_history(
tree: &GlobalTree,
- group: &GroupState,
+ scope: &ScopeVotes,
left: &ItemId,
right: &ItemId,
) -> Markup {
- let mut votes = edge_votes(group, left, right);
+ let mut votes = edge_votes(scope, left, right);
votes.sort_by(|a, b| b.ts.cmp(&a.ts));
let legend_left = child_title(tree, left);
let legend_right = child_title(tree, right);
@@ -228,9 +228,9 @@ pub(crate) fn vote_recorded_morph(
) -> JsBuilder {
let pool = children_of(tree, parent);
let empty = NodeState::default();
- let group = tree.get(parent).unwrap_or(&empty).local_ranking.clone();
- let edge_history = vote_edge_history(tree, &group, left, right);
- let next_pair = suggest_next(&group, left, right, &pool);
+ let scope = tree.get(parent).unwrap_or(&empty).votes.clone();
+ let edge_history = vote_edge_history(tree, &scope, left, right);
+ let next_pair = suggest_next(&scope, left, right, &pool);
let actions = vote_compare_actions(parent, next_pair.as_ref());
let sidebar = vote_ranking_sidebar(tree, parent, left, right);
JsBuilder::new()
@@ -252,12 +252,12 @@ fn vote_compare_item_card(tree: &GlobalTree, item: &ItemId, side_class: &str) ->
}
fn suggest_next(
- group: &GroupState,
+ scope: &ScopeVotes,
left: &ItemId,
right: &ItemId,
pool: &[ItemId],
) -> Option<(ItemId, ItemId)> {
- suggest_next_pair_in_pool(group, pool, Some((left, right)))
+ suggest_next_pair_in_pool(scope, pool, Some((left, right)))
}
pub async fn vote_page(
@@ -284,9 +284,9 @@ pub async fn vote_page(
};
let pool = children_of(&tree, &parent);
- let group = &parent_node.local_ranking;
- let next_pair = suggest_next(group, &left, &right, &pool);
- let edge_history = vote_edge_history(&tree, group, &left, &right);
+ let scope = &parent_node.votes;
+ let next_pair = suggest_next(&scope, &left, &right, &pool);
+ let edge_history = vote_edge_history(&tree, &scope, &left, &right);
let rpc_json = template_json_compact(&serde_json::json!({
"action": "record_vote",
@@ -388,8 +388,8 @@ mod polarity_tests {
let mut tree = GlobalTree::new();
tree.apply_vote(&parent, vote, TEST_ACTOR_UUID);
- let group = &tree.get(&parent).unwrap().local_ranking;
- let ranked = ranked_items(group);
+ let scope = &tree.get(&parent).unwrap().votes;
+ let ranked = ranked_items(scope);
assert_eq!(
ranked[0].item, left,
"left item should rank first when ratio favours the left"
diff --git a/server/src/pair.rs b/server/src/pair.rs
index 42a1b1eb2adf16730d34d0fe23c13d5a75d7ba27..9873295c51526726089875bbfd2f97d7faa91872 100644
--- a/server/src/pair.rs
+++ b/server/src/pair.rs
@@ -11,8 +11,8 @@ use std::collections::{HashMap, HashSet};
use crate::{
path_types::ItemId,
- ranking::{connected_components_from_voted_pairs, ranked_items},
- reducer::{GlobalTree, GroupState},
+ ranking::{pair_is_voted, ranked_items, scope_components},
+ reducer::{GlobalTree, ScopeVotes},
};
fn pairs_match(a: &ItemId, b: &ItemId, x: &ItemId, y: &ItemId) -> bool {
@@ -23,26 +23,15 @@ fn pair_excluded(a: &ItemId, b: &ItemId, exclude: Option<(&ItemId, &ItemId)>) ->
exclude.is_some_and(|(x, y)| pairs_match(a, b, x, y))
}
-fn pair_is_voted(group: &GroupState, a: &ItemId, b: &ItemId) -> bool {
- let Some(&ai) = group.item_to_idx.get(a) else {
- return false;
- };
- let Some(&bi) = group.item_to_idx.get(b) else {
- return false;
- };
- let (i, j) = if ai < bi { (ai, bi) } else { (bi, ai) };
- group.voted_pairs.contains(&(i, j))
-}
struct ComponentLayout {
ids: HashMap<ItemId, usize>,
established: HashSet<usize>,
}
-fn component_layout(group: &GroupState, pool: &[ItemId]) -> ComponentLayout {
- let n = group.idx_to_item.len();
- let (comps, isolates) =
- connected_components_from_voted_pairs(n, group.voted_pairs.iter().copied());
+fn component_layout(scope: &ScopeVotes, pool: &[ItemId]) -> ComponentLayout {
+ let (comps, isolates, idx_to_item) = scope_components(scope);
+ let n = idx_to_item.len();
let mut established = HashSet::new();
let mut ids: HashMap<ItemId, usize> = HashMap::new();
@@ -52,14 +41,14 @@ fn component_layout(group: &GroupState, pool: &[ItemId]) -> ComponentLayout {
}
for &idx in comp {
if idx < n {
- ids.insert(group.idx_to_item[idx].clone(), comp_idx);
+ ids.insert(idx_to_item[idx].clone(), comp_idx);
}
}
}
let mut next = comps.len();
for &idx in &isolates {
if idx < n {
- ids.insert(group.idx_to_item[idx].clone(), next);
+ ids.insert(idx_to_item[idx].clone(), next);
next += 1;
}
}
@@ -121,7 +110,7 @@ fn established_groups_in_pool<'a>(
groups
}
-fn ranked_pool_order(group: &GroupState, pool: &[ItemId]) -> Vec<ItemId> {
+fn ranked_pool_order(group: &ScopeVotes, pool: &[ItemId]) -> Vec<ItemId> {
let pool_set: HashSet<_> = pool.iter().collect();
ranked_items(group)
.into_iter()
@@ -132,7 +121,7 @@ fn ranked_pool_order(group: &GroupState, pool: &[ItemId]) -> Vec<ItemId> {
/// Walk 1↔2, 2↔3, …; optional `require_unvoted` skips voted edges.
fn zip_adjacent_pair(
- group: &GroupState,
+ group: &ScopeVotes,
order: &[ItemId],
exclude: Option<(&ItemId, &ItemId)>,
require_unvoted: bool,
@@ -153,7 +142,7 @@ fn zip_adjacent_pair(
/// Grow the voted graph toward one component (no rank centrality).
fn suggest_grow_pair(
- group: &GroupState,
+ group: &ScopeVotes,
pool: &[ItemId],
layout: &ComponentLayout,
exclude: Option<(&ItemId, &ItemId)>,
@@ -216,7 +205,7 @@ fn suggest_grow_pair(
/// Pick the next pair to vote on within `pool`.
pub fn suggest_next_pair_in_pool(
- group: &GroupState,
+ group: &ScopeVotes,
pool: &[ItemId],
exclude: Option<(&ItemId, &ItemId)>,
) -> Option<(ItemId, ItemId)> {
@@ -315,7 +304,7 @@ pub fn resolve_pair(
(None, None) => {
let group = tree
.get(parent)
- .map(|n| &n.local_ranking)
+ .map(|n| &n.votes)
.cloned()
.unwrap_or_default();
suggest_next_pair_in_pool(&group, &children, None).ok_or(PairError::NoPair)
@@ -398,7 +387,7 @@ mod tests {
"https://reddit.com/r/rust/b",
],
);
- let group = tree.get(&parent).unwrap().local_ranking.clone();
+ let group = tree.get(&parent).unwrap().votes.clone();
let pool = children_of(&tree, &parent);
assert!(!pair_is_voted(&group, &pool[0], &pool[1]));
assert!(suggest_next_pair_in_pool(&group, &pool, None).is_some());
@@ -417,7 +406,7 @@ mod tests {
);
let vote = test_vote(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1);
apply(&mut tree, &parent, vote);
- let group = tree.get(&parent).unwrap().local_ranking.clone();
+ let group = tree.get(&parent).unwrap().votes.clone();
let pool = children_of(&tree, &parent);
let (l, r) = suggest_next_pair_in_pool(&group, &pool, None).unwrap();
let voted_ab = (l.as_str() == "https://reddit.com/r/rust/a" && r.as_str() == "https://reddit.com/r/rust/b")
@@ -441,7 +430,7 @@ mod tests {
let cd = test_vote(2, "https://reddit.com/r/rust/c", "https://reddit.com/r/rust/d", 2, 1);
apply(&mut tree, &parent, ab);
apply(&mut tree, &parent, cd);
- let group = tree.get(&parent).unwrap().local_ranking.clone();
+ let group = tree.get(&parent).unwrap().votes.clone();
let pool = children_of(&tree, &parent);
let pair = suggest_next_pair_in_pool(&group, &pool, None).unwrap();
let chosen = pair_set(&pair);
@@ -467,7 +456,7 @@ mod tests {
);
let ab = test_vote(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1);
apply(&mut tree, &parent, ab);
- let group = tree.get(&parent).unwrap().local_ranking.clone();
+ let group = tree.get(&parent).unwrap().votes.clone();
let pool = children_of(&tree, &parent);
let pair = suggest_next_pair_in_pool(&group, &pool, None).unwrap();
let chosen = pair_set(&pair);
@@ -496,7 +485,7 @@ mod tests {
);
let ab = test_vote(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1);
apply(&mut tree, &parent, ab);
- let group = tree.get(&parent).unwrap().local_ranking.clone();
+
… preview truncated; 38,568 characters omittedB — c_77729db919ab (tommy-mor)
message
[239c074b] url schema stuff
diff preview
diff --git a/AGENTS.md b/AGENTS.md
index 426a88e7c1da54fe0a28c5c76fa4e1f1bc117fcf..e60b9ba6012593361ef10e8fdd9439cd9932e09b 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -58,3 +58,4 @@ Use **tmux** for `cargo run --package sorter2-server` (dev server). Rebuild afte
- First `cargo test` / `cargo build --release` is slow; Clojure smoke test always does a release build.
- `legacy/` and `ideas/` are not part of the workspace build.
+- **ItemId** for web URLs is a canonical full URL (`https://reddit.com/r/rust`). Rules live in [`server/src/url_rules/`](server/src/url_rules/) (composable Rust, not a config DSL). After changing canonicalization rules, rebuild the projection: `cargo run --package sorter2-server -- replay-index`.
diff --git a/Cargo.lock b/Cargo.lock
index 0dd4fce5fb6400ae153cca4e3dbf5a5158e6d8b4..49a908ef935c430dbe63c6a28d8a24e38b489486 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1951,6 +1951,7 @@ dependencies = [
"tower-http 0.5.2",
"tracing",
"tracing-subscriber",
+ "url",
"urlencoding",
]
diff --git a/REPLAY.sh b/REPLAY.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f2dbd8aea60c02d2feef74805f7ef5c2b7022537
--- /dev/null
+++ b/REPLAY.sh
@@ -0,0 +1,2 @@
+cargo run --package sorter2-server -- replay-index
+
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 27f552c20b97ef28cdde4cb6b1a4980375135111..ad4912791aff59fb1d3293f66ad381ae618cd60b 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -24,6 +24,7 @@ async-stream = "0.3"
futures-util = { version = "0.3", default-features = false, features = ["std"] }
rand = "0.8"
urlencoding = "2"
+url = "2"
durable = { path = "../durable" }
[dev-dependencies]
diff --git a/server/src/entity_store.rs b/server/src/entity_store.rs
index d5d17c3676e4a8ddec998e9f5a9dbafe9c2d9d0e..d29f39aecca6f12cdcf263cf77c3654eb4ee6cfa 100644
--- a/server/src/entity_store.rs
+++ b/server/src/entity_store.rs
@@ -124,7 +124,7 @@ mod tests {
fn round_trip_payload() {
let tmp = tempfile::tempdir().unwrap();
let store = EntityStore::open(tmp.path()).unwrap();
- let id = ItemId::parse("reddit.com/r/rust").unwrap();
+ let id = ItemId::from_url("https://reddit.com/r/rust").unwrap();
let payload = json!({"kind": "t5", "data": {"display_name": "rust"}});
store.put(&id, &payload).unwrap();
diff --git a/server/src/event_log.rs b/server/src/event_log.rs
index 36f5b406084065b608735987cdb483c236e03081..2c9290b6fdbf2c2ad1c0f1ffd7374b2d9cc97f36 100644
--- a/server/src/event_log.rs
+++ b/server/src/event_log.rs
@@ -199,7 +199,7 @@ mod tests {
log.append(&sample_record(
1,
Event::NodeEnsured {
- id: "reddit.com/r/rust".into(),
+ id: "https://reddit.com/r/rust".into(),
},
))
.await
@@ -237,7 +237,7 @@ mod tests {
let path = tmp.path().join("events.jsonl");
let log = EventLog::new(&path);
let event = Event::NodeEnsured {
- id: "reddit.com/r/rust".into(),
+ id: "https://reddit.com/r/rust".into(),
};
log.append(&sample_record(1, event)).await.unwrap();
@@ -255,7 +255,7 @@ mod tests {
let path = tmp.path().join("events.jsonl");
std::fs::write(
&path,
- r#"{"type":"node_ensured","id":"reddit.com/r/rust"}
+ r#"{"type":"node_ensured","id":"https://reddit.com/r/rust"}
{"schema":1,"seq":1,"ts":1,"event":{"type":"vote_recorded","ts":1,"a":"a","b":"b","ratio_left":2,"ratio_right":1,"scope":""}}
"#,
)
@@ -295,7 +295,7 @@ mod tests {
log.append(&sample_record(
1,
Event::NodeEnsured {
- id: "reddit.com/r/rust".into(),
+ id: "https://reddit.com/r/rust".into(),
},
))
.await
@@ -303,7 +303,7 @@ mod tests {
log.append(&sample_record(
3,
Event::NodeEnsured {
- id: "reddit.com/r/python".into(),
+ id: "https://reddit.com/r/python".into(),
},
))
.await
diff --git a/server/src/journal.rs b/server/src/journal.rs
index 521a108019de1ea870d14c4fafbfe572c20ce0de..50bc89f976edb82b7b0e49e954a8eccbbe82bf87 100644
--- a/server/src/journal.rs
+++ b/server/src/journal.rs
@@ -141,10 +141,10 @@ mod tests {
let j2 = journal.clone();
let (r1, r2) = tokio::join!(
j1.append(Event::NodeEnsured {
- id: "reddit.com/r/rust".into(),
+ id: "https://reddit.com/r/rust".into(),
}),
j2.append(Event::NodeEnsured {
- id: "reddit.com/r/python".into(),
+ id: "https://reddit.com/r/python".into(),
}),
);
r1.unwrap();
@@ -153,10 +153,10 @@ mod tests {
assert_eq!(projection_store.last_applied_event_count().unwrap(), 2);
let tree = projection_store.load_tree().unwrap();
assert!(tree
- .get(&ItemId::parse("reddit.com/r/rust").unwrap())
+ .get(&ItemId::parse("https://reddit.com/r/rust").unwrap())
.is_some());
assert!(tree
- .get(&ItemId::parse("reddit.com/r/python").unwrap())
+ .get(&ItemId::parse("https://reddit.com/r/python").unwrap())
.is_some());
}
@@ -170,7 +170,7 @@ mod tests {
1,
1,
Event::NodeEnsured {
- id: "reddit.com/r/rust".into(),
+ id: "https://reddit.com/r/rust".into(),
},
))
.await
@@ -186,7 +186,7 @@ mod tests {
1,
1,
Event::NodeEnsured {
- id: "reddit.com/r/rust".into(),
+ id: "https://reddit.com/r/rust".into(),
},
)],
)
@@ -202,7 +202,7 @@ mod tests {
);
journal
.append(Event::NodeEnsured {
- id: "reddit.com/r/python".into(),
+ id: "https://reddit.com/r/python".into(),
})
.await
.unwrap();
@@ -227,13 +227,13 @@ mod tests {
journal
.append_many(vec![
Event::NodeEnsured {
- id: "reddit.com/r/rust".into(),
+ id: "https://reddit.com/r/rust".into(),
},
Event::NodeEnsured {
- id: "reddit.com/r/python".into(),
+ id: "https://reddit.com/r/python".into(),
},
Event::NodeEnsured {
- id: "reddit.com/r/clojure".into(),
+ id: "https://reddit.com/r/clojure".into(),
},
])
.await
@@ -245,7 +245,7 @@ mod tests {
assert_eq!(projection_store.last_applied_event_count().unwrap(), 3);
let tree = projection_store.load_tree().unwrap();
assert!(tree
- .get(&ItemId::parse("reddit.com/r/clojure").unwrap())
+ .get(&ItemId::parse("https://reddit.com/r/clojure").unwrap())
.is_some());
}
}
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 9bd5f76fd1406b9b1be4c272f4ba8647edde2678..5c02c8e704e4664453bad75d819df8a067668176 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -9,6 +9,7 @@ pub mod journal;
pub mod pair;
pub mod parser;
pub mod path_types;
+pub mod url_rules;
pub mod projection_apply;
pub mod projection_store;
pub mod ranking;
diff --git a/server/src/pair.rs b/server/src/pair.rs
index 43f780ba6ea6ce1cdc2e1f4cbb252ba8a10684b9..815a97b80e3e9f348e0937a4f147f2862018edb0 100644
--- a/server/src/pair.rs
+++ b/server/src/pair.rs
@@ -381,42 +381,42 @@ mod tests {
#[test]
fn suggest_prefers_unvoted_pair() {
- let parent = ItemId::parse("reddit.com/r/rust").unwrap();
+ let parent = ItemId::parse("https://reddit.com/r/rust").unwrap();
let mut tree = seed_children(
&parent,
&[
- "reddit.com/r/rust/a",
- "reddit.com/r/rust/b",
- "reddit.com/r/rust/c",
+ "https://reddit.com/r/rust/a",
+ "https://reddit.com/r/rust/b",
+ "https://reddit.com/r/rust/c",
],
);
let vote =
- VoteData::from_recorded(1, "reddit.com/r/rust/a", "reddit.com/r/rust/b", 2, 1).unwrap();
+ VoteData::from_recorded(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1).unwrap();
tree.apply_vote(&parent, vote);
let group = tree.get(&parent).unwrap().local_ranking.clone();
let pool = children_of(&tree, &parent);
let (l, r) = suggest_next_pair_in_pool(&group, &pool, None).unwrap();
- let voted_ab = (l.as_str() == "reddit.com/r/rust/a" && r.as_str() == "reddit.com/r/rust/b")
- || (l.as_str() == "reddit.com/r/rust/b" && r.as_str() == "reddit.com/r/rust/a");
+ let voted_ab = (l.as_str() == "https://reddit.com/r/rust/a" && r.as_str() == "https://reddit.com/r/rust/b")
+ || (l.as_str() == "https://reddit.com/r/rust/b" && r.as_str() == "https://reddit.com/r/rust/a");
assert!(!voted_ab);
}
#[test]
fn suggest_bridges_separate_components() {
- let parent = ItemId::parse("reddit.com/r/rust").unwrap();
+ let parent = ItemId::parse("https://reddit.com/r/rust").unwrap();
let mut tree = seed_children(
&parent,
&[
- "reddit.com/r/rust/a",
- "reddit.com/r/rust/b",
- "reddit.com/r/rust/c",
- "reddit.com/r/rust/d",
+ "https://reddit.com/r/rust/a",
+ "https://reddit.com/r/rust/b",
+ "https://reddit.com/r/rust/c",
+ "https://reddit.com/r/rust/d",
],
);
let ab =
- VoteData::from_recorded(1, "reddit.com/r/rust/a", "reddit.com/r/rust/b", 2, 1).unwrap();
+ VoteData::from_recorded(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1).unwrap();
let cd =
- VoteData::from_recorded(2, "reddit.com/r/rust/c", "reddit.com/r/rust/d", 2, 1).unwrap();
+ VoteData::from_recorded(2, "https://reddit.com/r/rust/c", "https://reddit.com/r/rust/d", 2, 1).unwrap();
tree.apply_vote(&parent, ab);
tree.apply_vote(&parent, cd);
let group = tree.get(&parent).unwrap().local_ranking.clone();
@@ -424,37 +424,37 @@ mod tests {
let pair = suggest_next_pair_in_pool(&group, &pool, None).unwrap();
let chosen = pair_set(&pair);
let from_ab =
- chosen.contains("reddit.com/r/rust/a") || chosen.contains("reddit.com/r/rust/b");
+ chosen.contains("https://reddit.com/r/rust/a") || chosen.contains("https://reddit.com/r/rust/b");
let from_cd =
- chosen.contains("reddit.com/r/rust/c") || chosen.contains("reddit.com/r/rust/d");
+ chosen.contains("https://reddit.com/r/rust/c") || chosen.contains("https://reddit.com/r/rust/d");
assert!(from_ab && from_cd, "expected bridge pair, got {:?}", chosen);
}
#[test]
fn suggest_prefers_attach_over_isolate_pair_among_many_unranked() {
- let parent = ItemId::parse("reddit.com/r/rust").unwrap();
+ let parent = ItemId::parse("https://reddit.com/r/rust").unwrap();
let mut tree = seed_children(
&parent,
&[
- "reddit.com/r/rust/a",
- "reddit.com/r/rust/b",
- "reddit.com/r/rust/c",
- "reddit.com/r/rust/d",
- "reddit.com/r/rust/e",
+ "https://reddit.com/r/rust/a",
+ "https://reddit.com/r/rust/b",
+ "https://reddit.com/r/rust/c",
+ "https://reddit.com/r/
… preview truncated; 51,799 characters omittedHardlinks — judgments / attempts / prompt
judgments
attempts
Prompt text is loaded only by the download route.