feat: multi-page routing + shadcn baseline + useModuleRoute boundary fix#9
Conversation
Modules can now declare several pages in module.json → navigation[]; the shell renders them in its sidebar and useModuleRoute(moduleManifest.route) switches between them in-app via popstate. The template now ships with Overview, Items (shadcn Table + Badge), and Settings (Input + Label + Separator) as references. Pre-installs commonly-needed shadcn primitives (table, input, label, dialog, select, badge, separator, tabs) so devs don't fall back to native tags out of friction. ESLint now blocks raw <table>/<input>/<select>/ <dialog>/<textarea> and hardcoded #hex/rgb()/hsl() literals in src/; src/components/ui/ is exempt because shadcn primitives wrap natives. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mitives Forks created before the lint/primitives commit (3c0dfde) were not getting either on sync: eslint.config.js was not listed in any category, and Categoria D only acted on forks without shadcn. Result: strict lint rules stayed in the template and devs kept writing <input class="input"> in their forks without anything failing. - Adds eslint.config.js to Categoria A (always overwrite). It is a platform guardrail, not dev customization. Diff is shown before apply. - Extends Categoria D: when the fork already has components.json, copy template primitives that are absent from src/components/ui/. Never touches existing primitives — devs may have customized them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 8 more pre-installed primitives beyond the initial set so devs stop hitting friction for patterns that already show up in the current forks (boolean toggles, delete confirmations, row actions, loading states): - textarea — multi-line input (ESLint already bans native <textarea>) - checkbox — very common; a katools form still uses <input type=checkbox> - switch — boolean settings - sheet — edit/detail drawer (CRUD staple) - dropdown-menu — row action menus, user menu - alert-dialog — delete/destructive confirmations (replaces window.confirm) - tooltip — icon-only buttons - skeleton — loading states ESLint message for <input> now points at Checkbox/Switch/RadioGroup for the type=checkbox/radio cases. The <textarea> message no longer tells devs to run the shadcn CLI since it is pre-installed. <dialog> now surfaces AlertDialog as an option alongside Dialog. CLAUDE.md and sync-template/SKILL.md both list the full baseline so future sync runs know which primitives to auto-copy to forks. Deliberately skipped: form (drags react-hook-form + zod; should be opt-in), toast/sonner (shell context.notify() covers it), and heavy niche ones like calendar/chart/carousel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`pathname.startsWith(basePath)` returned a non-empty subPath whenever the current URL merely shared a prefix with the module route, e.g. basePath="/my-module" would also match "/my-module-foo/items" and return subPath="-foo/items". Unlikely in a single-module deploy, but the shell mounts multiple modules side-by-side and the names can overlap (auth, auth-admin, etc.). Switch to `pathname === basePath || pathname.startsWith(basePath + "/")` before slicing. Adds a regression test for the sibling-prefix case plus the three happy paths (root / deep / popstate). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Este PR recupera/introduz um baseline de módulo com multi-page routing interno (via useModuleRoute) e expande o conjunto de primitivas shadcn/Radix para cobrir padrões comuns de backoffice, além de reforçar guardrails de lint (banindo tags nativas e cores hardcoded fora de src/components/ui/**).
Changes:
- Adiciona o hook
useModuleRoute(basePath)com boundary-check e testes de regressão. - Refatora o
Apppara delegar renderização por sub-rota e cria páginas de referência (Overview/Items/Settings). - Adiciona várias primitivas UI em
src/components/ui/**, atualizaeslint.config.js, e expandemodule.jsonpara navegação multi-entry (além de atualizar deps/lockfile).
Reviewed changes
Copilot reviewed 28 out of 29 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/hooks/useModuleRoute.ts | Hook de roteamento mínimo por pathname + boundary-check para evitar match de módulos “irmãos”. |
| src/hooks/useModuleRoute.test.ts | Testes do hook (happy paths + regressão de prefixo de sibling). |
| src/components/App.tsx | Switch de páginas por subPath e uso de moduleManifest.route. |
| src/components/App.test.tsx | Ajusta testes para validar roteamento por sub-rota e mocks necessários. |
| src/components/pages/OverviewPage.tsx | Página exemplo “Overview” (referência de uso do ShellContext). |
| src/components/pages/ItemsPage.tsx | Página exemplo “Items” (referência de Table + Badge). |
| src/components/pages/SettingsPage.tsx | Página exemplo “Settings” (referência de Input/Label/Separator + auditLog). |
| src/components/ui/tooltip.tsx | Primitiva Tooltip (Radix) no baseline. |
| src/components/ui/textarea.tsx | Primitiva Textarea no baseline. |
| src/components/ui/tabs.tsx | Primitiva Tabs no baseline. |
| src/components/ui/table.tsx | Primitiva Table no baseline. |
| src/components/ui/switch.tsx | Primitiva Switch no baseline. |
| src/components/ui/skeleton.tsx | Primitiva Skeleton no baseline. |
| src/components/ui/sheet.tsx | Primitiva Sheet no baseline. |
| src/components/ui/separator.tsx | Primitiva Separator no baseline. |
| src/components/ui/select.tsx | Primitiva Select no baseline. |
| src/components/ui/label.tsx | Primitiva Label no baseline. |
| src/components/ui/input.tsx | Primitiva Input no baseline. |
| src/components/ui/dropdown-menu.tsx | Primitiva DropdownMenu no baseline. |
| src/components/ui/dialog.tsx | Primitiva Dialog no baseline. |
| src/components/ui/checkbox.tsx | Primitiva Checkbox no baseline. |
| src/components/ui/badge.tsx | Primitiva Badge no baseline. |
| src/components/ui/alert-dialog.tsx | Primitiva AlertDialog no baseline. |
| module.json | Expande navigation[] para múltiplas entradas (Overview/Items/Settings). |
| eslint.config.js | Guardrails: proíbe tags nativas específicas e cores hardcoded fora de src/components/ui/**. |
| package.json | Adiciona dependência radix-ui. |
| package-lock.json | Atualiza lockfile conforme novas dependências. |
| CLAUDE.md | Documenta padrão de múltiplas páginas e baseline shadcn. |
| .claude/skills/sync-template/SKILL.md | Atualiza skill de sync-template para propagar guardrails e baseline. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "class-variance-authority": "^0.7.1", | ||
| "clsx": "^2.1.1", | ||
| "lucide-react": "^1.8.0", | ||
| "radix-ui": "^1.4.3", |
There was a problem hiding this comment.
A dependência direta de @radix-ui/react-slot (já usada em src/components/ui/button.tsx) junto com radix-ui tende a gerar versões duplicadas de @radix-ui/react-slot no lockfile (ex.: 1.2.4 no root e 1.2.3 dentro de radix-ui). Isso aumenta bundle/instalação e pode causar inconsistências sutis. Sugestão: padronizar em um único modelo (ou usar apenas pacotes @radix-ui/react-* necessários e remover radix-ui, ou remover a dependência direta de @radix-ui/react-slot e ajustar os imports para vir de um único lugar).
| "radix-ui": "^1.4.3", |
| import * as React from "react" | ||
| import { cva, type VariantProps } from "class-variance-authority" | ||
| import { Slot } from "radix-ui" | ||
|
|
There was a problem hiding this comment.
Aqui o componente está usando Slot vindo de radix-ui, enquanto Button usa Slot de @radix-ui/react-slot. Misturar os dois caminhos pode contribuir para duplicação de dependências e APIs levemente diferentes. Ideal é padronizar a importação (ex.: usar o mesmo @radix-ui/react-slot já presente no template, ou então migrar tudo para um único entrypoint).
| import { cn } from "@/lib/utils" | ||
|
|
||
| function Skeleton({ className, ...props }: React.ComponentProps<"div">) { |
There was a problem hiding this comment.
Skeleton referencia React.ComponentProps mas não importa React. Dependendo da configuração de TypeScript/ESLint, isso pode virar erro de typecheck/lint (e também fica inconsistente com os demais componentes em src/components/ui/* que importam React para tipos). Sugestão: adicionar o import de React (pode ser import type * as React from "react" se for só para tipos).
| beforeEach(() => { | ||
| window.history.pushState({}, "", "/my-module"); | ||
| vi.spyOn(globalThis, "fetch").mockResolvedValue( | ||
| new Response(JSON.stringify({ source: "mock" }), { status: 200 }) | ||
| ); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| vi.restoreAllMocks(); | ||
| }); |
There was a problem hiding this comment.
Os testes alteram window.history no beforeEach e durante os casos, mas o afterEach só restaura mocks. Isso pode vazar estado de URL entre suites e deixar os testes dependentes da ordem de execução. Sugestão: salvar o window.location.pathname original (ex.: em beforeAll) e restaurar no afterEach/afterAll (idealmente disparando popstate após restaurar, se necessário).
…odule Phase 4 part 2 — closes the V2 contract loop on the module side. Template - module.json gains `accent: "coral"` and `icon: "shield"` so a freshly cloned module renders with declared identity instead of inheriting the auto-inferred default. Both fields stay optional in the contract. - README gets an "Identidade visual" section above the perspectives one: what the chip is, the closed accent set with hue + intent, the icon source (lucide kebab-case), and the auto-infer behavior so devs understand the safety net. Primitives — same fix already shipped in the shell (PR #9) - src/components/ui/input.tsx: bg-transparent → bg-card, dropped dark:bg-input/30 (bg-card adapts via the --card token) - src/components/ui/select.tsx: bg-transparent → bg-card on the trigger, plus a hover:bg-muted to mirror the shell - src/components/ui/tabs.tsx: data-[state=active]:bg-background → bg-card + the V2 shadow Without this, modules built from the template still ship with the old shadcn defaults that go invisible over the shell's V2 cream page (the Tweaks Management symptom). The shell already has a defensive data-slot override for legacy bundles; this stops the bleeding for new code. /check-module — three new checks - 2.9: accent must be one of the 7 V2 values when declared; warns informatively when ausente (auto-infer kicks in but identity stays generic) - 2.10: icon ausente → warn (chip falls back to the group default, which makes modules in the same group visually indistinguishable) - 2.11: greps src/ for hex / rgb() / rgba() that aren't behind a `var(--token)` — warn (not error) since SVG icons and shadow rgba()s legitimately use hex; reports up to 10 hits as a sample Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Recupera o trabalho de multi-page routing + expansão do baseline shadcn que ficou órfão na main local depois que a PR #7 foi re-stacked pra ser só o swap do Nexus. Rebaseado em cima da
origin/mainatual (que ganhou fixes de globals/preflight, bump do Hono e provider-token align desde que esses commits foram feitos em 17/abr) e com um fix adicional nouseModuleRoute.Commits
9a212c1feat: multi-page routing + pre-installed shadcn primitives + lint guardsuseModuleRoute(basePath)lê o pathname e re-renderiza empopstateOverview/Items/Settingscomo referência emsrc/components/pages/module.jsonpassa a declarar navegação multi-entrytable,input,label,dialog,select,badge,separator,tabs)<table>/<input>/<select>/<dialog>/<textarea>nativos e cores hardcoded (#hex/rgb()/hsl()) fora desrc/components/ui/**194bc6cfeat(sync-template): propagate eslint guardrails + missing shadcn primitiveseslint.config.jsentra na Categoria A do sync (sempre sobrescreve — é guardrail de plataforma, não customização do dev)components.json9439721feat: expand shadcn baseline to cover common backoffice patternstextarea,checkbox,switch,sheet,dropdown-menu,alert-dialog,tooltip,skeletonCheckbox/Switch/AlertDialogem vez de só sugerirshadcn addsync-template/SKILL.mdlistam o baseline completoe43fbf4fix(useModuleRoute): use boundary check so sibling modules don't matchpathname.startsWith(basePath)também casa/my-module-fooquandobasePathé/my-module, retornandosubPath="-foo/items"pathname === basePath || pathname.startsWith(basePath + "/")antes de fatiarsrc/hooks/useModuleRoute.test.tscom regressão pro sibling prefix + 3 happy pathsScope
Esse PR está grande (~3700 linhas) e estoura o limite de 400 do CLAUDE.md — a maior parte é
package-lock.json(+1900) e primitivas shadcn geradas pela CLI (cada uma é um arquivo auto-gerado). Os commits estão organizados em ordem de dependência caso prefiram revisar commit-a-commit.Alternativa: podemos quebrar em 3 PRs (multi-page + fix boundary / baseline expansion / sync-template update), mas os dois últimos dependem do primeiro, então vira fila de merge.
Test plan
pr/testroda typecheck + lint + vitest;pr/scan-prroda Trivy)npm run test:runverde localmente, incluindosrc/hooks/useModuleRoute.test.tsnpm run lintnão reclama de tags nativas nas páginas novas (elas usam só primitivas shadcn)platform create-modulenovo fork, confirmar que Items/Settings aparecem na sidebar do shell e navegação não re-monta o bundle🤖 Generated with Claude Code