nuxt-unified-ui 0.4.28 → 0.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
@@ -17,3 +17,12 @@ Start the development server on http://localhost:8080
17
17
  ```bash
18
18
  vpr serve
19
19
  ```
20
+
21
+ ## Agent Skills
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).
24
+
25
+ ```bash
26
+ npx skills add . --list
27
+ npx skills add <owner>/<repo>
28
+ ```
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-unified-ui",
3
3
  "type": "module",
4
- "version": "0.4.28",
4
+ "version": "0.5.0",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./index.d.ts",
7
7
  "exports": {
@@ -15,11 +15,12 @@
15
15
  "index.d.ts",
16
16
  "app",
17
17
  "i18n",
18
- "modules"
18
+ "modules",
19
+ "skills"
19
20
  ],
20
21
  "dependencies": {
21
22
  "@formkit/tempo": "1.1.0",
22
- "@iconify-json/lucide": "1.2.121",
23
+ "@iconify-json/lucide": "1.2.122",
23
24
  "@nuxt/kit": "4.5.2",
24
25
  "@nuxt/ui": "4.10.0",
25
26
  "@nuxtjs/i18n": "10.6.0",
@@ -0,0 +1,153 @@
1
+ ---
2
+ name: nuxt-unified-ui
3
+ description: >-
4
+ Single skill for the nuxt-unified-ui Nuxt layer and mandatory Nuxt code style:
5
+ install/extend the layer, required CSS, modules/config, radashi radXxx
6
+ auto-imports, un-form / useForm, launchFormPickerDialog /
7
+ launchChoicePickerDialog, toast helpers, un-card / un-typography, and the
8
+ whitespace/formatting/code-shape conventions for all Nuxt-generated code. Use
9
+ when working in or consuming nuxt-unified-ui, or whenever generating Vue/Nuxt
10
+ code that must match unified code style.
11
+ ---
12
+
13
+ # nuxt-unified-ui
14
+
15
+ Reusable **Nuxt layer** (Nuxt UI + helpers) **plus** the mandatory **code style** for Nuxt projects using this stack.
16
+
17
+ Peer dependency: **Nuxt `>=4.5.2`**.
18
+
19
+ This is the **only** installable skill in this repo. Deep topics live under `references/`.
20
+
21
+ ## When to use
22
+
23
+ - Installing / extending `nuxt-unified-ui` as a Nuxt layer
24
+ - Using `un-form`, dialogs, toasts, `un-card`, radashi `radXxx`, etc.
25
+ - **Whenever generating or editing Nuxt/Vue/server code** that must follow the unified look (whitespace, wrapping, template shape, sectioning)
26
+
27
+ ## References (read as needed)
28
+
29
+ | Topic | File |
30
+ |-------|------|
31
+ | **Code style (mandatory)** | [references/code-style.md](references/code-style.md) |
32
+ | Layer install + required CSS | [references/layer-setup.md](references/layer-setup.md) |
33
+ | Public surface inventory | [references/public-surface.md](references/public-surface.md) |
34
+ | Forms (`useForm` / `un-form`) | [references/forms.md](references/forms.md) |
35
+ | Form field schema | [references/form-field-schema.md](references/form-field-schema.md) |
36
+ | Form elements | [references/form-elements.md](references/form-elements.md) |
37
+ | Dialogs / toasts / UI | [references/dialogs.md](references/dialogs.md) |
38
+ | Dialog implementation | [references/dialogs-impl.md](references/dialogs-impl.md) |
39
+ | Toast + `un-*` details | [references/toast-and-ui.md](references/toast-and-ui.md) |
40
+ | Radashi `radXxx` catalog | [references/radashi.md](references/radashi.md) |
41
+
42
+ ---
43
+
44
+ ## Code style (read [code-style.md](references/code-style.md) before writing code)
45
+
46
+ **Always apply** to Vue SFCs and app/server `.ts` files. Higher-level idea: code should **scan vertically** — double blanks between major sections, multi-line literals, predictable template wrapping, section comments as a map.
47
+
48
+ Absolute highlights:
49
+
50
+ - `<script setup>` only — **never** `lang="ts"`; no TS annotations in Vue (runtime prop types)
51
+ - 2-space indent; single quotes; semicolons; trailing commas in multi-line literals
52
+ - Double blank lines between major sections; blank line before `</script>`; **two** blanks before `<template>`
53
+ - Non-trivial async/functions: blank line after `{`, double blank between major steps, blank before `}`
54
+ - `else` / `catch` on their own line after `}`
55
+ - Script object literals always multi-line (even one property)
56
+ - Kebab-case tags (`u-button`, `un-card`)
57
+ - `v-if` / `v-for` on `<template>` wrappers — not on rendered nodes
58
+ - **2+ attributes → one per line**; non-self-closing `>` on same line as last attr; multi-line self-closing `/>` on its own line
59
+ - `{{ ... }}` on its own line
60
+ - `/* section */` comments; imports co-located under the section that uses them
61
+ - Light naming: `handleXxx` handlers, `it` in short callbacks, descriptive `for...of`, computeds use block + `return`
62
+
63
+ ---
64
+
65
+ ## Quick start (host app)
66
+
67
+ 1. Install the package.
68
+ 2. Create host `assets/css/main.css`:
69
+
70
+ ```css
71
+ @import 'tailwindcss';
72
+ @import '@nuxt/ui';
73
+ @import 'nuxt-unified-ui/nuxt-ui-fixes.css';
74
+ ```
75
+
76
+ 3. Extend the layer (CSS wiring is **required**):
77
+
78
+ ```js
79
+ import { pathRelativeToBase } from 'nuxt-unified-ui';
80
+
81
+ export default defineNuxtConfig({
82
+ css: [
83
+ pathRelativeToBase(import.meta.url, './assets/css/main.css'),
84
+ ],
85
+ extends: [
86
+ 'nuxt-unified-ui',
87
+ ],
88
+ });
89
+ ```
90
+
91
+ 4. Wrap the app with `u-app`.
92
+ 5. Prefer layer helpers (`useForm`, `launchFormPickerDialog`, `toastSuccess`) over reinventing them.
93
+ 6. Generate all new code using [code-style.md](references/code-style.md).
94
+
95
+ Details: [layer-setup.md](references/layer-setup.md).
96
+
97
+ ## Package surface
98
+
99
+ | Export | Path |
100
+ |--------|------|
101
+ | `nuxt-unified-ui` | `./nuxt.config.ts` (also re-exports `pathRelativeToBase`) |
102
+ | `nuxt-unified-ui/app` | `./app` |
103
+ | `nuxt-unified-ui/nuxt-ui-fixes.css` | `./app/assets/css/nuxt-ui-fixes.css` |
104
+
105
+ Published: `nuxt.config.ts`, `index.d.ts`, `app/`, `i18n/`, `modules/`.
106
+
107
+ ## Mental model (`app/`)
108
+
109
+ | Path | Role |
110
+ |------|------|
111
+ | `app/components/` | `un-form`, `un-card`, `un-typography`, `un-spinner` |
112
+ | `app/composables/` | `useForm`, `useFormExtraElements` |
113
+ | `app/elements/` | Built-in form field renderers |
114
+ | `app/dialogs/` | Form / choice picker modal UIs |
115
+ | `app/utils/` | `launchDialog*`, `toast*`, `smartMatch`, `unSet`, dates, … |
116
+ | `app/plugins/` | `$toaster` via `useToast()` |
117
+ | `modules/radashi.ts` | Auto-imports radashi as `rad*` |
118
+ | `i18n/locales/` | `en.json`, `de.json` |
119
+
120
+ ## Layer config (inherited)
121
+
122
+ From `nuxt.config.ts`: `@vueuse/nuxt`, `@nuxt/ui`, `@nuxtjs/i18n`; `ui.colorMode: false`; default variant `neutral`; i18n `no_prefix` with `en`/`de`; `experimental.typedPages: true`.
123
+
124
+ ## Common tasks
125
+
126
+ | Task | Prefer |
127
+ |------|--------|
128
+ | Schema form | `useForm` + `<form-tag />` / `<un-form>` → [forms.md](references/forms.md) |
129
+ | Modal form | `launchFormPickerDialog` + `submitButton.onClick` → [dialogs.md](references/dialogs.md) |
130
+ | Confirm / choice | `launchChoicePickerDialog` + button `onClick` (avoid `value`) |
131
+ | Feedback | `toastSuccess` / `toastError` / `toast` |
132
+ | Page chrome | `un-typography` + `un-card` |
133
+ | Custom field | `registerFormExtraElement` in a plugin |
134
+ | Utilities | `radXxx` → [radashi.md](references/radashi.md) |
135
+ | Formatting any of the above | [code-style.md](references/code-style.md) |
136
+
137
+ ## Do / don’t
138
+
139
+ **Do**
140
+
141
+ - Extend via `extends: ['nuxt-unified-ui']`
142
+ - Keep required host `main.css` + `pathRelativeToBase` CSS entry + `nuxt-ui-fixes.css`
143
+ - Use field `identifier` for element kind; `type` only for HTML input types
144
+ - Handle dialog actions in `onClick`
145
+ - Follow code style for every generated file
146
+
147
+ **Don’t**
148
+
149
+ - Invent APIs not in source
150
+ - Reference any local playground as consumer docs
151
+ - Use PascalCase component tags in templates
152
+ - Set choice-button `value` unless the await result must distinguish buttons
153
+ - Assume color mode is enabled (layer disables it)
@@ -0,0 +1,520 @@
1
+ # Nuxt unified code style
2
+
3
+ **Mandatory** whenever generating or editing code in a Nuxt project that uses this stack. Applies to **all** Nuxt project files: Vue SFCs and `.ts`/`.js` under `app/`, `server/`, composables, utils, plugins, middleware, etc.
4
+
5
+ This document is about the **look and shape** of code — whitespace, wrapping, braces, template structure, sectioning, and light naming that affects scanning — not business logic or architecture.
6
+
7
+ ---
8
+
9
+ ## Higher-level reasoning
10
+
11
+ Write code so a reader can **scan vertically** and see structure before details.
12
+
13
+ 1. **Paragraphs of logic, not walls of text.**
14
+ Double blank lines mark major boundaries (sections, handlers, big steps). Single blank lines separate related statements inside a section. Tiny one-liner helpers stay tight — do not decorate them with empty lines.
15
+
16
+ 2. **Breathing room inside non-trivial blocks.**
17
+ A non-trivial function/computed is a mini-document: blank line after `{`, full-block guards, double blank between guard/setup and the main sequence, blank line before `}`. That rhythm makes async flows readable without comments.
18
+
19
+ 3. **One idea per line in structured data.**
20
+ Object/array literals in script are multi-line with trailing commas — even single-property objects passed to helpers (`toastSuccess`, `ufetch` options, etc.). Compact one-liners hide diffs and force horizontal reading.
21
+
22
+ 4. **Templates are layout, not mini-scripts.**
23
+ Structural directives live on `<template>` wrappers so the rendered node stays a clean component/element. Attributes wrap predictably; closing `>` / `/>` placement is consistent; interpolations sit on their own line so markup nesting is obvious.
24
+
25
+ 5. **Section comments are the map.**
26
+ `/* section */` labels replace scavenger hunts through long `<script setup>` blocks. Imports sit next to the section that needs them, not in a hoisted pile at the top.
27
+
28
+ 6. **Names that match role.**
29
+ Handlers read as actions (`handleLogin`), short callbacks use `it`, loops use real nouns. Shape and naming reinforce each other so you rarely need narrating comments.
30
+
31
+ 7. **Vue scripts stay runtime-shaped.**
32
+ `<script setup>` without `lang="ts"` and without type annotations keeps SFC style uniform and matches the dominant Nuxt UI / unified-ui codebase. Server/util `.ts` files may use TypeScript where the file already does; still follow the same whitespace and literal formatting.
33
+
34
+ When editing an existing file, **absolute rules below always win**. For choices not covered here (rare quote/semicolon drift), match the nearest sibling file.
35
+
36
+ ---
37
+
38
+ ## Absolute baseline
39
+
40
+ | Rule | Value |
41
+ |------|--------|
42
+ | Indentation | 2 spaces |
43
+ | New code quotes | single quotes `'` |
44
+ | Semicolons | use them |
45
+ | Trailing commas | always in multi-line literals |
46
+ | Vue script tag | `<script setup>` only — **never** `lang="ts"` |
47
+ | Vue script types | **no** TypeScript annotations; use runtime prop types (`String`, `Object`, `Array`, `Boolean`, `Number`, `Function`) |
48
+ | Component tags | lowercase kebab-case (`u-button`, `un-card`) — never PascalCase |
49
+ | Braces | always for `if` / `else` / `for` / `while` — no brace-less single-liners |
50
+ | `else` / `catch` | on their **own line** after `}` |
51
+
52
+ ---
53
+
54
+ ## Vertical whitespace (script / TS)
55
+
56
+ ### Section rhythm
57
+
58
+ - **Double blank line** between major boundaries: after `defineProps`/`defineEmits` blocks, between `/* section */` domains, before handler functions, between major async steps.
59
+ - **Single blank line** within a section (related consts, consecutive small helpers).
60
+ - Blank line **before and after** each `/* section */` comment.
61
+ - Blank line before `</script>`.
62
+ - **Two** blank lines between `</script>` and `<template>`.
63
+
64
+ ### Non-trivial functions and computeds
65
+
66
+ Applies to async handlers, multi-step loaders, and non-trivial callbacks:
67
+
68
+ ```ts
69
+ async function handleLogin() {
70
+
71
+ if (!loginForm.value.username) {
72
+ return;
73
+ }
74
+
75
+
76
+ const response = await ufetch('/api/authentication/login', {
77
+ method: 'post',
78
+ body: {
79
+ username: loginForm.value.username,
80
+ password: loginForm.value.password,
81
+ },
82
+ });
83
+
84
+
85
+ useToken().value = response.token;
86
+
87
+ await navigateTo({
88
+ name: 'authentication.account',
89
+ });
90
+
91
+ }
92
+ ```
93
+
94
+ - Blank line after opening `{`
95
+ - Guards as full blocks
96
+ - Double blank between guard/setup and main work / between major steps
97
+ - Blank line before closing `}`
98
+
99
+ ### Tiny blocks stay tight
100
+
101
+ Do **not** add decorative blanks when the body is a single delegation or a one-line reset:
102
+
103
+ ```ts
104
+ async function refreshResources() {
105
+ await resourceExplorerTableEl.value?.refreshResources();
106
+ }
107
+
108
+ async function handleSubmitSelection(items) {
109
+ await props.onSelected?.(items);
110
+ emit('close', items);
111
+ }
112
+ ```
113
+
114
+ Same for a `finally` that only flips one flag.
115
+
116
+ ### `else` / `catch`
117
+
118
+ ```ts
119
+ if (condition) {
120
+ ...
121
+ }
122
+ else {
123
+ ...
124
+ }
125
+
126
+ try {
127
+ await doSomething();
128
+ }
129
+ catch {
130
+ toastError({
131
+ title: 'Failed',
132
+ });
133
+ }
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Object / array / call formatting
139
+
140
+ ### Script literals — always multi-line
141
+
142
+ In script and `.ts` files, object literals use one property per line and a trailing comma — **including single-property objects** in call args:
143
+
144
+ ```ts
145
+ toastSuccess({
146
+ title: 'Saved',
147
+ });
148
+
149
+ await navigateTo({
150
+ name: 'authentication.login',
151
+ });
152
+
153
+ await ufetch('/api/authentication/login', {
154
+ method: 'post',
155
+ body: {
156
+ username: loginForm.value.username,
157
+ password: loginForm.value.password,
158
+ },
159
+ });
160
+ ```
161
+
162
+ ### Call wrapping
163
+
164
+ Prefer `fn(arg, {` on one line; put options on following lines. Do not break the call so the URL/first arg sits alone on a line above `{` unless the expression is structurally huge.
165
+
166
+ ```ts
167
+ // ✅
168
+ const response = await ufetch(`/api/${resourcePath.value}`, {
169
+ method: 'post',
170
+ body: form,
171
+ });
172
+
173
+ // ❌
174
+ const response = await ufetch(
175
+ `/api/${resourcePath.value}`,
176
+ {
177
+ method: 'post',
178
+ body: form,
179
+ },
180
+ );
181
+ ```
182
+
183
+ ### Template bindings — compactness
184
+
185
+ - Single-key object binding may stay inline: `:ui="{ content: 'max-w-7xl' }"`
186
+ - Multi-key template object/array bindings are multi-line
187
+ - Simple scalars and simple ternaries stay inline; break only when branches become objects/arrays or nested structure
188
+
189
+ ---
190
+
191
+ ## Vue SFC shape
192
+
193
+ ### Props / emits / models
194
+
195
+ ```ts
196
+ const props = defineProps({
197
+ resource: String,
198
+ items: Array,
199
+ multiple: Boolean,
200
+ });
201
+
202
+ const emit = defineEmits([
203
+ 'close',
204
+ ]);
205
+
206
+ const captchaId = defineModel('id', {
207
+ type: String,
208
+ });
209
+ ```
210
+
211
+ - Always assign `defineProps` / `defineEmits` / `defineModel` to a variable
212
+ - `defineProps`: shorthand `name: Type` only — never `{ type: Type, required: true }`
213
+ - `defineEmits`: array of strings, **always multi-line** (even one event)
214
+ - `defineModel` uses `{ type, default? }` (required by Vue) — that object still follows multi-line literal rules
215
+
216
+ ### Section comments
217
+
218
+ Group with `/* name */`:
219
+
220
+ | Common section | Contents |
221
+ |----------------|----------|
222
+ | `/* interface */` | props, emits, models |
223
+ | `/* page */` | `definePageMeta`, `useHead` |
224
+ | domain names | `/* login */`, `/* resource */`, `/* captcha */`, … |
225
+ | `/* outlets */` | `defineExpose` |
226
+
227
+ Blank line before and after the comment. Avoid comments that only restate obvious option names.
228
+
229
+ ### Script ordering
230
+
231
+ **Components / dialogs**
232
+
233
+ 1. `/* interface */`
234
+ 2. State refs
235
+ 3. Computeds
236
+ 4. Watchers / lifecycle
237
+ 5. Handlers / async functions
238
+ 6. `/* outlets */` / `defineExpose` if needed
239
+
240
+ **Pages**
241
+
242
+ 1. `/* page */`
243
+ 2. Route/params
244
+ 3. Data / forms / domain sections
245
+ 4. Watchers / lifecycle
246
+ 5. Handlers
247
+
248
+ ### Import co-location
249
+
250
+ Place non-auto-imported imports **inside the section that uses them**, not hoisted at the top of the file:
251
+
252
+ ```ts
253
+ /* charts */
254
+
255
+ import { VisXYContainer, VisLine } from '@unovis/vue';
256
+ ```
257
+
258
+ ### Watcher formatting
259
+
260
+ - Prefer `watchImmediate` over `watch(..., { immediate: true })`
261
+ - Pass function **references** directly — no `() => { fn(); }` wrappers
262
+ - One argument per line when registering:
263
+
264
+ ```ts
265
+ watchImmediate(
266
+ () => props.document?.uid,
267
+ loadPreview,
268
+ );
269
+ ```
270
+
271
+ Put guards **inside** the handler function so the reference stays clean.
272
+
273
+ ---
274
+
275
+ ## Template rules
276
+
277
+ ### Structural directives on `<template>` (critical)
278
+
279
+ Always put `v-if` / `v-else-if` / `v-else` / `v-for` on `<template>` wrappers — never on the rendered element:
280
+
281
+ ```vue
282
+ <template v-if="captcha">
283
+ <img
284
+ :src="`data:image/png;base64,${captcha.image}`"
285
+ alt="Captcha"
286
+ class="h-14 rounded-md border border-default"
287
+ />
288
+ </template>
289
+
290
+ <template v-for="item in items" :key="item.id">
291
+ <u-badge
292
+ variant="subtle"
293
+ :label="item.name"
294
+ />
295
+ </template>
296
+ ```
297
+
298
+ Keep tight `v-if` / `v-else` chains adjacent (no blank line between matching branches inside small slots). Blank lines are OK between large top-level page/card state branches.
299
+
300
+ ### Attribute wrapping (hard rule)
301
+
302
+ - **0–1 attributes:** may stay on one line with the tag
303
+ - **2+ attributes:** one attribute per line (always)
304
+
305
+ ```vue
306
+ <!-- ✅ 0–1 attributes — inline OK -->
307
+ <div class="space-y-3">
308
+ <u-form-field label="Captcha">
309
+ ...
310
+ </u-form-field>
311
+ <u-icon name="lucide:check" />
312
+
313
+ <!-- ✅ 2+ attributes — one per line -->
314
+ <u-modal
315
+ :ui="{ content: 'max-w-5xl' }"
316
+ scrollable
317
+ @update:open="!$event && emit('close')">
318
+ ...
319
+ </u-modal>
320
+
321
+ <u-button
322
+ variant="subtle"
323
+ icon="lucide:refresh-ccw"
324
+ @click="refresh"
325
+ />
326
+ ```
327
+
328
+ ### Attribute order
329
+
330
+ When wrapping, order attributes as:
331
+
332
+ 1. Refs / identity: `ref`, `id`, `name`
333
+ 2. Visual props: `variant`, `color`, `size`, `icon`, static `label`
334
+ 3. Static presentation: `class`, `style`
335
+ 4. Data bindings: `:items`, `:data`, `:placeholder`, `:value`, dynamic `:label`, …
336
+ 5. `v-model` / `:model-value` / `v-model:*`
337
+ 6. Navigation / state: `to`, `href`, `block`, `disabled`, `loading`, `loading-auto`, `fluid-body`, …
338
+ 7. Events last: `@click`, `@update:*`, …
339
+
340
+ Practical shortcuts:
341
+
342
+ - `u-button`: `variant` → `color` → `size` → `icon` → label/value → `block` → `disabled` → `loading-auto` → events
343
+ - `u-input` / `u-select*`: user-facing props → `:loading`/`:disabled` → `:items` → `class` → `v-model` → events
344
+
345
+ ### `>` and `/>` placement
346
+
347
+ **Non-self-closing**, multi-attribute: `>` on the **same line** as the last attribute:
348
+
349
+ ```vue
350
+ <un-card
351
+ icon="lucide:key"
352
+ :title="title"
353
+ fluid-body>
354
+ ...
355
+ </un-card>
356
+ ```
357
+
358
+ **Self-closing**, multi-attribute: `/>` on its **own line**; always a space before `/>`:
359
+
360
+ ```vue
361
+ <u-button
362
+ variant="subtle"
363
+ icon="lucide:refresh-ccw"
364
+ @click="refresh"
365
+ />
366
+
367
+ <u-icon name="lucide:check" />
368
+ ```
369
+
370
+ Closing tags for block components (`</un-card>`, `</u-modal>`, …) always on their own line.
371
+
372
+ ### Text interpolation
373
+
374
+ Put `{{ ... }}` on its own line:
375
+
376
+ ```vue
377
+ <h1 class="text-2xl font-semibold">
378
+ Login
379
+ </h1>
380
+ ```
381
+
382
+ ### Root structure
383
+
384
+ - One root node when possible
385
+ - If multiple sibling sections are needed, wrap in a single root (`div` etc.)
386
+ - Sibling cards/sections often use `class="space-y-3"` on the root wrapper
387
+ - Prefer a blank line after the root opener and before the root closer when the body is multi-block
388
+
389
+ ```vue
390
+ <template>
391
+ <div class="space-y-3">
392
+
393
+ <un-card ...>
394
+ ...
395
+ </un-card>
396
+
397
+ <un-card ...>
398
+ ...
399
+ </un-card>
400
+
401
+ </div>
402
+ </template>
403
+ ```
404
+
405
+ ### Refs in templates
406
+
407
+ Refs unwrap automatically — do not write `.value` in template expressions or in object literals bound from the template.
408
+
409
+ ---
410
+
411
+ ## Light naming (reading shape)
412
+
413
+ | Context | Convention |
414
+ |---------|------------|
415
+ | Async / UI action handlers | `handleXxx` (`handleLogin`, `handleResourceDelete`) |
416
+ | Short sync helpers | no `handle` prefix (`refresh`, `formatDate`) |
417
+ | Short `.map` / `.filter` / `.find` (≤ ~3 lines) | parameter name `it`; omit parens: `it =>` |
418
+ | `for...of` / `v-for` | descriptive names — not `u`, `fo`, `doc` abbreviations |
419
+ | `computed` returning array/object | block body + explicit `return` — not concise `() => [...]` |
420
+
421
+ ```ts
422
+ const actions = computed(() => {
423
+ return [
424
+ {
425
+ variant: 'subtle',
426
+ icon: 'lucide:plus',
427
+ label: `Create a ${title.value}`,
428
+ onClick: handleResourceCreate,
429
+ },
430
+ ];
431
+ });
432
+
433
+ items.value.find(it => it.id === selectedId.value);
434
+
435
+ for (const resource of resources) {
436
+ ...
437
+ }
438
+ ```
439
+
440
+ Pass handler **references** into action objects / watchers when possible (`onClick: handleResourceCreate`), instead of unnecessary `() => handleResourceCreate()` wrappers — unless arguments must be adapted.
441
+
442
+ ---
443
+
444
+ ## Server / plain TS files
445
+
446
+ Same whitespace, brace, literal, and call-formatting rules as script blocks:
447
+
448
+ ```ts
449
+ export default defineEventHandler(async event => {
450
+
451
+ await assertRateLimit({
452
+ event,
453
+ limit: 5,
454
+ });
455
+
456
+
457
+ const body = await assertBody({
458
+ event,
459
+ schema: {
460
+ 'username': 'string',
461
+ 'password': 'string',
462
+ },
463
+ });
464
+
465
+
466
+ if (!user) {
467
+ throw createUnauthenticatedError();
468
+ }
469
+
470
+
471
+ return resources.authenticationTokens.dbo.create({
472
+ document: {
473
+ user: user._id,
474
+ token: generateUuid(),
475
+ isActive: true,
476
+ },
477
+ });
478
+
479
+ });
480
+ ```
481
+
482
+ Leading blank line at top of file is fine when the local tree uses it. Prefer `async event =>` style consistent with siblings.
483
+
484
+ ---
485
+
486
+ ## Anti-patterns (quick)
487
+
488
+ | Don’t | Do |
489
+ |-------|-----|
490
+ | `<script setup lang="ts">` | `<script setup>` |
491
+ | `UButton` / `UnCard` | `u-button` / `un-card` |
492
+ | `<div v-if="x">` | `<template v-if="x"><div>` |
493
+ | `if (!x) return;` | braced block |
494
+ | `} else {` | `}\nelse {` |
495
+ | `toastSuccess({ title: 'x' })` one-liner object | multi-line object + trailing comma |
496
+ | 3 attrs on one line | one attr per line |
497
+ | `>` on its own line after attrs | `>` after last attr |
498
+ | `{{ x }}` glued to tags | interpolation on its own line |
499
+ | Hoisted import block | imports co-located under section |
500
+ | `computed(() => [ ... ])` | `computed(() => { return [ ... ]; })` |
501
+
502
+ ---
503
+
504
+ ## Checklist before finishing an edit
505
+
506
+ - [ ] `<script setup>` without `lang="ts"`; no TS annotations in Vue
507
+ - [ ] 2-space indent; single quotes; semicolons; trailing commas in multi-line literals
508
+ - [ ] Double blanks between major sections; blank line before `</script>`; two blanks before `<template>`
509
+ - [ ] Non-trivial functions: blank after `{`, double blank between major steps, blank before `}`
510
+ - [ ] Tiny helpers stay tight
511
+ - [ ] `else` / `catch` on new line
512
+ - [ ] Script objects multi-line; template single-key objects may be inline
513
+ - [ ] Kebab-case component tags
514
+ - [ ] `v-if` / `v-for` on `<template>` wrappers
515
+ - [ ] 2+ attributes → one per line; `>` same line as last attr; multi-line self-closing `/>` on own line
516
+ - [ ] Attribute order respected
517
+ - [ ] `{{ }}` on own line
518
+ - [ ] Section comments + import co-location where the file has sections
519
+ - [ ] `handleXxx` for action handlers; `it` for short callbacks; descriptive loop names
520
+ - [ ] Computeds that return structures use block + `return`