constitution · epochs · watch · epoch 3

comparison

c_597d3f736194 (tommy-mor) vs c_66eb04076a98 (tommy-mor)

download prompt · raw event · cmp_a8c40ca191ab3f

council reasoning

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

Side A fixes real bugs in test infrastructure (wrong regex-vs-string split arg, wrong response body length causing broken redirects, wrong stream method, unguarded nil token) plus adds exception handling and CSS-selector-based readers, unblocking a broken E2E auth suite. Side B is a smaller, self-contained UI refinement (min-max normalization for rank coloring) that's well-tested but lower-impact and more cosmetic than a functional bugfix.

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

A fixes concrete mock HttpServer bugs (request body via getRequestBody, regex split, null-safe tokens/state, 302 length, handler try/catch) that had broken Clojure E2E auth, plus aligns test helpers with real selectors—restoring a critical verification path. B is a sound production UX tweak (score min–max gradient vs ordinal) with tests, but it refines presentation rather than unblocking core flows.

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

Side A fixes multiple concrete failures in the OAuth test infrastructure that were breaking end-to-end authentication: it corrects query parsing (`str/split` with regex), reads POST bodies from `getRequestBody`, guards against null bearer tokens and missing state, fixes redirect response handling, wraps handlers to avoid crashes, and updates Playwright test selectors and alias handling. Side B improves the UI by coloring rank rows based on normalized score ranges instead of list position and adds focused tests, but it is primarily a presentation enhancement rather than a broad reliability fix.

sides

A — c_597d3f736194 (tommy-mor)

message

[075d4d37] Fix OAuth test mocks so Clojure E2E auth flows work again.

HttpServer handlers were crashing on query parsing and token POSTs, which broke Playwright login; also read alias/history via real CSS selectors.

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

diff preview

diff --git a/test/auth_login.clj b/test/auth_login.clj
index 6f2f00d7aa7340e63d1ac465b0a2234cec7a983f..aa63b66ccb2471b710ba3d168d0461252b39fc10 100644
--- a/test/auth_login.clj
+++ b/test/auth_login.clj
@@ -14,27 +14,27 @@
 (defn- type-alias! [pg text]
   (page/evaluate pg
                  (.replace
-                  "(() => { const i = document.getElementById('alias-input'); const f = document.getElementById('alias-check-form'); if (!i || !f) return;
+                  "(() => { const i = document.getElementById('alias-input'); const f = document.getElementById('alias-check-form'); if (!i || !f) return Promise.resolve('missing-form');
                     i.value = __TEXT__;
                     const cf = document.getElementById('alias-claim-field'); if (cf) cf.value = i.value;
                     return fetch(f.action, { method: 'POST', credentials: 'same-origin',
                       headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                       body: new URLSearchParams(new FormData(f)).toString() })
                       .then(function (r) { return r.text(); })
-                      .then(function (t) { eval(t); }); })()"
+                      .then(function (t) { eval(t); return document.getElementById('alias-status')?.textContent || ''; }); })()"
                   "__TEXT__"
-                  (pr-str text)))
-  (Thread/sleep 400))
+                  (pr-str text))))
 
