constitution · epochs · watch · epoch 3

comparison

c_6a02ffb06a41 (tommy-mor) vs c_9e1ff4fc0186 (tommy-mor)

download prompt · raw event · cmp_e0eb135ac7887a

council reasoning

~anthropic/claude-sonnet-latest · winner A · 65:35 · permalink

Side A fixes a real semantic issue (a silent unwrap_or(0) masking a should-never-happen case) and reconciles a 1-vs-0 indexing inconsistency across rpc.rs, garden.rs, and types docs, backed by new integration test assertions verifying the corrected indices. Side B is almost entirely rustfmt/tooling churn (reformatting examples, toolchain pin, .vscode settings) plus a trivial CSS dedupe, which is useful hygiene but contributes no functional or correctness value.

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

Commit A makes a real semantic fix: switches rank-history thread_post_index to consistent 0-based indexing matching /t/tag/N routes, replaces silent unwrap_or(0) with expect, drops the conditional UI hide, documents the contract, and adds integration assertions. Commit B is almost entirely rustfmt churn across examples and crates plus minor tooling (toolchain components, VS Code settings, one duplicated CSS rule merge), which adds little lasting design or correctness value.

openai/gpt-chat-latest · winner A · 9:1 · permalink

Side A makes a semantic correctness change by treating `thread_post_index` as a guaranteed 0-based index, replacing silent `unwrap_or(0)` fallbacks with `expect(...)`, always rendering the post link, updating API/docs to distinguish 0-based URL indices from 1-based feed ordinals, and adding integration tests to lock in the behavior. Side B is overwhelmingly workspace-wide rustfmt reformatting plus tooling/editor configuration (pinning rustfmt/clippy, VS Code settings, minor CSS cleanup), with little lasting change to project behavior.

sides

A — 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 A

B — c_9e1ff4fc0186 (tommy-mor)

message

[2a94401b] Run rustfmt workspace-wide and fix lint tooling.

Pin rustfmt and clippy in rust-toolchain.toml after a broken component install, merge a duplicate vote-slider CSS rule, and add VS Code settings so rust-analyzer uses the project toolchain.

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

diff preview

diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000000000000000000000000000000000..98ebd754a6d5a972550506c69c5a10c10e3210b6
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,8 @@
+{
+  "rust-analyzer.rustc.source": "discover",
+  "rust-analyzer.check.command": "check",
+  "rust-analyzer.procMacro.enable": true,
+  "rust-analyzer.cargo.extraEnv": {
+    "RUSTUP_TOOLCHAIN": "1.88.0"
+  }
+}
diff --git a/durable/examples/combined_example.rs b/durable/examples/combined_example.rs
index 626a6e1cf7e3c9c26f9f2edc58950d9bc31ec67e..6e9cb3210714d74c9a2cc0ed4f87e1b8d84788da 100644
--- a/durable/examples/combined_example.rs
+++ b/durable/examples/combined_example.rs
@@ -1,5 +1,5 @@
 use durable::{Db, DurableMap, DurableVec};
