jattac.libs.web.zest-button 1.2.9 → 1.4.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.
@@ -0,0 +1,455 @@
1
+ # Architectural Rules
2
+
3
+ Business behaviour is more important than implementation.
4
+
5
+ ---
6
+
7
+ # Composition
8
+
9
+ Composition SHALL be preferred over modification of existing code.
10
+
11
+ Decision process:
12
+
13
+ Can composition reasonably solve the problem?
14
+
15
+ YES
16
+
17
+ Use composition.
18
+
19
+ NO
20
+
21
+ Continue.
22
+
23
+ Does existing code violate business behaviour?
24
+
25
+ YES
26
+
27
+ Modify existing code.
28
+
29
+ NO
30
+
31
+ Continue.
32
+
33
+ Would composition become unreasonable?
34
+
35
+ YES
36
+
37
+ Modify existing code.
38
+
39
+ NO
40
+
41
+ Use composition.
42
+
43
+ When composition is chosen, the new code MUST NOT alter the behaviour of existing code.
44
+
45
+ Composition means addition, not modification.
46
+
47
+ ---
48
+
49
+ # Existing Code
50
+
51
+ Existing code SHALL NOT be rewritten merely because another implementation is preferred.
52
+
53
+ Do not replace architecture.
54
+
55
+ Do not modernise code.
56
+
57
+ Do not rename symbols.
58
+
59
+ Do not move files.
60
+
61
+ Do not reformat unrelated code.
62
+
63
+ Do not optimise speculative bottlenecks.
64
+
65
+ ---
66
+
67
+ # Behaviour
68
+
69
+ Existing behaviour is assumed correct unless:
70
+
71
+ • failing tests prove otherwise
72
+
73
+ OR
74
+
75
+ • user explicitly requests behavioural change
76
+
77
+ ---
78
+
79
+ # Scope
80
+
81
+ Only modify code necessary to complete the requested task.
82
+
83
+ Never perform opportunistic refactors.
84
+
85
+ ---
86
+
87
+ # Simplicity
88
+
89
+ Choose the smallest correct change.
90
+
91
+ Smaller changes reduce regression risk.
92
+
93
+ ---
94
+
95
+ # Unknowns
96
+
97
+ When uncertain:
98
+
99
+ Stop.
100
+
101
+ Explain uncertainty.
102
+
103
+ Never guess business rules.
104
+
105
+ ---
106
+
107
+ # Absolute Prohibitions
108
+
109
+ The following MUST NOT be done unless explicitly instructed by the user.
110
+
111
+ ## Dependencies
112
+
113
+ Do NOT change package versions, SDK versions, or tool versions.
114
+
115
+ Do NOT add NuGet packages, npm packages, pip packages, Cargo crates, Go modules, or any other dependencies.
116
+
117
+ Do NOT remove dependencies.
118
+
119
+ ## Configuration
120
+
121
+ Do NOT modify configuration files (appsettings.json, .env, docker-compose.yml, app.config, web.config, or any other config).
122
+
123
+ Do NOT modify environment variables.
124
+
125
+ Do NOT modify secrets or secret references.
126
+
127
+ ## CI/CD and Infrastructure
128
+
129
+ Do NOT modify CI/CD pipelines, build scripts, or deployment configurations.
130
+
131
+ Do NOT modify GitHub Actions, Azure DevOps pipelines, or any other CI configuration.
132
+
133
+ Do NOT modify Docker files or container configurations.
134
+
135
+ Do NOT modify infrastructure-as-code files.
136
+
137
+ ## Database
138
+
139
+ Do NOT modify database migrations.
140
+
141
+ Do NOT modify database schema.
142
+
143
+ Do NOT modify stored procedures.
144
+
145
+ Do NOT modify database scripts.
146
+
147
+ Do NOT create new database tables or columns.
148
+
149
+ ## Code Changes
150
+
151
+ Do NOT add comments unless explicitly instructed.
152
+
153
+ Do NOT add logging unless explicitly instructed.
154
+
155
+ Do NOT add error handling (try-catch, null checks, defensive code) unless explicitly instructed.
156
+
157
+ Do NOT add TODO comments.
158
+
159
+ Do NOT add comments about code that needs improvement.
160
+
161
+ Do NOT change method signatures, return types, or public API surface.
162
+
163
+ Do NOT change access modifiers (private to public, etc.).
164
+
165
+ Do NOT change synchronous code to asynchronous.
166
+
167
+ Do NOT add async/await where it was not present.
168
+
169
+ Do NOT add ConfigureAwait(false).
170
+
171
+ Do NOT add null-forgiving operators (!) to silence warnings.
172
+
173
+ Do NOT add #pragma disable to silence warnings.
174
+
175
+ Do NOT add SuppressMessage attributes.
176
+
177
+ Do NOT add Obsolete attributes.
178
+
179
+ Do NOT add EditorBrowsable(Never).
180
+
181
+ Do NOT add InternalsVisibleTo for test projects.
182
+
183
+ Do NOT add blanket try-catch blocks.
184
+
185
+ Do NOT swallow exceptions.
186
+
187
+ Do NOT change exception types.
188
+
189
+ Do NOT change return values.
190
+
191
+ ## Formatting and Naming
192
+
193
+ Do NOT rename variables, methods, classes, or any other symbols.
194
+
195
+ Do NOT reorder methods, properties, fields, or any other members.
196
+
197
+ Do NOT reformat code that was not part of the request.
198
+
199
+ Do NOT change whitespace or line breaks in unrelated code.
200
+
201
+ Do NOT change using directives or import statements.
202
+
203
+ Do NOT change namespace declarations.
204
+
205
+ ---
206
+
207
+ # Change Justification
208
+
209
+ Every change you make MUST be directly traceable to a specific requirement in the user's request.
210
+
211
+ If you cannot draw a direct line from the user's words to the change, do not make the change.
212
+
213
+ If you are unsure whether a change is required, ask the user.
214
+
215
+ ---
216
+
217
+ # Rollback
218
+
219
+ Every change MUST be independently revertible.
220
+
221
+ Do not create changes that depend on each other unless they are part of the same logical unit.
222
+
223
+ If a change cannot be independently reverted, document this in the change manifest.
224
+
225
+ ---
226
+
227
+ # Project-Specific Architecture — Lattice Admin Web
228
+
229
+ These are the concrete conventions of this repository. They are the "pattern discovery" reference required by AI_WORKFLOW.md Step 8 — match new code to these before inventing anything new.
230
+
231
+ ## Stack
232
+
233
+ Next.js (App Router), TypeScript, React class components for stateful pages.
234
+
235
+ ## Project layout
236
+
237
+ Features live under `app/admin/` (admin-only) or `app/` (shared). Each feature contains:
238
+ - `page.tsx` — Next.js route entry point
239
+ - `Data/IModel.ts` — TypeScript interface mirroring the backend model
240
+ - `Data/ModelApiAccessor.ts` — HTTP calls to the backend
241
+ - `State/ModelRepository.ts` — extends `RepositoryBase`, holds reactive state
242
+ - `State/ModelLogic.ts` — extends `LogicBase`, contains all business logic
243
+ - `UI/ModelManager.tsx` — the page component
244
+
245
+ ## Page entry pattern
246
+
247
+ ```tsx
248
+ "use client";
249
+ import ManagerAccount from "@/app/account/UI/ManagerAccount";
250
+ import FooManager from "./UI/FooManager";
251
+
252
+ export default function FooPage() {
253
+ return (
254
+ <ManagerAccount>
255
+ <FooManager />
256
+ </ManagerAccount>
257
+ );
258
+ }
259
+ ```
260
+
261
+ ## State management
262
+
263
+ ```ts
264
+ // Repository — holds only state, no logic
265
+ export default class FooRepository extends RepositoryBase {
266
+ items: IFoo[] = [];
267
+ }
268
+
269
+ // Logic — all async operations go here
270
+ const logic = new FooLogic(); // instantiated at module level, outside the component
271
+
272
+ export default class FooLogic extends LogicBase<FooRepository, IFoo> {
273
+ repository = new FooRepository();
274
+ model = {} as IFoo;
275
+
276
+ async initializeAsync() {
277
+ await this.runner(async () => {
278
+ this.repository.items = await new FooApiAccessor().getAllAsync();
279
+ });
280
+ }
281
+ }
282
+ ```
283
+ - `runner()` handles the `busy` flag automatically — always use it for async operations
284
+ - Logic is instantiated at module level (outside the component class), not inside it
285
+
286
+ ## Components
287
+
288
+ - Stateful pages use `PureComponent` class components with `logic.setRerender(() => this.forceUpdate())` in `componentDidMount`
289
+ - `componentDidMount` must call `logic.initializeAsync()`
290
+ - Use `FrostedGlassOverlay show={logic.repository.busy}` to block interaction during loads
291
+ - Use `PageContainer title="..." subtitle="..."` as the top-level wrapper
292
+
293
+ ## API accessors
294
+
295
+ ```ts
296
+ export default class FooApiAccessor extends ApiAccessor {
297
+ constructor() {
298
+ super({ controller: "foo", backendService: "fua" }); // or "lattice"
299
+ }
300
+
301
+ async getAllAsync(): Promise<IFoo[]> {
302
+ return await this.getUnwrappedAsync<IFoo[]>({ url: "items" });
303
+ }
304
+
305
+ async createAsync(payload: Omit<IFoo, "id">): Promise<void> {
306
+ await this.postWithUnwrappedResponseAsync({ url: "items", body: payload });
307
+ }
308
+ }
309
+ ```
310
+ - `fetchData` supports only `"GET"` and `"POST"` — there is no DELETE method
311
+ - Delete operations must POST to a delete endpoint: `postWithUnwrappedResponseAsync({ url: "items/delete/${id}", body: {} })`
312
+ - URL segments must match the backend controller route exactly (controller class name minus "Controller", lowercase)
313
+
314
+ ## UI components
315
+
316
+ | Need | Component |
317
+ |---|---|
318
+ | Page layout | `PageContainer` with `title` and `subtitle` |
319
+ | Sidepane layout | `ZestResponsiveLayout` with `sidePane` prop |
320
+ | Buttons | `ZestButton` with `visualOptions={{ variant: "success" \| "danger" \| "info" }}` |
321
+ | Text inputs | `ZestTextbox` — use `isMultiline` + `rows` for textareas, `stretch` for full width |
322
+ | Dropdowns | `SelectWrapper` — always use this, never a raw `<select>` |
323
+ | Forms | See form system section below |
324
+ | Tables | `ResponsiveTable` with `data` and `columnDefinitions` (each with `cellRenderer` + `displayLabel`) |
325
+ | Action rows | `ActionBar` |
326
+ | Loading overlay | `FrostedGlassOverlay show={logic.repository.busy}` |
327
+
328
+ ## Form system
329
+
330
+ All forms use the component system from `@/app/Forms/Form/UI/Form`. Never style inputs or form layouts with raw inline styles.
331
+
332
+ ### Component hierarchy
333
+
334
+ ```
335
+ Form
336
+ FormSection? ← optional named section (title + subtle background, good for long standalone forms)
337
+ FormControlGroup? ← optional titled group (dashed border header, good for sidepane sections)
338
+ FormRow ← horizontal flex row, wraps on small screens
339
+ FormControlGroup ← one input+label unit; min-width 250px so it auto-wraps when space runs out
340
+ FormLabel ← shows label + required asterisk by default
341
+ {input} ← ZestTextbox, SelectWrapper, checkbox etc.
342
+ ```
343
+
344
+ ### Rules
345
+
346
+ **Always use `FormRow` to place related fields side by side.** Never stack items that logically belong together by leaving them as sequential block elements — use `FormRow` so they share a row when space allows and wrap gracefully on narrow screens.
347
+
348
+ **Every input must be wrapped in `FormControlGroup > FormLabel + input`.** No bare inputs, no bare labels.
349
+
350
+ **Mark optional fields.** `FormLabel` adds `*` by default (required). Add `optional` prop for non-required fields:
351
+ ```tsx
352
+ <FormLabel optional>Description</FormLabel>
353
+ ```
354
+
355
+ **Use `FormControlGroup title="..."` to group related fields in a sidepane.** This renders a dashed section header — use it instead of `FormSection` inside sidepanes where vertical space is at a premium:
356
+ ```tsx
357
+ <FormControlGroup title="Trigger Settings">
358
+ <FormRow>
359
+ <FormControlGroup>...</FormControlGroup>
360
+ <FormControlGroup>...</FormControlGroup>
361
+ </FormRow>
362
+ </FormControlGroup>
363
+ ```
364
+
365
+ **Use `FormSection` for standalone full-page forms** where sections need more visual weight (title + `#f9fafb` background panel).
366
+
367
+ **Use `SelectWrapper` for all dropdowns**, including static option lists:
368
+ ```tsx
369
+ const RISK_LEVELS = [
370
+ { value: "High", label: "High" },
371
+ { value: "Critical", label: "Critical" },
372
+ ];
373
+
374
+ <SelectWrapper
375
+ data={RISK_LEVELS}
376
+ selectedResolver={(r) => r.value === item.riskLevel}
377
+ valueResolver={(r) => r.value}
378
+ labelResolver={(r) => r.label}
379
+ onChange={(selected) => update({ riskLevel: selected[0]?.value ?? "High" })}
380
+ isClearable={false}
381
+ isSearchable={false}
382
+ />
383
+ ```
384
+
385
+ **Full-width fields** (long text, description, multiline) go in a standalone `FormControlGroup` outside a `FormRow` — they naturally fill 100% width. Use `stretch` on `ZestTextbox`:
386
+ ```tsx
387
+ <FormControlGroup>
388
+ <FormLabel optional>Description</FormLabel>
389
+ <ZestTextbox type="text" stretch value={...} onChange={...} />
390
+ </FormControlGroup>
391
+ ```
392
+
393
+ **Number inputs** go in `FormRow` with their peers — never alone as a block element:
394
+ ```tsx
395
+ <FormRow>
396
+ <FormControlGroup>
397
+ <FormLabel>Threshold (events)</FormLabel>
398
+ <ZestTextbox type="number" value={...} onChange={...} />
399
+ </FormControlGroup>
400
+ <FormControlGroup>
401
+ <FormLabel>Window (days)</FormLabel>
402
+ <ZestTextbox type="number" value={...} onChange={...} />
403
+ </FormControlGroup>
404
+ </FormRow>
405
+ ```
406
+
407
+ **Checkboxes** — no styled component exists; use native `<input type="checkbox">` inside `FormControlGroup`:
408
+ ```tsx
409
+ <FormControlGroup>
410
+ <FormLabel>Enabled</FormLabel>
411
+ <input
412
+ type="checkbox"
413
+ checked={item.isEnabled}
414
+ onChange={(e) => update({ isEnabled: e.target.checked })}
415
+ style={{ width: 16, height: 16, marginTop: 6 }}
416
+ />
417
+ </FormControlGroup>
418
+ ```
419
+
420
+ ### Sidepane form reference layout
421
+
422
+ For a sidepane create/edit form, structure fields in this order:
423
+ 1. Identity fields (name, code, key identifier) — `FormRow` with type/category/risk selector
424
+ 2. Optional descriptive fields — standalone full-width `FormControlGroup`
425
+ 3. Related configuration fields — `FormControlGroup title="..."` wrapping a `FormRow`
426
+ 4. Long text fields (messages, notes) — `FormControlGroup title="..."` wrapping individual full-width fields
427
+ 5. Toggle/boolean fields — `FormRow` of checkboxes
428
+ 6. Help/instruction panel — `<div>` with `background: #f9fafb`, 11px text, business-language explanations
429
+
430
+ ## Sidepane pattern
431
+
432
+ ```tsx
433
+ <ZestResponsiveLayout
434
+ sidePane={{
435
+ visible: !!this.state.itemForSidePane,
436
+ title: this.#isEditing ? `Edit — ${item.name}` : "Add Item",
437
+ pane: this.#sidePaneContent ?? <></>,
438
+ onClose: () => this.setState({ itemForSidePane: undefined }),
439
+ }}
440
+ desktopSidePaneWidth="500px"
441
+ enableBounceAnimation={false}
442
+ detailPane={<>...table and action bar...</>}
443
+ />
444
+ ```
445
+ - Never use modals for create/edit forms — always use the sidepane
446
+ - Sidepane title adapts: "Add X" for new, "Edit — {name}" for existing
447
+
448
+ ## Navigation
449
+
450
+ New admin pages must be registered in `app/account/UI/MenuItems/AdminMenuItemsProvider.tsx` under the correct parent group.
451
+
452
+ ## Routing
453
+
454
+ - Admin pages: `app/admin/[feature]/page.tsx`
455
+ - Route segments must be kebab-case