Skip to content

Ve literal converter - #5

Merged
rhengles merged 89 commits into
mainfrom
ve-literal-converter
Jul 21, 2026
Merged

Ve literal converter#5
rhengles merged 89 commits into
mainfrom
ve-literal-converter

Conversation

@rhengles

Copy link
Copy Markdown
Member

No description provided.

rhengles and others added 30 commits May 24, 2026 15:05
Introduce generate-ve-theme scripts, audit playbook, and generated theme
family styles. Fix selector mappings for navbar collapse hooks, badge
variants, and bg utility rgba emission; verify modal, dropdowns, toasts,
tables, navbar, and badge at threshold 0.1.

Co-authored-by: Cursor <cursoragent@cursor.com>
…nsion, script tooling

Converts ve-project2 components from ad-hoc class strings to the VE literal
pattern across the full component tree (buttons, forms, UI components).
Expands theme contracts for utilities, badge, tables, navs, scrollspy, and toasts.
Adds ve2-literal-converter and ve2-literal-conversion-plan docs. Improves
capture-leaf-screenshots workflow and generate-ve-theme rule transpiler.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds two divergence actions to the literal emitter:
- overrideSelector: .dropdown-menu.show → dropdownMenuShow (VE JS hook)
- addMirrorRule: mirrors .btn.show rules with btnShowHook (VE JS hook)

Fixes dropdown-divider UA border by stamping horizontalRule on <hr>
in all 7 dropdown components so the Bootstrap hr reboot rule fires.
Removes the manually-patched border:0 from the generated bootstrap
literal styles and regenerates bootstrap with the clean generator.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pansion, all-theme literal fan-out

Extends the literal emitter (divergence manifest, emit logic, value-format), migrates UI components
(accordion, alerts, modals, navbar, popovers, tooltips, progress, button-group) to the literal style
loader, expands bootstrap theme styles across contents/forms/utilities/UI sections, and fans out
updated literal styles to all 26 non-bootstrap themes. Verified pixel-perfect on bootstrap (376/376
scenarios, 0 mismatches) with --style-loader=literal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
brite Bootswatch theme now verifies at 0.000000 for all 433 scenarios in
--style-loader=literal mode.

Generator/emitter/harness fixes (fan out to the remaining themes on regen):
- Root --bs-* vars are emitted by the literal emitter onto ${scope}${vars}
  via the shared contract registry (plan §6.3) instead of scope.css.ts. The
  one registry resolves both the writer and the reader, so a component var set
  at :root (e.g. --bs-carousel-indicator-active-bg in ui/carousel/_vars.css)
  can no longer be misrouted to the global _vars.css (the bug that left brite's
  carousel indicators invisible).
- scope-emitter: stop emitting the vars block; emit only body/frame/modal rules
  and import just the varBsBody* symbols they reference.
- parse-css-tree: walkCssEmitUnits now takes { includeRootVars } and surfaces
  :root / [data-bs-theme=dark] blocks as rootVars units (default off, so the v1
  pipeline is unaffected).
- capture harness: after the action, waitForVisualStable settles the anchor's
  CSS transition, then click/hover-visible scenarios dispatch a resize so Popper
  re-resolves the menu against the settled anchor. This makes brite's
  transition-animated dropdowns deterministic (was flaky 197/200/201).

Component fixes:
- Stamp the `link` element-contract on every <a> (brite's bare a{font-weight:500}
  rule only fires when the <a> carries its element contract). 34 components.
- TabbedNav: match the original app's curly typographic quotes byte-for-byte.

Baselines: re-captured brite from the original app (neo-brutalist button-shadow
refresh) using the deterministic harness; @screenshot directive heights updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…regen)

cerulean now verifies at 0.000000 for all 433 scenarios in --style-loader=literal
mode, with NO per-theme code changes — only:
- regenerate scope.css.ts (new vars-less format) + literal/styles.css.ts
- capture the full baseline set (cerulean's was incomplete — 252 button-outline /
  sizes scenarios had no baseline) from the original app with the deterministic
  harness

Every generator/emitter/harness/component fix from the brite work (root vars on
${scope}${vars} via the registry, the link element-contract stamps, the dropdown
Popper resize-nudge) fanned out automatically. The navbar-example mismatch seen
on the first pass was a stale baseline and cleared on re-capture.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ning themes to new scope format

quartz now verifies at 0.000000 for all 433 scenarios in --style-loader=literal
mode. Two generator-level fixes in generate-ve-theme/scope-emitter.mjs (fan out
to every theme on regen):

