@resistdesign/voltra 3.0.0-alpha.13 → 3.0.0-alpha.15

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,203 @@
1
+ /**
2
+ * @packageDocumentation
3
+ *
4
+ * Shared EasyLayout core types.
5
+ */
6
+ /**
7
+ * Raw layout template input.
8
+ */
9
+ type EasyLayoutTemplate = string;
10
+ /**
11
+ * Supported track units.
12
+ */
13
+ type TrackUnit = {
14
+ kind: "fr";
15
+ value: number;
16
+ } | {
17
+ kind: "px";
18
+ value: number;
19
+ } | {
20
+ kind: "pct";
21
+ value: number;
22
+ };
23
+ /**
24
+ * Track specification for rows/columns.
25
+ */
26
+ type TrackSpec = TrackUnit;
27
+ /**
28
+ * Parsed layout representation shared by web/native implementations.
29
+ */
30
+ type EasyLayoutParsed = {
31
+ areaGrid: string[][];
32
+ rowTracks: TrackSpec[];
33
+ colTracks: TrackSpec[];
34
+ areaNames: string[];
35
+ };
36
+ /**
37
+ * 1-based area bounds in row/column space.
38
+ */
39
+ type AreaBounds = {
40
+ name: string;
41
+ rowStart: number;
42
+ rowEnd: number;
43
+ colStart: number;
44
+ colEnd: number;
45
+ };
46
+ /**
47
+ * Shared core payload for parsed template + computed bounds.
48
+ */
49
+ type EasyLayoutCore = {
50
+ parsed: EasyLayoutParsed;
51
+ bounds: Record<string, AreaBounds>;
52
+ };
53
+
54
+ /**
55
+ * @packageDocumentation
56
+ *
57
+ * Platform-agnostic history state machine and path helpers.
58
+ */
59
+ /**
60
+ * A normalized location entry in history.
61
+ */
62
+ type HistoryLocation = {
63
+ /**
64
+ * Normalized pathname. Always starts with `/`.
65
+ */
66
+ path: string;
67
+ /**
68
+ * Optional query string (including leading `?`).
69
+ */
70
+ search?: string;
71
+ /**
72
+ * Optional hash fragment (including leading `#`).
73
+ */
74
+ hash?: string;
75
+ /**
76
+ * Optional app-defined state payload.
77
+ */
78
+ state?: unknown;
79
+ /**
80
+ * Stable entry key, unique per history entry.
81
+ */
82
+ key: string;
83
+ };
84
+ /**
85
+ * An entry inside the history stack.
86
+ */
87
+ type HistoryEntry = {
88
+ location: HistoryLocation;
89
+ };
90
+ /**
91
+ * History listener callback.
92
+ */
93
+ type HistoryListener = (location: HistoryLocation) => void;
94
+ /**
95
+ * Path parts extracted from a route string.
96
+ */
97
+ type HistoryPathParts = Pick<HistoryLocation, "path" | "search" | "hash">;
98
+ /**
99
+ * Shared history controller interface.
100
+ *
101
+ * This is intentionally platform-agnostic. Web/native adapters can translate
102
+ * platform events into calls on this interface.
103
+ */
104
+ type HistoryController = {
105
+ /**
106
+ * Current location.
107
+ */
108
+ location: HistoryLocation;
109
+ /**
110
+ * Number of entries in the history stack.
111
+ */
112
+ length: number;
113
+ /**
114
+ * Active entry index in the history stack.
115
+ */
116
+ index: number;
117
+ /**
118
+ * Push a new history entry.
119
+ *
120
+ * `replaceSearch` controls behavior when `path` does not include `?query`:
121
+ * - `false`/unset: carry forward current `search`.
122
+ * - `true`: clear current `search`.
123
+ */
124
+ push: (path: string, opts?: {
125
+ state?: unknown;
126
+ replaceSearch?: boolean;
127
+ }) => void;
128
+ /**
129
+ * Replace the current history entry.
130
+ *
131
+ * `replaceSearch` follows the same behavior as {@link push}.
132
+ */
133
+ replace: (path: string, opts?: {
134
+ state?: unknown;
135
+ replaceSearch?: boolean;
136
+ }) => void;
137
+ /**
138
+ * Move by delta within the history stack.
139
+ */
140
+ go: (delta: number) => void;
141
+ /**
142
+ * Move back one entry.
143
+ */
144
+ back: () => void;
145
+ /**
146
+ * Move forward one entry.
147
+ */
148
+ forward: () => void;
149
+ /**
150
+ * Subscribe to location changes.
151
+ */
152
+ listen: (listener: HistoryListener) => () => void;
153
+ };
154
+ /**
155
+ * Parse a path-like value into normalized path/search/hash parts.
156
+ *
157
+ * Supports full URLs and path-only strings.
158
+ *
159
+ * Examples:
160
+ * ```ts
161
+ * parseHistoryPath("voltra://host/books/42?tab=info#top")
162
+ * // { path: "/books/42", search: "?tab=info", hash: "#top" }
163
+ *
164
+ * parseHistoryPath("books/42")
165
+ * // { path: "/books/42" }
166
+ * ```
167
+ */
168
+ declare const parseHistoryPath: (inputPath: string) => HistoryPathParts;
169
+ /**
170
+ * Build a path string from normalized path/search/hash parts.
171
+ *
172
+ * Missing prefixes are normalized:
173
+ * - `search: "q=1"` -> `?q=1`
174
+ * - `hash: "section"` -> `#section`
175
+ *
176
+ * Example:
177
+ * ```ts
178
+ * buildHistoryPath({ path: "books/42", search: "tab=info" })
179
+ * // "/books/42?tab=info"
180
+ * ```
181
+ */
182
+ declare const buildHistoryPath: ({ path, search, hash, }: HistoryPathParts) => string;
183
+ /**
184
+ * Create an in-memory history implementation.
185
+ *
186
+ * @param initialPath - Initial path used for the first entry.
187
+ * @returns History controller backed by an in-memory stack.
188
+ *
189
+ * Behavior notes:
190
+ * - `go(delta)` clamps to valid bounds `[0, length - 1]`.
191
+ * - `push` after `back` drops forward entries.
192
+ * - listeners are called only when active location changes.
193
+ *
194
+ * Example:
195
+ * ```ts
196
+ * const history = createMemoryHistory("/home?tab=all");
197
+ * history.push("/details/42");
198
+ * history.back();
199
+ * ```
200
+ */
201
+ declare const createMemoryHistory: (initialPath?: string) => HistoryController;
202
+
203
+ export { type AreaBounds as A, type EasyLayoutParsed as E, type HistoryController as H, type TrackSpec as T, type EasyLayoutTemplate as a, type EasyLayoutCore as b, type HistoryEntry as c, type HistoryListener as d, type HistoryLocation as e, type HistoryPathParts as f, type TrackUnit as g, buildHistoryPath as h, createMemoryHistory as i, parseHistoryPath as p };
@@ -307,271 +307,4 @@ declare namespace ItemRelationshipInfoTypes {
307
307
  export { type ItemRelationshipInfoTypes_BaseItemRelationshipInfo as BaseItemRelationshipInfo, type ItemRelationshipInfoTypes_ItemRelationshipDestinationItemInfo as ItemRelationshipDestinationItemInfo, type ItemRelationshipInfoTypes_ItemRelationshipInfo as ItemRelationshipInfo, ItemRelationshipInfoTypes_ItemRelationshipInfoIdentifyingKeys as ItemRelationshipInfoIdentifyingKeys, ItemRelationshipInfoTypes_ItemRelationshipInfoKeys as ItemRelationshipInfoKeys, type ItemRelationshipInfoTypes_ItemRelationshipInfoType as ItemRelationshipInfoType, type ItemRelationshipInfoTypes_ItemRelationshipOriginInfo as ItemRelationshipOriginInfo, type ItemRelationshipInfoTypes_ItemRelationshipOriginItemInfo as ItemRelationshipOriginItemInfo, type ItemRelationshipInfoTypes_ItemRelationshipOriginatingItemInfo as ItemRelationshipOriginatingItemInfo };
308
308
  }
