Ajuda do Up Web DS

Como classificar, importar, criar, validar e distribuir componentes do Design System interno da Up Web Studio.

Importar não significa aprovar.

Todo item externo entra como matéria-prima e precisa passar por normalização, examples, testes, preview e validação antes de stable.

Introdução

O Up Web DS é uma camada de normalização entre fontes externas, componentes internos e os produtos da equipe. Nada entra por conveniência: cada item declara o que é, de onde veio, sob qual licença, e o que já foi verificado nele.

Esta página é a referência rápida para pessoas. AGENTS.md continua sendo a referência de engenharia para agentes, e as duas descrevem a mesma política — a taxonomia e as tabelas aqui são lidas de src/catalog/taxonomy.ts, o mesmo módulo que o CLI e o ds:validate usam para aceitar ou recusar um item.

Como classificar um item

A metadata tem três eixos independentes. Responder aos três é o que transforma um arquivo em um item do design system.

type
Onde o item vive na arquitetura.
category
O que o item faz na interface.
domain
Em qual contexto de produto ele ganha significado.

Os três eixos são independentes. Nunca use um deles para representar outro: category não é layer, domain não é categoria, e type não é o lugar onde o arquivo mora.

Guia de decisão

É uma unidade genérica de UI?
primitive
É só uma aparência diferente de um primitive existente?
recipe
É uma composição reutilizável com significado?
component
Resolve um fluxo recorrente?
pattern
É uma seção grande de página?
block
É um ponto de partida de página ou app?
template

Type

type é o nível de abstração — o que o item é arquiteturalmente. São seis valores, e a lista é fechada.

Types do Up Web DS
TypeO que éDomain
primitiveA menor unidade genérica de UI.Button · Input · Textarea · Checkbox · Radio Group · Select · Switch · Dialog · Popover · Tooltip · Tabs · Accordionnull
recipeUma variação visual construída sobre um primitive que já existe.Shimmer Button · Neumorph Button · Glass Card · Glow Buttonnull
componentComposição reutilizável com significado próprio, acima de um primitive.Search Field · Data Toolbar · Question Card · Product Card · Course Cardobrigatório
patternSolução recorrente para um fluxo ou problema de interação.Search Filters · Delete Confirmation · File Upload Flow · Quiz Navigation · Checkout Stepsobrigatório
blockSeção grande e reutilizável de uma página ou experiência.Hero · Pricing Section · Product Grid · Course Sidebar · Checkout Summaryobrigatório
templateEstrutura quase completa, usada como ponto de partida.Learning App Shell · Dashboard · Product Page · Storefront · Marketing Landing Pageobrigatório

layout não é um type. Uma estrutura pequena e genérica é primitive com category: 'layout'; uma estrutura grande é block ou template. Foundation também não é um type: tokens, tipografia e a escala de motion vivem na folha de estilo da Foundation, não em um diretório de item.

Category

category é a família funcional: o que o item faz na interface. Não representa layer e não representa domínio.

Primitives e recipes

Categorias de primitives e recipes
CategoryCobre
actionButton, Toggle, Toggle Group.
inputInput, Textarea.
selectionCheckbox, Radio Group, Select, Combobox, Switch. Select usa popup internamente, mas a função é seleção.
overlayDialog, Alert Dialog, Popover, Tooltip, Hover Card, Sheet, Drawer.
navigationTabs, Breadcrumb, Pagination.
disclosureAccordion, Collapsible.
feedbackAlert, Progress, Skeleton, Spinner.
data-displayBadge, Avatar, Separator, Table.
layoutScroll Area, Resizable, Aspect Ratio.

Um Select abre um popup para funcionar, mas a função é escolher um valor: ele é selection, não overlay.

Por domínio

Componentes de domínio não são forçados nas categorias de primitive quando isso destrói o significado. O domínio application reaproveita as famílias funcionais: uma Command Palette é overlay e uma Filter Bar é selection.

Categorias aceitas por domínio
DomainCategories
Applicationactioninputselectionoverlaynavigationdisclosurefeedbackdata-displaylayout
Educationassessmentlearningcourseprogresscontentlayout
Ecommerceproductcartcheckoutpricingorderlayout
Marketingherosocial-prooffeaturespricingctacontentlayout
Legallegislationjurisprudencecitationannotationlegal-contentlayout

Domain

domain é o contexto de produto em que o item ganha significado. primitive e recipe são sempre null; os demais types exigem um domínio.

Domínios do Up Web DS
DomainCobreExemplos
applicationComposições genéricas de dashboards, sistemas administrativos e ferramentas internas.Search Field · Data Toolbar · Command Palette · Filter Bar
educationAprendizagem, avaliação e progresso do aluno.Question Card · Flashcard · Lesson Progress · Course Card
ecommerceCatálogo, carrinho, checkout e pós-venda.Product Card · Cart Item · Checkout Steps · Product Gallery
marketingPáginas de aquisição, prova social e conversão.Hero · Pricing Section · Testimonials · CTA Section
legalLegislação, jurisprudência e material jurídico anotado.Legal Citation · Legislation Viewer · Jurisprudence Card · Annotation Panel

