Side A makes substantive functional fixes: correct out-of-band delegate attribution, real bugfix replacing a silent fallback with an explicit error when the sibling pool is too small, removal of a broken/pointless UI feature, and a route rename applied consistently across code and tests. Side B merely deletes a scratch notes file, which is trivial housekeeping with no functional impact on the project.
constitution · epochs · watch · epoch 3
c_11d6a0ec9839 (tommy-mor) vs c_5e6847233bd6 (tommy-mor)
download prompt · raw event · cmp_03c57651ee297e
council reasoning
Commit A delivers real product fixes: correct out-of-band delegate attribution, stopping a bad fallback when the sibling pool is too small, removing a useless control, and a route rename with matching call sites/tests. Commit B only deletes a scratch notes.tdsl file and adds no lasting design or behavior.
Side A makes multiple functional improvements: it correctly passes delegate attribution out-of-band instead of embedding it in DSL text, fixes vote-pair selection by returning no next pair rather than incorrectly falling back to all items when the sibling pool is too small, simplifies the UI by removing the swap button, and consistently renames the public voting route from /vote/compare to /vote across code and tests. Side B only deletes a developer notes file, removing project documentation without adding runtime behavior or architectural value.
Side A makes functional improvements: it fixes vote pair selection by returning None instead of incorrectly falling back to all items, properly threads delegate_opt through RPC calls, and updates routes and UI behavior (e.g., removing the swap button and renaming /vote/compare to /vote). Side B only deletes a notes file with no impact on runtime behavior.
Side A makes substantive behavioral changes: it correctly passes `delegate_opt` out-of-band instead of embedding `WEB_BROWSER_AGENT` in the DSL text, prevents incorrect fallback to all items when the sibling pool is < 2 (returning `None` instead), removes the ineffective "swap sides" UI, and renames `/vote/compare` to `/vote` with routing and test updates. Side B only deletes a `notes.tdsl` file containing informal TODO notes, with no impact on runtime behavior.
sides
A — c_11d6a0ec9839 (tommy-mor)
message
[36e92cf2] Fix /vote/compare page: attribution, fallback, swap button, and route rename. - Pass WEB_BROWSER_AGENT as delegate_opt out-of-band (not in DSL body text) - Error instead of falling back to all items when sibling pool < 2 - Remove pointless "swap sides" button - Rename route /vote/compare → /vote Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
diff preview
diff --git a/server/src/api/rpc.rs b/server/src/api/rpc.rs
index 0b7070a2b7289e5c40778c8d062bd4150285a8df..8e3d8e34922ddaaa89ea699edd99402bd40fdb1b 100644
--- a/server/src/api/rpc.rs
+++ b/server/src/api/rpc.rs
@@ -281,6 +281,7 @@ pub async fn rpc_post_with_bearer(
bearer_token: &str,
room: String,
thread_tag: String,
+ delegate_opt: Option<String>,
text: String,
) -> Result<RpcResult, RpcErr> {
use axum::http::{header, HeaderMap, HeaderValue};
@@ -288,7 +289,7 @@ pub async fn rpc_post_with_bearer(
let hv = HeaderValue::from_str(&format!("Bearer {bearer_token}"))
.map_err(|_| ("invalid session token".into(), None))?;
headers.insert(header::AUTHORIZATION, hv);
- rpc_post(state, &headers, room, thread_tag, None, text, false).await
+ rpc_post(state, &headers, room, thread_tag, delegate_opt, text, false).await
}
async fn rpc_check(
diff --git a/server/src/api/ui_html.rs b/server/src/api/ui_html.rs
index a13a94767d8905adc09e31effff838e166781b6c..5faa642451d69555cb391974beb0ad22c9355c8c 100644
--- a/server/src/api/ui_html.rs
+++ b/server/src/api/ui_html.rs
@@ -118,6 +118,7 @@ async fn dispatch_ui_action(
&session.bearer,
room.clone(),
thread_tag.clone(),
+ None,
text,
)
.await
@@ -246,8 +247,7 @@ async fn dispatch_ui_action(
}
let text = format!(
- "@{}\n{{\n{}\n}}\n{} {}:{} {}\n",
- crate::api::auth::WEB_BROWSER_AGENT,
+ "{{\n{}\n}}\n{} {}:{} {}\n",
exp,
left_id.as_str(),
rl,
@@ -260,6 +260,7 @@ async fn dispatch_ui_action(
&session.bearer,
room.clone(),
thread_tag.clone(),
+ Some(crate::api::auth::WEB_BROWSER_AGENT.to_string()),
text,
)
.await
diff --git a/server/src/html/forum/nav.rs b/server/src/html/forum/nav.rs
index d33dc50f0b89a5ed7730c3bca204a67f6309388f..ae67f550a4bdf0bbeff58384c29a715015b64bd5 100644
--- a/server/src/html/forum/nav.rs
+++ b/server/src/html/forum/nav.rs
@@ -85,7 +85,7 @@ impl ThreadNav {
format!("{}/{}/{}", self.thread_path_prefix, tag, idx)
}
- /// Empty for public; `/r/:seg` for room — prefix for routes like `/vote/compare`.
+ /// Empty for public; `/r/:seg` for room — prefix for routes like `/vote`.
pub(crate) fn room_path_prefix_for_vote_compare(&self) -> String {
match &self.scope {
ScopeId::Public => String::new(),
diff --git a/server/src/html/garden/vote.rs b/server/src/html/garden/vote.rs
index bdb4008c29ba49d7dc97b1cd2c32d9cdfce1ca04..1d7fc8aa7436bfa9c1186dfd940a8d751ab7e088 100644
--- a/server/src/html/garden/vote.rs
+++ b/server/src/html/garden/vote.rs
@@ -208,7 +208,7 @@ pub(crate) async fn vote_compare_post_success_js(
state: &AppState,
nav: &ThreadNav,
_room_wire: &str,
- thread_tag: &str,
+ _thread_tag: &str,
left: &ItemId,
right: &ItemId,
_post_id: &str,
@@ -218,8 +218,7 @@ pub(crate) async fn vote_compare_post_success_js(
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(), left, right, Some(thread_tag));
+ let nav_markup = vote_compare_nav_markup(nav, next_pair.as_ref());
drop(reduced);
JsBuilder::new()
.morph_inner_selector("#vote-edge-history-region", edge_history)
@@ -236,7 +235,7 @@ pub(super) fn vote_compare_href(
let left_q = urlencoding::encode(left.as_str());
let right_q = urlencoding::encode(right.as_str());
let base = format!(
- "{}/vote/compare?left={}&right={}",
+ "{}/vote?left={}&right={}",
nav.room_path_prefix_for_vote_compare(),
left_q,
right_q
@@ -251,12 +250,8 @@ pub(super) fn vote_compare_href(
fn vote_compare_nav_markup(
nav: &ThreadNav,
next_pair: Option<&(ItemId, ItemId)>,
- left: &ItemId,
- right: &ItemId,
- thread_override: Option<&str>,
) -> maud::Markup {
let next_pair_href = next_pair.map(|(nl, nr)| vote_compare_href(nav, nl, nr, None));
- let swap_pair_href = vote_compare_href(nav, right, left, thread_override);
html! {
div class="vote-compare-nav" {
@if let Some(href) = &next_pair_href {
@@ -264,7 +259,6 @@ fn vote_compare_nav_markup(
} @else {
span class="vote-compare-next is-disabled" { "no next pair" }
}
- a class="vote-compare-next" href=(swap_pair_href) { "swap sides" }
}
}
}
@@ -274,7 +268,7 @@ pub(super) fn suggest_next_vote_pair(
current_left: &ItemId,
current_right: &ItemId,
) -> Option<(ItemId, ItemId)> {
- let mut pool: Vec<ItemId> = if current_left.parent().as_ref().map(|p| p.as_str())
+ let pool: Vec<ItemId> = if current_left.parent().as_ref().map(|p| p.as_str())
== current_right.parent().as_ref().map(|p| p.as_str())
{
current_left
@@ -291,7 +285,7 @@ pub(super) fn suggest_next_vote_pair(
Vec::new()
};
if pool.len() < 2 {
- pool = content.items.iter().cloned().collect();
+ return None;
}
suggest_next_pair_in_pool(
&content.ranking_group,
@@ -334,7 +328,7 @@ pub struct VoteCompareQuery {
pub thread: Option<String>,
}
-/// Public pairwise vote UI — `/vote/compare?left=&right=&thread=`.
+/// Public pairwise vote UI — `/vote?left=&right=&thread=`.
pub async fn vote_compare_page(
State(state): State<AppState>,
Query(q): Query<VoteCompareQuery>,
@@ -426,7 +420,7 @@ async fn vote_compare_inner(
let next_path = uri
.path_and_query()
.map(|pq| pq.as_str().to_string())
- .unwrap_or_else(|| "/vote/compare".into());
+ .unwrap_or_else(|| "/vote".into());
let rpc_json = template_json_compact(&json!({
"action": "vote_compare_post",
@@ -462,7 +456,7 @@ async fn vote_compare_inner(
Some(&item_bodies_for_cards),
))
}
- (vote_compare_nav_markup(&nav, next_pair.as_ref(), &left, &right, q.thread.as_deref()))
+ (vote_compare_nav_markup(&nav, next_pair.as_ref()))
div id="vote-edge-history-region" {
(edge_history)
}
diff --git a/server/src/html/ui_action.rs b/server/src/html/ui_action.rs
index c7b643c8dde37dd1e3001f5ff716fe4f79d24c5e..dd5294202569454acf9f41b458718700bbf96f39 100644
--- a/server/src/html/ui_action.rs
+++ b/server/src/html/ui_action.rs
@@ -38,7 +38,7 @@ pub enum HtmlUiAction {
#[serde(default)]
form_id: Option<String>,
},
- /// Post a pairwise vote from `/vote/compare` (browser compose).
+ /// Post a pairwise vote from `/vote` (browser compose).
VoteComparePost {
room: String,
thread_tag: String,
diff --git a/server/src/lib.rs b/server/src/lib.rs
index ad8e31099c807fb5844acb16cd5086a2f19327a7..e102e73ec167fa35dcc22d4ec8c6f40202982429 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -82,9 +82,9 @@ pub fn create_app(state: AppState) -> Router {
.route("/u/:username", get(crate::html::user_profile_page))
.route("/try", get(crate::html::editor_page))
.route("/try/check", post(crate::html::editor_check))
- .route("/vote/compare", get(crate::html::vote_compare_page))
+ .route("/vote", get(crate::html::vote_compare_page))
.route(
- "/r/:room_key/vote/compare",
+ "/r/:room_key/vote",
get(crate::html::room_vote_compare_page),
)
.route("/~", get(crate::html::garden_index))
diff --git a/server/tests/integration_html.rs b/server/tests/integration_html.rs
index a25a456c7cafd74a69e65e0c2e8ed2bfb2e27498..b3e31376737b2d5aabb739f408984a1f5ae0c974 100644
--- a/server/tests/integration_html.rs
+++ b/server/tests/integration_html.rs
@@ -52,12 +52,12 @@ async fn test_view_counts_increment_and_display() {
.normalized_storage()
.to_storage_string();
let vote_q_right_first = format!(
- "/vote/compare?right={}&left={}",
+ "/vote?right={}&left={}",
urlencoding::encode(&right),
urlencoding::encode(&left)
);
let vote_q_left_first = format!(
- "/vote/compare?left={}&right={}",
+ "/vote?left={}&right={}",
urlencoding::encode(&left),
urlencoding::encode(&right)
);
@@ -148,7 +148,7 @@ https://github.com/ghvotehi/a/issues/10 {\n\
.normalized_storage()
.to_storage_string();
let q = format!(
- "/vote/compare?left={}&right={}",
+ "/vote?left={}&right={}",
urlencoding::encode(&left),
urlencoding::encode(&right)
);
diff --git a/server/tests/integration_ui.rs b/server/tests/integration_ui.rs
index 091afd922d28f48ab36aa9b9084aa825036d1f74..23db7b5672418d5bb0ab7529e05ef1f89e59062a 100644
--- a/server/tests/integration_ui.rs
+++ b/server/tests/integration_ui.rs
@@ -110,7 +110,7 @@ async fn test_choose_username_carries_redirect_next() {
.expect("pending session must exist");
pending.provider = Some("google".to_string());
pending.provider_id = Some("google-user-redirect".to_string());
- pending.redirect_next = Some("/vote/compare?left=%7E%2Fa&right=%7E%2Fb".to_string());
+ pending.redirect_next = Some("/vote?left=%7E%2Fa&right=%7E%2Fb".to_string());
}
let choose = client
@@ -122,7 +122,7 @@ async fn test_choose_username_carries_redirect_next() {
assert_eq!(choose.status(), reqwest::StatusCode::OK);
let body = choose.text().await.unwrap();
- assert!(body.contains("window.location = \"/vote/compare?left=%7E%2Fa&right=%7E%2Fb\""));
+ assert!(body.contains("window.location = \"/vote?left=%7E%2Fa&right=%7E%2Fb\""));
}
#[tokio::test]
diff --git a/test/browser_vote_compare.clj b/test/browser_vote_compare.clj
index 7cd1e467fdd8a4480df1be2afd78adb45b9cdc6d..a98e144756421e1fa5064db6a9fc0dd8b08471c7 100644
--- a/test/browser_vote_compare.clj
+++ b/test/browser_vote_compare.clj
@@ -66,7 +66,7 @@
:headers {"Authorization" (str "Bearer " alice-token)})
post-json (json/parse-string (:body post-resp) false)
_ (is (true? (get-in post-json ["results" 0 "ok"])) "seed items + edge vote via rpc")
- cmp-url (str base-url "/vote/compare?left=" (enc left-url) "&right=" (enc third-url))]
+ cmp-url (str base-url "/vote?left=" (enc left-url) "&right=" (enc third-url))]
(core/with-playwright [pw]
(core/with-browser [browser (core/launch-chromium pw {:headless true :channel "chrome"})]
(core/with-context [ctx (core/new-context browser)]
B — c_5e6847233bd6 (tommy-mor)
message
[3723ef77] Delete notes.tdsl
diff preview
diff --git a/notes.tdsl b/notes.tdsl
deleted file mode 100644
index 067951bfdaa10b9c450d23bd1095a68f769f1e81..0000000000000000000000000000000000000000
--- a/notes.tdsl
+++ /dev/null
@@ -1,32 +0,0 @@
-don't truncate ever on post page
-delete button should be in corner, expandable? and should work lol. errors are not being morphed properly, no error div.
-disabled a class doesn't work
-should have a tiny bit 3d
-vote history needs padding around its body. and cursor pointer on the conrner expand button
-post page shouldn't have h3, it should have link to self on breadcrumb. the breadcrumb is the title
-
-{{dark = time-ordered · light = vote-ranked }} should go at the very bottom as a footer/mantra, on all pages, below the cli link. or just in the controls next to theme switcher.
-
-post page doesn't have login header layout. garden doesn' thave matching login screne, breadcrumb strucutre... nede to have a validator or something to make that sync
-
-post prose should be serif. other fonts should be more consistent..
-
-room page needs 2 fewer headers lol
-
-need a consistent style for "collections". not sure if it sohuld have border...
-
-frontpage
- - "your rooms" haeder above
- -
-
-
-~ in garden inside a room says "~/unranked among siblings" which is wrong
-maybe root item body should be hardcoded to flower? you can't define a body for root in the syntax i don't think
-
-need consistent + in corener of section header that morphs/expands in add form. i think morph. you can only see the form if you're logged in.
-
-my json-encoded plan is not being used for the forms. i want that same plan for the morphable/expandable + signs that produce forms. form target encoded... there was a plan.md with this. plan2.md...
-
-new public thread form needs fewer words
-
-new thread errors don't propogate properly. no #errors div, it used to be more precise? maybe it should be more precise?
Hardlinks — judgments / attempts / prompt
judgments
- ~anthropic/claude-sonnet-latest: A (9:1)
- ~x-ai/grok-latest: A (10:1)
- openai/gpt-chat-latest: A (20:1)
- openai/gpt-5.3-chat: A (9:1)
- openai/gpt-5.2-chat: A (9:1)
attempts
- ~anthropic/claude-sonnet-latest #1
- ~x-ai/grok-latest #1
- openai/gpt-chat-latest #1
- openai/gpt-5.3-chat #1
- openai/gpt-5.2-chat #1
Prompt text is loaded only by the download route.