Commit graph

42 commits

Author SHA1 Message Date
865347f682 feat(db): migration 0004 — phase 2 schema + 4 new tokens
users: adds pull_quote, member_number (unique partial index, NULLs allowed),
focus_tags. title already exists from 0003 — not re-added. Backfills
member_number for existing cab users in COALESCE(cab_joined_date,
created_at) asc, tiebreak id asc. Lars gets #1.

events: rebuild required to widen the kind CHECK constraint (SQLite can't
ALTER it in place). Adds working_session as a new kind. Same rebuild adds
four new columns: audience, duration_label, action_label, notes_url. Data
preserved.

dispatches: new entity, status enum draft/published/archived, kind enum
decision/update/behind_the_scenes/note. published_at nullable until
publishPulse-equivalent stamps it. Indexes on (status, published_at) and
author_id.

Tokens (src/styles/tokens.css): adds --surface-card, --surface-card-border,
--ink, --ink-text, --ink-muted. Spec called these out as the only
additions; existing --radius-lg covers the spec's --border-radius-lg
reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 15:46:53 +02:00
fe27811d16 chore(demo): seed-demo.js + utc fix for last_seen_at
scripts/seed-demo.js — one open Pulse with realistic context, marks
"Traceability layer" as shipping with shipped_at -2 days and attributes
it to the cab user, two events (dinner in 5 weeks, office hours in 2
weeks), six hand-crafted activity rows mixing all 5 activity kinds so
the ticker has something to scroll on first load. Idempotent: skips if
any pulses exist. Backdates Lars's cab_joined_date so the greeting
renders "2 years, 4 months". Wired to db:setup and db:seed:demo.

Also fixes a parse bug on /pulse: SQL stores last_seen_at as
'YYYY-MM-DD HH:MM:SS' UTC, but new Date(string) parses that as local
time — on a non-UTC server the freshness check was wrong by the server's
offset. Coerce to UTC ISO before parsing. Manual smoke as Lars now shows
two member chips in "online now"; admin tabs all render.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 15:04:11 +02:00
f6e7337c5e feat(admin): pulses, roadmap, events, activity tabs
Four new tabs added to /admin, matching the existing pattern exactly: ?tab=
querystring, plain HTML POST with hidden action field, redirect-with-?msg=
success path, existing .tabs / .section / .data-table / .form-grid / .input
/ .select / .btn-primary / .danger-btn classes — no new form library, no JS
beyond `confirm()`.

Pulses tab — create / edit / publish / close / delete + a results view
(?view=ID) with per-option vote counts and bar charts. Publish writes the
'pulse_opened' activity row and calls notifyPulseOpened() so members can
be notified the same way once the integration lands.

Roadmap tab — full CRUD + multi-select attribution (checkbox grid of
council + pilot users) + up/down arrow reorder within each status column
(JS-free, swaps display_order with neighbour). Status transition to
'shipping' stamps shipped_at exactly once and writes 'roadmap_shipped'
activity.