Domínio é classificação, nunca comportamento: nenhum item muda o que faz em runtime por causa do seu domain.

Exemplos de classificação

Exemplos de classificação
ItemTypeCategoryDomain
ButtonprimitiveactionDomain-free
InputprimitiveinputDomain-free
Radio GroupprimitiveselectionDomain-free
SelectprimitiveselectionDomain-free
DialogprimitiveoverlayDomain-free
AccordionprimitivedisclosureDomain-free
Question Cardcomponentassessmenteducation
Product Cardcomponentproductecommerce
Pricing Sectionblockpricingmarketing
Learning App Shelltemplatelayouteducation

Na interface, domain: null aparece como Domain-free. No código continua sendo null.

Lifecycle

Todo item nasce incoming e sobe por prova, não por decisão. Somente stable entra no registry consumível.

  1. incomingAparece no catálogo, fora do registry.Scaffold ou matéria-prima recém-ingerida. A normalização ainda não aconteceu.
  2. experimentalAparece no catálogo, fora do registry.Implementação em avaliação: existe, funciona, mas o contrato ainda pode mudar.
  3. stableÚnico status publicado no registry consumível.Contrato aprovado. Exige metadata, examples, testes, tokens, acessibilidade, dark mode, responsivo, ícones semânticos, motion e validators verdes.
  4. deprecatedAparece no catálogo, não é distribuído como item novo.Mantido por compatibilidade e documentação, com substituto planejado.

Adicionar do shadcn/ui

O caminho tem duas etapas separadas de propósito: inspecionar não baixa nada para src/, e promover é uma decisão explícita.

1. Inspecionar

Inspecionar Select
npm run ds:inspect -- \
  --source shadcn \
  --name select

Mostra staging, licença, dependências, ícones semânticos, normalizações conhecidas e findings — sem escrever em src/.

2. Adicionar — fluxo interativo

Adicionar Select (interativo)
npm run ds:add -- \
  --source shadcn \
  --name select \
  --type primitive \
  --category selection

O CLI pergunta se deve criar o scaffold. Responder `y` cria o item em src/ como incoming.

3. Adicionar — fluxo explícito

Adicionar Select sem prompts
npm run ds:add -- \
  --source shadcn \
  --name select \
  --type primitive \
  --category selection \
  --promote

Sem --promote, ds:add escreve apenas em .tmp/ds-ingest (staging) e .ds/ingestion (proveniência).

Um scaffold, não dois

Não execute --promote depois de já responder y ao scaffold. O item já existe e o CLI reporta conflito — corretamente.

Outros exemplos

Inspecionar Dialog
npm run ds:inspect -- \
  --source shadcn \
  --name dialog
Adicionar Dialog
npm run ds:add -- \
  --source shadcn \
  --name dialog \
  --type primitive \
  --category overlay
Inspecionar Radio Group
npm run ds:inspect -- \
  --source shadcn \
  --name radio-group
Adicionar Radio Group
npm run ds:add -- \
  --source shadcn \
  --name radio-group \
  --type primitive \
  --category selection

Adicionar de outras fontes

A fonte não define o type. Um item do Magic UI pode ser uma recipe, um do Kibo pode ser um component, e o mesmo registry pode fornecer os dois — a classificação é decidida depois do inspect, com o código à vista.

Fontes suportadas pelo CLI
SourceNomeLicençaSituação
shadcnshadcn/uiMITRegistry HTTP configurado.
cossCoss UIMITRegistry HTTP configurado.
kiboKibo UIMITRegistry HTTP configurado.
magicMagic UIMITRegistry HTTP configurado.
cultCult UIMITRegistry HTTP configurado.
ncdaiChánh ĐạiMITRegistry HTTP configurado.
tailwindPlusTailwind Plusinternal-restrictedLicenciada: entra por --from-file, sob revisão humana.

Magic UI — recipe sobre primitive

Inspecionar Shimmer Button
npm run ds:inspect -- \
  --source magic \
  --name shimmer-button
Adicionar como recipe de Button
npm run ds:add -- \
  --source magic \
  --name shimmer-button \
  --type recipe \
  --category action \
  --base button

Se é aparência de Button, é uma recipe do Button oficial. Não se cria um segundo primitive.

Kibo UI — componente composto

Inspecionar Gantt
npm run ds:inspect -- \
  --source kibo \
  --name gantt
Adicionar Gantt
npm run ds:add -- \
  --source kibo \
  --name gantt \
  --type component \
  --domain application \
  --category data-display

A categoria final é confirmada semanticamente depois do inspect, não deduzida da fonte.

Coss UI — primitive que já existe

Comparar com o Button oficial
npm run ds:compare -- \
  --source coss \
  --name button \
  --against button

Comportamento genérico melhor → incorporar ao primitive oficial. Aparência diferente → recipe.

Não existe Button2, CossButton nem ButtonV2. Se o item externo tem comportamento genérico melhor, ele é incorporado ao primitive existente para que todas as recipes herdem.

Cult UI e Chánh Đại

Inspecionar item de Cult UI
npm run ds:inspect -- \
  --source cult \
  --name <item>
