@frt-platform/report-core 1.2.0 β†’ 1.3.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.
package/README.md CHANGED
@@ -1,551 +1,414 @@
1
- # @frt/report-core
1
+ # `@frt-platform/report-core`
2
2
 
3
- Core engine for building, validating, and migrating **report templates** – plus validating **report responses**.
3
+ Core engine for defining, validating, migrating, diffing, and serializing **dynamic report templates**.
4
4
 
5
5
  This package is:
6
6
 
7
- * **Framework-agnostic** (no React)
8
- * **Storage-agnostic** (no DB assumptions)
9
- * **Runtime-safe** (Zod-based validation)
7
+ * 🧠 **Framework-agnostic**
8
+ * 🧩 **UI-agnostic**
9
+ * πŸ”’ **Safe by design (Zod-based)**
10
+ * 🧱 **The foundation of the FRT incident & reporting platform**
10
11
 
11
- It gives you:
12
-
13
- * A **typed schema** for report templates (templates β†’ sections β†’ fields)
14
- * **Zod-based validation** and parsing from unknown / JSON input
15
- * **Legacy schema migration** (flat `fields` β†’ `sections`, old type names, etc.)
16
- * **Normalization** helpers to ensure safe IDs & consistent structure
17
- * **Field defaults** and **ID utilities** you can use to build your own UI
18
- * **Response validation** based on templates (`buildResponseSchema`, `validateReportResponse`)
19
- * **Template diffing** (`diffTemplates`) to compare template versions
20
- * **Type-level response inference** (`InferResponse`) for fully typed responses
21
-
22
- It’s the core that powers a flexible incident/report builder, but it’s generic enough to be reused in any app.
12
+ It contains **no React**, **no database code**, and **no styling**.
13
+ You can use it in **Node**, **Next.js**, **backend services**, or custom form engines.
23
14
 
24
15
  ---
25
16
 
26
- ## Installation
27
-
28
- ```bash
29
- # npm
30
- npm install @frt/report-core zod
17
+ # ✨ Features
31
18
 
32
- # yarn
33
- yarn add @frt/report-core zod
19
+ ### πŸ“„ Template Schema
34
20
 
35
- # pnpm
36
- pnpm add @frt/report-core zod
37
- ```
21
+ * Sections β†’ fields
22
+ * Field IDs, labels, descriptions, placeholders
23
+ * Built-in field types:
38
24
 
39
- `zod` is a peer dependency and is used for schema validation & parsing.
25
+ * `shortText`, `longText`
26
+ * `number`
27
+ * `date`
28
+ * `checkbox`
29
+ * `singleSelect`, `multiSelect`
30
+ * `repeatGroup` (nested fieldsets)
40
31
 
41
- ---
32
+ ### πŸŽ› Field Constraints
42
33
 
43
- ## Concepts
34
+ * min/max length (`shortText`, `longText`)
35
+ * min/max value (`number`)
36
+ * min/max selections (`multiSelect`)
37
+ * allowed options
38
+ * checkbox required semantics
39
+ * default values
44
40
 
45
- The core model looks like this:
41
+ ### πŸ”€ Conditional Logic
46
42
 
47
- * A **template** contains multiple **sections**
48
- * A **section** contains multiple **fields**
49
- * A **field** has a `type` (short text, long text, number, select, etc.) and optional constraints
43
+ * `visibleIf`
44
+ * `requiredIf`
45
+ * Supports:
46
+ `equals`, `any`, `all`, `not`
47
+ * Fully integrated into validation.
50
48
 
51
- Supported field types:
49
+ ### πŸ”₯ Validation Engine
52
50
 
53
- ```ts
54
- import { REPORT_TEMPLATE_FIELD_TYPES } from "@frt/report-core";
55
-
56
- console.log(REPORT_TEMPLATE_FIELD_TYPES);
57
- // [
58
- // "shortText",
59
- // "longText",
60
- // "number",
61
- // "date",
62
- // "checkbox",
63
- // "singleSelect",
64
- // "multiSelect",
65
- // ]
66
- ```
51
+ * Build response schema dynamically with conditions
52
+ * Throwing API: `validateReportResponse()`
53
+ * Non-throwing API: `validateReportResponseDetailed()`
54
+ * Rich `ResponseFieldError` objects with:
67
55
 