309
309
 
310
- /**
311
- * Search-related types used by list/search APIs.
312
- */
313
-
314
- /**
315
- * The logical operators for a search criteria.
316
- * */
317
- declare enum LogicalOperators {
318
- /**
319
- * Require all criteria to match.
320
- * */
321
- AND = "AND",
322
- /**
323
- * Require any criteria to match.
324
- * */
325
- OR = "OR"
326
- }
327
- /**
328
- * The comparison operators for a field criterion.
329
- * */
330
- declare enum ComparisonOperators {
331
- /**
332
- * Field value equals the criterion value.
333
- * */
334
- EQUALS = "EQUALS",
335
- /**
336
- * Field value does not equal the criterion value.
337
- * */
338
- NOT_EQUALS = "NOT_EQUALS",
339
- /**
340
- * Field value is greater than the criterion value.
341
- * */
342
- GREATER_THAN = "GREATER_THAN",
343
- /**
344
- * Field value is greater than or equal to the criterion value.
345
- * */
346
- GREATER_THAN_OR_EQUAL = "GREATER_THAN_OR_EQUAL",
347
- /**
348
- * Field value is less than the criterion value.
349
- * */
350
- LESS_THAN = "LESS_THAN",
351
- /**
352
- * Field value is less than or equal to the criterion value.
353
- * */
354
- LESS_THAN_OR_EQUAL = "LESS_THAN_OR_EQUAL",
355
- /**
356
- * Field value is in the provided options.
357
- * */
358
- IN = "IN",
359
- /**
360
- * Field value is not in the provided options.
361
- * */
362
- NOT_IN = "NOT_IN",
363
- /**
364
- * Field value contains the criterion value as a substring.
365
- * */
366
- LIKE = "LIKE",
367
- /**
368
- * Field value does not contain the criterion value as a substring.
369
- * */
370
- NOT_LIKE = "NOT_LIKE",
371
- /**
372
- * Field value exists and is not null.
373
- * */
374
- EXISTS = "EXISTS",
375
- /**
376
- * Field value is missing or null.
377
- * */
378
- NOT_EXISTS = "NOT_EXISTS",
379
- /**
380
- * Field value is not empty.
381
- * */
382
- IS_NOT_EMPTY = "IS_NOT_EMPTY",
383
- /**
384
- * Field value is empty.
385
- * */
386
- IS_EMPTY = "IS_EMPTY",
387
- /**
388
- * Field value is within an inclusive range.
389
- * */
390
- BETWEEN = "BETWEEN",
391
- /**
392
- * Field value is outside an inclusive range.
393
- * */
394
- NOT_BETWEEN = "NOT_BETWEEN",
395
- /**
396
- * Field value array contains the criterion value.
397
- * */
398
- CONTAINS = "CONTAINS",
399
- /**
400
- * Field value array does not contain the criterion value.
401
- * */
402
- NOT_CONTAINS = "NOT_CONTAINS",
403
- /**
404
- * Field value starts with the criterion value.
405
- * */
406
- STARTS_WITH = "STARTS_WITH",
407
- /**
408
- * Field value ends with the criterion value.
409
- * */
410
- ENDS_WITH = "ENDS_WITH",
411
- /**
412
- * Field value does not start with the criterion value.
413
- * */
414
- DOES_NOT_START_WITH = "DOES_NOT_START_WITH",
415
- /**
416
- * Field value does not end with the criterion value.
417
- * */
418
- DOES_NOT_END_WITH = "DOES_NOT_END_WITH"
419
- }
420
- /**
421
- * The field criterion for a search criteria.
422
- * */
423
- type FieldCriterion = {
424
- /**
425
- * Field name to compare.
426
- * */
427
- fieldName: string;
428
- /**
429
- * Comparison operator to apply.
430
- * */
431
- operator?: ComparisonOperators;
432
- /**
433
- * Custom operator label when using backend-specific operators.
434
- * */
435
- customOperator?: string;
436
- /**
437
- * Primary comparison value.
438
- * */
439
- value?: any;
440
- /**
441
- * Comparison value options (e.g., IN/BETWEEN).
442
- * */
443
- valueOptions?: any[];
444
- };
445
- /**
446
- * The criteria for a search.
447
- * */
448
- type SearchCriteria = {
449
- /**
450
- * Logical operator to apply across field criteria.
451
- * */
452
- logicalOperator: LogicalOperators;
453
- /**
454
- * Field-level criteria to evaluate.
455
- * */
456
- fieldCriteria: FieldCriterion[];
457
- };
458
- /**
459
- * The results from a request to list items.
460
- * */
461
- type ListItemsResults<ItemType extends Record<any, any>> = {
462
- /**
463
- * Cursor for paging into the next page, when available.
464
- * */
465
- cursor?: string;
466
- /**
467
- * Items returned by the request.
468
- * */
469
- items: ItemType[];
470
- };
471
- /**
472
- * The information used to sort a list of items by a specified field.
473
- * */
474
- type SortField = {
475
- /**
476
- * Field name to sort by.
477
- * */
478
- field?: string;
479
- /**
480
- * Whether to reverse sort order.
481
- * */
482
- reverse?: boolean;
483
- };
484
- /**
485
- * The data used to page a specific set of search results that uses full paging.
486
- * @see SupportedTags.fullPaging
487
- * */
488
- type StandardExpandedPagingCursor = {
489
- /**
490
- * Current page number.
491
- * */
492
- currentPage?: number;
493
- /**
494
- * Total number of pages available.
495
- * */
496
- totalPages?: number;
497
- };
498
- /**
499
- * The information for paging through a list of items.
500
- * */
501
- type PagingInfo = {
502
- /**
503
- * Items per page to request.
504
- * */
505
- itemsPerPage?: number;
506
- /**
507
- * Cursor token for paging.
508
- * */
509
- cursor?: string;
510
- };
511
- /**
512
- * Configuration for full-text search requests.
513
- * */
514
- type TextSearchConfig = {
515
- /**
516
- * Text query to search for.
517
- * */
518
- query: string;
519
- /**
520
- * Index mode to use when searching.
521
- * */
522
- mode?: "lossy" | "exact";
523
- /**
524
- * Optional index field name.
525
- * */
526
- indexField?: string;
527
- };
528
- /**
529
- * The configuration for listing and searching for items.
530
- * */
531
- type ListItemsConfig = PagingInfo & {
532
- /**
533
- * Structured search criteria.
534
- * */
535
- criteria?: SearchCriteria;
536
- /**
537
- * Sort fields to apply.
538
- * */
539
- sortFields?: SortField[];
540
- /**
541
- * Full-text search configuration.
542
- * */
543
- text?: TextSearchConfig;
544
- };
545
- /**
546
- * A configuration for listing relationships.
547
- * */
548
- type ListRelationshipsConfig = PagingInfo & {
549
- /**
550
- * Relationship origin info used to filter related records.
551
- * */
552
- relationshipItemOrigin: ItemRelationshipOriginItemInfo;
553
- };
554
- /**
555
- * The results from a request to list relationships.
556
- * */
557
- type ListRelationshipsResults = ListItemsResults<ItemRelationshipInfo>;
558
-
559
- type SearchTypes_ComparisonOperators = ComparisonOperators;
560
- declare const SearchTypes_ComparisonOperators: typeof ComparisonOperators;
561
- type SearchTypes_FieldCriterion = FieldCriterion;
562
- type SearchTypes_ListItemsConfig = ListItemsConfig;
563
- type SearchTypes_ListItemsResults<ItemType extends Record<any, any>> = ListItemsResults<ItemType>;
564
- type SearchTypes_ListRelationshipsConfig = ListRelationshipsConfig;
565
- type SearchTypes_ListRelationshipsResults = ListRelationshipsResults;
566
- type SearchTypes_LogicalOperators = LogicalOperators;
567
- declare const SearchTypes_LogicalOperators: typeof LogicalOperators;
568
- type SearchTypes_PagingInfo = PagingInfo;
569
- type SearchTypes_SearchCriteria = SearchCriteria;
570
- type SearchTypes_SortField = SortField;
571
- type SearchTypes_StandardExpandedPagingCursor = StandardExpandedPagingCursor;
572
- type SearchTypes_TextSearchConfig = TextSearchConfig;
573
- declare namespace SearchTypes {
574
- export { SearchTypes_ComparisonOperators as ComparisonOperators, type SearchTypes_FieldCriterion as FieldCriterion, type SearchTypes_ListItemsConfig as ListItemsConfig, type SearchTypes_ListItemsResults as ListItemsResults, type SearchTypes_ListRelationshipsConfig as ListRelationshipsConfig, type SearchTypes_ListRelationshipsResults as ListRelationshipsResults, SearchTypes_LogicalOperators as LogicalOperators, type SearchTypes_PagingInfo as PagingInfo, type SearchTypes_SearchCriteria as SearchCriteria, type SearchTypes_SortField as SortField, type SearchTypes_StandardExpandedPagingCursor as StandardExpandedPagingCursor, type SearchTypes_TextSearchConfig as TextSearchConfig };
575
- }
576
-
577
- export { type BaseItemRelationshipInfo as B, ComparisonOperators as C, type ExpandComplexType as E, type FieldCriterion as F, HelperTypes$1 as H, type ItemRelationshipInfo as I, type ListRelationshipsConfig as L, type SearchCriteria as S, type TypeInfoDataItem as T, type ListItemsResults as a, type ListItemsConfig as b, TypeOperation as c, type TypeKeyword as d, type TypeInfoField as e, type TypeInfoMap as f, type TypeInfo as g, type ItemRelationshipInfoType as h, TypeInfo$1 as i, type SortField as j, ItemRelationshipInfoKeys as k, ItemRelationshipInfoTypes as l, SearchTypes as m, type TypeInfoPack as n, ItemRelationshipInfoIdentifyingKeys as o, type LiteralValue as p, type ItemRelationshipOriginatingItemInfo as q, type ItemRelationshipOriginInfo as r };
310
+ export { type BaseItemRelationshipInfo as B, type ExpandComplexType as E, HelperTypes$1 as H, type ItemRelationshipInfo as I, type LiteralValue as L, type TypeInfoDataItem as T, type TypeInfoPack as a, ItemRelationshipInfoIdentifyingKeys as b, type TypeInfoMap as c, type ItemRelationshipOriginatingItemInfo as d, TypeOperation as e, type TypeInfo as f, type ItemRelationshipInfoType as g, ItemRelationshipInfoKeys as h, type ItemRelationshipOriginInfo as i, type TypeInfoField as j, TypeInfo$1 as k, type TypeKeyword as l, ItemRelationshipInfoTypes as m, type ItemRelationshipOriginItemInfo as n };
package/README.md CHANGED
@@ -18,10 +18,13 @@ yarn add @resistdesign/voltra
18
18
 
