Files
ForgeBucket/CHANGELOG.md

414 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Changelog
All notable changes to ForgeBucket are documented here.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [Unreleased]
### Planned — Phase 5 (Deployment Promotions + Rollback Visualization)
- Deployment promotion workflows (manual + automated)
- Rollback visualization and timeline
---
## [1.0.0] — 2026-05-13
Phase 4 complete. SBOM generation, secret scanning, dependency vulnerability scanning, signed artifacts, and OCI registry are operational.
### Added — SBOM Generation (`internal/domain/sbom/`)
- **`Generator`** — subscribes to `pipeline.completed` events and auto-generates CycloneDX 1.4 SBOM documents for every successful pipeline run; also supports on-demand generation via `GenerateOnDemand`
- **6 manifest parsers**: `go.mod`, `package.json`, `requirements.txt`, `Cargo.toml`, `Gemfile.lock`, `pom.xml` — lightweight line-scanning, no external parser dependencies
- **API endpoints** — `GET /sbom`, `GET /sbom/document`, `GET /runs/{runID}/sbom`, `GET /runs/{runID}/sbom/document`, `POST /sbom/generate?ref=&runID=`
- **Database** — migration `016_sbom` adds `SBOMReport` model with CycloneDX document body
- Automatic generation on pipeline completion now also fires directly from the orchestrator (not solely via NATS), ensuring SBOMs are generated even when NATS is unavailable
### Added — Secret Scanning (`internal/domain/scanning/`)
- **`Scanner`** — subscribes to `push.received` events, scans git diffs against 15 regex patterns for high/medium severity secrets
- **Secret patterns**: AWS keys, GitHub/GitLab tokens, generic API keys, Bearer tokens, Slack tokens, Google API keys, Google service accounts, SSH private keys, JWTs, NPM tokens, PostgreSQL/Redis connection strings, generic passwords
- **API endpoints** — `GET /secrets/leaks`, `POST /secrets/leaks/{leakID}/dismiss` (repo-scoped), `GET /api/v1/secrets/leaks` (global admin)
- **Database** — migration `018_scanning` adds `SecretLeak` model
### Added — Vulnerability Scanning (`internal/domain/vulnscan/`)
- **`Scanner`** — triggers on-demand scans against the OSV API (`api.osv.dev/v1`); supports scanning by PURL or by fetching the latest SBOM and scanning all components
- **OSV client** — HTTP client with 30-second timeout, queries OSV database for CVEs by PURL or ecosystem+name, extracts CVSS scores and fixed version ranges
- **API endpoints** — `GET /vulnerabilities`, `POST /vulnerabilities/scan`, `POST /vulnerabilities/{findingID}/dismiss` (repo-scoped), `GET /api/v1/vulnerabilities` (global admin)
- **Database** — migration `019_vulnscan` adds `VulnerabilityFinding` model
- Findings deduplicated by `(vuln_id, purl, repo_id)`
### Added — Artifact Signing (`internal/domain/signing/`)
- **`KeyStore`** — ECDSA P-256 signing and verification; produces self-verifying `Bundle` carrying payload, signature (ASN.1 DER), and public key PEM
- `Sign(artifactID, name, rawContent)` — computes SHA-256 digest, signs, returns signed `Bundle` with key ID fingerprint
- `Verify(bundleJSON)` — extracts public key from bundle, verifies ECDSA signature, returns `VerifyResult` with key-matching check
- `Generate()` — creates ephemeral ECDSA P-256 key when `ARTIFACT_SIGNING_KEY` env var is unset (logs warning; signatures lost on restart unless persisted)
- **API endpoints** — `GET /artifacts/{artifactID}/signature`, `GET /artifacts/{artifactID}/verify`
- **Database** — migration `015_signing` adds `ArtifactSignature` model
### Added — OCI Registry (`internal/domain/oci/`)
- **`Registry`** — content-addressable on-disk blob store implementing OCI Distribution Spec v1.1
- Storage layout: `{root}/blobs/sha256/<hex>` for blobs, `{root}/uploads/<uuid>` for in-progress uploads
- Full upload session lifecycle: start (POST), append chunk (PATCH), finalize with digest verification (PUT), cancel (DELETE), offset query (GET)
- 13 OCI distribution error codes defined (`ErrBlobUnknown`, `ErrDigestInvalid`, `ErrManifestInvalid`, etc.)
- **API handlers** (`internal/api/handlers/oci.go`, 525 lines) — full `/v2/{name}/{kind}/{ref}` routing: manifest push/get/delete, blob HEAD/get/delete, tag listing, chunked upload
- **Database** — migration `017_oci` adds `OCIRepository`, `OCIManifest`, `OCITag`, `OCIBlob`, `OCIUpload` models
- Registry is consumed by standard OCI tools (Docker, Podman, Skopeo, containerd)
### Added — Unified Security Page (`/repos/:owner/:repo/security`)
- **`RepoSecurityPage`** — single-page view combining SBOM status, secret leak detection, and vulnerability findings
- SBOM section: displays existing SBOM metadata with download button, or "Generate SBOM" form with branch/SHA input
- Secret Leaks section: lists leaks with severity badge, pattern name, commit SHA, ref, match sample, dismiss button
- Vulnerabilities section: lists findings with CVSS severity (CRITICAL/HIGH/MEDIUM/LOW), vuln ID, score, summary, PURL, version, fix suggestion, dismiss button; "Scan now" trigger
- **Route** added in `App.tsx`, nav link added in `RepoPage.tsx` tab bar
### Added — Pipeline Run SBOM Integration
- `PipelineRunPage` shows per-run SBOM section: metadata (components, SHA, generation time) + download button
- "Generate SBOM" button for completed/failed runs that lack one
- `useRunSBOM` / `useGenerateSBOM` hooks in `frontend/src/api/queries/sbom.ts`
- 404 from `useRunSBOM` handled gracefully (returns `null` instead of throwing)
### Added — Database Models
- Migration `016_sbom``SBOMReport` (repoId, runId, sha, format, componentCount, bomDocument, generatedAt)
- Migration `017_oci``OCIRepository`, `OCIManifest`, `OCITag`, `OCIBlob`, `OCIUpload`
- Migration `018_scanning``SecretLeak` (repoId, commitSha, ref, patternName, description, severity, matchSample, dismissed, dismissedBy, dismissedAt, detectedAt)
- Migration `019_vulnscan``VulnerabilityFinding` (repoId, vulnId, purl, version, summary, details, cvssScore, fixedVersion, dismissed, dismissedBy, dismissedAt, detectedAt)
- Migration `020_forgefed` — repository + pull request column updates
### Fixed
- SBOM per-run download endpoint (`/runs/{runID}/sbom/document`) was registered at the wrong router nesting level, causing a route conflict with the `GetLatestDocument` handler. Moved into the correct `/runs/{runID}` route block.
- `username` context key extraction in scanning and vulnerability handlers changed from raw string `"user"` to typed `middleware.ContextKeyUsername`
- Nil-safe `Needs` marshalling in orchestrator job creation
- Nil-safe findings response in vulnerability scan API
- `GenerateOnDemand` SBOM cache key now includes `runID` to prevent per-run generation from being shadowed by prior on-demand generation
---
## [0.9.0] — 2026-05-12
Phase 3F complete. ForgeBucket is now a first-class ActivityPub node — interoperable with Mastodon, Forgejo, and any fediverse server.
### Added — ActivityPub Federation (`internal/domain/federation/`)
- **`GET /.well-known/webfinger`** — resolves `acct:user@domain` to the actor URL; returns `application/jrd+json`
- **`GET /users/{username}`** — returns a JSON-LD actor document (`Person` type) including public key object for HTTP signature verification
- **`POST /users/{username}/inbox`** — receives and dispatches inbound ActivityPub activities; HTTP signature verification enforced in production (skipped in `DEBUG=true` mode for local testing)
- **`GET /users/{username}/outbox`** — serves an `OrderedCollection` (summary on page 0, paginated `OrderedCollectionPage` on page ≥ 1, 20 activities per page)
- **`GET /users/{username}/followers`** — stub `OrderedCollection` (zero items; social graph in Phase 4)
- **`GET /users/{username}/following`** — stub `OrderedCollection`
### Added — HTTP Signatures (`internal/domain/federation/signatures.go`)
- `Sign(req, keyID, privateKeyPEM)` — signs outgoing HTTP requests with RSA-SHA256; covers `(request-target)`, `host`, and `date` headers
- `Verify(r, db, instanceURL)` — parses `Signature` header, resolves sender's public key (local `FederationActor` first, then network fetch via `FetchActor`), verifies RSA-SHA256 digest
### Added — Actor Lifecycle (`internal/domain/federation/actor.go`)
- `GetOrCreate` — lazily creates a `FederationActor` for a local user; generates a fresh RSA-2048 key pair and derives `InboxURL`, `OutboxURL`, `APID` from `INSTANCE_URL`; stable across requests
- `ActorJSON` — returns the JSON-LD document shape expected by all ActivityPub clients
- `APID(instanceURL, username)` — canonical `{instanceURL}/users/{username}` helper
### Added — Follow / Accept Flow (`internal/domain/federation/inbox.go`)
- Incoming `Follow` activities are auto-accepted: remote actor is fetched (or retrieved from cache), an `Accept` activity is signed and delivered to their inbox asynchronously
- Both the inbound `Follow` and outbound `Accept` are persisted to `FederationActivity` for audit
### Added — Remote Actor Cache (`internal/domain/federation/remote.go`)
- `FetchActor` — HTTP GET with `Accept: application/activity+json`, extracts inbox URL and public key PEM, stores in `RemoteActor` table to avoid repeated fetches
- `DeliverActivity` — marshals activity JSON, signs the request, POSTs to recipient inbox with 15-second timeout
### Added — Database Models (migration `014_federation`)
- `FederationActivity` — append-only log of all inbound and outbound activities: `ActorAPID`, `Type`, `ObjectJSON`, `Direction` (inbound/outbound), `RemoteActor`, `Published`
- `RemoteActor` — cache for remote actor documents: `APID` (unique), `InboxURL`, `PublicKey`, `FetchedAt`
---
## [0.8.0] — 2026-05-12
Phase 3E complete. Prometheus metrics, structured health checks, and per-repo operational health are operational.
### Added — Prometheus Metrics (`internal/observability/`)
- `GET /metrics` — Prometheus text format endpoint (standard root-level path for k8s/Prometheus scraping)
- `GET /health` — upgraded from static `{"status":"ok"}` to a structured liveness response:
`{"status":"healthy","checks":{"database":"ok","nats":"ok"},"version":"0.8.0"}`
Returns HTTP 503 when any dependency is degraded
- `internal/observability/metrics.go` — metric definitions:
- `forgebucket_http_requests_total{method,path,status}` — counter
- `forgebucket_http_request_duration_seconds{method,path}` — histogram (Prometheus default buckets)
- `forgebucket_pipeline_runs_total{status}` — counter (succeeded/failed/cancelled), pre-initialized to 0
- `forgebucket_deployments_total{status}` — counter (pending/success/failure/cancelled), pre-initialized to 0
- `forgebucket_active_pipeline_runs` — gauge (in-flight runs)
- `internal/observability/health.go``Check(db, bus)` pings PostgreSQL and calls `bus.Healthy()`
- HTTP instrumentation middleware inserted after `Recoverer`, before `CORS` — records every request
- Path normalization prevents label cardinality explosion: `/repos/alice/myrepo/runs/42`
`/api/v1/repos/:owner/:repo/runs/:id`
- NATS metric watcher subscribes to `pipeline.>` and `deployment.>` and increments counters
### Added — Per-Repo Operational Health (`GET /api/v1/repos/{owner}/{repo}/health`)
- Returns a JSON summary for the repo page operational header:
- `ciPassRate7d` — fraction of pipeline runs that succeeded in the last 7 days
- `totalRuns7d` — total run count in the last 7 days
- `latestRun` — most recent `PipelineRun` record
- `latestDeployments` — one entry per environment showing latest deploy (envName, status, sha, finishedAt)
- `openDriftCount` — GitOpsConfigs in `drifted` state
- `openPRCount` — open pull request count
### Added — EventBus `Healthy() bool`
- Added to the `EventBus` interface; `NATSBus` returns `nc.IsConnected()`; `NoOpBus` returns `true`
### Changed — Middleware chain
- `observability.Middleware()` added between `Recoverer` and `CORS` (applies to all requests including `/health` and `/metrics`)
---
## [0.7.0] — 2026-05-12
Phase 3D complete. Git is now the source of truth for environment deployment state.
### Added — GitOps Controller (`internal/domain/gitops/`)
- `controller.go` — starts as a background goroutine; subscribes to `push.received`,
`deployment.succeeded`, `deployment.failed`; runs a periodic reconciliation ticker
(interval configurable via `GITOPS_RECONCILE_INTERVAL`); recovers stale `syncing`
configs to `drifted` on startup
- `drift.go``CheckDrift` calls `git rev-parse` via the existing git domain wrapper;
`handlePush` queries all GitOpsConfigs matching the pushed branch and evaluates drift;
`periodicCheck` iterates configs whose `SyncInterval` has elapsed; publishes
`environment.drift_detected` when drift is found
- `reconciler.go``TriggerSync` creates a `Deployment` record and publishes
`deployment.started` (same lifecycle path as manual deployments, `TriggeredBy="gitops"`);
`handleDeploymentSucceeded` resolves open drift events and marks config `synced` for
both GitOps and manual deployments; `handleDeploymentFailed` reverts to `drifted`
### Added — GitOps HTTP API (`internal/api/handlers/gitops.go`)
All routes live under `/api/v1/repos/{owner}/{repo}/environments/{envName}/gitops/`:
- `GET /gitops` — current GitOpsConfig or 404 if not configured
- `PUT /gitops` — idempotent upsert (branch, autoSync, syncInterval)
- `DELETE /gitops` — remove config without deleting deployments
- `POST /gitops/sync` — manual reconciliation trigger; creates deployment record
- `GET /gitops/drift` — current sync status: syncStatus, desiredSha, actualSha, isDrifted
- `GET /gitops/drift/history` — paginated drift event log (newest first)
- `POST /gitops/drift/{driftID}/acknowledge` — acknowledge without syncing
### Added — Database Models (migration `013_gitops`)
- `GitOpsConfig` — links environment to a branch; tracks `DesiredSHA`, `ActualSHA`,
`SyncStatus` (`unknown/synced/drifted/syncing`), `AutoSync`, `SyncInterval`,
`LastCheckedAt`
- `GitOpsDriftEvent` — append-only drift record: `DesiredSHA`, `ActualSHA`,
`SyncStatus` (`drifted/synced/acknowledged`), `DetectedAt`, `ResolvedAt`
### Added — Supporting Changes
- `git.RevParse(repoPath, ref)` — new function in `internal/domain/git/binary.go`
used by `CheckDrift` to resolve branch HEAD SHA
- `events.DeploymentEvent` + `events.DriftEvent` types added to `internal/events/types.go`
- `EnvironmentHandler.publishDeployEvent` updated to use shared `events.DeploymentEvent`
so the GitOps controller can unmarshal deployment lifecycle events correctly
- `GITOPS_RECONCILE_INTERVAL` env var (default `300`s); `0` disables the periodic ticker
- `ArtifactRoot` config field + `ARTIFACT_ROOT` env var
---
## [0.6.0] — 2026-05-12
Phase 3C complete. Multi-tenant workspaces and a full secret management hierarchy operational.
### Added — Workspaces
- `Workspace` model (migration `011`): globally unique handle, display name, description, avatarUrl
- `WorkspaceMember` model: owner/admin/member roles per workspace
- Repository `workspace_id` column (optional; null = personal repo)
- Full workspace CRUD API: `GET/POST /api/v1/workspaces`, `GET/PATCH/DELETE /api/v1/workspaces/{handle}`
- Workspace member management: list, add, update role, remove
- `GET /api/v1/workspaces/{handle}/repos` — repos in workspace
- Workspace frontend: WorkspacesPage, WorkspacePage, workspace switcher in sidebar header
- Workspace owner selector in repo create flow
### Added — Secret Management (`internal/api/handlers/secret.go`)
- `Secret` model (migration `012`): `Scope` (global/workspace/repo/env), `ScopeID`, `Name`,
`EncryptedValue` (AES-256-GCM, never returned by API)
- Unique constraint on (scope, scope_id, name)
- CRUD at all scope levels:
- `GET/POST/DELETE /api/v1/admin/secrets` (global, admin-only)
- `GET/POST/DELETE /api/v1/workspaces/{handle}/secrets` (workspace-scoped)
- `GET/POST/DELETE /api/v1/repos/{owner}/{repo}/secrets` (repo-scoped)
- `GET/POST/DELETE /api/v1/repos/{owner}/{repo}/environments/{envName}/secrets` (env-scoped)
- `ResolveSecretsForRun(db, repoID, workspaceID, envID, sessionSecret)` — hierarchy
resolution for CI executor: Env > Repo > Workspace > Global
- CI executor updated to inject resolved secrets as Docker `--env` flags
- RepoSecretsPage — write-only UI, values never displayed after creation
- Sidebar "Secrets" nav item in repo context
---
## [0.5.0] — 2026-05-11
Phases 3A and 3B complete. Environments, deployments, and the operational timeline are operational.
### Added — Environments + Deployments (Phase 3A)
- `Environment` model (migration `010`): repoId, name, URL, protectionRules (JSON)
- `Deployment` model: envId, repoId, sha, ref, status lifecycle
(`pending → in_progress → success/failure/cancelled`), triggeredBy, description, runId link
- CRUD API for environments: `GET/POST /environments`, `GET/PATCH/DELETE /environments/{envName}`
- Deployment API: `GET/POST /environments/{envName}/deployments`,
`PATCH /environments/{envName}/deployments/{id}/status`
- NATS events published on status transitions: `deployment.started`, `deployment.succeeded`,
`deployment.failed`
- `EnvironmentsPage` — environment cards each showing latest deployment status, SHA, actor,
and time since deploy; deployment history per env
- Sidebar "Environments" nav item in repo context
- Repo page deployment status badges (latest deploy per env at a glance)
### Added — Unified Operational Timeline (Phase 3B)
- `GET /api/v1/repos/{owner}/{repo}/timeline` — merged chronological feed of commits,
pipeline runs, and deployments; default 60 events, max 200
- `RepoTimelinePage` at `/repos/:owner/:repo/timeline` — vertical event feed with type
filter tabs (all / commits / runs / deployments)
- Sidebar "Timeline" nav item between Environments and Settings
- Answers "what changed before things broke?" without navigating between separate pages
---
## [0.4.0] — 2026-05-11
Phase 2C complete. CI results are legible in the UI; the dashboard is an operational command center.
### Added — Pipeline Visualization
- `PipelinesPage` — cross-repo pipeline runs feed with status filter tabs (all / running / failed / succeeded)
- `RepoPipelinesPage` — repo-scoped runs list at `/repos/:owner/:repo/pipelines`
- `PipelineRunPage` — run detail with topological DAG visualization using real `PipelineJob[]` +
`needs` graph; step log viewer (collapsible per step, ANSI color, auto-scroll with lock toggle)
- `PipelineWaterfall` — rewritten to accept live job data instead of static mock stages
- `GET /api/v1/pipelines/runs` — cross-repo recent runs for the dashboard
### Added — Dashboard CI Command Center
- Dashboard CI widget replaced "coming soon" with live recent pipeline runs
- Dashboard `recentRuns[]` field added to the `/api/v1/dashboard` response
### Added — Command Palette Wiring
- Pipeline run results surfaced in command palette results
- "Pipelines" quick-nav action
---
## [0.3.0] — 2026-05-11
Phase 2B complete. Full CI/CD execution backend operational.
### Added — CI Orchestrator (`internal/domain/ci/`)
- DAG-based pipeline orchestrator (`orchestrator.go`): subscribes to NATS `push.received`,
parses `.forgebucket/workflows/*.yml`, creates `PipelineRun/Job/Step` records, advances
DAG on `job.completed/failed`, recovers stale runs on startup
- Docker executor (`executor.go`): steps run in isolated containers (`docker run --rm`),
logs stream to DB and NATS via `pipeline.log`, workspace extracted via `git archive`
- Runner manager (`runner_manager.go`): semaphore-limited (default 4 concurrent),
subscribes to `job.queued`, skips gracefully if Docker is unavailable
- DAG engine (`dag.go`): `TopoSort`, `ReadyJobs`
- Workflow parser (`parser.go`): `.forgebucket/workflows/*.yml` from git ref,
`MatchesPushTrigger` with glob branch patterns; `StringOrSlice` YAML unmarshaler
### Added — CI API Handlers
- `GET /api/v1/repos/:owner/:repo/pipelines` — pipeline definitions
- `GET /api/v1/repos/:owner/:repo/runs` — pipeline runs (newest first)
- `GET /api/v1/repos/:owner/:repo/runs/:runID` — run detail with job + step tree
- `POST /api/v1/repos/:owner/:repo/runs/:runID/cancel`
- `POST /api/v1/repos/:owner/:repo/runs/:runID/jobs/:jobID/retry`
- `GET /api/v1/repos/:owner/:repo/runs/:runID/jobs/:jobID/logs` — step log chunks
- `GET/POST /api/v1/repos/:owner/:repo/runs/:runID/artifacts`
- `GET /api/v1/repos/:owner/:repo/artifacts/:artifactID/download` — path-traversal guarded
- `GET/POST /api/v1/admin/runners` — runner list + registration (admin-only, bcrypt token)
### Added — Database Models (migration `009_ci`)
- `Pipeline`, `PipelineRun`, `PipelineJob`, `PipelineStep`, `PipelineStepLog`
- `Runner` (name, labels, status, tokenHash, lastSeenAt)
- `Artifact` (runId, repoId, name, storagePath, size, contentType)
### Changed — Git HTTP handler
- `parseAndCheckBody` replaces `checkProtectionsFromBody` — now also returns parsed
`refUpdate` structs for publishing `push.received` after each successful receive-pack
---
## [0.2.0] — 2026-05-11
Phase 2A complete. Real-time event infrastructure and audit log operational.
### Added — NATS Event Bus (`internal/events/`)
- `EventBus` interface: `Publish`, `Subscribe`, `Close`
- `NATSBus`: NATS-backed with auto-reconnect; `NoOpBus` fallback when `NATS_URL` unset
- `New(url)` factory: returns `NATSBus` or `NoOpBus`
- 40+ event subjects in `subjects.go` covering repo, push, PR, issue, pipeline, job,
deployment, environment, and audit namespaces
### Added — WebSocket Hub
- `GET /ws` — NATS wildcard subscription (`>`) fans all events to connected clients as JSON
- `{ subject, payload }` envelope format
- Goroutine per client with buffered send channel (64 events); slow clients drop events
### Added — Audit Log (migration `008_audit_log`)
- `AuditLog` model: actorId, actorName, method, path, statusCode, ipAddress, userAgent
- Middleware records every POST/PUT/PATCH/DELETE in the protected route group
- Writes DB row + publishes `audit.event` asynchronously (never blocks the response)
- `GET /api/v1/audit` — paginated, filterable by actor/method/since (admin-only)
---
## [0.1.0] — 2026-05-11
Initial development milestone. Core Git hosting, collaboration, and frontend SPA functional.
### Added — Authentication & Security
- User registration and login with secure session cookies
- CSRF protection via double-submit cookie pattern (`X-CSRF-Token`)
- SSH key management per user
- OIDC / OAuth2 optional integration
- Scoped access tokens with optional expiration
- Repository deploy keys (read-only or read-write)
- ENV-driven config with fail-fast on missing secrets
### Added — Git Hosting
- Smart HTTP transport (clone, push, pull over HTTP)
- AGit protocol (`refs/for/` push for instant PR creation)
- Branch management, commit log, diff viewing
- Git LFS per-repository (configurable file size limits)
- Branch protection rules (force-push blocking)
- Repository visibility (public / private)
### Added — Collaboration
- Pull requests (open / merged / closed) with author tracking
- Issues (open / closed)
- Reviewer assignment (default reviewer per repo, per-PR overrides)
- Merge strategy selection per repository (merge / squash / rebase)
- Branching model configuration (feature / bugfix / release / hotfix prefixes)
- PR default description templates + excluded-files configuration
- Webhook system with event filtering (push, pull_request, issue)
- Repository member RBAC (read / write / admin)
### Added — Frontend SPA
- React 18 + TypeScript + Vite, embedded into Go binary via `//go:embed`
- 20 route-level pages covering auth, dashboard, repos, code, PRs, issues, and settings
- Triple-state sidebar: expanded (320px) / collapsed (56px) / mobile bottom bar
- Mobile-first responsive design (375px → 1440px)
- DiffViewer (side-by-side + unified), MobileComment (bottom-sheet), TreeBrowser
### Added — Design System
- Custom semantic token palette in `frontend/src/ui/tokens.ts`
- Full dark/light mode via Tailwind CSS v4 `@variant dark`
- 8px grid system; 44px minimum touch targets (WCAG 2.5.5)
- System font stack (Segoe UI, Roboto, sans-serif)
### Added — Infrastructure
- PostgreSQL + XORM with migrations 001007
- ActivityPub actor data model (FederationActor) — data layer only
- Docker Compose for local PostgreSQL + NATS
- Makefile: dev, build, migrate, test, lint, docker-up
---
[Unreleased]: https://github.com/forgeo/forgebucket/compare/v1.0.0...HEAD
[1.0.0]: https://github.com/forgeo/forgebucket/compare/v0.9.0...v1.0.0
[0.9.0]: https://github.com/forgeo/forgebucket/compare/v0.8.0...v0.9.0
[0.8.0]: https://github.com/forgeo/forgebucket/compare/v0.7.0...v0.8.0
[0.7.0]: https://github.com/forgeo/forgebucket/compare/v0.6.0...v0.7.0
[0.6.0]: https://github.com/forgeo/forgebucket/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/forgeo/forgebucket/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/forgeo/forgebucket/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/forgeo/forgebucket/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/forgeo/forgebucket/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/forgeo/forgebucket/releases/tag/v0.1.0