Repository navigation
chore: optimize docs, types, and CI for agentic coding - #271
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 bydocs/api-conventions.md(85 lines) recording only cross-cutting rules distributed across handlers: session auth (incl. anonymous mode), response envelopes, cursor grammar (incl. thecreated_at+before400 rule), error shape (incl. the three empty-body cases), status-code conventions, bookmark create modes, settings closed sets.Impact: anyone scripting
/apiloses the OpenAPI reference; the Fever API contract (fever-api.md) is unaffected.Frontend types audited field-by-field against Go (1eae219, d2cde86)
types.tsis the contract copy agents read on the frontend side, so it must mirror the backend exactly. Full audit againstmodel.goand the handler request structs:ListAPIResponsesplit intoListResponse(groups/feeds, no cursor) andPaginatedListResponse(items/bookmarks)CreateBookmarkRequestis now the two server modes ({item_id}vs full snapshot); the sole caller sendsitem_idonlyorder_bytyped to the documented enum (items.ts was sending an out-of-contract value)errorstyped asstring[] | nullWire 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.ymlran onlycheck: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 lintcannot start on any current typescript-eslint version (TS 7 unsupported). Re-add once upstream support lands. Note: the frontend job was renamedi18n→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.modsays 1.26. Applied consistently:backend/go.mod;package.jsongainsengines.nodeas the Node truth sourceSetupRouteris the inventory); env defaults point at.env.example; release checklist folded into CONTRIBUTINGdocs/README.mdnow 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 ruleEntry points and policy (4d08647, 7c60d02)
AGENTS.md documents the verification commands and API fact map;
scripts.shgainstest-frontendand a combinedtest; CONTRIBUTING's checklist matches CI. Contribution policy: issues instead of PRs.Verification
pnpm typecheck,pnpm test(23),check:i18npass locallytest.ymlrunsgo test ./...(pull_request-triggered)order_by=pub_dateordering + cursor paging, thecreated_at+before400 rule, bookmark{item_id}mode returns full server-side snapshot, snapshot mode, 204 mark-readuseCreateBookmark({item_id}-only) path; state persists across reload (server-side confirmation: bookmark hasitem_id+ full snapshot +feed_id)