You are a constitutional council ranking individual git commits for ownership allocation. Compare these two commits. Decide which contributed more lasting value to the project. Judge substance, not spectacle: - Prefer correct, lasting design and real bugfixes over churn, formatting, renames, or generated noise. - Prefer clarity and necessity over sheer line count. A small precise change can beat a large diffuse one. - Do not favor a side merely because its patch is longer or noisier. - Weight what the change does for the project, not the contributor's name. Return ONLY a JSON object: {"winner": "A" or "B", "ratio": "N:M", "explanation": "..."} The explanation must cite concrete differences in the patches (1-3 sentences). Side A — contributor: tommy-mor Side A — commit message: [aa64d2f6] nice Side A — unified diff (full patch): diff --git a/server/src/api/helpers.rs b/server/src/api/helpers.rs index 8cd23a02fa5219d6aa375e766e6b2bd2c7bb7dfb..6a78c7346e3505b54f2587bbca402379bb929f81 100644 --- a/server/src/api/helpers.rs +++ b/server/src/api/helpers.rs @@ -31,13 +31,13 @@ pub fn now_ms() -> i64 { t.as_millis() as i64 } -/// Resolve DSL/user input to a stored canonical item id. +/// Resolve DSL/user input to a stored [`ItemId`]. pub fn resolve_item(item: &str) -> Result { - let canonical = canonicalize_item(item); - if canonical.is_empty() { + let wire = canonicalize_item(item); + if wire.is_empty() { return Err(format!("empty item path: `{}`", item)); } - ItemId::parse(&canonical).ok_or_else(|| format!("invalid item path: `{}`", item)) + ItemId::parse(&wire).ok_or_else(|| format!("invalid item path: `{}`", item)) } pub fn parse_parent_specs(parent: Option<&String>) -> Vec { @@ -95,7 +95,7 @@ pub fn paginate_rankings( (out_components, out_unranked) } -pub fn pick_random_distinct_canonical(items: &[ItemId]) -> Option<(ItemId, ItemId)> { +pub fn pick_random_distinct_item_pair(items: &[ItemId]) -> Option<(ItemId, ItemId)> { use rand::seq::SliceRandom; if items.len() < 2 { return None; diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs index 1528b3ff69e8d634158ee56f6b658752a3b90e94..9f3c1cc21c2ae157c024a447980a97e190c8f066 100644 --- a/server/src/api/mod.rs +++ b/server/src/api/mod.rs @@ -27,7 +27,7 @@ pub use auth::{ pub use helpers::{ api_error, compute_connectivity_stats, is_pair_voted, now_ms, paginate_rankings, - parse_parent_specs, pick_random_distinct_canonical, resolve_item, sha256_hex, vote_touches_path, + parse_parent_specs, pick_random_distinct_item_pair, resolve_item, sha256_hex, vote_touches_path, }; pub use rpc::handle_rpc_batch; diff --git a/server/src/api/rpc.rs b/server/src/api/rpc.rs index 079d96eb6f1d717c203b1d4aec09f3916384b919..470e54413b9e98825a084a5095c576784c19e169 100644 --- a/server/src/api/rpc.rs +++ b/server/src/api/rpc.rs @@ -27,7 +27,7 @@ use crate::{ use super::auth::{parse_bearer, verify_bearer_principal}; use super::helpers::{ compute_connectivity_stats, is_pair_voted, now_ms, paginate_rankings, parse_parent_specs, - pick_random_distinct_canonical, resolve_item, vote_touches_path, + pick_random_distinct_item_pair, resolve_item, vote_touches_path, }; use super::validate::validate_ingest_document; @@ -721,7 +721,7 @@ async fn rpc_get_pair(state: &AppState, room: String, parent_path: String) -> Re let content = reduced.content.entry(scope.clone()).or_default(); let group = &mut content.ranking_group; if group.idx_to_item.is_empty() { - pick_random_distinct_canonical(&pool) + pick_random_distinct_item_pair(&pool) } else { let mut rng = rand::thread_rng(); let idxs: Vec = pool @@ -767,7 +767,7 @@ async fn rpc_get_pair(state: &AppState, room: String, parent_path: String) -> Re } } } - pick.or_else(|| pick_random_distinct_canonical(&pool)) + pick.or_else(|| pick_random_distinct_item_pair(&pool)) } }; let Some((left, right)) = selected else { diff --git a/server/src/external_resolver.rs b/server/src/external_resolver.rs index 6f5c982a627f9f620f0e3be0ba0cb92a3d7d4bb7..13f06b922a685f110d269f7397da807cf8600fe1 100644 --- a/server/src/external_resolver.rs +++ b/server/src/external_resolver.rs @@ -11,7 +11,7 @@ pub trait ExternalResolver: Send + Sync { fn normalize(&self, path: &str) -> String; /// Fetches body when missing; GitHub hook lands here in a follow-up. - async fn fetch_body(&self, canonical_url: &ItemId) -> Result; + async fn fetch_body(&self, item: &ItemId) -> Result; } /// Placeholder until domain-specific resolvers exist. @@ -27,7 +27,7 @@ impl ExternalResolver for DefaultExternalResolver { path.to_string() } - async fn fetch_body(&self, _canonical_url: &ItemId) -> Result { + async fn fetch_body(&self, _item: &ItemId) -> Result { Err("external fetch not implemented".to_string()) } } diff --git a/server/src/html/breadcrumb_path.rs b/server/src/html/breadcrumb_path.rs index 5743a98753f579c70e10961278469afd7cb9ddcf..3e949ce2cb70de3f024f46af773a93f3f8852260 100644 --- a/server/src/html/breadcrumb_path.rs +++ b/server/src/html/breadcrumb_path.rs @@ -2,33 +2,33 @@ use crate::path_types::{tilde_http_path_to_item_id, ItemId}; /// Semantic view of an ontology path for rendering and routing decisions. pub(super) struct OntologyPath { - canonical: ItemId, + item: ItemId, /// Breadcrumb segments: for `~/a/b` this is `["a", "b"]` (leading `~` rendered separately). segments: Vec, } impl OntologyPath { /// Path is the `*path` segment from `/~/*path` (e.g. `topic/a`). Always treat it as under `~/` - /// so it canonicalizes to `https://slug.social/~/…`, not the non-tilde site path. + /// so it resolves to `https://slug.social/~/…`, not the non-tilde site path. pub(super) fn from_input(path: &str) -> Self { - let canonical = tilde_http_path_to_item_id(path); - Self::from_canonical(canonical) + let item = tilde_http_path_to_item_id(path); + Self::from_item(item) } - pub(super) fn from_canonical(canonical: ItemId) -> Self { + pub(super) fn from_item(item: ItemId) -> Self { // tilde_segments() returns ["~", "a", "b"] but bc_path() renders "~" itself, // so we skip the leading "~" segment here. - let segments = canonical + let segments = item .tilde_segments() .into_iter() .skip(1) // drop the leading "~" .map(|s| s.to_string()) .collect(); - Self { canonical, segments } + Self { item, segments } } pub(super) fn root() -> Self { - Self::from_canonical(ItemId::ontology_root()) + Self::from_item(ItemId::ontology_root()) } pub(super) fn is_root(&self) -> bool { @@ -50,13 +50,13 @@ impl OntologyPath { } pub(super) fn as_str(&self) -> &str { - self.canonical.as_str() + self.item.as_str() } } /// External `https://host/…` items addressed as `/-/host/…` in the URL bar. pub(super) struct ExternalOntologyPath { - canonical: ItemId, + item: ItemId, /// e.g. `["github.com", "org", "repo", "issues"]` segments: Vec, } @@ -71,14 +71,14 @@ impl ExternalOntologyPath { } else { format!("-/{}", p.trim_start_matches('/')) }; - let Some(canonical) = ItemId::parse(&raw) else { - return Self::from_canonical(ItemId::opaque("https://.".to_string())); + let Some(parsed) = ItemId::parse(&raw) else { + return Self::from_item(ItemId::opaque("https://.".to_string())); }; - Self::from_canonical(canonical) + Self::from_item(parsed) } - pub(super) fn from_canonical(canonical: ItemId) -> Self { - let s = canonical.as_str(); + pub(super) fn from_item(item: ItemId) -> Self { + let s = item.as_str(); let rest = s .strip_prefix("https://") .or_else(|| s.strip_prefix("http://")) @@ -93,7 +93,7 @@ impl ExternalOntologyPath { } else { segments }; - Self { canonical, segments } + Self { item, segments } } pub(super) fn is_root(&self) -> bool { @@ -105,6 +105,6 @@ impl ExternalOntologyPath { } pub(super) fn as_str(&self) -> &str { - self.canonical.as_str() + self.item.as_str() } } diff --git a/server/src/html/garden.rs b/server/src/html/garden.rs index 2af9ac5bfdee1630883e6f8257883baeafcb5a44..01b665b207f7ad4a2dac60153b9165679aaa9ab7 100644 --- a/server/src/html/garden.rs +++ b/server/src/html/garden.rs @@ -32,7 +32,7 @@ fn item_display_path(item: &str) -> String { .unwrap_or_else(|| canonicalize_item(item)) } -/// Canonical ontology URL for an item path. +/// Garden href for an item path string in this nav scope. fn item_href(item: &str, nav: &ThreadNav) -> String { nav.garden_item_url(item) } @@ -67,7 +67,7 @@ enum GardenBrowsePath { } impl GardenBrowsePath { - fn canonical_url(&self) -> &str { + fn item(&self) -> &str { match self { GardenBrowsePath::Tilde(p) => p.as_str(), GardenBrowsePath::External(p) => p.as_str(), @@ -669,7 +669,7 @@ async fn render_scope_view( let scope = nav.scope(); let model = { let reduced = state.reduced.read().await; - build_item_page_view_model(&reduced, &scope, browse.canonical_url()) + build_item_page_view_model(&reduced, &scope, browse.item()) }; let thread_href = |tag: &str| nav.thread_url(tag); let external_empty_body = browse.is_external() && model.body.is_none(); diff --git a/server/src/html/mod.rs b/server/src/html/mod.rs index 668403a35c415e6f091362b28c63ac294057fb6d..d6571df9b98726c0d3a82516e13404b74966281e 100644 --- a/server/src/html/mod.rs +++ b/server/src/html/mod.rs @@ -376,7 +376,7 @@ fn bc_path_external(path: &ExternalOntologyPath) -> Markup { } } -/// Breadcrumb for any canonical path, e.g. "parables" or "parables/counting-the-cost". +/// Breadcrumb for any ontology path segment, e.g. "parables" or "parables/counting-the-cost". fn bc_path(path: &OntologyPath) -> Markup { html! { a href=(path.slug_root_href()) { "slug.social" } diff --git a/server/src/reducer.rs b/server/src/reducer.rs index cd11a5391de8e36b1c3bb6c9b8067ceba0878e31..8751595b1dbe472079021994bd452a0234d59af5 100644 --- a/server/src/reducer.rs +++ b/server/src/reducer.rs @@ -88,7 +88,7 @@ impl GroupState { if let Some(canon) = ItemId::parse(item) { self.ensure_item(&canon) } else { - // Fallback: treat as raw canonical string + // Fallback: treat as raw storage string let canon = ItemId::opaque(item.to_string()); self.ensure_item(&canon) } @@ -212,7 +212,7 @@ pub struct ContentState { pub ranking_group: GroupState, pub items: HashSet, pub item_bodies: HashMap, - /// Parent canonical URL -> direct children. + /// Parent [`ItemId`] -> direct children. pub item_children: HashMap>, /// Per-item vote history (most recent first). pub item_votes: HashMap>, @@ -365,7 +365,7 @@ impl ReducerState { } } - /// Resolve an item path as a first-class canonical path. + /// Resolve an item path as a first-class [`ItemId`]. fn normalize_item(item: &str) -> Option { ItemId::parse(item) } diff --git a/types/src/item_id.rs b/types/src/item_id.rs index 5fe9fad39c0d58e546c0a0162eba4348d64e0865..3411c35cd642d4bc4fc27fcb96a238d32dbd07a5 100644 --- a/types/src/item_id.rs +++ b/types/src/item_id.rs @@ -12,7 +12,7 @@ use crate::item_wire::{ /// Structural key for items in [`slug_types`] and the server reducer. /// -/// Wire / JSON uses the same single string as the former canonical item URL (via serde). +/// Wire / JSON uses the same single normalized storage string (via serde). #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum ItemId { /// Tilde ontology root (`~/`); storage [`SLUG_TILDE_ONTOLOGY_ROOT`]. @@ -65,6 +65,8 @@ impl ItemId { self.as_str().to_string() } + /// Tail after `https://slug.social/~/` for ontology items (empty string at root), or `None` + /// when this id is not under the tilde ontology. pub fn tilde_tail(&self) -> Option<&str> { match self { ItemId::Root => Some(""), @@ -81,11 +83,6 @@ impl ItemId { } } - /// HTTP garden tail after `~/` (empty at ontology root), or `None` if not under tilde ontology. - pub fn tilde_http_tail(&self) -> Option { - self.tilde_tail().map(str::to_owned) - } - pub fn last_segment(&self) -> &str { let s = self.as_str(); s.rsplit('/').find(|x| !x.is_empty()).unwrap_or(s) diff --git a/types/src/paths.rs b/types/src/paths.rs index a1d066d83e7fbcefc27707dd682e54dbcec1cc56..8669f6872b9fa71d72a3f5c346945427bc00ac5a 100644 --- a/types/src/paths.rs +++ b/types/src/paths.rs @@ -334,19 +334,19 @@ mod tests { use super::*; #[test] - fn canonical_parent_deep() { + fn item_parent_deep() { let c = ItemId::parse("~/a/b/c").unwrap(); assert_eq!(c.parent().unwrap().as_str(), "https://slug.social/~/a/b"); } #[test] - fn canonical_parent_one_level() { + fn item_parent_one_level() { let c = ItemId::parse("~/a").unwrap(); assert_eq!(c.parent().unwrap().as_str(), "https://slug.social/~"); } #[test] - fn canonical_parent_root_is_none() { + fn item_parent_root_is_none() { let root = ItemId::parse("~/").unwrap(); assert!(root.parent().is_none()); assert_eq!(root.as_str(), SLUG_TILDE_ONTOLOGY_ROOT); Side B — contributor: tommy-mor Side B — commit message: [af27c3ab] oicd first pass for npm Side B — unified diff (full patch): diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index faed75894dace9edcde19eb10753234a1c1261e9..6f3e27e491b56aaaa1ef64c547cc68921e010b3f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,9 +88,12 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 registry-url: "https://registry.npmjs.org" + - name: Upgrade npm for OIDC trusted publishing support + run: npm install -g npm@latest + - name: Copy binaries into npm platform packages shell: bash run: | @@ -125,8 +128,6 @@ jobs: - name: Publish npm platform packages shell: bash - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | set -euxo pipefail for pkg in packages/npm/platforms/*; do @@ -135,15 +136,10 @@ jobs: - name: Publish npm root package shell: bash - env: - # Unscoped packages need a token that is allowed to create/publish unscoped names. - # GitHub's setup-node expects NODE_AUTH_TOKEN. See: - # https://docs.github.com/en/actions/tutorials/publish-packages/publish-nodejs-packages - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_UNSCOPED }} run: | set -euxo pipefail cd packages/npm - npm publish --access public --provenance + npm publish --access public publish-pypi: name: publish PyPI