From 31538758f3ca4c0bcfaffdcf1920691896a8b52b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lomig=20Me=CC=81gard?= Date: Thu, 23 Jul 2026 21:51:36 +0200 Subject: [PATCH] feat(render): brand demo prints and refine report layout Brand the generated script and report headers with the Rite-ly name and logo (vendored at docs/demo/logo.svg), wire the flags into docs/demo/render-prints.sh, and make its output-dir glob slug-independent. Rename the showcase ceremony to "Demo: Root Signing Key Ceremony". Report layout: align the header with the script (eyebrow above the title), rename the Artifacts "SHA-256" column to "Fingerprint", and shrink the hash so a full fingerprint stays on two lines. Add a Roles legend and show role abbreviations in the Execution Log (Label first), reusing the script's abbreviation helper via a shared unique_abbrev. Regenerate the committed demo prints and update snapshots. The demo cast and GIF are left for regeneration at the next release. --- crates/rite-render/src/view.rs | 69 +++++++++++++---- .../rite-render/templates/report.html.jinja | 18 ++++- .../rite-render/templates/themes/formal.css | 2 + .../tests/snapshots/render__report_empty.snap | 5 +- .../tests/snapshots/render__script_demo.snap | 6 +- .../snapshots/render__script_named_acts.snap | 2 + docs/demo/demo-report.html | 42 +++++++---- docs/demo/demo-script.html | 8 +- docs/demo/demo-transcript.jsonl | 74 +++++++++---------- docs/demo/logo.svg | 1 + docs/demo/render-prints.sh | 25 +++++-- examples/showcase/README.md | 2 +- examples/showcase/demo.rite.yaml | 2 +- 13 files changed, 174 insertions(+), 82 deletions(-) create mode 100644 docs/demo/logo.svg diff --git a/crates/rite-render/src/view.rs b/crates/rite-render/src/view.rs index 88daa55..ae9df4f 100644 --- a/crates/rite-render/src/view.rs +++ b/crates/rite-render/src/view.rs @@ -492,22 +492,27 @@ pub(crate) fn render_prose_html(text: &str) -> String { } /// Build a deterministic, unique abbreviation for every role, keyed by id. -#[allow(clippy::arithmetic_side_effects)] fn build_abbrevs(resolved: &Ceremony) -> HashMap { let mut used: HashSet = HashSet::new(); - let mut out = HashMap::new(); - for (id, role) in resolved.roles.iter() { - let base = abbrev_of(&role.name); - let mut candidate = base.clone(); - let mut n = 2u32; - while used.contains(&candidate) { - candidate = format!("{base}{n}"); - n += 1; - } - used.insert(candidate.clone()); - out.insert(id.clone(), candidate); + resolved + .roles + .iter() + .map(|(id, role)| (id.clone(), unique_abbrev(&abbrev_of(&role.name), &mut used))) + .collect() +} + +/// Reserve a unique abbreviation derived from `base`, suffixing `2`, `3`, ... +/// on collision. Records the chosen value in `used` so later calls stay unique. +#[allow(clippy::arithmetic_side_effects)] +fn unique_abbrev(base: &str, used: &mut HashSet) -> String { + let mut candidate = base.to_string(); + let mut n = 2u32; + while used.contains(&candidate) { + candidate = format!("{base}{n}"); + n += 1; } - out + used.insert(candidate.clone()); + candidate } /// Derive a short abbreviation from a role name. @@ -559,6 +564,8 @@ pub struct ReportView { pub deviations: Vec, /// Produced artifacts. pub artifacts: Vec, + /// Distinct roles seen in the log, with abbreviations (legend). + pub roles: Vec, /// Per-step execution log. pub steps: Vec, /// The `rite` version that produced the report. @@ -617,6 +624,15 @@ pub struct ArtifactView { pub sha256: String, } +/// A role in the report's roles legend. +#[derive(Debug, Clone, Serialize)] +pub struct ReportRoleView { + /// Human-readable role name. + pub name: String, + /// Short abbreviation, unique within the report. + pub abbrev: String, +} + /// A per-step row in the report execution log. #[derive(Debug, Clone, Serialize)] pub struct ExecStepView { @@ -626,6 +642,8 @@ pub struct ExecStepView { pub label: String, /// Role name. pub role: String, + /// Role abbreviation, matching the roles legend. + pub role_abbrev: String, /// Formatted start timestamp. pub started: String, /// Formatted completion timestamp, if any. @@ -673,6 +691,25 @@ impl ReportView { }) }) .collect(); + // The report is transcript-only, so roles come from the names recorded + // in the log. Abbreviate them with the same helpers the script uses, so + // a run's report and its script agree on `CO`, `Wi`, and so on. + let mut used = HashSet::new(); + let mut roles: Vec = Vec::new(); + for name in data.steps.iter().map(|s| &s.role) { + if roles.iter().any(|r| &r.name == name) { + continue; + } + roles.push(ReportRoleView { + name: name.clone(), + abbrev: unique_abbrev(&abbrev_of(name), &mut used), + }); + } + let abbrev_by_name: HashMap<&str, &str> = roles + .iter() + .map(|r| (r.name.as_str(), r.abbrev.as_str())) + .collect(); + let steps = data .steps .iter() @@ -684,6 +721,11 @@ impl ReportView { ExecStepView { step_id: s.step_id.clone(), label: s.label.clone(), + role_abbrev: abbrev_by_name + .get(s.role.as_str()) + .copied() + .unwrap_or_default() + .to_string(), role: s.role.clone(), started: format_datetime(&s.started_at), completed: s.completed_at.as_ref().map(format_datetime), @@ -710,6 +752,7 @@ impl ReportView { attempts, deviations, artifacts, + roles, steps, rite_version: env!("CARGO_PKG_VERSION").to_string(), } diff --git a/crates/rite-render/templates/report.html.jinja b/crates/rite-render/templates/report.html.jinja index cbb7e19..deef519 100644 --- a/crates/rite-render/templates/report.html.jinja +++ b/crates/rite-render/templates/report.html.jinja @@ -18,7 +18,8 @@ {%- if branding.brand_name %}

