From 25ac80efdd3210fc4f1a380cc2eee4189e3ce7f0 Mon Sep 17 00:00:00 2001 From: Kleber Rocha Date: Fri, 8 May 2026 17:28:20 -0300 Subject: [PATCH 1/2] feat: V2 accent + icon adoption in template, primitives, and /check-module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .claude/skills/check-module/SKILL.md | 47 ++++++++++++++++++++++++++++ README.md | 41 ++++++++++++++++++++++++ module.json | 2 ++ src/components/ui/input.tsx | 5 ++- src/components/ui/select.tsx | 3 +- src/components/ui/tabs.tsx | 5 ++- 6 files changed, 100 insertions(+), 3 deletions(-) diff --git a/.claude/skills/check-module/SKILL.md b/.claude/skills/check-module/SKILL.md index 6294d70..584e48e 100644 --- a/.claude/skills/check-module/SKILL.md +++ b/.claude/skills/check-module/SKILL.md @@ -148,6 +148,53 @@ fi Necessario para `platform-setup` rodar. Se vazio ou ainda contem `stage.cora.team` num modulo prod, sinalize. +### 2.9 — `accent` no enum V2 (severity: error quando declarado, warn quando ausente) + +O shell aceita 7 valores fechados pro `accent`. Se declarado fora desse set, o `POST /api/modules` rejeita. Se ausente, o shell auto-infere via hash deterministico do `id` — sem custo, mas vale informar pro dev: + +```bash +ACCENT=$(jq -r '.accent // ""' module.json) +case "$ACCENT" in + "") + PROBLEMS+=("aviso: accent ausente — shell vai auto-inferir do hash do id (estavel, mas sem identidade declarada)") + ;; + coral|amber|violet|teal|blue|rose|lime) + : # ok + ;; + *) + PROBLEMS+=("erro: accent \"$ACCENT\" fora do enum (use coral|amber|violet|teal|blue|rose|lime)") + ;; +esac +``` + +### 2.10 — `icon` ausente (severity: warn) + +`icon` e nome lucide em kebab-case. Sem ele, o shell usa o default do `group`. Se ambos `accent` e `icon` ausentes, modulos do mesmo grupo ficam visualmente indistinguiveis na grid: + +```bash +ICON=$(jq -r '.icon // ""' module.json) +if [ -z "$ICON" ]; then + PROBLEMS+=("aviso: icon ausente — chip vai usar default do group \"$GROUP\"") +fi +``` + +### 2.11 — Hardcoded colors no `src/` (severity: warn) + +Modulos devem consumir CSS custom properties (`var(--token)`) em vez de fixar hex. Cores fixas escapam quando o shell troca de tema (light/dark) ou quando o palette V2 evolui: + +```bash +HARDCODED=$(grep -rE "#[0-9a-fA-F]{3,8}\b|rgb\(|rgba\(" src/ --include='*.ts' --include='*.tsx' --include='*.css' 2>/dev/null \ + | grep -v "var(--" \ + | grep -vE "^\s*//|^\s*\*" \ + | head -10) +if [ -n "$HARDCODED" ]; then + COUNT=$(echo "$HARDCODED" | wc -l) + PROBLEMS+=("aviso: $COUNT cor(es) hardcoded em src/ — use var(--token) do shell para respeitar light/dark e o palette V2") +fi +``` + +> Lista os 10 primeiros como amostra. Falsos positivos sao possiveis (rgba em sombras, hex em SVG icons inline). E aviso, nao erro. + ## Passo 3 — Reportar Imprima um sumario humano em PT-BR: diff --git a/README.md b/README.md index bff2639..25bba99 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,47 @@ platform/configs/ # Values por ambiente (stage, prod) module.json # Metadados para auto-registro ``` +## Identidade visual — accent e icon + +Cada módulo aparece no grid da Home do shell como um **card** com um chip colorido (44×44) e um ícone. Dois campos opcionais no `module.json` controlam isso: + +```json +"accent": "coral", +"icon": "shield", +``` + +**accent** — cor do chip. Um destes 7 valores: + +| accent | hue (aprox.) | uso típico | +|---|---|---| +| `coral` | vermelho coral | ações principais / regulatório | +| `amber` | âmbar | configuração / toggles | +| `violet` | roxo | orquestração / fluxo | +| `teal` | verde-azul | aprendizagem / coleta | +| `blue` | azul | ferramentas / API | +| `rose` | rosa | suporte / pessoas | +| `lime` | verde-lima | dados / analytics | + +**icon** — nome do ícone [lucide](https://lucide.dev/icons/) em kebab-case (ex.: `shield`, `git-branch`, `database`, `book-open`, `key`). + +### Os dois são opcionais + +Se você não declarar **accent**, o shell pega 1 dos 7 valores via hash determinístico do `id` do módulo — mesmo `id` sempre cai na mesma cor, em qualquer sessão e dispositivo. + +Se você não declarar **icon**, o shell usa um default por `group`: + +| group | icon default | +|---|---| +| `operations` | `layers` | +| `finance` | `wallet` | +| `customer-support` | `life-buoy` | +| `analytics` | `chart-line` | +| `settings` | `settings` | + +> Sem custo cognitivo no dia 1 — declare quando quiser dar identidade visual ao módulo, deixe vazio quando estiver tudo bem com o auto. + +A skill `/check-module` valida se o `accent` declarado está no enum e avisa quando os dois estão ausentes (informativo — não bloqueia deploy). + ## Sidebar — Navegação e Perspectivas Por padrão o `module.json` declara um **menu único** no campo `navigation` (caso da maioria dos módulos): diff --git a/module.json b/module.json index 5a695a5..7d9666a 100644 --- a/module.json +++ b/module.json @@ -6,6 +6,8 @@ "version": "0.1.0", "group": "operations", "order": 20, + "accent": "coral", + "icon": "shield", "permissions": ["my-module:read", "my-module:write"], "navigation": [ { "label": "Overview", "path": "/my-module", "requiredPermission": "my-module:read" }, diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index f1124ae..a27ca77 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -8,7 +8,10 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) { type={type} data-slot="input" className={cn( - "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30", + // Input bg follows --card so it stands off the page bg in the + // shell's V2 cream theme. The shadcn default (bg-transparent) + // blends into the cream and looks invisible. + "h-9 w-full min-w-0 rounded-md border border-input bg-card px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50", "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40", className diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index c0dc712..5a8f407 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -37,7 +37,8 @@ function SelectTrigger({ data-slot="select-trigger" data-size={size} className={cn( - "flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", + // Trigger bg follows --card to match the Input — same reasoning. + "flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-card px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 hover:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", className )} {...props} diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx index b463afd..4ed50a2 100644 --- a/src/components/ui/tabs.tsx +++ b/src/components/ui/tabs.tsx @@ -66,7 +66,10 @@ function TabsTrigger({ className={cn( "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent", - "data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground", + // Active tab uses --card (white panel) so it stands off the + // shell's V2 cream page bg. The shadcn default (bg-background) + // matches the page bg in the new theme. + "data-[state=active]:bg-card data-[state=active]:text-foreground data-[state=active]:shadow-[0_2px_6px_-2px_rgba(0,0,0,0.08)]", "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100", className )} From 041ea0d76c281df6c8bc06f90f357dbed7a1d2e0 Mon Sep 17 00:00:00 2001 From: Kleber Rocha Date: Fri, 8 May 2026 18:03:43 -0300 Subject: [PATCH 2/2] fix: address copilot review on V2 accent + icon adoption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tabs.tsx active shadow: replaced shadow-[0_2px_6px_-2px_rgba(0,0,0,0.08)] with Tailwind's shadow-sm utility. The literal rgba in the className would trip /check-module's new 2.11 hardcoded-color check on the template itself — embarrassing for a check shipped in the same PR. - /check-module 2.11 hardening: * Total count now computed before truncation (was counting only the first 10 lines) * Up to 5 sample matches now go into PROBLEMS as separate lines so the report is actionable instead of just "23 hits somewhere" * grep -n adds line numbers per match * Allowlist for false positives: - lines that are comments (//, *, /*) - lines that ARE the CSS var declaration (`--token: #hex`) Both checked after stripping the file:line prefix grep -n adds. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/skills/check-module/SKILL.md | 21 ++++++++++++++------- src/components/ui/tabs.tsx | 6 ++++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/.claude/skills/check-module/SKILL.md b/.claude/skills/check-module/SKILL.md index 584e48e..b8777e4 100644 --- a/.claude/skills/check-module/SKILL.md +++ b/.claude/skills/check-module/SKILL.md @@ -180,20 +180,27 @@ fi ### 2.11 — Hardcoded colors no `src/` (severity: warn) -Modulos devem consumir CSS custom properties (`var(--token)`) em vez de fixar hex. Cores fixas escapam quando o shell troca de tema (light/dark) ou quando o palette V2 evolui: +Modulos devem consumir CSS custom properties (`var(--token)`) em vez de fixar hex. Cores fixas escapam quando o shell troca de tema (light/dark) ou quando o palette V2 evolui. + +O grep filtra falsos positivos comuns: comentarios (`//`, `*`, `/*`), declaracoes de CSS var (linhas que comecam com `--token:`), e qualquer linha que ja referencia `var(--`. O total de ocorrencias e contado **antes** do truncate, e ate 5 amostras vao no report pra dar contexto: ```bash -HARDCODED=$(grep -rE "#[0-9a-fA-F]{3,8}\b|rgb\(|rgba\(" src/ --include='*.ts' --include='*.tsx' --include='*.css' 2>/dev/null \ +HARDCODED=$(grep -rnE "#[0-9a-fA-F]{3,8}\b|rgb\(|rgba\(" src/ \ + --include='*.ts' --include='*.tsx' --include='*.css' 2>/dev/null \ | grep -v "var(--" \ - | grep -vE "^\s*//|^\s*\*" \ - | head -10) + | grep -vE "^[^:]+:\s*//|^[^:]+:\s*\*|^[^:]+:\s*/\*" \ + | grep -vE "^[^:]+:\s*--[a-zA-Z0-9_-]+:") + if [ -n "$HARDCODED" ]; then - COUNT=$(echo "$HARDCODED" | wc -l) - PROBLEMS+=("aviso: $COUNT cor(es) hardcoded em src/ — use var(--token) do shell para respeitar light/dark e o palette V2") + TOTAL=$(echo "$HARDCODED" | wc -l) + PROBLEMS+=("aviso: $TOTAL cor(es) hardcoded em src/ — use var(--token) para respeitar light/dark e o palette V2") + while IFS= read -r line; do + PROBLEMS+=(" $line") + done <<< "$(echo "$HARDCODED" | head -5)" fi ``` -> Lista os 10 primeiros como amostra. Falsos positivos sao possiveis (rgba em sombras, hex em SVG icons inline). E aviso, nao erro. +> `grep -n` traz numero da linha pra cada amostra ficar acionavel. Falsos positivos ainda sao possiveis (rgba em sombras decorativas, hex em SVG icons inline) — por isso e aviso, nao erro. ## Passo 3 — Reportar diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx index 4ed50a2..9251a28 100644 --- a/src/components/ui/tabs.tsx +++ b/src/components/ui/tabs.tsx @@ -68,8 +68,10 @@ function TabsTrigger({ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent", // Active tab uses --card (white panel) so it stands off the // shell's V2 cream page bg. The shadcn default (bg-background) - // matches the page bg in the new theme. - "data-[state=active]:bg-card data-[state=active]:text-foreground data-[state=active]:shadow-[0_2px_6px_-2px_rgba(0,0,0,0.08)]", + // matches the page bg in the new theme. Shadow uses Tailwind's + // shadow-sm utility so the template stays free of literal rgba + // values that /check-module's hardcoded-color check would flag. + "data-[state=active]:bg-card data-[state=active]:text-foreground data-[state=active]:shadow-sm", "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100", className )}