constitution · epochs · watch · epoch 3

comparison

c_e4fb43f04791 (tommy-mor) vs c_6a02ffb06a41 (tommy-mor)

download prompt · raw event · cmp_4a02555f0be876

council reasoning

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

Side B fixes a real off-by-one/indexing bug (thread_post_index semantics), removing a fallback that masked an invariant violation with .expect() and documenting the 0-based vs 1-based confusion, plus adds regression tests asserting correct index values. Side A is purely cosmetic: dedenting HTML markup and tweaking CSS counter styling across theme files, which is low-risk but non-functional churn.

~x-ai/grok-latest · winner B · 1:4 · permalink

B fixes a real indexing bug (1-based/unwrap_or(0) vs 0-based /t/tag/N routes), enforces the invariant with expect, always emits correct post links, and adds integration assertions. A only unwraps a vote-compare shell div and tweaks ranking-number CSS—cosmetic churn with little lasting design impact.

openai/gpt-chat-latest · winner B · 5:1 · permalink

Side B fixes the semantics of `thread_post_index` by making rank history use the actual 0-based thread index consistently, replacing a silent `unwrap_or(0)` fallback with an `expect` that enforces the invariant, updating rendering to always generate the post link, correcting documentation, and adding integration tests for the behavior. Side A is largely a presentation change: it removes a wrapper element from the compare page and adjusts CSS styling for ranked lists across themes, with little impact on core functionality.

sides

A — c_e4fb43f04791 (tommy-mor)

message

[09842c93] remove shell

diff preview