{{ branding.brand_name }}

{%- endif %} -

{{ report.ceremony_name }} · Ceremony Report

+

Ceremony Report

+

{{ report.ceremony_name }}

@@ -63,7 +64,7 @@ {%- if report.artifacts %}

Artifacts

- + {%- for a in report.artifacts %} @@ -72,13 +73,22 @@
NameStepPathSHA-256
NameStepPathFingerprint
{{ a.name }}{{ a.step_id }}{{ a.path }}{{ a.sha256 }}
{%- endif %} +{%- if report.roles %} +

Roles

+
    +{%- for r in report.roles %} +
  • {{ r.abbrev }} {{ r.name }}
  • +{%- endfor %} +
+{%- endif %} + {%- if report.steps %}

Execution Log

- + {%- for s in report.steps %} - + {%- endfor %}
StepLabelRoleStartedCompletedOutcome
LabelStepRoleStartedCompletedOutcome
{{ s.step_id }}{{ s.label }}{{ s.role }}{{ s.started }}{{ s.completed if s.completed else "–" | safe }}{{ s.outcome }}
{{ s.label }}{{ s.step_id }}{% if s.role_abbrev %}{{ s.role_abbrev }}{% else %}{{ s.role }}{% endif %}{{ s.started }}{{ s.completed if s.completed else "–" | safe }}{{ s.outcome }}
diff --git a/crates/rite-render/templates/themes/formal.css b/crates/rite-render/templates/themes/formal.css index b7f165d..898b9fd 100644 --- a/crates/rite-render/templates/themes/formal.css +++ b/crates/rite-render/templates/themes/formal.css @@ -437,6 +437,8 @@ code, .hash { } .hash { word-break: break-all; + /* Slightly smaller so a full fingerprint stays compact in a narrow column. */ + font-size: 0.75em; } .status-completed { color: #1f6b2e; diff --git a/crates/rite-render/tests/snapshots/render__report_empty.snap b/crates/rite-render/tests/snapshots/render__report_empty.snap index d4399e9..dceac27 100644 --- a/crates/rite-render/tests/snapshots/render__report_empty.snap +++ b/crates/rite-render/tests/snapshots/render__report_empty.snap @@ -447,6 +447,8 @@ code, .hash { } .hash { word-break: break-all; + /* Slightly smaller so a full fingerprint stays compact in a narrow column. */ + font-size: 0.75em; } .status-completed { color: #1f6b2e; @@ -527,7 +529,8 @@ code, .hash {
-

· Ceremony Report

+

Ceremony Report

+

diff --git a/crates/rite-render/tests/snapshots/render__script_demo.snap b/crates/rite-render/tests/snapshots/render__script_demo.snap index aa2eb41..35a1fb7 100644 --- a/crates/rite-render/tests/snapshots/render__script_demo.snap +++ b/crates/rite-render/tests/snapshots/render__script_demo.snap @@ -6,7 +6,7 @@ expression: html - Root Signing Key Ceremony + Demo: Root Signing Key Ceremony