constitution · epochs · watch · epoch 3

comparison

c_c6f5afec1d36 (tommy-mor) vs c_cd965c070df3 (tommy-mor)

download prompt · raw event · cmp_5ed0e8a3e3d137

council reasoning

~anthropic/claude-sonnet-latest · winner B · 6:4 · permalink

Side B fixes a real bug (Reddit children being imported with wrong path/ensure_path semantics, causing incorrect tree structure) and adds/fixes integration tests covering the actual behavior, which is a concrete correctness improvement. Side A adds a new feature (pool-scoped voting) that is useful but more speculative/UI-driven, with less test coverage and more surface area for edge cases (e.g., empty pool handling, query param backward compatibility).

~x-ai/grok-latest · winner A · 2:1 · permalink

A adds an end-to-end pool-scoped voting path (optional pool query, pair pick from children, constrained next-pair nav, UI entry point, and pool threaded through VoteComparePost/success JS), which is lasting product design. B is a real but narrower fix: Reddit children attach via apply_entity_under_parent instead of ensure_path/link_child, titles in unranked labels, and test updates—correct and necessary, but smaller in scope than A’s full workflow.

openai/gpt-chat-latest · winner B · 3:2 · permalink

Side B fixes a concrete data-model bug in Reddit child imports by introducing `apply_entity_under_parent`, avoiding incorrect path nesting while still storing entity data, and updates the ranking panel to display imported child titles instead of path-derived labels. It also expands integration tests to cover SSE updates and child fetching. Side A adds a useful new pool-scoped voting feature and threads pool context through navigation, but it is primarily a feature addition rather than a correctness fix to existing behavior.

sides

A — c_c6f5afec1d36 (tommy-mor)

message

[5350388a] Add pool-scoped voting: /vote?pool=<parent> picks pairs from children.

- /vote now accepts an optional `pool` param (parent item path). When
  provided without left/right, it picks the first unvoted pair from the
  pool's children. When provided alongside left/right, it constrains
  "next pair" navigation to siblings within the pool.
- "vote on children" button appears on item pages with ≥2 children,
  linking to /vote?pool=<item>.
- Pool is threaded through VoteComparePost → success JS so in-page
  morph after voting keeps the pool context for next-pair navigation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

diff preview