diff --git a/server/src/html/garden.rs b/server/src/html/garden.rs
index 8fc6be1de8dd975f9547de615809222236be4b70..76ba96e45c9e16291a6ccd8096fdd01b274c1560 100644
--- a/server/src/html/garden.rs
+++ b/server/src/html/garden.rs
@@ -1321,55 +1321,53 @@ async fn vote_compare_inner(
     .expect("vote compare rpc json");
 
     let body = html! {
-        section class="vote-compare-shell" {
-            h2 { "compare" }
-            div class="vote-compare-pair" {
-                a class="vote-compare-item" href=(nav.garden_item_href(&left)) {
-                    code { (item_display_path(left.as_str())) }
-                }
-                span class="vote-compare-vs" { "vs" }
-                a class="vote-compare-item" href=(nav.garden_item_href(&right)) {
-                    code { (item_display_path(right.as_str())) }
-                }
-            }
-            div id="vote-edge-history-region" {
-                (edge_history)
-            }
-            @if can_post {
-                form id="vote-compare-form" method="POST" action="/ui" {
-                    input type="hidden" name=(UI_RPC_FIELD) value=(rpc_json);
-                    div class="vote-thread-picker" {
-                        label class="vote-thread-picker-label" { "thread" }
-                        select id="vote-thread-select" name="thread_tag" aria-label="Thread to post vote into" {
-                            @if thread_tags.is_empty() {
-                                option value="vote" selected { "#vote" }
-                            }
-                            @for t in &thread_tags {
-                                @if *t == auto_thread {
-                                    option value=(t) selected { "#" (t) }
-                                } @else {
-                                    option value=(t) { "#" (t) }
-                                }
-                            }
-                        }
+    h2 { "compare" }
+    div class="vote-compare-pair" {
+        a class="vote-compare-item" href=(nav.garden_item_href(&left)) {
+            code { (item_display_path(left.as_str())) }
+        }
+        span class="vote-compare-vs" { "vs" }
+        a class="vote-compare-item" href=(nav.garden_item_href(&right)) {
+            code { (item_display_path(right.as_str())) }
+        }
+    }
+    div id="vote-edge-history-region" {
+        (edge_history)
+    }
+    @if can_post {
+        form id="vote-compare-form" method="POST" action="/ui" {
+            input type="hidden" name=(UI_RPC_FIELD) value=(rpc_json);
+            div class="vote-thread-picker" {
+                label class="vote-thread-picker-label" { "thread" }
+                select id="vote-thread-select" name="thread_tag" aria-label="Thread to post vote into" {
+                    @if thread_tags.is_empty() {
+                        option value="vote" selected { "#vote" }
                     }
-                    input type="hidden" name="ratio_left" id="vote-ratio-left" value="50";
-                    input type="hidden" name="ratio_right" id="vote-ratio-right" value="50";
-                    label class="vote-compare-slider-label" {
-                        span id="vote-slider-left-label" { (item_display_path(left.as_str())) }
-                        input type="range" id="vote-preference-slider" min="0" max="100" value="50"
-                            aria-valuemin="0" aria-valuemax="100";
-                        span id="vote-slider-right-label" { (item_display_path(right.as_str())) }
+                    @for t in &thread_tags {
+                        @if *t == auto_thread {
+                            option value=(t) selected { "#" (t) }
+                        } @else {
+                            option value=(t) { "#" (t) }
+                        }
                     }
-                    label class="vote-explain-label" { "reason (required)" }
-                    textarea name="explanation" id="vote-explain" rows="5" placeholder="why this split?" required {}
-                    div id="vote-compare-errors" {}
-                    p { button type="submit" { "post vote" } }
                 }
-            } @else {
-                p class="muted" { a href="/login" { "log in" } " to post this vote." }
             }
+            input type="hidden" name="ratio_left" id="vote-ratio-left" value="50";
+            input type="hidden" name="ratio_right" id="vote-ratio-right" value="50";
+            label class="vote-compare-slider-label" {
+                span id="vote-slider-left-label" { (item_display_path(left.as_str())) }
+                input type="range" id="vote-preference-slider" min="0" max="100" value="50"
+                    aria-valuemin="0" aria-valuemax="100";
+                span id="vote-slider-right-label" { (item_display_path(right.as_str())) }
+            }
+            label class="vote-explain-label" { "reason (required)" }
+            textarea name="explanation" id="vote-explain" rows="5" placeholder="why this split?" required {}
+            div id="vote-compare-errors" {}
+            p { button type="submit" { "post vote" } }
         }
+    } @else {
+        p class="muted" { a href="/login" { "log in" } " to post this vote." }
+    }
     };
 
     let page = layout_full_bleed_chromeless(
diff --git a/server/static/theme_default.css b/server/static/theme_default.css
index a441c4f79d8cf88f5a8240f9992f47dbbd1ab46b..fdcde86c718eb53cce8273e45a9844c2d8041f88 100644
--- a/server/static/theme_default.css
+++ b/server/static/theme_default.css
@@ -1270,8 +1270,12 @@ body.view-ontology-dark .ont-ranking-list li {
 body.view-ontology-dark .ont-ranking-list li::before {
   color: var(--meta);
   content: counter(ont-rank) ".";
-  font-size: 11px;
-  min-width: 18px;
+  flex-shrink: 0;
+  font-size: 1.35rem;
+  font-weight: 700;
+  font-variant-numeric: tabular-nums;
+  line-height: 1;
+  min-width: 2.25ch;
   text-align: right;
 }
 body.view-ontology-dark .ont-rank-score {
@@ -1424,8 +1428,12 @@ body.view-ontology-light .ont-ranking-list li {
 body.view-ontology-light .ont-ranking-list li::before {
   color: var(--meta);
   content: counter(ont-rank) ".";
-  font-size: 11px;
-  min-width: 18px;
+  flex-shrink: 0;
+  font-size: 1.35rem;
+  font-weight: 700;
+  font-variant-numeric: tabular-nums;
+  line-height: 1;
+  min-width: 2.25ch;
   text-align: right;
 }
 body.view-ontology-light .ont-rank-score {
diff --git a/server/static/theme_retro.css b/server/static/theme_retro.css
index 373f15bbd75b62604cdd14b9f0fadda2d6176991..61e1448b2f66a075c0e33325d6980448712fc927 100644
--- a/server/static/theme_retro.css
+++ b/server/static/theme_retro.css
@@ -135,6 +135,30 @@ body.view-ontology nav.breadcrumb a:hover {
 body.view-ontology nav.breadcrumb a.bc-current { color: #111; font-weight: 600; }
 body.view-ontology nav.breadcrumb .bc-sep { color: #888; padding: 0 2px; }
 
+body.view-ontology ol.ont-ranking-list {
+  counter-reset: ont-rank;
+  list-style: none;
+  margin: 0.5rem 0;
+  padding: 0;
+}
+body.view-ontology ol.ont-ranking-list li {
+  align-items: baseline;
+  counter-increment: ont-rank;
+  display: flex;
+  gap: 0.35rem;
+}
+body.view-ontology ol.ont-ranking-list li::before {
+  flex-shrink: 0;
+  color: #666;
+  content: counter(ont-rank) ".";
+  font-size: 1.35rem;
+  font-weight: 700;
+  font-variant-numeric: tabular-nums;
+  line-height: 1;
+  min-width: 2.25ch;
+  text-align: right;
+}
+
 nav.breadcrumb.ont-sibling-nav {
   margin-top: 0;
   width: 100%;
diff --git a/server/static/theme_retro_craft.css b/server/static/theme_retro_craft.css
index 6eb9222184a8795d67a5d09d41de08c8ac1b148f..7da102040484c887833158a37c307d078205c701 100644
--- a/server/static/theme_retro_craft.css
+++ b/server/static/theme_retro_craft.css
@@ -742,12 +742,32 @@ body.view-ontology button.ont-garden-pin-ico:focus-visible {
   outline-offset: 2px;
 }
 
+body.view-ontology ol.ont-ranking-list {
+  counter-reset: ont-rank;
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}
 body.view-ontology ol.ont-ranking-list li,
 body.view-ontology ul.ont-group-list li {
   display: flex;
   align-items: baseline;
   gap: 0.35rem;
 }
+body.view-ontology ol.ont-ranking-list li {
+  counter-increment: ont-rank;
+}
+body.view-ontology ol.ont-ranking-list li::before {
+  flex-shrink: 0;
+  color: #5c574e;
+  content: counter(ont-rank) ".";
+  font-size: 1.35rem;
+  font-weight: 700;
+  font-variant-numeric: tabular-nums;
+  line-height: 1;
+  min-width: 2.25ch;
+  text-align: right;
+}
 body.view-ontology ol.ont-ranking-list li .item-link,
 body.view-ontology ul.ont-group-list li .item-link {
   flex: 1;

download full diff A

B — c_6a02ffb06a41 (tommy-mor)

message

[3bc88847] removed optional

diff preview

diff --git a/server/src/api/rpc.rs b/server/src/api/rpc.rs
index ba93ca87182d49f57dffc8220f604ffa150f9a6c..5049a26b096bd8435d6eb9e75ccb751b6f489061 100644
--- a/server/src/api/rpc.rs
+++ b/server/src/api/rpc.rs
@@ -1337,8 +1337,7 @@ pub async fn handle_rpc_batch(
                         .ingests_by_scope_thread
                         .get(&(scope.clone(), e.thread.clone()))
                         .and_then(|q| q.iter().rev().position(|id| id == &e.post_id))
-                        .map(|i| i + 1)
-                        .unwrap_or(0);
+                        .expect("rank history post_id must be in ingests_by_scope_thread for (scope, thread)");
                     RankHistoryRow {
                         ts: e.ts,
                         scope_rank: e.scope_rank,
diff --git a/server/src/html/garden.rs b/server/src/html/garden.rs
index 319feb15a6b68d2b5df98b4289fedbc9bdd048d3..23245d6fdfc9019b199ab8417150faf5f3297067 100644
--- a/server/src/html/garden.rs
+++ b/server/src/html/garden.rs
@@ -450,6 +450,7 @@ struct RankHistoryEntryView {
     scope_total: usize,
     scope_rank_delta: i32,
     thread: String,
+    /// 0-based index as [`crate::html::forum::ingest::thread_post_index_in_scope`] / `/t/tag/N`.
     thread_post_index: usize,
     caused_by: Vec<crate::reducer::VoteData>,
 }
@@ -573,11 +574,11 @@ fn build_rank_history(
             })
             .unwrap_or_default();
 
-        let thread_post_index = reduced.ingests_by_scope_thread
+        let thread_post_index = reduced
+            .ingests_by_scope_thread
             .get(&(scope.clone(), e.thread.clone()))
             .and_then(|q| q.iter().rev().position(|id| id == &e.post_id))
-            .map(|i| i + 1)
-            .unwrap_or(0);
+            .expect("rank history post_id must be in ingests_by_scope_thread for (scope, thread)");
 
         RankHistoryEntryView {
             ts: e.ts,
@@ -704,11 +705,9 @@ async fn render_scope_view(
                                 span class="muted" { (ago) (label) }
                                 " · "
                                 a href=(thread_href(&e.thread)) { "#" (e.thread) }
-                                @if e.thread_post_index > 0 {
-                                    " "
-                                    a href=(format!("{}/{}", thread_href(&e.thread), e.thread_post_index)) {
-                                        span class="muted" { "post #" (e.thread_post_index) }
-                                    }
+                                " "
+                                a href=(format!("{}/{}", thread_href(&e.thread), e.thread_post_index)) {
+                                    span class="muted" { "post #" (e.thread_post_index) }
                                 }
                             }
                             @if e.caused_by.is_empty() {
diff --git a/server/tests/integration.rs b/server/tests/integration.rs
index d4c5bfe9c6f1c71dc61878bd8c5e729b1b7c69ef..9766adb43ad315a64f5df17b79f66718ce149509 100644
--- a/server/tests/integration.rs
+++ b/server/tests/integration.rs
@@ -1322,6 +1322,11 @@ async fn test_rank_history() {
     assert_eq!(entry["scope_rank_delta"], 0, "delta is 0 on first appearance");
     let caused_by = entry["caused_by"].as_array().unwrap();
     assert_eq!(caused_by.len(), 2, "both votes in the ingest touched rust");
+    assert_eq!(
+        entry["thread_post_index"],
+        0,
+        "rank history links use same 0-based index as /t/hist-test/0"
+    );
 
     ingest(
         "00000000-0000-0000-0000-000000000002:rig:test/model",
@@ -1349,6 +1354,16 @@ async fn test_rank_history() {
     assert_eq!(caused_by2.len(), 1);
     assert!(caused_by2[0]["a"].as_str().unwrap().ends_with("python") ||
             caused_by2[0]["b"].as_str().unwrap().ends_with("python"));
+    assert_eq!(
+        hist2[0]["thread_post_index"],
+        0,
+        "first hist-test post is chronological index 0"
+    );
+    assert_eq!(
+        hist2[1]["thread_post_index"],
+        1,
+        "second ingest is chronological index 1"
+    );
 
     let hist_rust2 = rpc_batch(
         &client,
diff --git a/types/src/lib.rs b/types/src/lib.rs
index edbb923e4d7d41ad82dfc254c3bd697562383527..49abba8ea9f786ef68e3157d2a5d309e15e09ba0 100644
--- a/types/src/lib.rs
+++ b/types/src/lib.rs
@@ -253,7 +253,7 @@ pub struct FeedPost {
     /// Primary thread tag (without #), if the ingest declared one.
     #[serde(skip_serializing_if = "Option::is_none")]
     pub thread: Option<String>,
-    /// 1-indexed chronological position of this post within the thread.
+    /// 1-based display ordinal for this post within the thread (feed only; URLs use 0-based paths).
     #[serde(skip_serializing_if = "Option::is_none")]
     pub thread_post_index: Option<usize>,
     /// Full raw body of the ingest document.
@@ -628,7 +628,7 @@ pub struct RankHistoryRow {
     pub score: f64,
     /// Thread tag of the ingest that triggered this rank change.
     pub thread: String,
-    /// 1-indexed chronological position of this post within the thread.
+    /// 0-indexed chronological position of this post within the thread (same as `/t/tag/N` routes).
     pub thread_post_index: usize,
     /// Votes from this ingest that directly touched this item. Empty when change was transitive.
     pub caused_by: Vec<VoteRow>,

download full diff B

Hardlinks — judgments / attempts / prompt

prompt download

judgments

attempts

Prompt text is loaded only by the download route.