68
- ---
56
+ * section title
57
+ * field label
58
+ * error code
59
+ * full message
60
+ * nested repeatGroup row context
69
61
 
70
- ## Quick Start
62
+ ### πŸ”„ Template Migration & Normalization
71
63
 
72
- ### 1. Define a template in JSON
64
+ * Legacy format migration (`fields β†’ sections`)
65
+ * Automatic ID normalization & uniqueness enforcement
66
+ * Safe parsing from JSON or raw objects
73
67
 
74
- You can define templates statically or load them from DB / API:
68
+ ### πŸ” Schema Diffing
75
69
 
76
- ```ts
77
- import type { ReportTemplateSchema } from "@frt/report-core";
70
+ Detect:
78
71
 
79
- const templateJson: ReportTemplateSchema = {
80
- version: 1,
81
- title: "Incident follow-up",
82
- description: "Collect a quick summary after an incident has been resolved.",
83
- sections: [
84
- {
85
- id: "section-overview",
86
- title: "Incident overview",
87
- fields: [
88
- {
89
- id: "summary",
90
- type: "longText",
91
- label: "What happened?",
92
- required: true,
93
- minLength: 10,
94
- },
95
- {
96
- id: "status",
97
- type: "singleSelect",
98
- label: "Incident status",
99
- required: true,
100
- options: ["Pending review", "Resolved", "Escalated"],
101
- defaultValue: "Resolved",
102
- },
103
- ],
104
- },
105
- ],
106
- };
107
- ```
72
+ * Added / removed / reordered sections
73
+ * Added / removed / reordered fields
74
+ * Modified fields
75
+ * Nested diffs inside repeat groups
108
76
 
109
- ### 2. Validate and normalize the template
77
+ ### πŸ“¦ JSON Schema Export
110
78
 
111
- Use the helpers to safely parse and normalize unknown input:
79
+ * Export a template as a valid JSON Schema (2020-12 draft)
80
+ * Includes vendor extensions:
112
81
 
113
- ```ts
114
- import {
115
- parseReportTemplateSchema,
116
- normalizeReportTemplateSchema,
117
- } from "@frt/report-core";
82
+ * `x-frt-visibleIf`
83
+ * `x-frt-requiredIf`
84
+ * repeatGroup min/max
85
+ * placeholders
86
+ * Useful for OpenAPI, Postman, or other backend runtimes.
118
87
 
119
- const raw = /* from DB, file, API, etc. */ templateJson;
88
+ ### πŸ— Field Registry
120
89
 
121
- // 1) parse + validate (throws on invalid input)
122
- const parsed = parseReportTemplateSchema(raw);
90
+ Extend the system at runtime:
123
91
 
124
- // 2) optional extra normalization step (IDs, trimming, etc.)
125
- const normalized = normalizeReportTemplateSchema(parsed);
92
+ * Add custom types (`richText`, `fileUpload`, etc.)
93
+ * Override validation logic
94
+ * Provide metadata for UI packages
126
95
 
127
- console.log(normalized.sections[0].fields[0].id);
128
- // always a non-empty, safe identifier
129
- ```
96
+ ### 🧬 Type Inference
130
97
 
131
- ### 3. Serialize for storage
98
+ Get a fully typed response type from a template:
132
99
 
133
100
  ```ts
134
- import { serializeReportTemplateSchema } from "@frt/report-core";
135
-
136
- const jsonString = serializeReportTemplateSchema(normalized);
137
- // ready to store in DB, file, etc.
101
+ type MyResponse = InferResponse<typeof template>;
138
102
  ```
139
103
 
140
- ---
104
+ ### 🧾 Serialization Helpers
141
105
 
142
- ## Parsing from a JSON string
143
-
144
- If you have a JSON string (e.g. from a form textarea):
106
+ Deterministic JSON output with sorting options:
145
107
 
