Skip to content

chore: optimize docs, types, and CI for agentic coding - #271

Merged
0x2E merged 11 commits into
mainfrom
chore/agentic-coding-optimization
Oct 7, 2026
Merged

0x2E merged 11 commits into
mainfrom
chore/agentic-coding-optimization

Conversation

@0x2E

@0x2E 0x2E commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Motivation

Optimize the repo for agentic coding: single source of truth for every fact (API contract, versions, schema), a verification loop CI actually enforces, and docs that answer why instead of duplicating what. The PR went through two adversarial review rounds and one full doc restructure driven by review feedback.

Changes

Contract: code-first instead of a hand-maintained spec (5ac08b8, 9dfebee)

docs/openapi.yaml (1344 lines) had no machine consumers — no codegen, no CI validation; the frontend hand-writes its own types. It is replaced by docs/api-conventions.md (85 lines) recording only cross-cutting rules distributed across handlers: session auth (incl. anonymous mode), response envelopes, cursor grammar (incl. the created_at+before 400 rule), error shape (incl. the three empty-body cases), status-code conventions, bookmark create modes, settings closed sets.

Impact: anyone scripting /api loses the OpenAPI reference; the Fever API contract (fever-api.md) is unaffected.

Frontend types audited field-by-field against Go (1eae219, d2cde86)

types.ts is the contract copy agents read on the frontend side, so it must mirror the backend exactly. Full audit against model.go and the handler request structs:

  • ListAPIResponse split into ListResponse (groups/feeds, no cursor) and PaginatedListResponse (items/bookmarks)
  • CreateBookmarkRequest is now the two server modes ({item_id} vs full snapshot); the sole caller sends item_id only
  • order_by typed to the documented enum (items.ts was sending an out-of-contract value)
  • batch-create errors typed as string[] | null

Wire note: two request bodies changed shape; server-observable behavior is identical in both cases. Both changed paths are e2e-verified (below).

CI blind spot + first frontend unit tests (972b7a0, c6dda9c)

frontend.yml ran only check:i18n — TypeScript was never typechecked in CI and the frontend had zero tests. Now runs typecheck + vitest (23 tests: article-pins store, safe-url sanitizer, feed-error boundary, article-filter set).

Known gap: pnpm lint cannot start on any current typescript-eslint version (TS 7 unsupported). Re-add once upstream support lands. Note: the frontend job was renamed i18n → check; update branch protection if it lists required checks by job name.

Documentation restructured around single-source-of-truth (f9b51b3, 04e270d)

Review feedback: the PR claimed to eliminate divergence but docs still copied code facts — e.g. three docs said "Go 1.25" while go.mod says 1.26. Applied consistently:

  • Version claims replaced by pointers to backend/go.mod; package.json gains engines.node as the Node truth source
  • backend-design: tech-stack table and module tree replaced by stack decisions and package-responsibility notes; endpoint enumeration dropped (SetupRouter is the inventory); env defaults point at .env.example; release checklist folded into CONTRIBUTING
  • frontend-design: tech-stack table and route map dropped; UI-area inventory compressed; the design narrative (URL state, undo pins, data flow) stays
  • docs/README.md now states the doc-writing rules: docs answer why, never copy file-backed facts, one place per fact, code wins on conflict — and AGENTS.md carries the same rule

Entry points and policy (4d08647, 7c60d02)

AGENTS.md documents the verification commands and API fact map; scripts.sh gains test-frontend and a combined test; CONTRIBUTING's checklist matches CI. Contribution policy: issues instead of PRs.

Verification

  • pnpm typecheck, pnpm test (23), check:i18n pass locally
  • Backend untouched; test.yml runs go test ./... (pull_request-triggered)
  • API-level e2e against a real build (local RSS fixture, 18/18 assertions): envelope split verified key-by-key, order_by=pub_date ordering + cursor paging, the created_at+before 400 rule, bookmark {item_id} mode returns full server-side snapshot, snapshot mode, 204 mark-read
  • UI-level e2e in a browser: login → unread list renders correctly through the changed items query; starring via the drawer exercises the changed useCreateBookmark ({item_id}-only) path; state persists across reload (server-side confirmation: bookmark has item_id + full snapshot + feed_id)

0x2E added 11 commits October 2, 2026 00:37
The hand-maintained OpenAPI spec had no machine consumers (no codegen,
no CI validation) and duplicated facts that live in handler code, which
agents read more reliably because it cannot go stale. It is replaced by
a short conventions page that only records cross-cutting rules that are
distributed across handlers (auth, envelopes, cursor grammar, errors,
status codes). Route inventory: SetupRouter; payload shapes: model.go.

