tree: redesign to match Quire.html design spec
- Remove table header row; entries use rhythm alone, no column labels
- Remove per-row border-bottom; rows breathe without rules
- Update grid columns to 18px/240px/1fr/96px (was 22px/1.1fr/2fr/90px)
- Widen body split to 2.7fr/1fr (was 2.4fr)
- Add 16px top padding to left column
- Replace "This directory" stats sidebar with recent commit log
- Fetch recent_changes in tree handler; add field to TreeTemplate
- Add .tree-log-item/.tree-log-subject/.tree-log-meta CSS for sidebar

https://claude.ai/code/session_01VpGAN6wqjUnjCNL86fW8wv
change
commit d6b0bd27ca95a6c3572599b2b2e82cc9c4b9594f
author Claude <noreply@anthropic.com>
date
parent oxklkwsq
diff --git a/quire-server/src/quire/web/handlers/tree.rs b/quire-server/src/quire/web/handlers/tree.rs
index 1b9d875..7676d6e 100644
--- a/quire-server/src/quire/web/handlers/tree.rs
+++ b/quire-server/src/quire/web/handlers/tree.rs
@@ -35,12 +35,13 @@ async fn tree_at_path(quire: Quire, repo: String, path: String) -> Response {
         let tree_data = read_tree_data(&reader, &path_clone)?;
         let bookmarks = reader.bookmarks();
         let tags = reader.tags();
-        Some((tree_data, bookmarks, tags))
+        let recent_changes = reader.recent_changes();
+        Some((tree_data, bookmarks, tags, recent_changes))
     })
     .await
     .unwrap_or(None);
 
-    let (tree_data, bookmarks, tags) = match result {
+    let (tree_data, bookmarks, tags, recent_changes) = match result {
         Some(v) => v,
         None => return StatusCode::NOT_FOUND.into_response(),
     };
@@ -65,6 +66,7 @@ async fn tree_at_path(quire: Quire, repo: String, path: String) -> Response {
         bookmark: tree_data.bookmark,
         sha_short: tree_data.sha_short,
         entries: tree_data.entries,
+        recent_changes,
     };
     render(&tmpl)
 }