146
108
  ```ts
147
- import { parseReportTemplateSchemaFromString } from "@frt/report-core";
148
-
149
- try {
150
- const template = parseReportTemplateSchemaFromString(userInputString);
151
- // valid template here
152
- } catch (err) {
153
- // invalid JSON or schema – show error to user
154
- }
109
+ serializeReportTemplateSchema(template, {
110
+ pretty: true,
111
+ sortSectionsById: true,
112
+ sortFieldsById: true,
113
+ });
155
114
  ```
156
115
 
157
- ---
158
-
159
- ## Validating report responses
160
-
161
- Once you have a template, you can validate **user-submitted responses** against it.
162
-
163
- ### Runtime schema for responses
164
-
165
- ```ts
166
- import {
167
- buildResponseSchema,
168
- validateReportResponse,
169
- } from "@frt/report-core";
170
-
171
- const template = /* a valid ReportTemplateSchema */ normalized;
116
+ Perfect for Git diffs and storage.
172
117
 
173
- // Build a Zod schema for responses
174
- const responseSchema = buildResponseSchema(template);
175
-
176
- // Example response object
177
- const responseData = {
178
- summary: "Student slipped in the hallway, no serious injuries.",
179
- status: "Resolved",
180
- };
118
+ ---
181
119
 
182
- // Validate (throws ZodError on failure)
183
- const safeResponse = responseSchema.parse(responseData);
120
+ # πŸ“¦ Installation
184
121
 
185
- // Or use the convenience helper:
186
- const safeResponse2 = validateReportResponse(template, responseData);
122
+ ```bash
123
+ npm install @frt-platform/report-core zod
187
124
  ```
188
125
 
189
- `buildResponseSchema` respects:
190
-
191
- * `required` flags
192
- * `minLength` / `maxLength` (short/long text)
193
- * `minValue` / `maxValue` (numbers)
194
- * `minSelections` / `maxSelections` (multiSelect)
195
- * `options` (singleSelect + multiSelect)
196
- * `checkbox` semantics (required checkbox must be `true`)
126
+ `zod` is a peer dependency.
197
127
 
198
128
  ---
199
129
 
200
- ## Type-level response inference (`InferResponse`)
130
+ # πŸš€ Quickstart
201
131
 
202
- If you define templates statically (`as const`), you can get a fully-typed response type from the template:
132
+ ## 1. Define a template
203
133
 
204
134
  ```ts
205
- import type { InferResponse } from "@frt/report-core";
135
+ import type { ReportTemplateSchema } from "@frt-platform/report-core";
206
136
 
