Show jj change ID alongside git commit hash on commit detail page
jj stores its change ID as a `change-id` header in the git commit
object. Parse it via `git cat-file -p` and display it as the primary
identity in the metadata block, with the git SHA shown beneath it in
a muted style. The change ID is split head/tail like the existing SHA
styling (first 12 chars accented, rest muted).

https://claude.ai/code/session_01QpsCsbWqQx8L4Tzrg9ukBP
change
commit 259e133a427168786d5a20818366da1f835a4812
author Claude <noreply@anthropic.com>
date
parent tzoqwqvs
diff --git a/quire-server/src/quire/web/handlers/commit.rs b/quire-server/src/quire/web/handlers/commit.rs
index 9b10009..3193af6 100644
--- a/quire-server/src/quire/web/handlers/commit.rs
+++ b/quire-server/src/quire/web/handlers/commit.rs
@@ -68,6 +68,8 @@ pub async fn commit_view(
             .run(&["log", "-1", "--patch", "--format=", &full_sha])
             .unwrap_or_default();
 
+        let change_id = reader.change_id(&full_sha).unwrap_or_default();
+
         Some((
             sha,
             author,
@@ -77,11 +79,14 @@ pub async fn commit_view(
             body,
             parent_shars,
             diff,
+            change_id,
         ))
     })
     .await?;
 
-    let Some((sha, author, email, timestamp_ms, subject, body, parent_shas, diff)) = result else {
+    let Some((sha, author, email, timestamp_ms, subject, body, parent_shas, diff, change_id)) =
+        result
+    else {
         return Ok(StatusCode::NOT_FOUND.into_response());
     };
 
@@ -119,6 +124,7 @@ pub async fn commit_view(
         body,
         parents,
         diff,
+        change_id,
     };
     Ok(render(&tmpl))
 }
diff --git a/quire-server/src/quire/web/handlers/git.rs b/quire-server/src/quire/web/handlers/git.rs
index ac298d2..6f00382 100644
--- a/quire-server/src/quire/web/handlers/git.rs
+++ b/quire-server/src/quire/web/handlers/git.rs
@@ -87,6 +87,20 @@ impl<'a> RepoView<'a> {
             .collect()
     }
 
+    /// Read the jj change-id header from a commit object, if present.
+    pub(super) fn change_id(&self, sha: &str) -> Option<String> {
+        let raw = self.run(&["cat-file", "-p", sha])?;
+        for line in raw.lines() {
+            if line.is_empty() {
+                break; // end of headers
+            }
+            if let Some(id) = line.strip_prefix("change-id ") {
+                return Some(id.trim().to_string());
+            }
+        }
+        None
+    }
+
     fn render_markdown(markdown: &str) -> String {
         use pulldown_cmark::{Options, Parser, html};
         let opts = Options::ENABLE_TABLES | Options::ENABLE_STRIKETHROUGH;
diff --git a/quire-server/src/quire/web/templates.rs b/quire-server/src/quire/web/templates.rs
index d4e634e..5ca2906 100644
--- a/quire-server/src/quire/web/templates.rs
+++ b/quire-server/src/quire/web/templates.rs
@@ -509,12 +509,21 @@ pub struct CommitTemplate {
     pub body: String,
     pub parents: Vec<CommitParent>,
     pub diff: String,
+    pub change_id: String,
 }
 
 impl CommitTemplate {
     pub fn version(&self) -> &'static str {
         pkg_version()
     }
+
+    pub fn change_id_head(&self) -> &str {
+        &self.change_id[..self.change_id.len().min(12)]
+    }
+
+    pub fn change_id_tail(&self) -> &str {
+        &self.change_id[self.change_id.len().min(12)..]
+    }
 }
 
 pub struct CommitParent {
diff --git a/quire-server/static/style.css b/quire-server/static/style.css
index 7660119..212408f 100644
--- a/quire-server/static/style.css
+++ b/quire-server/static/style.css
@@ -1250,6 +1250,23 @@ pre code[data-lang] {
   color: var(--ink);
 }
 
+.commit-meta-val--secondary {
+  color: var(--muted);
+}
+
+.change-id-full {
+  font-family: var(--font-mono);
+}
+
+.change-id-head {
+  color: var(--accent);
+  font-weight: 500;
+}
+
+.change-id-tail {
+  color: var(--muted);
+}
+
 .commit-meta-val .change-id {
   margin-right: var(--space-2xs);
 }
diff --git a/quire-server/templates/commit.html b/quire-server/templates/commit.html
index b0ff93d..ba03c68 100644
--- a/quire-server/templates/commit.html
+++ b/quire-server/templates/commit.html
@@ -32,9 +32,15 @@
     </div>
 
     <div class="commit-meta-list">
+      <div class="commit-meta-row">
+        <span class="commit-meta-key">change</span>
+        <span class="commit-meta-val">
+          <span class="change-id-full"><span class="change-id-head">{{ self.change_id_head() }}</span><span class="change-id-tail">{{ self.change_id_tail() }}</span></span>
+        </span>
+      </div>
       <div class="commit-meta-row">
         <span class="commit-meta-key">commit</span>
-        <span class="commit-meta-val">{{ sha }}</span>
+        <span class="commit-meta-val commit-meta-val--secondary">{{ sha }}</span>
       </div>
       <div class="commit-meta-row">
         <span class="commit-meta-key">author</span>