@fui-org/fui-cli 1.2.0 → 1.3.1

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.
Files changed (51) hide show
  1. package/dist/fui-x7kph78r.js +420 -0
  2. package/dist/fui.js +1 -429
  3. package/package.json +3 -3
  4. package/skills/fui/SKILL.md +3 -3
  5. package/skills/fui-skill-cli/SKILL.md +139 -0
  6. package/skills/fui-skill-cli/references/INDEX.md +110 -0
  7. package/skills/fui-skill-cli/references/advanced-techniques.md +168 -0
  8. package/skills/fui-skill-cli/references/coding-standards.md +112 -0
  9. package/skills/fui-skill-cli/references/component-design.md +448 -0
  10. package/skills/fui-skill-cli/references/component-quickref.md +78 -0
  11. package/skills/fui-skill-cli/references/component-table.md +248 -0
  12. package/skills/fui-skill-cli/references/components-dialog.md +191 -0
  13. package/skills/fui-skill-cli/references/components-display.md +141 -0
  14. package/skills/fui-skill-cli/references/components-echart.md +316 -0
  15. package/skills/fui-skill-cli/references/components-input.md +335 -0
  16. package/skills/fui-skill-cli/references/controls-patterns.md +701 -0
  17. package/skills/fui-skill-cli/references/controls-styling-vocabulary.md +137 -0
  18. package/skills/fui-skill-cli/references/db-table-design.md +73 -0
  19. package/skills/fui-skill-cli/references/db-workflow.md +288 -0
  20. package/skills/fui-skill-cli/references/default-function.md +425 -0
  21. package/skills/fui-skill-cli/references/design-modes.md +57 -0
  22. package/skills/fui-skill-cli/references/echart-templates.md +489 -0
  23. package/skills/fui-skill-cli/references/fastproject.md +99 -0
  24. package/skills/fui-skill-cli/references/fsheet.md +203 -0
  25. package/skills/fui-skill-cli/references/fullstack-workflow.md +313 -0
  26. package/skills/fui-skill-cli/references/module-data-patterns.md +117 -0
  27. package/skills/fui-skill-cli/references/module-json-anatomy.md +132 -0
  28. package/skills/fui-skill-cli/references/module-structure.md +141 -0
  29. package/skills/fui-skill-cli/references/new-session.md +85 -0
  30. package/skills/fui-skill-cli/references/pdfmake.md +60 -0
  31. package/skills/fui-skill-cli/references/permission-system.md +150 -0
  32. package/skills/fui-skill-cli/references/platform-architecture.md +269 -0
  33. package/skills/fui-skill-cli/references/project-config.md +303 -0
  34. package/skills/fui-skill-cli/references/project-provisioning.md +278 -0
  35. package/skills/fui-skill-cli/references/script-map.md +262 -0
  36. package/skills/fui-skill-cli/references/sql-clr-functions.md +225 -0
  37. package/skills/fui-skill-cli/references/system-design.md +89 -0
  38. package/skills/fui-skill-cli/references/tapi-file-api.md +185 -0
  39. package/skills/fui-skill-cli/references/tapi-permission-patterns.md +156 -0
  40. package/skills/fui-skill-cli/references/tapi-reference.md +474 -0
  41. package/skills/fui-skill-cli/references/tools-registry.md +84 -0
  42. package/skills/fui-skill-cli/references/ui-crosswindow-patterns.md +321 -0
  43. package/skills/fui-skill-cli/references/ui-dialog-patterns.md +255 -0
  44. package/skills/fui-skill-cli/references/ui-layout-patterns.md +176 -0
  45. package/skills/fui-skill-cli/references/ui-patterns.md +303 -0
  46. package/skills/fui-skill-cli/references/ui-screenshot-review.md +95 -0
  47. package/skills/fui-skill-cli/references/ui-table-cell-patterns.md +318 -0
  48. package/skills/fui-skill-cli/references/ui-templates.md +22 -0
  49. package/skills/fui-skill-cli/references/verification.md +236 -0
  50. package/skills/fui-skill-cli/references/watcher-patterns.md +163 -0
  51. package/skills/fui-skill-cli/references/websocket-realtime.md +271 -0