207
- const incidentTemplate = {
137
+ const template: ReportTemplateSchema = {
208
138
  version: 1,
209
139
  sections: [
210
140
  {
211
- id: "section-overview",
141
+ id: "general",
142
+ title: "General Info",
212
143
  fields: [
213
144
  {
214
- id: "summary",
215
- type: "longText",
216
- label: "What happened?",
145
+ id: "title",
146
+ type: "shortText",
147
+ label: "Incident title",
217
148
  required: true,
218
- minLength: 10,
219
149
  },
220
150
  {
221
- id: "status",
151
+ id: "severity",
222
152
  type: "singleSelect",
223
- label: "Status",
153
+ label: "Severity",
224
154
  required: true,
225
- options: ["Pending", "Resolved", "Escalated"] as const,
226
- },
227
- {
228
- id: "follow_up_date",
229
- type: "date",
230
- label: "Follow-up date",
231
- },
232
- {
233
- id: "tags",
234
- type: "multiSelect",
235
- label: "Tags",
236
- options: ["Safety", "Medical", "Security"] as const,
155
+ options: ["Low", "Medium", "High"],
237
156
  },
238
157
  {
239
- id: "confirmed",
240
- type: "checkbox",
241
- label: "I confirm this report is accurate",
242
- required: true,
158
+ id: "details",
159
+ type: "longText",
160
+ label: "Details",
161
+ minLength: 10,
243
162
  },
244
163
  ],
245
164
  },
246
165
  ],
247
- } as const;
248
-
249
- type IncidentResponse = InferResponse<typeof incidentTemplate>;
250
- ```
251
-
252
- `IncidentResponse` will be inferred as:
253
-
254
- ```ts
255
- type IncidentResponse = {
256
- summary: string; // required
257
- status: "Pending" | "Resolved" | "Escalated"; // required
258
- follow_up_date?: string; // optional
259
- tags?: ("Safety" | "Medical" | "Security")[]; // optional
260
- confirmed: boolean; // required checkbox
261
166
  };
262
167
  ```
263
168
 
264
- This is extremely useful for:
265
-
266
- * API handlers (`(body: IncidentResponse) => { ... }`)
267
- * DB layers that store `data` blobs
268
- * Frontend forms with type-safe state
269
-
270
- ---
271
-
272
- ## Using field defaults
273
-
274
- The package includes core defaults for each field type (no UI, no icons):
275
-
276
- ```ts
277
- import {
278
- CORE_FIELD_DEFAULTS,
279
- DEFAULT_FIELD_LABEL,
280
- type ReportTemplateFieldType,
281
- } from "@frt/report-core";
282
-
283
- function createDefaultField(type: ReportTemplateFieldType) {
284
- const defaults = CORE_FIELD_DEFAULTS[type] ?? {};
285
- return {
286
- id: "your-id-here",
287
- type,
288
- label: (defaults.label as string) ?? DEFAULT_FIELD_LABEL,
289
- ...defaults,
290
- };
291
- }
292
-
293
- const field = createDefaultField("shortText");
294
- ```
295
-
296
- This is especially useful in a form builder UI when the user adds a new field of a given type.
297
-
298
169
  ---
299
170
 
300
- ## Generating unique IDs
301
-
302
- The core package exposes a generic `createUniqueId` helper:
171
+ ## 2. Validate a response (throwing API)
303
172
 
304
173
  ```ts
305
- import { createUniqueId } from "@frt/report-core";
174
+ import { validateReportResponse } from "@frt-platform/report-core";
306
175
 
307
- const existingSectionIds = ["section-1", "section-2"];
308
- const newId = createUniqueId("section", existingSectionIds);
309
- // "section-3" (or the next available)
176
+ const parsed = validateReportResponse(template, {
177
+ title: "Broken fire alarm",
178
+ severity: "High",
179
+ details: "Triggered after smoke test",
180
+ });
310
181
  ```
311
182
 
312
- You can use this for both section and field IDs when building templates dynamically.
183
+ If invalid β†’ throws a ZodError.
313
184
 
314
185
  ---
315
186
 
316
- ## Legacy schema migration
317
-
318
- If you had older templates that:
319
-
320
- * Used a flat `fields` array instead of `sections`, or
321
- * Used older field type names (`text`, `textarea`, `dropdown`, `multiselect`, …)
322
-
323
- you can pass them through the migration pipeline:
187
+ ## 3. Validate without throwing (UI-friendly)
324
188
 
325
189
  ```ts
326
- import {
327
- migrateLegacySchema,
328
- parseReportTemplateSchema,
329
- } from "@frt/report-core";
330
-
331
- const legacy = {
332
- title: "Old template",
333
- description: "Using flat fields",
334
- fields: [
335
- { id: "summary", type: "textarea", label: "Summary" },
336
- { id: "status", type: "dropdown", label: "Status", options: ["Open", "Closed"] },
337
- ],
338
- };
190
+ import { validateReportResponseDetailed } from "@frt-platform/report-core";
339
191
 
340
- const migrated = migrateLegacySchema(legacy);
341
- const parsed = parseReportTemplateSchema(migrated);
192
+ const result = validateReportResponseDetailed(template, {
193
+ title: "",
194
+ severity: "High",
195
+ });
342
196
 
343
- console.log(parsed.sections.length); // 1
344
- console.log(parsed.sections[0].fields[0].type); // "longText"
197
+ if (!result.success) {
198
+ console.log(result.errors);
199
+ }
345
200
  ```
346
201
 
347
- You don’t normally need to call `migrateLegacySchema` yourself – `parseReportTemplateSchema` already does:
202
+ Produces:
348
203
 
349
204
  ```ts
350
- // internally:
351
- const migrated = migrateLegacySchema(raw);
352
- const parsed = ReportTemplateSchemaValidator.parse(migrated);
205
+ [
206
+ {
207
+ fieldId: "title",
208
+ sectionId: "general",
209
+ sectionTitle: "General Info",
210
+ label: "Incident title",
211
+ code: "field.too_small",
212
+ message: 'Section "General Info" β†’ Field "Incident title": String must contain at least 1 character(s).'
213
+ }
214
+ ]
353
215
  ```
354
216
 
355
217
  ---
356
218
 
357
- ## Diffing templates
358
-
359
- When you version templates (e.g. editing in a UI), you might want to see what changed between versions. The core exposes a simple diff utility:
219
+ # πŸ”€ Conditional Logic Example
360
220
 
361
221
  ```ts
362
- import { diffTemplates } from "@frt/report-core";
222
+ {
223
+ id: "follow_up_notes",
224
+ type: "longText",
225
+ label: "Follow-up notes",
226
+ visibleIf: { equals: { follow_up_required: true } },
227
+ requiredIf: { equals: { follow_up_required: true } },
228
+ }
229
+ ```
363
230
 
364
- const beforeTemplate = /* old template */;
365
- const afterTemplate = /* updated template */;
231
+ Behavior:
366
232
 
367
- const diff = diffTemplates(beforeTemplate, afterTemplate);
233
+ * If `follow_up_required = false` β†’ field is **hidden** and **ignored**
234
+ * If `true` β†’ field becomes **required**
368
235
 
369
- console.log(diff.addedSections);
370
- console.log(diff.removedSections);
371
- console.log(diff.fieldChanges);
372
- ```
236
+ ---
373
237
 
374
- `diff.fieldChanges` contains entries like:
238
+ # πŸ” Repeat Group Example
375
239
 
376
240
  ```ts
377
241
  {
378
- type: "modified",
379
- sectionId: "section-overview",
380
- fieldId: "summary",
381
- before: { label: "What happened?", required: false, ... },
382
- after: { label: "What happened exactly?", required: true, ... },
383
- changes: [
384
- { key: "label", before: "What happened?", after: "What happened exactly?" },
385
- { key: "required", before: false, after: true },
386
- ],
242
+ id: "injured",
243
+ type: "repeatGroup",
244
+ label: "Injured people",
245
+ min: 1,
246
+ max: 5,
247
+ fields: [
248
+ { id: "name", type: "shortText", label: "Name", required: true },
249
+ { id: "injury", type: "longText", label: "Injury description" }
250
+ ]
387
251
  }
388
252
  ```
389
253
 
390
- Use this for:
254
+ Response shape:
391
255
 
392
- * β€œChanges” tabs in your UI
393
- * Migration & compatibility warnings
394
- * Audit trails
256
+ ```ts
257
+ injured: Array<{ name: string; injury?: string }>;
258
+ ```
395
259
 
396
260
  ---
397
261
 
398
- ## API Overview
399
-
400
- ### Types
262
+ # 🧩 Field Registry (Custom Types)
401
263
 
402
264
  ```ts
403
- import type {
404
- ReportTemplateSchema,
405
- ReportTemplateSection,
406
- ReportTemplateField,
407
- ReportTemplateFieldType,
408
- InferResponse,
409
- } from "@frt/report-core";
265
+ import { FieldRegistry } from "@frt-platform/report-core";
266
+ import { z } from "zod";
267
+
268
+ FieldRegistry.register("richText", {
269
+ defaults: { label: "Details" },
270
+ buildResponseSchema(field) {
271
+ let schema = z.string();
272
+ if (field.minLength) schema = schema.min(field.minLength);
273
+ return field.required ? schema : schema.optional();
274
+ },
275
+ });
410
276
  ```
411
277
 
412
- * `ReportTemplateFieldType` – union of all field type strings
413
- * `ReportTemplateField` – one question/field in a section
414
- * `ReportTemplateSection` – a logical grouping of fields
415
- * `ReportTemplateSchema` – full template
416
- * `InferResponse<TTemplate>` – builds a typed response object from a static template
417
-
418
- ### Constants
278
+ Now templates may include fields like:
419
279
 
420
280
  ```ts
421
- import {
422
- REPORT_TEMPLATE_VERSION,
423
- REPORT_TEMPLATE_FIELD_TYPES,
424
- DEFAULT_FIELD_LABEL,
425
- CORE_FIELD_DEFAULTS,
426
- } from "@frt/report-core";
281
+ { id: "body", type: "richText", label: "Report body" }
427
282
  ```
428
283
 
429
- * `REPORT_TEMPLATE_VERSION: number`
430
- * `REPORT_TEMPLATE_FIELD_TYPES: readonly ReportTemplateFieldType[]`
431
- * `DEFAULT_FIELD_LABEL: string`
432
- * `CORE_FIELD_DEFAULTS: Record<ReportTemplateFieldType, Record<string, unknown>>`
284
+ ---
433
285
 
434
- ### Validation & parsing
286
+ # 🧾 JSON Schema Export
435
287
 
436
288
  ```ts
437
- import {
438
- ReportTemplateFieldSchema,
439
- ReportTemplateSectionSchema,
440
- ReportTemplateSchemaValidator,
441
- parseReportTemplateSchema,
442
- parseReportTemplateSchemaFromString,
443
- normalizeReportTemplateSchema,
444
- serializeReportTemplateSchema,
445
- } from "@frt/report-core";
289
+ import { exportJSONSchema } from "@frt-platform/report-core";
290
+
291
+ const jsonSchema = exportJSONSchema(template);
446
292
  ```
447
293
 
448
- Use the Zod schemas directly if you want:
294
+ Produces JSON Schema with:
449
295
 
450
- ```ts
451
- import { ReportTemplateSchemaValidator } from "@frt/report-core";
296
+ * field types
297
+ * enums
298
+ * min/max constraints
299
+ * default values
300
+ * conditional logic preserved as custom `x-frt-*` properties
452
301
 
453
- const result = ReportTemplateSchemaValidator.safeParse(raw);
454
- if (!result.success) {
455
- // handle validation errors
456
- }
457
- ```
302
+ ---
458
303
 
459
- ### Migration
304
+ # πŸ” Diff Templates
460
305
 
461
306
  ```ts
462
- import { migrateLegacySchema } from "@frt/report-core";
463
- ```
307
+ import { diffTemplates } from "@frt-platform/report-core";
464
308
 
465
- Takes `unknown` legacy input and returns a shape better suited for current schema validation.
309
+ const diff = diffTemplates(oldTemplate, newTemplate);
310
+ ```
466
311
 
467
- ### Response validation
312
+ Detects:
468
313
 
469
- ```ts
470
- import {
471
- buildResponseSchema,
472
- validateReportResponse,
473
- type InferResponse,
474
- } from "@frt/report-core";
475
- ```
314
+ * added/removed/reordered sections
315
+ * added/removed/reordered fields
316
+ * modified fields
317
+ * nested diffs for repeat groups
476
318
 
477
- ### Template diffing
319
+ Perfect for:
478
320
 
479
- ```ts
480
- import { diffTemplates } from "@frt/report-core";
481
- ```
321
+ * Version history
322
+ * Audit logs
323
+ * Template editing UI
482
324
 
483
325
  ---
484
326
 
485
- ## Integration Ideas
486
-
487
- This package is intentionally minimal. You can layer it into:
327
+ # 🧬 Type Inference
488
328
 
489
- * **Next.js / React** apps for:
329
+ Given a template:
490
330
 
491
- * Template builders
492
- * Dynamic report forms
493
- * Admin panels for managing report templates
494
- * **Node/Edge backends** for:
331
+ ```ts
332
+ export const myTemplate = {
333
+ version: 1,
334
+ sections: [
335
+ {
336
+ id: "s",
337
+ fields: [
338
+ { id: "title", type: "shortText", required: true },
339
+ { id: "tags", type: "multiSelect", options: ["A", "B"] },
340
+ ]
341
+ }
342
+ ]
343
+ } as const;
344
+ ```
495
345
 
496
- * Validating templates on save
497
- * Migrating old template shapes
498
- * Ensuring consistent data before persisting to a DB
499
- * Validating report responses before saving them
346
+ Infer response type:
500
347
 
501
- Typical pattern in a full stack app:
348
+ ```ts
349
+ type MyResponse = InferResponse<typeof myTemplate>;
350
+ ```
502
351
 
503
- 1. **Frontend**
352
+ Produces:
504
353
 
505
- * Build a visual editor for templates
506
- * Send template JSON to server
354
+ ```ts
355
+ type MyResponse = {
356
+ title: string;
357
+ tags?: ("A" | "B")[];
358
+ };
359
+ ```
507
360
 
508
- 2. **Backend**
361
+ ---
509
362
 
510
- * `parseReportTemplateSchema` to validate templates
511
- * Store `ReportTemplateSchema` JSON in your DB (e.g. Mongo via Prisma)
512
- * On response submission:
363
+ # 🧾 Serialization
513
364
 
514
- * Load template from DB
515
- * `validateReportResponse(template, data)` before insert
365
+ ```ts
366
+ import { serializeReportTemplateSchema } from "@frt-platform/report-core";
516
367
 
517
- 3. **Runtime**
368
+ const json = serializeReportTemplateSchema(template, {
369
+ pretty: true,
370
+ sortSectionsById: true,
371
+ sortFieldsById: true,
372
+ });
373
+ ```
518
374
 
519
- * Load templates from DB
520
- * Use them to render dynamic forms
521
- * Use `buildResponseSchema` to validate and normalize responses
375
+ Useful for deterministic output in Git.
522
376
 
523
377
  ---
524
378
 
525
- ## Roadmap / Extensions (ideas)
379
+ # 🧱 Roadmap
380
+
381
+ ### Phase 1 β€” Core Maturation (βœ”οΈ COMPLETE)
382
+
383
+ * Validation
384
+ * Conditional logic
385
+ * Diffing
386
+ * Field Registry
387
+ * Error helpers
388
+ * Serialization features
526
389
 
527
- Things you might add on top (in a separate package, e.g. `@frt/report-react`):
390
+ ### Phase 2 β€” Advanced Field System (IN PROGRESS)
528
391
 
529
- * React components for:
392
+ * Repeat group conditional rules (`minIf` / `maxIf`)
393
+ * Conditional logic inside group rows
394
+ * Computed fields (design)
395
+ * RichText / FileUpload via registry
530
396
 
531
- * Template builder UI
532
- * Field editors/renderers (short text, selects, etc.)
533
- * Auto-generated `<ReportForm />` that consumes a template
534
- * More advanced field types:
397
+ ### Phase 3 β€” Reactions & Analytics (Planned)
535
398
 
536
- * File uploads
537
- * Signature pads
538
- * Matrix / grid fields
539
- * Conditional logic:
399
+ * Scoring rules
400
+ * Auto-tagging
401
+ * Suggested outcomes
540
402
 
541
- * `visibleIf`
542
- * `requiredIf`
543
- * Multi-language labels & descriptions
403
+ ### Phase 4 β€” React UI Package (Planned)
544
404
 
545
- The core stays simple: **schema + validation + migration + response validation + diffing**.
405
+ * Form renderer
406
+ * Template builder
407
+ * Field palette
408
+ * Full ShadCN integration
546
409
 
547
410
  ---
548
411
 
549
- ## License
412
+ # πŸ“„ License
550
413
 
551
- MIT – use it in your projects, commercial or not. Contributions welcome.
414
+ MIT β€” feel free to use, extend, or contribute.