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 +9 -0
- package/package.json +4 -3
- package/skills/nuxt-unified-ui/SKILL.md +153 -0
- package/skills/nuxt-unified-ui/references/code-style.md +520 -0
- package/skills/nuxt-unified-ui/references/dialogs-impl.md +57 -0
- package/skills/nuxt-unified-ui/references/dialogs.md +168 -0
- package/skills/nuxt-unified-ui/references/form-elements.md +59 -0
- package/skills/nuxt-unified-ui/references/form-field-schema.md +50 -0
- package/skills/nuxt-unified-ui/references/forms.md +194 -0
- package/skills/nuxt-unified-ui/references/layer-setup.md +88 -0
- package/skills/nuxt-unified-ui/references/public-surface.md +73 -0
- package/skills/nuxt-unified-ui/references/radashi.md +190 -0
- package/skills/nuxt-unified-ui/references/toast-and-ui.md +61 -0
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
|
+
"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.
|
|
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`
|