constitution · epochs · watch · epoch 3

comparison

c_b2cf41238f57 (tommy-mor) vs c_35dc6601c89d (tommy-mor)

download prompt · raw event · cmp_f04a8a78ca4c3d

council reasoning

~anthropic/claude-sonnet-latest · winner B · 9:1 · permalink

Side B introduces a substantial, functional module (JsBuilder abstraction, theming, layout, breadcrumb rendering, link embedding logic, and tests) that provides real, lasting functionality to the codebase. Side A is a trivial config trim removing a few bb.edn tasks and renaming one, offering negligible lasting value compared to B's substantive feature work.

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

A only deletes/renames babashka tasks in bb.edn (config churn with no product logic). B adds a large, lasting HTML/UI module including JsBuilder, theme handling, layout shells, linkify/embeds, and related helpers that form core application behavior.

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

Side B adds a substantial new Rust module that introduces core application functionality, including theme handling, static asset serving, JavaScript response/building utilities, layout rendering, linkification, media embedding, routing exports, and associated helper logic and tests. Side A only removes two Babashka tasks from `bb.edn` and renames a remaining task from `walkthrough-fixture` to `fixture`, which is a small maintenance/configuration cleanup with limited lasting impact.

sides

A — c_b2cf41238f57 (tommy-mor)

message

[dd76f7b1] trim bb.edn

diff preview

diff --git a/bb.edn b/bb.edn
index f4319094c7864757901ed60df4a87160b7a27e59..5d1d642d75d03f70879d7673e7ad95360336a8b1 100644
--- a/bb.edn
+++ b/bb.edn
@@ -46,18 +46,7 @@
                                            "PORT"          "8080"
                                            "RUST_LOG"      "info"})})))}
 
-  test
-  {:doc "HTTP integration tests via Kaocha (clojure -M:kaocha :http-integration). Full suite: clojure -M:kaocha."
-   :task (let [r (deref (p/process ["clojure" "-M:kaocha" ":http-integration"] {:inherit true}))]
-           (when-not (zero? (:exit r)) (System/exit (:exit r))))}
-
-  browser-ui-morph
-  {:doc "Playwright (Spel): POST /ui __rpc__ DOM morph (expand_post_full). Requires: clojure CLI, Chrome, `npx playwright install chromium`."
-   :requires ([babashka.process :as p])
-   :task (let [r (deref (p/process ["clojure" "-M" "-m" "test.runner" "browser-ui-morph"] {:inherit true}))]
-           (when-not (zero? (:exit r)) (System/exit (:exit r))))}
-
-  walkthrough-fixture
+  fixture
   {:doc "Run local server + mock OAuth + seeded walkthrough data for manual browser demos"
    :requires ([test.walkthrough-fixture :as walkthrough-fixture])
    :task (walkthrough-fixture/run-fixture)}

download full diff A

B — c_35dc6601c89d (tommy-mor)

message

[deb79714] jsbuilder ofc

diff preview