Impact: anyone scripting /api loses the OpenAPI reference; the Fever
API contract (fever-api.md) is unaffected.
- Split ListAPIResponse into ListResponse (groups/feeds, no cursor) and
  PaginatedListResponse (items/bookmarks); the old type claimed a
  required next_cursor on lists that never return one.
- CreateBookmarkRequest is now a union of the two server modes
  ({item_id} vs full snapshot); the sole caller now sends item_id only,
  letting the backend snapshot authoritative fields (also removes the
  client-side "Unknown" feed-name fallback, which the server ignored
  anyway whenever item_id was present).
- ListItemsParams.order_by is typed to the documented enum; items.ts
  sent "pub_date:desc", which is outside the contract and only worked
  because the store silently falls back to pub_date DESC.
frontend.yml previously ran only check:i18n, so TypeScript was never
typechecked in CI and the frontend had no unit tests at all. Adds
vitest with tests for the article-pins store (pure view-state logic
behind the unread-view undo pins) plus typecheck/test scripts and CI
steps.

Lint is intentionally not added: eslint cannot start on this repo
because typescript-eslint does not support TypeScript 7.0 (upstream
tracking: typescript-eslint#10940).
…e format

The backend always emits the errors key (gin.H), with null when every
feed was created; the old optional field claimed the key could be
absent. Found by auditing types.ts against model.go and the handler
request/response structs field by field.
The hand-copied field inventory had already drifted (missing the
feed_id column from 003 and auto_mark_read from 005, and the "no hidden
DB-level side effects" claim contradicted 003's ON DELETE SET NULL).
Table list now names roles and points at migrations, which already
carry per-change rationale in comments. The duplicated breaking-change
note in docs/README.md is consolidated into backend-design.md §7.
AGENTS.md now tells agents about pnpm test and the API fact map
(handler.go routes, model.go shapes, api-conventions.md), scripts.sh
gains test-frontend and a combined test command, and CONTRIBUTING.md's
checklist matches what CI actually runs (lint noted as blocked on
typescript-eslint TS 7 support).
safe-url is the security-relevant link sanitizer (protocol allowlist,
unsafe-base handling); feed-error guards the 48-char preview boundary;
article-filter guards the closed filter set. All are DOM-free pure
modules.
The project is maintained by a small team working with coding agents;
external PRs are no longer explicitly welcomed. Issues (bug report and
feature request templates already exist) are the contribution channel.
The local setup and validation sections stay for anyone building from
source.
api-conventions.md (the load-bearing file of this PR):
- cursor + order_by=created_at is a 400 — the doc's own rules composed
  into a rejected request
- GET /api/sessions is a 204/401 probe, not a 200 read; 200 covers all
  body-bearing responses including updates
- three empty-body error cases (unknown route 404, CORS 403, panic 500)
  break the 'always {error}' claim
- bare GET /oidc/callback compat route and anonymous mode were missing
- limit cap applies to search too, not just cursor-paginated lists

backend-design.md §6 listed two FK actions; the migrations declare five.
§5 pinned migration 003's rationale to item_id, which comes from 001.
Also: request bodies live in handler structs, not model.go (AGENTS.md
map fixed); README npm/pnpm inconsistency; a typo in types.ts.
Review feedback: the PR claimed to eliminate doc/code divergence but
kept copies of code facts. Applied consistently now:

- Version claims (Go 1.25 vs go.mod's 1.26) replaced by pointers to
  backend/go.mod; package.json gains engines.node as the Node truth
  source; CI already reads go-version-file, docs now do the same.
- backend-design: tech-stack table and module tree replaced by stack
  decisions and package responsibilities; endpoint enumeration dropped
  (SetupRouter is the inventory); env-var defaults point at
  .env.example; release checklist folded into CONTRIBUTING.
- frontend-design: tech-stack table and route map dropped; UI-area
  inventory compressed; the design narrative (URL state, undo pins,
  data flow) stays.
- docs/README.md states the doc-writing rules: docs answer why, never
  copy file-backed facts, one place per fact, code wins on conflict.
- AGENTS.md carries the same rule so agents follow it.
routeTree.gen.ts is generated (tsr generate) and gitignored, so a fresh
checkout has no route types and bare 'tsc -b' collapses every route
registration to never/undefined. The typecheck script now generates the
tree first, mirroring the build script. Verified by deleting the file
and running the script cold. AGENTS.md's verification command updated to
match.
@0x2E
0x2E merged commit 75db207 into main Oct 7, 2026
2 checks passed
@0x2E
0x2E deleted the chore/agentic-coding-optimization branch October 7, 2026 07:52
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.

1 participant