nuxt-unified-ui 0.5.16 → 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 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).
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
@@ -134,15 +134,44 @@ function getExtraActionItems(row) {
134
134
  'href',
135
135
  'disabled',
136
136
  'onClick',
137
+ 'onSelect',
137
138
  ]),
138
139
  href: resolveActionValue(it.href, row),
139
140
  to: resolveActionValue(it.to, row),
140
141
  disabled: resolveActionValue(it.disabled, row),
141
- onClick: () => it.onClick?.(row.original),
142
+ onSelect: () => it.onClick?.(row.original),
142
143
  };
143
144
  });
144
145
  }
145
146
 
147
+ function handleRowSelect(_event, row) {
148
+
149
+ if (!props.rowTo) {
150
+ return;
151
+ }
152
+
153
+
154
+ const destination = props.rowTo(row.original);
155
+
156
+ if (!destination) {
157
+ return;
158
+ }
159
+
160
+
161
+ return navigateTo(destination);
162
+
163
+ }
164
+
165
+
166
+ const rowSelectHandler = computed(() => {
167
+ if (!props.rowTo) {
168
+ return undefined;
169
+ }
170
+ else {
171
+ return handleRowSelect;
172
+ }
173
+ });
174
+
146
175
 
147
176
  /* pagination */
148
177
 
