Show matched interests as a tag row instead of a callout
With the why line gone the accent-bar callout held nothing but "Matches: a · b", which no longer earned its quoting. Every surface now puts one row directly under the rubric: a small-caps "Matches" label and one pill chip per interest. On the web that is one `.interest-chip` component (admins still get links to the interest's article filter); the EPUB gets `.matches`/`.interest` rules in both stylesheets, and `Understanding::interests_line` goes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESJbbzQ1b8fs5AA65tKw2q
This commit is contained in:
+29
-15
@@ -239,17 +239,6 @@ pub struct InterestRef {
|
|||||||
pub href: String,
|
pub href: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Understanding {
|
|
||||||
/// EPUBs keep interest names as plain text, without dashboard links.
|
|
||||||
pub fn interests_line(&self) -> String {
|
|
||||||
self.interests
|
|
||||||
.iter()
|
|
||||||
.map(|interest| interest.name.as_str())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(" \u{00b7} ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reader-facing assessment details, split so templates can give each part the
|
/// Reader-facing assessment details, split so templates can give each part the
|
||||||
/// same editorial hierarchy across web and EPUB surfaces.
|
/// same editorial hierarchy across web and EPUB surfaces.
|
||||||
pub fn understanding(pick: &Pick) -> Understanding {
|
pub fn understanding(pick: &Pick) -> Understanding {
|
||||||
@@ -919,7 +908,21 @@ mod tests {
|
|||||||
// The lead's rubric and matches are on the index; the second pick has neither.
|
// The lead's rubric and matches are on the index; the second pick has neither.
|
||||||
assert_eq!(chapter.xhtml.matches("class=\"rubric\"").count(), 1);
|
assert_eq!(chapter.xhtml.matches("class=\"rubric\"").count(), 1);
|
||||||
assert!(chapter.xhtml.contains("Software engineering · Analysis"));
|
assert!(chapter.xhtml.contains("Software engineering · Analysis"));
|
||||||
assert!(chapter.xhtml.contains("Matches: Filesystems · Rust"));
|
assert!(
|
||||||
|
chapter
|
||||||
|
.xhtml
|
||||||
|
.contains("<span class=\"interest\">Filesystems</span>")
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
chapter
|
||||||
|
.xhtml
|
||||||
|
.contains("<span class=\"interest\">Rust</span>")
|
||||||
|
);
|
||||||
|
let rubric_position = chapter.xhtml.find("class=\"rubric\"").unwrap();
|
||||||
|
let matches_position = chapter.xhtml.find("class=\"matches\"").unwrap();
|
||||||
|
let summary_position = chapter.xhtml.find("class=\"index-summary\"").unwrap();
|
||||||
|
assert!(rubric_position < matches_position);
|
||||||
|
assert!(matches_position < summary_position);
|
||||||
// Titles are escaped (askama emits numeric references), never injected raw.
|
// Titles are escaped (askama emits numeric references), never injected raw.
|
||||||
assert!(chapter.xhtml.contains("A Niche Delight & Other Tales"));
|
assert!(chapter.xhtml.contains("A Niche Delight & Other Tales"));
|
||||||
assert_xml_ok(&chapter.xhtml);
|
assert_xml_ok(&chapter.xhtml);
|
||||||
@@ -992,11 +995,22 @@ mod tests {
|
|||||||
assert!(chapter.xhtml.contains("class=\"rubric\""));
|
assert!(chapter.xhtml.contains("class=\"rubric\""));
|
||||||
assert!(chapter.xhtml.contains("Software engineering · Analysis"));
|
assert!(chapter.xhtml.contains("Software engineering · Analysis"));
|
||||||
assert!(chapter.xhtml.contains("copy-on-write · ZFS"));
|
assert!(chapter.xhtml.contains("copy-on-write · ZFS"));
|
||||||
assert!(chapter.xhtml.contains("Matches: Filesystems · Rust"));
|
assert!(
|
||||||
|
chapter
|
||||||
|
.xhtml
|
||||||
|
.contains("<span class=\"interest\">Filesystems</span>")
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
chapter
|
||||||
|
.xhtml
|
||||||
|
.contains("<span class=\"interest\">Rust</span>")
|
||||||
|
);
|
||||||
let rubric_position = chapter.xhtml.find("class=\"rubric\"").unwrap();
|
let rubric_position = chapter.xhtml.find("class=\"rubric\"").unwrap();
|
||||||
|
let matches_position = chapter.xhtml.find("class=\"matches\"").unwrap();
|
||||||
let summary_position = chapter.xhtml.find("class=\"summary\"").unwrap();
|
let summary_position = chapter.xhtml.find("class=\"summary\"").unwrap();
|
||||||
let social_position = chapter.xhtml.find("class=\"social\"").unwrap();
|
let social_position = chapter.xhtml.find("class=\"social\"").unwrap();
|
||||||
assert!(rubric_position < summary_position);
|
assert!(rubric_position < matches_position);
|
||||||
|
assert!(matches_position < summary_position);
|
||||||
assert!(summary_position < social_position);
|
assert!(summary_position < social_position);
|
||||||
let footer = chapter
|
let footer = chapter
|
||||||
.xhtml
|
.xhtml
|
||||||
@@ -1004,7 +1018,7 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.1;
|
.1;
|
||||||
assert!(!footer.contains("class=\"rubric\""));
|
assert!(!footer.contains("class=\"rubric\""));
|
||||||
assert!(!footer.contains("Matches:"));
|
assert!(!footer.contains("class=\"matches\""));
|
||||||
let second = render_article(
|
let second = render_article(
|
||||||
&issue,
|
&issue,
|
||||||
&issue.lineup.picks[1],
|
&issue.lineup.picks[1],
|
||||||
|
|||||||
@@ -11,11 +11,7 @@
|
|||||||
<p class="rubric">{% if let Some(kicker) = understanding.kicker %}<span class="kicker">{{ kicker }}</span>{% if let Some(topics) = understanding.topics %}   {{ topics }}{% endif %}{% else %}{% if let Some(topics) = understanding.topics %}{{ topics }}{% endif %}{% endif %}</p>
|
<p class="rubric">{% if let Some(kicker) = understanding.kicker %}<span class="kicker">{{ kicker }}</span>{% if let Some(topics) = understanding.topics %}   {{ topics }}{% endif %}{% else %}{% if let Some(topics) = understanding.topics %}{{ topics }}{% endif %}{% endif %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if !understanding.interests.is_empty() %}
|
{% if !understanding.interests.is_empty() %}
|
||||||
<div class="why">
|
<p class="matches"><span class="kicker">Matches</span>{% for interest in understanding.interests %} <span class="interest">{{ interest.name }}</span>{% endfor %}</p>
|
||||||
{% if !understanding.interests.is_empty() %}
|
|
||||||
<p class="why-matches">Matches: {{ understanding.interests_line() }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if let Some(text) = summary %}
|
{% if let Some(text) = summary %}
|
||||||
<p class="summary">{{ text }}</p>
|
<p class="summary">{{ text }}</p>
|
||||||
|
|||||||
@@ -14,11 +14,7 @@
|
|||||||
<p class="rubric">{% if let Some(kicker) = entry.understanding.kicker %}<span class="kicker">{{ kicker }}</span>{% if let Some(topics) = entry.understanding.topics %}   {{ topics }}{% endif %}{% else %}{% if let Some(topics) = entry.understanding.topics %}{{ topics }}{% endif %}{% endif %}</p>
|
<p class="rubric">{% if let Some(kicker) = entry.understanding.kicker %}<span class="kicker">{{ kicker }}</span>{% if let Some(topics) = entry.understanding.topics %}   {{ topics }}{% endif %}{% else %}{% if let Some(topics) = entry.understanding.topics %}{{ topics }}{% endif %}{% endif %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if !entry.understanding.interests.is_empty() %}
|
{% if !entry.understanding.interests.is_empty() %}
|
||||||
<div class="index-why">
|
<p class="matches"><span class="kicker">Matches</span>{% for interest in entry.understanding.interests %} <span class="interest">{{ interest.name }}</span>{% endfor %}</p>
|
||||||
{% if !entry.understanding.interests.is_empty() %}
|
|
||||||
<p class="why-matches">Matches: {{ entry.understanding.interests_line() }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if !entry.summary.is_empty() %}
|
{% if !entry.summary.is_empty() %}
|
||||||
<p class="index-summary">{{ entry.summary }}</p>
|
<p class="index-summary">{{ entry.summary }}</p>
|
||||||
|
|||||||
@@ -95,16 +95,19 @@ img {
|
|||||||
font-variant: small-caps;
|
font-variant: small-caps;
|
||||||
}
|
}
|
||||||
|
|
||||||
.why,
|
.matches {
|
||||||
.index-why {
|
margin: 0.35em 0 0 0;
|
||||||
margin: 0.2em 0 0 0;
|
font-size: 0.8em;
|
||||||
font-size: 0.9em;
|
color: #444444;
|
||||||
}
|
}
|
||||||
|
|
||||||
.why-matches {
|
.interest {
|
||||||
margin: 0;
|
display: inline-block;
|
||||||
font-size: 0.85em;
|
margin: 0 0.15em 0.15em 0;
|
||||||
color: #444444;
|
padding: 0 0.5em;
|
||||||
|
border: 1px solid #888888;
|
||||||
|
border-radius: 0.8em;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dateline,
|
.dateline,
|
||||||
|
|||||||
@@ -160,16 +160,19 @@ hr.rule {
|
|||||||
color: #444444;
|
color: #444444;
|
||||||
}
|
}
|
||||||
|
|
||||||
.why,
|
.matches {
|
||||||
.index-why {
|
|
||||||
margin: 0.35em 0 0 0;
|
margin: 0.35em 0 0 0;
|
||||||
font-size: 0.9em;
|
font-size: 0.8em;
|
||||||
|
color: #444444;
|
||||||
}
|
}
|
||||||
|
|
||||||
.why-matches {
|
.interest {
|
||||||
margin: 0.1em 0 0 0;
|
display: inline-block;
|
||||||
font-size: 0.85em;
|
margin: 0 0.15em 0.15em 0;
|
||||||
color: #444444;
|
padding: 0 0.5em;
|
||||||
|
border: 1px solid #888888;
|
||||||
|
border-radius: 0.8em;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary {
|
.summary {
|
||||||
|
|||||||
+12
-3
@@ -1994,7 +1994,13 @@ mod tests {
|
|||||||
assert!(issue.contains("A short abstract for the second piece"));
|
assert!(issue.contains("A short abstract for the second piece"));
|
||||||
assert!(issue.contains("Software engineering · Analysis"));
|
assert!(issue.contains("Software engineering · Analysis"));
|
||||||
assert!(issue.contains("copy-on-write · ZFS"));
|
assert!(issue.contains("copy-on-write · ZFS"));
|
||||||
assert!(issue.contains("Matches: Filesystems · Rust"));
|
assert!(issue.contains("<span class=\"interest-chip\">Filesystems</span>"));
|
||||||
|
assert!(issue.contains("<span class=\"interest-chip\">Rust</span>"));
|
||||||
|
let issue_rubric_position = issue.find("Software engineering · Analysis").unwrap();
|
||||||
|
let issue_matches_position = issue.find(">Matches</span>").unwrap();
|
||||||
|
let issue_summary_position = issue.find("What it argues").unwrap();
|
||||||
|
assert!(issue_rubric_position < issue_matches_position);
|
||||||
|
assert!(issue_matches_position < issue_summary_position);
|
||||||
assert!(issue.contains("A. Writer · Example Feed"));
|
assert!(issue.contains("A. Writer · Example Feed"));
|
||||||
assert!(!issue.contains("example feed · Example Feed"));
|
assert!(!issue.contains("example feed · Example Feed"));
|
||||||
assert!(issue.contains("World Briefing"));
|
assert!(issue.contains("World Briefing"));
|
||||||
@@ -2027,7 +2033,9 @@ mod tests {
|
|||||||
assert!(article.contains("loading=\"lazy\""));
|
assert!(article.contains("loading=\"lazy\""));
|
||||||
assert!(article.contains("referrerpolicy=\"no-referrer\""));
|
assert!(article.contains("referrerpolicy=\"no-referrer\""));
|
||||||
let rubric_position = article.find("Software engineering · Analysis").unwrap();
|
let rubric_position = article.find("Software engineering · Analysis").unwrap();
|
||||||
let matches_position = article.find("Matches: Filesystems · Rust").unwrap();
|
let matches_position = article.find(">Matches</span>").unwrap();
|
||||||
|
assert!(article.contains("<span class=\"interest-chip\">Filesystems</span>"));
|
||||||
|
assert!(article.contains("<span class=\"interest-chip\">Rust</span>"));
|
||||||
let summary_position = article
|
let summary_position = article
|
||||||
.find("What it argues, and why it is worth the time.")
|
.find("What it argues, and why it is worth the time.")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -2734,7 +2742,8 @@ mod tests {
|
|||||||
assert!(admin_issue.contains("Was this a good pick?"));
|
assert!(admin_issue.contains("Was this a good pick?"));
|
||||||
assert!(admin_issue.contains("value=\"loved\" data-label=\"loved\" class=\"active\""));
|
assert!(admin_issue.contains("value=\"loved\" data-label=\"loved\" class=\"active\""));
|
||||||
assert!(admin_issue.contains(&dashboard_href));
|
assert!(admin_issue.contains(&dashboard_href));
|
||||||
assert!(admin_issue.contains(&crate::interests::articles_href("Filesystems")));
|
let interest_href = crate::interests::articles_href("Filesystems");
|
||||||
|
assert!(admin_issue.contains(&format!("class=\"interest-chip\" href=\"{interest_href}\"")));
|
||||||
|
|
||||||
let admin_article = app
|
let admin_article = app
|
||||||
.clone()
|
.clone()
|
||||||
|
|||||||
+3
-2
@@ -444,9 +444,10 @@ mod tests {
|
|||||||
assert!(!html.contains("A small-scene delight outside the usual technical orbit"));
|
assert!(!html.contains("A small-scene delight outside the usual technical orbit"));
|
||||||
assert!(html.contains("Software engineering · Analysis"));
|
assert!(html.contains("Software engineering · Analysis"));
|
||||||
assert!(html.contains("copy-on-write · ZFS"));
|
assert!(html.contains("copy-on-write · ZFS"));
|
||||||
assert!(html.contains("Matches: Filesystems · Rust"));
|
assert!(html.contains("<span class=\"interest-chip\">Filesystems</span>"));
|
||||||
|
assert!(html.contains("<span class=\"interest-chip\">Rust</span>"));
|
||||||
let rubric_position = html.find("Software engineering · Analysis").unwrap();
|
let rubric_position = html.find("Software engineering · Analysis").unwrap();
|
||||||
let matches_position = html.find("Matches: Filesystems · Rust").unwrap();
|
let matches_position = html.find(">Matches</span>").unwrap();
|
||||||
let summary_position = html
|
let summary_position = html
|
||||||
.find("What it argues, and why it is worth the time.")
|
.find("What it argues, and why it is worth the time.")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -186,6 +186,8 @@
|
|||||||
|
|
||||||
.reader-page :where(h1, h2, h3, h4), .reader-section-heading { text-wrap:balance; }
|
.reader-page :where(h1, h2, h3, h4), .reader-section-heading { text-wrap:balance; }
|
||||||
.reader-section-heading { @apply border-t-[3px] border-double border-rule-strong pt-3 font-serif text-[1.05rem] font-semibold uppercase leading-[1.2] tracking-[0.15em] text-ink; }
|
.reader-section-heading { @apply border-t-[3px] border-double border-rule-strong pt-3 font-serif text-[1.05rem] font-semibold uppercase leading-[1.2] tracking-[0.15em] text-ink; }
|
||||||
|
.interest-chip { @apply inline-block rounded-full border border-rule-strong px-2.5 py-0.5 text-xs leading-5 text-ink-2 no-underline; }
|
||||||
|
a.interest-chip:hover { @apply border-accent text-accent; }
|
||||||
|
|
||||||
.rating { @apply my-5 flex flex-wrap items-center gap-x-3 font-sans text-xs; }
|
.rating { @apply my-5 flex flex-wrap items-center gap-x-3 font-sans text-xs; }
|
||||||
.rating-prompt { @apply font-serif text-[0.8rem] italic text-muted; }
|
.rating-prompt { @apply font-serif text-[0.8rem] italic text-muted; }
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{% if understanding.kicker.is_some() || understanding.topics.is_some() %}<p class="mt-3 font-sans text-sm text-muted">{% if let Some(kicker) = understanding.kicker %}<span class="text-[0.72rem] uppercase tracking-[0.12em]">{{ kicker }}</span>{% if let Some(topics) = understanding.topics %}<span class="ml-3">{{ topics }}</span>{% endif %}{% else %}{% if let Some(topics) = understanding.topics %}<span>{{ topics }}</span>{% endif %}{% endif %}</p>{% endif %}{% if !understanding.interests.is_empty() %}<div class="mt-4 border-l-2 border-accent pl-3">{% if !understanding.interests.is_empty() %}<p class="mt-1 font-sans text-sm text-muted">Matches: {% for interest in understanding.interests %}{% if page.is_admin() %}<a href="{{ interest.href }}">{{ interest.name }}</a>{% else %}{{ interest.name }}{% endif %}{% if !loop.last %} · {% endif %}{% endfor %}</p>{% endif %}</div>{% endif %}
|
{% if understanding.kicker.is_some() || understanding.topics.is_some() %}<p class="mt-3 font-sans text-sm text-muted">{% if let Some(kicker) = understanding.kicker %}<span class="text-[0.72rem] uppercase tracking-[0.12em]">{{ kicker }}</span>{% if let Some(topics) = understanding.topics %}<span class="ml-3">{{ topics }}</span>{% endif %}{% else %}{% if let Some(topics) = understanding.topics %}<span>{{ topics }}</span>{% endif %}{% endif %}</p>{% endif %}{% if !understanding.interests.is_empty() %}<p class="mt-2.5 flex flex-wrap items-center gap-1.5 font-sans"><span class="mr-1 text-[0.72rem] uppercase tracking-[0.12em] text-muted">Matches</span>{% for interest in understanding.interests %}{% if page.is_admin() %}<a class="interest-chip" href="{{ interest.href }}">{{ interest.name }}</a>{% else %}<span class="interest-chip">{{ interest.name }}</span>{% endif %}{% endfor %}</p>{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user