From 5e4b21e5d9077f9a2d5de6bb829595b8ea01d766 Mon Sep 17 00:00:00 2001 From: "Anthony Fu (via agent)" Date: Thu, 16 Jul 2026 05:28:01 +0000 Subject: [PATCH 1/5] feat(ui): add Card, Modal, Checkbox and EmptyState primitives Upstream reusable overlay/form/container primitives styled only with the shared semantic shortcuts so they render on both the Wind4 (Nuxt) and Wind3 (webcomponents) surfaces. These back the oxc client's migration off @nuxt/ui and are available to every analyzer. --- .../Container/ContainerCard.stories.ts | 30 ++++++++ .../components/Container/ContainerCard.vue | 18 +++++ .../components/Form/FormCheckbox.stories.ts | 21 ++++++ .../ui/src/components/Form/FormCheckbox.vue | 22 ++++++ .../Overlay/OverlayModal.stories.ts | 25 +++++++ .../src/components/Overlay/OverlayModal.vue | 73 +++++++++++++++++++ .../Visual/VisualEmptyState.stories.ts | 21 ++++++ .../components/Visual/VisualEmptyState.vue | 24 ++++++ 8 files changed, 234 insertions(+) create mode 100644 packages/ui/src/components/Container/ContainerCard.stories.ts create mode 100644 packages/ui/src/components/Container/ContainerCard.vue create mode 100644 packages/ui/src/components/Form/FormCheckbox.stories.ts create mode 100644 packages/ui/src/components/Form/FormCheckbox.vue create mode 100644 packages/ui/src/components/Overlay/OverlayModal.stories.ts create mode 100644 packages/ui/src/components/Overlay/OverlayModal.vue create mode 100644 packages/ui/src/components/Visual/VisualEmptyState.stories.ts create mode 100644 packages/ui/src/components/Visual/VisualEmptyState.vue diff --git a/packages/ui/src/components/Container/ContainerCard.stories.ts b/packages/ui/src/components/Container/ContainerCard.stories.ts new file mode 100644 index 000000000..f41410630 --- /dev/null +++ b/packages/ui/src/components/Container/ContainerCard.stories.ts @@ -0,0 +1,30 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' +import { h } from 'vue' +import ContainerCard from './ContainerCard.vue' + +const meta = { + title: 'Container/ContainerCard', + component: ContainerCard, + tags: ['autodocs'], + parameters: { + docs: { description: { component: 'Neutral card container with optional `#header` / `#footer` slots and a default body slot.' } }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => ({ + setup: () => () => h(ContainerCard, null, { default: () => h('div', { class: 'p4' }, 'Card body') }), + }), +} + +export const WithHeader: Story = { + render: () => ({ + setup: () => () => h(ContainerCard, null, { + header: () => h('div', { class: 'font-medium' }, 'Header'), + default: () => h('div', { class: 'p4' }, 'Card body'), + }), + }), +} diff --git a/packages/ui/src/components/Container/ContainerCard.vue b/packages/ui/src/components/Container/ContainerCard.vue new file mode 100644 index 000000000..10a8ddaea --- /dev/null +++ b/packages/ui/src/components/Container/ContainerCard.vue @@ -0,0 +1,18 @@ + + + diff --git a/packages/ui/src/components/Form/FormCheckbox.stories.ts b/packages/ui/src/components/Form/FormCheckbox.stories.ts new file mode 100644 index 000000000..26620c014 --- /dev/null +++ b/packages/ui/src/components/Form/FormCheckbox.stories.ts @@ -0,0 +1,21 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' +import FormCheckbox from './FormCheckbox.vue' + +const meta = { + title: 'Form/FormCheckbox', + component: FormCheckbox, + tags: ['autodocs'], + args: { label: 'Hide passed' }, + parameters: { + docs: { description: { component: 'Brand-tinted checkbox bound through `v-model`, with an optional `label`.' } }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = {} + +export const Checked: Story = { + args: { modelValue: true }, +} diff --git a/packages/ui/src/components/Form/FormCheckbox.vue b/packages/ui/src/components/Form/FormCheckbox.vue new file mode 100644 index 000000000..8e22e6f62 --- /dev/null +++ b/packages/ui/src/components/Form/FormCheckbox.vue @@ -0,0 +1,22 @@ + + + diff --git a/packages/ui/src/components/Overlay/OverlayModal.stories.ts b/packages/ui/src/components/Overlay/OverlayModal.stories.ts new file mode 100644 index 000000000..2a999b370 --- /dev/null +++ b/packages/ui/src/components/Overlay/OverlayModal.stories.ts @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' +import { h } from 'vue' +import OverlayModal from './OverlayModal.vue' + +const meta = { + title: 'Overlay/OverlayModal', + component: OverlayModal, + tags: ['autodocs'], + parameters: { + docs: { description: { component: 'Teleported dialog with a frosted `bg-glass` panel. Backdrop click and Escape close it; the `#trigger` slot receives an `open()` callback.' } }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => ({ + setup: () => () => h(OverlayModal, null, { + trigger: ({ open }: { open: () => void }) => h('button', { class: 'btn-action', onClick: open }, 'Open modal'), + title: () => 'Dialog title', + default: () => h('div', 'Dialog body content.'), + }), + }), +} diff --git a/packages/ui/src/components/Overlay/OverlayModal.vue b/packages/ui/src/components/Overlay/OverlayModal.vue new file mode 100644 index 000000000..a24a9495b --- /dev/null +++ b/packages/ui/src/components/Overlay/OverlayModal.vue @@ -0,0 +1,73 @@ + + + diff --git a/packages/ui/src/components/Visual/VisualEmptyState.stories.ts b/packages/ui/src/components/Visual/VisualEmptyState.stories.ts new file mode 100644 index 000000000..a76b8605b --- /dev/null +++ b/packages/ui/src/components/Visual/VisualEmptyState.stories.ts @@ -0,0 +1,21 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' +import VisualEmptyState from './VisualEmptyState.vue' + +const meta = { + title: 'Visual/VisualEmptyState', + component: VisualEmptyState, + tags: ['autodocs'], + args: { + icon: 'i-ph-folder-simple-duotone', + title: 'No sessions found', + description: 'Run the linter to generate a report.', + }, + parameters: { + docs: { description: { component: 'Centered empty-state placeholder with an icon, title, and description.' } }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = {} diff --git a/packages/ui/src/components/Visual/VisualEmptyState.vue b/packages/ui/src/components/Visual/VisualEmptyState.vue new file mode 100644 index 000000000..cb77729a4 --- /dev/null +++ b/packages/ui/src/components/Visual/VisualEmptyState.vue @@ -0,0 +1,24 @@ + + + From 64284d0530b4c94056dbfed549276cd6f8759ad1 Mon Sep 17 00:00:00 2001 From: "Anthony Fu (via agent)" Date: Thu, 16 Jul 2026 05:28:16 +0000 Subject: [PATCH 2/5] refactor(oxc): migrate the DevTools client onto @vitejs/devtools-ui + UnoCSS Move the oxc client off @nuxt/ui + Tailwind onto the shared stack used by the rolldown and vite analyzers: UnoCSS via presetDevToolsUI, shared semantic tokens, DM Sans/Mono, the dotted-grid background, floating-vue tooltips and the shared dark-mode composable. The primary scale is derived from the BannerOxcDevTools cyan accent. @nuxt/ui overlays/forms are replaced by the new shared Card/Modal/Checkbox/EmptyState primitives, badges by badge-color-*, and icons by UnoCSS i-* classes; the Monaco config viewers keep their momoa cursor-to-docs mapping and follow dark mode. Wires oxc into the build, typecheck and export-snapshot gates (root tsconfig reference) and prunes the unused @nuxt/ui and tailwindcss catalog entries. oxc keeps dogfooding its own oxlint/oxfmt, whose formatting conflicts with the repo-wide antfu ESLint config, so it stays out of the shared ESLint run. --- .gitignore | 2 + AGENTS.md | 2 +- eslint.config.js | 7 +- packages/oxc/package.json | 6 +- packages/oxc/src/app/app.config.ts | 16 - packages/oxc/src/app/app.vue | 12 +- packages/oxc/src/app/assets/css/main.css | 78 +- packages/oxc/src/app/components/Back.vue | 11 +- .../src/app/components/ColorModeButton.vue | 27 - .../oxc/src/app/components/CongratState.vue | 8 - .../oxc/src/app/components/EmptyState.vue | 0 .../oxc/src/app/components/ErrorTooltip.vue | 64 +- packages/oxc/src/app/components/FileCard.vue | 27 +- packages/oxc/src/app/components/LineError.vue | 60 +- .../oxc/src/app/components/LoadingState.vue | 11 - packages/oxc/src/app/components/Search.vue | 29 +- .../oxc/src/app/components/SessionCard.vue | 92 +- .../oxc/src/app/components/SummaryCard.vue | 206 +-- .../oxc/src/app/components/visual/Loading.vue | 2 +- packages/oxc/src/app/composables/monaco.ts | 67 +- .../oxc/src/app/composables/useFileUtils.ts | 35 +- packages/oxc/src/app/pages/fmt/config.vue | 56 +- packages/oxc/src/app/pages/index.vue | 267 ++-- packages/oxc/src/app/pages/lint/config.vue | 56 +- .../oxc/src/app/pages/lint/report/[id].vue | 10 +- .../oxc/src/app/pages/lint/report/index.vue | 35 +- packages/oxc/src/app/plugins/floating-vue.ts | 6 + packages/oxc/src/nuxt.config.ts | 30 +- packages/oxc/src/uno.config.ts | 42 + packages/rolldown/src/nuxt.config.ts | 5 +- plans/README.md | 2 +- pnpm-lock.yaml | 1392 +---------------- pnpm-workspace.yaml | 2 - tsconfig.json | 1 + 34 files changed, 671 insertions(+), 1995 deletions(-) delete mode 100644 packages/oxc/src/app/app.config.ts delete mode 100644 packages/oxc/src/app/components/ColorModeButton.vue delete mode 100644 packages/oxc/src/app/components/CongratState.vue delete mode 100644 packages/oxc/src/app/components/EmptyState.vue delete mode 100644 packages/oxc/src/app/components/LoadingState.vue create mode 100644 packages/oxc/src/app/plugins/floating-vue.ts create mode 100644 packages/oxc/src/uno.config.ts diff --git a/.gitignore b/.gitignore index 9cbd5ac40..eeab3f764 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ packages/vite/runtime packages/rolldown/src/public/fonts packages/rolldown/src/app/public/fonts packages/rolldown/runtime +packages/oxc/src/public/fonts +packages/oxc/src/app/public/fonts packages/kit/skills .rolldown .devtools-oxc diff --git a/AGENTS.md b/AGENTS.md index 50384e774..27fe06c7f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,7 +23,7 @@ Monorepo (`pnpm` workspaces + `turbo`). ESM TypeScript; bundled with `tsdown`. P | `packages/ui` | `@vitejs/devtools-ui` | Shared UI components, composables, and UnoCSS preset (`presetDevToolsUI`). Private, not published. | | `packages/rolldown` | `@vitejs/devtools-rolldown` | Nuxt UI for Rolldown build data. Hub-mounted via `Plugin.devtools.setup`. Serves at `/__devtools-rolldown/`. | | `packages/vite` | `@vitejs/devtools-vite` | Nuxt UI for Vite DevTools (WIP). Hub-mounted via `Plugin.devtools.setup`. Serves at `/__devtools-vite/`. | -| `packages/oxc` | `@vitejs/devtools-oxc` | Oxc toolchain (oxlint/oxfmt) inspector, donated from [`yuyinws/oxc-inspector`](https://github.com/yuyinws/oxc-inspector) with full history; owned by Leo. Advertised by core as a built-in install launcher in the `~viteplus` group (installed on demand), mounted via `DevToolsOxc()` from `@vitejs/devtools-oxc/vite` once present, plus a standalone CLI/client. A workspace member on the current devframe/hub APIs; its build is not wired into turbo yet (the `@nuxt/ui` client is pending a UI-stack decision), so it stays out of the build/typecheck/lint/export-snapshot gates. | +| `packages/oxc` | `@vitejs/devtools-oxc` | Oxc toolchain (oxlint/oxfmt) inspector, donated from [`yuyinws/oxc-inspector`](https://github.com/yuyinws/oxc-inspector) with full history; owned by Leo. Advertised by core as a built-in install launcher in the `~viteplus` group (installed on demand), mounted via `DevToolsOxc()` from `@vitejs/devtools-oxc/vite` once present, plus a standalone CLI/client. Its Nuxt client is on the shared stack — UnoCSS via `presetDevToolsUI` and `@vitejs/devtools-ui` components (an oxc-cyan `primary` scale derived from the `BannerOxcDevTools` accent) — and is wired into the turbo build, `vue-tsc` typecheck (own `src/tsconfig.json` reference) and export-snapshot gates. It dogfoods its own oxlint/oxfmt on itself (run `pnpm -C packages/oxc lint`), whose formatting conflicts with the repo-wide antfu ESLint config, so it stays out of the shared ESLint run. | | `packages/vitest` | `@vitejs/devtools-vitest` | Slim launcher for the Vitest UI, in the `~viteplus` dock group. A `launcher` dock (only shown when the project uses Vitest) installs `@vitest/ui` on demand, spawns `vitest --ui`, then swaps to an iframe. Serves its favicon at `/__devtools-vitest/`. | | `packages/webext` | — | Browser extension scaffolding (ancillary). | diff --git a/eslint.config.js b/eslint.config.js index 506b2c9ca..5ac10fcf2 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -14,9 +14,10 @@ export default antfu({ // dependency specifiers rather than the repo catalog and stays out of the // repo-wide ESLint run. 'playgrounds/production', - // `packages/oxc` (donated from yuyinws/oxc-inspector) carries its own - // oxlint/oxfmt style and is linted by its own toolchain, so it stays out of - // the repo-wide ESLint run. + // `packages/oxc` is DevTools for the Oxc toolchain and dogfoods its own + // oxlint/oxfmt on itself, whose formatting (e.g. `arrowParens: "avoid"`) + // conflicts with this shared antfu config, so it is linted by its own + // toolchain (`pnpm -C packages/oxc lint`) rather than the repo-wide run. 'packages/oxc', '!packages/oxc/package.json', ], diff --git a/packages/oxc/package.json b/packages/oxc/package.json index 633953eff..3f470bf03 100644 --- a/packages/oxc/package.json +++ b/packages/oxc/package.json @@ -55,14 +55,15 @@ "devDependencies": { "@humanwhocodes/momoa": "catalog:frontend", "@nuxt/kit": "catalog:build", - "@nuxt/ui": "catalog:frontend", "@types/picomatch": "catalog:types", + "@unocss/nuxt": "catalog:build", "@vitejs/devtools": "workspace:*", "@vitejs/devtools-kit": "workspace:*", "@vitejs/devtools-ui": "workspace:*", "@vueuse/core": "catalog:frontend", "@vueuse/nuxt": "catalog:build", "devframe": "catalog:deps", + "floating-vue": "catalog:frontend", "modern-monaco": "catalog:frontend", "nuxt": "catalog:build", "oxfmt": "catalog:devtools", @@ -70,10 +71,11 @@ "pathe": "catalog:deps", "picomatch": "catalog:frontend", "shiki": "catalog:frontend", - "tailwindcss": "catalog:build", + "theme-vitesse": "catalog:frontend", "tsdown": "catalog:build", "tsx": "catalog:build", "typescript": "catalog:devtools", + "unocss": "catalog:build", "vite": "catalog:build", "vue": "catalog:deps", "vue-router": "catalog:frontend" diff --git a/packages/oxc/src/app/app.config.ts b/packages/oxc/src/app/app.config.ts deleted file mode 100644 index 86415e2ae..000000000 --- a/packages/oxc/src/app/app.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -export default defineAppConfig({ - ui: { - card: { - slots: { - root: 'rounded-lg', - header: 'py-2 px-4 sm:px-4', - body: 'p-0 sm:p-0', - footer: 'p-4 sm:px-6', - }, - }, - colors: { - primary: 'cyan', - neutral: 'zinc', - }, - }, -}) diff --git a/packages/oxc/src/app/app.vue b/packages/oxc/src/app/app.vue index bccb00461..5194a1280 100644 --- a/packages/oxc/src/app/app.vue +++ b/packages/oxc/src/app/app.vue @@ -1,17 +1,17 @@ diff --git a/packages/oxc/src/app/assets/css/main.css b/packages/oxc/src/app/assets/css/main.css index 2a7f40c48..ccb5079cb 100644 --- a/packages/oxc/src/app/assets/css/main.css +++ b/packages/oxc/src/app/assets/css/main.css @@ -1,51 +1,67 @@ -@import 'tailwindcss'; -@import '@nuxt/ui'; -@custom-variant dark (&:where(.dark, .dark *)); +@import '@vitejs/devtools-ui/styles/global.css'; -body { - background-color: unset; - @apply text-neutral-800 dark:text-neutral-300; +/* Floating Vue tooltips / dropdowns — align to the shared semantic tokens. */ +.v-popper--theme-dropdown .v-popper__inner, +.v-popper--theme-tooltip .v-popper__inner { + --uno: bg-tooltip color-base font-sans rounded border border-base shadow dark: shadow-2xl; + box-shadow: 0 6px 30px #0000001a; } -.bg-dots { - background-image: url('/dot-grid-light.png'); - background-size: 50px; - background-repeat: repeat; +.v-popper--theme-dropdown .v-popper__inner { + max-width: 50vw; + max-height: 50vh; + overflow: auto; } -.dark .bg-dots, -.bg-dots.dark { - color-scheme: dark; - background-color: black; - background-image: url('/dot-grid-dark.png'); +.v-popper--theme-tooltip .v-popper__arrow-inner, +.v-popper--theme-dropdown .v-popper__arrow-inner { + visibility: visible; + --uno: border-solid border-white dark: border-#111; } -.shiki, -.shiki span { - color: var(--shiki-dark) !important; - background-color: var(--ui-bg) !important; +.v-popper--theme-tooltip .v-popper__arrow-outer, +.v-popper--theme-dropdown .v-popper__arrow-outer { + --uno: border-solid border-base; } -.summary-card { - @apply border gap-1 border-neutral-200 dark:border-neutral-700 rounded-sm px-2 py-1 flex items-center; +/* Shiki inline code blocks: follow the color scheme via the dual-theme CSS + variables, and stay transparent so the card / page background shows through + instead of Shiki's baked-in theme background. */ +.shiki, +.shiki span { + color: var(--shiki-light); + --uno: bg-transparent; } -.color-scale-neutral { - @apply text-gray-700/75 dark:text-gray-300/75; +.dark .shiki, +.dark .shiki span { + color: var(--shiki-dark); } -.color-scale-low { - @apply text-lime-700/75 dark:text-lime-300/75 dark:saturate-50; +/* Monaco */ +.monaco-editor, +.monaco-editor .margin, +.monaco-editor .view-lines, +.monaco-editor .view-line, +.monaco-editor .line-numbers { + font-family: 'Input Mono', 'FiraCode', monospace !important; + font-size: 13px !important; } -.color-scale-medium { - @apply text-amber-700/85 dark:text-amber-300/85 dark:saturate-80; +.monaco-editor, +.monaco-editor .monaco-editor-background, +.monaco-editor .overflow-guard, +.monaco-editor .margin { + background-color: transparent !important; } -.color-scale-high { - @apply text-orange-700/95 dark:text-orange-300/95; +.monaco-scrollable-element > .scrollbar > .slider { + background-color: var(--app-scrollbar-thumb) !important; + border-radius: var(--app-scrollbar-radius) !important; + transition: background 0.2s ease; } -.color-scale-critical { - @apply text-red-700/95 dark:text-red-300/95; +.monaco-scrollable-element > .scrollbar > .slider.active, +.monaco-scrollable-element > .scrollbar > .slider:hover { + background-color: var(--app-scrollbar-thumb-hover) !important; } diff --git a/packages/oxc/src/app/components/Back.vue b/packages/oxc/src/app/components/Back.vue index ecf2f34e0..b42246176 100644 --- a/packages/oxc/src/app/components/Back.vue +++ b/packages/oxc/src/app/components/Back.vue @@ -14,13 +14,8 @@ function goBack() { diff --git a/packages/oxc/src/app/components/ColorModeButton.vue b/packages/oxc/src/app/components/ColorModeButton.vue deleted file mode 100644 index af106d99d..000000000 --- a/packages/oxc/src/app/components/ColorModeButton.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - diff --git a/packages/oxc/src/app/components/CongratState.vue b/packages/oxc/src/app/components/CongratState.vue deleted file mode 100644 index 154d65e55..000000000 --- a/packages/oxc/src/app/components/CongratState.vue +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/packages/oxc/src/app/components/EmptyState.vue b/packages/oxc/src/app/components/EmptyState.vue deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/oxc/src/app/components/ErrorTooltip.vue b/packages/oxc/src/app/components/ErrorTooltip.vue index bdbbe3cf3..9909e4515 100644 --- a/packages/oxc/src/app/components/ErrorTooltip.vue +++ b/packages/oxc/src/app/components/ErrorTooltip.vue @@ -11,14 +11,12 @@ interface Props { const props = defineProps() -const severityClasses = computed(() => { - return props.message.severity === 'error' - ? 'bg-red-50 text-red-700 dark:bg-red-900/50 dark:text-red-300' - : 'bg-yellow-50 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-300' +const severityBadge = computed(() => { + return props.message.severity === 'error' ? 'badge-color-red' : 'badge-color-amber' }) const severityIcon = computed(() => { - return props.message.severity === 'error' ? 'ph:x-circle' : 'ph:warning-circle' + return props.message.severity === 'error' ? 'i-ph-x-circle' : 'i-ph-warning-circle' }) const rpc = useRpc() @@ -29,58 +27,68 @@ function handleOpenInEditor() {