constitution · epochs · watch · epoch 3

comparison

c_254ba025ef2e (tommy-mor) vs c_9c6e7864a657 (tommy-mor)

download prompt · raw event · cmp_b2dd5b2fd3488a

council reasoning

~anthropic/claude-sonnet-latest · winner A · 3:2 · permalink

Side A introduces the actual feature (a fixed compact bottom HUD with slider, ratio, and actions), restructuring vote.rs and CSS to enable a real UX improvement, plus adds tests verifying the new structure. Side B is a smaller polish/bugfix pass on top of A's HUD (fixed column widths to prevent reflow, winner-colored slider track), which is useful but incremental and entirely dependent on A's groundwork.

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

A introduces the pinned vote HUD itself: new markup (vote_hud_form), page structure, fixed bottom bar CSS, removal of repeated item titles, and the --vote-slider-pct hook—core lasting UX design. B only polishes that HUD with fixed column widths to stop reflow and data-winner track coloring, which is useful but incremental on top of A.

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

Side A introduces the core vote HUD architecture by extracting a new fixed bottom control form, restructuring the compare page, updating JavaScript to drive the slider state and accessibility, and adding substantial CSS plus integration/tests to support the new always-visible workflow. Side B is a refinement on top of that foundation, improving layout stability with fixed column widths and making the slider track visually indicate the leading side via a new data attribute, but it is primarily a UX polish rather than a fundamental capability.

sides

A — c_254ba025ef2e (tommy-mor)

message

[df769734] Pin vote controls in a compact bottom HUD on compare pages.

Keeps ratio, slider, and actions always visible without repeating item titles beside the slider.

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

diff preview

diff --git a/server/src/html/mod.rs b/server/src/html/mod.rs
index 1822528e3e3b526059899349ba2f73ae0666f944..dcf05df143825f4f8ba9ee3b3b5f8ab2e69950c7 100644
--- a/server/src/html/mod.rs
+++ b/server/src/html/mod.rs
@@ -528,5 +528,6 @@ mod tests {
         assert!(SORTER_UI_JS.contains("Math.max(1, v)"));
         assert!(SORTER_UI_JS.contains("var divisor = gcd(left, right)"));
         assert!(SORTER_UI_JS.contains("ratioDisplay.textContent = left + ':' + right"));
+        assert!(SORTER_UI_JS.contains("--vote-slider-pct"));
     }
 }
diff --git a/server/src/html/vote.rs b/server/src/html/vote.rs
index 7be4be152ea331fb7afb7ce598aed27d23e35cc4..872d23ddf6c18fab016a624de85736fdc2dcde63 100644
--- a/server/src/html/vote.rs
+++ b/server/src/html/vote.rs
@@ -134,6 +134,36 @@ fn vote_back_nav(parent: &ItemId) -> Markup {
     }
 }
 