diff --git a/mod.rs b/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b1dfe895515a1ba94304c52ac4ea82ff6bb84c16
--- /dev/null
+++ b/mod.rs
@@ -0,0 +1,929 @@
+use axum::{
+    body::Body,
+    extract::Path,
+    http::{header, HeaderValue, StatusCode, Uri},
+    response::{IntoResponse, Response},
+    Form,
+};
+use axum_extra::extract::cookie::CookieJar;
+use maud::{html, Markup, DOCTYPE};
+use serde::Deserialize;
+use std::collections::{HashMap, HashSet};
+
+mod auth;
+mod breadcrumb_path;
+mod editor;
+mod forum;
+mod garden;
+pub mod routing;
+mod search;
+pub mod ui_action;
+use breadcrumb_path::{ExternalOntologyPath, OntologyPath};
+
+pub use auth::{
+    auth_complete_page, auth_signed_in_fragment, choose_username_error_fragment,
+    choose_username_page,
+};
+pub use editor::{editor_check, editor_page};
+pub use forum::{
+    home, room_page, room_thread_post_view, room_thread_view, thread_feed_html,
+    thread_feed_html_for_room, thread_feed_region_markup, thread_post_view, thread_view, ThreadNav,
+};
+
+pub use forum::user_profile_page;
+pub(crate) use forum::{
+    fragment_new_thread_slot, login_to_post_hint_markup, room_members_section_markup,
+    thread_ui_collapse_redacted_post, thread_ui_copy_thread, thread_ui_expand_post_full, thread_ui_expand_redacted_post,
+    user_can_post_room, user_can_view_room,
+};
+pub(crate) use garden::{
+    encode_pin_cookie_value, external_resolver_status_markup, vote_compare_post_success_js,
+    GARDEN_PIN_COOKIE,
+};
+pub use garden::{
+    external_garden_index, external_ontology_path, garden_index, ontology_path,
+    room_external_garden_index, room_external_ontology_path, room_garden_index, room_ontology_path,
+    room_vote_compare_page, vote_compare_page,
+};
+pub use routing::RouteContext;
+pub use search::{search_page, search_results_fragment};
+pub use ui_action::{parse_html_ui_from_form, HtmlUiAction, HtmlUiParseError, UI_RPC_FIELD};
+
+/// Public profile URL path for a stored username (no `@`).
+pub(crate) fn profile_href(username: &str) -> String {
+    format!("/u/{username}")
+}
+
+/// Cookie name for UI theme (must match document.cookie migration in [`layout`]).
+pub const SLUG_THEME_COOKIE: &str = "slug-theme";
+
+/// Normalize a requested theme id to a known stylesheet key.
+pub fn normalize_theme(raw: &str) -> &'static str {
+    match raw {
+        "retro" => "retro",
+        "retro_craft" => "retro_craft",
+        _ => "default",
+    }
+}
+
+/// Resolved theme for rendering and cookie re-issue.
+pub fn theme_from_jar(jar: &CookieJar) -> &'static str {
+    jar.get(SLUG_THEME_COOKIE)
+        .map(|c| normalize_theme(c.value()))
+        .unwrap_or("default")
+}
+
+/// `Path` + optional `?query` for round-tripping after `POST /theme`.
+pub fn theme_next_from_uri(uri: &Uri) -> String {
+    uri.path_and_query()
+        .map(|pq| pq.as_str().to_string())
+        .filter(|s| !s.is_empty())
+        .unwrap_or_else(|| "/".to_string())
+}
+
+/// `Set-Cookie` for a validated theme (ASCII cookie value).
+pub fn theme_cookie_header_value(theme: &str) -> HeaderValue {
+    let t = normalize_theme(theme);
+    let s = format!("{SLUG_THEME_COOKIE}={t}; Path=/; SameSite=Lax; Max-Age=31536000");
+    HeaderValue::from_str(&s).expect("theme cookie must be ASCII")
+}
+
+/// Re-issue theme cookie on responses that also set `slug_session`, so login does not drop theme.
+pub fn theme_cookie_header_from_jar(jar: &CookieJar) -> Option<HeaderValue> {
+    let c = jar.get(SLUG_THEME_COOKIE)?;
+    Some(theme_cookie_header_value(c.value()))
+}
+
+fn sanitize_theme_next(next: Option<&str>) -> String {
+    let s = next.unwrap_or("/").trim();
+    if s.starts_with('/') && !s.starts_with("//") && s.len() < 8192 {
+        s.to_string()
+    } else {
+        "/".to_string()
+    }
+}
+
+#[derive(Debug, Deserialize)]
+pub struct ThemeForm {
+    theme: String,
+    next: Option<String>,
+}
+
+/// `POST /theme` — set theme cookie and redirect back (full navigation, no fetch).
+pub async fn post_theme(Form(form): Form<ThemeForm>) -> impl IntoResponse {
+    let theme = normalize_theme(&form.theme);
+    let next = sanitize_theme_next(form.next.as_deref());
+    let loc =
+        HeaderValue::try_from(next.as_str()).unwrap_or_else(|_| HeaderValue::from_static("/"));
+    Response::builder()
+        .status(StatusCode::SEE_OTHER)
+        .header(header::LOCATION, loc)
+        .header(header::SET_COOKIE, theme_cookie_header_value(theme))
+        .body(Body::empty())
+        .expect("theme redirect response")
+}
+
+// Embed CSS and shared UI script at compile time
+const THEME_DEFAULT_CSS: &str = include_str!("../../static/theme_default.css");
+const THEME_RETRO_CSS: &str = include_str!("../../static/theme_retro.css");
+const THEME_RETRO_CRAFT_CSS: &str = include_str!("../../static/theme_retro_craft.css");
+const SLUG_UI_JS: &str = include_str!("../../static/slug_ui.js");
+
+pub async fn serve_static(Path(filename): Path<String>) -> impl IntoResponse {
+    if filename == "slug_ui.js" {
+        return Response::builder()
+            .status(StatusCode::OK)
+            .header(header::CONTENT_TYPE, "text/javascript; charset=utf-8")
+            .header(header::CACHE_CONTROL, "public, max-age=3600")
+            .body(SLUG_UI_JS.to_string())
+            .unwrap()
+            .into_response();
+    }
+
+    let theme = filename
+        .strip_prefix("theme_")
+        .and_then(|s| s.strip_suffix(".css"));
+
+    let css = match theme {
+        Some("default") => THEME_DEFAULT_CSS,
+        Some("retro") => THEME_RETRO_CSS,
+        Some("retro_craft") => THEME_RETRO_CRAFT_CSS,
+        _ => return (StatusCode::NOT_FOUND, "static file not found").into_response(),
+    };
+
+    Response::builder()
+        .status(StatusCode::OK)
+        .header(header::CONTENT_TYPE, "text/css; charset=utf-8")
+        .header(header::CACHE_CONTROL, "public, max-age=3600")
+        .body(css.to_string())
+        .unwrap()
+        .into_response()
+}
+
+pub(crate) fn js_string_literal(s: &str) -> String {
+    serde_json::to_string(s).expect("javascript string escaping")
+}
+
+/// `console.warn` as `text/javascript` — for `POST /ui` parse errors and inline morph failures.
+pub(crate) fn ui_js_warn(msg: &str) -> Response {
+    let js = format!("console.warn({});", js_string_literal(msg));
+    Response::builder()
+        .status(StatusCode::OK)
+        .header(header::CONTENT_TYPE, "text/javascript; charset=utf-8")
+        .body(Body::from(js))
+        .unwrap()
+}
+
+pub(crate) struct JsBuilder {
+    snippets: Vec<String>,
+}
+
+pub(crate) struct JsQueryBuilder {
+    builder: JsBuilder,
+    expr: String,
+}
+
+impl JsBuilder {
+    pub(crate) fn new() -> Self {
+        Self {
+            snippets: Vec::new(),
+        }
+    }
+
+    pub(crate) fn morph_selector(self, selector: &str, markup: Markup) -> Self {
+        self.morph_expr(
+            &format!("document.querySelector({})", js_string_literal(selector)),
+            markup,
+            None,
+        )
+    }
+
+    /// Morph **children** of `selector` so the outer element (e.g. `#new-thread-ui-slot`) keeps its `id`.
+    pub(crate) fn morph_inner_selector(self, selector: &str, markup: Markup) -> Self {
+        self.qs(selector).morph_inner(markup)
+    }
+
+    pub(crate) fn morph_expr(
+        mut self,
+        expr: &str,
+        markup: Markup,
+        morph_style: Option<&str>,
+    ) -> Self {
+        let html = js_string_literal(&markup.into_string());
+        let opts = morph_style
+            .map(|style| format!(", {{morphStyle: {}}}", js_string_literal(style)))
+            .unwrap_or_default();
+        self.snippets.push(format!(
+            "var __slugEl = {expr}; if (__slugEl) {{ Idiomorph.morph(__slugEl, {html}{opts}); }}",
+        ));
+        self
+    }
+
+    pub(crate) fn qs(self, selector: &str) -> JsQueryBuilder {
+        JsQueryBuilder {
+            builder: self,
+            expr: format!("document.querySelector({})", js_string_literal(selector)),
+        }
+    }
+
+    pub(crate) fn id(self, id: &str) -> JsQueryBuilder {
+        self.qs(&format!("#{id}"))
+    }
+
+    pub(crate) fn if_current_path_matches(
+        mut self,
+        path: &str,
+        f: impl FnOnce(JsBuilder) -> JsBuilder,
+    ) -> Self {
+        let inner = f(JsBuilder::new()).build();
+        self.snippets.push(format!(
+            "var __slugHere = window.location.pathname + window.location.search; var __slugPath = {path}; if (__slugHere === __slugPath || __slugHere.indexOf(__slugPath + '?') === 0) {{ {inner} }}",
+            path = js_string_literal(path),
+        ));
+        self
+    }
+
+    pub(crate) fn if_current_path_not_matches(
+        mut self,
+        path: &str,
+        f: impl FnOnce(JsBuilder) -> JsBuilder,
+    ) -> Self {
+        let inner = f(JsBuilder::new()).build();
+        self.snippets.push(format!(
+            "var __slugHere = window.location.pathname + window.location.search; var __slugPath = {path}; if (!(__slugHere === __slugPath || __slugHere.indexOf(__slugPath + '?') === 0)) {{ {inner} }}",
+            path = js_string_literal(path),
+        ));
+        self
+    }
+
+    pub(crate) fn redirect(mut self, to: &str) -> Self {
+        self.snippets
+            .push(format!("window.location = {};", js_string_literal(to)));
+        self
+    }
+
+    pub(crate) fn clipboard_write_text_and_label_btn(
+        mut self,
+        text: &str,
+        btn_id: &str,
+        copied_label: &str,
+    ) -> Self {
+        self.snippets.push(format!(
+            "navigator.clipboard.writeText({text}).then(function(){{ var __slugCopyBtn = document.getElementById({btn_id}); if (__slugCopyBtn) {{ __slugCopyBtn.textContent = {label}; }} }}).catch(function(__slugErr){{ console.warn(__slugErr); }});",
+            text = js_string_literal(text),
+            btn_id = js_string_literal(btn_id),
+            label = js_string_literal(copied_label),
+        ));
+        self
+    }
+
+    /// Focus first matching element (e.g. after morphing open a compose form).
+    pub(crate) fn focus_selector(mut self, selector: &str) -> Self {
+        self.snippets.push(format!(
+            "var __slugF = document.querySelector({}); if (__slugF && __slugF.focus) {{ __slugF.focus(); }}",
+            js_string_literal(selector),
+        ));
+        self
+    }
+
+    pub(crate) fn build(self) -> String {
+        self.snippets.join(" ")
+    }
+
+    pub(crate) fn into_response(self) -> Response {
+        Response::builder()
+            .status(StatusCode::OK)
+            .header(header::CONTENT_TYPE, "text/javascript; charset=utf-8")
+            .body(Body::from(self.build()))
+            .unwrap()
+    }
+}
+
+impl JsQueryBuilder {
+    pub(crate) fn morph(mut self, markup: Markup) -> JsBuilder {
+        let html = js_string_literal(&markup.into_string());
+        self.builder.snippets.push(format!(
+            "var __slugTarget = {expr}; if (__slugTarget) {{ Idiomorph.morph(__slugTarget, {html}); }}",
+            expr = self.expr,
+        ));
+        self.builder
+    }
+
+    pub(crate) fn morph_inner(mut self, markup: Markup) -> JsBuilder {
+        let html = js_string_literal(&markup.into_string());
+        self.builder.snippets.push(format!(
+            "var __slugTarget = {expr}; if (__slugTarget) {{ Idiomorph.morph(__slugTarget, {html}, {{morphStyle: 'innerHTML'}}); }}",
+            expr = self.expr,
+        ));
+        self.builder
+    }
+
+    pub(crate) fn reset(mut self) -> JsBuilder {
+        self.builder.snippets.push(format!(
+            "var __slugTarget = {expr}; if (__slugTarget) {{ __slugTarget.reset(); }}",
+            expr = self.expr,
+        ));
+        self.builder
+    }
+}
+
+#[allow(clippy::too_many_arguments)]
+pub(super) fn layout(
+    title: &str,
+    view: &str,
+    body: Markup,
+    views: Option<u64>,
+    theme: &str,
+    theme_nex

… preview truncated; 20,888 characters omitted

download full diff B

Hardlinks — judgments / attempts / prompt

prompt download

judgments

attempts

Prompt text is loaded only by the download route.