nuxt-unified-ui 0.5.14 → 0.5.16

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-unified-ui",
3
3
  "type": "module",
4
- "version": "0.5.14",
4
+ "version": "0.5.16",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./index.d.ts",
7
7
  "exports": {
@@ -20,15 +20,15 @@
20
20
  ],
21
21
  "dependencies": {
22
22
  "@formkit/tempo": "1.1.0",
23
- "@iconify-json/lucide": "1.2.135",
23
+ "@iconify-json/lucide": "1.2.136",
24
24
  "@nuxt/kit": "4.5.2",
25
- "@nuxt/ui": "4.11.1",
25
+ "@nuxt/ui": "4.11.2",
26
26
  "@nuxtjs/i18n": "10.6.0",
27
27
  "@types/canvas-confetti": "1.9.0",
28
28
  "@vueuse/core": "15.0.0",
29
29
  "@vueuse/nuxt": "15.0.0",
30
30
  "canvas-confetti": "1.9.4",
31
- "radashi": "12.9.5",
31
+ "radashi": "12.9.6",
32
32
  "unified-mongo-filter": "0.4.0"
33
33
  },
34
34
  "peerDependencies": {
@@ -8,10 +8,11 @@ description: >-
8
8
  (attribute order, row actions, pagination), pages /
9
9
  definePageMeta, page /* params */ and /* seo */ blocks, ufetch / useUFetch wrapping, unified resources
10
10
  (server plugins, REST handleResource*, resource-manager dashboard /
11
- customization), attribute order/defaults, and whitespace/formatting
12
- conventions for all Nuxt-generated code. Use when working in or consuming
13
- nuxt-unified-ui, declaring or customizing resources, or whenever generating
14
- Vue/Nuxt code that must match unified code style.
11
+ customization), layer-private app/atoms and app/libs vs public
12
+ components/utils (relative imports), attribute order/defaults, and
13
+ whitespace/formatting conventions for all Nuxt-generated code. Use when
14
+ working in or consuming nuxt-unified-ui, declaring or customizing resources,
15
+ or whenever generating Vue/Nuxt code that must match unified code style.
15
16
  ---
16
17
 
17
18
  # nuxt-unified-ui
@@ -69,12 +70,13 @@ Absolute highlights:
69
70
  - Kebab-case tags (`u-button`, `un-card`)
70
71
  - `v-if` / `v-for` on `<template>` wrappers — not on rendered nodes
71
72
  - If a condition changes several component attributes, prefer explicit `<template v-if>` / `v-else` component variants over nested ternaries and overly dynamic bindings
72
- - **2+ attributes → one per line** except structural **`template` wrappers** and **`u-modal`** (keep either on one line); attribute order + default omissions (`variant="subtle"`, **Cancel only → `ghost`**, omit neutral `color`, `loading-auto`); non-self-closing `>` on same line as last attr; multi-line self-closing `/>` on its own line
73
+ - Tags with children keep all attributes on one line with the opening tag unless a multiline attribute (multi-line array/object/function binding) forces a split — then the opening tag goes on its own line, one attribute per line, value styled like JS; childless tags are self-closing — one single-line attribute stays inline, otherwise tag and attributes each go on their own line with `/>` on its own line; attribute order + default omissions (`variant="subtle"`, **Cancel only → `ghost`**, omit neutral `color`, `loading-auto`); split non-self-closing `>` on same line as last attr; split self-closing `/>` on its own line
73
74
  - `{{ ... }}` on its own line (static + dynamic text may mix)
74
75
  - `/* section */` comments; imports co-located under the section that uses them
75
76
  - Light naming: `handleXxx` handlers, `it` in short callbacks, descriptive `for...of`, computeds use block + `return`
76
77
  - Pages: explicit `definePageMeta.name`, `/* params */` for `route.params` / `route.query`, required `/* seo */` (`useHead` + `useSeoMeta`, `useJsonld` when installed) → [pages.md](references/pages.md)