diff --git a/quire-server/src/quire/web/templates.rs b/quire-server/src/quire/web/templates.rs
index 5e7b087..20b9c8d 100644
--- a/quire-server/src/quire/web/templates.rs
+++ b/quire-server/src/quire/web/templates.rs
@@ -381,6 +381,7 @@ pub struct TreeTemplate {
     /// Short commit hash for HEAD.
     pub sha_short: String,
     pub entries: Vec<TreeEntry>,
+    pub recent_changes: Vec<ChangeRow>,
 }
 
 impl TreeTemplate {
diff --git a/quire-server/static/style.css b/quire-server/static/style.css
index 8b8d625..97d7a70 100644
--- a/quire-server/static/style.css
+++ b/quire-server/static/style.css
@@ -792,7 +792,7 @@ pre code[data-lang] {
 
 .tree-body {
   display: grid;
-  grid-template-columns: minmax(0, 2.4fr) minmax(0, 1fr);
+  grid-template-columns: minmax(0, 2.7fr) minmax(0, 1fr);
 }
 
 /* Tree table — left column */
@@ -800,36 +800,19 @@ pre code[data-lang] {
 .tree-table-col {
   border-right: 1px solid var(--rule);
   min-width: 0;
+  padding-top: 16px;
 }
 
-.tree-table-header {
-  display: grid;
-  grid-template-columns: 22px 1.1fr 2fr 90px;
-  gap: 14px;
-  padding: 8px var(--space-xl);
-  font-family: var(--font-mono);
-  font-size: 10.5px;
-  letter-spacing: 1.2px;
-  text-transform: uppercase;
-  color: var(--mutedFaint);
-  border-bottom: 1px solid var(--rule2);
-}
-
-.tree-th-age { text-align: right; }
-
 .tree-row {
   display: grid;
-  grid-template-columns: 22px 1.1fr 2fr 90px;
-  gap: 14px;
-  padding: 5px var(--space-xl);
+  grid-template-columns: 18px 240px minmax(0, 1fr) 96px;
+  gap: 16px;
+  padding: 6px var(--space-xl);
   align-items: baseline;
-  border-bottom: 1px dotted var(--rule2);
   font-family: var(--font-mono);
   font-size: 13px;
 }
 
-.tree-row:last-child { border-bottom: none; }
-
 .tree-icon {
   display: flex;
   align-items: center;
@@ -879,26 +862,50 @@ pre code[data-lang] {
   text-align: right;
 }
 
-/* Tree sidebar — right column */
+/* Tree sidebar — right column, recent commit log */
 
 .tree-sidebar {
-  padding: 18px var(--space-xl) 56px 36px;
+  padding: 12px var(--space-xl) 0 40px;
   font-family: var(--font-mono);
   font-size: 12.5px;
   min-width: 0;
 }
 
-/* Dir stats */
+.tree-log-item {
+  display: block;
+  padding: 10px 0;
+  text-decoration: none;
+}
 
-.tree-stats { line-height: 1.8; }
+.tree-log-subject {
+  color: var(--ink);
+  font-size: 12.5px;
+  line-height: 1.5;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
 
-.tree-stat-row {
+.tree-log-meta {
+  margin-top: 4px;
+  font-size: 11px;
   display: flex;
-  justify-content: space-between;
-  color: var(--ink);
+  gap: 10px;
+  align-items: baseline;
 }
 
-.tree-stat-label { color: var(--muted); }
+.tree-log-sha { color: var(--muted); }
+
+.tree-log-age { color: var(--mutedFaint); }
+
+.tree-log-more {
+  display: block;
+  margin-top: 12px;
+  color: var(--mutedFaint);
+  text-decoration: none;
+  font-size: 12px;
+}
 
 /* README preview */
 
@@ -958,6 +965,6 @@ pre code[data-lang] {
 
   .tree-sidebar {
     border-top: 1px solid var(--rule);
-    padding: 18px var(--space-xl);
+    padding: 18px var(--space-xl) 0;
   }
 }
diff --git a/quire-server/templates/tree.html b/quire-server/templates/tree.html
index 2e80c54..3a4744f 100644
--- a/quire-server/templates/tree.html
+++ b/quire-server/templates/tree.html
@@ -28,20 +28,13 @@
 {# ── Two-column body ─────────────────────────────────────────────── #}
 <div class="tree-body">
 
-  {# LEFT — tree table #}
+  {# LEFT — tree table, no header, no per-row rules #}
   <div class="tree-table-col">
-    <div class="tree-table-header">
-      <span></span>
-      <span>name</span>
-      <span>last change</span>
-      <span class="tree-th-age">age</span>
-    </div>
-
     {% for entry in entries %}
     <div class="tree-row {% if entry.is_dir_like() %}tree-row--dir{% endif %}{% if entry.is_submodule() %} tree-row--sub{% endif %}{% if entry.is_up() %} tree-row--up{% endif %}">
       <span class="tree-icon" aria-hidden="true">
         {% if entry.is_dir() %}
-        <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.2">
+        <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round">
           <path d="M1 3.5h4l1 1.2h7v7.8h-12z"/>
         </svg>
         {% elif entry.is_submodule() %}
@@ -53,7 +46,7 @@
           <path d="M3 7l4-4 4 4M7 3v9"/>
         </svg>
         {% else %}
-        <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.2">
+        <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round">
           <path d="M3 1.5h5l3 3v8.5h-8z"/>
           <path d="M8 1.5v3h3"/>
         </svg>
@@ -76,22 +69,18 @@
     {% endfor %}
   </div>
 
-  {# RIGHT — sidebar #}
+  {# RIGHT — recent commit log for this ref #}
   <aside class="tree-sidebar">
-
-    <div class="side-block side-block--last">
-      <div class="side-block-title">This directory</div>
-      <div class="tree-stats">
-        {% if self.dir_count() > 0 %}
-        <div class="tree-stat-row"><span class="tree-stat-label">directories</span><span>{{ self.dir_count() }}</span></div>
-        {% endif %}
-        {% if self.submodule_count() > 0 %}
-        <div class="tree-stat-row"><span class="tree-stat-label">submodules</span><span>{{ self.submodule_count() }}</span></div>
-        {% endif %}
-        <div class="tree-stat-row"><span class="tree-stat-label">files</span><span>{{ self.file_count() }}</span></div>
+    {% for change in recent_changes %}
+    <a class="tree-log-item" href="/{{ repo }}/log">
+      <div class="tree-log-subject">{{ change.description }}</div>
+      <div class="tree-log-meta">
+        <span class="tree-log-sha">{{ change.change_head() }}{{ change.change_tail() }}</span>
+        <span class="tree-log-age">{{ change.age }}</span>
       </div>
-    </div>
-
+    </a>
+    {% endfor %}
+    <a class="tree-log-more" href="/{{ repo }}/log">log →</a>
   </aside>
 </div>