From a0a1ab44920e4bd52daffd415df817542732b0f3 Mon Sep 17 00:00:00 2001 From: John Leider Date: Sat, 1 Aug 2026 09:55:14 -0500 Subject: [PATCH 01/18] =?UTF-8?q?feat(bulma):=20add=20@paper/bulma=20?= =?UTF-8?q?=E2=80=94=20Bulma=20compat=20design=20system=20(Tier=201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Components render real Bulma 1.0.4 markup and classes against the user's own bulma.css while @vuetify/v0 supplies behavior, focus management, and accessibility. Bulma ships zero JavaScript by design; this package is the JS its docs tell you to bring. - 22 Tier-1 components (Bu* prefix): modal, dropdown, tabs, navbar, message, notification, pagination, breadcrumb, panel, menu, and the full form set (field/control/label/help/input/textarea/select/ checkbox/radio/file) - Conformance harness: fixtures captured from bulma.io's documented markup, DOM/class diffing with a strict additive-attr tolerance charter, plus an axe-core a11y sweep over every component - SPEC.md declares the compat-class deviations from DESIGN_SYSTEMS.md rulings and known Tier-1 limitations, each with its v0-core follow-up - No stylesheet artifact and no owned token namespace: bulma@^1.0 is an optional peer; the user's CSS is the styling source --- .gitignore | 1 + eslint.config.js | 7 + packages/bulma/SPEC.md | 94 +++++ packages/bulma/harness/CANON.md | 80 ++++ packages/bulma/harness/V0-CHEATSHEET.md | 276 +++++++++++++ packages/bulma/harness/a11y.browser.test.ts | 384 ++++++++++++++++++ .../bulma/harness/conform.browser.test.ts | 73 ++++ packages/bulma/harness/conform.ts | 182 +++++++++ .../bulma/harness/fixtures/breadcrumb.html | 58 +++ packages/bulma/harness/fixtures/dropdown.html | 55 +++ .../bulma/harness/fixtures/form-checkbox.html | 60 +++ .../bulma/harness/fixtures/form-file.html | 162 ++++++++ .../bulma/harness/fixtures/form-general.html | 88 ++++ .../bulma/harness/fixtures/form-input.html | 169 ++++++++ .../bulma/harness/fixtures/form-radio.html | 66 +++ .../bulma/harness/fixtures/form-select.html | 179 ++++++++ .../bulma/harness/fixtures/form-textarea.html | 106 +++++ packages/bulma/harness/fixtures/menu.html | 38 ++ packages/bulma/harness/fixtures/message.html | 31 ++ packages/bulma/harness/fixtures/modal.html | 36 ++ packages/bulma/harness/fixtures/navbar.html | 93 +++++ .../bulma/harness/fixtures/notification.html | 25 ++ .../bulma/harness/fixtures/pagination.html | 55 +++ packages/bulma/harness/fixtures/panel.html | 73 ++++ packages/bulma/harness/fixtures/tabs.html | 47 +++ packages/bulma/package.json | 54 +++ .../BuBreadcrumb/BuBreadcrumb.browser.test.ts | 52 +++ .../components/BuBreadcrumb/BuBreadcrumb.vue | 105 +++++ .../src/components/BuBreadcrumb/index.ts | 2 + .../BuCheckbox/BuCheckbox.browser.test.ts | 34 ++ .../src/components/BuCheckbox/BuCheckbox.vue | 133 ++++++ .../bulma/src/components/BuCheckbox/index.ts | 2 + .../BuControl/BuControl.browser.test.ts | 58 +++ .../src/components/BuControl/BuControl.vue | 57 +++ .../bulma/src/components/BuControl/index.ts | 3 + .../BuDropdown/BuDropdown.browser.test.ts | 125 ++++++ .../src/components/BuDropdown/BuDropdown.vue | 140 +++++++ .../bulma/src/components/BuDropdown/index.ts | 3 + .../BuField/BuField.browser.test.ts | 78 ++++ .../bulma/src/components/BuField/BuField.vue | 71 ++++ .../bulma/src/components/BuField/index.ts | 3 + .../components/BuFile/BuFile.browser.test.ts | 63 +++ .../bulma/src/components/BuFile/BuFile.vue | 209 ++++++++++ packages/bulma/src/components/BuFile/index.ts | 2 + .../components/BuHelp/BuHelp.browser.test.ts | 91 +++++ .../bulma/src/components/BuHelp/BuHelp.vue | 75 ++++ packages/bulma/src/components/BuHelp/index.ts | 3 + .../BuInput/BuInput.browser.test.ts | 90 ++++ .../bulma/src/components/BuInput/BuInput.vue | 160 ++++++++ .../bulma/src/components/BuInput/index.ts | 3 + .../BuLabel/BuLabel.browser.test.ts | 66 +++ .../bulma/src/components/BuLabel/BuLabel.vue | 55 +++ .../bulma/src/components/BuLabel/index.ts | 3 + .../components/BuMenu/BuMenu.browser.test.ts | 100 +++++ .../bulma/src/components/BuMenu/BuMenu.vue | 112 +++++ packages/bulma/src/components/BuMenu/index.ts | 3 + .../BuMessage/BuMessage.browser.test.ts | 78 ++++ .../src/components/BuMessage/BuMessage.vue | 79 ++++ .../bulma/src/components/BuMessage/index.ts | 3 + .../BuModal/BuModal.browser.test.ts | 153 +++++++ .../bulma/src/components/BuModal/BuModal.vue | 201 +++++++++ .../bulma/src/components/BuModal/index.ts | 3 + .../BuNavbar/BuNavbar.browser.test.ts | 100 +++++ .../src/components/BuNavbar/BuNavbar.vue | 110 +++++ .../bulma/src/components/BuNavbar/index.ts | 2 + .../BuNotification.browser.test.ts | 80 ++++ .../BuNotification/BuNotification.vue | 69 ++++ .../src/components/BuNotification/index.ts | 3 + .../BuPagination/BuPagination.browser.test.ts | 50 +++ .../components/BuPagination/BuPagination.vue | 117 ++++++ .../src/components/BuPagination/index.ts | 2 + .../BuPanel/BuPanel.browser.test.ts | 139 +++++++ .../bulma/src/components/BuPanel/BuPanel.vue | 135 ++++++ .../bulma/src/components/BuPanel/index.ts | 3 + .../BuRadio/BuRadio.browser.test.ts | 45 ++ .../bulma/src/components/BuRadio/BuRadio.vue | 137 +++++++ .../bulma/src/components/BuRadio/index.ts | 2 + .../BuSelect/BuSelect.browser.test.ts | 46 +++ .../src/components/BuSelect/BuSelect.vue | 159 ++++++++ .../bulma/src/components/BuSelect/index.ts | 2 + packages/bulma/src/components/BuTab/BuTab.vue | 63 +++ packages/bulma/src/components/BuTab/index.ts | 2 + .../src/components/BuTabPanel/BuTabPanel.vue | 33 ++ .../bulma/src/components/BuTabPanel/index.ts | 2 + .../components/BuTabs/BuTabs.browser.test.ts | 113 ++++++ .../bulma/src/components/BuTabs/BuTabs.vue | 67 +++ packages/bulma/src/components/BuTabs/index.ts | 2 + .../BuTextarea/BuTextarea.browser.test.ts | 77 ++++ .../src/components/BuTextarea/BuTextarea.vue | 147 +++++++ .../bulma/src/components/BuTextarea/index.ts | 3 + packages/bulma/src/components/index.ts | 22 + packages/bulma/src/index.ts | 2 + packages/bulma/src/utilities/validate.ts | 77 ++++ packages/bulma/src/vite-env.d.ts | 3 + packages/bulma/tsconfig.json | 42 ++ packages/bulma/tsdown.config.mts | 34 ++ packages/bulma/vitest.browser.config.ts | 59 +++ pnpm-lock.yaml | 33 ++ vitest.config.ts | 1 + 99 files changed, 6893 insertions(+) create mode 100644 packages/bulma/SPEC.md create mode 100644 packages/bulma/harness/CANON.md create mode 100644 packages/bulma/harness/V0-CHEATSHEET.md create mode 100644 packages/bulma/harness/a11y.browser.test.ts create mode 100644 packages/bulma/harness/conform.browser.test.ts create mode 100644 packages/bulma/harness/conform.ts create mode 100644 packages/bulma/harness/fixtures/breadcrumb.html create mode 100644 packages/bulma/harness/fixtures/dropdown.html create mode 100644 packages/bulma/harness/fixtures/form-checkbox.html create mode 100644 packages/bulma/harness/fixtures/form-file.html create mode 100644 packages/bulma/harness/fixtures/form-general.html create mode 100644 packages/bulma/harness/fixtures/form-input.html create mode 100644 packages/bulma/harness/fixtures/form-radio.html create mode 100644 packages/bulma/harness/fixtures/form-select.html create mode 100644 packages/bulma/harness/fixtures/form-textarea.html create mode 100644 packages/bulma/harness/fixtures/menu.html create mode 100644 packages/bulma/harness/fixtures/message.html create mode 100644 packages/bulma/harness/fixtures/modal.html create mode 100644 packages/bulma/harness/fixtures/navbar.html create mode 100644 packages/bulma/harness/fixtures/notification.html create mode 100644 packages/bulma/harness/fixtures/pagination.html create mode 100644 packages/bulma/harness/fixtures/panel.html create mode 100644 packages/bulma/harness/fixtures/tabs.html create mode 100644 packages/bulma/package.json create mode 100644 packages/bulma/src/components/BuBreadcrumb/BuBreadcrumb.browser.test.ts create mode 100644 packages/bulma/src/components/BuBreadcrumb/BuBreadcrumb.vue create mode 100644 packages/bulma/src/components/BuBreadcrumb/index.ts create mode 100644 packages/bulma/src/components/BuCheckbox/BuCheckbox.browser.test.ts create mode 100644 packages/bulma/src/components/BuCheckbox/BuCheckbox.vue create mode 100644 packages/bulma/src/components/BuCheckbox/index.ts create mode 100644 packages/bulma/src/components/BuControl/BuControl.browser.test.ts create mode 100644 packages/bulma/src/components/BuControl/BuControl.vue create mode 100644 packages/bulma/src/components/BuControl/index.ts create mode 100644 packages/bulma/src/components/BuDropdown/BuDropdown.browser.test.ts create mode 100644 packages/bulma/src/components/BuDropdown/BuDropdown.vue create mode 100644 packages/bulma/src/components/BuDropdown/index.ts create mode 100644 packages/bulma/src/components/BuField/BuField.browser.test.ts create mode 100644 packages/bulma/src/components/BuField/BuField.vue create mode 100644 packages/bulma/src/components/BuField/index.ts create mode 100644 packages/bulma/src/components/BuFile/BuFile.browser.test.ts create mode 100644 packages/bulma/src/components/BuFile/BuFile.vue create mode 100644 packages/bulma/src/components/BuFile/index.ts create mode 100644 packages/bulma/src/components/BuHelp/BuHelp.browser.test.ts create mode 100644 packages/bulma/src/components/BuHelp/BuHelp.vue create mode 100644 packages/bulma/src/components/BuHelp/index.ts create mode 100644 packages/bulma/src/components/BuInput/BuInput.browser.test.ts create mode 100644 packages/bulma/src/components/BuInput/BuInput.vue create mode 100644 packages/bulma/src/components/BuInput/index.ts create mode 100644 packages/bulma/src/components/BuLabel/BuLabel.browser.test.ts create mode 100644 packages/bulma/src/components/BuLabel/BuLabel.vue create mode 100644 packages/bulma/src/components/BuLabel/index.ts create mode 100644 packages/bulma/src/components/BuMenu/BuMenu.browser.test.ts create mode 100644 packages/bulma/src/components/BuMenu/BuMenu.vue create mode 100644 packages/bulma/src/components/BuMenu/index.ts create mode 100644 packages/bulma/src/components/BuMessage/BuMessage.browser.test.ts create mode 100644 packages/bulma/src/components/BuMessage/BuMessage.vue create mode 100644 packages/bulma/src/components/BuMessage/index.ts create mode 100644 packages/bulma/src/components/BuModal/BuModal.browser.test.ts create mode 100644 packages/bulma/src/components/BuModal/BuModal.vue create mode 100644 packages/bulma/src/components/BuModal/index.ts create mode 100644 packages/bulma/src/components/BuNavbar/BuNavbar.browser.test.ts create mode 100644 packages/bulma/src/components/BuNavbar/BuNavbar.vue create mode 100644 packages/bulma/src/components/BuNavbar/index.ts create mode 100644 packages/bulma/src/components/BuNotification/BuNotification.browser.test.ts create mode 100644 packages/bulma/src/components/BuNotification/BuNotification.vue create mode 100644 packages/bulma/src/components/BuNotification/index.ts create mode 100644 packages/bulma/src/components/BuPagination/BuPagination.browser.test.ts create mode 100644 packages/bulma/src/components/BuPagination/BuPagination.vue create mode 100644 packages/bulma/src/components/BuPagination/index.ts create mode 100644 packages/bulma/src/components/BuPanel/BuPanel.browser.test.ts create mode 100644 packages/bulma/src/components/BuPanel/BuPanel.vue create mode 100644 packages/bulma/src/components/BuPanel/index.ts create mode 100644 packages/bulma/src/components/BuRadio/BuRadio.browser.test.ts create mode 100644 packages/bulma/src/components/BuRadio/BuRadio.vue create mode 100644 packages/bulma/src/components/BuRadio/index.ts create mode 100644 packages/bulma/src/components/BuSelect/BuSelect.browser.test.ts create mode 100644 packages/bulma/src/components/BuSelect/BuSelect.vue create mode 100644 packages/bulma/src/components/BuSelect/index.ts create mode 100644 packages/bulma/src/components/BuTab/BuTab.vue create mode 100644 packages/bulma/src/components/BuTab/index.ts create mode 100644 packages/bulma/src/components/BuTabPanel/BuTabPanel.vue create mode 100644 packages/bulma/src/components/BuTabPanel/index.ts create mode 100644 packages/bulma/src/components/BuTabs/BuTabs.browser.test.ts create mode 100644 packages/bulma/src/components/BuTabs/BuTabs.vue create mode 100644 packages/bulma/src/components/BuTabs/index.ts create mode 100644 packages/bulma/src/components/BuTextarea/BuTextarea.browser.test.ts create mode 100644 packages/bulma/src/components/BuTextarea/BuTextarea.vue create mode 100644 packages/bulma/src/components/BuTextarea/index.ts create mode 100644 packages/bulma/src/components/index.ts create mode 100644 packages/bulma/src/index.ts create mode 100644 packages/bulma/src/utilities/validate.ts create mode 100644 packages/bulma/src/vite-env.d.ts create mode 100644 packages/bulma/tsconfig.json create mode 100644 packages/bulma/tsdown.config.mts create mode 100644 packages/bulma/vitest.browser.config.ts diff --git a/.gitignore b/.gitignore index ba4b40d7b5..787ab25c0d 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,4 @@ apps/docs/build/generated/ # Vitest failure artifacts packages/0/.vitest-attachments/ **/__screenshots__/ +.vitest-attachments/ diff --git a/eslint.config.js b/eslint.config.js index 72641d3a43..b5859b4620 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -94,6 +94,13 @@ export default vuetify({ '@typescript-eslint/no-explicit-any': 'off', }, }, +{ + // @paper/bulma conformance tests: conform() throws on mismatch — it IS the assertion + files: ['packages/bulma/**/*.browser.test.ts'], + rules: { + 'vitest/expect-expect': ['error', { assertFunctionNames: ['expect', 'conform'] }], + }, +}, { files: ['**/*.vue'], rules: { diff --git a/packages/bulma/SPEC.md b/packages/bulma/SPEC.md new file mode 100644 index 0000000000..e3dfcdcabe --- /dev/null +++ b/packages/bulma/SPEC.md @@ -0,0 +1,94 @@ +# @paper/bulma — Design Spec + +## Identity + +**Class:** Compat (third class — see [DESIGN_SYSTEMS.md](../../DESIGN_SYSTEMS.md); the +compat-class addendum is extracted from daisyfy + this package after Tier 1, not authored +up front). Full approved spec: `.claude/specs/2026-07-31-paper-bulma-compat.md`. + +Bulma ships zero JavaScript by design — its docs tell you to bring your own JS for modal, +dropdown, navbar burger, and dismissals; `@paper/bulma` is that JS. Components render +Bulma's real markup and classes against the user's own `bulma.css`, while `@vuetify/v0` +provides all behavior, focus management, and accessibility. A second wedge resurrects the +abandoned `bulma-extensions` ecosystem (switch, slider, steps, tooltip, …) on v0 +primitives, themed by the user's own Bulma variables. + +Component prefix: `Bu*` (`B*` is reserved for a future Bootstrap compat). + +## Upstream pin + +- **Peer:** `bulma@^1.0` — optional (`peerDependenciesMeta`); zero-config consumers may + load Bulma off-CDN, so the package never hard-requires the npm dependency. +- Latest verified: **1.0.4**. +- Bulma 0.9.x explicitly unsupported (no CSS variables). +- No stylesheet artifact: the compat class ships no CSS for Tier 1 — the user's own + `bulma.css` (css, sass, or CDN) is the styling source. + +## Declared deviations from DESIGN_SYSTEMS.md + +Inherent to the compat class — the package's entire purpose is markup fidelity against an +upstream CSS framework: + +1. **No owned token namespace** (deviation from **ruling 1**) — `--bulma-*` belongs to + upstream. There is no `theme.ts`-owned prefix; runtime theming (the optional + `createBulmaPlugin()`) drives upstream's own `--bulma-*` variables and `data-theme` + via `V0StyleSheetThemeAdapter`. +2. **Upstream state classes, unprefixed** (deviation from **ruling 3**) — `is-active`, + `is-current`, `is-hoverable` instead of data-attribute hooks, and Bulma's unprefixed + class names (`.modal`, `.dropdown`) instead of package-prefixed ones (`bulma-*`). + State classes are owned by the component and driven by v0 state. +3. **Native form controls where Bulma's CSS demands them** (deviation from **ruling 2**, + "never compose native HTML form controls") — Bulma styles native `` wrapped in `.select`/`.checkbox`/`.file`; a + non-native BuSelect gets none of the user's CSS. Tier 1 form components wrap natives; + rich equivalents (BuCombobox) live in Tier 2 on full v0 primitives. Verified per + component during build — where v0's primitive already renders a compatible element, + it is preferred. + +Rules kept in full: never ` + + +
+ + + diff --git a/apps/docs/src/components.d.ts b/apps/docs/src/components.d.ts index d93dd76a92..fa264ccf9d 100644 --- a/apps/docs/src/components.d.ts +++ b/apps/docs/src/components.d.ts @@ -168,6 +168,7 @@ declare module 'vue' { DocsSkillToggle: typeof import('./components/docs/meta/DocsSkillToggle.vue')['default'] DocsSponsor: typeof import('./components/docs/DocsSponsor.vue')['default'] DocsSponsorSlot: typeof import('./components/docs/DocsSponsorSlot.vue')['default'] + DocsSystemExample: typeof import('./components/docs/DocsSystemExample.vue')['default'] DocsTimeline: typeof import('./components/docs/DocsTimeline.vue')['default'] DocsToc: typeof import('./components/docs/DocsToc.vue')['default'] HomeAiFirst: typeof import('./components/home/HomeAiFirst.vue')['default'] diff --git a/apps/docs/src/components/app/AppNav.vue b/apps/docs/src/components/app/AppNav.vue index 5a8124925e..a839a0ee85 100644 --- a/apps/docs/src/components/app/AppNav.vue +++ b/apps/docs/src/components/app/AppNav.vue @@ -16,7 +16,7 @@ import { useAppStore } from '@/stores/app' // Utilities - import { computed, nextTick, onMounted, shallowRef, useTemplateRef, watch } from 'vue' + import { computed, nextTick, onMounted, shallowRef, toRef, useTemplateRef, watch } from 'vue' import { useRoute } from 'vue-router' // Types @@ -82,6 +82,23 @@ const hasNavContent = computed(() => visibleNav.value.some(item => !('divider' in item)), ) + + // Link-less sections are keyed by the synthetic id createNested assigns them, + // which counts categories only — dividers and links don't advance it. Keying + // off the render index instead leaves every category after the first with no + // registered children, and so no expand toggle. + const categories = toRef(() => { + const ids = new Map() + let index = 0 + + for (const [i, item] of visibleNav.value.entries()) { + if ('divider' in item || 'to' in item) continue + + ids.set(i, `category-root-${index++}`) + } + + return ids + }) const navRef = useTemplateRef('nav') // Match Tailwind's md breakpoint (768px) for nav visibility @@ -217,7 +234,7 @@ diff --git a/apps/docs/src/components/docs/DocsSystemExample.vue b/apps/docs/src/components/docs/DocsSystemExample.vue new file mode 100644 index 0000000000..28cb3141d8 --- /dev/null +++ b/apps/docs/src/components/docs/DocsSystemExample.vue @@ -0,0 +1,190 @@ + + +