@@ -0,0 +1,176 @@
1
+ # FUI Layout Patterns — page-container, master-detail, switch, dynamic label
2
+
3
+ > Owns: **page layout patterns: sticky page-container, 2-column master-detail, v-switch, date init, dynamic label**.
4
+
5
+ Layout and helper-control patterns, drawn from project **security-manager**. See also: [ui-patterns.md](ui-patterns.md), [ui-table-cell-patterns.md](ui-table-cell-patterns.md), [module-structure.md](module-structure.md).
6
+
7
+ ---
8
+
9
+ ## 1. Date init with moment()
10
+
11
+ ```json
12
+ "data": [
13
+ {
14
+ "TuNgay": "moment().format('YYYY-MM-DD')",
15
+ "DenNgay": "moment().format('YYYY-MM-DD')"
16
+ }
17
+ ]
18
+ ```
19
+
20
+ > The value is a JS expression string, eval'd at init. `moment()` is available in the FUI runtime.
21
+
22
+ ---
23
+
24
+ ## 2. page-container + page-navigation
25
+
26
+ Use for a fixed filter bar on top with scrolling content below.
27
+
28
+ ```json
29
+ {
30
+ "prop": "page-container grid-list-md fluid",
31
+ "rows": [
32
+ {
33
+ "prop": "page-navigation",
34
+ "cols": [
35
+ { "el": "f-date", "attr": { "v-model": "TuNgay", "label": "Từ ngày", "date-add": 0 }, "w": "2" },
36
+ { "el": "f-date", "attr": { "v-model": "DenNgay", "label": "Tới ngày", "date-add": 0 }, "w": "2" },
37
+ {
38
+ "el": "f-button",
39
+ "attr": {
40
+ "color": "primary",
41
+ "icon-text": "mdi-filter",
42
+ "label": "Tìm",
43
+ ":action": { "CALL": "getLogData" }
44
+ },
45
+ "w": 3
46
+ }
47
+ ]
48
+ },
49
+ {
50
+ "prop": "",
51
+ "cols": [{ "el": "f-table", "attr": { "label": "Log viewer", ":items": "LogData" } }]
52
+ }
53
+ ]
54
+ }
55
+ ```
56
+
57
+ - `page-container` + `page-navigation` → sticky top filter bar
58
+ - `f-table` without `:update-api` → readonly; without `:headers` → headers auto-built from data
59
+
60
+ ---
61
+
62
+ ## 3. Dynamic label from state
63
+
64
+ Use `:label` with an expression to show context.
65
+
66
+ ```json
67
+ {
68
+ "el": "f-title",
69
+ "attr": {
70
+ ":label": "sFullName + ` - ` + sDepartment"
71
+ }
72
+ }
73
+ ```
74
+
75
+ ```json
76
+ {
77
+ "el": "f-table",
78
+ "attr": {
79
+ ":label": "`${fTableTitle==''? 'Computer list' : fTableTitle}`"
80
+ }
81
+ }
82
+ ```
83
+
84
+ ---
85
+
86
+ ## 4. v-switch — toggle with label
87
+
88
+ ```json
89
+ {
90
+ "el": "v-layout",
91
+ "attr": {},
92
+ "innerHTML": [
93
+ {
94
+ "el": "v-switch",
95
+ "attr": {
96
+ "v-model": "switchResetPassword",
97
+ ":required": false,
98
+ "label": "Đặt lại mật khẩu"
99
+ },
100
+ "w": "12"
101
+ }
102
+ ]
103
+ }
104
+ ```
105
+
106
+ Combine with `v-if` to show/hide extra fields:
107
+
108
+ ```json
109
+ {
110
+ "el": "v-layout",
111
+ "attr": { "v-if": "switchResetPassword" },
112
+ "innerHTML": [...]
113
+ }
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 5. Two-column master-detail
119
+
120
+ Left table row pick → watch → load right table. No new window needed.
121
+
122
+ > Detail button in a table cell: choose `t-button` vs `t-menu` per [ui-table-cell-patterns.md](ui-table-cell-patterns.md) — for master-detail the action is "view/load related data", so use `t-button`.
123
+
124
+ **`data[]` and watch:**
125
+
126
+ ```json
127
+ "data": [
128
+ { "dsParent": [], "dsDetail": [], "selectedId": null },
129
+ {
130
+ "getParent": { "API": "/SM_Parent_Select", "OUT": "dsParent" },
131
+ "getDetail": {
132
+ "API": "/SM_Detail_Select",
133
+ "IN": { "ParentID": "selectedId" },
134
+ "OUT": "dsDetail"
135
+ }
136
+ },
137
+ { "CALL": "getParent" }
138
+ ],
139
+ "watch": {
140
+ "selectedId": { "CALL": "getDetail" }
141
+ }
142
+ ```
143
+
144
+ **`t-button` column in the left table:**
145
+
146
+ ```json
147
+ {
148
+ "text": "Chi tiết",
149
+ "value": "ParentID",
150
+ "align": "center",
151
+ "el": "t-button",
152
+ "attr": {
153
+ "icon-text": "mdi-eye",
154
+ "color": "primary",
155
+ ":outlined": true,
156
+ ":action": { "selectedId": "item.ParentID" }
157
+ }
158
+ }
159
+ ```
160
+
161
+ **2-column layout:**
162
+
163
+ ```json
164
+ {
165
+ "prop": "fluid grid-list-md",
166
+ "rows": [
167
+ {
168
+ "prop": "row wrap",
169
+ "cols": [
170
+ { "w": 5, "el": "f-table", "attr": { ":items": "dsParent", "item-key": "ParentID", ":headers": [...] } },
171
+ { "w": 7, "el": "f-table", "attr": { ":items": "dsDetail", "item-key": "DetailID", ":headers": [...] } }
172
+ ]
173
+ }
174
+ ]
175
+ }
176
+ ```
@@ -0,0 +1,303 @@
1
+ # FUI UI Patterns — Core
2
+
3
+ > Owns: **core UI patterns: toolbar/filter, permission gate, UI design checklist**. Table/dialog/cross-window/layout live in separate `ui-*` files.
4
+
5
+ Patterns taken from project **security-manager** — use as reference for new modules. Load specialised files as needed:
6
+
7
+ | Need | File |
8
+ | -------------------------------------------------------------------------------------- | -------------------------------------------------------- |
9
+ | f-table + in-cell controls (t-check/t-button/t-select/t-menu, ctrl-update, chip-group) | [ui-table-cell-patterns.md](ui-table-cell-patterns.md) |
10
+ | Dialog/popup form (hidden-container, open dialog with row, checkbox-list) | [ui-dialog-patterns.md](ui-dialog-patterns.md) |
11
+ | Sub-window & data exchange (`openWindow`, `#PARENT`/`#Win`) | [ui-crosswindow-patterns.md](ui-crosswindow-patterns.md) |
12
+ | Layout (page-container, master-detail, v-switch, date init, dynamic label) | [ui-layout-patterns.md](ui-layout-patterns.md) |
13
+
14
+ ---
15
+
16
+ ## 1. `data[]` structure
17
+
18
+ `data` initialises state + actions, processed top to bottom.
19
+
20
+ - Object with **no action key** (no API, CALL, IF…) → state init
21
+ - Object whose **key is a function name** → named (reusable) action
22
+ - Trailing `{ "API": ..., "OUT": ... }` or `{ "CALL": ... }` → runs automatically on load
23
+
24
+ ```json
25
+ "data": [
26
+ {
27
+ "dsUser": [],
28
+ "dsGroup": [],
29
+ "sDepartment": null,
30
+ "dlUserInfo": false
31
+ },
32
+ {
33
+ "getDepartment": { "API": "SM_DepartmentSelectAll", "OUT": "dsDepartment" },
34
+ "getGroup": { "API": "SM_Group_SelectAll", "OUT": "dsGroup" },
35
+ "getUser": {
36
+ "API": "SM_Users_SelectByDepGroup",
37
+ "IN": { "GroupID": "sGroup", "DepartmentID": "sDepartment" },
38
+ "OUT": "dsUser"
39
+ }
40
+ },
41
+ { "CALL": "getDepartment" },
42
+ { "CALL": "getGroup" }
43
+ ]
44
+ ```
45
+
46
+ > **Auto-startup**: trailing CALLs in `data[]` run on module load — no event handler needed.
47
+
48
+ ---
49
+
50
+ ## 2. Cascading API init
51
+
52
+ `CALLBACK` sets a value from the first API result; the next API runs via `watch`.
53
+
54
+ ```json
55
+ "data": [
56
+ { "dsModule": [], "sModuleID": null },
57
+ {
58
+ "API": "/SM_Modules_SelectAll",
59
+ "OUT": "dsModule",
60
+ "CALLBACK": { "sModuleID": "dsModule[0].ModuleID" }
61
+ }
62
+ ],
63
+ "watch": {
64
+ "sModuleID": { "CALL": "getFunctionRight" }
65
+ }
66
+ ```
67
+
68
+ > CALLBACK sets `sModuleID` → watch fires `getFunctionRight`. No manual call.
69
+
70
+ ---
71
+
72
+ ## 3. Watch — cascading filter
73
+
74
+ Filter change → reload. Several actions → array of CALL.
75
+
76
+ ```json
77
+ "watch": {
78
+ "sDepartment": { "CALL": "getUser" },
79
+ "sGroup": { "CALL": "getUser" },
80
+ "sModuleID": [
81
+ { "CALL": "getUserModule" },
82
+ { "CALL": "getSysRight" },
83
+ { "CALL": "getFunctionRight" }
84
+ ]
85
+ }
86
+ ```
87
+
88
+ ---
89
+
90
+ ## 4. Toolbar / filter row
91
+
92
+ Row of filter fields + action buttons; `v-spacer` pushes the button group right.
93
+
94
+ > **Rule: EVERY column is `"col": "shrink"` except `v-spacer`.** A non-`shrink` column takes a share of free space — including an empty-`w` column and buttons. Missing `shrink` → buttons stretch, gaps appear between buttons, and the trailing group doesn't reach the right edge even with `v-spacer`. Standard template + measurements: [controls-patterns.md](controls-patterns.md) §Toolbar row.
95
+
96
+ ```json
97
+ {
98
+ "prop": "",
99
+ "cols": [
100
+ {
101
+ "el": "v-autocomplete",
102
+ "col": { "class": "shrink" },
103
+ "attr": {
104
+ "class": "flex-md-grow-0",
105
+ "style": "width:200px",
106
+ "v-model": "sGroup",
107
+ "label": "Group",
108
+ ":items": "dsGroup",
109
+ "item-text": "GroupName",
110
+ "item-value": "GroupID",
111
+ ":required": false
112
+ },
113
+ "w": "200"
114
+ },
115
+ {
116
+ "el": "v-text-field",
117
+ "col": { "class": "shrink" },
118
+ "attr": {
119
+ "class": "flex-md-grow-0",
120
+ "style": "width:200px",
121
+ "v-model": "UserID",
122
+ "label": "User ID",
123
+ "clearable": ""
124
+ },
125
+ "w": "200"
126
+ },
127
+ {
128
+ "el": "f-button",
129
+ "col": { "class": "shrink" },
130
+ "attr": {
131
+ ":checkvalid": false,
132
+ "color": "primary",
133
+ "label": "Tìm",
134
+ "icon-text": "mdi-magnify",
135
+ ":action": { "CALL": "getUser" }
136
+ }
137
+ },
138
+ { "el": "v-spacer", "attr": {} },
139
+ {
140
+ "el": "f-button",
141
+ "col": { "v-if": "vueData.user.SystemRight==9", "class": "shrink" },
142
+ "attr": {
143
+ ":checkvalid": false,
144
+ ":disabled": "!sGroup",
145
+ "color": "primary",
146
+ "label": "Add User",
147
+ "icon-text": "mdi-account-multiple-plus",
148
+ ":action": [{ "addNew": 1, "sUserID": "", "sUserName": "" }, { "dlUserInfo": true }]
149
+ }
150
+ }
151
+ ]
152
+ }
153
+ ```
154
+
155
+ - Filter fields: `w` in px (`>= 25`) + `col: "shrink"`. The `attr: { class: "flex-md-grow-0", style: "width:Xpx" }` pair above is **redundant** — `w` + `shrink` already gives that width (measured: `w:150` + `shrink` → exactly 150px on V2 and V3). It stays only because the example is copied from production; omit it in new code.
156
+ - Search button right after the fields, also `col: "shrink"` (a non-shrink button stretches).
157
+ - **Always `class: "shrink"` / `"col": "shrink"`, never `shrink: true`** — the bare attribute works only on V2; on V3 it's inert and the column goes full width.
158
+ - `v-spacer` is the **only** non-`shrink` column — it must grow to push the following group right.
159
+ - Admin Add/Delete buttons: after the spacer, permission `v-if` — `col: { "v-if": "…", "class": "shrink" }` (object form when extra keys are needed; keep `shrink`).
160
+ - Right-align the whole row without `v-spacer`: `"prop": "row justify-end"`.
161
+
162
+ ---
163
+
164
+ ## 5. Permission gating
165
+
166
+ Show/hide by the user's `SystemRight`.
167
+
168
+ ```json
169
+ "col": { "v-if": "vueData.user.SystemRight>1" }
170
+ "col": { "v-if": "vueData.user.SystemRight==9" }
171
+ ```
172
+
173
+ - `>1` — any user with rights (not the lowest level)
174
+ - `==9` — top admin only
175
+
176
+ ---
177
+
178
+ ## 6. IF/THEN/ELSE
179
+
180
+ Branching inside an action or CALLBACK.
181
+
182
+ ```json
183
+ {
184
+ "IF": "TypeLoad==1",
185
+ "THEN": { "CALL": "getComputer" },
186
+ "ELSE": {
187
+ "IF": "TypeLoad==2",
188
+ "THEN": { "CALL": "getComputerDinied" },
189
+ "ELSE": { "CALL": "getComputerNotVisited" }
190
+ }
191
+ }
192
+ ```
193
+
194
+ - Condition: JS expression using vueData fields directly (no `vueData.` prefix).
195
+ - Nest IF/ELSE for multi-branch switch.
196
+
197
+ Real use: module with several view modes (`TypeLoad`) — after an update, refresh the list currently shown.
198
+
199
+ ```json
200
+ "data": [
201
+ { "TypeLoad": 1 }
202
+ ]
203
+ ```
204
+
205
+ ---
206
+
207
+ ## 7. CALLBACK chaining
208
+
209
+ Array CALLBACK → runs in order.
210
+
211
+ > **Several commands MUST be an array, one `{}` per command.** Merging `CALL` + `MESS` + `EXE` into one object runs only **one** (priority `API` → `CALL` → `EXE` → `FUN` → `IF` → `MESS` → `MESSBOX`, each branch `return`s immediately); the rest are dropped silently — no error, no `[Vue warn]`. Full rule + runtime reason: [controls-patterns.md](controls-patterns.md) §CORE RULE.
212
+
213
+ ```json
214
+ "CALLBACK": [
215
+ { "MESS": "Cập nhật thành công." },
216
+ { "dlUserInfo": false },
217
+ { "CALL": "getUser" }
218
+ ]
219
+ ```
220
+
221
+ ```json
222
+ "CALLBACK": [
223
+ { "CALL": "getUserModule" },
224
+ { "sUserDeptSelected": [], "dlAddUser": false },
225
+ { "MESS": "Thêm user thành công." }
226
+ ]
227
+ ```
228
+
229
+ Usual order: reload data → reset state → show message.
230
+
231
+ ---
232
+
233
+ ## 8. `set` — module-level config
234
+
235
+ `set` at module.json root configures the whole module.
236
+
237
+ ```json
238
+ "set": {
239
+ "menu": false
240
+ }
241
+ ```
242
+
243
+ - `menu: false` — hide navigation menu (sub-windows, log viewers).
244
+
245
+ > Module opened via `openWindow`: declare `"set": { "menu": false }` in the child module's module.json — **don't** pass `?menu=0` in the URL. Full pattern (incl. setting it dynamically from `data[]`): [ui-crosswindow-patterns.md](ui-crosswindow-patterns.md).
246
+
247
+ ---
248
+
249
+ ## 9. CALLBACK with JS expression
250
+
251
+ `{{ expression }}` in CALLBACK runs complex code after the API.
252
+
253
+ ```json
254
+ "CALLBACK": "{{ _.forEach(dsUsers, (x)=> x.FunctionName = JSON.parse(x.FunctionName)) }}"
255
+ ```
256
+
257
+ ```json
258
+ "CALLBACK": "{{ _.forEach(dsUserFunction, (x)=> x.FunctionName = JSON.parse(x.FunctionName)), _.forEach(dsUserFunction, (x)=> x.UserSysItem = JSON.parse(x.UserSysItem)) }}"
259
+ ```
260
+
261
+ > Use when the API returns a JSON string that must be parsed to object/array before render.
262
+
263
+ ---
264
+
265
+ ## 10. script.js — helpers
266
+
267
+ `script.js` holds helpers called from `v-on:click` or actions. They access `vueData` directly; use for logic plain action JSON can't express.
268
+
269
+ ```javascript
270
+ // script.js
271
+ function setFunction(user, fc) {
272
+ vueData.sFunctionSelected = [];
273
+ vueData.sUserSelected = [];
274
+ vueData.sUserSelected.push(user);
275
+ if (!fc) return;
276
+ vueData.dsFunctionRight.map((item) => {
277
+ if (fc.some((fci) => fci.FunctionID == item.FunctionID)) {
278
+ vueData.sFunctionSelected.push(item.FunctionID.toString());
279
+ }
280
+ });
281
+ }
282
+ ```
283
+
284
+ Called from `v-on:click` in a header:
285
+
286
+ ```json
287
+ "v-on:click": "vueData.dlFunction = true; CALL(setFunction(item, item.FunctionName));"
288
+ ```
289
+
290
+ ---
291
+
292
+ ## UI design checklist
293
+
294
+ > `module.json` structure checklist (`data[]` order, grid wrapper, `w`, `v-on:`, `set`, valid JSON) is in [module-json-anatomy.md](module-json-anatomy.md). Below is UI-specific only.
295
+
296
+ 1. **Toolbar row**: shrink filters + v-spacer + permission-gated buttons
297
+ 2. **f-table**: fixed-header, item-class for row highlight, correct item-key — [ui-table-cell-patterns.md](ui-table-cell-patterns.md)
298
+ 3. **Dialogs**: always in hidden-container, v-card[padding:0] + v-toolbar[dense flat] — [ui-dialog-patterns.md](ui-dialog-patterns.md)
299
+ 4. **menuChucNang**: define in data[], reference via t-menu in headers — [ui-table-cell-patterns.md](ui-table-cell-patterns.md)
300
+ 5. **ctrl-update**: simple CRUD; manual dialog for complex forms
301
+ 6. **CALLBACK**: chaining array — reload → reset state → MESS
302
+ 7. **#PARENT**: set at the top of data[] before startup CALLs — [ui-crosswindow-patterns.md](ui-crosswindow-patterns.md)
303
+ 8. **Permission**: `v-if: "vueData.user.SystemRight>1"` for admin-only sections
@@ -0,0 +1,95 @@
1
+ # UI review from screenshots
2
+
3
+ > Owns: **reading and assessing UI screenshots (user-sent or from `--render`)**.
4
+
5
+ Sources: (1) **user screenshot in chat** ("why is it ugly", "make it nicer", or an image + bug description); (2) **`fui module simulate --render`** — `render.png` (inline), `render-full.png`, `render-dialog.jpg`.
6
+
7
+ Act as design QA: scan with the checklist; map every finding to a **concrete code cause** (controls[i] in module.json, class/style, CSS in header.html) + fix. No vague "doesn't look good".
8
+
9
+ **MANDATORY — FIX findings, don't just report them.** Stuck/overlapping text (too little gap), misaligned controls, abnormal whitespace between controls are bugs **to fix in the same turn**, like hard rendering bugs. "Noted in the report" ≠ done. Ask the user only when design intent is unclear (e.g. should two blocks be on separate rows); otherwise fix and re-render (step 4).
10
+
11
+ ---
12
+
13
+ ## Workflow
14
+
15
+ 1. **Establish context** first:
16
+ - App-mode (CRUD/admin — aesthetic standard `fui design read fui`) or Web-mode (landing — chosen brand DESIGN.md)?
17
+ - Desktop or mobile? (narrow/portrait image → judge by responsive standards)
18
+ - module.json in workspace? Yes → cross-check image ↔ controls to pinpoint the fix; no → comment on the image, ask for module.json if a fix is needed
19
+ 2. **Scan with checklist A→D**, record each finding.
20
+ 3. **Report as a table**: `Finding | Severity (bug/should fix/suggestion) | Location in image | Likely cause | Proposed fix`.
21
+ 4. **Fix → re-render → compare** (required for every A/B finding): with workspace + Playwright, edit module.json, run `fui module simulate --render` (same viewport/device as the original if guessable), compare with the user's image; repeat until clean. `--render` auto-detects stuck/overlapping text (bounding rects, see A) and prints it in DOM AUDIT with "PHẢI sửa, không được bỏ qua" — fix it this turn; don't hand it to the user.
22
+
23
+ Blurry/cropped/insufficient image → ask the user to recapture the specific area; don't guess.
24
+
25
+ ---
26
+
27
+ ## Scan checklist
28
+
29
+ ### A. Hard rendering bugs (must fix)
30
+
31
+ - Abnormal empty area: component didn't render (empty app, blank card, white/0-size chart)
32
+ - Leaked error text: `undefined`, `NaN`, `[object Object]`, exception messages, keys instead of values
33
+ - Horizontal overflow: horizontal scrollbar, elements clipped at the right edge
34
+ - Overlapping text/elements, or so close they read as one word; unintended truncation (`...`). `--render` catches most (line `⚠ text dính/chồng lấn` in DOM AUDIT) — only a safety net; still scan by eye, the heuristic misses non-text cases (icon over button, image over card...)
35
+ - Broken image (empty frame/broken icon), missing icon (tofu square — usually mdi font not loaded or wrong icon name)
36
+ - Spinner forever (v_Loading never returns to false — usually a hung API/init error)
37
+
38
+ ### B. Layout, alignment, spacing
39
+
40
+ - **Alignment**: fields in a row share baseline and height (consistent `dense`/`outlined`); cards/blocks in a row share height
41
+ - **Semantic alignment**: text left; **numbers/currency in tables right** (`t-num`); column header matches data alignment
42
+ - **Button placement**: page primary action in the toolbar on the right; in a dialog the primary button (Lưu/Đồng ý) sits **right** in `v-card-actions` (pushed by `v-spacer`); no redundant "Đóng" button (D7)
43
+ - **Toolbar row — bloated buttons / equal gaps** = columns missing `shrink`: buttons much wider than their text, **equal** gaps, trailing group **not flush right** despite `v-spacer` ⇒ columns still `flex-grow: 1`, sharing free space like the spacer. Standard: **every column `"col": "shrink"` except `v-spacer`** — including empty-`w` columns ([controls-patterns.md](controls-patterns.md) §Toolbar row). `fui module validate` catches this before any screenshot
44
+ - **Consistent spacing**: Vuetify rhythm (multiples of 4px — pa-2/ma-2...); no abnormally large gaps between blocks, no blocks touching the screen edge
45
+ - **Data tables**: scroll ONLY inside the table (main table declares `":height": -16`), no two nested vertical page scrollbars, table header stays visible on scroll (D9)
46
+ - **Grid balance**: sensible column ratios in a row (Σw ≤ 12), no column squeezed so narrow content wraps badly
47
+
48
+ ### C. Color & visual hierarchy (App-mode vs `fui design read fui`)
49
+
50
+ - Validator rules D1–D9: dialog toolbar **same background** as body (no color/dark); delete button **warning orange** (not red/error); dialog width in 400/550/600/650/800
51
+ - 1–2 accent colors per screen; no arbitrary color on cards/secondary buttons
52
+ - Readable contrast (light grey on white, text on colored backgrounds — report if doubtful)
53
+ - Status (chip/badge) semantic colors consistent: success/warning/error mean the same everywhere
54
+
55
+ ### D. Density & responsive
56
+
57
+ - Consistent `dense`/compact level across the screen (no dense-here, sparse-there)
58
+ - Mobile image: columns wrap correctly, buttons don't overflow and aren't too small to tap, readable font, tables have a mobile plan (`mobile-breakpoint`)
59
+ - `viewport-fit=cover` (FUI default): content at top/bottom edges on notched screens → needs safe-area padding
60
+
61
+ ---
62
+
63
+ ## Symptom → common cause
64
+
65
+ | Symptom | Common cause | Fix |
66
+ | ----------------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
67
+ | Page has vertical scroll, endless table | f-table/f-sheet without `:height` → grows with row count | Add `":height": -16` (measures `rect.top`, fills to bottom) |
68
+ | Table short/overflowing by a fixed amount | Old hand-computed `:height` `"$( window ).height()-N"` doesn't match the real toolbar | Replace with `":height": -16` |
69
+ | Form fields of uneven height | Mixed with/without `dense`, `hide-details` | Use `outlined dense hide-details` across the form |
70
+ | Numbers left-aligned in table | Column missing `el: "t-num"` / align | See `component-table.md` |
71
+ | Blank chart | Container 0-height at chart init | Give the container a concrete `:height`; `--render` audit "canvas 0-size" confirms |
72
+ | Square icon | Wrong `mdi-*` name or font not loaded | Check icon name; import materialdesignicons |
73
+ | Dialog overflowing / tiny | Non-standard width | Widths 400/550/600/650/800 (D3) |
74
+ | Primary button left in dialog | Missing `v-spacer` in v-card-actions | Add v-spacer before the primary button |
75
+ | Block touching screen edge | Missing page-container / pa-* | See `ui-layout-patterns.md` |
76
+
77
+ ---
78
+
79
+ ## FUI default meta viewport (affects UI)
80
+
81
+ FUI pages already include in `<head>` (do NOT repeat in header.html):
82
+
83
+ ```html
84
+ <meta charset="UTF-8" />
85
+ <meta
86
+ name="viewport"
87
+ content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
88
+ />
89
+ ```
90
+
91
+ Consequences:
92
+
93
+ - `user-scalable=no, maximum-scale=1` — **no pinch-zoom** → fonts/buttons must be large enough on mobile from the start.
94
+ - `width=device-width, initial-scale=1` — layout in true device CSS pixels; Vuetify breakpoints work normally.
95
+ - `viewport-fit=cover` — page extends into iOS notch/home-indicator → content at top/bottom edges needs `env(safe-area-inset-*)` padding for fullscreen/HTMLOnly.