nuxt-unified-ui 0.5.17 → 0.5.18
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 +1 -1
- package/app/components/un-table.vue +11 -1
- package/package.json +1 -1
- package/skills/nuxt-unified-ui/SKILL.md +68 -7
- package/skills/nuxt-unified-ui/references/apply.md +30 -5
- package/skills/nuxt-unified-ui/references/code-style.md +88 -11
- package/skills/nuxt-unified-ui/references/dialogs.md +0 -2
- package/skills/nuxt-unified-ui/references/forms.md +3 -0
- package/skills/nuxt-unified-ui/references/layer-setup.md +1 -0
- package/skills/nuxt-unified-ui/references/pages.md +1 -0
- package/skills/nuxt-unified-ui/references/resources.md +4 -2
- package/skills/nuxt-unified-ui/references/toast-and-ui.md +4 -3
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ vpr serve
|
|
|
20
20
|
|
|
21
21
|
## Agent Skills
|
|
22
22
|
|
|
23
|
-
One installable Agent Skill lives under `skills/nuxt-unified-ui/` (`npx skills` compatible). It covers the layer API **and** mandatory Nuxt code style (forms, dialogs, radashi, formatting). When an agent finishes implementing work, it runs `/nuxt-unified-ui apply`: one subagent per `.vue`, `.ts`, or `.js` file applies the skill's logical, structural, and code-style rules
|
|
23
|
+
One installable Agent Skill lives under `skills/nuxt-unified-ui/` (`npx skills` compatible). It covers the layer API **and** mandatory Nuxt code style (forms, dialogs, radashi, formatting). When an agent finishes implementing work, it runs `/nuxt-unified-ui apply`: one subagent per `.vue`, `.ts`, or `.js` file, launched using a cheap fast model, applies the skill's logical, structural, and code-style rules. On `dev`, `main`, or `master`, it targets uncommitted files and falls back to the whole project when the branch is clean; outside a Git worktree it also targets the whole project. On other branches, it targets files changed relative to the base branch. You can also invoke `/nuxt-unified-ui apply` directly.
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
npx skills add . --list
|
|
@@ -163,6 +163,16 @@ function handleRowSelect(_event, row) {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
|
|
166
|
+
const rowSelectHandler = computed(() => {
|
|
167
|
+
if (!props.rowTo) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
return handleRowSelect;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
|
|
166
176
|
/* pagination */
|
|
167
177
|
|
|
168
178
|
const pageSizeItems = computed(() => {
|
|
@@ -194,7 +204,7 @@ const pageSizeItems = computed(() => {
|
|
|
194
204
|
:column-pinning="columnPinning"
|
|
195
205
|
:ui="tableUi"
|
|
196
206
|
:meta="props.meta"
|
|
197
|
-
|
|
207
|
+
:on-select="rowSelectHandler">
|
|
198
208
|
|
|
199
209
|
<template v-for="(_, name) in $slots" #[name]="slotData">
|
|
200
210
|
<slot
|
package/package.json
CHANGED
|
@@ -8,9 +8,10 @@ description: >-
|
|
|
8
8
|
resource-manager, createUnifiedResourceController). Use when package.json
|
|
9
9
|
depends on nuxt-unified-ui, nuxt.config extends it, or code uses these
|
|
10
10
|
APIs. Whenever implementation work is finished, run `/nuxt-unified-ui
|
|
11
|
-
apply`, which applies the skill file by file to
|
|
12
|
-
main, or master) or to the current branch's changed files
|
|
13
|
-
|
|
11
|
+
apply`, which applies the skill file by file to uncommitted work (on dev,
|
|
12
|
+
main, or master) or to the current branch's changed files, falling back to
|
|
13
|
+
the whole project when there is no Git worktree or the default branch is
|
|
14
|
+
clean; users can also invoke it directly.
|
|
14
15
|
---
|
|
15
16
|
|
|
16
17
|
# nuxt-unified-ui
|
|
@@ -21,7 +22,7 @@ The main agent decides what code exists, where it lives, and which APIs it calls
|
|
|
21
22
|
|
|
22
23
|
Whenever you finish implementing work that added or edited a `.vue`, `.js`, or `.ts` file, run `/nuxt-unified-ui apply` before your final reply: follow [references/apply.md](references/apply.md). Do the same when the skill is invoked with the argument `apply`, or the user asks to apply nuxt-unified-ui across the project or the current branch. Invoked without an argument, the skill only loads its guidance for the task at hand.
|
|
23
24
|
|
|
24
|
-
On `dev`, `main`, or `master` it processes
|
|
25
|
+
On `dev`, `main`, or `master` it processes uncommitted `.vue`, `.js`, and `.ts` files; when that branch is clean, or the project is not a Git worktree, it processes every eligible file. On any other branch, it processes only the files that branch added or changed relative to its base branch. One subagent per file, launched using a cheap fast model, applies the logical, structural, and code-style rules, then the main agent carries out the cross-file follow-ups.
|
|
25
26
|
|
|
26
27
|
## APIs
|
|
27
28
|
|
|
@@ -116,7 +117,67 @@ Make these choices while implementing. The apply pipeline checks them again afte
|
|
|
116
117
|
|
|
117
118
|
## i18n
|
|
118
119
|
|
|
120
|
+
**i18n is optional. Conform to the project's current state.**
|
|
121
|
+
|
|
122
|
+
- If the project does not translate its own strings (literal text, no `$t`, no locale files of its own), keep writing literal strings. Do not introduce `$t`, locale files, or i18n config unless the user decides to integrate i18n.
|
|
123
|
+
- If the project already uses i18n in another layout, follow that layout.
|
|
124
|
+
- The rules below apply once the project has decided to integrate i18n, or already follows them. This layer's own `$t` usage does not count as a decision for the project.
|
|
125
|
+
- Examples in the references often use English literals for brevity. In an i18n project those strings are `$t('...')` keys.
|
|
126
|
+
|
|
127
|
+
**When i18n is integrated**
|
|
128
|
+
|
|
119
129
|
- Every user-facing string goes through `$t`, in templates and in script. `$t` is available in both without calling `useI18n()`.
|
|
120
|
-
-
|
|
121
|
-
- `
|
|
122
|
-
-
|
|
130
|
+
- Global i18n settings — `strategy`, `defaultLocale`, and the full `locales` list (codes, names, languages) — go in the `nuxt.config.ts` of the **aarde layer**: the project's base app layer that extends `nuxt-unified-ui` (for example `layers/100-aarde/`). If the project has no aarde layer, use the layer that extends `nuxt-unified-ui`.
|
|
131
|
+
- Every other layer (feature) keeps its own locale files in its own `i18n/locales/<code>.json`. In its own `nuxt.config.ts` it declares only those files — `i18n: { locales: [{ code, file }] }` — and no other i18n settings. `@nuxtjs/i18n` loads a layer's locale files only when that layer lists them, then merges the same locale from every layer into one message tree.
|
|
132
|
+
- The layer that owns a file is the nearest directory above it with a `nuxt.config`. Its strings go in that layer's locale files only — never in another layer's or the host's.
|
|
133
|
+
- Each layer's locale file has **exactly one top-level key**: the layer's namespace, such as `patients` or `billing`. Every key nests under it (`$t('patients.single.title')`), so merged trees never clash.
|
|
134
|
+
- Add every new key to each locale that layer declares.
|
|
135
|
+
- This layer owns two top-level keys: `un` for its components and `common` for shared labels (`common.submit`, `common.cancel`, `common.close`, …). Reuse `common.*` instead of duplicating those labels, but never add keys to `un` or `common` from another layer.
|
|
136
|
+
|
|
137
|
+
Aarde layer `nuxt.config.ts` (fragment):
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
i18n: {
|
|
141
|
+
strategy: 'no_prefix',
|
|
142
|
+
defaultLocale: 'en',
|
|
143
|
+
locales: [
|
|
144
|
+
{
|
|
145
|
+
code: 'en',
|
|
146
|
+
name: 'English',
|
|
147
|
+
file: 'en.json',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
code: 'de',
|
|
151
|
+
name: 'Deutsch',
|
|
152
|
+
file: 'de.json',
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Feature layer `nuxt.config.ts` (fragment) and its `i18n/locales/en.json`:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
i18n: {
|
|
162
|
+
locales: [
|
|
163
|
+
{
|
|
164
|
+
code: 'en',
|
|
165
|
+
file: 'en.json',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
code: 'de',
|
|
169
|
+
file: 'de.json',
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"patients": {
|
|
178
|
+
"single": {
|
|
179
|
+
"title": "Patient"
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
@@ -17,13 +17,36 @@ Do not edit files while subagents are running.
|
|
|
17
17
|
|
|
18
18
|
Run from the repository root.
|
|
19
19
|
|
|
20
|
+
First check whether the project is a Git worktree:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
git rev-parse --is-inside-work-tree >/dev/null 2>&1
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
If it is not, select every eligible file in the project:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
rg --files --hidden -g '*.vue' -g '*.js' -g '*.ts' -g '!*.d.ts'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Otherwise, get the branch:
|
|
33
|
+
|
|
20
34
|
```bash
|
|
21
35
|
branch=$(git branch --show-current)
|
|
22
36
|
```
|
|
23
37
|
|
|
24
38
|
If `branch` is empty (detached HEAD), stop and ask the user which files to process.
|
|
25
39
|
|
|
26
|
-
**On `dev`, `main`, or `master`** —
|
|
40
|
+
**On `dev`, `main`, or `master`** — select only added or changed, uncommitted files (staged, unstaged, or untracked):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
{
|
|
44
|
+
git diff --name-only --diff-filter=AMR HEAD -- '*.vue' '*.js' '*.ts' ':!*.d.ts'
|
|
45
|
+
git ls-files --others --exclude-standard -- '*.vue' '*.js' '*.ts' ':!*.d.ts'
|
|
46
|
+
} | sort -u
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If this selects no files because all work has been committed, select every tracked or new, non-ignored eligible file instead:
|
|
27
50
|
|
|
28
51
|
```bash
|
|
29
52
|
git ls-files --cached --others --exclude-standard -- '*.vue' '*.js' '*.ts' ':!*.d.ts'
|
|
@@ -53,12 +76,14 @@ The first line is the base: `<commits> <ref> <merge-base sha>`. If there is no o
|
|
|
53
76
|
} | sort -u
|
|
54
77
|
```
|
|
55
78
|
|
|
56
|
-
Deleted files are excluded. Tell the user the branch, the base (when there is one), and how many files were selected. If none were selected, stop.
|
|
79
|
+
Deleted files are excluded. Tell the user whether the project is outside Git or on a branch, the base (when there is one), whether the clean default-branch fallback was used, and how many files were selected. If none were selected, stop.
|
|
57
80
|
|
|
58
81
|
### 2. Launch one subagent per file
|
|
59
82
|
|
|
60
83
|
Launch exactly one subagent per selected file — never several files in one subagent. Run them in parallel batches. Each subagent edits only its own file, so parallel runs do not conflict.
|
|
61
84
|
|
|
85
|
+
**Model:** launch every per-file subagent using a cheap fast model. The main agent keeps its own model for steps 1, 3, and 5.
|
|
86
|
+
|
|
62
87
|
Use this prompt:
|
|
63
88
|
|
|
64
89
|
```text
|
|
@@ -75,13 +100,13 @@ Return the report described there.
|
|
|
75
100
|
|
|
76
101
|
When every subagent has reported, collect their follow-ups, remove duplicates, and apply them **one at a time** in the main agent:
|
|
77
102
|
|
|
78
|
-
- **`i18n`** — add each key to every locale file the
|
|
103
|
+
- **`i18n`** — add each key to the locale files of the layer that owns the reporting file, following the i18n rules in `SKILL.md` (that layer's `i18n/locales/`, under its single top-level key, in every locale it declares). When the layer has no locale files yet, create them and add its `i18n: { locales: [{ code, file }] }` declaration for the locales the aarde layer defines; global i18n settings stay in the aarde layer. Use the reported English text for English; translate for other locales when confident, otherwise use the English text and list those keys in the summary.
|
|
79
104
|
- **`split`** / **`move`** / **`promote`** — apply the file-structure rules from `SKILL.md`: create or move the files, then update every caller and import.
|
|
80
105
|
- **`other`** — apply cross-file changes that follow directly from the skill (for example a missing REST route of a resource). List anything that needs a product decision in the summary instead of guessing.
|
|
81
106
|
|
|
82
107
|
### 4. Second round for what step 3 touched
|
|
83
108
|
|
|
84
|
-
Launch the same per-file subagent (step 2) for every `.vue`, `.js`, or `.ts` file created or edited in step 3, then apply their `i18n` follow-ups. Do not start a third round: list any other follow-ups from this round in the summary.
|
|
109
|
+
Launch the same per-file subagent (step 2, using a cheap fast model) for every `.vue`, `.js`, or `.ts` file created or edited in step 3, then apply their `i18n` follow-ups. Do not start a third round: list any other follow-ups from this round in the summary.
|
|
85
110
|
|
|
86
111
|
### 5. Verify and summarize
|
|
87
112
|
|
|
@@ -106,7 +131,7 @@ You bring one `.vue`, `.js`, or `.ts` file in line with the nuxt-unified-ui skil
|
|
|
106
131
|
3. **Logical pass** — fix in place:
|
|
107
132
|
- Replace hand-rolled code with the APIs in `SKILL.md`: raw `$fetch` / `useFetch` → `ufetch` / `useUFetch`; hand-built modals → dialog launchers; direct `radashi` imports → `radXxx`; and so on.
|
|
108
133
|
- Apply the decisions in `SKILL.md` and the rules of the references you read: page `definePageMeta.name` and SEO, dialog logic in `onClick`, named routes, `to` for navigation-only actions, a dumb `un-table`, reactive fetch gates, component conventions.
|
|
109
|
-
-
|
|
134
|
+
- Only when the project has integrated i18n (`SKILL.md` i18n rules): move user-facing literals to `$t('...')` keys under the owning layer's top-level key. Reuse existing keys, including this layer's `common.*` labels. Record each new key as an `i18n` follow-up with its full path. In a project without i18n, leave literal strings as they are.
|
|
110
135
|
- Make `atoms` / `libs` imports relative.
|
|
111
136
|
4. **Structural pass** — decide, do not execute:
|
|
112
137
|
- Two or more independent responsibilities → `split` follow-up with each responsibility and its suggested path.
|
|
@@ -53,10 +53,10 @@ Shape, in this order:
|
|
|
53
53
|
2. The line `/* responsibility */`
|
|
54
54
|
3. One blank line
|
|
55
55
|
4. The job, as short `//` comments. Use several short lines. Do not write one long line, and do not put the job in a second `/* */` block
|
|
56
|
-
5.
|
|
56
|
+
5. **Two** blank lines
|
|
57
57
|
6. The rest of the file
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
Those two blank lines are the whole gap between the header and what follows — whether that is a section comment, an import, or the first statement. Never one, never three.
|
|
60
60
|
|
|
61
61
|
`.js` / `.ts`: this block is the start of the file. Nothing comes before the leading blank line. Imports, when the file has them, start after step 5.
|
|
62
62
|
|
|
@@ -69,6 +69,7 @@ That last blank line is the blank line the next section or the first statement a
|
|
|
69
69
|
// Issues a session token
|
|
70
70
|
// after checking the login payload.
|
|
71
71
|
|
|
72
|
+
|
|
72
73
|
import { join } from 'node:path';
|
|
73
74
|
```
|
|
74
75
|
|
|
@@ -80,6 +81,7 @@ import { join } from 'node:path';
|
|
|
80
81
|
// Renders the login form
|
|
81
82
|
// and submits credentials.
|
|
82
83
|
|
|
84
|
+
|
|
83
85
|
/* login */
|
|
84
86
|
```
|
|
85
87
|
|
|
@@ -96,6 +98,17 @@ import { join } from 'node:path';
|
|
|
96
98
|
<script setup>
|
|
97
99
|
```
|
|
98
100
|
|
|
101
|
+
```ts
|
|
102
|
+
// ❌ only one blank line under the // lines
|
|
103
|
+
|
|
104
|
+
/* responsibility */
|
|
105
|
+
|
|
106
|
+
// Issues a session token
|
|
107
|
+
// after checking the login payload.
|
|
108
|
+
|
|
109
|
+
import { join } from 'node:path';
|
|
110
|
+
```
|
|
111
|
+
|
|
99
112
|
The `//` lines name the job. They do not narrate steps, list options, or repeat the file name.
|
|
100
113
|
|
|
101
114
|
---
|
|
@@ -433,6 +446,24 @@ const form = {
|
|
|
433
446
|
};
|
|
434
447
|
```
|
|
435
448
|
|
|
449
|
+
### Parameter type literals
|
|
450
|
+
|
|
451
|
+
The multi-line rule covers object **values**, not types. An object type written inline as the type of a function parameter stays on **one line**, however many members it has:
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
export async function launchDialog(options: { component: any, props: any }) {
|
|
455
|
+
...
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// ❌
|
|
459
|
+
export async function launchDialog(options: {
|
|
460
|
+
component: any,
|
|
461
|
+
props: any,
|
|
462
|
+
}) {
|
|
463
|
+
...
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
436
467
|
### Multiline assignment isolation
|
|
437
468
|
|
|
438
469
|
A **multiline assignment** is any `const` / `let` / `var` / `export const` declaration, or any `=` reassignment, whose statement spans more than one line (usually a multi-line object, array, call, or destructure: `useUFetch`, `computed(() => { … })`, `defineProps({ … })`, …).
|
|
@@ -484,6 +515,7 @@ How this combines with other spacing:
|
|
|
484
515
|
| Next to a single-line statement (including consecutive refs) | Isolation **wins**: one blank before and after |
|
|
485
516
|
| First statement after a function `{` that already wants a blank, or last before a `}` that wants one | **Share** that blank — do not add a second |
|
|
486
517
|
| Declaration-group or major-step boundary (already **two** blanks) | Keep the **two** |
|
|
518
|
+
| First statement right after the responsibility header | Keep the header's **two** |
|
|
487
519
|
| Blank already required after `/* section */` | That blank **is** the before-blank |
|
|
488
520
|
| Single-line assignment, including `{}` / `[]` | Not multiline — no isolation |
|
|
489
521
|
| Tiny / single-block function whose only statement is the assignment | Stay **flush** with the braces |
|
|
@@ -584,7 +616,7 @@ Omit unused properties; keep the rest in this order:
|
|
|
584
616
|
|
|
585
617
|
| Object | Order |
|
|
586
618
|
|--------|-------|
|
|
587
|
-
| Action objects (`:actions`, `:append-actions`, table row actions) | `vIf` → `actionType` → `variant` → `color` → `icon` → `label` → `tooltip` → `warning` → `disabled` → `to` → `href` → `onClick` → `items` |
|
|
619
|
+
| Action objects (`:actions`, `:append-actions`, table row actions) | `vIf` → `actionType` → `variant` → `color` → `icon` → `label` → `trailingIcon` → `tooltip` → `warning` → `disabled` → `to` → `href` → `onClick` → `items` |
|
|
588
620
|
| Table column defs | `accessorKey` (or `id`) → `header` → others |
|
|
589
621
|
| Tab / select / menu item objects | `value` → `icon` → `label` → others |
|
|
590
622
|
| `ufetch` options | see [Fetch calls](#fetch-calls) |
|
|
@@ -681,7 +713,7 @@ A lone `v-if` with no `v-else` is one child and stays flush. Each tag looks only
|
|
|
681
713
|
<u-button
|
|
682
714
|
variant="subtle"
|
|
683
715
|
icon="lucide:log-out"
|
|
684
|
-
@click="handleLogout"
|
|
716
|
+
@click="handleLogout()"
|
|
685
717
|
/>
|
|
686
718
|
|
|
687
719
|
</p>
|
|
@@ -723,7 +755,7 @@ A lone `v-if` with no `v-else` is one child and stays flush. Each tag looks only
|
|
|
723
755
|
- **Childless, more than one attribute or any multiline attribute:** opening `<tag` on its own line, one attribute per line, `/>` on its own line.
|
|
724
756
|
- **Tags with children:** all attributes stay on the **same line** as the opening tag, regardless of count or length. This applies to every tag, including `u-modal` and structural `<template>` wrappers.
|
|
725
757
|
- **The only split trigger for tags with children is a multiline attribute**: an array, object, or function literal bound to an attribute that spans multiple lines. Then the opening `<tag` goes on its own line, every attribute gets its own line, and the multiline value is formatted like a JS literal.
|
|
726
|
-
- References, calls, and scalar expressions (`:field="field"`, `v-bind="radOmit(action, [...])"`, `@click="handleSave"`) are **not** multiline attributes. A single-pair object with scalar values (`:ui="{ content: 'max-w-5xl' }"`) stays inline. Multi-key or nested object / array bindings are multiline attributes.
|
|
758
|
+
- References, calls, and scalar expressions (`:field="field"`, `v-bind="radOmit(action, [...])"`, `@click="handleSave()"`) are **not** multiline attributes. A single-pair object with scalar values (`:ui="{ content: 'max-w-5xl' }"`) stays inline. Multi-key or nested object / array bindings are multiline attributes.
|
|
727
759
|
|
|
728
760
|
```vue
|
|
729
761
|
<!-- ✅ children, no multiline attribute — one line -->
|
|
@@ -744,13 +776,13 @@ A lone `v-if` with no `v-else` is one child and stays flush. Each tag looks only
|
|
|
744
776
|
<u-button
|
|
745
777
|
variant="subtle"
|
|
746
778
|
icon="lucide:refresh-ccw"
|
|
747
|
-
@click="refresh"
|
|
779
|
+
@click="refresh()"
|
|
748
780
|
/>
|
|
749
781
|
```
|
|
750
782
|
|
|
751
783
|
```vue
|
|
752
784
|
<!-- ❌ childless multi-attribute tag on one line -->
|
|
753
|
-
<u-button variant="subtle" icon="lucide:refresh-ccw" @click="refresh" />
|
|
785
|
+
<u-button variant="subtle" icon="lucide:refresh-ccw" @click="refresh()" />
|
|
754
786
|
|
|
755
787
|
<!-- ❌ empty open/close pair -->
|
|
756
788
|
<div class="grow"></div>
|
|
@@ -800,7 +832,8 @@ Applies whenever attributes are on separate lines:
|
|
|
800
832
|
|
|
801
833
|
Shortcuts:
|
|
802
834
|
|
|
803
|
-
- `
|
|
835
|
+
- `img`: `src` / `:src` is always the **first** attribute, before everything else (including `ref` and `id`); the rest follow the order above
|
|
836
|
+
- `u-button`: `variant` → `color` → `size` → `icon` → label/value → `trailing-icon` → `block` → `disabled` → `loading-auto` → events
|
|
804
837
|
- `u-input` / `u-select*`: `:placeholder`, `:label` → `:loading`, `:disabled` → `:items` → `class` → `v-model` → events
|
|
805
838
|
- `un-table`: `:columns` → `class` / `:ui` → `:loading` → `:data` → `hide-pagination` → `:total-items` → `:items-per-page-items` → `:row-to` → `v-model:items-per-page` → `v-model:current-page` → `sticky-actions` → `:actions` → `:extra-actions` → `:meta` (page size model always before page model)
|
|
806
839
|
|
|
@@ -809,13 +842,55 @@ Shortcuts:
|
|
|
809
842
|
- Simple scalars and simple ternaries stay inline.
|
|
810
843
|
- When one condition changes several attributes, labels / icons, or an object binding such as `to`, use sibling `<template v-if>` / `v-else-if` / `v-else` branches with explicit component variants instead of nested ternaries.
|
|
811
844
|
|
|
845
|
+
### Event handlers
|
|
846
|
+
|
|
847
|
+
A function bound with `@event` is always called explicitly, with its arguments written out — never passed as a bare reference:
|
|
848
|
+
|
|
849
|
+
| The function receives | Write |
|
|
850
|
+
|-----------------------|-------|
|
|
851
|
+
| No argument | `@event="handleSave()"` |
|
|
852
|
+
| The event's one argument | `@event="handleSelect($event)"` |
|
|
853
|
+
| Several event arguments | `@event="(value, index) => handleMove(value, index)"` |
|
|
854
|
+
|
|
855
|
+
Name arrow parameters after what the event passes.
|
|
856
|
+
|
|
857
|
+
When converting a bare reference (`@click="handleSave"`), keep what Vue passed it: if the function declares parameters, pass the event's arguments explicitly (`$event` for one, an arrow for several); if it declares none, call it with `()`. If a component event's argument count is unclear, check its `emits` / docs; if still unclear, pass `$event`.
|
|
858
|
+
|
|
859
|
+
Inline statements that are not a single function call (`@update:page="currentPage = $event"`, `@update:open="!$event && emit('close')"`) stay as they are. This rule covers `@` bindings only — `onClick` in action objects and watcher callbacks still take references (see [Naming](#naming)).
|
|
860
|
+
|
|
861
|
+
```vue
|
|
862
|
+
<!-- ✅ -->
|
|
863
|
+
<u-button
|
|
864
|
+
variant="subtle"
|
|
865
|
+
icon="lucide:save"
|
|
866
|
+
@click="handleSave()"
|
|
867
|
+
/>
|
|
868
|
+
|
|
869
|
+
<u-select
|
|
870
|
+
:items="statusItems"
|
|
871
|
+
@update:model-value="handleStatusChange($event)"
|
|
872
|
+
/>
|
|
873
|
+
|
|
874
|
+
<draggable-list
|
|
875
|
+
:items="items"
|
|
876
|
+
@move="(item, index) => handleMove(item, index)"
|
|
877
|
+
/>
|
|
878
|
+
|
|
879
|
+
<!-- ❌ bare references -->
|
|
880
|
+
<u-button
|
|
881
|
+
variant="subtle"
|
|
882
|
+
icon="lucide:save"
|
|
883
|
+
@click="handleSave"
|
|
884
|
+
/>
|
|
885
|
+
```
|
|
886
|
+
|
|
812
887
|
### Default attribute values
|
|
813
888
|
|
|
814
889
|
Omit props that restate a default:
|
|
815
890
|
|
|
816
891
|
| Component / context | Convention |
|
|
817
892
|
|---------------------|------------|
|
|
818
|
-
| `un-table` `actions` / `extraActions` objects | omit `variant: 'subtle'` — `un-table` already sets it.
|
|
893
|
+
| `un-table` `actions` / `extraActions` objects | omit `variant: 'subtle'` — `un-table` already sets it. Do not add or remove `variant` on any other button or action object; which variant it uses is a design choice, not formatting |
|
|
819
894
|
| `u-badge` | omit `color` for neutral (`undefined` in ternaries, never `color="neutral"`); no `size` |
|
|
820
895
|
| `u-tooltip` | do not set `:delay-duration` |
|
|
821
896
|
|
|
@@ -869,6 +944,7 @@ Same whitespace, brace, literal, and call rules as script blocks, starting with
|
|
|
869
944
|
// Creates an authentication token
|
|
870
945
|
// after checking the login body.
|
|
871
946
|
|
|
947
|
+
|
|
872
948
|
export default defineEventHandler(async event => {
|
|
873
949
|
|
|
874
950
|
await assertRateLimit({
|
|
@@ -906,7 +982,7 @@ export default defineEventHandler(async event => {
|
|
|
906
982
|
|
|
907
983
|
## Checklist before finishing an edit
|
|
908
984
|
|
|
909
|
-
- [ ] Responsibility header: blank line, `/* responsibility */`, blank line, short `//` lines, blank
|
|
985
|
+
- [ ] Responsibility header: blank line, `/* responsibility */`, blank line, short `//` lines, two blank lines; in Vue inside `<script setup>`; nothing above it
|
|
910
986
|
- [ ] `<script setup>` without `lang="ts"`; no TS annotations in Vue
|
|
911
987
|
- [ ] 2-space indent; single quotes; semicolons; trailing commas in multi-line literals
|
|
912
988
|
- [ ] Every domain section starts with `/* section name */` + blank line; sections separated by two blank lines; imports co-located
|
|
@@ -914,7 +990,7 @@ export default defineEventHandler(async event => {
|
|
|
914
990
|
- [ ] Multiline assignments have exactly one blank line before and after (shared with required blanks; group boundaries keep two)
|
|
915
991
|
- [ ] Workflow functions: blank after `{`, double blanks between major steps, blank before `}`; tiny helpers tight; single-block functions flush; return-only decisions as one `if` / `else if` / `else` chain
|
|
916
992
|
- [ ] Braces everywhere; `else` / `catch` on their own line
|
|
917
|
-
- [ ] Script literals multi-line, except `{}` / `[]`; no statement wrapped only for length; `fn(arg, {` on one line
|
|
993
|
+
- [ ] Script literals multi-line, except `{}` / `[]`; inline object types of function parameters on one line; no statement wrapped only for length; `fn(arg, {` on one line
|
|
918
994
|
- [ ] Page scripts: section order, `/* params */` shape, `/* seo */` placement and call order
|
|
919
995
|
- [ ] `useUFetch` shape; `ufetch` options order; destructure names `xxxData` / `isXxxPending` / `refreshXxx`; `response` for `ufetch` results
|
|
920
996
|
- [ ] `handleXxx` handlers; `it` for short callbacks; descriptive loop names; structure-returning computeds use block + `return`; handler references, not wrappers
|
|
@@ -923,6 +999,7 @@ export default defineEventHandler(async event => {
|
|
|
923
999
|
- [ ] Child spacing: one child flush; 2+ children with exactly one blank after the opener, between children, and before the closer (each branch counts)
|
|
924
1000
|
- [ ] Attribute wrapping: tags with children single-line unless a multiline attribute forces a split; childless tags self-closing, split when more than one attribute; `>` / `/>` placement
|
|
925
1001
|
- [ ] Attribute order; redundant defaults omitted
|
|
1002
|
+
- [ ] `@event` bindings call their function explicitly: `fn()`, `fn($event)`, or `(a, b) => fn(a, b)`; no bare references
|
|
926
1003
|
- [ ] Text / `{{ }}` on its own line
|
|
927
1004
|
- [ ] `atoms` / `libs` imports relative; file not moved
|
|
928
1005
|
- [ ] No behavior change
|
|
@@ -22,7 +22,6 @@ await launchChoicePickerDialog({
|
|
|
22
22
|
text: 'Are you sure you want to submit your application?',
|
|
23
23
|
startButtons: [
|
|
24
24
|
{
|
|
25
|
-
variant: 'subtle',
|
|
26
25
|
icon: 'lucide:check',
|
|
27
26
|
label: 'Submit',
|
|
28
27
|
onClick: async () => {
|
|
@@ -63,7 +62,6 @@ await launchFormPickerDialog({
|
|
|
63
62
|
firstName: 'John',
|
|
64
63
|
},
|
|
65
64
|
submitButton: {
|
|
66
|
-
variant: 'subtle',
|
|
67
65
|
icon: 'lucide:send',
|
|
68
66
|
label: 'Submit',
|
|
69
67
|
onClick: async form => {
|
|
@@ -12,6 +12,7 @@ Schema-driven forms via `useForm` / `un-form`. Field type is selected with **`id
|
|
|
12
12
|
// Collects the applicant's
|
|
13
13
|
// personal details.
|
|
14
14
|
|
|
15
|
+
|
|
15
16
|
/* form */
|
|
16
17
|
|
|
17
18
|
const { form, formTag } = useForm({
|
|
@@ -178,6 +179,7 @@ A custom element is a layer-private component in `app/atoms/`. It receives the `
|
|
|
178
179
|
// Renders a text input
|
|
179
180
|
// for a custom form field.
|
|
180
181
|
|
|
182
|
+
|
|
181
183
|
/* interface */
|
|
182
184
|
|
|
183
185
|
const props = defineProps({
|
|
@@ -206,6 +208,7 @@ Register it once from a Nuxt plugin. The identifier must be unique among built-i
|
|
|
206
208
|
// Registers the custom text
|
|
207
209
|
// form element.
|
|
208
210
|
|
|
211
|
+
|
|
209
212
|
export default defineNuxtPlugin(() => {
|
|
210
213
|
registerFormExtraElement({
|
|
211
214
|
identifier: 'custom-text',
|
|
@@ -50,6 +50,7 @@ Minimal example:
|
|
|
50
50
|
// Registers the users resource
|
|
51
51
|
// on the unified app registry.
|
|
52
52
|
|
|
53
|
+
|
|
53
54
|
const { schema, type, inferred } = parseSchema({
|
|
54
55
|
'name': 'string',
|
|
55
56
|
'username': 'string',
|
|
@@ -161,6 +162,7 @@ Every file is a thin wrapper:
|
|
|
161
162
|
// Lists flash cards
|
|
162
163
|
// for admins.
|
|
163
164
|
|
|
165
|
+
|
|
164
166
|
export default defineEventHandler(async event => {
|
|
165
167
|
return handleResourceList({
|
|
166
168
|
resource: 'flashCards',
|
|
@@ -192,6 +194,7 @@ Generic page (one page for all standard resources), `pages/dashboard/resources/[
|
|
|
192
194
|
// Manages any standard resource
|
|
193
195
|
// named by the route.
|
|
194
196
|
|
|
197
|
+
|
|
195
198
|
/* page */
|
|
196
199
|
|
|
197
200
|
definePageMeta({
|
|
@@ -263,6 +266,7 @@ Example: `pages/resources/users.vue` (named route, not necessarily under `dashbo
|
|
|
263
266
|
// Manages users with onboarding
|
|
264
267
|
// and password reset actions.
|
|
265
268
|
|
|
269
|
+
|
|
266
270
|
/* page */
|
|
267
271
|
|
|
268
272
|
definePageMeta({
|
|
@@ -307,7 +311,6 @@ async function handleOnboardUser() {
|
|
|
307
311
|
},
|
|
308
312
|
],
|
|
309
313
|
submitButton: {
|
|
310
|
-
variant: 'subtle',
|
|
311
314
|
label: 'Onboard',
|
|
312
315
|
onClick: async form => {
|
|
313
316
|
|
|
@@ -335,7 +338,6 @@ async function handleResetPassword(user) {
|
|
|
335
338
|
text: `Send a password reset to ${user.name}?`,
|
|
336
339
|
startButtons: [
|
|
337
340
|
{
|
|
338
|
-
variant: 'subtle',
|
|
339
341
|
icon: 'lucide:check',
|
|
340
342
|
label: 'Reset',
|
|
341
343
|
onClick: async () => {
|
|
@@ -7,9 +7,10 @@ Toast helpers, the `un-*` layout components, and the button / badge / icon conve
|
|
|
7
7
|
This is the only place these rules are stated.
|
|
8
8
|
|
|
9
9
|
- **Icons** are always Lucide: `lucide:*`.
|
|
10
|
-
- **
|
|
11
|
-
-
|
|
12
|
-
-
|
|
10
|
+
- **Bottom-of-card action buttons use the default variant — set no `variant`**: `un-card` `actions` (also with `verticalActions`), form picker `submitButton`, and choice picker `startButtons` / `endButtons`.
|
|
11
|
+
- **All other buttons set `variant: 'subtle'` (`variant="subtle"`) explicitly**: top-of-card buttons — `un-card` `appendActions` / `subtitleActions`, `<resource-manager>` `actions` (rendered as its card's `appendActions`) — and standalone `u-button`s. Nuxt UI's default variant is `solid`, so leaving `variant` out is not the same.
|
|
12
|
+
- **`un-table` row actions** (`actions` / `extraActions`, and `<resource-manager>` `resource-actions`, which feed them) omit `variant`, because `un-table` already renders them subtle.
|
|
13
|
+
- **Cancel / dismiss** actions use `variant: 'ghost'`, including in a bottom action row. Never use `ghost` on primary, submit, row, or any other non-Cancel action.
|
|
13
14
|
- **Async buttons** use `loading-auto` instead of a hand-rolled `isLoading` flag, unless something else depends on that flag.
|
|
14
15
|
- **Badges** (`u-badge`) are always `variant="subtle"` with `icon` + `:label` (no default-slot text), no `size`, and no `color` for neutral states.
|
|
15
16
|
|