mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-12 22:13:05 +00:00
Skills vendoradas (UPSTREAM.md): suíte Taste (design-taste-frontend, gpt-taste, redesign-existing-projects, briefs de estilo, stitch-design-taste, full-output-enforcement), web-design-guidelines e awesome-design-md. Playwright CLI global + skill. Guardrail de marca do kit 1313 retirado: DESIGN.md §7 (Evolution) passa a ser o dono vivo das regras visuais. Doutrina ui-design-html.md §Skills de design, designer/-degraded e designer-campanha-solla alinhados; changelog DESIGN1.
52 lines
8.7 KiB
Text
52 lines
8.7 KiB
Text
---
|
|
description: Teqo engineering standards — dead-code hygiene, client-boundary composition, caching ladder, typed escape hatches, zero-warning gate. Read before and enforce after ANY code change.
|
|
alwaysApply: true
|
|
---
|
|
|
|
# Engineering standards (Teqo)
|
|
|
|
Established 2026-07-23 by the engineering-standards hardening (tracker: `docs/IMPROVE-CODE-QUALITY-PLAN.md`; ledger: `docs/TECH-DEBT.md`; safety net: `docs/TESTING.md`). These are gates, not suggestions — CI enforces most of them.
|
|
|
|
## After every change (non-negotiable)
|
|
|
|
1. **Dead code dies immediately.** After your edits, check what your change orphaned and delete it in the same delivery: run `pnpm exec knip` (files/dependencies are CI-blocking errors; unused exports are warnings being driven to zero). Never leave commented-out code, unused exports, superseded components, or one-shot scripts behind — git history is the archive.
|
|
2. **Zero warnings.** `pnpm lint` runs with `--max-warnings=0`. Deliberate placeholders use a `_` prefix. Never silence a rule without an inline justification comment.
|
|
3. **Gate em duas velocidades (desde 2026-07-30, paradigma de agentes paralelos — `docs/AGENT-OPS.md`).** **Fast gate local do agente** (iteração durante a sessão): `pnpm gate:fast` (lint + typecheck + test:unit). **Push canônico:** `pnpm push` — `ensure-repo-deps` (install + Husky em worktrees) + `git push`; o hook pre-push roda `gate:push` → `gate:ci` (espelho serial do `ci-pr.yml` **sem e2e** — OPS72: o e2e é passo **explícito da skill** (`work-issue`/`agent-work-issue`): antes do push rode localmente os e2e que criou + os da mesma superfície, **discricionário** via `pnpm test:e2e:affected`; o CI de PR roda só o blast radius `selected` — nunca `full` — e o full fica no `verify` do deploy (o run nasce no merge — OPS104); **com os `docs-guards` locais** — OPS63/OPS85: changelog additions-only + HISTORY congelado + agregado não-commitado, e marcadores de conflito, mesmos scripts do CI; o escape `changelog-rewrite:` fica só no CI, body do PR). Não use `git push` nu. Fonte única: scripts `gate:*` e `scripts/git-push.mjs` em `package.json`; escape para WIP: `git push --no-verify`. Migrations NÃO são serializadas entre PRs: a cadeia é validada por `pnpm migrate` + `test:int` (incl. `campaignMigrationReconciliation`) em todo PR; rebase antes de `migrate:create` continua obrigatório (o antigo `migration-lock` foi removido em 2026-08-12 — deadlock de CI). Entrega = merge em `main`; publish gated pelo `verify` do `deploy.yml` (início automático no merge, approve humano na produção — OPS104; não há `ci.yml` verificador de main). **CI vermelho no teu PR é teu, mesmo pré-existente ou de infra** — "fora do escopo da feature" não é critério de parada (política "Dono do PR, dono do CI" em `docs/AGENT-OPS.md`).
|
|
4. **Run gate commands bare, never piped** (`pnpm test | tail` swallows the exit code). Never run `knip --fix` blind — verify each removal with `git grep -w <symbol>` first (knip's graph is deliberately scoped: the Payload plugin is disabled — `knip.json` `"payload": false`, OPS124 — because it could never load `payload.config.ts` under default Node conditions, so `--fix` can delete exports migrations/tests still reference).
|
|
|
|
## Type honesty
|
|
|
|
- **`as never` is banned** (ESLint `no-restricted-syntax`). Use the typed escape hatches that keep property-level checking: `hookFilledCreateData<Slug>()` for create data whose remaining required fields are filled by collection hooks; tests use `stub<T>(partial)` (`tests/helpers/stub.ts`). Casting from `unknown` after a runtime check is acceptable; a justified `eslint-disable-next-line` is the last resort.
|
|
- Prefer fixing the type over casting. When a generic can't be proven, restructure so the caller keeps concrete types (see `runStaffEntityMutation` — policy in the helper, typed mutation in the caller).
|
|
|
|
## Client boundary (Next.js composition)
|
|
|
|
- **Server-first.** Pages and data assembly are Server Components; `'use client'` only where there is interactivity (hooks, handlers, browser APIs). Static subtrees of an interactive area are passed as `children`/slots from the server (`LeaderContactsPanel` + `LeaderContactForm`, `CampaignDashboard.mapSlot` are the references).
|
|
- **Client components must not import server data modules for values.** Types/constants shared with the client live in a client-safe contract module (`municipalityMapContract.ts` is the reference; `municipalityMapData.ts` is `server-only`). `import type` is always safe.
|
|
- **State lives where it is used.** Hover/selection in the leaf that renders it (`MunicipalityMapSelection`); context providers wrap only their consumers (estimate-scenario provider wraps results, not filters). Do not lift state so high that siblings re-render.
|
|
- Heavy view payloads: pass view models with `select`ed fields, never whole Payload docs.
|
|
|
|
## Loading feedback (see also `campanha-action-feedback.mdc`)
|
|
|
|
- Every navigation-triggering control participates in a transition; the RESULTS region dims (`CampaignListPendingBoundary`/`CampaignListResults`), not just the control. Optimistic UI must revalidate on success (`revalidatePath`) or it will lie by reverting.
|
|
- Heavy server subtrees stream behind `<Suspense>` with a local skeleton; heavy route segments get their own `loading.tsx`.
|
|
|
|
## Caching ladder (decide in this order)
|
|
|
|
1. **React `cache()`** for per-request dedup of repeated reads in one RSC tree (`loadMunicipalityScope`, `getCampaignUser`).
|
|
2. **`unstable_cache` + tag** for immutable/slow-changing cross-request data (election reads use tag `election-tse`; bust via `POST /api/revalidate?tag=...` — allowlist in `revalidateRequest.ts`). Auth checks stay OUTSIDE the cached core.
|
|
3. **Committed artifact via re-runnable script** for truly immutable derived data (`pnpm build:election-aggregates` → `src/lib/electionAggregates/`, pinned to the catalog by a snapshot test). Never compute artifacts during `pnpm build` — deploys must not depend on DB content.
|
|
4. Never long-cache live 2026 campaign data (pledges, expectedVotes) without a write-path invalidation.
|
|
|
|
## Access control (see also `security-critical.mdc`)
|
|
|
|
- **Every collection declares `access` explicitly.** Payload's default ("any authenticated user") is a bug here: campaign JWTs work against `/api/*`. Admin-only collections use `payloadAdminOnly`.
|
|
- Access helpers live in `src/utilities/access/` per domain, re-exported through `campaignAccess.ts`. New collections get helpers there plus int tests proving the denied paths.
|
|
|
|
## Module organization
|
|
|
|
- `src/lib/` = pure logic and static data (client-safe unless marked). `src/utilities/` = Payload/Next-coupled server code (mark `server-only` when server-bound). React components never live in `utilities/`.
|
|
- Follow the domain prefixes (`campaign*`, `municipality*`, `supporter*`, `actionPlan*`); the access domain has a real folder (`utilities/access/`).
|
|
- **Edit the owner, don't twin.** New behavior lands in the module that already owns that concern (`CampaignTable`, list URL helpers, form-action wrappers, portfolio/relation cells, access helpers, …). A parallel file that reimplements the same machine is a defect unless the delivery names a deliberate policy fork. Prefer changing existing code over adding beside it; delete what your change supersedes in the same delivery (see "Dead code dies immediately"). Product constraints in `AGENTS.md` and frozen migration files are not a ban on editing application code.
|
|
- **Visual structure belongs to the `designer`.** Deciding layout, hierarchy, states and visual tokens is the `designer`'s call (`.opencode/agent/designer.md`; doctrine `.agents/skills/plan-issue/ui-design-html.md`), and the standing visual rules live in `DESIGN.md` (§7 Evolution — can be updated by the designer/implementer when a better pattern is decided, with rationale). The implementer ports the approved artifact (`docs/plans/<slug>-ui-design.html`) to markup and wires data/routes/queries/copy. A diff that changes visual structure without the designer's verdict is a defect (data/copy wiring on the approved markup is a non-trigger) — the triggers, non-triggers and fail-closed closure live in `execution-pipeline.md` (§Design). This is ownership, not a new module: change the owner, don't improvise the visual structure in code.
|
|
- **Reuse vs premature abstraction.** Prefer extending the existing owner. Do **not** invent a new shared abstraction for a speculative third use. If you are about to copy an existing path and the knowledge is the same, **edit the owner** (or extract in the same delivery) — waiting for "call site #3" is how twins spawn. Defer only true look-alikes that differ in policy, with an explicit revisit trigger. Named policy wrappers (`runCampaignRedirectFormAction`, `runStaffEntityMutation`) stay justified even at few call sites.
|