Skip to content

feat(frontend): typed API client generated from the OpenAPI spec + gen-verify CI#59

Open
AgentWrapper wants to merge 1 commit into
mainfrom
feat/projects-openapi-codegen
Open

feat(frontend): typed API client generated from the OpenAPI spec + gen-verify CI#59
AgentWrapper wants to merge 1 commit into
mainfrom
feat/projects-openapi-codegen

Conversation

@AgentWrapper
Copy link
Copy Markdown
Contributor

@AgentWrapper AgentWrapper commented May 31, 2026

Why (scope change)

This PR originally added the code-first OpenAPI generator + a frontend client. The backend generator has since landed on main (via #68/#65) and now covers projects and sessions — superseding this branch's backend work. Rather than duplicate it, this PR is repurposed to the parts main still lacks.

What's here

  • frontend/src/api/schema.d.ts — generated from backend/internal/httpd/apispec/openapi.yaml via openapi-typescript (npm run gen:api). Covers projects, sessions, and orchestrators.
  • frontend/src/api/client.ts — a small openapi-fetch client typed by that schema, so the renderer's request/response types come from the daemon contract instead of being hand-maintained.
  • gen-verify CI job — regenerates the spec from Go and the TS client from the spec, failing if either committed artifact is stale. Backend drift is already covered by the apispec tests; this additionally guards the frontend artifact, which nothing else checks.

Verification

frontend: npm run gen:api ✓   npm run typecheck ✓
pipeline: go generate ./... + npm run gen:api reproduce the committed files with no drift

Rebased on current main; no overlap with the backend codegen or the recent CLI PRs.

🤖 Generated with Claude Code

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 31, 2026

Greptile Summary

This PR introduces a typed openapi-fetch client for the Electron renderer, a generated schema.d.ts from the backend's OpenAPI YAML, and a gen-verify CI job that re-runs the full code-first pipeline and fails if committed artifacts are stale.

  • frontend/src/api/client.ts + schema.d.ts: openapi-fetch client parameterized by the generated path and component types; eliminates hand-maintained request/response types and keeps the frontend in sync with the daemon contract via the CI gate.
  • .github/workflows/go.yml: New gen-verify job runs go generate ./... then npm run gen:api, then git diff --exit-code to catch any drift between Go types, the OpenAPI spec, and the TS schema; also widens the workflow trigger to include frontend/**.

Confidence Score: 4/5

Safe to merge with one fix: the hardcoded daemon port needs to be read from the runfile before the client is wired up to real callers.

The client hardcodes http://127.0.0.1:3001 but the runfile package was explicitly designed to publish the daemon's actual bound port to the Electron main process — if 3001 is occupied and the daemon binds elsewhere, every API call will target a dead address. The CI job and the generated schema are clean.

frontend/src/api/client.ts — the baseUrl should derive the port from the runfile rather than hardcoding 3001.

Important Files Changed

Filename Overview
frontend/src/api/client.ts Typed openapi-fetch client wrapping the generated schema; baseUrl is hardcoded to port 3001 rather than being read from the runfile port-discovery mechanism that already exists in the backend.
frontend/src/api/schema.d.ts Auto-generated TypeScript types from the OpenAPI spec covering projects, sessions, and orchestrators. No manual changes; safe as-is.
.github/workflows/go.yml Adds gen-verify CI job that regenerates openapi.yaml and schema.d.ts end-to-end and fails on any diff; also extends the workflow trigger to include frontend/** changes. Logic is correct.
frontend/package.json Adds openapi-fetch runtime dependency and openapi-typescript dev dependency, plus gen:api script; dependency placement is correct.

Sequence Diagram

sequenceDiagram
    participant Dev as Developer
    participant Go as Go Backend
    participant Spec as openapi.yaml
    participant TS as schema.d.ts
    participant CI as gen-verify CI

    Dev->>Go: modify contract type
    Dev->>Go: go generate ./...
    Go->>Spec: regenerate openapi.yaml
    Dev->>TS: npm run gen:api
    Spec->>TS: regenerate schema.d.ts
    Dev->>CI: push / open PR
    CI->>Go: go generate ./...
    Go->>Spec: regenerate openapi.yaml
    CI->>TS: npm run gen:api
    Spec->>TS: regenerate schema.d.ts
    CI->>CI: git diff --exit-code
    alt committed files match regenerated output
        CI-->>Dev: pass
    else committed files are stale
        CI-->>Dev: fail — regenerate and commit
    end
Loading

Reviews (5): Last reviewed commit: "feat(frontend): typed API client generat..." | Re-trigger Greptile

Comment thread backend/internal/httpd/controllers/dto.go
Comment thread backend/internal/httpd/apispec/specgen/build.go
Comment thread backend/internal/httpd/apispec/specgen/build.go Outdated
Comment thread backend/internal/httpd/controllers/dto.go
AgentWrapper pushed a commit that referenced this pull request May 31, 2026
…chema names

Resolve the four review comments on #59:

- ProjectOrDegraded.MarshalJSON now errors when neither Project nor Degraded
  is set instead of silently emitting `{"project": null}`, which would breach
  the required oneOf[Project, Degraded] contract.
- requestBody.required: true is now set for addProject and updateProjectConfig
  via WithCustomize — swaggest left it absent (== optional) before.
- schemaName replaces the TrimPrefix catch-all with an exhaustive default→clean
  map; an unrecognised type is returned verbatim so it surfaces in the diff
  rather than silently colliding with an existing schema.
- nonNullableSlices strips the spurious "null" swaggest unions into every Go
  slice, so `projects` is `Summary[]` not `Summary[] | null`; the list handler
  normalises a nil slice to [] so the wire matches the non-nullable schema.

Regenerated openapi.yaml + frontend schema.d.ts. Refs #59.
@AgentWrapper AgentWrapper force-pushed the feat/projects-openapi-codegen branch from 0de7ce0 to 49c7883 Compare May 31, 2026 22:12
Comment thread backend/internal/httpd/controllers/dto.go
AgentWrapper pushed a commit that referenced this pull request May 31, 2026
Addresses the P1 follow-up on #59: returning an error from
ProjectOrDegraded.MarshalJSON does not "surface" the contract violation —
envelope.WriteJSON discards the encode error after the 200 status and a partial
JSON frame have already been flushed, leaving the client with a truncated,
unparseable 200 (worse than the previous null).

Validate the invariant upstream instead: newGetProjectResponse now returns an
error when the GetResult sets neither Project nor Degraded, and the get handler
maps that to a 500 envelope before any status/body is written. MarshalJSON keeps
the error branch only as an unreachable last-resort backstop, with the comment
corrected to say so. Adds TestProjectsAPI_GetEmptyResultIs500 to lock the
clean-500 behavior.

Refs #59.
Copy link
Copy Markdown
Collaborator

@neversettle17-101 neversettle17-101 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes LGTM

neversettle17-101 added a commit that referenced this pull request Jun 1, 2026
…penAPI

- Remove POST /reload, PATCH /{id}, POST /{id}/repair routes and their
  Manager methods (Reload, UpdateConfig, Repair) and DTOs (ReloadResult,
  UpdateConfigInput) — not needed at this stage
- Merge Manager interface into manager.go; delete project.go (single-impl
  split served no purpose)
- Remove dead notImplemented helper from errors.go
- Port PR #59 code-first OpenAPI generation: controllers/dto.go named
  response types, specgen/build.go (4 routes), parity + drift tests,
  cmd/genspec, go generate wiring; regenerate openapi.yaml
- Add swaggest deps; add YAML() method to apispec.Spec

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
AgentWrapper pushed a commit that referenced this pull request Jun 1, 2026
…chema names

Resolve the four review comments on #59:

- ProjectOrDegraded.MarshalJSON now errors when neither Project nor Degraded
  is set instead of silently emitting `{"project": null}`, which would breach
  the required oneOf[Project, Degraded] contract.
- requestBody.required: true is now set for addProject and updateProjectConfig
  via WithCustomize — swaggest left it absent (== optional) before.
- schemaName replaces the TrimPrefix catch-all with an exhaustive default→clean
  map; an unrecognised type is returned verbatim so it surfaces in the diff
  rather than silently colliding with an existing schema.
- nonNullableSlices strips the spurious "null" swaggest unions into every Go
  slice, so `projects` is `Summary[]` not `Summary[] | null`; the list handler
  normalises a nil slice to [] so the wire matches the non-nullable schema.

Regenerated openapi.yaml + frontend schema.d.ts. Refs #59.
AgentWrapper pushed a commit that referenced this pull request Jun 1, 2026
Addresses the P1 follow-up on #59: returning an error from
ProjectOrDegraded.MarshalJSON does not "surface" the contract violation —
envelope.WriteJSON discards the encode error after the 200 status and a partial
JSON frame have already been flushed, leaving the client with a truncated,
unparseable 200 (worse than the previous null).

Validate the invariant upstream instead: newGetProjectResponse now returns an
error when the GetResult sets neither Project nor Degraded, and the get handler
maps that to a 500 envelope before any status/body is written. MarshalJSON keeps
the error branch only as an unreachable last-resort backstop, with the comment
corrected to say so. Adds TestProjectsAPI_GetEmptyResultIs500 to lock the
clean-500 behavior.

Refs #59.
@AgentWrapper AgentWrapper force-pushed the feat/projects-openapi-codegen branch from 63f5daa to 4645ab9 Compare June 1, 2026 16:27
AgentWrapper pushed a commit that referenced this pull request Jun 1, 2026
…penAPI

- Remove POST /reload, PATCH /{id}, POST /{id}/repair routes and their
  Manager methods (Reload, UpdateConfig, Repair) and DTOs (ReloadResult,
  UpdateConfigInput) — not needed at this stage
- Merge Manager interface into manager.go; delete project.go (single-impl
  split served no purpose)
- Remove dead notImplemented helper from errors.go
- Port PR #59 code-first OpenAPI generation: controllers/dto.go named
  response types, specgen/build.go (4 routes), parity + drift tests,
  cmd/genspec, go generate wiring; regenerate openapi.yaml
- Add swaggest deps; add YAML() method to apispec.Spec

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
AgentWrapper pushed a commit that referenced this pull request Jun 1, 2026
* refactor(project): manager talks to the sqlite store; drop the in-memory store

The project Manager now runs only against the durable backend store: remove the
process-local MemoryStore (and NewMemoryManager), and require a real Store. The
daemon already wires the sqlite store; tests now build a real temp-dir sqlite
store instead of the mock.

- Move Row + the Store port to project/store.go. The Store interface stays
  because it is the dependency-inversion port that lets the manager reach the
  backend without an import cycle (storage imports project.Row), not an extra
  mock layer — there is no longer any in-memory implementation.
- NewManager requires a non-nil Store (no in-memory fallback).
- Add project/manager_test.go: List/Add/Get/Remove happy paths +
  PATH_REQUIRED/NOT_A_GIT_REPO/PATH_ALREADY_REGISTERED/ID_ALREADY_REGISTERED,
  PROJECT_NOT_FOUND/INVALID_PROJECT_ID, and UpdateConfig — all against a real
  sqlite store (the service-logic tests #47 lacked).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(project): trim routes, consolidate package, add code-first OpenAPI

- Remove POST /reload, PATCH /{id}, POST /{id}/repair routes and their
  Manager methods (Reload, UpdateConfig, Repair) and DTOs (ReloadResult,
  UpdateConfigInput) — not needed at this stage
- Merge Manager interface into manager.go; delete project.go (single-impl
  split served no purpose)
- Remove dead notImplemented helper from errors.go
- Port PR #59 code-first OpenAPI generation: controllers/dto.go named
  response types, specgen/build.go (4 routes), parity + drift tests,
  cmd/genspec, go generate wiring; regenerate openapi.yaml
- Add swaggest deps; add YAML() method to apispec.Spec

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix(project): address PR review comments

- t.Skipf → t.Fatalf in gitRepo helper: git failures now hard-fail
  instead of silently skipping manager tests on a misconfigured runner
- FindProjectByPath: add AND archived_at IS NULL so archived paths don't
  permanently block re-registration (update queries/projects.sql and
  generated gen/projects.sql.go)
- Add TestManager_ReaddAfterRemove to lock the fix

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fixed lint and fmt

* addressed greptile comments

* Apply suggestions from code review

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* project tests fix

* project_tests fix

* fix: Linting and formatting fix

* refactor: move project manager into service layer (#68)

* refactor: split service package by resource (#68)

* fix: ignore archived project id conflicts (#68)

* refactor: move pr manager into service layer (#68)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: itrytoohard <ayetrytoohard@gmail.com>
@AgentWrapper AgentWrapper force-pushed the feat/projects-openapi-codegen branch from 55e4f5e to 064c188 Compare June 2, 2026 16:17
…n-verify CI

The code-first OpenAPI generator landed on main (covering projects + sessions),
superseding this branch's backend work. This repurposes the PR to the parts main
still lacks:

- frontend/src/api/schema.d.ts — generated from backend/internal/httpd/apispec/
  openapi.yaml via openapi-typescript (`npm run gen:api`); covers projects,
  sessions, and orchestrators.
- frontend/src/api/client.ts — a small openapi-fetch client typed by that schema,
  so the renderer's request/response types come from the daemon contract instead
  of being hand-maintained.
- CI gen-verify job — regenerates the spec from Go and the TS client from the
  spec, failing if either committed artifact is stale. Backend drift is also
  covered by the apispec tests; this additionally guards the frontend artifact,
  which nothing else checks.

Frontend typecheck passes; the gen pipeline reproduces the committed files with
no drift.
@AgentWrapper AgentWrapper force-pushed the feat/projects-openapi-codegen branch from 064c188 to 019c985 Compare June 2, 2026 16:18
@AgentWrapper AgentWrapper changed the title feat(api): code-first OpenAPI generation + typed frontend client feat(frontend): typed API client generated from the OpenAPI spec + gen-verify CI Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants