Skip to content

feat: multi-page routing + shadcn baseline + useModuleRoute boundary fix#9

Merged
klinux merged 4 commits into
mainfrom
feat/multi-page-routing-and-shadcn-baseline
Apr 23, 2026
Merged

feat: multi-page routing + shadcn baseline + useModuleRoute boundary fix#9
klinux merged 4 commits into
mainfrom
feat/multi-page-routing-and-shadcn-baseline

Conversation

@klinux
Copy link
Copy Markdown
Contributor

@klinux klinux commented Apr 23, 2026

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/main atual (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 no useModuleRoute.

Commits

  1. 9a212c1 feat: multi-page routing + pre-installed shadcn primitives + lint guards

    • useModuleRoute(basePath) lê o pathname e re-renderiza em popstate
    • Páginas Overview / Items / Settings como referência em src/components/pages/
    • module.json passa a declarar navegação multi-entry
    • 8 primitivas shadcn (table, input, label, dialog, select, badge, separator, tabs)
    • ESLint bloqueia <table> / <input> / <select> / <dialog> / <textarea> nativos e cores hardcoded (#hex / rgb() / hsl()) fora de src/components/ui/**
    • CLAUDE.md documenta o padrão de múltiplas páginas
  2. 194bc6c feat(sync-template): propagate eslint guardrails + missing shadcn primitives

    • eslint.config.js entra na Categoria A do sync (sempre sobrescreve — é guardrail de plataforma, não customização do dev)
    • Categoria D passa a copiar primitivas shadcn ausentes em forks que já têm components.json
  3. 9439721 feat: expand shadcn baseline to cover common backoffice patterns

    • Mais 8 primitivas: textarea, checkbox, switch, sheet, dropdown-menu, alert-dialog, tooltip, skeleton
    • Mensagens do ESLint apontam pra Checkbox / Switch / AlertDialog em vez de só sugerir shadcn add
    • CLAUDE.md e sync-template/SKILL.md listam o baseline completo
  4. e43fbf4 fix(useModuleRoute): use boundary check so sibling modules don't match

    • Copilot flagged na review da PR feat: consume @platform/shell-contract from Nexus (drop the local copy) #7: pathname.startsWith(basePath) também casa /my-module-foo quando basePath é /my-module, retornando subPath="-foo/items"
    • Fix: pathname === basePath || pathname.startsWith(basePath + "/") antes de fatiar
    • Adiciona src/hooks/useModuleRoute.test.ts com regressão pro sibling prefix + 3 happy paths

Scope

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

  • CI verde (pr/test roda typecheck + lint + vitest; pr/scan-pr roda Trivy)
  • npm run test:run verde localmente, incluindo src/hooks/useModuleRoute.test.ts
  • npm run lint não reclama de tags nativas nas páginas novas (elas usam só primitivas shadcn)
  • Manual após mergear: platform create-module novo fork, confirmar que Items/Settings aparecem na sidebar do shell e navegação não re-monta o bundle

🤖 Generated with Claude Code

klinux and others added 4 commits April 23, 2026 15:36
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>
Copilot AI review requested due to automatic review settings April 23, 2026 18:38
@klinux klinux merged commit c0651ff into main Apr 23, 2026
5 checks passed
@klinux klinux deleted the feat/multi-page-routing-and-shadcn-baseline branch April 23, 2026 18:41
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 App para 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/**, atualiza eslint.config.js, e expande module.json para 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.

Comment thread package.json
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^1.8.0",
"radix-ui": "^1.4.3",
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
"radix-ui": "^1.4.3",

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +4
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"

Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +3
import { cn } from "@/lib/utils"

function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +21
beforeEach(() => {
window.history.pushState({}, "", "/my-module");
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(JSON.stringify({ source: "mock" }), { status: 200 })
);
});

afterEach(() => {
vi.restoreAllMocks();
});
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
klinux added a commit that referenced this pull request May 9, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants