1. Markdown preview in the admin edit panel now re-renders from the
textarea's current value on every toggle (dynamic-imports marked on
the client). Previously the panel showed the server-rendered seed
value forever, so new dispatches always previewed empty.
2. Pulse sub-form drops the opens_at field (opens on dispatch publish
automatically) and changes closes_at to a date input — the chosen
day is treated as end-of-day in the DB.
3. /dispatches/[slug] reading width widened 50% (720 → 1080px).
4. Roadmap display_order cascades on insert / update / delete:
inserting at N bumps N..end up by 1, deleting N pulls N+1..end
down by 1, moving from A to B shifts the intermediate range by 1
in the appropriate direction. Order stays dense — no gaps, no
collisions. All three transitions run in a transaction.
5. /roadmap always anchors at scrollLeft=0 on mount so the first
milestone aligns with the content-column left edge. Previously
the page jumped to the last-shipping milestone, which felt random
once items past the viewport landed.
6. Events admin list shows the actual date (fmtDateTime) instead of
"in 3 days" — easier to scan when planning across months.
7. duration_label is auto-computed from starts_at + ends_at on save
(minutes < 90, hours < 4, "Half day", "Full day", "N days").
The manual field is gone from the admin form; the column on the
member-facing event pages keeps reading the stored value as before.
8. Pulse hero still skips office hours per the existing logic — no
change. Confirmed via the test note's clarification.
9. Pulse "also coming up" strip relabeled to Previous + Upcoming.
Previous = most recent past non-office-hours event. Upcoming =
next non-office-hours event after the hero. Each card now carries
a small terracotta eyebrow with the label.
Typecheck clean, build clean, 147/147 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pulse vote widget on /pulse and on /dispatches/[slug] now behaves
the way the v5 spec asks:
- Eyebrow shortens: 'This week's pulse' → 'This week's'.
- Status line copy changes shape depending on whether the viewer has
voted yet. Pre-vote: 'Vote to see how the council weighs in · Closes
TUESDAY' — sets expectations that the percentages reveal after a
vote. Post-vote: '2 of 7 voted · Closes TUESDAY · Click to change' —
tells the viewer they can change their mind.
- Each option now renders a right-aligned tabular-nums percentage badge,
but only after the viewer has voted. Pre-vote there's no percentage
on screen at all — voting is a commitment, not a peek.
- Options stay clickable after voting (no `disabled`). Re-clicking a
different option changes the vote.
DB: new helper castOrChangeVote(pulseId, userId, optionIndex) does an
UPSERT — INSERT on first vote, UPDATE option_index + voted_at on
subsequent. Returns true if this was the brand-new vote, so the caller
can write the 'voted' activity row exactly once and not double-count
changes-of-mind in the feed. castVote(...) (INSERT OR IGNORE) stays in
db.ts for callers that explicitly want first-vote-wins semantics.
Status-class rename: .locked → .closed on both pulse-option and
inline-poll-option. The class now reflects what it actually represents
(the pulse is closed) rather than the false invariant 'the user has
voted and can't change'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Layout (per the v4 follow-up spec):
1b. Latest from Fenja is now a two-box layout when there's an attached
poll: article on the left (wider), poll widget on the right. Without
a poll, the article box takes the full row. Both boxes are surfaced
on --surface-card with the same generous padding so they read as
sibling pieces.
1c. Featured excerpt is extended to ~720 chars (was ~520) via a wider
threshold on dispatchLongPreview. Below the article+poll row, the
next two most-recent published dispatches render as minimalist rows
— just title + kind + relative time, separated by ghost borders.
2. Hero event: date column is now 150px wide (was 110px); grid uses
align-items: center so the date+detail columns are vertically aligned
rather than top-stuck. Day number scaled up to 3.5rem (was 2.75).
Outer card padding bumped from --space-7 to --space-10. Hero title
bumped to 2rem.
3. More air: page-level section gap --space-10 → --space-12. Each
on-page card has been re-padded; outer page horizontal padding goes
down to --space-16 from --space-20 to match the narrower canvas.
6. Council members no longer have individual card chrome. One outer
--surface-card wraps the whole grid; each member cell is just an
avatar + name + title + company stack with no background or border.
Cells use a larger 6/8 grid gap so they don't crowd each other.
Inline poll widget on /dispatches/[slug]: when a dispatch has an
attached pulse, the article body is followed by a compact poll card
matching the /pulse-side widget. Vote POST handled inline; the page
re-renders with the locked + result-bar state.
scripts/seed-demo.js: the existing 'Which milestone should we anchor Q3
around?' pulse now attaches to the decision dispatch ('We are
deprioritising public-cloud parity for Q3') via pulse_id. Other
dispatches stay poll-free.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Applies the v3 rules globally: italics reserved for the Bifrost wordmark
and section-links; uppercase tracked eyebrows removed from page heads
and content sections.
Pages updated:
- /members: drops the 'MEMBERS' eyebrow + italic on h1 + italic on member
name + italic on pull-quote.
- /events: drops the head eyebrow + 'Next up · Members only' /
'Invitation by hand' hero eyebrows + 'Also coming up' / 'Past
gatherings' sub-section eyebrows + italics on hero day, hero title,
also-row titles, past-card titles. The past-gatherings header-row
'View all →' link migrates to the bottom of the section as a
section-link.
- /events/past: drops the eyebrow + italic h1; back-link uses
.section-link.
- /dispatches/: drops the 'DISPATCHES' eyebrow + italic h1 + dispatch-row
title italic + date column italic.
- /dispatches/[slug]: drops italic on the article title + h2/h3 inside
rendered markdown + blockquote italic + adjacent prev/next title
italic. Back-link migrates to .section-link.
- /roadmap: drops the 'ROADMAP' eyebrow and the .lead class on the
subtitle.
Orphaned eyebrow class rules left in place; harmless and the next
visual pass can sweep them with the rest of the unused CSS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
/dispatches: editorial header (DISPATCHES · 'Notes from the studio.' ·
'Decisions, half-built ideas, and things we've changed our mind about.')
+ a vertical list of every published dispatch. Each row is a single link
target with a three-column grid: 180px byline (avatar + author name + title
or role label) / 1fr body (serif italic title + kind pill, then a single-
paragraph excerpt) / 130px date column. 0.5px bottom borders, hover tint.
/dispatches/[slug]: 720px single-column read view. Header is kind pill +
publish date, serif italic title at 2rem, author byline with 32px avatar.
Body uses the existing renderMd() (marked) with serif italic h2s, copper
blockquotes, mono code blocks. Footer is a 0.5px divider then two adj-card
links (prev / next in published order) on opposite ends — the missing side
renders an empty grid slot so layout is preserved.
Canonical-slug redirect: if /dispatches/12-old-title is hit but the title
has since changed, the page issues a 302 to /dispatches/12-new-title. id
is the authority, kebab title is for readability.
format.ts: adds roleLabel (pilot/cab/fenja → 'Pilot' / 'Council' /
'Fenja team') for the byline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>