Inspecionar item de Chánh Đại
npm run ds:inspect -- \
  --source ncdai \
  --name <item>

Criar do zero

Antes de implementar: procure primitives existentes, componha Button/Radio/Badge em vez de duplicar comportamento de foundation, e não acople o item a Firebase, Supabase, Prisma, Server Actions ou autenticação. Um item recebe dados e devolve eventos por props.

Criar Question Card
npm run ds:create -- \
  --type component \
  --domain education \
  --name question-card \
  --category assessment \
  --description "Enunciado, alternativas e feedback de uma questão."

O scaffold nasce incoming: implementação, examples, testes e revisão de acessibilidade são do autor.

Estrutura gerada
src/components/education/question-card/
├── question-card.tsx
├── question-card.examples.tsx
├── question-card.meta.ts
├── question-card.test.tsx
└── index.ts
API correta: dados e callbacks
<QuestionCard
  question={question}
  value={value}
  onValueChange={setValue}
/>

Adicionar código licenciado

Fontes comerciais não são baixadas automaticamente. O código chega por --from-file, sob revisão humana de licença.

Ingerir um Hero do Tailwind Plus
npm run ds:add -- \
  --source tailwindPlus \
  --name hero \
  --from-file ./hero.tsx \
  --type block \
  --domain marketing \
  --category hero

Acrescente --promote quando a revisão terminar; sem ele o item fica em staging.

Uso não é redistribuição

Tailwind Plus e outras bibliotecas comerciais nascem distribution: 'restricted'. O build do registry as exclui automaticamente (DS004). Usar em uma entrega da Up Web Studio não dá direito de redistribuir por registry.

Restrito não é isento: o código licenciado passa pelos mesmos tokens, primitives, API pública, light/dark, acessibilidade, responsividade, testes e preview que qualquer outro item.

Quando o componente já existe

Se ds:add reportar conflito, não force outro nome. Compare.

Comparar antes de decidir
npm run ds:compare -- \
  --source coss \
  --name button \
  --against button
O que fazer com um item que já existe
SituaçãoDecisão
Comportamento genérico melhorIncorpore ao item existente, para que todas as recipes herdem.
Aparência diferenteRecipe sobre o primitive oficial.
Semântica de domínio novaComponent ou pattern — se realmente houver uma solução nova, não só um nome novo.

Preservação da API upstream

Normalizar um item externo preserva, por padrão, a API pública observável, o comportamento funcional e a identidade visual dele. O que pode mudar é a implementação: primitives, imports, tokens, semantic icons, acessibilidade, foco, reduced motion e infraestrutura.

base é implementação, não API

base diz qual primitive a recipe reutiliza por dentro. Ela nunca é a origem da API pública. Uma recipe cuja API virou ComponentProps<typeof Dialog> deixou de ser o componente que foi ingerido — é isso que DS_API_003 reporta.

O que a normalização pode e não pode fazer com a API
MudançaPolítica
Remover ou renomear uma prop públicaFAIL. Só com decisão registrada no ingestion record.
Opcional vira obrigatóriaFAIL.
Tipo incompatível, variante removidaFAIL.
Obrigatória vira opcionalPermitido.
Nova prop opcionalPermitido.
Comparar API, capabilities e reuso
npm run ds:compare -- \
  --source magic \
  --name hero-video-dialog \
  --against hero-video-dialog

ds:compare separa quatro dimensões: compatibilidade de API, reuso de implementação, delegação de capability e preservação visual.

Uma capability que vive no primitive base aparece como delegated(dialog), não como ausente. Não copie uma implementação ruim do upstream só para as colunas baterem: um role="button" manual não é melhor que o Dialog oficial.

Uma quebra pode acontecer — registrada

Grave "publicApi": { "preserve": false } no .ds/ingestion/<source>-<id>.json, com o motivo. É o único jeito de DS_API_001 e DS_API_003 pararem de reportar, e deixa a decisão auditável ao lado da proveniência.

Normalização obrigatória

A automação detecta e relata; ela não decide semântica. Qual token um azul representa é decisão de quem tem contexto de produto. Esta é a lista que um item precisa fechar antes de reivindicar stable.

Classificação e origem

  • type, category e domain corretos
  • source e licença registrados
  • primitives existentes reutilizados
  • API pública normalizada

Foundation

  • tokens semânticos de cor, radius e sombra
  • ícones semânticos do adapter
  • motion semântico (duration-fast / default / slow)
  • light mode
  • dark mode
  • responsivo

Acessibilidade

  • teclado
  • foco visível
  • leitor de tela quando aplicável
  • touch quando aplicável
  • reduced motion quando aplicável

Prova

  • examples
  • testes unitários e de integração
  • E2E quando a afirmação depende de layout
  • preview

Remover um item

ds:remove tira o item e tudo que foi gerado a partir dele. Nunca tira o que ele apenas usa: primitive base, hook compartilhado, semantic icon, style capability, token da Foundation e dependência npm ficam.

Ver o plano (dry-run, padrão)
npm run ds:remove -- \
  --name hero-video-dialog

Sem flag nenhuma, ds:remove não altera um único arquivo.

Remover de verdade
npm run ds:remove -- \
  --name hero-video-dialog \
  --confirm

