@gallopsystems/agent-skills 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,6 +18,7 @@ Then install the skills you want:
18
18
  /plugin install doctl@gallop-systems-agent-skills
19
19
  /plugin install git-github@gallop-systems-agent-skills
20
20
  /plugin install copier-template@gallop-systems-agent-skills
21
+ /plugin install volt-primevue@gallop-systems-agent-skills
21
22
  ```
22
23
 
23
24
  ## Updating
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gallopsystems/agent-skills",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Gallop Systems Claude Code skills, symlinked into .claude/skills on install.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "volt-primevue",
3
+ "description": "Volt (unstyled PrimeVue + Tailwind) UI: adding components, pt: pass-through styling, the surface-* vs semantic-token color model, and prefers-color-scheme dark mode",
4
+ "version": "1.0.0",
5
+ "author": {
6
+ "name": "yeedle"
7
+ }
8
+ }
@@ -0,0 +1,126 @@
1
+ ---
2
+ name: volt-primevue
3
+ description: Build UIs with Volt (unstyled PrimeVue + Tailwind). Covers adding components, pt: pass-through customization, choosing components, and the two-layer color model for prefers-color-scheme dark mode.
4
+ ---
5
+
6
+ # Volt + PrimeVue UI
7
+
8
+ Volt components are **PrimeVue unstyled components styled with Tailwind**. They
9
+ ship as source you vendor into `src/volt/` and register with a `Volt` prefix, so
10
+ you own the markup but track upstream styling conventions.
11
+
12
+ ## When to Use This Skill
13
+
14
+ - Building UI in a Nuxt + Tailwind v4 + PrimeVue/Volt project
15
+ - Adding or customizing a Volt component
16
+ - Anything color/theme/dark-mode related in this stack
17
+ - Deciding between a Volt component and a hand-rolled one
18
+
19
+ ## What Volt is
20
+
21
+ - PrimeVue **unstyled** components + Tailwind classes, vendored under `src/volt/`.
22
+ - Registered with a `Volt` prefix via `nuxt.config.ts` (`<VoltButton>`, `<VoltCard>`, …).
23
+ - `app/assets/css/main.css` has `@source "../../../src/volt";` so Tailwind scans
24
+ the vendored sources for class names. If a Volt class isn't generating, that
25
+ `@source` line is the first thing to check.
26
+
27
+ ## Adding components
28
+
29
+ Use the CLI — **never hand-create a Volt component**:
30
+
31
+ ```bash
32
+ npx volt-vue add MultiSelect # adds src/volt/MultiSelect.vue
33
+ ```
34
+
35
+ Hand-writing one means you've guessed the PrimeVue part structure and the
36
+ `surface-*`/`dark:` conventions; the generator gets both right and stays
37
+ consistent with upstream.
38
+
39
+ ## Customization — `pt:` pass-through
40
+
41
+ Volt uses PrimeVue's pass-through API. Target a component's internal section with
42
+ `pt:<section>:class`:
43
+
44
+ ```vue
45
+ <VoltButton pt:root:class="bg-zinc-900 hover:bg-zinc-800" />
46
+ <VoltCard pt:root:class="rounded-2xl" pt:body:class="p-6" />
47
+ ```
48
+
49
+ **Use `pt:`, not a plain `class`.** Volt merges classes with
50
+ [tailwind-merge](https://volt.primevue.org/overview/#twmerge), and the two paths
51
+ differ in precedence:
52
+
53
+ - `pt:{section}:class` is **merged with the component's defaults** and reliably
54
+ overrides them — `pt:root:class="bg-primary"` wins.
55
+ - A plain `class="bg-primary"` has **lower precedence and may not apply** — its
56
+ conflicting utilities can lose to the component's own classes.
57
+
58
+ So `<VoltInputText pt:root:class="bg-primary" />` works; `<VoltInputText
59
+ class="bg-primary" />` may silently not.
60
+
61
+ What `pt:` **can't** do is change DOM — it only restyles sections the component
62
+ already renders. To add an element the component doesn't have (e.g. an animated
63
+ overlay), you have two honest options, because Volt components are **vendored and
64
+ yours to edit** (see [Choosing a component](#choosing-a-component-volt-vs-custom)):
65
+ edit the component's source in `src/volt/`, or build a standalone component.
66
+
67
+ ## Choosing a component: Volt vs custom
68
+
69
+ Volt gives you selection semantics + accessibility for free. The question is only
70
+ whether you need DOM/behavior the component doesn't render — and remember Volt
71
+ components are **vendored and editable**, so "the component doesn't do X" has
72
+ three answers, not two: restyle via `pt:`, **edit the source in `src/volt/`**, or
73
+ build standalone.
74
+
75
+ Worked example — **segmented toggle** (`SelectButton` vs a custom `SlidingTabs`):
76
+
77
+ - `VoltSelectButton` ships v-model, single/multi-select, label+icon options, and
78
+ proper radiogroup a11y, with a *highlighted-active* look.
79
+ - A custom `SlidingTabs` adds an **animated indicator** (a single shared element
80
+ that measures the active button and slides), responsive label collapse, and
81
+ token-matched styling.
82
+ - The slide **can't** come from `pt:` — `SelectButton` toggles a background class
83
+ per button and has no shared, position-measured overlay, and `pt:` changes
84
+ classes, not DOM. But that doesn't force a rewrite: since the source lives in
85
+ `src/volt/SelectButton.vue`, adding the indicator **there** is a legitimate
86
+ option (you keep its a11y + selection model).
87
+
88
+ So the real decision:
89
+
90
+ - **No animation needed → `SelectButton` as-is** (`pt:` to match your design).
91
+ Free a11y + multi-select; don't reinvent it.
92
+ - **Animated indicator / responsive collapse needed →** either **edit the
93
+ vendored `SelectButton`** (keep its a11y, accept that you now own that file and
94
+ lose easy `volt-vue add` regeneration) **or build a standalone `SlidingTabs`**
95
+ (clean and decoupled, but you owe the a11y yourself — `role="group"` +
96
+ `aria-pressed` at minimum). Standalone wins when it's a *filter* toggle rather
97
+ than a form field; editing the source wins when you want the full input
98
+ semantics.
99
+
100
+ ## Theming & dark mode
101
+
102
+ This is the part people get wrong. Read **[theming.md](./theming.md)** — the
103
+ two-layer color model (`surface-*` for Volt, semantic tokens for your markup),
104
+ why they can't be unified, and the `prefers-color-scheme` mechanics.
105
+
106
+ The one-paragraph version: dark mode follows the OS via
107
+ `@media (prefers-color-scheme: dark)`. **Your app markup** uses semantic `@theme`
108
+ tokens (`bg-surface`, `text-fg`, `border-line`) written **once** — the
109
+ `--color-*` var flips in the dark media block, so there's no `dark:` half to
110
+ forget. **Volt internals** stay on the `surface-*` scale with explicit `dark:`
111
+ pairs — they need the full 0–950 ramp, and leaving them as `volt-vue add`
112
+ generated them keeps regeneration easy. You *could* restyle a vendored component
113
+ to tokens (it's your code), but the small token set can't express the whole ramp,
114
+ so don't — tokens for your opinionated markup, `surface-*` for the component
115
+ library.
116
+
117
+ ## Gotchas
118
+
119
+ See **[gotchas.md](./gotchas.md)** — the ones that cost real debugging time:
120
+
121
+ - `@reference "main.css"` (not `"tailwindcss"`) for `@apply` of custom tokens in
122
+ SFC `<style>` blocks.
123
+ - JS-driven colors (ApexCharts, canvas) can't read CSS tokens — pick them from a
124
+ reactive `prefers-color-scheme` palette.
125
+ - A bare `boolean` prop casts to `false` when absent — default it with
126
+ `withDefaults`, never rely on `undefined`.
@@ -0,0 +1,66 @@
1
+ # Gotchas
2
+
3
+ The ones that cost real debugging time in this stack.
4
+
5
+ ## `@apply` of custom tokens in `<style>` needs `@reference "main.css"`
6
+
7
+ Tailwind v4 SFC `<style>` blocks need a `@reference` to resolve `@apply`. The
8
+ trap: `@reference "tailwindcss"` only loads the **default** theme — built-in
9
+ colors (`zinc`, etc.) work, but your custom `@theme` tokens (`text-fg`,
10
+ `bg-surface`) fail with *"Cannot apply unknown utility class."*
11
+
12
+ ```vue
13
+ <style scoped>
14
+ /* ❌ @reference "tailwindcss"; → @apply text-fg fails */
15
+ @reference "../assets/css/main.css"; /* ✅ exposes your tokens */
16
+ .prose :where(h2) { @apply text-fg; }
17
+ </style>
18
+ ```
19
+
20
+ `@reference` is relative to the SFC. Note `yarn build` validates `@apply` in
21
+ `<style>`; a bare `@tailwindcss/cli` compile does **not**, so the CLI can pass
22
+ while the real build fails — include `build` in your check.
23
+
24
+ ## JS-driven colors can't read CSS tokens — use a reactive palette
25
+
26
+ Anything that sets colors as JS values (ApexCharts, canvas, SVG attributes
27
+ written from script) can't use `bg-surface`/`var(--color-fg)` — the value is
28
+ baked at render. Pick concrete colors from a reactive `prefers-color-scheme`
29
+ match and recompute on change:
30
+
31
+ ```ts
32
+ const isDark = ref(false);
33
+ let mq: MediaQueryList | null = null;
34
+ const sync = () => (isDark.value = mq?.matches ?? false);
35
+ onMounted(() => {
36
+ mq = window.matchMedia("(prefers-color-scheme: dark)");
37
+ sync();
38
+ mq.addEventListener("change", sync);
39
+ });
40
+ onBeforeUnmount(() => mq?.removeEventListener("change", sync));
41
+
42
+ const palette = computed(() =>
43
+ isDark.value ? { fg: "#f4f4f5", line: "#f4f4f5", grid: "#27272a" }
44
+ : { fg: "#18181b", line: "#18181b", grid: "#f4f4f5" });
45
+ ```
46
+
47
+ Mirror the dark values from `main.css`. A chart `colors: ["#18181b"]` (near-black)
48
+ is invisible on dark — invert the line to a light value via the palette.
49
+
50
+ ## A bare `boolean` prop casts to `false` when absent — `withDefaults` it
51
+
52
+ Vue's Boolean-prop casting: a prop typed `boolean` with **no default** coerces to
53
+ `false` when the parent doesn't pass it — *not* `undefined`. So a "default-on"
54
+ flag written as `responsive?: boolean` + `props.responsive !== false` is always
55
+ `false` unless explicitly passed, and the feature silently never fires.
56
+
57
+ ```ts
58
+ // ❌ absent → false → feature off, no error
59
+ const props = defineProps<{ responsive?: boolean }>();
60
+ // ✅ absent → true
61
+ const props = withDefaults(defineProps<{ responsive?: boolean }>(), { responsive: true });
62
+ ```
63
+
64
+ Not Volt-specific, but it bit the `SlidingTabs` responsive collapse, so it lives
65
+ here. Any "defaults to on" boolean prop must use `withDefaults` (or invert it to
66
+ an opt-out flag that naturally defaults false).
@@ -0,0 +1,123 @@
1
+ # Theming & dark mode
2
+
3
+ Dark mode follows the OS: `@media (prefers-color-scheme: dark)`. No toggle, no
4
+ `dark` class on `<html>`. Tailwind v4's `dark:` variant also keys on
5
+ `prefers-color-scheme` by default, so everything reacts to the same signal.
6
+
7
+ There are **two color systems** in play, and the whole skill is knowing which to
8
+ use where.
9
+
10
+ ## The two layers
11
+
12
+ | | App markup (you own) | Volt internals (vendored) |
13
+ |---|---|---|
14
+ | **System** | semantic `@theme` tokens | `surface-*` scale + `dark:` pairs |
15
+ | **Example** | `bg-surface text-fg` | `bg-surface-0 dark:bg-surface-900` |
16
+ | **How it flips** | the `--color-*` var changes value in the dark media block | each shade is fixed; the component picks the dark end with `dark:` |
17
+ | **You write** | once | both halves |
18
+
19
+ ### Why not use semantic tokens everywhere (incl. Volt)?
20
+
21
+ You *can* — Volt components are vendored and editable, so nothing stops you from
22
+ restyling one to `bg-surface text-fg`. The reasons not to are practical, in
23
+ descending order of weight:
24
+
25
+ 1. **Vocabulary mismatch (the real one).** Semantic tokens are a small,
26
+ opinionated set (`surface`, `surface-muted`, `line`, `fg`, `fg-muted`, …) for
27
+ "card / text / border" decisions. A component library reaches all over the
28
+ 0–950 ramp — subtle fills, two border weights, icon idle vs hover, elevation
29
+ layering. To express all of Volt in tokens you'd expand them until they *are*
30
+ the ramp, at which point you've just renamed `surface-*`.
31
+ 2. **Regeneration convenience.** `volt-vue add` scaffolds a component in the
32
+ upstream `surface-*` + `dark:` convention. Restyle it and you own that file —
33
+ re-adding or pulling a newer version later means redoing your edits. Leaving
34
+ Volt as-generated keeps that escape hatch cheap. (This is a convenience cost,
35
+ not "you diverge from upstream forever" — there's no continuous sync; you add
36
+ once and own it either way.)
37
+ 3. **The convention they ship in.** As generated, `--p-surface-0…950` are fixed
38
+ (never flipped), so a Volt component flips by *picking the dark end* with
39
+ `dark:bg-surface-900` — not by a value that changes. Semantic tokens flip the
40
+ variable's value instead, so one class covers both schemes. You could rewrite
41
+ a component to the token style, but then you're back to reasons 1 and 2.
42
+
43
+ ## The token set
44
+
45
+ Defined in `app/assets/css/main.css`: light values in a top-level `@theme`
46
+ block, dark values overriding the same `--color-*` vars inside the
47
+ `prefers-color-scheme: dark` media block.
48
+
49
+ ```css
50
+ @theme {
51
+ --color-canvas: #fafafa; /* app background */
52
+ --color-surface: #ffffff; /* cards, panels, dialogs */
53
+ --color-surface-muted: #fafafa; /* subtle fills, row hover */
54
+ --color-line: #e4e4e7; /* borders */
55
+ --color-line-soft: #f4f4f5; /* soft dividers */
56
+ --color-fg: #18181b; /* primary text */
57
+ --color-fg-muted: #71717a; /* secondary text */
58
+ --color-fg-subtle: #a1a1aa; /* tertiary text */
59
+ --color-accent: #18181b; /* inverted/primary fills (buttons) */
60
+ --color-on-accent: #ffffff; /* text/icon on an accent fill */
61
+ --color-fill: #e4e4e7; /* neutral solid fills: avatars, tracks, dots */
62
+ }
63
+
64
+ @media (prefers-color-scheme: dark) {
65
+ :root {
66
+ --color-canvas: #09090b;
67
+ --color-surface: #18181b;
68
+ --color-surface-muted: #27272a;
69
+ --color-line: #27272a;
70
+ --color-line-soft: #27272a;
71
+ --color-fg: #f4f4f5;
72
+ --color-fg-muted: #b4b4bc; /* lifted off a strict mirror-invert for legibility */
73
+ --color-fg-subtle: #8c8c95; /* ~5.5:1 on near-black */
74
+ --color-accent: #f4f4f5; /* inverts so filled buttons read on dark */
75
+ --color-on-accent: #18181b;
76
+ --color-fill: #3f3f46;
77
+ }
78
+ }
79
+ ```
80
+
81
+ The dark `fg-muted` / `fg-subtle` are **lifted** off a strict mirror-invert —
82
+ the perfectly-inverted values are too dark to read on near-black.
83
+
84
+ ### Where each layer lives (and why it's not fighting Volt)
85
+
86
+ [Volt's Nuxt setup](https://volt.primevue.org/nuxt/#css-variables) prescribes the
87
+ `--p-*` palette + semantic tokens in **`:root`**, with dark mode via
88
+ `@media (prefers-color-scheme: dark)`. Keep that exactly as-is — don't move
89
+ `--p-*` into `@theme`; the `tailwindcss-primeui` plugin already turns them into
90
+ `surface-*` / `primary-*` utilities.
91
+
92
+ Your semantic tokens go in **`@theme`** instead, because that's the Tailwind v4
93
+ mechanism that generates the utilities (`--color-canvas` → `bg-canvas`). Defining
94
+ them only in `:root` would set the variable but produce **no class**. So:
95
+
96
+ - `--p-*` → `:root` (Volt's way; plugin generates `surface-*`)
97
+ - `--color-*` → `@theme` (Tailwind's way; generates `bg-surface`, `text-fg`, …)
98
+
99
+ Different namespaces, no collision: `bg-surface-0` (primeui, numbered) and
100
+ `bg-surface` (your token, bare) coexist. Dark values for **both** sit in the same
101
+ `prefers-color-scheme` block — your `--color-*` overrides next to Volt's `--p-*`
102
+ overrides.
103
+
104
+ ## The naming trap (this one bites)
105
+
106
+ In Tailwind v4 the utility name is the **full** variable suffix:
107
+
108
+ - `--color-fg-subtle` → `text-fg-subtle` ✅
109
+ - `text-subtle` ❌ — generates **nothing**, element falls back to near-black
110
+
111
+ A wrong/shortened token name fails silently (no class generated), so a
112
+ suspicious "secondary text is black in dark mode" is almost always a misnamed
113
+ token, not a wrong value. **Compile the CSS and grep the generated utilities** —
114
+ don't eyeball the browser.
115
+
116
+ ## Adding a new token
117
+
118
+ 1. Add `--color-foo: <light>;` to the `@theme` block.
119
+ 2. Add `--color-foo: <dark>;` inside the dark media block.
120
+ 3. Use `bg-foo` / `text-foo` / `border-foo` — done, both schemes covered.
121
+
122
+ No `dark:` variant, ever, for app markup. If you're typing `dark:` in a page or
123
+ app component, you're reaching for the wrong layer.