19
19
  Prefer the public entrypoints below to keep imports stable and IDE auto-imports clean.
20
20
 
21
+ The root import `@resistdesign/voltra` is intentionally unsupported to avoid
22
+ cross-runtime bundling issues.
23
+
21
24
  Preferred:
22
25
 
23
26
  ```ts
24
- import {IaC} from "@resistdesign/voltra";
27
+ import * as IaC from "@resistdesign/voltra/iac";
25
28
  import {Packs} from "@resistdesign/voltra/iac";
26
29
  import {addDNS} from "@resistdesign/voltra/iac/packs";
27
30
  ```
@@ -34,12 +37,14 @@ import addDNS from "@resistdesign/voltra/iac/packs/dns";
34
37
 
35
38
  Public entrypoints:
36
39
 
37
- - `@resistdesign/voltra`
38
40
  - `@resistdesign/voltra/api`
39
41
  - `@resistdesign/voltra/app`
40
42
  - `@resistdesign/voltra/common`
43
+ - `@resistdesign/voltra/web`
44
+ - `@resistdesign/voltra/native`
41
45
  - `@resistdesign/voltra/iac`
42
46
  - `@resistdesign/voltra/iac/packs`
47
+ - `@resistdesign/voltra/build`
43
48
 
44
49
  ------------