+fn vote_hud_form(
+    parent: &ItemId,
+    left: &ItemId,
+    right: &ItemId,
+    rpc_json: &str,
+    next_pair: Option<&(ItemId, ItemId)>,
+) -> Markup {
+    html! {
+        div id="vote-hud" class="vote-hud" role="region" aria-label="Vote controls" {
+            form id="vote-compare-form" method="POST" action="/ui" {
+                input type="hidden" name=(UI_RPC_FIELD) value=(rpc_json);
+                input type="hidden" name="ratio_left" id="vote-ratio-left" value="1";
+                input type="hidden" name="ratio_right" id="vote-ratio-right" value="1";
+                div class="vote-hud-inner" {
+                    div class="vote-ratio-readout" {
+                        span class="muted small" { "ratio " }
+                        strong id="vote-ratio-display" { "1:1" }
+                    }
+                    label class="vote-hud-slider" {
+                        input type="range" id="vote-preference-slider" min="0" max="100" value="50"
+                            aria-valuemin="0" aria-valuemax="100" aria-valuenow="50"
+                            aria-label=(format!("Preference: {} vs {}", left.as_str(), right.as_str()));
+                    }
+                    (vote_compare_actions(parent, next_pair))
+                }
+            }
+        }
+    }
+}
+
 fn vote_compare_actions(parent: &ItemId, next: Option<&(ItemId, ItemId)>) -> Markup {
     let next_href = next.map(|(l, r)| vote_compare_href(parent, l, r));
     html! {
@@ -250,41 +280,28 @@ pub async fn vote_page(
     );
 
     let body = html! {
-        div class="scope-theme vote-page-grid" style=(scope_theme_style(&parent)) {
-            section class="vote-compare-shell" {
-                h1 { "compare" }
-                (breadcrumb_path(&parent))
-                p class="muted vote-compare-scope" {
-                    "ranking children of "
-                    a href=(item_href(&parent)) { (child_title(&tree, &parent)) }
-                }
-                div class="vote-compare-pair" {
-                    (vote_compare_item_card(&tree, &left, "vote-compare-left"))
-                    span class="vote-compare-vs" { "vs" }
-                    (vote_compare_item_card(&tree, &right, "vote-compare-right"))
-                }
-                (vote_back_nav(&parent))
-                form id="vote-compare-form" method="POST" action="/ui" {
-                    input type="hidden" name=(UI_RPC_FIELD) value=(rpc_json);
-                    input type="hidden" name="ratio_left" id="vote-ratio-left" value="1";
-                    input type="hidden" name="ratio_right" id="vote-ratio-right" value="1";
-                    div class="vote-ratio-readout" {
-                        span class="muted small" { "ratio " }
-                        strong id="vote-ratio-display" { "1:1" }
+        div class="scope-theme vote-page" style=(scope_theme_style(&parent)) {
+            div class="vote-page-grid" {
+                section class="vote-compare-shell" {
+                    h1 { "compare" }
+                    (breadcrumb_path(&parent))
+                    p class="muted vote-compare-scope" {
+                        "ranking children of "
+                        a href=(item_href(&parent)) { (child_title(&tree, &parent)) }
                     }
-                    label class="vote-compare-slider-label" {
-                        span id="vote-slider-left-label" { (child_title(&tree, &left)) }
-                        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" { (child_title(&tree, &right)) }
+                    div class="vote-compare-pair" {
+                        (vote_compare_item_card(&tree, &left, "vote-compare-left"))
+                        span class="vote-compare-vs" { "vs" }
+                        (vote_compare_item_card(&tree, &right, "vote-compare-right"))
+                    }
+                    (vote_back_nav(&parent))
+                    div id="vote-edge-history-region" {
+                        (edge_history)
                     }
-                    (vote_compare_actions(&parent, next_pair.as_ref()))
-                }
-                div id="vote-edge-history-region" {
-                    (edge_history)
                 }
+                (vote_ranking_sidebar(&tree, &parent, &left, &right))
             }
-            (vote_ranking_sidebar(&tree, &parent, &left, &right))
+            (vote_hud_form(&parent, &left, &right, &rpc_json, next_pair.as_ref()))
         }
     };
 
diff --git a/server/static/sorter.css b/server/static/sorter.css
index 7d88114ff3c3d19011c003cf89e5fc32dd23ee74..50e34484c2b50d2eeb18f8d0ec4f34338363811a 100644
--- a/server/static/sorter.css
+++ b/server/static/sorter.css
@@ -287,6 +287,11 @@ h1 {
   }
 }
 
+.vote-page {
+  --vote-hud-height: 4.75rem;
+  padding-bottom: calc(var(--vote-hud-height) + 0.75rem);
+}
+
 .vote-page-grid {
   display: grid;
   grid-template-columns: minmax(0, 1fr) minmax(280px, 360px);
@@ -294,6 +299,50 @@ h1 {
   align-items: start;
 }
 
+.vote-hud {
+  position: fixed;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 50;
+  border-top: 2px solid var(--border);
+  background: color-mix(in oklch, var(--panel) 92%, var(--bg));
+  box-shadow: 0 -0.35rem 1.25rem rgba(0, 0, 0, 0.4);
+  padding: 0.45rem 0.75rem;
+  backdrop-filter: blur(8px);
+}
+
+.vote-hud-inner {
+  max-width: 56rem;
+  margin: 0 auto;
+  display: grid;
+  grid-template-columns: min-content minmax(0, 1fr) auto;
+  gap: 0.5rem 0.65rem;
+  align-items: center;
+}
+
+.vote-hud #vote-compare-form {
+  margin: 0;
+}
+
+.vote-hud .vote-ratio-readout {
+  margin: 0;
+  text-align: center;
+}
+
+.vote-hud .vote-compare-actions {
+  margin-top: 0;
+  justify-content: flex-end;
+  gap: 0.5rem;
+}
+
+.vote-hud .btn-primary,
+.vote-hud .btn-secondary,
+.vote-hud .vote-compare-next {
+  padding: 0.35rem 0.65rem;
+  font-size: 0.875rem;
+}
+
 .vote-compare-shell {
   min-width: 0;
 }
@@ -366,14 +415,14 @@ h1 {
   cursor: default;
 }
 
-.vote-ratio-readout {
-  margin: 1rem 0 0.25rem;
-  text-align: center;
-}
-
-.vote-ratio-readout strong {
+.vote-hud .vote-ratio-readout strong {
   color: var(--accent);
   font-variant-numeric: tabular-nums;
+  font-size: 1.1rem;
+}
+
+.vote-hud .vote-ratio-readout .small {
+  font-size: 0.75rem;
 }
 
 .vote-compare-next {
@@ -393,25 +442,69 @@ h1 {
   text-decoration: underline;
 }
 
-.vote-compare-slider-label {
-  display: grid;
-  grid-template-columns: 1fr auto 1fr;
-  gap: 0.75rem;
-  align-items: center;
-  margin: 1.25rem 0;
+.vote-hud-slider {
+  display: block;
+  margin: 0;
+  min-width: 0;
 }
 
-.vote-compare-slider-label input[type="range"] {
+.vote-hud-slider input[type="range"] {
+  -webkit-appearance: none;
+  appearance: none;
+  display: block;
   width: 100%;
-  min-width: 160px;
+  height: 1.5rem;
+  margin: 0;
+  background: transparent;
+  cursor: pointer;
+}
+
+.vote-hud-slider input[type="range"]::-webkit-slider-runnable-track {
+  height: 0.65rem;
+  border-radius: 0;
+  border: 2px solid var(--border);
+  background: linear-gradient(
+    to right,
+    var(--accent) 0%,
+    var(--accent) var(--vote-slider-pct, 50%),
+    color-mix(in oklch, var(--muted) 55%, var(--bg)) var(--vote-slider-pct, 50%),
+    color-mix(in oklch, var(--muted) 55%, var(--bg)) 100%
+  );
+}
+
+.vote-hud-slider input[type="range"]::-webkit-slider-thumb {
+  -webkit-appearance: none;
+  appearance: none;
+  width: 1.1rem;
+  height: 1.35rem;
+  margin-top: -0.42rem;
+  border: 2px solid var(--fg);
+  border-radius: 0;
+  background: var(--accent);
+  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.35);
+}
+
+.vote-hud-slider input[type="range"]::-moz-range-track {
+  height: 0.65rem;
+  border-radius: 0;
+  border: 2px solid var(--border);
+  background: color-mix(in oklch, var(--muted) 55%, var(--bg));
 }
 
-#vote-slider-left-label {
-  text-align: right;
+.vote-hud-slider input[type="range"]::-moz-range-progress {
+  height: 0.65rem;
+  border-radius: 0;
+  background: var(--accent);
 }
 
-#vote-slider-right-label {
-  text-align: left;
+.vote-hud-slider input[type="range"]::-moz-range-thumb {
+  width: 1.1rem;
+  height: 1.35rem;
+  border: 2px solid var(--fg);
+  border-radius: 0;
+  background: var(--accent);
+  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.35);
+  cursor: pointer;
 }
 
 .vote-edge-history-title {
@@ -459,4 +552,24 @@ h1 {
     position: static;
     max-height: none;
   }
+}
+
+@media (max-width: 640px) {
+  .vote-page {
+    --vote-hud-height: 7.5rem;
+  }
+
+  .vote-hud-inner {
+    grid-template-columns: 1fr;
+    grid-template-rows: auto auto auto;
+    gap: 0.35rem;
+  }
+
+  .vote-hud .vote-compare-actions {
+    justify-content: center;
+  }
+
+  .vote-hud {
+    padding-bottom: max(0.45rem, env(safe-area-inset-bottom));
+  }
 }
\ No newline at end of file
diff --git a/server/static/sorter_ui.js b/server/static/sorter_ui.js
index 4c19a053c0fa62c951e8356d35c028b6f0d0f538..c438851b792ad85f8b6106b7c3fad9ea945275f7 100644
--- a/server/static/sorter_ui.js
+++ b/server/static/sorter_ui.js
@@ -138,6 +138,8 @@
     function update() {
       var v = parseInt(slider.value, 10);
       if (!Number.isFinite(v)) v = 50;
+      slider.style.setProperty('--vote-slider-pct', v + '%');
+      slider.setAttribute('aria-valuenow', String(v));
       var left = Math.max(1, 100 - v);
       var right = Math.max(1, v);
       var divisor = gcd(left, right);
diff --git a/server/tests/integration_ui.rs b/server/tests/integration_ui.rs
index 9d770cda9990eef86e46721a22221aa8c1f9571c..1929d90ec7a35f53592c622699640d0dfc9f763c 100644
--- a/server/tests/integration_ui.rs
+++ b/server/tests/integration_ui.rs
@@ -185,8 +185,10 @@ async fn vote_page_renders_live_ranking_sidebar() {
     assert!(html.contains("scope-theme"));
     assert!(html.contains("--accent: oklch("));
     assert!(html.contains("--bg: oklch("));
+    assert!(html.contains("vote-hud"));
     assert!(html.contains("vote-ratio-display"));
     assert!(html.contains(">1:1<"));
+    assert!(!html.contains("vote-slider-left-label"));
     assert!(html.contains("--rank-bg: oklch("));
     assert!(html.contains("--rank-fg: #"));
     assert!(html.contains("data-rank-item=\"alpha\""));

download full diff A

B — c_9c6e7864a657 (tommy-mor)

message

[df844c52] Stabilize vote HUD layout and color the slider toward the winning side.

Fixed-width ratio and action columns stop track reflow flicker; track fill flips to the right when the right item leads.

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

diff preview

diff --git a/server/src/html/mod.rs b/server/src/html/mod.rs
index dcf05df143825f4f8ba9ee3b3b5f8ab2e69950c7..588cf62bcea608ed4ede363810f022a67b43b960 100644
--- a/server/src/html/mod.rs
+++ b/server/src/html/mod.rs
@@ -529,5 +529,6 @@ mod tests {
         assert!(SORTER_UI_JS.contains("var divisor = gcd(left, right)"));
         assert!(SORTER_UI_JS.contains("ratioDisplay.textContent = left + ':' + right"));
         assert!(SORTER_UI_JS.contains("--vote-slider-pct"));
+        assert!(SORTER_UI_JS.contains("dataset.winner"));
     }
 }
diff --git a/server/src/html/vote.rs b/server/src/html/vote.rs
index 872d23ddf6c18fab016a624de85736fdc2dcde63..d32b206e2fb043466c0ce333d7d215dc1a3e91c9 100644
--- a/server/src/html/vote.rs
+++ b/server/src/html/vote.rs
@@ -154,6 +154,7 @@ fn vote_hud_form(
                     }
                     label class="vote-hud-slider" {
                         input type="range" id="vote-preference-slider" min="0" max="100" value="50"
+                            data-winner="left"
                             aria-valuemin="0" aria-valuemax="100" aria-valuenow="50"
                             aria-label=(format!("Preference: {} vs {}", left.as_str(), right.as_str()));
                     }
diff --git a/server/static/sorter.css b/server/static/sorter.css
index 50e34484c2b50d2eeb18f8d0ec4f34338363811a..7b485dd232f60363f4eff0c5a362addf191d05c6 100644
--- a/server/static/sorter.css
+++ b/server/static/sorter.css
@@ -316,9 +316,12 @@ h1 {
   max-width: 56rem;
   margin: 0 auto;
   display: grid;
-  grid-template-columns: min-content minmax(0, 1fr) auto;
+  /* Fixed side columns so ratio text / buttons never resize the slider track. */
+  grid-template-columns: var(--vote-ratio-col) minmax(0, 1fr) var(--vote-actions-col);
   gap: 0.5rem 0.65rem;
   align-items: center;
+  --vote-ratio-col: 3.25rem;
+  --vote-actions-col: 11.5rem;
 }
 
 .vote-hud #vote-compare-form {
@@ -327,13 +330,23 @@ h1 {
 
 .vote-hud .vote-ratio-readout {
   margin: 0;
+  width: var(--vote-ratio-col);
+  min-width: var(--vote-ratio-col);
   text-align: center;
+  white-space: nowrap;
+}
+
+.vote-hud .vote-ratio-readout .small {
+  display: none;
 }
 
 .vote-hud .vote-compare-actions {
   margin-top: 0;
   justify-content: flex-end;
   gap: 0.5rem;
+  width: var(--vote-actions-col);
+  min-width: var(--vote-actions-col);
+  flex-shrink: 0;
 }
 
 .vote-hud .btn-primary,
@@ -416,15 +429,13 @@ h1 {
 }
 
 .vote-hud .vote-ratio-readout strong {
+  display: inline-block;
+  min-width: 3ch;
   color: var(--accent);
   font-variant-numeric: tabular-nums;
   font-size: 1.1rem;
 }
 
-.vote-hud .vote-ratio-readout .small {
-  font-size: 0.75rem;
-}
-
 .vote-compare-next {
   color: var(--accent);
   text-decoration: none;
@@ -459,7 +470,11 @@ h1 {
   cursor: pointer;
 }
 
-.vote-hud-slider input[type="range"]::-webkit-slider-runnable-track {
+.vote-hud-slider input[type="range"] {
+  --vote-track-muted: color-mix(in oklch, var(--muted) 55%, var(--bg));
+}
+
+.vote-hud-slider input[type="range"][data-winner="left"]::-webkit-slider-runnable-track {
   height: 0.65rem;
   border-radius: 0;
   border: 2px solid var(--border);
@@ -467,8 +482,21 @@ h1 {
     to right,
     var(--accent) 0%,
     var(--accent) var(--vote-slider-pct, 50%),
-    color-mix(in oklch, var(--muted) 55%, var(--bg)) var(--vote-slider-pct, 50%),
-    color-mix(in oklch, var(--muted) 55%, var(--bg)) 100%
+    var(--vote-track-muted) var(--vote-slider-pct, 50%),
+    var(--vote-track-muted) 100%
+  );
+}
+
+.vote-hud-slider input[type="range"][data-winner="right"]::-webkit-slider-runnable-track {
+  height: 0.65rem;
+  border-radius: 0;
+  border: 2px solid var(--border);
+  background: linear-gradient(
+    to right,
+    var(--vote-track-muted) 0%,
+    var(--vote-track-muted) var(--vote-slider-pct, 50%),
+    var(--accent) var(--vote-slider-pct, 50%),
+    var(--accent) 100%
   );
 }
 
@@ -484,17 +512,35 @@ h1 {
   box-shadow: 0 2px 0 rgba(0, 0, 0, 0.35);
 }
 
-.vote-hud-slider input[type="range"]::-moz-range-track {
+.vote-hud-slider input[type="range"][data-winner="left"]::-moz-range-track,
+.vote-hud-slider input[type="range"][data-winner="right"]::-moz-range-track {
   height: 0.65rem;
   border-radius: 0;
   border: 2px solid var(--border);
-  background: color-mix(in oklch, var(--muted) 55%, var(--bg));
+}
+
+.vote-hud-slider input[type="range"][data-winner="left"]::-moz-range-track {
+  background: linear-gradient(
+    to right,
+    var(--accent) 0%,
+    var(--accent) var(--vote-slider-pct, 50%),
+    var(--vote-track-muted) var(--vote-slider-pct, 50%),
+    var(--vote-track-muted) 100%
+  );
+}
+
+.vote-hud-slider input[type="range"][data-winner="right"]::-moz-range-track {
+  background: linear-gradient(
+    to right,
+    var(--vote-track-muted) 0%,
+    var(--vote-track-muted) var(--vote-slider-pct, 50%),
+    var(--accent) var(--vote-slider-pct, 50%),
+    var(--accent) 100%
+  );
 }
 
 .vote-hud-slider input[type="range"]::-moz-range-progress {
-  height: 0.65rem;
-  border-radius: 0;
-  background: var(--accent);
+  background: transparent;
 }
 
 .vote-hud-slider input[type="range"]::-moz-range-thumb {
@@ -563,6 +609,14 @@ h1 {
     grid-template-columns: 1fr;
     grid-template-rows: auto auto auto;
     gap: 0.35rem;
+    --vote-ratio-col: 100%;
+    --vote-actions-col: 100%;
+  }
+
+  .vote-hud .vote-ratio-readout,
+  .vote-hud .vote-compare-actions {
+    width: auto;
+    min-width: 0;
   }
 
   .vote-hud .vote-compare-actions {
diff --git a/server/static/sorter_ui.js b/server/static/sorter_ui.js
index c438851b792ad85f8b6106b7c3fad9ea945275f7..f665b45f4fd4771427dc4c37e563e4976873beee 100644
--- a/server/static/sorter_ui.js
+++ b/server/static/sorter_ui.js
@@ -148,6 +148,7 @@
       if (leftInput) leftInput.value = String(left);
       if (rightInput) rightInput.value = String(right);
       if (ratioDisplay) ratioDisplay.textContent = left + ':' + right;
+      slider.dataset.winner = left >= right ? 'left' : 'right';
     }
     slider.addEventListener('input', update);
     update();

download full diff B

Hardlinks — judgments / attempts / prompt

prompt download

judgments

attempts

Prompt text is loaded only by the download route.