77
78
  - Fetching: `ufetch(url, {` one line; `useUFetch` with URL on next line + `data*Data` / `is*Loading` / `refresh*` → [data-fetching.md](references/data-fetching.md)
79
+ - Layer modules: generate private first (`app/atoms/` components, `app/libs/` functions and similar); promote to `app/components/` / `app/utils/` only when another layer needs them; import `atoms`/`libs` with relative paths → [code-style.md](references/code-style.md)
78
80
 
79
81
  ---
80
82
 
@@ -152,6 +154,7 @@ From `nuxt.config.ts`: `@vueuse/nuxt`, `@nuxt/ui`, `@nuxtjs/i18n`; `ui.colorMode
152
154
  | New page / route | [pages.md](references/pages.md) |
153
155
  | List/detail fetch or mutation | [data-fetching.md](references/data-fetching.md) |
154
156
  | New / custom resource | [resources.md](references/resources.md) |
157
+ | New component / util in a layer | Start in `app/atoms/` or `app/libs/`; relative imports; promote to `components/` / `utils/` only if another layer needs it → [code-style.md](references/code-style.md) |
155
158
  | Formatting any of the above | [code-style.md](references/code-style.md) |
156
159
 
157
160
  ## Do / don’t
@@ -164,6 +167,7 @@ From `nuxt.config.ts`: `@vueuse/nuxt`, `@nuxt/ui`, `@nuxtjs/i18n`; `ui.colorMode
164
167
  - Handle dialog actions in `onClick`
165
168
  - Follow code style for every generated file
166
169
  - Resources: plugin → full REST set → dashboard nav / custom `<resource-manager>` page → [resources.md](references/resources.md)
170
+ - Start generated layer components in `app/atoms/` and functions in `app/libs/`; import them with relative paths; move to `app/components/` / `app/utils/` only when another layer needs them
167
171
 
168
172
  **Don’t**
169
173
 
@@ -173,3 +177,5 @@ From `nuxt.config.ts`: `@vueuse/nuxt`, `@nuxt/ui`, `@nuxtjs/i18n`; `ui.colorMode
173
177
  - Set choice-button `value` unless the await result must distinguish buttons
174
178
  - Assume color mode is enabled (layer disables it)
175
179
  - Reimplement resource CRUD in route files (use `handleResource*`; customize via dedicated pages + domain APIs)
180
+ - Put a layer-private component or util in `components/` or `utils/` (becomes app-global)
181
+ - Import `atoms`/`libs` via `~/`, `#layers`, or from another layer — use relative paths; promote first if another layer needs them
@@ -1,8 +1,8 @@
1
1
  # Nuxt unified code style
