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
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>