Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e83f53b
feat(admin-frontend): paginação da lista de pessoas atendidas (#155, …
evertonschuster Oct 14, 2026
336e1a5
refactor(admin-frontend): rodapé de paginação numa linha, com seletor…
evertonschuster Oct 14, 2026
fb5e13a
feat(admin-frontend): rodapé de paginação pensado para o celular (#15…
evertonschuster Oct 14, 2026
eb92dc9
fix(admin-frontend): barra de navegação do celular fora da tela quand…
evertonschuster Oct 14, 2026
d2b3085
feat(admin-frontend): esqueleto da lista de pessoas com tantas linhas…
evertonschuster Oct 15, 2026
bba8ca9
refactor(admin-frontend): ListSection sem paginação e PaginatedListSe…
evertonschuster Oct 15, 2026
e0e73b7
feat(admin-frontend): esqueleto da tabela imita a tabela, com cabeçal…
evertonschuster Oct 15, 2026
94d1808
feat(admin-frontend): trocar de página enquanto uma página ainda carr…
evertonschuster Oct 15, 2026
93ca185
refactor(admin-frontend): um hook para as listas paginadas, com os fi…
evertonschuster Oct 16, 2026
cdda550
Keep paginated list state in its hook
evertonschuster Oct 20, 2026
2663299
refactor(admin-frontend): lista paginada identifica o pedido pela ref…
evertonschuster Oct 20, 2026
c6d8381
docs(admin-frontend): ADRs 0068 e 0069, a skill e o §6 apontam para o…
evertonschuster Oct 20, 2026
394b814
test(admin-frontend): e2e lê o usuário de demonstração do DemoTenant …
evertonschuster Oct 20, 2026
28a6b78
test(admin-frontend): paginação sai do e2e, com o registerPeopleThrou…
evertonschuster Oct 20, 2026
b776dea
docs(admin-frontend): ADR 0068 marca as notas da URL como históricas …
evertonschuster Oct 20, 2026
9ad5650
Rename client list filter q to search
evertonschuster Oct 20, 2026
19ec941
refactor(admin-frontend): ListSection sempre em tabela, sem o modo re…
evertonschuster Oct 20, 2026
fca7d2a
refactor(admin-frontend): usePaginatedList busca no backend só quando…
evertonschuster Oct 20, 2026
efac901
docs(admin-frontend): ADR 0070 aponta o que a ADR 0071 mudou, e AGENT…
evertonschuster Oct 20, 2026
f7681e9
Add delay to client listing endpoint
evertonschuster Oct 20, 2026
a5e813c
Update use-paginated-list.ts
evertonschuster Oct 20, 2026
c913bd4
Merge origin/main no feat/admin-frontend-paginacao-listas (#180 em main)
evertonschuster Oct 20, 2026
4bb78bc
Refine paginated list and pagination utilities
evertonschuster Oct 20, 2026
761be8f
Merge origin/feat/admin-frontend-paginacao-listas: traz o #180 (pagin…
evertonschuster Oct 20, 2026
dbdf7bd
docs(admin-frontend): ADRs da paginação passam a 0070–0073, depois da…
evertonschuster Oct 20, 2026
04488eb
docs(admin-frontend): ADRs 0072 e 0070 marcam o inventário antigo do …
evertonschuster Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .claude/skills/agenza-frontend-slice/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ enforces it together with the layer direction. It only inspects files inside `ap
`widgets/` and `shared/` — `src/main.tsx`, `src/test/`, `e2e/` and config files are on review.

Direction is `app → features → widgets → shared`, and inside a slice `ui → model | api`,
`api → model`. A domain type never lives in `api/`. `widgets/` imports only `shared/`; `shared/`
imports none of the others.
`api → model`. A domain type never lives in `api/`. `widgets/` imports `shared/` and, to compose one, another widget (never a cycle);
`shared/` imports none of the others.

## 3. Route wiring

Expand All @@ -70,8 +70,9 @@ page-owned state. Nothing implements this yet; you are writing the first one. Sh
## 4. Pages are shells

`<Page>.tsx` holds no `useState`, `useEffect` or `useRef` of its own. All of it lives in that page's
**own** hook. Hooks are never shared between pages — the single exception is a pure Context accessor
like `useAuth`. Two pages wanting the same logic means the logic is a pure function for `model/`, not
**own** hook. Hooks are never shared between pages — the exceptions are a pure Context accessor
like `useAuth` and a generic hook with no domain in a widget, such as `usePaginatedList` (ADR 0071), which
a paged list's hook calls with its initial filters and repository `list` (ADR 0072: the hook keeps that state, not the URL). Two pages wanting the same logic means the logic is a pure function for `model/`, not
a shared hook. Details, sub-component placement and the StrictMode guards:
[references/pages.md](references/pages.md).

Expand Down
7 changes: 5 additions & 2 deletions .claude/skills/agenza-frontend-slice/references/pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ single-use code) with a terse *why* comment. That guard has to live somewhere it
without rendering a route tree — the hook. Spread across a component body, it becomes a race you
rediscover in production.

**Hooks are never shared between pages.** The one exception is a pure Context accessor like
`useAuth`. If two pages want the same behaviour, extract the *pure* part into `model/` as a plain
**Hooks are never shared between pages.** The exceptions are a pure Context accessor like
`useAuth`, and a generic hook with no domain that a widget exports, such as `usePaginatedList` in
`widgets/paginated-list-section` (ADR 0071): a paged list declares its filters in `model/` as an
interface, and its page hook passes the initial filters and the repository's `list` to
`usePaginatedList`, which keeps the filters, the page and the size in its own state (ADR 0072). If two pages want the same behaviour, extract the *pure* part into `model/` as a plain
function and let each hook call it. A `useSharedThing` imported by two pages is the smell that ends
with one page's lifecycle quietly driving another's.

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/agenza-ptbr-copy/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ i18n layer to "prepare" for a language nobody asked for.
The one exception: a **code → message map is data, not markup**. It belongs in the slice's `model/`,
where it is pure and testable without React, next to the branch that consumes it.

**Formatters live in `src/shared/format/`** (`date.ts`, `age.ts`) — cross-cutting, no business rule,
**Formatters live in `src/shared/format/`** (`date.ts`, `age.ts`, `count.ts`) — cross-cutting, no business rule,
so `shared/` by the dependency direction in
[`docs/ARCHITECTURE.md` §1](../../../apps/admin-frontend/docs/ARCHITECTURE.md). A value with its own
validation and limits (CPF, phone, e-mail, birth date) is not a formatter: its mask and parse live with
Expand Down
3 changes: 2 additions & 1 deletion .claude/skills/agenza-ptbr-copy/references/formatting.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ whether she has to leave now.

Use `Intl.PluralRules(LOCALE)` rather than `n === 1`. CLDR's rule for `pt` is `i = 0..1 → one`, so
`select(0)` returns `'one'` — the opposite of the English instinct, and worth confirming in a
one-liner before you depend on it.
one-liner before you depend on it. In `admin-frontend` that is `formatCount(count, { singular, plural })`
in `shared/format/count.ts` ("1 pessoa", "1.234 pessoas"), beside `formatNumber`.

Better still: **never render a zero count.** Zero is an empty state, and "Nenhum serviço ainda" says
more than "0 serviços". → [`states.md`](states.md)
Expand Down
6 changes: 6 additions & 0 deletions .claude/skills/agenza-testing/references/patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ and TypeScript keeps the literal types.
Fixture builders live in [`src/test/oidcUser.ts`](../../../../apps/admin-frontend/src/test/oidcUser.ts)
(`makePrincipal`, `makeOidcUser`, `makeAccessToken`). Add to that file rather than hand-rolling a
principal in a test; a token built by hand tends to be missing exactly the claim under test.
Beside it, `src/test/pending.ts` gives a promise the test resolves by hand (`pending<T>()`, for an
answer that arrives after another) and `src/test/footerSkeleton.ts` finds the paged list's footer
skeleton; import them instead of declaring a copy in the test file.

---

Expand Down Expand Up @@ -224,6 +227,9 @@ async function loginAsDemoUser(page: Page) {

Rules that keep these green:

- **Read the demo login from the seed's own config.** `DEMO_EMAIL` and `DEMO_PASSWORD` come from
`DemoTenant` in identity-service's `appsettings.Development.json`, the values its seeder creates the
user from; never type them again in a spec.
- **Read seeded ids from the running system.** The demo tenant's id is generated fresh on every
seed; the live file decodes it out of the issued access token instead of asserting a constant.
- **Prefer a reload over waiting out a real clock.** Session persistence is proven by reloading and
Expand Down
12 changes: 11 additions & 1 deletion apps/admin-frontend/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,17 @@ ainda. Crie-os quando uma segunda feature precisar da mesma entidade, não antes

**Páginas são cascas.** `<Page>.tsx` não tem `useState`, `useEffect` nem `useRef` próprios; toda a
lógica mora no hook **daquela** página (`use<Page>.ts`). Hooks não são compartilhados entre páginas —
a única exceção é um acessor puro de contexto como `useAuth`.
as exceções são um acessor puro de contexto como `useAuth` e um hook genérico, sem domínio, num widget, como o
`usePaginatedList` de `widgets/paginated-list-section` ([ADR 0071](../../docs/adr/0071-admin-frontend-paged-list-hook.md)).
Uma lista paginada declara a interface dos seus filtros em `model/`, e o hook da página só passa os filtros
iniciais e o repositório ao `usePaginatedList`, que guarda filtros, página e tamanho no próprio estado, fora da URL
([ADR 0072](../../docs/adr/0072-admin-frontend-paged-list-state-in-the-hook.md)).

**Buscar no backend é uma ação, não uma reação.** O `usePaginatedList` só pede uma página quando uma ação manda
(`applyFilters`, troca de página ou de tamanho, "Tentar novamente"), na montagem e para corrigir uma página além da
última. Nenhum efeito depende do estado dele: não acrescente um `useEffect` com dependências que dispare
`fetchPage`, e uma ação nova passa pelo `request` do hook, conforme a
[ADR 0073](../../docs/adr/0073-admin-frontend-paged-list-asks-only-when-told.md).

**`loader` e `action` moram em `ui/pages/<Page>/route.ts`** e são reexportados pelo barril, para que
`app/routes.tsx` os importe via `@/features/<slice>` e a direção continue `app → features`.
Expand Down
Loading
Loading