From 1ec01a22578f3df936485792b687919954de97ec Mon Sep 17 00:00:00 2001 From: Jonathan Hvid Date: Tue, 12 May 2026 12:00:14 +0200 Subject: [PATCH] feat(roadmap): 'In motion right now' strip + subtitle copy + admin helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New component renders between the dispatch banner and the route's section header. Pulls the most-recent shipping item (same selection rule the .rr-current marker uses) and prints the first sentence of its description as a 18px serif italic line preceded by an 'IN MOTION RIGHT NOW' tracked eyebrow. A member who only spends 5 seconds on /roadmap now still walks away with a sentence about what just shipped — no scroll, no hover. firstSentenceOf() is the obvious regex against the first [.!?](?=\s|$). Bails to the 200-char slice if no sentence boundary fits (covers 'Dr.' / 'e.g.' confusables). Returns '' on null. The strip hides itself entirely when there's no shipping item, or when the shipping item has no description text. Page subtitle: 'Hover any milestone for the full story.' → 'Tap or hover any milestone for the full story.' — touch devices don't have hover, and the kind of detail that says we're paying attention. Admin description-field gains a helper note: 'For shipping items: the first sentence appears on /roadmap as the "In motion right now" line. Make it count.' Nudges good first-sentence writing without adding a new field to maintain. Banner margin under the dispatch banner reduces 56 → 40px because the in-motion strip carries its own 36px bottom margin to the route. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/RoadmapInMotion.astro | 69 +++++++++++++++++++++++++++ src/components/admin/RoadmapTab.astro | 1 + src/pages/roadmap.astro | 15 +++--- 3 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 src/components/RoadmapInMotion.astro diff --git a/src/components/RoadmapInMotion.astro b/src/components/RoadmapInMotion.astro new file mode 100644 index 0000000..216c9a4 --- /dev/null +++ b/src/components/RoadmapInMotion.astro @@ -0,0 +1,69 @@ +--- +import type { RoadmapItemWithAttribution } from '../lib/db'; + +interface Props { + items: RoadmapItemWithAttribution[]; +} + +const { items } = Astro.props; + +/** First sentence of a description — naive but matches the user's needs. + * Returns '' for null/empty input. Falls back to a 200-char slice if no + * sentence-ending punctuation is found in a reasonable window. */ +function firstSentenceOf(text: string | null): string { + if (!text) return ''; + const trimmed = text.trim(); + const match = trimmed.match(/^[^.!?]*[.!?](?=\s|$)/); + return match ? match[0] : trimmed.slice(0, 200); +} + +// Most recent shipping item, in display_order (same selection rule as +// the .rr-current marker on the route). +let currentItem: RoadmapItemWithAttribution | null = null; +items.forEach((it) => { if (it.status === 'shipping') currentItem = it; }); + +const line = currentItem ? firstSentenceOf(currentItem.description) : ''; +const visible = !!currentItem && line.length > 0; +--- +{visible && ( +
+ In motion right now +

{line}

+
+)} + + diff --git a/src/components/admin/RoadmapTab.astro b/src/components/admin/RoadmapTab.astro index db42c6b..91f4872 100644 --- a/src/components/admin/RoadmapTab.astro +++ b/src/components/admin/RoadmapTab.astro @@ -64,6 +64,7 @@ const grouped: Record = {
+ For shipping items: the first sentence appears on /roadmap as the "In motion right now" line. Make it count.
diff --git a/src/pages/roadmap.astro b/src/pages/roadmap.astro index efdf520..771b7d3 100644 --- a/src/pages/roadmap.astro +++ b/src/pages/roadmap.astro @@ -1,6 +1,7 @@ --- import AppLayout from '../layouts/AppLayout.astro'; import LatestDispatchBanner from '../components/LatestDispatchBanner.astro'; +import RoadmapInMotion from '../components/RoadmapInMotion.astro'; import RoadmapRoute from '../components/RoadmapRoute.astro'; import { getAllRoadmapItems } from '../lib/db'; @@ -18,13 +19,15 @@ const items = getAllRoadmapItems()

What we are building.

A live picture of the work. What's in motion, what's queued, - what we're still thinking about. Hover any milestone for the - full story. + what we're still thinking about. Tap or hover any milestone + for the full story.

+ +
@@ -63,14 +66,14 @@ const items = getAllRoadmapItems() max-width: 540px; } - /* The banner already has its own bottom-margin via the layout in - the parent. Add the spec'd 56px below it before the route header. */ - .page :global(.banner) { margin-bottom: 56px; } + /* In-motion strip lives between banner and route; banner margin + tightens to 40px because the strip carries its own 36px below. */ + .page :global(.banner) { margin-bottom: 40px; } @media (max-width: 767px) { .page { padding: 32px 24px 64px; } .page-title { font-size: 36px; } .page-header { margin-bottom: 28px; } - .page :global(.banner) { margin-bottom: 36px; } + .page :global(.banner) { margin-bottom: 28px; } }