Render home and project pages with the design system #5

Merged
arne merged 9 commits from feat/orbit-design-system-port into main 2026-04-05 15:34:28 +02:00
Owner

PR #4 committed to orbit's visual direction in design/ but left the
live web layer on its MVP-era generic styles. This PR ports the
styleguide into production: web/static/orbit.css is now the design
system, web/templates/* render with the new components, and both pages
reflect the new flow-based structure.

Home page

A single column of .project-row entries ordered by last-activity
descending. Each row shows the project slug and name, a one-line summary
of the most recent history entry, and a relative time (3h, 2d, 1w).
The leading dot glows sodium if the project moved within the last 7 days,
muted grey otherwise.

Project detail page

Five flow sections — Ideas, Design, Build, Review,
History — with the mapping specified in #4's spec. Empty stages
collapse. History is grouped by calendar day with .day-divider headers,
and entries beyond the first 12 collapse into a <details class="entry-expand"> with show N earlier entries as the summary.

Activity helpers

New web/activity.go module exposes ActiveWindow, isActive(t time.Time), and projectLastActivity(project, ideas, issues, history).
The latter aggregates timestamps across all child entities so a project's
dot state reflects its most recent movement across any flow domain.

Font loading

Iosevka .woff2 files vendored under web/static/fonts/ and loaded via
<link rel="preload"> in web/templates/layout.html, matching the
design spec's FOUT-elimination approach.

Also: UTC timestamp bugfix

While evaluating this PR live against the production orbit instance we
hit a latent sync bug: sync.go and api/webhooks.go were calling
time.Parse(time.RFC3339, ...) which returns a time.Time with a
fixed-offset zone. The modernc.org/sqlite driver serializes those via
Time.String() (producing "2026-04-05 13:11:46 +0200 +0200") and then
cannot scan them back into time.Time. Store tests passed because they
used .UTC(), but production data was unreadable once the new home and
project handlers started reading Issue.ForgejoUpdatedAt for the
activity rule.

Fixed at the store boundary in UpsertIssue: every forgejoUpdatedAt
is normalized with .UTC() before hitting the driver. The fix is bundled
into this PR because it blocked live evaluation.

Known cuts

Per-issue triage between Ideas/Design/Build stages (no schema yet),
open-PR tracking for the Review stage (addressed in #13), and design-spec
last-activity from file mtime (designs.File doesn't expose one) — all
deferred to follow-up work.

PR #4 committed to orbit's visual direction in `design/` but left the live web layer on its MVP-era generic styles. This PR ports the styleguide into production: `web/static/orbit.css` is now the design system, `web/templates/*` render with the new components, and both pages reflect the new flow-based structure. ## Home page A single column of `.project-row` entries ordered by last-activity descending. Each row shows the project slug and name, a one-line summary of the most recent history entry, and a relative time (`3h`, `2d`, `1w`). The leading dot glows sodium if the project moved within the last 7 days, muted grey otherwise. ## Project detail page Five flow sections — **Ideas**, **Design**, **Build**, **Review**, **History** — with the mapping specified in #4's spec. Empty stages collapse. History is grouped by calendar day with `.day-divider` headers, and entries beyond the first 12 collapse into a `<details class="entry-expand">` with `show N earlier entries` as the summary. ## Activity helpers New `web/activity.go` module exposes `ActiveWindow`, `isActive(t time.Time)`, and `projectLastActivity(project, ideas, issues, history)`. The latter aggregates timestamps across all child entities so a project's dot state reflects its most recent movement across any flow domain. ## Font loading Iosevka `.woff2` files vendored under `web/static/fonts/` and loaded via `<link rel="preload">` in `web/templates/layout.html`, matching the design spec's FOUT-elimination approach. ## Also: UTC timestamp bugfix While evaluating this PR live against the production orbit instance we hit a latent sync bug: `sync.go` and `api/webhooks.go` were calling `time.Parse(time.RFC3339, ...)` which returns a `time.Time` with a fixed-offset zone. The `modernc.org/sqlite` driver serializes those via `Time.String()` (producing `"2026-04-05 13:11:46 +0200 +0200"`) and then cannot scan them back into `time.Time`. Store tests passed because they used `.UTC()`, but production data was unreadable once the new home and project handlers started reading `Issue.ForgejoUpdatedAt` for the activity rule. Fixed at the store boundary in `UpsertIssue`: every `forgejoUpdatedAt` is normalized with `.UTC()` before hitting the driver. The fix is bundled into this PR because it blocked live evaluation. ## Known cuts Per-issue triage between Ideas/Design/Build stages (no schema yet), open-PR tracking for the Review stage (addressed in #13), and design-spec last-activity from file mtime (`designs.File` doesn't expose one) — all deferred to follow-up work.
- Template used relative 'design-preview/' href which resolved to /projects/design-preview/ instead of /projects/{slug}/design-preview/ since the project URL has no trailing slash. Rewrite button and design-spec hrefs as absolute paths including .Project.Slug.

- UpsertIssue stored forgejoUpdatedAt via time.Parse(RFC3339) which returns a fixed-offset zone; modernc.org/sqlite serializes that via Time.String() as '2006-01-02 15:04:05 +0200 +0200' and then cannot scan it back. Normalize to UTC inside the store layer so both sync.go and webhooks.go paths round-trip. Latent bug: exposed by the new home/project handlers that read ForgejoUpdatedAt for activity computation.
arne merged commit 0ab37d048d into main 2026-04-05 15:34:28 +02:00
arne changed title from feat: port design system to web layer (home + project detail) to Render home and project pages with the design system 2026-04-05 22:08:50 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
arne/orbit!5
No description provided.