2
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.
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/` (including `atoms/` and `libs/`), `server/`, composables, utils, plugins, middleware, etc.
4
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.
5
+ This document is about the **look and shape** of code — whitespace, wrapping, braces, template structure, sectioning, file placement for layer-private vs public modules, and light naming that affects scanning — not business logic.
6
6
 
7
7
  ---
8
8
 
@@ -19,8 +19,8 @@ Write code so a reader can **scan vertically** and see structure before details.
19
19
  3. **One idea per line in structured data.**
20
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
21
 
22
- 4. **Templates are layout, not mini-scripts.**
23
- Structural directives live on one-line `<template>` wrappers so the rendered node stays a clean component/element. Rendered component attributes wrap predictably (`u-modal` stays one line); closing `>` / `/>` placement is consistent; interpolations sit on their own line (static + dynamic text may share one `{{ ... }}` or surrounding text).
22
+ 4. **Templates stay single-line unless a multiline attribute — or a childless multi-attribute tag — forces a split.**
23
+ A tag with children keeps all attributes on the same single line as the opening tag — no wrapping for length or attribute count. Only a multiline attribute (an array, object, or function literal bound to an attribute that spans multiple lines) forces such a tag to split: then the opening tag goes on its own line, every other attribute gets its own line, and the multiline value is formatted like a JS literal. Childless tags follow their own exception (self-closing; split when they carry more than one attribute or a multiline attribute). Closing `>` / `/>` placement is consistent; interpolations sit on their own line (static + dynamic text may share one `{{ ... }}` or surrounding text).
24
24
 
25
25
  5. **Section comments are the map.**
26
26
  `/* section */` labels define logical domains, while declaration-kind spacing exposes the structure inside each domain. Imports sit in the section that needs them, not in a hoisted pile at the top.
@@ -49,6 +49,7 @@ When editing an existing file, **absolute rules below always win**. For choices
49
49
  | Braces | always for `if` / `else` / `for` / `while` — no brace-less single-liners |
50
50
  | `else` / `catch` | on their **own line** after `}` |
51
51
  | `.js` / `.ts` file start | **two** blank lines at the top, **unless** the file starts with imports — then **no** blank lines before the first `import` |
52
+ | **Single-line principle (JS/TS only)** | **Do not wrap JS/TS code solely because it has grown long**; keep every statement on a single line unless the explicit JS/TS wrapping rules below dictate otherwise. The default is single-statement-per-line; only break across lines when a specific rule permits or requires it. Template sections have their own rule (single-line attributes unless a multiline attribute forces a split) — see Template rules. |
52
53
 
53
54
  ---
54
55
 
@@ -296,9 +297,9 @@ const response = await ufetch(
296
297
 
297
298
  ### Template bindings — compactness
298
299
 
299
- - Single-key object binding may stay inline: `:ui="{ content: 'max-w-7xl' }"`
300
- - Multi-key template object/array bindings are multi-line
301
- - Simple scalars and simple ternaries stay inline; break only when branches become objects/arrays or nested structure
300
+ - A bound array, object, or function literal that spans multiple lines is a **multiline attribute** and forces the tag to split (see Attribute wrapping). A single-pair object with scalar values stays inline and does **not** trigger a split: `:ui="{ content: 'max-w-7xl' }"`
301
+ - Multi-key or nested template object/array bindings are multiline attributes: split the tag and format the value like a JS literal
302
+ - Simple scalars and simple ternaries stay inline; break only when a branch becomes a multiline object/array or nested structure
302
303
  - When one condition changes several attributes, labels/icons, or an object-shaped binding such as `to`, use adjacent `<template v-if>` / `v-else-if` / `v-else` branches with explicit component variants. Prefer small markup duplication over nested ternaries and overly dynamic attributes.
303
304
 
304
305
  ```vue
@@ -372,7 +373,7 @@ The comment names a logical domain, not a declaration kind. Follow it with a bla
372
373
  ```ts
373
374
  /* resource */
374
375
 
375
- import ResourceExplorerCell from '~/atoms/resource-explorer-cell.vue';
376
+ import ResourceExplorerCell from '../atoms/resource-explorer-cell.vue';
376
377
 
377
378
 
378
379
  const itemsPerPage = ref(20);
@@ -431,6 +432,39 @@ Place non-auto-imported imports **inside the section that uses them**, not hoist
431
432
  import { VisXYContainer, VisLine } from '@unovis/vue';
432
433
  ```
433
434
 