-(defn- element-text [pg test-id]
+(defn- element-text [pg selector]
   (let [raw (page/evaluate pg
-                           (str "document.querySelector('[data-testid=\"" test-id "\"]')?.textContent || ''"))]
+                           (str "document.querySelector(" (pr-str selector) ")?.textContent || ''"))]
     (when (string? raw) (str/trim raw))))
 
 (defn- wait-for-text [pg test-id text timeout-ms]
-  (let [deadline (+ (System/currentTimeMillis) timeout-ms)]
+  (let [deadline (+ (System/currentTimeMillis) timeout-ms)
+        selector (str "[data-testid=\"" test-id "\"]")]
     (loop []
-      (let [got (or (element-text pg test-id) "")]
+      (let [got (or (element-text pg selector) "")]
         (cond
           (= got text) got
           (< (System/currentTimeMillis) deadline) (do (Thread/sleep 200) (recur))
diff --git a/test/support/mock_oauth.clj b/test/support/mock_oauth.clj
index 5ba7e9be3cf6d2226648e9609a09ed45f306930f..333fe08d56cb06bac2a338cad1c73894d54c4495 100644
--- a/test/support/mock_oauth.clj
+++ b/test/support/mock_oauth.clj
@@ -7,7 +7,7 @@
 (defn- query-param [query key]
   (when query
     (some (fn [pair]
-            (let [[k v] (str/split pair "=" 2)]
+            (let [[k v] (str/split pair #"=" 2)]
               (when (= k key)
                 (URLDecoder/decode (or v "") "UTF-8"))))
           (str/split query #"&"))))
@@ -31,11 +31,11 @@
 
 (defn- send-redirect [^HttpExchange ex location]
   (.set (.getResponseHeaders ex) "Location" location)
-  (.sendResponseHeaders ex 302 -1)
+  (.sendResponseHeaders ex 302 0)
   (.close (.getResponseBody ex)))
 
 (defn- read-form [^HttpExchange ex]
-  (let [body (slurp (.getInputStream ex))]
+  (let [body (slurp (.getRequestBody ex))]
     {:code (query-param body "code")
      :grant (query-param body "grant_type")}))
 
@@ -45,7 +45,7 @@
           (str/replace #"^[Bb]earer " "")))
 
 (defn- parse-token-user [token]
-  (when (str/starts-with? token "mock:")
+  (when (and token (str/starts-with? token "mock:"))
     (parse-mock-user (subs token 5))))
 
 (defn- authorize-redirect [exchange query]
@@ -55,7 +55,7 @@
         user (parse-mock-user mock-user)
         code (str "mock:" (:id user) ":" (:login user))
         loc (str redirect-uri "?code=" (java.net.URLEncoder/encode code "UTF-8")
-                 "&state=" (java.net.URLEncoder/encode state "UTF-8"))]
+                 "&state=" (java.net.URLEncoder/encode (or state "") "UTF-8"))]
     (send-redirect exchange loc)))
 
 (defn start-mock-oauth
@@ -65,49 +65,56 @@
         handler
         (proxy [HttpHandler] []
           (handle [^HttpExchange exchange]
-            (let [uri (.getRequestURI exchange)
-                  path (.getPath uri)
-                  query (.getQuery uri)
-                  method (.getRequestMethod exchange)]
-              (cond
-                ;; GitHub authorize
-                (str/ends-with? path "/login/oauth/authorize")
-                (authorize-redirect exchange query)
+            (try
+              (let [uri (.getRequestURI exchange)
+                    path (.getPath uri)
+                    query (.getQuery uri)
+                    method (.getRequestMethod exchange)]
+                (cond
+                  ;; GitHub authorize
+                  (str/ends-with? path "/login/oauth/authorize")
+                  (authorize-redirect exchange query)
 
-                ;; Reddit authorize
-                (str/ends-with? path "/api/v1/authorize")
-                (authorize-redirect exchange query)
+                  ;; Reddit authorize
+                  (str/ends-with? path "/api/v1/authorize")
+                  (authorize-redirect exchange query)
 
-                ;; GitHub token
-                (and (= method "POST") (str/ends-with? path "/login/oauth/access_token"))
-                (let [code (or (:code (read-form exchange)) "mock:1002:newbie")]
-                  (send-json exchange 200 (str "{\"access_token\":\"" code "\",\"token_type\":\"bearer\"}")))
+                  ;; GitHub token
+                  (and (= method "POST") (str/ends-with? path "/login/oauth/access_token"))
+                  (let [code (or (:code (read-form exchange)) "mock:1002:newbie")]
+                    (send-json exchange 200 (str "{\"access_token\":\"" code "\",\"token_type\":\"bearer\"}")))
 
-                ;; Reddit token (client_credentials for import + authorization_code for login)
-                (and (= method "POST") (str/ends-with? path "/api/v1/access_token"))
-                (let [form (read-form exchange)
-                      grant (or (:grant form) "")
-                      code (or (:code form) "mock:t2_test:redditor")]
-                  (if (= grant "client_credentials")
-                    (send-json exchange 200 "{\"access_token\":\"app-token\",\"token_type\":\"bearer\",\"expires_in\":3600}")
-                    (send-json exchange 200 (str "{\"access_token\":\"" code "\",\"token_type\":\"bearer\",\"expires_in\":3600}"))))
+                  ;; Reddit token (client_credentials for import + authorization_code for login)
+                  (and (= method "POST") (str/ends-with? path "/api/v1/access_token"))
+                  (let [form (read-form exchange)
+                        grant (or (:grant form) "")
+                        code (or (:code form) "mock:t2_test:redditor")]
+                    (if (= grant "client_credentials")
+                      (send-json exchange 200 "{\"access_token\":\"app-token\",\"token_type\":\"bearer\",\"expires_in\":3600}")
+                      (send-json exchange 200 (str "{\"access_token\":\"" code "\",\"token_type\":\"bearer\",\"expires_in\":3600}"))))
 
-                ;; GitHub user
-                (= path "/user")
-                (let [token (bearer-token exchange)
-                      user (or (parse-token-user token) {:id "1002" :login "newbie" :numeric? true})]
-                  (send-json exchange 200
-                             (str "{\"id\":" (:id user) ",\"login\":\"" (:login user) "\"}")))
+                  ;; GitHub user
+                  (= path "/user")
+                  (let [token (bearer-token exchange)
+                        user (or (parse-token-user token) {:id "1002" :login "newbie" :numeric? true})]
+                    (send-json exchange 200
+                               (str "{\"id\":" (:id user) ",\"login\":\"" (:login user) "\"}")))
 
-                ;; Reddit /api/v1/me
-                (str/ends-with? path "/api/v1/me")
-                (let [token (bearer-token exchange)
-                      user (or (parse-token-user token) {:id "t2_test" :login "redditor"})]
-                  (send-json exchange 200
-                             (str "{\"id\":\"" (:id user) "\",\"name\":\"" (:login user) "\"}")))
+                  ;; Reddit /api/v1/me
+                  (str/ends-with? path "/api/v1/me")
+                  (let [token (bearer-token exchange)
+                        user (or (parse-token-user token) {:id "t2_test" :login "redditor"})]
+                    (send-json exchange 200
+                               (str "{\"id\":\"" (:id user) "\",\"name\":\"" (:login user) "\"}")))
 
-                :else
-                (send-json exchange 404 "{\"error\":\"not found\"}")))))]
+                  :else
+                  (send-json exchange 404 "{\"error\":\"not found\"}")))
+              (catch Throwable t
+                (binding [*out* *err*]
+                  (println "mock-oauth handler error:" t))
+                (try
+                  (send-json exchange 500 "{\"error\":\"mock-oauth internal\"}")
+                  (catch Throwable _))))))]
     (.createContext server "/" handler)
     (.setExecutor server nil)
     (.start server)
diff --git a/test/support/mock_reddit.clj b/test/support/mock_reddit.clj
index a630cf0938722193e9af88382d60e777ff371be4..faa27945b6394363914c853627a0bad1e819a5f9 100644
--- a/test/support/mock_reddit.clj
+++ b/test/support/mock_reddit.clj
@@ -12,7 +12,7 @@
 (defn- query-param [query key]
   (when query
     (some (fn [pair]
-            (let [[k v] (str/split pair "=" 2)]
+            (let [[k v] (str/split pair #"=" 2)]
               (when (= k key)
                 (URLDecoder/decode (or v "") "UTF-8"))))
           (str/split query #"&"))))
@@ -34,11 +34,11 @@
 
 (defn- send-redirect [^HttpExchange ex location]
   (.set (.getResponseHeaders ex) "Location" location)
-  (.sendResponseHeaders ex 302 -1)
+  (.sendResponseHeaders ex 302 0)
   (.close (.getResponseBody ex)))
 
 (defn- read-form [^HttpExchange ex]
-  (let [body (slurp (.getInputStream ex))]
+  (let [body (slurp (.getRequestBody ex))]
     {:code (query-param body "code")
      :grant (query-param body "grant_type")}))
 
@@ -48,7 +48,7 @@
           (str/replace #"^[Bb]earer " "")))
 
 (defn- parse-token-user [token]
-  (when (str/starts-with? token "mock:")
+  (when (and token (str/starts-with? token "mock:"))
     (parse-mock-user (subs token 5))))
 
 (defn start-mock-reddit
@@ -72,7 +72,7 @@
                        user (parse-mock-user (query-param query "mock_user"))
                        code (str "mock:" (:id user) ":" (:login user))
                        loc (str redirect-uri "?code=" (java.net.URLEncoder/encode code "UTF-8")
-                                "&state=" (java.net.URLEncoder/encode state "UTF-8"))]
+                                "&state=" (java.net.URLEncoder/encode (or state "") "UTF-8"))]
                    (send-redirect exchange loc))
 
                  (and (= method "POST") (str/ends-with? path "/api/v1/access_token"))

download full diff A

B — c_66eb04076a98 (tommy-mor)

message

[0366806e] Color rank rows by vote mass within each group, not list position.

Min–max normalization keeps similar scores visually close while still
using the full gradient as groups grow and absolute mass dilutes.

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

diff preview

diff --git a/server/src/html/mod.rs b/server/src/html/mod.rs
index a58cbbee3490a08a625cb06df06848c59a615d65..4eff2e19ed4d303ff8e80c1eabd8a15b4990e643 100644
--- a/server/src/html/mod.rs
+++ b/server/src/html/mod.rs
@@ -264,12 +264,19 @@ pub fn scope_theme_style(parent: &ItemId) -> String {
     )
 }
 
-fn rank_row_style(parent: &ItemId, ordinal: usize, total: usize) -> String {
-    let t = if total <= 1 {
-        0.0
-    } else {
-        ordinal as f64 / (total - 1) as f64
-    };
+/// Map vote mass to gradient position using the group's score range, not raw mass or
+/// list position. Vote mass sums to 1 across the component, so absolute values dilute
+/// as N grows; min–max within the visible list preserves similar scores → similar colors.
+fn score_gradient_t(score: f64, min_score: f64, max_score: f64) -> f64 {
+    let spread = max_score - min_score;
+    if spread < 1e-9 {
+        return 0.5;
+    }
+    ((max_score - score) / spread).clamp(0.0, 1.0)
+}
+
+fn rank_row_style(parent: &ItemId, score: f64, min_score: f64, max_score: f64) -> String {
+    let t = score_gradient_t(score, min_score, max_score);
     let base_hue = scope_base_hue(parent);
     let hue = (base_hue + 118.0 * t) % 360.0;
     let lightness = 0.74 - 0.34 * t;
@@ -295,14 +302,15 @@ fn rank_list(
     highlighted: &HashSet<ItemId>,
     tree: &GlobalTree,
 ) -> Markup {
-    let group_len = items.len();
+    let min_score = items.iter().map(|r| r.score).fold(f64::INFINITY, f64::min);
+    let max_score = items.iter().map(|r| r.score).fold(f64::NEG_INFINITY, f64::max);
     html! {
         @if !items.is_empty() {
             h3 class="rank-heading muted small" { (label) }
             ol class="rank-list" {
                 @for (i, r) in items.iter().enumerate() {
                     @let href = item_href(&r.item);
-                    @let style = rank_row_style(parent, i, group_len);
+                    @let style = rank_row_style(parent, r.score, min_score, max_score);
                     @let class = rank_row_class(&r.item, highlighted);
                     li class=(class)
                         data-rank-item=(r.item.as_str())
@@ -517,19 +525,37 @@ pub async fn browse(State(state): State<AppState>, uri: Uri) -> impl IntoRespons
 
 #[cfg(test)]
 mod tests {
-    use super::{rank_row_style, SORTER_UI_JS};
+    use super::{rank_row_style, score_gradient_t, SORTER_UI_JS};
     use crate::path_types::ItemId;
 
     #[test]
-    fn rank_row_style_gradients_per_group_not_globally() {
+    fn score_gradient_t_uses_group_range_not_absolute_mass() {
+        assert!((score_gradient_t(0.12, 0.08, 0.12) - 0.0).abs() < 1e-9);
+        assert!((score_gradient_t(0.08, 0.08, 0.12) - 1.0).abs() < 1e-9);
+        // Raw 12% mass would map near the dark end globally; within this group it's the top.
+        assert!(score_gradient_t(0.12, 0.08, 0.12) < score_gradient_t(0.12, 0.0, 1.0));
+    }
+
+    #[test]
+    fn score_gradient_t_similar_scores_similar_t() {
+        let a = score_gradient_t(0.41, 0.20, 0.60);
+        let b = score_gradient_t(0.40, 0.20, 0.60);
+        assert!((a - b).abs() < 0.05);
+        assert!((a - score_gradient_t(0.60, 0.20, 0.60)).abs() > 0.3);
+    }
+
+    #[test]
+    fn score_gradient_t_tied_scores_neutral() {
+        assert!((score_gradient_t(0.25, 0.25, 0.25) - 0.5).abs() < 1e-9);
+    }
+
+    #[test]
+    fn rank_row_style_same_inputs_same_color() {
         let parent = ItemId::opaque("test-scope");
-        let first_in_four = rank_row_style(&parent, 0, 4);
-        let last_in_four = rank_row_style(&parent, 3, 4);
-        let first_in_two = rank_row_style(&parent, 0, 2);
-        let last_in_two = rank_row_style(&parent, 1, 2);
-        assert_eq!(first_in_four, first_in_two);
-        assert_eq!(last_in_four, last_in_two);
-        assert_ne!(first_in_four, last_in_four);
+        assert_eq!(
+            rank_row_style(&parent, 0.33, 0.20, 0.60),
+            rank_row_style(&parent, 0.33, 0.20, 0.60),
+        );
     }
 
     #[test]

download full diff B

Hardlinks — judgments / attempts / prompt

prompt download

judgments

attempts

Prompt text is loaded only by the download route.