- Gradient body background: extractBodyDeclarations now merges ALL standalone
  `body {}` rules (gradient themes like quartz add a separate
  `body { background-image: linear-gradient(...) }` block), anchored on `}`/start
  so compound `[data-bs-theme=dark] body` is excluded; bodyFrame frame props now
  include background-image/background.
- display: flow-root on bodyFrame: makes it a block formatting context so
  descendant negative margins (Bootstrap `.row { margin-top: -gutter }`) can't
  collapse up through the padding-less wrapper and drag it above the viewport.
  Without it, bodyFrame's min-height:100vh ended short and the page background
  (gradient / dark body-bg, which the real <body> propagates to the canvas) was
  left uncovered at the bottom. Content positions are unchanged.

Also regenerates the remaining 23 Bootswatch themes' scope.css.ts (new vars-less
format) + literal/styles.css.ts. These are format-migrated but not yet verified;
each is finalized (incl. the gradient/flow-root fixes) on its own verification
pass. bootstrap/brite/cerulean unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…arness fix

All 433 sketchy literal scenarios verify at 0 diff (full --verify-ve-rendering
sweep, style-loader=literal).

Capture harness (scripts/capture-leaf-screenshots/playwright-actions.mjs): await
document.fonts.ready before the Popper resize-nudge in click-visible/hover-visible
scenarios. A lazily-loaded web font can change a floating element's width after it
opens — sketchy's Cabin Sketch in the HTML tooltip's <b> shrinks the box ~6px — and
Popper anchors once and never re-anchors on the swap, so its left stays width-stale.
Baseline vs VE then landed ~3px apart depending on font-load timing (a race; neither
side "right"). Gating the nudge on fonts.ready makes the final re-anchor use settled
metrics in both apps -> deterministic, matching positions. Generalizes to any
web-font-driven width change on tooltips/popovers/dropdowns/modals.

Plus sketchy literal conversion work: component/emitter updates and screenshot
rebaselines (incl. the html-tooltip baseline re-captured to the settled position).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ooltip round

- ve2-debugging-mismatch-walkthrough.md: new walkthrough for the sketchy
  html-tooltip 3px horizontal shift (pixel cross-correlation to prove a rigid
  shift, width-across-font-load probe, document.fonts.ready harness fix), a key
  lesson, pitfalls 5-7, and a horizontal-shift branch in the diagnostic tree.
- ve2-migration-playbook.md: gotcha rows for floating-element font-load races
  and the --style-loader=literal default trap.
- capture-leaf-screenshots.md: document the fonts.ready-gated Popper re-anchor
  for hover-visible/click-visible kinds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sandstone passed as-is — no emitter fix needed. Font-isolation
pitfall (.lead{300}) already handled by T5. Regenerated literal
styles + T9 progress tracker.

Also documents the preview-server port-4175 collision gotcha:
never run two captures concurrently or the browser attaches to a
half-dead server and hangs before the first per-scenario log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two general generator fixes (fan out to all themes on regen):