-use serde::{Serialize, Deserialize};
+use serde::{Deserialize, Serialize};
 use std::time::{SystemTime, UNIX_EPOCH};
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -28,36 +28,48 @@ fn get_timestamp() -> u64 {
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     // Open or create a database
     let db = Db::open("chat_db")?;
-    
+
     // Create our collections
     let mut users = DurableMap::<String, User>::new(&db, "users")?;
     let mut messages = DurableVec::<Message>::new(&db, "messages")?;
     let mut user_message_indices = DurableMap::<String, Vec<usize>>::new(&db, "user_messages")?;
-    
+
     // Create some users
-    users.insert("alice".to_string(), User {
-        username: "alice".to_string(),
-        display_name: "Alice Smith".to_string(),
-        message_count: 0,
-    })?;
-    
-    users.insert("bob".to_string(), User {
-        username: "bob".to_string(),
-        display_name: "Bob Johnson".to_string(),
-        message_count: 0,
-    })?;
-    
-    users.insert("charlie".to_string(), User {
-        username: "charlie".to_string(),
-        display_name: "Charlie Brown".to_string(),
-        message_count: 0,
-    })?;
-    
+    users.insert(
+        "alice".to_string(),
+        User {
+            username: "alice".to_string(),
+            display_name: "Alice Smith".to_string(),
+            message_count: 0,
+        },
+    )?;
+
+    users.insert(
+        "bob".to_string(),
+        User {
+            username: "bob".to_string(),
+            display_name: "Bob Johnson".to_string(),
+            message_count: 0,
+        },
+    )?;
+
+    users.insert(
+        "charlie".to_string(),
+        User {
+            username: "charlie".to_string(),
+            display_name: "Charlie Brown".to_string(),
+            message_count: 0,
+        },
+    )?;
+
     // Helper to send a message
-    let send_message = |from: &str, to: &str, content: &str, 
+    let send_message = |from: &str,
+                        to: &str,
+                        content: &str,
                         messages: &mut DurableVec<Message>,
                         users: &mut DurableMap<String, User>,
-                        indices: &mut DurableMap<String, Vec<usize>>| -> Result<(), Box<dyn std::error::Error>> {
+                        indices: &mut DurableMap<String, Vec<usize>>|
+     -> Result<(), Box<dyn std::error::Error>> {
         // Create message
         let msg_id = messages.len()? as u64;
         let message = Message {
@@ -67,61 +79,93 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
             content: content.to_string(),
             timestamp: get_timestamp(),
         };
-        
+
         // Store message
         messages.push(message)?;
         let msg_index = messages.len()? - 1;
-        
+
         // Update sender's message count
         if let Some(mut sender) = users.get(&from.to_string())? {
             sender.message_count += 1;
             users.insert(from.to_string(), sender)?;
         }
-        
+
         // Track message indices for recipient
         let mut recipient_indices = indices.get(&to.to_string())?.unwrap_or_default();
         recipient_indices.push(msg_index);
         indices.insert(to.to_string(), recipient_indices)?;
-        
+
         Ok(())
     };
-    
+
     // Send some messages
     println!("💬 Chat Application Demo\n");
     println!("Sending messages...");
-    
-    send_message("alice", "bob", "Hey Bob, how's the Durable library coming along?", 
-                 &mut messages, &mut users, &mut user_message_indices)?;
-    
-    send_message("bob", "alice", "It's going great! We have DurableVec and DurableMap working!", 
-                 &mut messages, &mut users, &mut user_message_indices)?;
-    
-    send_message("charlie", "alice", "That sounds awesome! Can I help with testing?", 
-                 &mut messages, &mut users, &mut user_message_indices)?;
-    
-    send_message("alice", "charlie", "Absolutely! The more testing the better!", 
-                 &mut messages, &mut users, &mut user_message_indices)?;
-    
-    send_message("bob", "charlie", "Check out the examples directory for usage patterns", 
-                 &mut messages, &mut users, &mut user_message_indices)?;
-    
+
+    send_message(
+        "alice",
+        "bob",
+        "Hey Bob, how's the Durable library coming along?",
+        &mut messages,
+        &mut users,
+        &mut user_message_indices,
+    )?;
+
+    send_message(
+        "bob",
+        "alice",
+        "It's going great! We have DurableVec and DurableMap working!",
+        &mut messages,
+        &mut users,
+        &mut user_message_indices,
+    )?;
+
+    send_message(
+        "charlie",
+        "alice",
+        "That sounds awesome! Can I help with testing?",
+        &mut messages,
+        &mut users,
+        &mut user_message_indices,
+    )?;
+
+    send_message(
+        "alice",
+        "charlie",
+        "Absolutely! The more testing the better!",
+        &mut messages,
+        &mut users,
+        &mut user_message_indices,
+    )?;
+
+    send_message(
+        "bob",
+        "charlie",
+        "Check out the examples directory for usage patterns",
+        &mut messages,
+        &mut users,
+        &mut user_message_indices,
+    )?;
+
     // Display all users and their message counts
     println!("\n👥 Users:");
     let mut all_users = users.to_vec()?;
     all_users.sort_by_key(|(username, _)| username.clone());
-    
+
     for (username, user) in all_users {
-        println!("  {} ({}) - {} messages sent", 
-                 user.display_name, username, user.message_count);
+        println!(
+            "  {} ({}) - {} messages sent",
+            user.display_name, username, user.message_count
+        );
     }
-    
+
     // Display all messages
     println!("\n📨 All messages:");
     for (i, msg) in messages.iter()?.enumerate() {
         let msg = msg?;
         println!("  [{}] {} → {}: {}", i, msg.from, msg.to, msg.content);
     }
-    
+
     // Show inbox for each user
     println!("\n📥 User inboxes:");
     for item in users.iter() {
@@ -135,26 +179,26 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
             }
         }
     }
-    
+
     // Statistics
     println!("\n📊 Statistics:");
     println!("  Total users: {}", users.len()?);
     println!("  Total messages: {}", messages.len()?);
-    
+
     // Demonstrate persistence
     println!("\n💾 Data has been persisted to disk!");
     println!("  Database location: ./chat_db");
-    
+
     // Clean up
     drop(messages);
     drop(users);
     drop(user_message_indices);
     drop(db);
