# Migrate Mercury UI to the shadcn-vue version of @nychthemeron/library ## Background `@nychthemeron/library` was rebuilt from PrimeVue (unstyled mode + a custom `ThemeEngine` passthrough layer) to shadcn-vue + Tailwind v4. PrimeVue moved to a paid licensing model, so the old build was replaced in place under the same `0.0.1` version on the private registry (`git.mcpeakdev.com`) rather than published as a new version — confirmed intentional by the maintainer. The consuming app (`Mercury/ui`) still targets the old PrimeVue-era API everywhere: `main.ts` wires up the PrimeVue plugin, and 10 admin views plus `Login.vue` use globally-registered `Nych*` components with PrimeVue-style props (`label`, `severity`, `fluid`, `v-model:visible`, `:options`/ `optionLabel`/`optionValue`). None of that matches the new library's API, so this is a real port, not a version bump. ## Scope **In scope:** everything needed to get Mercury UI running correctly against the new library — build/plugin wiring, and a native rewrite of every view that uses a `Nych*` component. **Out of scope:** any new features, any styling changes beyond what's needed to preserve current look/behavior, and the library's own source (it's an external package on a separate registry). ## Current usage inventory | File | Button | InputText | Dialog | Select | Textarea | Message | |---|---|---|---|---|---|---| | Login.vue | 1 | 2 | | | | | | admin/ApiKeys.vue | 4 | 2 | 2 | 1 | | 1 | | admin/Blacklist.vue | 5 | 6 | 2 | 1 | | | | admin/Cache.vue | 2 | | | | | | | admin/Cdn.vue | 5 | 6 | 2 | | | | | admin/Cors.vue | 3 | 1 | 1 | | | | | admin/Permissions.vue | 5 | 5 | 2 | | | | | admin/Queries.vue | 5 | 3 | 2 | | 2 | | | admin/Tables.vue | 7 | 3 | 3 | 1 | | | | admin/Users.vue | 5 | 4 | 2 | 2 | | | | **Total** | **42** | **32** | **16** | **5** | **2** | **1** | ## Component API mapping **Button** (`NychButton`) — low friction, variant names carry over almost 1:1: - Content moves from `label="X"` prop to default slot: `X` - `severity="danger"` → `variant="danger"` (also: `primary` (default), `secondary`, `info`, `success`, `warning`, `danger`) - `size="small"` → `size="sm"` (also: `default`, `lg`, `icon`) - `:loading`, `:disabled` — unchanged - `fluid` prop is gone — use `class="w-full"` **Input** (`NychInputText` → `NychInput`) — rename only, `v-model`, `type`, `placeholder`, `autocomplete` unchanged. `fluid` → `class="w-full"`. **Textarea** (`NychTextarea`) — same shape as Input, `fluid` → `class="w-full"`. **Dialog** — flat component becomes a compound one: ``` ...body... Title ...body... ``` - `:closable="false"` (used once, in ApiKeys.vue's key-reveal dialog, to force the user to acknowledge before dismissing) → pass `:show-close-button="false"` on `NychDialogContent` to drop the X button, and ignore attempts to close via `@update:open="(v) => { if (v) show = v }"` so outside-click/Escape can't dismiss it either. - `:draggable="false"` — no-op, the new Dialog isn't draggable to begin with. - Width customization (`style="width: min(560px, 95vw)"`) — pass through as `class` on `NychDialogContent` since it accepts a `class` prop merged via `cn()`. **Select** — flat `:options` array becomes compound children: ``` {{ r.label }} ``` **Message → Alert** — no `NychMessage` in the new library; `NychAlert` is the closest analog: - `severity="warn"` → `variant="warning"` (also: `info` (default), `success`, `danger`, `secondary`) - Body goes in the default slot, same as before. ## Phase 1 — Foundation (build/plugin wiring) 1. **`src/main.ts`** — remove the `@primevue/core/config` import and `app.use(PrimeVue, { unstyled: true })` call. Keep `import '@nychthemeron/library/theme'` and `app.use(createNychthemeron())` unchanged (both still resolve, just point at new content). 2. **`vite.config.ts`** — add the `@tailwindcss/vite` plugin (required because the library now ships source CSS with `@import "tailwindcss"` that must be processed at build time, not a precompiled stylesheet). Replace the `primevue: ["@primevue/core"]` manual chunk with nothing (package no longer exists). 3. **`package.json`** — add `tailwindcss` and `@tailwindcss/vite` as direct devDependencies (currently only reachable transitively through `@nychthemeron/library`, which is fragile) at the versions already resolved in `bun.lock`. Bump `vue` from `^3.4.0` to `^3.5.0` to match the library's peer requirement. 4. **`src/assets/main.css`** — leave the design-token-based custom classes (`.table-card`, `.data-table`, `.dialog-form`, `.gold-rule`, `.loading-overlay`, etc.) as-is; they key off CSS custom properties (`--surface-1`, `--primary`, ...) which kept the same names and values in the new theme. Remove the dead overrides tied to the old unstyled-PrimeVue class names: `.nych-dialog { min-width: 500px }`, `[class^="nych-button"] { ... }`, and re-check `.nych-loading-icon .wreath` still applies (the new `NychLoadingIcon` keeps the same class name/markup, so this one likely stays). 5. Verify with `bun run dev` that the app boots without console errors before touching any view. ## Phase 2 — View-by-view native migration Migrate one file at a time, in this order (simplest/lowest-risk first, so mistakes in the component mapping surface early on a small file rather than a large one): 1. `Login.vue` (3 call sites, no Dialog/Select — good smoke test) 2. `admin/Cache.vue` (2 call sites, Button only) 3. `admin/Cors.vue` 4. `admin/Queries.vue` 5. `admin/Blacklist.vue` 6. `admin/Permissions.vue` 7. `admin/Users.vue` 8. `admin/Cdn.vue` 9. `admin/Tables.vue` 10. `admin/ApiKeys.vue` (has the one `NychMessage` → `NychAlert` conversion and the non-dismissible dialog pattern — do it last once that pattern is proven elsewhere isn't needed, i.e. it's the only file needing it) For each file: apply the mapping above, then run the app (`bun run dev`), navigate to that view in the browser, and check the golden path (list renders, create/edit dialog opens and submits, select options populate, delete/revoke works) before moving to the next file. This is a visual/UI change, so it needs to be checked in a real browser, not just type-checked. ## Verification - `vue-tsc --noEmit` (part of `bun run build`) after each file, to catch prop-shape mistakes the compiler can see. - Manual browser check of each migrated view per the sequencing above. - Final full pass: `bun run build` succeeds, and a click-through of every admin view's create/edit/delete flow. ## Open questions / risks - The non-dismissible-dialog pattern for `ApiKeys.vue`'s key-reveal dialog (ignoring `update:open` on close attempts) isn't a built-in library feature — it's an app-level workaround. If it feels fragile once implemented, worth flagging back to the library rather than solving twice.