posts are markdown files with yaml frontmatter (title, slug, date;
optional draft/public) in the grenade/blog repo. the worker's new
BlogSource polls the repo — one branch-tip request when nothing
changed — and upserts posts into events with source='blog' and
occurred_at from the frontmatter date, so imported posts keep their
original publish dates and backfill the contribution graph.
- new /v1/blog and /v1/blog/{slug} endpoints over the existing
EventReader port; drafts stay hidden via the public gate
- new /blog and /blog/:slug routes, nav link, activity-feed entry
with post icon and filter toggle; relative image srcs resolve to
gitea raw urls
- shared Markdown component extracted from ProjectPage
- vite proxy target overridable via API_PROXY_TARGET for local dev
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wires two historical sources for completeness with the 2019 timeline:
- hg-edge.mozilla.org: scans json-pushes for a configured set of
build/* repos and matches changeset author client-side, since the
pushlog `user=` filter targets the pusher (sheriffs/reviewers in
this case) rather than the author. Daily poll cadence — mozilla
retired hg, no new events expected.
- bugzilla.mozilla.org: queries /rest/bug?creator=<email>. Without
an api key the unauthenticated endpoint only returns public bugs,
which is what the public timeline wants anyway.
Reshape renders "<author> committed <short_node> in <repo>" for hg
and "filed bug #<id> in <product>" for bugzilla, both linking back
to the canonical upstream URL via a stamped `_host` payload field.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hits /api/v1/users/{user}/activities/feeds?only-performed-by=true
on the configured gitea host (default git.lair.cafe). Page-1 polling
on a 10-min cadence; first run paginates back through up to 20
pages (1000 items) to seed history.
Gitea has no ETag support on this endpoint, so each tick is a fresh
fetch — relying on idempotent upsert by `gitea:<id>` for dedup.
Reshape covers the gitea op_type set:
commit_repo → "pushed N commits to repo:branch" + commits body,
parsing the JSON-encoded `content` field
push_tag → "tagged X in repo"
create_repo → "created repo"
rename/transfer/delete_branch/delete_tag/star/fork — straightforward
create/close/reopen_issue → "{verb} issue #N in repo: title"
create/close/reopen_pull_request → "{verb} pull request #N"
merge_pull_request → GitMerge icon
comment_issue, comment_pull → markdown body from comment.body
approve/reject_pull_request, publish_release
fallback for anything else (mirror_sync_*, future op_types)
Issue / PR / release events use gitea's pipe-separated
`<index>|<title>` content field; pushes have JSON-encoded content.
Host stamping: parse_gitea_event injects `_host` into each row's
payload so the reshape layer can construct web URLs without a
config dependency. Multi-host gitea would still work as long as
each source instance has its own host configured.
Worker config:
GITEA_HOST default git.lair.cafe
GITEA_USER default grenade
GITEA_TOKEN optional (raises rate limit; required
for private repo activity to surface)
GITEA_POLL_INTERVAL_SECS default 600
Tests: +2 in moments-data (commit_repo parses, private flag
captured), +4 in moments-core (commit_repo with body, create_issue
pipe-content, merge icon swap, fallback) — 27 total green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GET /v1/events now returns the presentation form rather than raw
upstream payloads. The frontend stays dumb: it renders title /
subtitle / body segments and picks an icon from a small kebab-case
enum. Title and subtitle are arrays of {text} | {text, url} segments
so the UI can interleave plain copy with anchors without parsing.
New entities (in moments-entities):
TimelineItem — id, source, action, occurred_at, icon, title, subtitle, body
TitleSegment — Text | Link
TimelineBody — Markdown | Commits | Links
CommitSummary — sha, short_sha, message, url, author
TimelineIcon — kebab-case enum; UI falls back to Generic on unknowns
Reshape lives in moments-core::presentation, dispatched by source.
github.rs covers the event types observed on grenade's feed:
PushEvent, PullRequest{,Review,ReviewComment}Event, Issues{,Comment}Event,
Create/Delete/Fork/Watch/Release/CommitComment/PublicEvent.
Anything else falls back to a generic "<action> on <repo>" line.
Other sources (gitea, hg, bugzilla) currently use a stub fallback;
they get their own reshape modules in steps 5 and 6.
4 unit tests cover the load-bearing cases (push commit list, merged
PR icon swap, issue-comment markdown body, unknown-event fallback).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>