-    
+
     // Remove the database for this example
     std::fs::remove_dir_all("chat_db").ok();
-    
+
     println!("\n✅ Example completed!");
-    
+
     Ok(())
-} 
\ No newline at end of file
+}
diff --git a/durable/examples/map_example.rs b/durable/examples/map_example.rs
index 08b8f2c8826caf53c4c20b422a540c92f6624029..1d9c4a14b0f4cfe39ade84ebb1f1a14033e8ff1b 100644
--- a/durable/examples/map_example.rs
+++ b/durable/examples/map_example.rs
@@ -1,5 +1,5 @@
 use durable::{Db, DurableMap};
-use serde::{Serialize, Deserialize};
+use serde::{Deserialize, Serialize};
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
 struct UserProfile {
@@ -11,10 +11,10 @@ struct UserProfile {
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     // Open or create a database
     let db = Db::open("example_db")?;
-    
+
     // Create a persistent map of user profiles
     let mut users = DurableMap::<String, UserProfile>::new(&db, "users")?;
-    
+
     // Insert some users
     // Using put() when we don't need the old value - more efficient!
     users.put(
@@ -25,7 +25,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
             score: 1500,
         },
     )?;
-    
+
     users.put(
         "bob".to_string(),
         UserProfile {
@@ -34,7 +34,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
             score: 1200,
         },
     )?;
-    
+
     // Using insert() when we might need the old value
     let old_charlie = users.insert(
         "charlie".to_string(),
@@ -44,21 +44,24 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
             score: 1800,
         },
     )?;
-    
+
     if old_charlie.is_some() {
         println!("Replaced existing charlie entry");
     }
-    
+
     println!("Total users: {}", users.len()?);
-    
+
     // Look up a specific user
     if let Some(alice) = users.get(&"alice".to_string())? {
         println!("\nAlice's profile: {:?}", alice);
     }
-    
+
     // Check if a user exists
-    println!("\nDoes 'david' exist? {}", users.contains_key(&"david".to_string())?);
-    
+    println!(
+        "\nDoes 'david' exist? {}",
+        users.contains_key(&"david".to_string())?
+    );
+
     // Update a user's score
     if let Some(mut bob) = users.get(&"bob".to_string())? {
         bob.score += 100;
@@ -66,34 +69,40 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         users.put("bob".to_string(), bob)?;
         println!("Updated Bob's score!");
     }
-    
+
     // Iterate over all users
     println!("\nAll users (sorted by username):");
     let mut all_users = users.to_vec()?;
     all_users.sort_by_key(|(username, _)| username.clone());
-    
+
     for (username, profile) in all_users {
-        println!("  {} ({}) - Score: {}", username, profile.email, profile.score);
+        println!(
+            "  {} ({}) - Score: {}",
+            username, profile.email, profile.score
+        );
     }
-    
+
     // Get just the usernames
     let mut usernames = users.keys_vec()?;
     usernames.sort();
     println!("\nAll usernames: {:?}", usernames);
-    
+
     // Find the highest scoring user
     let profiles = users.values_vec()?;
     if let Some(top_user) = profiles.iter().max_by_key(|p| p.score) {
-        println!("\nTop scorer: {} with {} points", top_user.name, top_user.score);
+        println!(
+            "\nTop scorer: {} with {} points",
+            top_user.name, top_user.score
+        );
     }
-    
+
     // Remove a user
     if let Some(removed) = users.remove(&"charlie".to_string())? {
         println!("\nRemoved user: {}", removed.name);
         println!("Users remaining: {}", users.len()?);
     }
-    
+
     println!("\nData has been persisted to disk.");
-    
+
     Ok(())
-} 
\ No newline at end of file
+}
diff --git a/durable/examples/nested_example.rs b/durable/examples/nested_example.rs
index 3b880f2c8b8ad2633d4b5fcf2016652216c9de8e..f16090cf6fb854ac7a1b00acfd31fbd12c25dbce 100644
--- a/durable/examples/nested_example.rs
+++ b/durable/examples/nested_example.rs
@@ -3,27 +3,31 @@ use durable::{Db, DurableMap, DurableVec};
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     // Open a database
     let db = Db::open("nested_example_db")?;
-    
+
     // Create a map where each user has a list of posts
-    let user_posts: DurableMap<String, DurableVec<String>> = DurableMap::new_nested(&db, "user_posts");
-    
+    let user_posts: DurableMap<String, DurableVec<String>> =
+        DurableMap::new

… preview truncated; 96,900 characters omitted

download full diff B

Hardlinks — judgments / attempts / prompt

prompt download

judgments

attempts

Prompt text is loaded only by the download route.