435
+ ### Layer-private `atoms` / `libs` vs public `components` / `utils`
436
+
437
+ Nuxt auto-imports `app/components/` and `app/utils/` across the **entire** app (every layer). Files that must stay inside one layer do not belong there.
438
+
439
+ Any component or util in a layer which is not supposed to be used from another layer should be put in (`atoms` for components) and (`libs` for functions and ...), and if we notice that one of these is needed in another layer, we move it to `components` or `utils` folder. A generated component or util starts in these private folders, then moves into public ones if needed. These should be imported with relative path in their callsites.
440
+
441
+ | Role | Directory | Visibility |
442
+ |------|-----------|------------|
443
+ | Private Vue component | `app/atoms/` | This layer only |
444
+ | Private function / helper / similar | `app/libs/` | This layer only |
445
+ | Public Vue component | `app/components/` | Whole app (auto-imported) |
446
+ | Public util | `app/utils/` | Whole app (auto-imported) |
447
+
448
+ `app/composables/` is public like `utils/`. Private composables and other non-component helpers go in `app/libs/`.
449
+
450
+ **Generate private first.** New components go in `app/atoms/`. New functions and similar go in `app/libs/`. Promote (move the file, update call sites) to `app/components/` or `app/utils/` only when another layer needs them. After a promote, drop the relative import — public modules are auto-imported.
451
+
452
+ **Import `atoms` / `libs` with relative paths only** (`../atoms/foo.vue`, `../libs/bar`). Never `~/`, `@/`, `#layers/`, or other aliases. Never import another layer's `atoms` or `libs`; promote first, then use the public auto-import.
453
+
454
+ Do not register `atoms` or `libs` with Nuxt `components` / `imports` config.
455
+
456
+ ```ts
457
+ // ✅ same layer
458
+ import ResourceExplorerCell from '../atoms/resource-explorer-cell.vue';
459
+ import { formatColumn } from '../libs/format-column';
460
+ ```
461
+
462
+ ```ts
463
+ // ❌ aliases / other layers
464
+ import ResourceExplorerCell from '~/atoms/resource-explorer-cell.vue';
465
+ import { formatColumn } from '#layers/other-layer/libs/format-column';
466
+ ```
467
+
434
468
  ### Watcher formatting
435
469
 
436
470
  - Prefer `watchImmediate` over `watch(..., { immediate: true })`