Regenera catalog, docs, registry e cli:sync, e depois roda ds:validate.

Flags de ds:remove
FlagO que faz
sem flagDry-run: mostra o que sairia, o que ficaria e quem ainda referencia o item.
--confirmRemove com as guardas ligadas. O ingestion record é mantido e carimbado com removedAt; o staging em .tmp sai, porque é cache.
--purgeRemove também o histórico: ingestion record e staging.
--forceRemove mesmo com consumidores. O repositório para de compilar até que eles sejam atualizados.
--keep-ingestionNão encosta no ingestion record, nem para carimbar.

Consumidores bloqueiam

Se alguma coisa fora do item ainda importa o módulo, usa o id como string ou renderiza o componente, a remoção normal para e não escreve nada. --force existe para quando isso é exatamente o que se quer.

Documentação do item

A página de um item ganha Usage, Props e Composition quando elas existem — e nenhuma delas é escrita na página. npm run ds:docs deriva tudo do source e grava src/catalog/docs.generated.ts; o catálogo lê dado puro.

De onde vem cada parte da documentação
FonteDo que ela é responsável
TypeScriptProps, tipos, obrigatoriedade, defaults do destructuring, e os valores reais de um VariantProps<typeof xVariants>.
JSDocDescrição da prop, @default e @deprecated — inclusive o que vem do Base UI.
*.examples.tsxO Usage. É código real, que o preview renderiza e o typecheck compila.
metadata.docsSó o que não se deriva: qual example usar, a árvore de composition, ordem e ocultação.

Props herdadas não entram na tabela. Uma prop declarada em @types/react é atributo de HTML, e listar as 285 de um <button> enterra as duas que são do design system — elas viram uma linha de “Estende”. Prop semântica que uma biblioteca de comportamento adiciona (render, onValueChange) é prop do item e é documentada.

Composition é declarada, nunca inferida

A regra não é “mais de um export”: Button exporta Button e buttonVariants e não tem composição. Ela existe quando há uma estrutura recomendada de pai e filho entre partes públicas — e aí a árvore vive na metadata.

metadata.docs de um item composto
docs: {
  usage: { example: 'basic' },
  composition: {
    description: 'Os gatilhos ficam agrupados na TabsList.',
    tree: [
      { name: 'TabsList', children: [{ name: 'TabsTrigger' }] },
      { name: 'TabsContent' },
    ],
  },
}

Export utilitário (buttonVariants, TabsPrimitive) não é componente: não entra na árvore nem na tabela.

Erros de documentação
CódigoO que ele recusa
DS_DOCS_001docs.usage.example nomeia um example que o arquivo não exporta.
DS_DOCS_002A composition referencia um componente que o item não exporta.
DS_DOCS_003A metadata referencia uma prop ou um componente que não existe.
DS_DOCS_004O root da composition não é um componente público do item.
DS_DOCS_005O example não tem código próprio para a tab Code do preview mostrar.

Em incoming e experimental os cinco são aviso; em stable são erro. Documentação quebrada não chega ao registry, e um acervo em normalização não trava o CI.

Particles

Um Particle é uma composição copiável de itens que já existem, construída sobre um example real, publicada em /particles. Ele mostra como primitives, recipes e os demais itens se combinam para resolver uma necessidade concreta de interface — um date picker, uma paleta de comandos, um painel de filtros.

Particle não é um type

A lista de types continua fechada em seis valores. Particle é documentação executável: não entra na taxonomia, não entra no registry como item, não tem lifecycle próprio e não se instala pela CLI. up-web-ds add continua instalando itens reais.

Como publicar um example na galeria

Uma linha na metadata do item dono do example. O código continua vivendo em *.examples.tsx — nunca uma string de JSX no .meta.ts, e nunca uma pasta paralela com cópias.

docs.particles de um item
docs: {
  particles: [
    {
      example: 'withForm',
      title: 'Edição rápida em Popover',
      description: 'Um campo e duas ações ancorados ao item.',
      tags: ['form', 'inline-edit'],
    },
  ],
}

Depois, npm run ds:docs. O resto — id, uses, código, readiness — é derivado.

O que é declarado e o que é derivado
CampoDe onde vem
exampleDeclarado. Nome do export no *.examples.tsx do item. É a implementação: não existe segunda cópia dela em lugar nenhum.
title, description, tagsDeclarados. Só o que um gerador não teria como saber: como a composição se chama e por quais palavras alguém a procuraria.
idDerivado: <item>/<example>. Determinístico, nunca um uuid e nunca o title — dois itens podem ter composições com o mesmo nome.
usesDerivado dos imports do example. Só itens do catálogo entram: React, date-fns, cn, hooks de suporte e ícones são dependência de implementação, não parte da composição.
códigoDerivado. É o mesmo snippet da tab Code do preview, reendereçado pelo placement.ts do registry — o leitor copia @/components/ui/button, que é onde o item cai no projeto dele.
readinessDerivado dos status dos itens usados. Nunca escrito à mão.

Tags