diff --git a/server/src/api/ui_html.rs b/server/src/api/ui_html.rs
index 5faa642451d69555cb391974beb0ad22c9355c8c..b79efdb4d52bd445a67f38cbfd61d3507d2b3014 100644
--- a/server/src/api/ui_html.rs
+++ b/server/src/api/ui_html.rs
@@ -193,6 +193,7 @@ async fn dispatch_ui_action(
             ratio_right,
             explanation,
             next,
+            pool,
             form_action,
         } => {
             if form_action != "/ui" {
@@ -239,6 +240,9 @@ async fn dispatch_ui_action(
                         .into_response();
                 }
             };
+            let pool_id = pool.as_deref().and_then(|p| {
+                crate::path_types::ItemId::parse(p.trim()).map(|i| i.normalized_storage())
+            });
             let mut rl = ratio_left.trim().parse::<i32>().unwrap_or(0).max(0);
             let mut rr = ratio_right.trim().parse::<i32>().unwrap_or(0).max(0);
             if rl == 0 && rr == 0 {
@@ -294,6 +298,7 @@ async fn dispatch_ui_action(
                         &thread_tag,
                         &left_id,
                         &right_id,
+                        pool_id.as_ref(),
                         pid.as_str(),
                         post_index,
                     )
diff --git a/server/src/html/garden/pin.rs b/server/src/html/garden/pin.rs
index 5865cfdbc06aeb6555c97380e25591fae6b3d125..1820d7fe7ee167ecbf5ad5124f23b2ab92ffb01f 100644
--- a/server/src/html/garden/pin.rs
+++ b/server/src/html/garden/pin.rs
@@ -79,7 +79,7 @@ pub(super) fn ont_pin_vote_controls(
                         }
                     }
                 } @else {
-                    a class="ont-vote-compare-btn" href=(vote_compare_href(nav, pi, &current, None)) title="Compare and vote" {
+                    a class="ont-vote-compare-btn" href=(vote_compare_href(nav, pi, &current, None, None)) title="Compare and vote" {
                         span class="ont-vote-glyph" aria-hidden="true" { "⚖" }
                         span { "vote" }
                     }
@@ -121,7 +121,7 @@ pub(super) fn child_row_pin_or_vote(
                         if nv == 1 { "" } else { "s" },
                     );
                     @let aria = format!("Vote; {} pairwise {}", nv, if nv == 1 { "vote" } else { "votes" });
-                    a class="ont-garden-vote-ico" href=(vote_compare_href(nav, pi, row_item, None)) title=(tip) aria-label=(aria) {
+                    a class="ont-garden-vote-ico" href=(vote_compare_href(nav, pi, row_item, None, None)) title=(tip) aria-label=(aria) {
                         span class="ont-garden-vote-glyph" aria-hidden="true" { "⚖" }
                         span class="ont-garden-vote-count" { (format!("{}", nv)) }
                     }
diff --git a/server/src/html/garden/render.rs b/server/src/html/garden/render.rs
index 5c3986ca36902f6c9019a4b5590f0b3b9d2cb4ff..bd8cbce059ee789de6e8dc9b6f69f54beee2f994 100644
--- a/server/src/html/garden/render.rs
+++ b/server/src/html/garden/render.rs
@@ -29,6 +29,7 @@ use super::{
     item::{child_depth_from_uri, item_code_label, item_display_path, item_href},
     item_page::{build_item_page_view_model, sibling_nav_markup},
     pin::{child_row_pin_or_vote, ont_pin_vote_controls, pinned_item_from_jar},
+    vote::vote_pool_href,
 };
 
 pub(super) async fn render_scope_view(
@@ -186,12 +187,21 @@ pub(super) async fn render_scope_view(
             }
 
             section class="ont-tab-panel ont-tab-panel-children" {
+                @let total_children = model.child_rankings.component_rankings
+                    .iter().map(|c| c.ranked.len()).sum::<usize>()
+                    + model.child_rankings.unranked_items.len();
                 h3 {
                     "ranked child groups"
                     @if model.child_depth > 1 {
                         " "
                         span class="muted" { (format!("(depth {})", model.child_depth)) }
                     }
+                    @if total_children >= 2 {
+                        " "
+                        a class="ont-vote-children-btn" href=(vote_pool_href(&nav, &model.item)) {
+                            "vote on children"
+                        }
+                    }
                 }
                 @if model.child_rankings.component_rankings.is_empty() {
                     p class="muted" { "no voted pairs yet in this scope" }
diff --git a/server/src/html/garden/tests.rs b/server/src/html/garden/tests.rs
index 6900a6795bcde866a176722149d6378e5127b4c3..c2036fca7752aef63d260e36cfe9649f63b5c550 100644
--- a/server/src/html/garden/tests.rs
+++ b/server/src/html/garden/tests.rs
@@ -105,7 +105,7 @@ fn suggest_next_vote_pair_prefers_unvoted_sibling_pair() {
     let content = content_for_garden_view(&reduced, &ScopeId::Public);
     let a = ItemId::parse("~/topic/a").unwrap().normalized_storage();
     let b = ItemId::parse("~/topic/b").unwrap().normalized_storage();
-    let next = suggest_next_vote_pair(content, &a, &b).expect("next sibling pair");
+    let next = suggest_next_vote_pair(content, &a, &b, None).expect("next sibling pair");
     assert_ne!(
         canonical_edge_items(&next.0, &next.1),
         canonical_edge_items(&a, &b)
diff --git a/server/src/html/garden/vote.rs b/server/src/html/garden/vote.rs
index 1d7fc8aa7436bfa9c1186dfd940a8d751ab7e088..2682cfcbd2f834b56459a02831aa225cffe67c58 100644
--- a/server/src/html/garden/vote.rs
+++ b/server/src/html/garden/vote.rs
@@ -211,14 +211,15 @@ pub(crate) async fn vote_compare_post_success_js(
     _thread_tag: &str,
     left: &ItemId,
     right: &ItemId,
+    pool: Option<&ItemId>,
     _post_id: &str,
     _post_idx: Option<usize>,
 ) -> String {
     let reduced = state.reduced.read().await;
     let content = content_for_garden_view(&reduced, &nav.scope());
     let edge_history = vote_edge_history_markup(content, left, right);
-    let next_pair = suggest_next_vote_pair(content, left, right);
-    let nav_markup = vote_compare_nav_markup(nav, next_pair.as_ref());
+    let next_pair = suggest_next_vote_pair(content, left, right, pool);
+    let nav_markup = vote_compare_nav_markup(nav, next_pair.as_ref(), pool);
     drop(reduced);
     JsBuilder::new()
         .morph_inner_selector("#vote-edge-history-region", edge_history)
@@ -231,27 +232,39 @@ pub(super) fn vote_compare_href(
     left: &ItemId,
     right: &ItemId,
     thread_override: Option<&str>,
+    pool: Option<&ItemId>,
 ) -> String {
     let left_q = urlencoding::encode(left.as_str());
     let right_q = urlencoding::encode(right.as_str());
-    let base = format!(
+    let mut base = format!(
         "{}/vote?left={}&right={}",
         nav.room_path_prefix_for_vote_compare(),
         left_q,
         right_q
     );
     if let Some(t) = thread_override.filter(|s| !s.is_empty()) {
-        format!("{}&thread={}", base, urlencoding::encode(t))
-    } else {
-        base
+        base = format!("{}&thread={}", base, urlencoding::encode(t));
+    }
+    if let Some(p) = pool {
+        base = format!("{}&pool={}", base, urlencoding::encode(p.as_str()));
     }
+    base
+}
+
+pub(super) fn vote_pool_href(nav: &ThreadNav, pool_item_str: &str) -> String {
+    format!(
+        "{}/vote?pool={}",
+        nav.room_path_prefix_for_vote_compare(),
+        urlencoding::encode(pool_item_str)
+    )
 }
 
 fn vote_compare_nav_markup(
     nav: &ThreadNav,
     next_pair: Option<&(ItemId, ItemId)>,
+    pool: Option<&ItemId>,
 ) -> maud::Markup {
-    let next_pair_href = next_pair.map(|(nl, nr)| vote_compare_href(nav, nl, nr, None));
+    let next_pair_href = next_pair.map(|(nl, nr)| vote_compare_href(nav, nl, nr, None, pool));
     html! {
         div class="vote-compare-nav" {
             @if let Some(href) = &next_pair_href {
@@ -267,8 +280,15 @@ pub(super) fn suggest_next_vote_pair(
     content: &ContentState,
     current_left: &ItemId,
     current_right: &ItemId,
+    pool_parent: Option<&ItemId>,
 ) -> Option<(ItemId, ItemId)> {
-    let pool: Vec<ItemId> = if current_left.parent().as_ref().map(|p| p.as_str())
+    let pool: Vec<ItemId> = if let Some(parent) = pool_parent {
+        content
+            .item_children
+            .get(parent)
+            .map(|s| s.iter().cloned().collect())
+            .unwrap_or_default()
+    } else if current_left.parent().as_ref().map(|p| p.as_str())
         == current_right.parent().as_ref().map(|p| p.as_str())
     {
         current_left
@@ -322,10 +342,14 @@ pub(super) fn vote_compare_item_card(
 }
 #[derive(Debug, Deserialize)]
 pub struct VoteCompareQuery {
-    pub left: String,
-    pub right: String,
+    #[serde(default)]
+    pub left: Option<String>,
+    #[serde(default)]
+    pub right: Option<String>,
     #[serde(default)]
     pub thread: Option<String>,
+    #[serde(default)]
+    pub pool: Option<String>,
 }
 
 /// Public pairwise vote UI — `/vote?left=&right=&thread=`.
@@ -376,17 +400,53 @@ async fn vote_compare_inner(
     jar: CookieJar,
     uri: Uri,
 ) -> axum::response::Response {
-    let left = match ItemId::parse(q.left.trim()) {
-        Some(i) => i.normalized_storage(),
-        None => return (StatusCode::NOT_FOUND, "bad left item").into_response(),
+    let pool_id: Option<ItemId> = match q.pool.as_deref() {
+        Some(p) => match ItemId::parse(p.trim()) {
+            Some(i) => Some(i.normalized_storage()),
+            None => return (StatusCode::BAD_REQUEST, "bad pool item").into_response(),
+        },
+        None => None,
     };
-    let right = match ItemId::parse(q.right.trim()) {
-        Some(i) => i.normalized_storage(),
-        None => return (StatusCode::NOT_FOUND, "bad right item").into_response(),
+
+    let (left, right) = match (q.left.as_deref(), q.right.as_deref()) {
+        (Some(l), Some(r)) => {
+            let left = match ItemId::parse(l.trim()) {
+                Some(i) => i.normalized_storage(),
+                None => return (StatusCode::NOT_FOUND, "bad left item").into_response(),
+            };
+            let right = match ItemId::parse(r.trim()) {
+                Some(i) => i.normalized_storage(),
+                None => return (StatusCode::NOT_FOUND, "bad right item").into_response(),
+            };
+            if left == right {
+                return (StatusCode::BAD_REQUEST, "items must differ").into_response();
+            }
+            (left, right)
+        }
+        (None, None) => {
+            let Some(pool) = pool_id.as_ref() else {
+                return (StatusCode::BAD_REQUEST, "provide left+right or pool").into_response();
+            };
+            let reduced = state.reduced.read().await;
+            let content = content_for_garden_view(&reduced, &nav.scope());
+            let children: Vec<ItemId> = content
+                .item_children
+                .get(pool)
+                .map(|s| s.iter().cloned().collect())
+                .unwrap_or_default();
+            if children.len() < 2 {
+                drop(reduced);
+                return (StatusCode::BAD_REQUEST, "pool has fewer than 2 children to compare").into_response();
+            }
+            let pair = suggest_next_pair_in_pool(&content.ranking_group, &children, None);
+            drop(reduced);
+            match pair {
+                Some(p) => p,
+                None => return (StatusCode::BAD_REQUEST, "no pairs available in pool").into_response(),
+            }
+        }
+        _ => return (StatusCode::BAD_REQUEST, "provide both left and right, or just pool").into_response(),
     };
-    if left == right {
-        return (StatusCode::BAD_REQUEST, "items must differ").into_response();
-    }
 
     let reduced = state.reduced.read().await;
     let content = content_for_garden_view(&reduced, &nav.scope());
@@ -409,7 +469,7 @@ async fn vote_compare_inner(
     let left_body = content.item_bodies.get(&left).cloned();
     let right_body = content.item_bodies.get(&right).cloned();
     let item_bodies_for_cards = content.item_bodies.

… preview truncated; 1,501 characters omitted

download full diff A

B — c_cd965c070df3 (tommy-mor)

message

[993d359c] Fix Reddit children import wiring and unranked child labels.

Listing imports attach posts directly under the subreddit without ensure_path
pulling comment-path segments in, and the ranking panel shows imported titles.
Update integration tests for JS SSE morphs and children fetch.

Co-authored-by: Cursor <cursoragent@cursor.com>

diff preview

diff --git a/server/src/api/ui_html.rs b/server/src/api/ui_html.rs
index d1defd28242fd2ca3b886adc91a7070bef75e653..e649a7d192feade465e19ce6187a829f6ec74372 100644
--- a/server/src/api/ui_html.rs
+++ b/server/src/api/ui_html.rs
@@ -58,7 +58,7 @@ pub async fn post_ui_html(
             let tree = state.tree.read().await;
             let empty = crate::reducer::NodeState::default();
             let node = tree.get(&parent).unwrap_or(&empty);
-            let panel = ranking_panel(&parent, node);
+            let panel = ranking_panel(&parent, node, &tree);
             JsBuilder::new()
                 .morph_selector("#ranking-panel", panel)
                 .into_response()
diff --git a/server/src/fetch/mod.rs b/server/src/fetch/mod.rs
index 0177bb161cea1b72a100b52efdfc5e710271c9eb..35f968c21c69ca557bab7951413e3cfbbccfebfd 100644
--- a/server/src/fetch/mod.rs
+++ b/server/src/fetch/mod.rs
@@ -107,7 +107,7 @@ pub fn fetch_entity_stream(
                 let mut b = JsBuilder::new()
                     .morph_selector("#entity-section", html::entity_section(&id, node, false));
                 if kind == FetchKind::Children {
-                    b = b.morph_selector("#ranking-panel", ranking_panel(&id, node));
+                    b = b.morph_selector("#ranking-panel", ranking_panel(&id, node, &tree));
                 }
                 yield Ok(js_event(b.build()));
             }
diff --git a/server/src/html/mod.rs b/server/src/html/mod.rs
index 27ce9118c73ec5643e04363ab1a36cf5da6101bf..e88cc43ddc9d8100f7994be6f5960ec4d8f22c55 100644
--- a/server/src/html/mod.rs
+++ b/server/src/html/mod.rs
@@ -15,7 +15,7 @@ use crate::{
     ranking::{
         connected_components_from_voted_pairs, ranked_items_subset, RankedItem, MAX_ITERS, TOL,
     },
-    reducer::NodeState,
+    reducer::{GlobalTree, NodeState},
     state::AppState,
     ui_action::UI_RPC_FIELD,
 };
@@ -182,8 +182,15 @@ fn display_label(id: &ItemId) -> String {
         .to_string()
 }
 
+fn child_label(tree: &GlobalTree, id: &ItemId) -> String {
+    tree.get(id)
+        .and_then(|n| n.data.as_ref())
+        .map(|d| d.title.clone())
+        .unwrap_or_else(|| display_label(id))
+}
+
 /// Plain (unscored) list of children that have no votes yet.
-fn unranked_list(label: &str, items: &[ItemId]) -> Markup {
+fn unranked_list(label: &str, items: &[ItemId], tree: &GlobalTree) -> Markup {
     html! {
         @if !items.is_empty() {
             h3 class="rank-heading muted small" { (label) }
@@ -191,7 +198,7 @@ fn unranked_list(label: &str, items: &[ItemId]) -> Markup {
                 @for it in items {
                     li {
                         a href=(item_href(it)) {
-                            strong { (display_label(it)) }
+                            strong { (child_label(tree, it)) }
                         }
                     }
                 }
@@ -200,7 +207,7 @@ fn unranked_list(label: &str, items: &[ItemId]) -> Markup {
     }
 }
 
-pub fn ranking_panel(item: &ItemId, node: &NodeState) -> Markup {
+pub fn ranking_panel(item: &ItemId, node: &NodeState, tree: &GlobalTree) -> Markup {
     let group = &node.local_ranking;
     let n = group.idx_to_item.len();
     let (comps, _isolates) =
@@ -248,7 +255,7 @@ pub fn ranking_panel(item: &ItemId, node: &NodeState) -> Markup {
                     @let label = if multi { format!("Ranking group {}", gi + 1) } else { "Ranking".to_string() };
                     (rank_list(&label, ranked, 1))
                 }
-                (unranked_list("Unranked", &unranked))
+                (unranked_list("Unranked", &unranked, tree))
             }
         }
     }
@@ -297,7 +304,7 @@ async fn item_page(state: AppState, uri: Uri, item: ItemId) -> Markup {
         (input_panel("", None))
         (breadcrumb_path(&item))
         (entity_section(&item, node, false))
-        (ranking_panel(&item, node))
+        (ranking_panel(&item, node, &tree))
     };
     layout("sorter2", body, views)
 }
diff --git a/server/src/reddit.rs b/server/src/reddit.rs
index a0eb688709478ee0185b953b41a5d26cc354764d..69d979bc7e4a1cb078f70114dc539bdc1986b574 100644
--- a/server/src/reddit.rs
+++ b/server/src/reddit.rs
@@ -300,9 +300,16 @@ async fn reddit_worker(
                     }
                     {
                         let mut tree = tree.write().await;
-                        apply_entity_import(&mut tree, &child_id, child_payload);
                         if kind == FetchKind::Children {
-                            tree.link_child(&fetch_id, &child_id);
+                            let view = entity_view_from_payload(&child_id, &child_payload);
+                            tree.apply_entity_under_parent(
+                                &fetch_id,
+                                &child_id,
+                                child_payload,
+                                view,
+                            );
+                        } else {
+                            apply_entity_import(&mut tree, &child_id, child_payload);
                         }
                     }
                     written += 1;
diff --git a/server/src/reducer.rs b/server/src/reducer.rs
index a36cd5c9287d61536f9f4a3f6b0df2342388857a..4e42d0369dab50bb2f8ca664aa69b628292f6c07 100644
--- a/server/src/reducer.rs
+++ b/server/src/reducer.rs
@@ -209,14 +209,24 @@ impl GlobalTree {
         }
     }
 
-    /// Directly attach `child` under `parent`, bypassing path-based nesting.
-    /// Used for imported listings (e.g. a subreddit's posts) so they show up
-    /// as children of the subreddit rather than a deep `…/comments/<id>` path.
-    pub fn link_child(&mut self, parent: &ItemId, child: &ItemId) {
+    /// Import entity data for `id` and attach it as a direct child of `parent`
+    /// without running [`Self::ensure_path`] on `id` (avoids Reddit `/comments/`
+    /// parent rules pulling intermediate path segments into the subreddit).
+    pub fn apply_entity_under_parent(
+        &mut self,
+        parent: &ItemId,
+        id: &ItemId,
+        payload: Value,
+        view: Option<EntityData>,
+    ) {
         self.ensure_path(parent);
-        self.ensure_path(child);
+        self.ensure_node(id);
+        if let Some(node) = self.nodes.get_mut(id) {
+            node.entity_raw = Some(payload);
+            node.data = view;
+        }
         if let Some(p) = self.nodes.get_mut(parent) {
-            p.children.insert(child.clone());
+            p.children.insert(id.clone());
         }
     }
 }
diff --git a/test/reddit_import.clj b/test/reddit_import.clj
index 84cbdcf7965e50290313cbce2243a16b583d2097..45a2a19f20799d77e84d8aa64735ab5e7e45f97c 100644
--- a/test/reddit_import.clj
+++ b/test/reddit_import.clj
@@ -67,6 +67,36 @@
           (do (Thread/sleep 200) (recur))
           false)))))
 
+(defn- run-reddit-fetch-assertions [app-base data-dir]
+  (let [browse-url (str app-base "/~/https://reddit.com/r/rust")
+        log-path (str data-dir "/events.jsonl")
+        before (:out (process/shell {:out :string :err :string}
+                                    "curl" "-sf" browse-url))]
+    (is (str/includes? before "Fetch from Reddit"))
+    (is (not (str/includes? before "The Rust Programming Language")))
+    (let [sse (curl-fetch-ui-sse app-base "reddit.com/r/rust" "self")]
+      (is (zero? (:exit sse)) "POST /ui fetch_entity (self) SSE succeeds")
+      (is (str/includes? (:out sse) "Idiomorph.morph"))
+      (is (str/includes? (:out sse) "The Rust Programming Language"))
+      (is (wait-event-log log-path 2000) "event log written"))
+    (let [after (:out (process/shell {:out :string :err :string}
+                                     "curl" "-sf" browse-url))
+          log (slurp (io/file log-path))]
+      (is (str/includes? after "The Rust Programming Language"))
+      (is (str/includes? log "\"type\":\"entity_imported\""))
+      (is (str/includes? log "\"subscribers\":350000"))
+      (is (str/includes? log "\"display_name\":\"rust\"")))
+    (let [children-sse (curl-fetch-ui-sse app-base "reddit.com/r/rust" "children")]
+      (is (zero? (:exit children-sse)) "POST /ui fetch_entity (children) SSE succeeds")
+      (is (str/includes? (:out children-sse) "Idiomorph.morph"))
+      (is (str/includes? (:out children-sse) "Announcing Rust 1.99")))
+    (let [after-children (:out (process/shell {:out :string :err :string}
+                                              "curl" "-sf" browse-url))
+          log2 (slurp (io/file log-path))]
+      (is (str/includes? after-children "Announcing Rust 1.99"))
+      (is (str/includes? after-children "Unranked"))
+      (is (str/includes? log2 "announcing_rust_199")))))
+
 (deftest reddit-fetch-via-mock-api
   (testing "Fetch more queues import; event log stores full payload; page shows title"
     (let [root (repo-root)
@@ -102,24 +132,7 @@
                                     bin)]
           (try
             (is (wait-health app-base 20000) "app healthz")
-            (let [browse-url (str app-base "/~/https://reddit.com/r/rust")
-                  before (:out (process/shell {:out :string :err :string}
-                                              "curl" "-sf" browse-url))]
-              (is (str/includes? before "Fetch from Reddit"))
-              (is (not (str/includes? before "The Rust Programming Language")))
-              (let [log-path (str data-dir "/events.jsonl")
-                    sse (curl-fetch-ui-sse app-base "reddit.com/r/rust")]
-                (is (zero? (:exit sse)) "POST /ui fetch_entity SSE succeeds")
-                (is (str/includes? (:out sse) "event: complete"))
-                (is (str/includes? (:out sse) "The Rust Programming Language"))
-                (is (wait-event-log log-path 2000) "event log written")
-                (let [after (:out (process/shell {:out :string :err :string}
-                                                 "curl" "-sf" browse-url))
-                      log (slurp (io/file log-path))]
-                  (is (str/includes? after "The Rust Programming Language"))
-                  (is (str/includes? log "\"type\":\"entity_imported\""))
-                  (is (str/includes? log "\"subscribers\":350000"))
-                  (is (str/includes? log "\"display_name\":\"rust\"")))))
+            (run-reddit-fetch-assertions app-base data-dir)
             (finally
               (process/destroy proc))))
         (finally
diff --git a/test/smoke.clj b/test/smoke.clj
index 11887c48282088e140d823a88ba616f6325835b3..ce9f958c84b9a89ae55e215ab519f1df6435e24b 100644
--- a/test/smoke.clj
+++ b/test/smoke.clj
@@ -49,7 +49,7 @@
           (is (wait-health base 15000) "server responds to /healthz")
           (let [home (:out (process/shell {:out :string :err :string}
                                          "curl" "-sf" (str base "/")))]
-            (is (str/includes? home "vote-panel"))
+            (is (str/includes? home "entity-section"))
             (is (str/includes? home "ranking-panel"))
             (is (str/includes? home "parser-panel"))
             (is (str/includes? home "__rpc__")))

download full diff B

Hardlinks — judgments / attempts / prompt

prompt download

judgments

attempts

Prompt text is loaded only by the download route.