@@ -475,40 +509,69 @@ Keep tight `v-if` / `v-else` chains adjacent (no blank line between matching bra
475
509
 
476
510
  ### Attribute wrapping (hard rule)
477
511
 
478
- - **0–1 attributes:** may stay on one line with the tag
479
- - **Structural `<template>` wrappers:** keep the opening tag on one line, even with several directives, keys, or dynamic slot bindings
480
- - **2+ attributes on rendered elements/components:** one attribute per line — **except `u-modal`**
481
- - **`u-modal` only:** keep **all** attributes on the **same single line** as the tag (do not wrap), even when there are many
512
+ - **Childless tags must be self-closing:** a tag with no children is written `<tag ... />` — never an empty open/close pair (`<tag ...></tag>`).
513
+ - **Tags with children — single line unless multiline:** all attributes stay on the **same single line** as the opening tag, regardless of attribute count or line length. Never wrap only because the line got long or because there are 2+ attributes. This applies equally to rendered elements, components, `u-modal`, and structural `<template>` wrappers.
514
+ - **Only trigger for tags with children — multiline attribute:** such a tag splits only when one of its attributes is a **multiline attribute**: an array, object, or function literal bound to the attribute that spans multiple lines (e.g. `:class="{\n … \n}"`, `:items="[\n … \n]"`). References, calls, and scalar expressions (`:field="field"`, `v-bind="radOmit(action, [...])"`, `:is="elementsMap[x]"`, `@click="handleSave"`) are **not** multiline attributes, even when their runtime value is an object. A single-pair object with scalar values (`:ui="{ content: 'max-w-5xl' }"`) stays inline and does **not** trigger a split.
515
+ - **Childless exception:** a childless (self-closing) tag with a single single-line attribute stays on one line with the tag (`<u-icon name="lucide:check" />`). When it has more than one attribute **or** at least one multiline attribute, the tag and its attributes each go on their own line: the opening tag on its own line (`<tag`), one attribute per line in attribute order, and `/>` on its own line.
516
+ - **Split shape (tags with children):** when a multiline attribute triggers the split, the opening tag goes on its **own line** (`<tag`), every other attribute gets **its own line** in attribute order, and the multiline attribute's value is formatted **like a JS value** (one entry per line, trailing commas). Closing `>` / `/>` placement follows [`>` and `/>` placement](#-and--placement).
482
517
 
483
518
  ```vue
484
- <!-- ✅ 0–1 attributes — inline OK -->
485
- <div class="space-y-3">
486
- <u-form-field label="Captcha">
519
+ <!-- ✅ tag with children, no multiline attribute — one line, however many attributes -->
520
+ <u-modal :ui="{ content: 'max-w-5xl' }" scrollable @update:open="!$event && emit('close')">
487
521
  ...
488
- </u-form-field>
489
- <u-icon name="lucide:check" />
522
+ </u-modal>
490
523
 
491
- <!-- ✅ structural template wrappers stay on one line -->
524
+ <!-- ✅ structural template wrappers follow the same rule -->
492
525
  <template v-for="column in columns" :key="column.accessorKey" #[column.accessorKey+'-cell']="{ row }">
493
526
  ...
494
527
  </template>
495
528
 
496
- <!-- ✅ u-modal — always one line (exception) -->
497
- <u-modal :ui="{ content: 'max-w-5xl' }" scrollable @update:open="!$event && emit('close')">
529
+ <!-- ✅ tag with children + multiline attribute — splits, value styled like JS -->
530
+ <div
531
+ v-if="show"
532
+ :class="{
533
+ 'p-3': !fluidBody,
534
+ }">
498
535
  ...
499
- </u-modal>
536
+ </div>
537
+
538
+ <!-- ✅ childless, single single-line attribute — one line -->
539
+ <u-icon name="lucide:check" />
500
540
 
501
- <!-- ✅ other components — 2+ attributes, one per line -->
541
+ <!-- ✅ childless, more than one attribute — tag and attributes each on their own line -->
502
542
  <u-button
503
543
  variant="subtle"
504
544
  icon="lucide:refresh-ccw"
505
545
  @click="refresh"
506
546
  />
547
+
548
+ <!-- ✅ childless with a multiline attribute — same split shape -->
549
+ <component
550
+ :is="elementsMap[field.identifier]"
551
+ :field="field"
552
+ :class="{
553
+ 'p-2': true,
554
+ }"
555
+ />
556
+
557
+ <!-- ❌ childless multi-attribute tag on one line -->
558
+ <u-button variant="subtle" icon="lucide:refresh-ccw" @click="refresh" />
559
+
560
+ <!-- ❌ empty open/close pair — self-close instead -->
561
+ <div class="grow"></div>
562
+
563
+ <!-- ❌ splitting a tag with children that has no multiline attribute -->
564
+ <un-card
565
+ icon="lucide:key"
566
+ :title="title"
567
+ fluid-body>
568
+ ...
569
+ </un-card>
507
570
  ```
508
571
 
509
572
  ### Attribute order
510
573
 
511
- When wrapping, order attributes as:
574
+ When a multiline attribute forces a split, order attributes as:
512
575
 
513
576
  1. Refs / identity: `ref`, `id`, `name`
514
577
  2. Component visual props: `variant`, `color`, `size`, `icon`, static `label`
@@ -573,27 +636,30 @@ Prefer defaults by **omitting** props rather than restating them:
573
636
  ```
574
637
  ### `>` and `/>` placement
575
638
 
576
- **Non-self-closing**, multi-attribute: `>` on the **same line** as the last attribute:
639
+ Single-line tags keep their closer on the same line (`<u-icon name="lucide:check" />`, `<div class="space-y-3">`).
640
+
641
+ **Split (multiline-attribute) non-self-closing:** `>` on the **same line** as the last attribute:
577
642
 
578
643
  ```vue
579
- <un-card
580
- icon="lucide:key"
581
- :title="title"
582
- fluid-body>
644
+ <div
645
+ v-if="show"
646
+ :class="{
647
+ 'p-3': !fluidBody,
648
+ }">
583
649
  ...
584
- </un-card>
650
+ </div>
585
651
  ```
586
652
 
587
- **Self-closing**, multi-attribute: `/>` on its **own line**; always a space before `/>`:
653
+ **Split self-closing (childless multi-attribute or multiline attribute):** `/>` on its **own line**; always a space before `/>`:
588
654
 
589
655
  ```vue
590
- <u-button
591
- variant="subtle"
592
- icon="lucide:refresh-ccw"
593
- @click="refresh"
656
+ <component
657
+ :is="elementsMap[field.identifier]"
658
+ :field="field"
659
+ :class="{
660
+ 'p-2': true,
661
+ }"
594
662
  />