1. scope-emitter: extractBodyDeclarations now captures any rule whose
   comma-separated selector list contains a standalone `body`, not just
   a lone `body { }`. Picks up Materia's `body, input, button {
   letter-spacing: 0.1px }` so bodyText carries the letter-spacing
   (grouped selectors apply every declaration to each member).

2. divergence-manifest EXTRA_RULES: formSwitch gets `isolation: isolate`.
   The page background lives on the bodyFrame DIV (body-split §7.1), not
   the <body> canvas, so Materia's `.form-switch .form-check-input::after`
   focus-glow (z-index:-1) paints behind the opaque bodyFrame div and
   vanishes. Isolating form-switch (a transparent ancestor of the ::after,
   not of body-portaled modals) makes the glow composite over the
   bodyFrame bg. Isolating bodyFrame itself instead traps the modal dialog
   below its body-level backdrop (verified modals still pass).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three general fixes for cross-theme contamination + one theme-prop fix,
all of which the multi-theme VE app exposes because it loads every theme's
assets together:

1. scope-emitter: capture body `text-shadow` onto bodyText (vapor's neon
   glow, inherited by all text). Same gap-class as the earlier letter-spacing
   fix.

2. Font isolation (vite.config transformIndexHtml, order:'pre'): index.html
   statically links every theme's fonts.generated.css. A foreign `Lato:ital`
   face made vapor's synthesized-oblique <cite> render as real italic
   (narrower) → reflow mismatch. Strip non-active themes' font links at build
   so only the active theme's fonts load (matches the always-isolated baseline).

3. Scoped modalOpenHook: every theme's scope.css emitted a GLOBAL
   `${modalOpenHook}` rule; since Ve2Shell imports all 29 scope.css, the body's
   modal-open typography was a last-wins cross-theme mix, leaking letter-spacing
   into modal text via inheritance. Fork modal.js now splits a space-separated
   CLASS_NAME_OPEN; modal components stamp `${theme} ${modalOpenHook}` on <body>;
   scope-emitter scopes the rule `${scope}${modalOpenHook}`. (Fork change lives
   in the bootstrap-fork submodule, committed separately.)

Scoping modalOpenHook + the "root vars now live in the literal emitter" change
required regenerating ALL themes' scope.css AND literal/styles.css.ts so the two
stay consistent (scope.css no longer emits the ${scope}${vars} block; the literal
does). Regenerated all 27.

Verified to zero: vapor (433/433), materia (433/433 re-check), bootstrap modal
routes (8/8). Other already-verified themes regenerated but re-checked only at
modal routes pending the full all-themes pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Points to the fork commit that enables `${theme} ${modalOpenHook}` on <body>
(multi-class CLASS_NAME_OPEN), required by the scoped-modalOpenHook fix in the
vapor literal work. Also carries the fork test fixes (897/897 passing).
General fix: add `legend-element-mirror` divergence so bare `legend {…}`
element rules (which map to the elLegend element contract) are mirrored
onto the `legend` class contract that the components actually stamp on
<legend>. This makes theme-specific overrides reach the element — slate's
`legend { color:#fff }` was previously dropped, leaving the legend in the
inherited body color (470px diff on disabled-radio-buttons).

Replaces the static `legend-class` EXTRA_RULE, which hand-copied only the
reboot props and missed both theme color overrides and the @media font-size.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
No emitter change needed. The single mismatch (navbar-example, 2.3% diff)
was a stale baseline: the morph navbar baseline had been captured with the
lazy-loaded brand logo broken/unloaded, while every other theme shows the
clean Bootstrap "B". VE already rendered the logo correctly — rebuilt the
morph navbar baseline so they match. Adds the missing morph capture
artifacts (theme.css, navbar style.css) and the generated literal bundle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Baselines regenerated first. Two general emitter fixes:

1. thead contract split. thead/tbody/tfoot all mapped to one `tableSection`,
   so `.table > thead { vertical-align:bottom }` and `.table > tbody {
   vertical-align:inherit }` collapsed onto one VE selector and thead's
   `bottom` won for tbody too. Only lux exposed it: its `th { font-size:.875rem }`
   shrinks the tbody header digits enough that the misalignment shows (~113px).
   Added a `tableHead` contract (thead -> tableHead); the 13 table components
   now stamp it on <thead>. tbody returns to the correct `inherit`.

2. Substring class-attribute expansion. `[class*=btn-outline-] { border-width:
   2px }` can't pass through in the element-owned model (DOM classes are hashed),
   so it matched nothing and lux outline buttons lost their 2px border (~1050px
   diff, text+border shifted). The emitter now expands `[class*=...]` / `[class^=
   ...]` / etc. to the contract refs whose Bootstrap class name matches (here all
   8 btnOutline* variants). Only lux/lumen/spacelab use this selector.

Both fixes regenerated all 27 themes (tbody bottom->inherit is the correct
baseline behavior; materia tables re-verified clean, no regression).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
No emitter change. The only mismatch (html-tooltip/opened-tooltip, ratio
0.008) was a stale baseline: a Playwright computed-style/rect comparison
showed baseline and VE render the tooltip pixel-identically (same bounding
rect x=116 y=74 w=123.9 h=29, same em/u/b font metrics), so the committed
baseline had simply been captured at a slightly different Popper-settle
moment. Re-captured that one baseline; the rest of solar was already green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Superhero baselines were broadly stale/incomplete (all ui/buttons/** missing,
carousel interactive baselines missing, stale breadcrumb hover, broken-logo
navbar) — regenerated all superhero baselines. Two general harness fixes that
fan out to every theme:

1. Carousel pause-order: prepareForCapture now pauses auto-cycling carousels
   BEFORE the multi-second font wait instead of after. A data-bs-ride carousel
   autoplays on a 5s timer; if the font wait runs first, a slow-font theme's
   carousel advances a slide mid-wait and pause() freezes it on the wrong slide
   (VE landed on slide 2, baseline on slide 1). Pausing first freezes slide 0.

2. Image-decode wait: prepareForCapture forces loading="lazy" images eager and
   awaits decode, so the navbar brand logo never captures as a broken-image
   placeholder on either baseline or VE (this race hit morph too — fixed there
   only by luck on re-capture).

One flaky modal-open timeout (centered-scrollable-modal/opened-modal) verified
clean on isolated re-run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Baselines regenerated (stale/incomplete). Fixes:

1. Carousel autoplay race: even with the superhero pause-order fix, lumen's
   slow hydration let the data-bs-ride carousel auto-advance before
   stabilizeForScreenshot ran. The harness now force-resets uncontrolled
   (static-state) carousels to slide 0 via class manipulation; interactive
   scenarios mark data-pw-carousel-controlled to opt out.

2. Pagination <li> elLi gap: legacy lumen `.pagination > li > a` targets <li>
   by tag, but the 3 pagination components stamped only pageItem, not elLi
   (breadcrumb already stamped elLi). Added elLi.

3. §10.2 specificity flatten: that legacy rule's element tags became contract
   classes + per-segment scope (${pagination}>${elLi}>${link} = 0,6,0),
   outranking `.disabled>.page-link {color:#999}` (0,4,0) so the disabled
   Previous link rendered #555. Added manifest LOWER_ELEMENT_SPECIFICITY_SELECTORS
   that :where()-wraps pure-element segments for listed selectors (lumen
   `.pagination > li` only; sketchy `.table-dark th` excluded — it relies on
   the inflated specificity to beat the cell rule).

4. Modal-open race: no warmup before the opened-modal click meant lumen's slow
   hydration hadn't bound the modal JS; added a 600ms opened-modal warmup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regenerated stale journal baselines; VE already rendered correctly,
no emitter fix needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regenerated stale spacelab baselines; VE already rendered correctly,
no emitter fix needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regenerated stale darkly baselines; VE already rendered correctly,
no emitter fix needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regenerated stale simplex baselines; VE already rendered correctly,
no emitter fix needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regenerated stale cyborg baselines; VE already rendered correctly,
no emitter fix needed. (Preview-server cold-start readiness timeout
hit during capture; warming Vite's preview cache resolved it.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat: minty theme literal verification to zero (433/433)

Stale/missing baselines only; no emitter change (VE already correct).
Regenerated failing families: ui/buttons (whole tree absent),
ui/button-group, ui/dropdowns, ui/carousel, ui/list-group, ui/navbar,
ui/popovers. Added missing style.css/theme.css metadata.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
feat: united theme literal verification to zero (433/433)

Full baseline regen, then verified clean on first pass; no emitter
change (VE already rendered correctly).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
Full baseline regen + one general component fix: heading examples
render p.h1 elements but stamped only clsH1-clsH6, not the paragraph
element contract, so litera serif p font-family override never fired.
Stamp paragraph on Heading.tsx and HeadingBadges.tsx; neutral for
other themes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Full baseline regen, then verified clean on first pass; no emitter
change (VE already rendered correctly).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
claude and others added 28 commits July 20, 2026 20:51
…fixer

generate-preset.mjs compila bootstrap-utilities.scss (sass) → autoprefixer
(browserslist do Bootstrap) → parseia em 482 utilities atômicas + variantes
responsiva (infix -{bp}-) e print (-print-), reconstruídas por variants
restritas às utilities realmente responsivas. Emite
preset/bootstrap-utilities.generated.mjs (derivado, não editar à mão).

preset-bootswatch.mjs consome os dados gerados: static rules 1:1 com o
Bootstrap (!important preservado), cores via var(--bs-*-rgb) theme-agnostic,
prefixo só nas utilities, variants de breakpoint e print.

Validação (preset-probe.mjs) contra o bootstrap.css de referência:
46/46 classes batem, 891 variantes responsivas 0 mismatch, prefixo e
print corretos. autoprefixer foi essencial (user-select/-webkit- etc.).

Gaps documentados: RFS fluido fs-1..4, estados hover/focus dos
link-utilities, .clearfix::after/.ratio::before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
…públicas

Rafael: nada de --bs-* literal (colisão em projetos do consumidor); o preset
referencia os nomes hasheados compilados. Achado: o contract cobre só 10 das
65 vars que as utilities usam — as outras 55 são as globais de :root do
Bootstrap 5.3, ausentes do contract. Plano (§3.3-bis): mapa único
--bs-* → hash determinístico, emitido no scope de cada tema (valores do
:root de cada bootstrap.css) e substituído no generate-preset. Segue
theme-agnostic (mapa compartilhado) e sincronizado (mesmo pacote/versão).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
Correção (Rafael): as vars públicas não levam prefixo/namespace próprio —
são hasheadas pelo MESMO createVar() do VE, caindo no mesmo --bsve_ das
classes. Prefixo só (opcional) nas classes de utility do preset. §3.3-bis
reescrita: estender o contract com um _public-vars.css.ts novo (não desloca
os hashes existentes → componentes seguem verificados), o scope de cada
tema atribui os valores, e o generate-preset substitui --bs-* pelo hash.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
…s (§3.3-bis)

As utilities referenciam 65 vars globais --bs-* ausentes do contract. Agora
hasheadas pelo MESMO VE (sem literal, sem prefixo próprio):

- gen-public-vars.mjs: _public-vars.css.ts (createVar das 65) + mapa de nomes
  + themes/*/public-vars.css.ts (27 temas) atribuindo os valores do :root de
  cada bootstrap.css (59 no scope; 6 opacidades são utility-local).
- gen-var-hashes.mjs: compila _public-vars sozinho (nada compartilhado) →
  public-vars.hash.json (export → var(--bsve_hash)), mesmos identifiers do build.
- generate-preset.mjs: substitui cada --bs-* das utilities pelo hash (chaves e
  refs var()); 0 --bs-* remanescente.
- build-package.mjs: inclui _public-vars no contract e public-vars.css como família.

Cross-match verificado: preset usa var(--bsve_54j6y419) p/ primary-rgb; o scope
atribui --bsve_54j6y419: 13,110,253 (valor do tema). preset-probe: 46/46 de
fidelidade (referência mapeada) e nenhum --bs-* literal no CSS emitido.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
- solid/: BootswatchProvider (Solid 2.0: <Context value=…>), useBootswatch,
  useUtility/u(); prefixClasses puro e testado. Scope + utilityPrefix via
  contexto (componentes ficam agnósticos).
- preset: extractor que reconhece u('...') e entrega tokens prefixados ao
  UnoCSS — resolve o atrito JIT×runtime (fonte tem 'mb-3', runtime emite
  'bsu-mb-3'). Validado com createGenerator.
- examples/demo/: Solid + UnoCSS(preset, prefix bsu-) + CSS do dist-pkg.
  Compilada (vite build) e RENDERIZADA no Chromium: botão com scope+contract
  (VE hasheado) + bsu-mb-3/bsu-me-2 (JIT só as usadas) + bg rgb(13,110,253)
  da var pública hasheada. mb=16px, mr=8px, texto branco.

Descoberta: 25 --bs-* literais restam no global.css do port (--bs-gray-*,
--bs-breakpoint-*, uns rgb) — resíduo da família global, a varrer na F2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
…tema)

build-package.mjs generalizado: enumera todas as ~33 famílias + scope +
public-vars, gera o barrel de contract (todos os ~575 nomes hasheados),
compila só com o plugin VE, normaliza nomes (X.css.ts.css → X.css) e
remove .css vazios. utilities baked excluída (o caminho é o preset UnoCSS;
a baked reintroduziria literais/duplicaria).

Estado: 34 CSS, 189 KB. Literal --bs-* residual = 24 decls :root
informativas no global (gray/breakpoints, ~não-referenciadas) + 2 refs de
componente (progress-height, breadcrumb-font-size). É resíduo do PORT (o
conversor emitiu literal em vez de createVar); eliminar 100% é sweep de
port, não strip no build (que quebra referências).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
Hasheia todos os --bs-* literais residuais do port declarando os vars que
faltavam nos _vars.css.ts (com o comentário // --bs-x que o registry lê):
gray-100..900, secondary/tertiary-color-rgb, breakpoint-xs..xxl,
border-radius-2xl (global); breadcrumb-font-size (ui/breadcrumb),
scroll-height (ui/navbar), dropdown-item-border-radius (ui/dropdowns).

Resultado: 34 CSS, 189.1 KB, emitted=2977 skipped=0, 0 literal --bs-*
(só --bsve_… + o atributo HTML data-bs-theme).

Corrige três bugs do gerador expostos pela varredura:
- symbolToCssVarName perdia o hífen antes de dígito (varBsGray100 →
  --bs-gray100); resolvido com o comentário explícito.
- vars declarados em _vars.css.ts E _public-vars.css.ts (56 símbolos) tinham
  a família decidida pela ordem do readdir — se _public-vars (family=null)
  vinha primeiro, o import do var era descartado e o VE quebrava com
  ReferenceError. contract-registry.mjs agora torna `global` autoritativo p/
  compartilhados e dá família `public-vars` (+ import _public-vars.css) aos
  exclusivos.
- emitKeyframes não traduzia var() nos valores (vazava
  var(--bs-progress-height) no @Keyframes progress-bar-stripes); passou a usar
  o mesmo parseVeValue/formatVeValue das declarações normais.

build-package.mjs: contador de refs agora pega var(--bs-x, fallback).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
scripts/pack-dist.mjs monta a raiz publicável `package/` no layout do §2 a
partir do build VE + preset (F3) + runtime /solid (F4) compilado:

  package/
    package.json  README.md
    contract/  index.js  index.d.ts   # manifesto theme-agnostic (743 nomes)
    themes/bootstrap/  scope.{css,js,d.ts}  public-vars.css
                       <familia>.css ×28  index.css
    preset/  index.js  index.d.ts + dados
    solid/   index.js  index.d.ts

Mudanças de suporte:
- build-package: contract barrel agora é THEME-AGNOSTIC (sem re-export do
  scope) e EXCLUI literal/** — o monólito re-declara os contracts por família,
  e nomes duplicados viravam ambíguos p/ `export *` (o ES os omitia: btn,
  btnPrimary… sumiam). Manifesto passou de 586 → 743 nomes.
- solid/index.tsx: provider reescrito com createComponent (sem JSX) → o pacote
  compila p/ JS executável em qualquer consumidor, sem depender do transform
  JSX do Solid no build do pacote (babel-preset-solid não é necessário).
- exports: `./themes/*.css` (um só `*`, cruza barras) no lugar do inválido
  `./themes/*/*.css`; com sufixo .css tem prioridade sobre `./themes/*`.
- .gitignore: package/ (artefato de build).

Verificado: 43 arquivos, 62.9 kB tarball / 531.9 kB desempacotado; 0 literal
--bs-*, 0 CSS de demo; todos os exports resolvem (contract/preset/solid/themes)
e os .d.ts passam no tsc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
O Rafael viu 23 mismatches na captura, todos em ui/dropdowns/split-*-dropdown
(~0.0035, determinísticos). Causa (medida por geometria de DOM no app ao vivo,
independente de fonte): o override do caret `.dropdown-toggle-split::after
{margin-left:0}` era emitido na família ui/buttons, mas sua BASE
`.dropdown-toggle::after {margin-left:0.255em}` está em ui/dropdowns. Mesma
especificidade (0,2,1) → empate resolvido por ORDEM DE FONTE. No monólito
resolve (base antes do override, como no bootstrap.css); no granular
ui/dropdowns carrega DEPOIS de ui/buttons, a base vence e o caret do split
ganha 0.255em em vez de 0 → deslocado ~4px, botão ~6px mais largo.

Fix: partSubjectFamily (emit.mjs) co-loca o override do caret do split
(seletores `dropdownToggleSplit` com ::after/::before) em ui/dropdowns — mesmo
padrão já usado p/ `[type=checkbox]→forms`. Assim o empate resolve por ordem de
fonte, como no monólito. Só afeta o modo granular (o monólito literal não muda).

Verificado (app ao vivo, granular): caret do split 4.08px → 0px; largura do
botão 32 → 28. Dropdowns normais intactos (a base não muda; o override só casa
a classe split). No sandbox o ratio do split-primary caiu ~148px (o componente
estrutural); o resto é font-AA do ambiente (o pixel-diff absoluto é
dependente de ambiente — verificação definitiva no ambiente do Rafael).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
Regenera os 27 temas (generate-ve-literal --all-themes --families, todos
skipped=0) aplicando o sweep de port + o fix do split-caret a todos.

build-package.mjs generalizado: itera os 27 temas (contract compilado UMA vez,
theme-agnostic; scope+public-vars+famílias por tema) e conta o literal --bs-*
residual por tema, falhando se >0.

Resultado: 0 literal --bs-* em TODOS os 27 temas (5.23 MB de CSS granular).
yeti e zephyr tinham resíduo específico (--bs-primary-color / --bs-primary-bg —
referenciadas mas nunca setadas no tema, como no original); resolvido
declarando varBsPrimaryColor/varBsPrimaryBg no _vars.css.ts (743→745 nomes).

pack-dist monta os 27 temas → package/ (901 arquivos, 948.9 kB tarball /
11.1 MB desempacotado). Theme-agnostic confirmado: contract/preset/solid únicos
e compartilhados; btn=bsve_xm6f6u1 idêntico em bootstrap e darkly (só os VALORES
das vars mudam por tema); cada tema tem seu scope.js (darklyScope ≠
bootstrapScope).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
Scripts no package.json da raiz: pkg:generate|build|pack|probe|ci. pack-dist
aceita a versão via env BSVE_VERSION (default 0.1.0) p/ o workflow de publish
derivar da tag. Doc do plano com o fluxo de CI/publish e release.

Os workflows do GitHub Actions (.github/workflows/ci.yml e publish.yml) ficam
p/ o mantenedor commitar (o token do agente não tem escopo `workflow`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
…workflows)

Os workflows ci.yml e publish.yml ficam versionados aqui porque o token do
agente não tem o escopo `workflow` p/ escrever em .github/workflows/. O
mantenedor move os dois p/ .github/workflows/ e commita (ver README).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
Os tokens npm "bypass 2FA" estão sendo depreciados (ago/2026: param de pular
2FA em ops sensíveis; ~jan/2027: perdem o publish direto). Troca o publish.yml
p/ trusted publishing via OIDC: sem token de longa duração, funciona com 2FA na
conta, provenance automática. Requer id-token: write, npm >= 11.5.1 (o workflow
atualiza o CLI) e configurar o trusted publisher no npmjs.com. Atualiza README
e o plano.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
…SIGINT-safe)

- scripts/verify-all-themes.mjs: orquestra a verificação VE (ve-project2) tema a
  tema com estado em verify-run/ (progress.json + mismatches.json + logs). Resume
  por rota via --skip-to-route=<lastRoute>; merge de mismatches entre execuções
  parciais (mantém rotas < anchor, substitui >= anchor).
- workflow.mjs: VERIFY_JSON_OUT agora incremental (reescrito por cenário, com
  complete/lastRoute/lastState); handler SIGINT/SIGTERM faz flush e para após o
  cenário em voo (2o sinal força exit 130).
- package.json: script verify:themes. .gitignore: /verify-run/.
- docs: changelog + nota em capture-leaf-screenshots.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Granular mode (?style-loader=granular) loaded all 27 themes/*/scope.css
side-effect modules per page. Two static-import sources caused it:

- theme-runtime.ts (generated) statically imported all 27 scope.css to
  build the themeScopes map behind resolveVe2ThemeClass() — a one-key
  lookup dragging in 27 modules.
- Ve2ShellRuntime statically imported Ve2Shell, whose 27 scope imports
  loaded even in granular mode (Ve2Shell is only the theme/literal
  fallback; <Show> gates rendering, not imports).

Fix (runtime-only; emitter/contract untouched):
- Generator now emits ve2ThemeScopeLoaders (dynamic per-theme import) +
  loadVe2ThemeScope(), replacing the static themeScopes map and
  resolveVe2ThemeClass(). theme-runtime.ts regenerated.
- Ve2GranularShell resolves the scope class async via the two-arg
  createEffect (Solid 2.0) into a signal and gates its subtree behind
  <Show> until the one active scope.css loads.
- Ve2ShellRuntime makes Ve2Shell lazy() so its 27 scope imports load
  only in theme/literal mode.

Result (/ui/offcanvas/default-offcanvas): granular 27 -> 1 scope request
(darkly loads only darkly); theme mode unchanged at 27. All render, no
console errors. Documented in docs/ve3-granular-family-split-plan.md §7a
and docs/changelog/2026-07-20-granular-scope-lazy-load.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The generated font stylesheet <link>s sat after </html>, outside the
document head. Move them inside <head> so the markup is well-formed and
the fonts load as part of the head.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`>=1.8.0` exclui prereleases (semver), então npm conflita em projetos no Solid
2.0-beta (tenta puxar um 1.x stable p/ o peerOptional). Amplia p/
`>=1.8.0 || >=2.0.0-0`. Descoberto ao montar o frontend do DDSOFT (F6)
consumindo o pacote de verdade. Entra no próximo release (0.1.1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
…r de literal

Os estilos de estado (.componente + .active/.disabled/.fade/.collapsing) e de cor
de badge (.bg-* + .rounded-pill) existem no CSS, ligados aos contracts GENÉRICOS
de theme-contract/literal. Mas o barrel do /contract exclui literal/ (p/ evitar
ambiguidade no `export *`), e as cópias em ui/*/utilities/generated são vazias
(vestigiais). Efeito: o consumidor não alcançava as classes que realmente
estilizam — usava listGroupItemActive/badgeSecondary (vazios) e nada aplicava.

Adiciona um re-export EXPLÍCITO de literal p/ os genéricos (active, disabled,
fade, collapsing, bgPrimary…bgLight, roundedPill). Explícito vence a ambiguidade
do `export *` → resolve p/ o hash ESTILIZADO de literal. Um genérico conserta o
estado de TODOS os componentes de uma vez (list-group, dropdown, nav, breadcrumb,
carousel, buttons…).

Verificado: rebuild dos 27 temas OK; C.active=bsve_17c3vgbe3 (o mesmo hash de
.list-group-item.active), C.disabled/bgPrimary/roundedPill idem; manifesto
745→757 nomes; 0 --bs-* residual.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxSz
…c-vars

As 65 public vars de theme-contract/_public-vars.css.ts colidem em NOME com as
vars internas de _vars.css.ts, então o `export *` das famílias as descartava por
ambiguidade — só 2 (varBsSecondaryBgRgb, varBsTertiaryBgRgb, exclusivas do
public-vars) sobreviviam; as outras 63 (varBsBorderColor, varBsBorderRadius,
varBsPrimaryRgb, …) ficavam inacessíveis via import.

Adiciona um re-export EXPLÍCITO de _public-vars.css (lista lida do próprio
arquivo p/ pegar novas vars) — explícito vence a ambiguidade e resolve p/ o hash
da public var. Agora dá pra compor estilos tipados com o valor de --bs-* do tema.

Verificado: rebuild dos 27 temas OK; manifesto 757→820 nomes;
varBsBorderColor=var(--bsve_54j6y45); 0 --bs-* residual.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxSz
Os identifiers do vanilla-extract passam de `bsve_${hash}` para `b${hash}`
(build-package.mjs, gen-var-hashes.mjs). O prefixo de 5 letras não agregava
valor — os hashes já são únicos/estáveis — e só inchava o CSS. Prefixo zero é
inviável porque muitos hashes começam com dígito (1ki2kjo2, 17c3vgbe3, 54j6y45)
e classe/var CSS não pode iniciar com dígito; a menor forma válida é 1 letra.

Regenerados os artefatos versionados (preset/public-vars.hash.json,
preset/bootstrap-utilities.generated.mjs). generate-preset.mjs já era
prefix-agnostic (h.slice(4,-1)). Comentários atualizados. Verificado: 0 `bsve_`
residual em preset/ e no dist-pkg dos 27 temas; classes agora .b<hash> e vars
var(--b<hash>).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
- scripts/release.mjs: encadeia pkg:build (0 literal nos 27 temas) → pkg:pack
  (monta package/ com BSVE_VERSION) → confere versão → pkg:probe → npm pack
  --dry-run. Por padrão PARA antes de publicar; --publish faz o npm publish de
  dentro de package/. Flags: --dry-run, --skip-build.
- package.json: npm script `pkg:release`.
- docs/releasing.md: guia do processo (caminho automático via GitHub Release +
  OIDC, caminho local/manual, setup de 1ª vez, semver). Esclarece que o
  package.json da raiz NÃO é o pacote publicável — este é montado em package/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
O `npm ci` do CI falhava com EUSAGE: o lockfile estava fora de sincronia com o
package.json, faltando as deps transitivas opcionais @emnapi/core@1.11.2 e
@emnapi/runtime@1.11.2 (helpers WASM/napi). `npm install` as adiciona e
normaliza marcadores `peer` — sem bump de nenhuma versão. Com o lockfile em dia,
`npm ci` passa (código 0), destravando os 3 checks do PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
O job de lint do CI (`biome ci .`) falhava com ~2650 problemas, a maioria em
arquivos GERADOS (ve-project2/src/themes, reescritos pelo pkg:generate) e no
projeto legado ve-project (v1, não usado). Não dá pra formatar os gerados — o
job generated-up-to-date os regenera e passaria a divergir.

- biome.json: escopo do `files.includes` reduzido ao código mantido — remove
  ve-project (legado) e exclui ve-project2/src/themes/** (gerado) e
  theme-contract/_public-vars.css.ts (gerado). Override desliga
  noTemplateCurlyInString em scripts/** (geradores manipulam sintaxe de
  template como texto, intencional).
- Aplicado `biome check --write` (safe + unsafe) nas fontes autorais: formatação
  (CRLF/tabs/lineWidth), useTemplate, organizeImports, useNodejsImportProtocol.
- Fixes manuais: preset-probe.mjs `[^]*` → `[\s\S]*` (forma explícita);
  src/index.tsx `{}` → `object`; 2× biome-ignore no idioma `while((m=re.exec()))`.

Verificado: `biome ci .` sai 0; pkg:generate mantém themes/ idêntico
(generated-up-to-date passa); pkg:ci (build 27 temas 0-literal + pack + probe
46/46) passa. Os 3 checks do PR ficam verdes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhNp7wH1dgomCT18AgRuxZ
@rhengles
rhengles merged commit 9d05390 into main Jul 21, 2026
3 checks passed
@rhengles
rhengles deleted the ve-literal-converter branch July 21, 2026 20:31
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