@@ -174,7 +203,8 @@ const pageSizeItems = computed(() => {
174
203
  :data="props.data || []"
175
204
  :column-pinning="columnPinning"
176
205
  :ui="tableUi"
177
- :meta="props.meta">
206
+ :meta="props.meta"
207
+ :on-select="rowSelectHandler">
178
208
 
179
209
  <template v-for="(_, name) in $slots" #[name]="slotData">
180
210
  <slot
@@ -11,8 +11,15 @@
11
11
  * @example
12
12
  * unSet({}, 'cards[0].value', 2)
13
13
  * // => { cards: [{ value: 2 }] }
14
+ *
15
+ * Paths that contain `__proto__`, `prototype`, or `constructor` are ignored.
14
16
  */
15
17
 
18
+ function isUnsafePathSegment(segment: string) {
19
+ return segment === '__proto__' || segment === 'prototype' || segment === 'constructor';
20
+ }
21
+
22
+
16
23
  export function unSet(target: any, key: string, value: any) {
17
24
 
18
25
  const segments = (
@@ -22,7 +29,7 @@ export function unSet(target: any, key: string, value: any) {
22
29
  .filter(Boolean)
23
30
  );
24
31
 
25
- if (segments.length === 0) {
32
+ if (segments.length === 0 || segments.some(isUnsafePathSegment)) {
26
33
  return target;
27
34
  }
28
35
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-unified-ui",
3
3
  "type": "module",
4
- "version": "0.5.16",
4
+ "version": "0.5.18",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./index.d.ts",
7
7
  "exports": {
@@ -20,9 +20,9 @@
20
20
  ],
21
21
  "dependencies": {
22
22
  "@formkit/tempo": "1.1.0",
23
- "@iconify-json/lucide": "1.2.136",
23
+ "@iconify-json/lucide": "1.2.138",
24
24
  "@nuxt/kit": "4.5.2",
25
- "@nuxt/ui": "4.11.2",
25
+ "@nuxt/ui": "4.11.3",
26
26
  "@nuxtjs/i18n": "10.6.0",
27
27
  "@types/canvas-confetti": "1.9.0",
28
28
  "@vueuse/core": "15.0.0",
@@ -1,181 +1,183 @@
1
1
  ---
2
2
  name: nuxt-unified-ui
3
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 / un-table
8
- (attribute order, row actions, pagination), pages /
9
- definePageMeta, page /* params */ and /* seo */ blocks, ufetch / useUFetch wrapping, unified resources
10
- (server plugins, REST handleResource*, resource-manager dashboard /
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.
4
+ Builds and edits Nuxt apps and layers that use nuxt-unified-ui: un-form /
5
+ useForm schema forms, un-card, un-table, launchFormPickerDialog /
6
+ launchChoicePickerDialog / launchDialog, toast helpers, radXxx radashi
7
+ auto-imports, and the companion unified stack (ufetch, useUFetch,
8
+ resource-manager, createUnifiedResourceController). Use when package.json
9
+ depends on nuxt-unified-ui, nuxt.config extends it, or code uses these
10
+ APIs. Whenever implementation work is finished, run `/nuxt-unified-ui
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.
16
15
  ---
17
16
 
18
17
  # nuxt-unified-ui
19
18
 
20
- Reusable **Nuxt layer** (Nuxt UI + helpers) **plus** the mandatory **code style** for Nuxt projects using this stack.
21
-
22
- Peer dependency: **Nuxt `>=4.5.2`**.
23
-
24
- This is the **only** installable skill in this repo. Deep topics live under `references/`.
25
-
26
- ## When to use
27
-
28
- - Installing / extending `nuxt-unified-ui` as a Nuxt layer
29
- - Using `un-form`, dialogs, toasts, `un-card`, `un-table`, radashi `radXxx`, etc.
30
- - Declaring or customizing **unified resources** (plugins, REST, dashboard)
31
- - **Whenever generating or editing Nuxt/Vue/server code** that must follow the unified look (whitespace, wrapping, template shape, sectioning)
32
-
33
- ## References (read as needed)
34
-
35
- | Topic | File |
36
- |-------|------|
37
- | **Code style (mandatory)** | [references/code-style.md](references/code-style.md) |
38
- | Unified resources (plugins → REST → dashboard) | [references/resources.md](references/resources.md) |
39
- | Pages / routing | [references/pages.md](references/pages.md) |
40
- | Data fetching (`ufetch` / `useUFetch`) | [references/data-fetching.md](references/data-fetching.md) |
41
- | Layer install + required CSS | [references/layer-setup.md](references/layer-setup.md) |
42
- | Public surface inventory | [references/public-surface.md](references/public-surface.md) |
43
- | Forms (`useForm` / `un-form`) | [references/forms.md](references/forms.md) |
44
- | Form field schema | [references/form-field-schema.md](references/form-field-schema.md) |
45
- | Form elements | [references/form-elements.md](references/form-elements.md) |
46
- | Dialogs / toasts / UI | [references/dialogs.md](references/dialogs.md) |
47
- | Dialog implementation | [references/dialogs-impl.md](references/dialogs-impl.md) |
48
- | Toast + `un-*` details | [references/toast-and-ui.md](references/toast-and-ui.md) |
49
- | Tables (`un-table`) | [references/tables.md](references/tables.md) |
50
- | Radashi `radXxx` catalog | [references/radashi.md](references/radashi.md) |
51
-
52
- ---
53
-
54
- ## Code style (read [code-style.md](references/code-style.md) before writing code)
55
-
56
- **Always apply** to Vue SFCs and app/server `.ts` files. Higher-level idea: code should **scan vertically** — named sections, declaration-kind groups, multi-line literals, and predictable template wrapping.
57
-
58
- Absolute highlights:
59
-
60
- - `<script setup>` only — **never** `lang="ts"`; no TS annotations in Vue (runtime prop types)
61
- - 2-space indent; single quotes; semicolons; trailing commas in multi-line literals
62
- - **`.js` / `.ts` file start:** two leading blank lines, **except** when the file starts with imports — then **no** blank lines before the first `import`
63
- - Every `<script setup>` section starts with `/* section name */`, then a blank line
64
- - Within a section, group declarations by kind (imports, refs, computeds, watchers, functions, etc.): two blank lines between groups; no blanks between consecutive refs; one blank between consecutive members of other groups
65
- - Non-trivial async/functions: blank line after `{`, double blank between major steps, blank before `}`
66
- - A function whose body is one `for` / `while` / `if` or one connected `if` / `else` / `else if` or `try` / `catch` / `finally` chain stays flush: no blank lines between the function `{` / `}` and that block
67
- - A function dedicated to choosing a return value from multiple criteria uses one exhaustive `if` / `else if` / `else` chain; broader functions may use guard clauses and early returns
68
- - `else` / `catch` on their own line after `}`
69
- - Script object literals always multi-line (even one property)
70
- - Kebab-case tags (`u-button`, `un-card`)
71
- - `v-if` / `v-for` on `<template>` wrappers — not on rendered nodes
72
- - If a condition changes several component attributes, prefer explicit `<template v-if>` / `v-else` component variants over nested ternaries and overly dynamic bindings
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
74
- - `{{ ... }}` on its own line (static + dynamic text may mix)
75
- - `/* section */` comments; imports co-located under the section that uses them
76
- - Light naming: `handleXxx` handlers, `it` in short callbacks, descriptive `for...of`, computeds use block + `return`
77
- - Pages: explicit `definePageMeta.name`, `/* params */` for `route.params` / `route.query`, required `/* seo */` (`useHead` + `useSeoMeta`, `useJsonld` when installed) → [pages.md](references/pages.md)
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)
80
-
81
- ---
82
-
83
- ## Quick start (host app)
84
-
85
- 1. Install the package.
86
- 2. Create host `assets/css/main.css`:
87
-
88
- ```css
89
- @import 'tailwindcss';
90
- @import '@nuxt/ui';
91
- @import 'nuxt-unified-ui/nuxt-ui-fixes.css';
19
+ The main agent decides what code exists, where it lives, and which APIs it calls. When the work is done, the apply pipeline runs one subagent per file to check the skill's rules and apply the code style; it is the only place formatting rules apply. The main agent does not load `references/code-style.md`.
20
+
21
+ ## Finishing work: `/nuxt-unified-ui apply`
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.
24
+
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.
26
+
27
+ ## APIs
28
+
29
+ **Layer** APIs come from this layer and are auto-imported across the app and every layer. **Companion** APIs come from the companion unified layers, which real projects always include next to this one. When an API is not listed here, check the source before using it.
30
+
31
+ | API | Use it to | From | Reference |
32
+ |---|---|---|---|
33
+ | `un-form` / `useForm` | Render a schema-driven form; `useForm` returns `{ form, formTag }` | layer | [forms.md](references/forms.md) |
34
+ | `registerFormExtraElement` | Add a custom form element `identifier` (call from a Nuxt plugin) | layer | [forms.md](references/forms.md) |
35
+ | `useFormExtraElements` | Read the registered custom elements (add them with `registerFormExtraElement`) | layer | [forms.md](references/forms.md) |
36
+ | `un-card` | Card with icon / title / subtitle header, body, and action rows | layer | [toast-and-ui.md](references/toast-and-ui.md) |
37
+ | `un-typography` | Icon + title + subtitle + text block | layer | [toast-and-ui.md](references/toast-and-ui.md) |
38
+ | `un-spinner` | Show a loading spinner | layer | [toast-and-ui.md](references/toast-and-ui.md) |
39
+ | `un-table` | Show one page of rows with row actions and a pagination footer | layer | [tables.md](references/tables.md) |
40
+ | `launchFormPickerDialog` | Ask for form input in a modal; submit logic goes in `submitButton.onClick` | layer | [dialogs.md](references/dialogs.md) |
41
+ | `launchChoicePickerDialog` | Confirm or choose in a modal; logic goes in each button's `onClick` | layer | [dialogs.md](references/dialogs.md) |
42
+ | `launchDialog` | Open any dialog component; resolves with its `close` payload | layer | [dialogs.md](references/dialogs.md) |
43
+ | `toast`, `toastSuccess`, `toastError`, `toastWarning`, `toastInfo` | Show a toast (typed helpers set icon and color) | layer | [toast-and-ui.md](references/toast-and-ui.md) |
44
+ | `smartMatch` | Test a function, mongo-style filter, or truthy value against an object | layer | [forms.md](references/forms.md) |
45
+ | `unSet` | Set a nested path on an object, creating missing levels (mutates) | layer | — |
46
+ | `formatDate` / `parseDate` | Format and parse dates (`@formkit/tempo`) | layer | — |
47
+ | `isSlotFilled` | Check whether a slot has content | layer | — |
48
+ | `pathRelativeToBase` | Resolve a path against a file URL; also exported from the package for `nuxt.config` | layer | [layer-setup.md](references/layer-setup.md) |
49
+ | `makeConfetti` | Fire confetti: `template` picks a built-in effect (`parade`, `on-top` / `on-left` / `on-right` / `on-bottom`, `on-frame`, `split-on-top`, `on-curtain`), `amount` sets particles per burst, other args go to `canvas-confetti` | layer | — |
50
+ | `radXxx` | Any radashi function (`radGet`, `radPick`, …), in app and server | layer | [radashi.md](references/radashi.md) |
51
+ | `ufetch` | Make a one-off API request (submit, delete, click) | companion | [data-fetching.md](references/data-fetching.md) |
52
+ | `useUFetch` | Load reactive page data (`data`, `pending`, `refresh`) | companion | [data-fetching.md](references/data-fetching.md) |
53
+ | `parseSchema` | Compile a resource schema DSL into `{ schema, type, inferred }` | companion | [resources.md](references/resources.md) |
54
+ | `createUnifiedResourceController` / `UnifiedResourceController` | Create the typed Mongo controller (`dbo`) for a resource | companion | [resources.md](references/resources.md) |
55
+ | `app` / `UnifiedAppRegistry` | Global typed registry of resources (`app.users.dbo`) | companion | [resources.md](references/resources.md) |
56
+ | `handleResourceSchema`, `handleResourceList`, `handleResourceCreate`, `handleResourceCount`, `handleResourceRetrieve`, `handleResourceUpdate`, `handleResourceDelete` | Implement the standard REST routes of a resource | companion | [resources.md](references/resources.md) |
57
+ | `useResourceName` | Derive a resource's API path and display titles from its name | companion | [resources.md](references/resources.md) |
58
+ | `useResourceMeta` | Map a resource schema to form fields and table columns | companion | [resources.md](references/resources.md) |
59
+ | `<resource-manager>` | Full CRUD dashboard card for a resource; exposes `refreshResources()` | companion | [resources.md](references/resources.md) |
60
+ | `<resource-explorer-table>` | Filter bar + sortable headers + `un-table` for a resource | companion | [tables.md](references/tables.md) |
61
+ | `assertBody` | Validate a request body against a schema in a server route | companion | — |
62
+ | `assertRateLimit` | Rate-limit a server route | companion | — |
63
+ | `createUnauthenticatedError` | Throw a 401 from a server route | companion | — |
64
+ | `generateUuid` | Generate a UUID | companion | — |
65
+ | `useToken` | Read or set the auth token | companion | — |
66
+ | `is-authenticated` middleware; `dashboard` / `empty` layouts | Protect pages; dashboard and full-bleed page layouts | companion | [pages.md](references/pages.md) |
67
+ | `useJsonld` | Emit JSON-LD on public pages, only when `nuxt-jsonld` is installed | `nuxt-jsonld` | [pages.md](references/pages.md) |
68
+
69
+ ## File structure
70
+
71
+ The main agent decides where a file lives and which one job it has, and does it while implementing. Apply subagents only report misplaced files; they never move files or update callers.
72
+
73
+ Each `.vue`, `.js`, and `.ts` file has one responsibility. When a file would have two independent jobs, create two files.
74
+
75
+ Nuxt auto-imports `app/components/` and `app/utils/` across every layer. `app/composables/` is public in the same way. A file that must stay inside one layer does not go in those directories.
76
+
77
+ | Role | Directory | Visibility |
78
+ |---|---|---|
79
+ | Private Vue component | `app/atoms/` | This layer only |
80
+ | Private function, composable, or helper | `app/libs/` | This layer only |
81
+ | Public Vue component | `app/components/` | Whole app, auto-imported |
82
+ | Public util | `app/utils/` | Whole app, auto-imported |
83
+
84
+ Generate private first. New components start in `app/atoms/`. New functions and similar helpers start in `app/libs/`. Promote a file to `app/components/` or `app/utils/` only when another layer needs it. After a promote, update every caller and remove the relative import; public modules are auto-imported.
85
+
86
+ Import `atoms` and `libs` with relative paths only (`../atoms/foo.vue`, `../libs/bar`). Never use `~/`, `@/`, `#layers/`, or another alias. Never import another layer's `atoms` or `libs`; promote that file first, then use the public auto-import.
87
+
88
+ Do not register `atoms` or `libs` with Nuxt `components` or `imports` config.
89
+
90
+ ## Before writing
91
+
92
+ Read the reference that matches the task before creating or editing files. The details stay in that reference.
93
+
94
+ | Task | Read first |
95
+ |---|---|
96
+ | Layer installation and required CSS | [references/layer-setup.md](references/layer-setup.md) |
97
+ | Forms, field schema, built-in and custom elements | [references/forms.md](references/forms.md) |
98
+ | Dialogs | [references/dialogs.md](references/dialogs.md) |
99
+ | Toasts, `un-*` UI, button / badge / icon conventions | [references/toast-and-ui.md](references/toast-and-ui.md) |
100
+ | Tables | [references/tables.md](references/tables.md) |
101
+ | Pages and routing | [references/pages.md](references/pages.md) |
102
+ | `ufetch` / `useUFetch` | [references/data-fetching.md](references/data-fetching.md) |
103
+ | Unified resources | [references/resources.md](references/resources.md) |
104
+ | Radashi `radXxx` exports | [references/radashi.md](references/radashi.md) |
105
+
106
+ ## Decisions the main agent owns
107
+
108
+ Make these choices while implementing. The apply pipeline checks them again afterward.
109
+
110
+ - Prefer the APIs above over hand-rolled equivalents (raw `$fetch`, hand-built `u-modal` flows, direct `radashi` imports).
111
+ - A new resource includes its server plugin, the full REST route set, and a dashboard nav entry or a custom `<resource-manager>` page.
112
+ - Use `ufetch` for a one-off request. Use `useUFetch` for reactive page data.
113
+ - Every page has an explicit `definePageMeta.name` and sets SEO with `useHead` (title) and `useSeoMeta` (description).
114
+ - Buttons, badges, and icons follow the component conventions in [references/toast-and-ui.md](references/toast-and-ui.md#component-conventions). Read them before writing any button.
115
+ - When splitting or promoting a file, update its callers in the same task.
116
+ - Installing the layer includes the required host `assets/css/main.css` and the `pathRelativeToBase` CSS entry in `nuxt.config`.
117
+
118
+ ## i18n
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
+
129
+ - Every user-facing string goes through `$t`, in templates and in script. `$t` is available in both without calling `useI18n()`.
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
+ },
92
156
  ```
93
157
 
94
- 3. Extend the layer (CSS wiring is **required**):
95
-
96
- ```js
97
- import { pathRelativeToBase } from 'nuxt-unified-ui';
98
-
99
- export default defineNuxtConfig({
100
- css: [
101
- pathRelativeToBase(import.meta.url, './assets/css/main.css'),
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
+ },
102
171
  ],
103
- extends: [
104
- 'nuxt-unified-ui',
105
- ],
106
- });
172
+ },
107
173
  ```
108
174
 
109
- 4. Wrap the app with `u-app`.
110
- 5. Prefer layer helpers (`useForm`, `launchFormPickerDialog`, `toastSuccess`) over reinventing them.
111
- 6. Generate all new code using [code-style.md](references/code-style.md).
112
-
113
- Details: [layer-setup.md](references/layer-setup.md).
114
-
115
- ## Package surface
116
-
117
- | Export | Path |
118
- |--------|------|
119
- | `nuxt-unified-ui` | `./nuxt.config.ts` (also re-exports `pathRelativeToBase`) |
120
- | `nuxt-unified-ui/app` | `./app` |
121
- | `nuxt-unified-ui/nuxt-ui-fixes.css` | `./app/assets/css/nuxt-ui-fixes.css` |
122
-
123
- Published: `nuxt.config.ts`, `index.d.ts`, `app/`, `i18n/`, `modules/`.
124
-
125
- ## Mental model (`app/`)
126
-
127
- | Path | Role |
128
- |------|------|
129
- | `app/components/` | `un-form`, `un-card`, `un-typography`, `un-spinner`, `un-table` |
130
- | `app/composables/` | `useForm`, `useFormExtraElements` |
131
- | `app/elements/` | Built-in form field renderers |
132
- | `app/dialogs/` | Form / choice picker modal UIs |
133
- | `app/utils/` | `launchDialog*`, `toast*`, `smartMatch`, `unSet`, dates, … |
134
- | `app/plugins/` | `$toaster` via `useToast()` |
135
- | `modules/radashi.ts` | Auto-imports radashi as `rad*` |
136
- | `i18n/locales/` | `en.json`, `de.json` |
137
-
138
- ## Layer config (inherited)
139
-
140
- 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`.
141
-
142
- ## Common tasks
143
-
144
- | Task | Prefer |
145
- |------|--------|
146
- | Schema form | `useForm` + `<form-tag />` / `<un-form>` → [forms.md](references/forms.md) |
147
- | Modal form | `launchFormPickerDialog` + `submitButton.onClick` → [dialogs.md](references/dialogs.md) |
148
- | Confirm / choice | `launchChoicePickerDialog` + button `onClick` (avoid `value`) |
149
- | Feedback | `toastSuccess` / `toastError` / `toastWarning` / `toastInfo` / `toast` |
150
- | Page chrome | `un-typography` + `un-card` |
151
- | Data table | `un-table` + parent-owned fetch / sort / filter → [tables.md](references/tables.md) |
152
- | Custom field | `registerFormExtraElement` in a plugin |
153
- | Utilities | `radXxx` → [radashi.md](references/radashi.md) |
154
- | New page / route | [pages.md](references/pages.md) |
155
- | List/detail fetch or mutation | [data-fetching.md](references/data-fetching.md) |
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) |
158
- | Formatting any of the above | [code-style.md](references/code-style.md) |
159
-
160
- ## Do / don’t
161
-
162
- **Do**
163
-
164
- - Extend via `extends: ['nuxt-unified-ui']`
165
- - Keep required host `main.css` + `pathRelativeToBase` CSS entry + `nuxt-ui-fixes.css`
166
- - Use field `identifier` for element kind; `type` only for HTML input types
167
- - Handle dialog actions in `onClick`
168
- - Follow code style for every generated file
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
171
-
172
- **Don’t**
173
-
174
- - Invent APIs not in source
175
- - Reference any local playground as consumer docs
176
- - Use PascalCase component tags in templates
177
- - Set choice-button `value` unless the await result must distinguish buttons
178
- - Assume color mode is enabled (layer disables it)
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
175
+ ```json
176
+ {
177
+ "patients": {
178
+ "single": {
179
+ "title": "Patient"
180
+ }
181
+ }
182
+ }
183
+ ```