Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 9 additions & 1 deletion .claude/skills/sync-template/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Aplicados em todos os modos:
- `.woodpecker/scan-pr.yaml`
- `Dockerfile` (a nova versao NAO faz `COPY nginx.conf` — mostre o diff e aplique)
- `.dockerignore`
- `eslint.config.js` (guardrail de plataforma — bane `<table>/<input>/<select>/<dialog>/<textarea>` nativos e cores hardcoded; mostre diff antes)
- `.claude/skills/platform-setup/SKILL.md`
- `.claude/skills/onboarding/SKILL.md`
- `.claude/skills/sync-template/SKILL.md`
Expand Down Expand Up @@ -140,7 +141,14 @@ O quickstart usa shadcn + Tailwind v4. Forks antigos podem nao ter:
- Vite plugin `@tailwindcss/vite` no `vite.config.ts`
- Deps no `package.json`: `tailwindcss@^4`, `@tailwindcss/vite`, `tw-animate-css`, `class-variance-authority`, `clsx`, `tailwind-merge`, `lucide-react`, `@radix-ui/react-slot`

Se o fork ja tem `components.json` → nada a fazer (respeite o scaffold existente, nao sobrescreva `src/components/ui/*`).
Se o fork ja tem `components.json`:
- NAO sobrescreva primitivas que ja existem em `src/components/ui/*` — o dev pode ter customizado.
- **Mas** copie primitivas do template que estao **ausentes** no fork. O template pre-instala um baseline (`button`, `card`, `table`, `input`, `textarea`, `label`, `select`, `checkbox`, `switch`, `dialog`, `alert-dialog`, `sheet`, `dropdown-menu`, `tooltip`, `badge`, `separator`, `tabs`, `skeleton`) pra devs nao caírem em `<input class="...">` nativo por atrito. Para cada primitiva do template nao presente no fork:
```bash
cp "$TEMPLATE_DIR/src/components/ui/<primitive>.tsx" \
"src/components/ui/<primitive>.tsx"
```
Liste as primitivas adicionadas no resumo final.

Se o fork NAO tem, pergunte:
> O quickstart atual usa shadcn + Tailwind v4 pra alinhar com o shell. Seu fork ainda usa CSS vanilla (`src/styles/module.css`). Migrar agora?
Expand Down
12 changes: 11 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ This module implements `ModuleManifest` from `@platform/shell-contract`. The she
2. `mount(container, context)` — render into the shell's module area
3. `unmount()` — cleanup when navigating away

## Multiple pages — sidebar nav, not tabs

The shell matches the active module by `pathname.startsWith("/<module-id>")`, so `/my-module`, `/my-module/items`, `/my-module/settings` all land on the same bundle. Each sub-route you want users to reach must be declared in [module.json](module.json) under `navigation[]` — the shell renders those entries in its sidebar with permission filtering and deep-linking.

To switch pages inside the module, use `useModuleRoute(moduleManifest.route)` ([src/hooks/useModuleRoute.ts](src/hooks/useModuleRoute.ts)). It listens for `popstate` (the shell re-dispatches it on every React Router navigation) and returns the current `subPath`. [src/components/App.tsx](src/components/App.tsx) shows the switch pattern against [src/components/pages/](src/components/pages/).

Use `Tabs` from `@/components/ui/tabs` **only** for tightly-coupled sub-views inside a single page. Distinct pages always belong in `navigation[]`.

## ShellContext API