45
50
 
@@ -94,6 +99,194 @@ App features include form generation via TypeInfo-driven AutoForm/AutoField with
94
99
  | ORM: TypeScript Type Driven Auto-generated Data Contexts with Relationships | Form Generation: AutoForm/AutoField + constraints/relations | Typed Build Spec Creation |
95
100
  | | Form Engine: validation, defaults, denied ops, custom type flow | Typed Resource Parameters |
96
101
 
102
+ ## EasyLayout (Web + Native + Core)
103
+
104
+ EasyLayout now has:
105
+
106
+ - Shared core parsing/math in `@resistdesign/voltra/app` (`Utils.parseTemplate`, `Utils.computeTrackPixels`, etc.).
107
+ - Web rendering via CSS Grid in `@resistdesign/voltra/web`.
108
+ - Native coordinate computation in `@resistdesign/voltra/native`.
109
+
110
+ ### Template syntax
111
+
112
+ ```text
113
+ header header, 1fr
114
+ side main, 2fr
115
+ \ 100px 1fr
116
+ ```
117
+
118
+ - Row lines: `<areas>, <row-track>` (row track optional)
119
+ - Column line: `\ <col-track> <col-track> ...`
120
+ - Supported units: `fr`, `px`, `%`
121
+ - Named areas must form rectangles
122
+
123
+ ### Web usage
124
+
125
+ ```tsx
126
+ import { Utils as WebUtils } from "@resistdesign/voltra/web";
127
+
128
+ const { layout: Layout, areas } = WebUtils.getEasyLayout(undefined, undefined, {
129
+ gap: 12,
130
+ padding: 16,
131
+ })`
132
+ header header, 1fr
133
+ side main, 2fr
134
+ \ 1fr 2fr
135
+ `;
136
+ ```
137
+
138
+ ### Native usage
139
+
140
+ ```tsx
141
+ import { Utils as NativeUtils } from "@resistdesign/voltra/native";
142
+
143
+ const layout = NativeUtils.makeNativeEasyLayout(`
144
+ header header, 100px
145
+ side main, 1fr
146
+ \\ 1fr 2fr
147
+ `);
148
+
149
+ const coords = layout.computeNativeCoords({
150
+ width: 320,
151
+ height: 240,
152
+ padding: 12,
153
+ gap: 8,
154
+ });
155
+ ```
156
+
157
+ ### Web vs Native
158
+
159
+ | Runtime | Rendering model | Output |
160
+ |---------|------------------|--------|
161
+ | Web | CSS Grid (browser layout engine) | CSS template strings |
162
+ | Native | Computed absolute layout | `{ left, top, width, height }` per area |
163
+
164
+ ## Routing (Web + Native)
165
+
166
+ Voltra ships a render-agnostic Route core in `@resistdesign/voltra/app` plus platform adapters.
167
+
168
+ Web usage (auto-wires `window.history`):
169
+
170
+ ```tsx
171
+ import { Utils as WebUtils } from "@resistdesign/voltra/web";
172
+
173
+ const { Route } = WebUtils;
174
+ ```
175
+
176
+ Native usage (adapter-driven):
177
+
178
+ ```tsx
179
+ import { Utils as NativeUtils } from "@resistdesign/voltra/native";
180
+
181
+ const { Route, RouteProvider, createManualRouteAdapter } = NativeUtils;
182
+ const { adapter, updatePath } = createManualRouteAdapter("/home");
183
+ ```
184
+
185
+ For React Native navigation libraries, Voltra is optimized for react-navigation as the primary native default. Provide a RouteAdapter that maps navigation state to a path and call `RouteProvider`.
186
+
187
+ Native navigation mapping example:
188
+
189
+ ```tsx
190
+ import { Utils as NativeUtils } from "@resistdesign/voltra/native";
191
+
192
+ const { createNavigationStateRouteAdapter, buildPathFromRouteChain } = NativeUtils;
193
+
194
+ const adapter = createNavigationStateRouteAdapter({
195
+ getState: () => navigationRef.getRootState(),
196
+ subscribe: (listener) => navigationRef.addListener("state", listener),
197
+ toPath: (state) =>
198
+ buildPathFromRouteChain(
199
+ state.routes.map((route) => ({
200
+ name: route.name,
201
+ params: route.params as Record<string, any>,
202
+ })),
203
+ {
204
+ Home: "home",
205
+ Book: "books/:id",
206
+ },
207
+ ),
208
+ navigate: (path) => {
209
+ const routeName = path === "/home" ? "Home" : "Book";
210
+ navigationRef.navigate(routeName);
211
+ },
212
+ });
213
+ ```
214
+
215
+ For RN web builds, keep your navigation library linking config in sync with the same route patterns used in `buildPathFromRouteChain`.
216
+
217
+ ## Form Suites (Web + Native + BYOCS)
218
+
219
+ Voltra's form system is split into a platform-agnostic core and platform suites:
220
+
221
+ - Core contracts live under `src/app/forms/core` (field kinds, suite resolution, renderer factories).
222
+ - Web DOM suite lives under `src/web/forms`.
223
+ - React Native suite lives under `src/native/forms`.
224
+
225
+ ### Web Usage
226
+
227
+ ```tsx
228
+ import { Forms } from "@resistdesign/voltra/web";
229
+
230
+ const { AutoField } = Forms.createWebFormRenderer();
231
+ ```
232
+
233
+ Override a single renderer:
234
+
235
+ ```tsx
236
+ import { Forms } from "@resistdesign/voltra/web";
237
+
238
+ const { AutoField } = Forms.createWebFormRenderer({
239
+ suite: Forms.withRendererOverride("string", (ctx) => {
240
+ return <input value={(ctx.value as string) || ""} onChange={(e) => ctx.onChange(e.target.value)} />;
241
+ }),
242
+ });
243
+ ```
244
+
245
+ ### Native Usage
246
+
247
+ ```tsx
248
+ import { Forms } from "@resistdesign/voltra/native";
249
+
250
+ const { AutoField } = Forms.createNativeFormRenderer();
251
+ ```
252
+
253
+ ### BYOCS (Bring Your Own Component Suite)
254
+
255
+ Provide partial overrides (renderers and/or primitives). Missing renderers are filled from the default suite and validated.
256
+
257
+ ```tsx
258
+ import { Forms } from "@resistdesign/voltra/web";
259
+
260
+ const { AutoField } = Forms.createWebFormRenderer({
261
+ suite: {
262
+ primitives: {
263
+ Button: ({ children }) => <button className="my-button">{children}</button>,
264
+ },
265
+ renderers: {
266
+ boolean: (ctx) => (
267
+ <label>
268
+ <input
269
+ type="checkbox"
270
+ checked={!!ctx.value}
271
+ onChange={(e) => ctx.onChange(e.target.checked)}
272
+ />
273
+ {ctx.label}
274
+ </label>
275
+ ),
276
+ },
277
+ },
278
+ });
279
+ ```
280
+
281
+ ### Relation + Custom Type Hooks
282
+
283
+ Renderers emit actions via:
284
+
285
+ - `onRelationAction(payload)` for relation fields
286
+ - `onCustomTypeAction(payload)` for custom types
287
+
288
+ Use these to wire modals, selectors, or editors without baking UI into the core engine.
289
+
97
290
  ## Docs Site
98
291
 
99
292
  The docs site is both reference documentation and a canonical usage example.