Redesign CI and config pages to match repo home layout
- Remove README.md eyebrow header from repo home
- Fix ci-inline alignment (baseline instead of center) in repo position token
- Redesign ci/run_list.html with two-column layout (run list + sidebar)
- Redesign ci/run_detail.html with two-column layout (detail + sidebar)
- Redesign config.html to use repo-style section nav instead of table layout
- Add corresponding CSS for new page layouts, remove orphaned ci-heading/ci-table styles
Assisted-by: Owl Alpha via pi
diff --git a/quire-server/src/quire/web/auth.rs b/quire-server/src/quire/web/auth.rs
index 82f51cf..a6c08ab 100644
--- a/quire-server/src/quire/web/auth.rs
+++ b/quire-server/src/quire/web/auth.rs
@@ -23,10 +23,9 @@ impl<S: Send + Sync> FromRequestParts<S> for Auth {
/// `Auth` extractor behaves as if a real user is present.
#[cfg(feature = "dev")]
pub async fn inject_dev_user(mut request: axum::extract::Request, next: Next) -> Response {
- request.headers_mut().insert(
- "Remote-User",
- axum::http::HeaderValue::from_static("dev"),
- );
+ request
+ .headers_mut()
+ .insert("Remote-User", axum::http::HeaderValue::from_static("dev"));
next.run(request).await
}
diff --git a/quire-server/src/quire/web/templates.rs b/quire-server/src/quire/web/templates.rs
index 2fa1db5..3229bbf 100644
--- a/quire-server/src/quire/web/templates.rs
+++ b/quire-server/src/quire/web/templates.rs
@@ -22,15 +22,31 @@ pub struct SectionLink {
/// template.
pub fn nav_sections(repo: &str, active: &str, authed: bool) -> Vec<SectionLink> {
let mut sections = vec![
- SectionLink { label: "overview", href: format!("/{repo}"), active: active == "overview" },
- SectionLink { label: "tree", href: format!("/{repo}/tree"), active: active == "tree" },
- SectionLink { label: "log", href: format!("/{repo}/log"), active: active == "log" },
+ SectionLink {
+ label: "overview",
+ href: format!("/{repo}"),
+ active: active == "overview",
+ },
+ SectionLink {
+ label: "tree",
+ href: format!("/{repo}/tree"),
+ active: active == "tree",
+ },
+ SectionLink {
+ label: "log",
+ href: format!("/{repo}/log"),
+ active: active == "log",
+ },
SectionLink {
label: "bookmarks",
href: format!("/{repo}/bookmarks"),
active: active == "bookmarks",
},
- SectionLink { label: "tags", href: format!("/{repo}/tags"), active: active == "tags" },
+ SectionLink {
+ label: "tags",
+ href: format!("/{repo}/tags"),
+ active: active == "tags",
+ },
];
if authed {
sections.push(SectionLink {
diff --git a/quire-server/static/style.css b/quire-server/static/style.css
index 0de745e..6adac15 100644
--- a/quire-server/static/style.css
+++ b/quire-server/static/style.css
@@ -99,33 +99,6 @@ pre { white-space: pre-wrap; word-break: break-word; }
/* CI run list */
-.ci-heading {
- font-family: var(--font-mono);
- font-size: var(--step-1);
- font-weight: 600;
- margin: 0 0 var(--space-s);
-}
-
-.ci-table {
- width: 100%;
- border-collapse: collapse;
- font-family: var(--font-mono);
- font-size: var(--step--1);
- line-height: 1.6;
-}
-
-.ci-table thead th {
- text-align: left;
- padding: var(--space-3xs) var(--space-2xs);
- font-weight: 400;
- color: var(--mutedFaint);
-}
-
-.ci-table thead tr { border-bottom: 1px solid var(--rule2); }
-.ci-table tbody { border-top: 1px solid var(--rule); }
-.ci-table td { padding: var(--space-3xs) var(--space-2xs); }
-.ci-table .empty { padding: var(--space-s); color: var(--muted); }
-
.ci-status-dot {
display: inline-block;
width: 6px;
@@ -146,26 +119,6 @@ pre { white-space: pre-wrap; word-break: break-word; }
border-bottom: 1px dotted var(--rule2);
}
-/* CI run detail */
-
-.ci-meta {
- padding: var(--space-s) 0;
- border-bottom: 1px solid var(--rule);
-}
-
-.ci-meta-primary {
- font-family: var(--font-mono);
- font-size: var(--step-0);
- line-height: 1.6;
-}
-
-.ci-meta-secondary {
- font-family: var(--font-mono);
- font-size: var(--step--1);
- color: var(--mutedFaint);
- margin-top: var(--space-3xs);
-}
-
/* CI jobs */
.ci-job {
@@ -306,7 +259,7 @@ html.dark {
.repo-meta-sep { color: var(--rule2); }
.repo-meta-dot { color: var(--rule2); }
-.ci-inline { display: inline-flex; align-items: center; gap: var(--space-3xs); }
+.ci-inline { display: inline-flex; align-items: baseline; gap: var(--space-3xs); }
.bookmark-glyph { color: var(--mutedFaint); }
.bookmark-name { color: var(--accent); }
@@ -661,6 +614,89 @@ html.dark {
color: var(--mutedFaint);
}
+/* ── CI run list (full page) ───────────────────────────────────── */
+
+.ci-run-list {
+ padding: var(--space-l) var(--space-xl);
+ min-width: 0;
+}
+
+.ci-run-row {
+ display: flex;
+ align-items: baseline;
+ gap: var(--space-2xs);
+ font-family: var(--font-mono);
+ font-size: 12.5px;
+ line-height: 2;
+}
+
+.ci-run-row .ci-status-dot {
+ flex-shrink: 0;
+}
+
+.ci-run-branch {
+ color: var(--accent);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.ci-run-age {
+ color: var(--mutedFaint);
+ white-space: nowrap;
+}
+
+.ci-run-dur {
+ color: var(--muted);
+ white-space: nowrap;
+ margin-left: auto;
+}
+
+/* ── CI run detail ─────────────────────────────────────────────── */
+
+.ci-detail {
+ padding: var(--space-l) var(--space-xl);
+ min-width: 0;
+}
+
+.ci-detail .ci-meta {
+ padding: 0 0 var(--space-s);
+ border-bottom: 1px solid var(--rule);
+ font-family: var(--font-mono);
+ font-size: 12.5px;
+ color: var(--muted);
+}
+
+/* ── Config ────────────────────────────────────────────────────── */
+
+.config-table {
+ padding: var(--space-l) var(--space-xl);
+ min-width: 0;
+}
+
+.config-row {
+ display: flex;
+ align-items: baseline;
+ gap: var(--space-s);
+ font-family: var(--font-mono);
+ font-size: 12.5px;
+ line-height: 2;
+}
+
+.config-key {
+ color: var(--muted);
+ min-width: 200px;
+}
+
+.config-val {
+ color: var(--ink);
+}
+
+.config-val--empty {
+ color: var(--mutedFaint);
+ font-style: italic;
+}
+
/* ── Arborium syntax highlighting integration ──────────────────── */
/* Keep quire's paper-toned code background; strip any theme-imposed radius. */
diff --git a/quire-server/templates/ci/run_detail.html b/quire-server/templates/ci/run_detail.html
index fdc7797..515dc11 100644
--- a/quire-server/templates/ci/run_detail.html
+++ b/quire-server/templates/ci/run_detail.html
@@ -7,54 +7,99 @@
{% endblock %}
{% block fullpage %}
+
<nav class="repo-section-nav">
{% include "_repo_section_nav.html" %}
+ <span class="repo-position">
+ <span class="ci-commit-link">{{ run.sha_short() }}</span>
+ <span class="repo-meta-dot">·</span>
+ <span>{{ run.branch_short() }}</span>
+ <span class="repo-meta-dot">·</span>
+ <span class="ci-state-label {{ run.state_class() }}"><span class="ci-status-dot {{ run.state_class() }}"></span> {{ run.state() }}</span>
+ </span>
</nav>
-<main class="page-main">
-<div class="ci-meta">
- <div class="ci-meta-primary">
- <span class="c-accent">{{ run.sha_short() }}</span>
- · {{ run.branch_short() }}
- · <span class="ci-state-label {{ run.state_class() }}"><span class="ci-status-dot {{ run.state_class() }}"></span> {{ run.state() }}</span>
- </div>
- <div class="ci-meta-secondary">
- {% if run.is_terminal() %}
- queued <time title="{{ run.queued_iso() }}">{{ run.queued_relative() }}</time>
- · ran <span title="started {{ run.started_iso() }}\nfinished {{ run.finished_iso() }}">{{ run.duration_display() }}</span>
- {% elif run.has_started() %}
- queued <time title="{{ run.queued_iso() }}">{{ run.queued_relative() }}</time>
- · started <time title="{{ run.started_iso() }}">{{ run.started_display() }}</time>
+
+<div class="repo-body">
+
+ <article class="ci-detail">
+ <div class="ci-meta">
+ {% if run.is_terminal() %}
+ queued <time title="{{ run.queued_iso() }}">{{ run.queued_relative() }}</time>
+ <span class="repo-meta-dot">·</span>
+ ran <span title="started {{ run.started_iso() }}\nfinished {{ run.finished_iso() }}">{{ run.duration_display() }}</span>
+ {% elif run.has_started() %}
+ queued <time title="{{ run.queued_iso() }}">{{ run.queued_relative() }}</time>
+ <span class="repo-meta-dot">·</span>
+ started <time title="{{ run.started_iso() }}">{{ run.started_display() }}</time>
+ {% else %}
+ queued <time title="{{ run.queued_iso() }}">{{ run.queued_relative() }}</time>
+ {% endif %}
+ </div>
+
+ {% for job in jobs %}
+ <div class="ci-job">
+ <div class="ci-job-header">
+ {{ job.job_id }}<span class="repo-meta-dot">·</span>{{ job.duration_display() }}{% if let Some(code) = job.exit_code_filter_nonzero() %}<span class="repo-meta-dot">·</span>exit {{ code }}{% endif %}
+ <span class="repo-meta-dot">·</span>
+ <span class="ci-state-label {{ job.state_class() }}"><span class="ci-status-dot {{ job.state_class() }}"></span> {{ job.state }}</span>
+ </div>
+ {% for sh in job.sh_events %}
+ <div class="ci-sh">
+ <div class="ci-sh-cmd">
+ {{ sh.cmd_display() }}
+ <span class="ci-sh-meta">{{ sh.duration_display() }}{% if sh.exit_code != 0 %} · exit {{ sh.exit_code }}{% endif %}</span>
+ </div>
+ {% if !sh.log_content.is_empty() %}
+ <pre class="ci-sh-log">{{ sh.log_content }}</pre>
+ {% endif %}
+ </div>
+ {% endfor %}
+ </div>
{% else %}
- queued <time title="{{ run.queued_iso() }}">{{ run.queued_relative() }}</time>
+ <div class="ci-empty">no jobs recorded</div>
+ {% endfor %}
+
+ {% if !quire_ci_log.is_empty() %}
+ <div class="ci-job">
+ <div class="ci-job-header">quire-ci</div>
+ <pre class="ci-sh-log">{{ quire_ci_log }}</pre>
+ </div>
{% endif %}
- </div>
-</div>
+ </article>
-{% for job in jobs %}
-<div class="ci-job">
- <div class="ci-job-header">
- {{ job.job_id }} · {{ job.duration_display() }}{% if let Some(code) = job.exit_code_filter_nonzero() %} · exit {{ code }}{% endif %}
- · <span class="ci-state-label {{ job.state_class() }}"><span class="ci-status-dot {{ job.state_class() }}"></span> {{ job.state }}</span>
- </div>
- {% for sh in job.sh_events %}
- <div class="ci-sh">
- <div class="ci-sh-cmd">
- {{ sh.cmd_display() }} <span class="ci-sh-meta">{{ sh.duration_display() }}{% if sh.exit_code != 0 %} · exit {{ sh.exit_code }}{% endif %}</span>
+ <aside class="repo-sidebar">
+ {% if !bookmarks.is_empty() %}
+ <div class="side-block">
+ <div class="side-block-title">Bookmarks</div>
+ <div class="bookmark-list">
+ {% for b in bookmarks %}
+ <div class="bookmark-row">
+ <span class="bookmark-kind {% if loop.first %}bookmark-kind--trunk{% endif %}">{% if loop.first %}trunk{% else %}local{% endif %}</span>
+ <a class="bookmark-link" href="/{{ repo }}/log">
+ <span class="bookmark-glyph-sm">※</span>{{ b.name }}
+ </a>
+ <span class="bookmark-age">{{ b.age }}</span>
+ </div>
+ {% endfor %}
+ </div>
</div>
- {% if !sh.log_content.is_empty() %}
- <pre class="ci-sh-log">{{ sh.log_content }}</pre>
{% endif %}
- </div>
- {% endfor %}
-</div>
-{% else %}
-<div class="ci-empty">no jobs recorded</div>
-{% endfor %}
-{% if !quire_ci_log.is_empty() %}
-<div class="ci-job">
- <div class="ci-job-header">quire-ci</div>
- <pre class="ci-sh-log">{{ quire_ci_log }}</pre>
+
+ {% if !tags.is_empty() %}
+ <div class="side-block">
+ <div class="side-block-title">Tags</div>
+ <div class="bookmark-list">
+ {% for t in tags %}
+ <div class="bookmark-row">
+ <a class="bookmark-link" href="/{{ repo }}/log">{{ t.name }}</a>
+ <span class="bookmark-age">{{ t.age }}</span>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+ {% endif %}
+ </aside>
+
</div>
-{% endif %}
-</main>
+
{% endblock %}
diff --git a/quire-server/templates/ci/run_list.html b/quire-server/templates/ci/run_list.html
index b3d6226..4189018 100644
--- a/quire-server/templates/ci/run_list.html
+++ b/quire-server/templates/ci/run_list.html
@@ -7,34 +7,60 @@
{% endblock %}
{% block fullpage %}
+
<nav class="repo-section-nav">
{% include "_repo_section_nav.html" %}
</nav>
-<main class="page-main">
-<h2 class="ci-heading">ci runs</h2>
-<table class="ci-table">
- <thead>
- <tr>
- <th></th>
- <th>commit</th>
- <th>ref</th>
- <th>queued</th>
- <th>duration</th>
- </tr>
- </thead>
- <tbody>
- {% for run in runs %}
- <tr>
- <td><span class="ci-status-dot {{ run.state_class() }}"></span></td>
- <td><a href="/{{ repo }}/ci/{{ run.id }}" class="ci-commit-link">{{ run.sha_short() }}</a></td>
- <td>{{ run.branch_short() }}</td>
- <td><time title="{{ run.queued_iso() }}">{{ run.queued_relative() }}</time></td>
- <td>{{ run.duration_display() }}</td>
- </tr>
- {% else %}
- <tr><td colspan="5" class="empty">no runs yet</td></tr>
- {% endfor %}
- </tbody>
-</table>
-</main>
+
+<div class="repo-body">
+
+ <article class="ci-run-list">
+ {% for run in runs %}
+ <div class="ci-run-row">
+ <span class="ci-status-dot {{ run.state_class() }}"></span>
+ <a class="ci-commit-link" href="/{{ repo }}/ci/{{ run.id }}">{{ run.sha_short() }}</a>
+ <span class="ci-run-branch">{{ run.branch_short() }}</span>
+ <span class="ci-run-age"><time title="{{ run.queued_iso() }}">{{ run.queued_relative() }}</time></span>
+ <span class="ci-run-dur">{{ run.duration_display() }}</span>
+ </div>
+ {% else %}
+ <p class="ci-empty">no runs yet</p>
+ {% endfor %}
+ </article>
+
+ <aside class="repo-sidebar">
+ {% if !bookmarks.is_empty() %}
+ <div class="side-block">
+ <div class="side-block-title">Bookmarks</div>
+ <div class="bookmark-list">
+ {% for b in bookmarks %}
+ <div class="bookmark-row">
+ <span class="bookmark-kind {% if loop.first %}bookmark-kind--trunk{% endif %}">{% if loop.first %}trunk{% else %}local{% endif %}</span>
+ <a class="bookmark-link" href="/{{ repo }}/log">
+ <span class="bookmark-glyph-sm">※</span>{{ b.name }}
+ </a>
+ <span class="bookmark-age">{{ b.age }}</span>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+ {% endif %}
+
+ {% if !tags.is_empty() %}
+ <div class="side-block">
+ <div class="side-block-title">Tags</div>
+ <div class="bookmark-list">
+ {% for t in tags %}
+ <div class="bookmark-row">
+ <a class="bookmark-link" href="/{{ repo }}/log">{{ t.name }}</a>
+ <span class="bookmark-age">{{ t.age }}</span>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+ {% endif %}
+ </aside>
+
+</div>
+
{% endblock %}
diff --git a/quire-server/templates/config.html b/quire-server/templates/config.html
index a64367d..ceddb6f 100644
--- a/quire-server/templates/config.html
+++ b/quire-server/templates/config.html
@@ -15,37 +15,62 @@
</svg>
<span class="nav-wordmark-text">quire</span>
</a>
- {% for crumb in crumbs %}
<span class="sep">/</span>
- {% if let Some(href) = crumb.href %}
- <a class="nav-crumb" href="{{ href }}">{{ crumb.label }}</a>
- {% else %}
- <span class="nav-crumb">{{ crumb.label }}</span>
- {% endif %}
- {% endfor %}
+ <span class="nav-crumb">config</span>
</div>
</nav>
{% endblock %}
-{% block content %}
-<h2 class="ci-heading">config</h2>
-<table class="ci-table">
- <tbody>
- <tr><th>port</th><td>{{ config.port }}</td></tr>
- <tr><th>ci.executor</th><td>{{ config.ci.executor }}</td></tr>
+{% block fullpage %}
+
+<nav class="repo-section-nav">
+ <span class="repo-position">config</span>
+</nav>
+
+<div class="repo-body">
+
+ <article class="config-table">
+ <div class="config-row">
+ <span class="config-key">port</span>
+ <span class="config-val">{{ config.port }}</span>
+ </div>
+ <div class="config-row">
+ <span class="config-key">ci.executor</span>
+ <span class="config-val">{{ config.ci.executor }}</span>
+ </div>
{% if let Some(sentry) = config.sentry %}
- <tr><th>sentry.dsn</th><td>{{ sentry.dsn }}</td></tr>
+ <div class="config-row">
+ <span class="config-key">sentry.dsn</span>
+ <span class="config-val">{{ sentry.dsn }}</span>
+ </div>
{% else %}
- <tr><th>sentry</th><td class="empty">disabled</td></tr>
+ <div class="config-row">
+ <span class="config-key">sentry</span>
+ <span class="config-val config-val--empty">disabled</span>
+ </div>
{% endif %}
{% if let Some(token) = config.github.mirror_token %}
- <tr><th>github.mirror-token</th><td>{{ token }}</td></tr>
+ <div class="config-row">
+ <span class="config-key">github.mirror-token</span>
+ <span class="config-val">{{ token }}</span>
+ </div>
{% else %}
- <tr><th>github.mirror-token</th><td class="empty">not set</td></tr>
+ <div class="config-row">
+ <span class="config-key">github.mirror-token</span>
+ <span class="config-val config-val--empty">not set</span>
+ </div>
{% endif %}
{% for (key, value) in sorted_secrets() %}
- <tr><th>secrets.{{ key }}</th><td>{{ value }}</td></tr>
+ <div class="config-row">
+ <span class="config-key">secrets.{{ key }}</span>
+ <span class="config-val">{{ value }}</span>
+ </div>
{% endfor %}
- </tbody>
-</table>
+ </article>
+
+ <aside class="repo-sidebar">
+ </aside>
+
+</div>
+
{% endblock %}
diff --git a/quire-server/templates/repo_home.html b/quire-server/templates/repo_home.html
index aa250ab..9b7fe82 100644
--- a/quire-server/templates/repo_home.html
+++ b/quire-server/templates/repo_home.html
@@ -38,7 +38,6 @@
{# Left column: README #}
<article class="repo-readme">
- <div class="readme-eyebrow">README.md</div>
{% if let Some(html) = readme_html %}
<div class="readme-content">{{ html|safe }}</div>
{% else %}