refactor(server): migrate board view to ForgePort #276

Merged
charles merged 3 commits from refactor/migrate-board-to-forgeport into main 2026-04-23 21:18:17 +00:00
Collaborator

Second per-consumer Forgejo migration after pipeline (#275). Board shares the same shape surface — IssueListEntry, PullRequestSummary, PullReview, WorkflowRunSummary — so nothing new needed from the port.

Depends on #275. The assignee merge + CI timed_out / action_required fold + milestone forwarding fixes in #275 are implicitly relied on by this migration. Merge order: #275 → this.

Changes

board.ts

  • BoardDeps signatures now take ForgePort shapes; PROD_DEPS builds a ForgejoAdapter per call
  • foldCiState rewritten against the port's resolved status enum — no more status === "completed" && conclusion === "..." dance
  • foldReviewState reads lowercase "approved" / "changes_requested" and takes string[] for requested reviewers
  • fetchPrStateForRepo reads domain fields (pr.headSha, detail.requestedReviewers, detail.mergeable)
  • isUserStory / labelNames collapse to trivial string-array ops
  • assigneeLogin simplifies to issue.assignees[0] ?? null since the adapter merges singular assignee into the plural array
  • Dropped the !i.pull_request filter; api.listRepoIssues already sets type=issues server-side

Tests

  • board.test.ts fixture helpers return ForgeIssue / ForgePullRequest / ForgeReview / ForgeWorkflowRun
  • New pr(), workflowRun(), review() builders keep per-test fixtures terse (same pattern as pipeline test)
  • All 11 board tests pass

Checks

  • bunx tsc --noEmit -p apps/server/tsconfig.json — EXIT=0
  • bun --cwd apps/server test — 1006 pass / 4 pre-existing fails (same as main)

Remaining migrations

  • webhook-handlers.ts — core event dispatch
  • webhook-ci.ts — CI state machine
  • deps.ts — dependency graph, DI pattern
  • janitor.ts — impl-seam pattern, 15 fns

agent-runner.ts + foreman.ts → Claude SDK (Phase 5).

Second per-consumer Forgejo migration after pipeline (#275). Board shares the same shape surface — `IssueListEntry`, `PullRequestSummary`, `PullReview`, `WorkflowRunSummary` — so nothing new needed from the port. **Depends on #275.** The assignee merge + CI `timed_out` / `action_required` fold + milestone forwarding fixes in #275 are implicitly relied on by this migration. Merge order: #275 → this. ## Changes ### `board.ts` - `BoardDeps` signatures now take `ForgePort` shapes; `PROD_DEPS` builds a `ForgejoAdapter` per call - `foldCiState` rewritten against the port's resolved status enum — no more `status === "completed" && conclusion === "..."` dance - `foldReviewState` reads lowercase `"approved"` / `"changes_requested"` and takes `string[]` for requested reviewers - `fetchPrStateForRepo` reads domain fields (`pr.headSha`, `detail.requestedReviewers`, `detail.mergeable`) - `isUserStory` / `labelNames` collapse to trivial string-array ops - `assigneeLogin` simplifies to `issue.assignees[0] ?? null` since the adapter merges singular `assignee` into the plural array - Dropped the `!i.pull_request` filter; `api.listRepoIssues` already sets `type=issues` server-side ### Tests - `board.test.ts` fixture helpers return `ForgeIssue` / `ForgePullRequest` / `ForgeReview` / `ForgeWorkflowRun` - New `pr()`, `workflowRun()`, `review()` builders keep per-test fixtures terse (same pattern as pipeline test) - All 11 board tests pass ## Checks - `bunx tsc --noEmit -p apps/server/tsconfig.json` — EXIT=0 - `bun --cwd apps/server test` — 1006 pass / 4 pre-existing fails (same as main) ## Remaining migrations - `webhook-handlers.ts` — core event dispatch - `webhook-ci.ts` — CI state machine - `deps.ts` — dependency graph, DI pattern - `janitor.ts` — impl-seam pattern, 15 fns `agent-runner.ts` + `foreman.ts` → Claude SDK (Phase 5).
refactor(server): migrate pipeline to ForgePort; port now covers pipeline surface
All checks were successful
qa / qa (pull_request) Successful in 4m8s
qa / dockerfile (pull_request) Successful in 10s
4d2ce71a5e
Pipeline was the biggest shape-leak site — IssueListEntry, PullRequestSummary,
PullReview, WorkflowRunSummary all bled into the view derivation. This
commit switches pipeline.ts and its 3 test files to the domain shapes from
ForgePort.

### Port extensions surfaced by pipeline usage

- `ForgeReview.stale` — Forgejo flags stranded pending requests stale;
  webhook-ci bounce path (future migration) needs this
- `ForgeWorkflowRun.htmlUrl` — pipeline picks the newest run's URL as
  the stage's "click to see CI" link
- `ReviewState` now includes `request_review` — distinct from submitted
  reviews with state "pending"; the review-stage stall detection needs
  this to count pending reviewer requests separately

### pipeline.ts changes

- `PipelineDeps` signatures take ForgePort shapes; `PROD_DEPS` builds a
  `ForgejoAdapter` per call and delegates
- Adapter's `listIssues` filters out PR-backed rows, so pipeline no
  longer needs the `!i.pull_request` guard
- Derivation logic reads domain fields: `r.reviewer` / `r.state` /
  `r.commitSha` / `r.submittedAt` instead of `r.user.login` /
  raw Forgejo strings / `r.commit_id` / `r.submitted_at`
- Review-state comparisons use the lowercase enum: `"approved"`,
  `"changes_requested"`, `"request_review"`
- Workflow run status folding rewritten — the port's status field is
  already the terminal resolution (no separate `conclusion`), so the
  aggregate logic is simpler
- `foldWorkflowRuns` no longer reads `conclusion`; the port's status
  enum ("success" | "failure" | "in_progress" | …) encodes what
  conclusion used to

### Tests

- `pipeline.test.ts` / `pipeline-stall.test.ts` fixture helpers now
  construct ForgeIssue / ForgePullRequest / ForgeReview / ForgeWorkflowRun
- Added `review()` and `workflowRun()` fixture builders to keep per-test
  fixtures terse
- CI fixtures drop `{status: "completed", conclusion: "success"}` in
  favour of `workflowRun({status: "success"})` — the port's resolved
  status eliminates the two-field dance

Full server suite: 990 pass / 4 pre-existing fails (3 sweeper JSONL
pruning + 1 foreman listSessions flaky). Zero regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
refactor(server): address PR #275 review — milestone forwarding, CI conclusion folding, assignee merge
All checks were successful
qa / qa (pull_request) Successful in 4m10s
qa / dockerfile (pull_request) Successful in 6s
860f46a2ca
Three correctness fixes plus dedicated adapter tests. No runtime behavior
change outside the fixed paths.

### Milestone filter now forwards to Forgejo (critical)

`PROD_DEPS.listRepoIssues` in `pipeline.ts` accepted a numeric milestone
id but never forwarded it, while the pipeline cache key still included
milestone — so different milestones produced identical cache entries
and no server-side filtering. Fix:

- `ForgePort.listIssues` opts now accept `milestone: number | string`
- Numeric id → forwarded to `api.listRepoIssues` as `milestones=<id>`
  (matches Forgejo's server-side query param)
- String title → client-side filter on `milestone.title` (unchanged
  behavior for that branch)
- `PROD_DEPS.listRepoIssues` forwards the milestone when present

### CI `timed_out` / `action_required` fold to failure (critical)

`toForgeWorkflowRun` had no case for these two terminal-failure
conclusions; both fell to `"unknown"`, which pipeline treated as
pending. Users saw "CI running" on timed-out workflows until the stall
threshold fired. Fix: fold both to `"failure"` in the mapper — matches
the pre-migration `foldWorkflowRuns` semantics.

### Singular `assignee` merged into `assignees` array

Forgejo populates both the deprecated singular `assignee` and the plural
`assignees` field; on older rows only the singular is set. `toForgeIssue`
now merges so consumers never have to check both. Pipeline's
`issue.assignees[0] ?? null` is now equivalent to the old
`issue.assignee?.login ?? issue.assignees?.find(...)` two-step.

### New test file: forgejo-adapter.test.ts

17 tests covering the three fixed paths plus `parseRepo` guard
regressions from PR #274 review. Uses a minimal fetch spy — no
`mock.module`, no leak risk to sibling suites.

Server suite: 1006 pass / 4 pre-existing fails.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
refactor(server): migrate board view to ForgePort
All checks were successful
qa / qa (pull_request) Successful in 4m7s
qa / dockerfile (pull_request) Successful in 7s
b2fd3221d7
Second per-consumer Forgejo migration after pipeline (PR #275). Board
shares the same shape surface as pipeline — IssueListEntry,
PullRequestSummary, PullReview, WorkflowRunSummary — so the port
additions that landed with pipeline already carry everything board needs.

### board.ts changes

- `BoardDeps` signatures take ForgePort shapes; `PROD_DEPS` builds a
  `ForgejoAdapter` per call
- `foldCiState` rewritten — reads the port's resolved status enum
  instead of the two-field (`status`/`conclusion`) Forgejo shape
- `foldReviewState` switched to lowercase enum (`"approved"`,
  `"changes_requested"`) and string-array `requestedReviewers`
- `fetchPrStateForRepo` reads `pr.headSha` / `detail.requestedReviewers`
  / `detail.mergeable` instead of raw Forgejo field names
- `isUserStory` / `labelNames` now trivial string-array ops
- `assigneeLogin` simplified to `issue.assignees[0] ?? null` — the
  adapter merges singular `assignee` into the plural array
- Dropped the `!i.pull_request` filter — `api.listRepoIssues` already
  sets `type=issues` server-side

### Tests

- `board.test.ts` fixture helpers now construct `ForgeIssue` /
  `ForgePullRequest` / `ForgeReview` / `ForgeWorkflowRun`
- Added `pr()`, `workflowRun()`, `review()` builders (same pattern as
  pipeline test) so per-test fixtures stay terse
- PR fixtures use domain field names (`mergeable: true`, `requested: ["reviewer"]`)
- Full board test suite: 11/11 pass

Full server suite: 1006 pass / 4 pre-existing fails. Zero regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
charles deleted branch refactor/migrate-board-to-forgeport 2026-04-23 21:18:17 +00:00
Sign in to join this conversation.
No reviewers
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
charles/claude-hooks!276
No description provided.