```typescript
Expand Down Expand Up @@ -97,13 +105,15 @@ browser

The module is wired to the same design system as the Backoffice Shell: **shadcn primitives on top of Tailwind v4**. Use utility classes and shadcn components, never hand-written CSS.

- Import primitives from `@/components/ui/*` (`Button`, `Card`, etc). Add more with `npx shadcn@latest add <name>` — the CLI reads `components.json` and drops the file under `src/components/ui/`.
- Import primitives from `@/components/ui/*`. The template pre-installs a baseline: `button`, `card`, `table`, `input`, `textarea`, `label`, `select`, `checkbox`, `switch`, `dialog`, `alert-dialog`, `sheet`, `dropdown-menu`, `tooltip`, `badge`, `separator`, `tabs`, `skeleton`. Add more with `npx shadcn@latest add <name>` — the CLI reads `components.json` and drops the file under `src/components/ui/`.
- Use Tailwind tokens that map to the shell's CSS variables: `bg-background`, `text-foreground`, `text-muted-foreground`, `bg-primary`, `text-primary-foreground`, `bg-secondary`, `bg-muted`, `border`, `bg-destructive`, `bg-cora-positive`, `bg-cora-negative`, `bg-cora-alert`, `bg-cora-info`. The mapping lives in `src/styles/globals.css` under `@theme inline` and the variables themselves come from the shell at runtime, so light/dark switch automatically.
- Compose with `cn(...)` from `@/lib/utils` when you need conditional classes.
- `useShellTheme(context)` is still available if you need JS-level branching (rare — prefer CSS-only with dark variants).

**NEVER hardcode colors** (hex, rgb, named). **NEVER write a new `.css` file for component styling.** If a token you need does not exist, add it to the shell first so every module stays in sync.

ESLint enforces this: raw `<table>`, `<input>`, `<select>`, `<dialog>`, `<textarea>` and any `#hex`/`rgb()`/`hsl()` literal in `src/**/*.{ts,tsx}` fail the build. `src/components/ui/**` is exempt because shadcn primitives wrap native elements internally.

---

## Code Quality Rules
Expand Down
38 changes: 38 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
import js from "@eslint/js";
import tseslint from "typescript-eslint";

const BANNED_NATIVE_TAGS = [
{ tag: "table", replacement: "Table from @/components/ui/table" },
{
tag: "input",
replacement:
"Input from @/components/ui/input (or Checkbox/Switch/RadioGroup for type=checkbox/radio)",
},
{ tag: "select", replacement: "Select from @/components/ui/select" },
{ tag: "dialog", replacement: "Dialog or AlertDialog from @/components/ui/*" },
{ tag: "textarea", replacement: "Textarea from @/components/ui/textarea" },
];

const nativeTagRestrictions = BANNED_NATIVE_TAGS.map(({ tag, replacement }) => ({
selector: `JSXOpeningElement[name.name='${tag}']`,
message: `Do not use <${tag}>. Use ${replacement}.`,
}));

const hardcodedColorRestriction = {
selector:
"Literal[value=/(^|[^a-zA-Z0-9])#[0-9a-fA-F]{3,8}\\b|\\brgba?\\s*\\(|\\bhsla?\\s*\\(/]",
message:
"Hardcoded colors are not allowed. Use Tailwind tokens that map to shell CSS variables (bg-primary, text-muted-foreground, bg-cora-positive, etc).",
};

export default [
js.configs.recommended,
...tseslint.configs.recommended,
Expand All @@ -22,6 +46,20 @@ export default [
rules: {
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
"@typescript-eslint/no-explicit-any": "warn",
"no-restricted-syntax": [
"error",
...nativeTagRestrictions,
hardcodedColorRestriction,
],
},
},
{
// shadcn primitives wrap native elements and use CVA color strings —
// they are the only place where native tags and token-less CSS values
// are allowed. Do not add your own files here.
files: ["src/components/ui/**/*.{ts,tsx}"],
rules: {
"no-restricted-syntax": "off",
},
},
];
4 changes: 3 additions & 1 deletion module.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"route": "/my-module",
"permissions": ["my-module:read", "my-module:write"],
"navigation": [
{ "label": "My Module", "path": "/my-module", "requiredPermission": "my-module:read" }
{ "label": "Overview", "path": "/my-module", "requiredPermission": "my-module:read" },
{ "label": "Items", "path": "/my-module/items", "requiredPermission": "my-module:read" },
{ "label": "Settings", "path": "/my-module/settings", "requiredPermission": "my-module:write" }
],
"hasBackend": true,
"healthCheck": "/health",
Expand Down
Loading