- .github/workflows/ci.yml: two jobs — frontend (tsc --noEmit, lint, build, Playwright) and backend (Postgres 16 + odoo:19 --test-enable --test-tags encoach_api) — catches regressions before merge. - docs/adr/: start an Architecture Decision Record trail with 0001 canonical directory layout, 0002 JWT refresh flow, 0003 paginated response envelope, 0004 RAG metadata + chunking. - docs/PROJECT_SUMMARY.md §21 Hardening Release: full recap of the AI quality loop, compliance, Paymob, i18n, and CI work shipped in this drop, plus new DB tables, REST routes, frontend routes, verification results, and operator-facing configuration. - README.md refreshed for the v4 split-repo doctrine and the new feature surface. - new_project/DEPRECATED.md: formal retirement notice pointing at backend/ as the canonical tree. Made-with: Cursor
2.0 KiB
2.0 KiB
ADR 0003: Canonical paginated response envelope
- Status: Accepted
- Date: 2026-04-09
- Deciders: Platform team, Frontend team
Context
Different Odoo controllers returned paginated data in at least three shapes:
{ data: [...], total: N, page, limit }{ results: [...], count: N }[...](bare array, no totals)
The frontend grew defensive code paths to handle all three, and every new endpoint risked inventing a fourth shape.
Decision
Every list endpoint MUST return the canonical envelope produced by
encoach_api.controllers.base.paginated_envelope:
{
"items": [ … ],
"data": [ … ],
"total": 123,
"page": 1,
"size": 20
}
itemsis the canonical field name. New code reads fromitems.datamirrorsitemsfor backwards compatibility with older callers and can be removed once every consumer migrates.totalis the total number of matching records across all pages.pageis 1-indexed.sizeis the requested page size (capped server-side).
On the frontend, PaginatedResponse<T> in frontend/src/types/common.ts
exposes both items and an optional data, and service methods
(users.service.ts, lms.service.ts, etc.) construct a clean
PaginatedResponse object from whatever the server returns so that UI code
never sees the legacy fields.
Consequences
- Positive: frontend code is simpler and typesafe — read
items, done. - Positive: OpenAPI spec advertises one consistent shape across endpoints.
- Negative: one additional key (
data) is duplicated in responses. Cheap (same reference, no JSON bloat) and easy to delete later.
Alternatives considered
- Follow JSON:API's
{ data, meta, links }convention. Rejected as overkill: we don't use HATEOAS, and the extra indirection would force a rewrite of every existing consumer. - Return a bare array + pagination headers. Rejected because Odoo's controller helpers make setting custom headers awkward and because it hides totals from curl/Postman users.