595
-
596
- <u-icon name="lucide:check" />
597
663
  ```
598
664
 
599
665
  Closing tags for block components (`</un-card>`, `</u-modal>`, …) always on their own line.
@@ -766,8 +832,10 @@ export default defineEventHandler(async event => {
766
832
  | `if (!x) return;` | braced block |
767
833
  | `} else {` | `}\nelse {` |
768
834
  | `toastSuccess({ title: 'x' })` one-liner object | multi-line object + trailing comma |
769
- | 3 attrs on one line (rendered element/component other than `u-modal`) | one attr per line |
770
- | Multi-line `u-modal` attrs | keep `u-modal` attrs on one line |
835
+ | Splitting a tag with children that has no multiline attribute (wrapping for count/length) | keep all attributes on one line with the opening tag |
836
+ | Childless multi-attribute tag on one line | split: opening tag on its own line, one attribute per line, `/>` on its own line |
837
+ | Empty open/close pair (`<div ...></div>`) | self-close (`<div ... />`) |
838
+ | Keeping a tag single-line when it has a multiline attribute | split: opening tag on its own line, one attribute per line, value like JS |
771
839
  | `>` on its own line after attrs | `>` after last attr |
772
840
  | `{{ x }}` glued to tags | interpolation on its own line (static + dynamic mix OK) |
773
841
  | `ghost` on non-Cancel buttons | `ghost` only for Cancel |
@@ -781,6 +849,10 @@ export default defineEventHandler(async event => {
781
849
  | `.ts`/`.js` with no leading blanks (and no imports) | two blank lines at file start |
782
850
  | Blank lines before first `import` | `import` on line 1 |
783
851
  | Blank lines around a function that is only one `for` / `if` / `try` chain | Function `{` / `}` flush against that block |
852
+ | Layer-private component in `components/` | `app/atoms/` |
853
+ | Layer-private util in `utils/` or `composables/` | `app/libs/` |
854
+ | `~/atoms/...` / `#layers/.../atoms` / `#layers/.../libs` | Relative `../atoms/...` / `../libs/...` |
855
+ | Import another layer's `atoms` or `libs` | Promote to `components/` / `utils/`, then auto-import |
784
856
 
785
857
  ---
786
858
 
@@ -796,11 +868,11 @@ export default defineEventHandler(async event => {
796
868
  - [ ] Tiny helpers stay tight
797
869
  - [ ] Return-only multi-criteria functions use a compact exhaustive `if` / `else if` / `else`; broader functions may use early returns
798
870
  - [ ] `else` / `catch` on new line
799
- - [ ] Script objects multi-line; template single-key objects may be inline
871
+ - [ ] Script objects multi-line (the JS/TS single-line principle does not cover templates); template tags stay single-line unless a multiline attribute (multi-line array/object/function binding) forces a split; single-pair scalar objects (e.g. `:ui="{ content: '...' }"`) stay inline
800
872
  - [ ] Kebab-case component tags
801
873
  - [ ] `v-if` / `v-for` on `<template>` wrappers
802
874
  - [ ] Conditional states that change several attributes use explicit `<template v-if>` component variants, not nested ternaries
803
- - [ ] Structural `<template>` wrappers stay on one line; rendered elements/components with 2+ attributes wrap one per line (**`u-modal` stays on one line**); closing `>` / `/>` placement is correct
875
+ - [ ] Tags with children keep all attributes on one line with the opening tag unless a multiline attribute forces a split (opening tag on its own line, one attribute per line, value like JS); childless tags are self-closing — single single-line attribute stays inline, otherwise tag and attributes each go on their own line with `/>` on its own line; closing `>` / `/>` placement is correct
804
876
  - [ ] Attribute order + default-value omissions respected (`subtle`, **Cancel → `ghost` only**, no neutral color noise, `loading-auto`)
805
877
  - [ ] `{{ }}` on own line (static + dynamic mix OK)
806
878
  - [ ] Every section is named; imports are co-located with the section that uses them
@@ -808,3 +880,4 @@ export default defineEventHandler(async event => {
808
880
  - [ ] Computeds that return structures use block + `return`
809
881
  - [ ] Pages: explicit `definePageMeta.name`, `/* params */` + `/* seo */` placement, named navigation ([pages.md](pages.md))
810
882
  - [ ] Fetching: `ufetch` / `useUFetch` wrap styles and destructure names ([data-fetching.md](data-fetching.md))
883
+ - [ ] New layer components/utils start in `app/atoms/` or `app/libs/`; `atoms`/`libs` imported with relative paths only; promote to `components/` / `utils/` only when another layer needs them
@@ -156,7 +156,7 @@ Action entries support `actionType: 'spacer'` (flex grow) and optional `tooltip`
156
156
 
157
157
  Cancel / dismiss actions use `variant: 'ghost'` (or `variant="ghost"`). Do **not** use `ghost` on primary/submit/other actions — prefer omit / `subtle`.
158
158
 
159
- Hand-rolled dialog shells keep **`u-modal` attributes on one line** (see [code-style.md](code-style.md)):
159
+ Hand-rolled dialog shells follow the template single-line rule — this shell has no multiline attribute, so it stays on one line (see [code-style.md](code-style.md)):
160
160
 
161
161
  ```vue
162
162
  <u-modal :ui="{ content: 'max-w-5xl' }" scrollable @update:open="!$event && emit('close')">
@@ -169,7 +169,7 @@ Hand-rolled dialog shells keep **`u-modal` attributes on one line** (see [code-s
169
169
  - Prefer `launchFormPickerDialog` / `launchChoicePickerDialog` over hand-rolled `u-modal` for these flows
170
170
  - Handle actions in button / submit `onClick`
171
171
  - Keep field lists consistent with `un-form` (`identifier`, not `type`, for element kind)
172
- - Mark Cancel with `variant: 'ghost'`; keep `u-modal` attrs on one line
172
+ - Mark Cancel with `variant: 'ghost'`; keep dialog shells single-line unless a multiline attribute forces a split
173
173
 
174
174
  **Don’t**
175
175
 
@@ -57,7 +57,7 @@ Omit unused props. When present, write them in this order:
57
57
  13. `:extra-actions`
58
58
  14. `:meta`
59
59
 
60
- Models are always **page size, then page**. Closing `>` sits on the same line as the last attribute.
60
+ Models are always **page size, then page**. Attributes stay on one line with the opening tag (a multiline attribute is the only split trigger — see [code-style.md](code-style.md)).
61
61
 
62
62
  ### Paged table
63
63
 
@@ -84,10 +84,7 @@ Models are always **page size, then page**. Closing `>` sits on the same line as
84
84
  No footer, no page models, no `total-items`:
85
85
 
86
86
  ```vue
87
- <un-card
88
- :icon="icon"
89
- :title="title"
90
- fluid-body>
87
+ <un-card :icon="icon" :title="title" fluid-body>
91
88
  <un-table
92
89
  :columns="columns"
93
90
  :data="rows"
@@ -298,10 +295,7 @@ const currentPage = ref(1);
298
295
  Prefer an `un-card` with `fluid-body` so the table and footer are edge-to-edge:
299
296
 
300
297
  ```vue
301
- <un-card
302
- :title="`Manage ${titlePlural}`"
303
- fluid-body
304
- :append-actions="toolbarActions">
298
+ <un-card :title="`Manage ${titlePlural}`" fluid-body :append-actions="toolbarActions">
305
299
  <un-table
306
300
  :columns="columns"
307
301
  :loading="isItemsLoading"
@@ -60,7 +60,10 @@ Action buttons: `loading-auto`, props via `radOmit` excluding `actionType` / `to
60
60
  ## `un-spinner` — `app/components/un-spinner.vue`
61
61
 
62
62
  ```vue
63
- <u-icon name="lucide:loader-circle" class="animate-spin" />
63
+ <u-icon
64
+ name="lucide:loader-circle"
65
+ class="animate-spin"
66
+ />
64
67
  ```
65
68
 
66
69
  No props.