Tags existem só para descoberta em /particles. Elas não substituem nem restringem type, category e domain, e não entram em src/catalog/taxonomy.ts. São lowercase e kebab-case: date-picker, nunca Date Picker ou date_picker. A interface imprime o rótulo; a metadata guarda o slug. A lista de tags e a contagem de cada uma são derivadas dos Particles existentes.

Readiness

Um Particle não tem status próprio: ele é tão pronto quanto os itens que compõe. Se algum deles é deprecated, o Particle é deprecated; se todos são stable, ele é ready; caso contrário, experimental. Os dois últimos aparecem no card como aviso em texto — nunca só por cor. Nada disso é escrito na metadata, e é por isso que um item que muda de status muda a galeria na mesma passada.

Quando promover a item do catálogo

Continua Particle enquanto for só uma composição útil: um exemplo que ninguém precisa versionar, instalar ou evoluir como unidade. Vira item do catálogo quando ganha API pública própria, precisa de testes como unidade, precisa de lifecycle ou passa a ser instalado por projetos. Aí vale a taxonomia normal — primitive, recipe, component, pattern, block, template — e o docs.particles sai.

Não confunda com docs.composition: aquela árvore descreve como as partes públicas de um item se relacionam (TabsList > TabsTrigger). Particle descreve como itens distintos se combinam (Button + Popover + Input). E Particle também não é recipe: uma recipe é uma variação visual de um primitive, não uma composição.

Erros de Particles
CódigoO que ele recusa
DS_DOCS_006docs.particles[].example nomeia um example que o arquivo não exporta.
DS_DOCS_007Um Particle sem tag, ou com uma tag que não é slug kebab-case.
DS_DOCS_008Dois Particles reivindicando o mesmo <item>/<example>.
DS_DOCS_009Um Particle construído sobre um item deprecated.
DS_DOCS_010Um import sob uma camada do DS que não resolve para nenhum item do catálogo.

Como os cinco anteriores: aviso em incoming e experimental, erro em stable. Um example sem código próprio continua sendo DS_DOCS_005 — o Particle apenas não é gerado, em vez de virar um segundo aviso sobre o mesmo fato.

Registrar um papel de ícone

Componente do DS não importa lucide-react: importa o papel de que precisa de @/lib/up-web-icons. O papel é o contrato — close, chevron-down, folder-open — e qual glifo o desenha é decisão do projeto que consome, em components.json → iconLibrary. Registrar um papel novo é preencher essa equivalência nos quatro providers e levá-la até quem depende dela.