Events tab — full CRUD + an RSVP summary view (?view=ID) showing going /
interested / declined counts. Slug is required on create and readonly on
edit (it's the URL handle).

Activity tab — read-only debug table of the last 200 activity rows. Per
your call: shipping with the rest, not optional. Saves hours of "why
isn't the ticker showing X" later.

Tab views extracted to src/components/admin/{Pulses,Roadmap,Events,Activity}Tab.astro
to keep admin/index.astro navigable; the POST handlers and data loading
stay in index.astro as the single dispatch point.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:57:44 +02:00
4611b687c9 feat(lib): notify() stub for pulse-opened events
Today this just logs to console. Wired up in step 10's admin pulse-publish
handler so the integration point exists from day one — only the body
changes when we pick a transactional email or Slack webhook later.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:53:40 +02:00
351d90a3e8 feat(pulse): full member-portal landing at /pulse
Sections (top to bottom, all wrapped in a cascade-entry animation):
- greeting block: MONDAY · 11 MAY label, serif-italic "Good morning, X."
  greeting (server time, Europe/Copenhagen), tenure line that uses
  cab_joined_date for cab members and falls back to created_at
- ActivityTicker fed by getRecentActivity (hidden when empty)
- this-week Pulse card: live breathing dot + time-left countdown, italic
  serif question, 2×2 grid of option buttons that submit a hidden-action
  vote form. Once voted, options lock and show a distribution bar fill;
  the user's choice keeps a terracotta border. Empty state when no pulse
  is open.
- 2-col roadmap preview + council mark: three most-recently-updated items
  with status dot (copper/ochre/muted), and the user's CouncilMark md with
  the "{n} of 12 quarters" stat
- members-in-the-room: 4 chips for other CAB members seen within 5 min,
  +N overflow chip; pulsing copper dot for the "online now" indicator
- two-column event row: dark indigo "members-only" card (next dinner or
  summit) + light "office hours" card (next office_hours event). Hidden
  per-card when no matching upcoming event exists.

Vote POST uses the existing admin form pattern (hidden action field,
redirect on success). Activity row for 'voted' is written inline here;
step 11 covers the other activity sources.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:52:51 +02:00
39c9c805cd feat(component): ActivityTicker + format helpers
ActivityTicker — pure presentational. Renders a horizontal strip of items
that scrolls right→left over 38s, with CSS mask fades on both edges and
pause-on-hover. Items are duplicated so translateX(-50%) wraps seamlessly.
Hidden entirely when empty (no "no activity" placeholder).

format.ts — small set of formatters used by the ticker and the upcoming
/pulse greeting:
- relativeTime (2m / 3h / 5d / just now)
- redactName ("Maya Rasmussen" → "Maya R.")
- tenureSince (e.g. "2 years, 4 months")
- pulseDateLabel ("MONDAY · 11 MAY", Europe/Copenhagen)
- timeOfDay (morning/afternoon/evening, Europe/Copenhagen)
- tickerItem (ActivityRow + subject label → display struct, with role-dot
  colours mapped to existing pigments: copper for pilot, terracotta for
  cab, indigo for fenja)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:50:10 +02:00
6f23c47e7a feat(component): CouncilMark — generative member sigil
SVG component, sizes sm (48) / md (100) / lg (200) on a 120 viewBox.

12 quarter positions on a circle, i=0 = current quarter at 12 o'clock,
going clockwise into the past. A position is lit iff the member has a
roadmap_attribution to an item with shipped_at in that quarter. Pulse
votes do NOT light a quarter — the sigil represents consequential
contribution, not participation.

Visual layers:
- faint dotted outer ring rotating 90s linear
- small grey dots on empty quarters
- terracotta-filled polygon (15% fill) connecting lit dots, draws in
  over 1.6s on mount via stroke-dasharray
- terracotta dots on lit quarters with staggered 2.4s breathing
- serif italic initials in the centre, walnut secondary

Uses --pigment-terracotta instead of the spec's coral — no new tokens.
Honours prefers-reduced-motion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:48:47 +02:00
3cb76b33c8 feat(home): role-based / redirect via homeRouteForRole
cab and fenja are sent to /pulse on landing; pilot continues to see the
existing editorial home content in place. Uses the same helper the test
suite already covers, so behaviour is locked in.

Adds a minimal /pulse stub so the redirect target resolves; step 7 replaces
it with the full member view.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:47:28 +02:00
267ba34747 feat(nav): restructure header + add footer for council portal
Nav now reads Pulse · Roadmap · Members · Events · [Admin]. Old links
(Home / Vision / Product / Updates / Contribute) are dropped from the
header; Vision moves to the footer alongside a Council manifesto stub.

Header keeps the name + Sign out on the right exactly as before — Sign out
is NOT duplicated in the footer.

Footer: small Fenja icon mark + © year + two text links. Uses the existing
ghost-border, no shadows, matches editorial flatness.

/council-manifesto added as a one-screen stub so the link doesn't 404.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:46:55 +02:00
20209db2d8 test: vitest suite — pulse status, vote uniqueness, home route
Three test files covering the Phase 1 invariants:
- derivePulseStatus: draft/closed are sticky; open auto-closes by date.
- vote UNIQUE(pulse_id, user_id): castVote (OR IGNORE) keeps the first vote
  silently, a raw second INSERT raises a constraint error, and uniqueness is
  per-pulse (same user on a different pulse is fine).
- homeRouteForRole: cab/fenja → /pulse, pilot → null (render existing home).

tests/setup.ts opens BIFROST_DB_PATH=':memory:' and applies all migrations
before tests run, so the in-memory DB has the live schema. Each vitest fork
gets its own globalThis → its own fresh in-memory DB.

The homeRouteForRole helper extraction makes the / role-redirect testable
without booting Astro. Step 6 will use it from /index.astro.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:45:55 +02:00
1735487ab9 feat(db): pulse/vote/roadmap/event/activity helpers + derivePulseStatus
Adds typed query functions for the council-portal entities. Pulse status is
stored AND derived: draft and closed are sticky, open auto-decays to closed
once now ≥ closes_at. Draft → open is an explicit admin Publish action, not
date-driven, so admins can stage a pulse without surprise auto-publishing.

Roadmap updateRoadmapItem stamps shipped_at the first time status transitions
to 'shipping' and never resets it; returns { shippedNow } so callers can fire
the roadmap_shipped activity row exactly once.

Event RSVPs reuse the existing attendance table with kind='event'; no
parallel Rsvp table. setEventRsvp upserts on UNIQUE(user_id, meeting_slug).

getLitQuarters drives the CouncilMark dot pattern from
roadmap_attributions × shipped_at — admin-curated, not derived from votes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:41:49 +02:00
56992ed4ca feat(db): configurable path, user field extensions, slug generation
- BIFROST_DB_PATH env var overrides the default bifrost.db path; lets
  vitest open ':memory:' per suite without touching prod data.
- Extend User/UserPublic with title, cab_joined_date, slug.
- Update SELECT lists for getUserPublicById and getAllUsersPublic.
- Add getUserBySlug for /members/:slug routes.
- Add slugifyName + generateUniqueSlug; createUser now auto-slugs from name
  and stamps cab_joined_date for cab-role users.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:40:24 +02:00
Jonathan
d054b56bf7 chore: remove participants and calendar pages and nav links 2026-04-19 20:51:55 +02:00
Jonathan
a0931ea527 refactor: home page CTA as proper button with confirmation state 2026-04-19 20:46:43 +02:00
Jonathan
bc8e08d022 refactor: home page structure and pacing 2026-04-19 20:46:06 +02:00
Jonathan
f6e6fb255d fix: hero lockup — Fenja wordmark scale and colour 2026-04-19 20:44:57 +02:00
Jonathan
6cbab5ba36 chore: add Innofounder logo to public, wire to home page 2026-04-19 20:36:54 +02:00
Jonathan
2d3391f531 feat: product page with architecture and platform framing 2026-04-19 20:31:51 +02:00
Jonathan
f7bd9085de feat: vision page as manifesto 2026-04-19 20:30:57 +02:00
Jonathan
d9c75a1921 refactor: rebuild home as welcome and pitch page 2026-04-19 20:30:14 +02:00
Jonathan
4bed3a5fe0 feat: join_requests table and join CTA flow 2026-04-19 20:29:09 +02:00
Jonathan
fa5e6d8414 docs: add editorial patterns (pulled quote, stat figure, arch diagram) to style guide 2026-04-19 20:28:10 +02:00
Jonathan
26173b7396 docs: add ProjectLockup to style guide 2026-04-19 20:27:20 +02:00
Jonathan
5a7af0b0d8 feat: ProjectLockup component 2026-04-19 20:26:45 +02:00
Jonathan
f8c7152fa9 chore: remove preview page in preparation for product page 2026-04-19 20:26:31 +02:00
Jonathan
82861ca4d2 chore: typecheck and build clean 2026-04-18 22:54:25 +02:00
Jonathan
7f02600c05 feat: admin panel 2026-04-18 22:52:29 +02:00
Jonathan
99f3052651 feat: participants directory and account page 2026-04-18 22:51:30 +02:00
Jonathan
636ef129bb feat: product preview page 2026-04-18 22:50:42 +02:00
Jonathan
40aed88525 feat: contribute feed, reactions, and edit flow 2026-04-18 22:50:11 +02:00
Jonathan
caab3ab187 feat: calendar 2026-04-18 22:48:27 +02:00
Jonathan
edc0cfdb0f feat: roadmap 2026-04-18 22:47:38 +02:00
Jonathan
d300e4a76e feat: updates 2026-04-18 22:47:13 +02:00
Jonathan
76c7dfa985 feat: vision page 2026-04-18 22:45:56 +02:00
Jonathan
9de5602d2d feat: authentication and invite flow 2026-04-18 22:45:25 +02:00
Jonathan
0dc2dbd849 feat: database schema, migrations, and seed data 2026-04-18 22:43:16 +02:00
Jonathan
b2338f815a feat: Welcome page (index) — greeting, framing, nav cards 2026-04-18 16:50:42 +02:00
Jonathan
918231f5f2 fix: style-guide polish — section labels, representative content, emphasis scope 2026-04-18 16:42:54 +02:00
Jonathan
13a0d32dda fix: style-guide page actually applies tokens and fonts 2026-04-18 16:22:09 +02:00
Jonathan
31070c69b2 feat: add /style-guide route for design review 2026-04-18 16:17:17 +02:00
Jonathan
e099d26ee4 refactor: move inline styles into scoped <style> blocks 2026-04-18 16:10:00 +02:00
Jonathan
a7131e0f79 wip: scaffold and index before style-guide 2026-04-18 16:09:49 +02:00