Onde um papel de ícone vive
ArquivoO que ele guarda
src/catalog/types.tssemanticIconNames: os papéis que o sistema de tipos conhece. Escrito à mão.
scripts/ds/icons/mappings.tsA equivalência papel ↔ glifo nos quatro providers. Fonte única, escrita à mão.
src/lib/up-web-icons.tsxO adapter que os componentes importam. Gerado por ds:icons — editar à mão é desfeito no comando seguinte.
packages/cli/src/icons/*.generated.tsA projeção do mapping para a Consumer CLI, que roda onde este repositório não existe. Gerada por ds:cli:sync — e é ela que a resolução lê, dos dois lados.
registry/icons.jsonO mapping publicado, que a CLI instalada lê no add. Gerado por ds:registry.

O passo a passo

1. declarar o papel
// src/catalog/types.ts
export const semanticIconNames = [
  // …
  'folder-open',
] as const

SemanticIconName sai daqui. Papel não declarado não compila no mapping nem em metadata.icons.

2. mapear o papel nos quatro providers
// scripts/ds/icons/mappings.ts
'folder-open': {
  lucide: lucide('FolderOpenIcon'),
  phosphor: phosphor('FolderOpenIcon'),
  tabler: tabler('IconFolderOpen'),
  hugeicons: hugeicons('FolderOpenIcon'),
},

Equivalência de significado, não de nome: o Phosphor desenha um caret onde o Lucide desenha um chevron, e os dois dizem a mesma coisa.

3. projetar o mapping para a fronteira da CLI
npm run ds:cli:sync

Antes de gerar e antes de validar, não depois: a resolução lê packages/cli/src/icons/mappings.generated.ts, então pular este passo valida o mapping de ontem e o validador acusa um nome que você já corrigiu.

4. regenerar o adapter
npm run ds:icons

Reescreve src/lib/up-web-icons.tsx com os papéis que os itens declaram.

5. compilar o mapping nos quatro providers
npm run ds:icons:validate

Gera o adapter de cada provider num fixture e roda o tsc contra os pacotes reais. Glifo que a biblioteca não exporta reprova aqui, e não no projeto que instalou.

6. publicar o mapping no registry
npm run ds:registry

O icons.json leva o papel para quem instala. É por isso que papel novo não exige CLI nova.

7. fechar
npm run verify

ds:catalog e ds:docs entram junto quando o item que usa o papel também mudou.

O adapter só exporta o papel que alguém usa

ds:icons gera exatamente os papéis declarados em metadata.icons. Enquanto nenhum item importar o papel novo, o passo 4 não escreve nada — e isso não é falha: o mapping já está válido e publicável. Quando o item passar a usá-lo, ele importa de @/lib/up-web-icons e declara icons: ['folder-open'] na metadata. Código e metadata em desacordo é DS_ICON_005, nos dois sentidos.

A validação prova que o glifo existe, não que ele significa a coisa certa

O tsc reprova um nome que a biblioteca não exporta. Um nome que existe e quer dizer outra coisa passa: IconFolderOff é “pasta desativada”, não “pasta aberta”, e compila igual. Escolher o glifo é a única parte deste fluxo que nenhum comando faz por você.

Cobertura é governada pela maturidade de quem usa o papel, não pelo mapping (requireEveryProviderFrom em .ds/icons.ts). Papel usado por item stable sem os quatro providers é erro e barra a distribuição; usado só por incoming ou experimental, é aviso; sem nenhum item usando, é informativo.

Trocar o glifo de um papel que já existe

Corrigir o glifo de um papel é o mesmo fluxo sem o passo 1: o papel já está declarado, e o que muda é a equivalência. A diferença está do outro lado — o projeto que já instalou guarda o mapping em .up-web-ds.json, como um lockfile, e essa cópia não se atualiza sozinha.

No design system, depois de editar o mapping
npm run ds:cli:sync
npm run ds:icons
npm run ds:icons:validate
npm run ds:registry
npm run verify

Mesma ordem do passo a passo. ds:registry é o que publica o valor novo: sem ele, o registry continua servindo o glifo antigo.

No projeto que consome, para adotar o valor novo
npx up-web-ds init

init é o refresh: ele relê o icons.json do registry e regrava os papéis que o projeto já usa com o mapping publicado hoje. É idempotente.

icons sync não busca valor novo — ele é local por definição

icons sync e icons migrate não falam com o registry: os dois regeneram o adapter a partir do que .up-web-ds.json já guarda, e é justamente por isso que funcionam offline e para papel que a CLI instalada nunca conheceu. Para um glifo que mudou de valor, o que atualiza o registro é init — ou o próximo add, que também resolve contra o registry.

Validação

npm run verify encadeia biome, typecheck, testes, ds:catalog --check, ds:docs --check, ds:validate, ds:icons:validate, registry:validate e o build da CLI. É o portão que precisa estar verde antes de concluir qualquer tarefa — npm run check continua funcionando como alias.

Sequência recomendada
npm run ds:catalog
npm run ds:docs
npm run ds:validate
npm run ds:icons:validate
npm run verify
npm run verify:full
npm run ds:registry

verify:full antes de abrir a PR; ds:registry quando o item realmente virou stable.

O que cada comando responde
ComandoPergunta que responde
npm run ds:catalogO catálogo tipado reflete os *.meta.ts em disco?
npm run ds:docsUsage, Props e Composition ainda correspondem ao source?
npm run ds:validateAs políticas do design system estão satisfeitas (DS001–DS020, DS_ICON_*, DS_DOCS_*)?
npm run ds:icons:validateCada papel semântico compila nos quatro providers de ícone?
npm run verifyLint, tipos, testes, catálogo, políticas, registry e CLI — tudo o que o CI roda.
npm run verify:fullTudo acima, mais o build de produção e o Playwright completo. A PR pode ser aberta?
npm run test:e2eO que só existe com layout: contraste, alvo de toque, reduced motion, overflow.
npm run ds:registryO registry consumível corresponde à metadata atual?

Fluxo até a Pull Request

A regressão completa roda na sua máquina, antes da PR. O GitHub Actions fica com as verificações leves e determinísticas, e nada é executado duas vezes entre local, PR, main e Vercel.

  1. Desenvolvimentolocalnpm run verifyA cada ajuste relevante. Lint, tipos, testes unitários e políticas do DS — sem build e sem browser.
  2. Antes da PRlocalnpm run verify:fullObrigatório. Soma o build de produção e o Playwright completo: axe em todas as rotas, page-overflow e os specs de cada item.
  3. Pull RequestGitHub Actionsnpm run verifyO check Verify roda o mesmo comando do desenvolvimento. Um commit novo cancela o run anterior da mesma PR.
  4. Merge em mainGitHubnenhum jobNada se repete: o merge só leva para a main o que o check Verify já aprovou.
  5. DeployVercelnpm run buildRefaz e valida o registry e gera o build de produção. Não roda testes nem instala browser.

O CI não roda Playwright — de propósito.

Enquanto o repositório for privado, de uso interno e com equipe pequena, a regressão completa é responsabilidade de quem abre a PR. Um check Verify verde não prova contraste, layout nem acessibilidade: só npm run verify:full prova.

verify e verify:full
ComandoOnde rodaExecuta
npm run verifyLocal e no CI da PRbiome, typecheck, Vitest, checks de catálogo, docs, estilos e CLI, ds:validate, ícones, registry:validate e cli:verify.
npm run verify:fullLocal, antes da PRverify, depois o build do registry com security check, o next build, o trace check e o Playwright completo sobre esse mesmo build — sem compilar duas vezes.

Passo a passo

1. instalar as dependências
npm ci
npx playwright install chromium

O Chromium do Playwright é instalado uma vez por máquina; a suíte não usa outro browser.

2. validar enquanto desenvolve
npm run verify

Rode quantas vezes quiser: não faz build nem abre browser.

Testar um item só
npm run test:run -- tabs
E2E_BASE_URL=http://localhost:3000 npm run test:e2e -- tabs

O segundo comando roda o spec do item contra o npm run dev que já está aberto, sem esperar um build de produção.

3. validação completa, antes da PR
npm run verify:full

É a etapa demorada: build de produção e toda a suíte de Playwright, em desktop e mobile. Se falhar, npm run test:e2e:report abre o relatório.

4. abrir a Pull Request
git push -u origin <sua-branch>
gh pr create --base main --fill

Ou pela interface do GitHub. A main recebe código só por PR, nunca por push direto.

5. aguardar o check Verify
gh pr checks --watch

Se falhar, rode npm run verify: é exatamente o que o CI executa. Corrija e volte ao passo 2.

Depois de ajustar uma PR aberta

O que rodar depois de ajustar uma PR
A mudança tocaAntes do novo push
Comportamento, UI, acessibilidade ou responsividadenpm run verify:full
Infraestrutura de testes (tests/e2e, playwright.config.ts)npm run verify:full
Só documentação, sem impacto executávelnpm run verify

Quando não dá para rodar localmente

Regressão completa no GitHub, sob demanda
gh workflow run full-regression.yml --ref <sua-branch>

Roda o mesmo npm run verify:full em GitHub Actions e anexa o relatório do Playwright. É manual e não bloqueia o merge. Pela interface: Actions → Full regression → Run workflow.

verify:full não roda em hook de pre-push: um git push não pode prender o trabalho por vários minutos. A validação completa é uma ação consciente antes da PR.

Publicação no registry

Catálogo e registry respondem perguntas diferentes. O preview documenta o que a equipe está construindo; o registry distribui o que um produto pode instalar.

Catálogo e registry
SuperfícieO que contém
preview / catálogoincoming, experimental, stable e deprecated.
registry consumívelApenas stable, e nunca restricted.

Interface oficial de consumo

Instalar um item em um projeto
npx up-web-ds add dialog

Só este CLI lê components.json → iconLibrary, instala o provider certo e gera o adapter de ícones. O registry por baixo é infraestrutura que ele consome.

Não é o caminho oficial

npx shadcn add @up-web-ds/dialog instala os arquivos sem resolver a biblioteca de ícones do projeto. O componente chega sem o adapter que ele importa — e, sem a credencial que o CLI configura, sem chegar a lugar nenhum.

O item chega como um arquivo, na pasta que o components.json do projeto declara — nunca num caminho literal, porque quem decide se @ui é src/components/ui ou app/ui é o consumidor. A taxonomia daqui não viaja: os imports entre itens são reescritos no build, e o index.ts de cada item não é publicado.

Destino no consumidor
typetargetimport
primitive, recipe@ui/<id>.tsx@/components/ui/<id>
component, pattern@components/<id>.tsx@/components/<id>
block, template@components/<id>.tsx@/components/<id>
Instalar fora da pasta padrão
npx up-web-ds add badge --path src/components/ds

Desvio deliberado, não política de layout: o shadcn aplica --path a todos os arquivos daquele install, e os imports dentro deles continuam resolvendo pelos aliases do components.json. Um item por vez.

Onboarding de um projeto novo

A CLI vem do GitHub Packages privado; os componentes vêm do registry autenticado na Vercel. São duas credenciais diferentes, para dois serviços diferentes — o PAT do GitHub nunca é enviado para o registry.

1. autenticar o npm no scope da Up Web Studio
npm login --scope=@upwebstudio --auth-type=legacy --registry=https://npm.pkg.github.com

Uma vez por máquina, com permissão mínima de leitura de packages. Nunca comite um PAT.

2. instalar a CLI no projeto
npm install -D @upwebstudio/up-web-ds

O pacote leva só a CLI compilada: nenhum componente viaja dentro dele.

3. guardar o token do registry
npx up-web-ds auth set

Fica no perfil do usuário, com permissão 0600. Nunca no projeto, nunca no components.json.

4. conectar o projeto
npx up-web-ds init

Idempotente: configura registries["@up-web-ds"], registra a biblioteca de ícones e gera o adapter, sem tocar no resto do arquivo.

O token nunca entra no projeto

components.json guarda o nome da variável de ambiente, e o shadcn a expande no momento do fetch. Em CI, basta definir UP_WEB_DS_REGISTRY_TOKEN.

Novos componentes e a CLI

Componente novo não pede CLI nova. O icons.json do registry leva tudo o que a instalação precisa: o glifo de cada ícone nos quatro providers, o CSS de cada style capability, os hooks e os itens base. A CLI instalada lê isso no momento do add. A cópia que vem dentro do pacote serve só de reserva.

Quando um componente entra no DS

Quem mantém o design system faz isto uma vez por componente, depois que ele vira stable.

1. regenerar catálogo e documentação
npm run ds:catalog
npm run ds:docs
2. atualizar a cópia de reserva da CLI
npm run ds:cli:sync

Obrigatório quando o item trouxe ícone ou capability nova. Nos outros casos não muda nada, e o check recusa uma cópia velha.

3. validar tudo
npm run verify

Reprova se o item usa ícone sem mapping nos quatro providers, capability não descrita, base não publicado ou hook ausente, e diz o que falta.

4. gerar o registry
npm run ds:registry

Escreve registry.json e registry/icons.json. Recusa gerar um manifest com dependência que o registry não carrega.

5. publicar o registry
npm run verify:full
gh pr create --base main --fill

O merge da PR na main dispara o deploy da Vercel, que roda npm run build e refaz e valida o registry antes de servir /r/*. A CLI não é republicada.

No projeto que consome

Instalar o componente novo
npx up-web-ds add <item>

Com a CLI que já está no projeto. Ícones, CSS e hooks do item vêm do registry. O .up-web-ds.json passa a guardar o mapping dos ícones usados, e icons sync e icons migrate continuam funcionando offline.

Quando atualizar a CLI

Quando atualizar a CLI
SituaçãoAtualizar a CLI?
Componente novoNão. Vale também para ícone, style capability ou hook novo: basta o deploy do registry.
Projeto na CLI 0.1.0Sim, uma vez. A 0.1.0 só conhece os ícones embutidos e recusa os mais novos, como o sidebar.
Mudança em packages/cliSim. Comando, flag ou correção na CLI pede versão nova, e os projetos que precisam da mudança atualizam.
Erro DS_REGISTRY_PROTOCOLSim. O formato do icons.json mudou, e só uma CLI nova consegue lê-lo.

Publicar uma versão da CLI

Publicar é uma decisão explícita: nenhum merge publica a CLI sozinho.

1. subir a versão da CLI
cd packages/cli && npm version minor --no-git-tag-version && cd -

minor para componente ou comando novo, patch para correção (SemVer). Não cria commit nem tag, e a versão fica declarada só em packages/cli/package.json.

2. verificar o pacote localmente
npm run cli:verify

Typecheck, build, fronteira do pacote e npm pack --dry-run.

3. disparar a publicação
gh workflow run publish-cli.yml --ref main

Depois do merge na main. Pela interface: Actions → Publish Consumer CLI → Run workflow. Com -f dry_run=true, roda todos os gates sem publicar.

Atualizar a CLI em um projeto

1. instalar a versão mais recente da CLI
npm install -D @upwebstudio/up-web-ds@latest
2. conferir a versão instalada
npx up-web-ds --version
3. registrar os mappings de ícones do projeto
npx up-web-ds init

Idempotente. Grava no .up-web-ds.json o mapping atual dos ícones que o projeto já usa, e depois é só voltar ao add.

Troubleshooting rápido

Problemas frequentes
SintomaO que fazer
ds:add reporta conflito de idO item já existe. Rode ds:compare em vez de inventar outro nome.
Invalid metadata classificationA combinação type / category / domain não é aceita. A mensagem lista os valores válidos.
DS012: item não registrado no catálogoRode npm run ds:catalog depois de criar ou alterar metadata.
DS014: movimento sem política de Reduced MotionDeclare o que o movimento faz sob reduce: motion-safe: no decorativo, motion-reduce: encurtando o funcional. Trocar a duração não resolve.
DS016: duração fora da escala de motionSó em componente nativo: use duration-fast, duration-default ou duration-slow. Código ingerido preserva o timing upstream.
DS_ICON_002: mapping incompletoMapeie o papel nos quatro providers em scripts/ds/icons/mappings.ts e rode npm run ds:icons.
DS_ICON_001 ao rodar up-web-ds addNa CLI 0.1.0: atualize a CLI. Da 0.2.0 em diante, o registry publicou um item sem o mapping do ícone. É defeito no build do registry, então atualizar a CLI não resolve: rode npm run verify no design system.
up-web-ds add para com arquivos marcados como updateO item traz dependências (o sidebar traz button, sheet, tooltip…) e o projeto já tem uma versão diferente de algum desses arquivos. Faça commit das edições locais e responda y, ou rode de novo com --overwrite. Sem isso, nada é escrito.
DS_REGISTRY_PROTOCOLO registry mudou o formato do icons.json. Rode npm install -D @upwebstudio/up-web-ds@latest.
DS004: item restrito no registryConfira distribution: a licença de uso não autoriza redistribuição, e o registry exigir token não muda isso.
DS_REGISTRY_AUTH_REQUIREDO projeto não tem credencial do registry. Rode npx up-web-ds auth set, ou defina UP_WEB_DS_REGISTRY_TOKEN em CI.
DS_REGISTRY_UNAUTHORIZED (401)O registry recusou a credencial. O token pode ter sido rotacionado: peça o atual e rode npx up-web-ds auth set de novo.
DS_REGISTRY_NOT_FOUND (404)O registry publica apenas stable. Confira o nome do item nesta página de catálogo.
Check Verify falhou na PRRode npm run verify localmente: é exatamente o que o CI executa, com a mesma saída.
verify:full falha no PlaywrightAbra o relatório com npm run test:e2e:report e rode só o spec que falhou contra o npm run dev com E2E_BASE_URL.
DS011: stable sem Definition of DoneComplete examples, testes e a revisão de acessibilidade, ou volte o item para experimental.