@inubeapislive/screen-configurator
v1.0.0
Published
iNube screen configuration platform - five-level configurable screen engine (app/flow/screen/region/node) with a visual builder
Readme
@inubeapislive/screen-configurator
A configuration platform for screens: a five-level document model (app / flow / screen / region / node), a runtime engine that renders it, and a visual builder that authors it.
Status: scaffolding. The folder and file structure is in place; modules are stubs carrying their task reference. Nothing is implemented yet.
Specification
The design is fully specified in four documents plus a legacy reference. Read them in this order:
| Document | What it covers |
|---|---|
| PLATFORM.md | Start here. Five levels, screen kinds, navigation graph, data scopes, layered security |
| FEATURES.md | The single-screen engine: controls, layout, bindings, rules, validation, actions, datasources |
| SECURITY.md | Threat model, findings S1-S15, and the 15 binding rules R1-R15 |
| TASKS.md | Implementation breakdown T0.0 -> T3.7 with per-task done criteria |
| legacy/DynamicPageConfiguration.md | Authoring reference for the system being replaced; input spec for the T3.1 migrator |
Every source file carries the task id it implements, so TASKS.md is the index to this tree.
Layout
src/
# ---- Phase 0: foundations (T0.0-T0.8) ----
security/ R1-R15 helpers: safeUrl, safeFilename, sanitizeHtml, isSafeKey, masking, audit, PII
devtools/ logger with redaction, trace types
paths/ immutable, prototype-pollution-safe get/set/has/unset + binding contexts
scopes/ seven scopes, scope-qualified binding resolution, per-scope write permissions
entities/ app-level entity registry: types, validators, PII class, field permissions
expression/ tokenizer -> parser -> sandboxed evaluator + whitelisted function library
rules/ nested all/any/not rule trees, typed coercion, evaluation trace
dependency-graph/ path -> dependents, cycle detection, cross-screen impact analysis
schema/ app/flow/screen/region/node schemas, validation, defaults, node factory
# ---- Phase 1: runtime engine (T1.1-T1.15) ----
registry/ control registry - palette, inspector and runtime all read from it
controls/ one module per control; adding a control is one new file + one register call
runtime/ store, ConfigRenderer, ScreenHost, LayoutNode, FieldHost, field-state recompute
validation/ one validation engine; descriptors, named groups, async validators
datasources/ request executor, cache, dedupe, cancellation, cascades, error policy
actions/ action registry, pipeline runner, trace, reviewed plugin extension point
# ---- Phase 1P: platform (T1P.1-T1P.9) ----
screen-kinds/ kind registry + one module per kind (form, list, wizard, detail, ...)
navigation/ screen graph, typed param/return contracts, guards, presentation modes, URL state
flows/ multi-screen journeys: flow state, persistence, resume, expiry
theme/ tenant theming tokens
i18n/ locale bundles, formatters
a11y/ automated accessibility checks (a floor, not a certificate)
telemetry/ counts, node ids, error codes, durations - never PII
# ---- Phase 3: migration (T3.1) ----
migrations/ schemaVersion upgrades + the legacy v3 -> v4 migrator and its refusal report
# ---- Phase 2: builder ----
builder/ store, shell, panels, drag & drop, importers, testersWhat this package reuses, and what it deliberately does not
Reused
| Package | Used for |
|---|---|
| @inubeapislive/components | The presentational layer. Controls here are registry wrappers that delegate rendering to it. Also supplies the MUI theme and the i18n runtime, which src/theme and src/i18n adapt rather than reimplement. |
| @inubeapislive/validations | Validator primitives. All nine format validators the engine needs are already there (isPan, isGstNo, isPassport, isMobileNumber, isEmail, isIFSCode, isRequired, isMinLength, isMaxLength), and it exports ~45 in total. src/validation is the descriptor, group and scope layer over them. |
src/controls/component-mapping.ts is the authoritative table of which shared component backs each
control, and which controls are implemented locally with the reason why.
Five components were added to @inubeapislive/components as part of this work, because the config
platform needed them and they are generically useful: Switch, Chip, Accordion, TimePicker and
IconButton. IconButton takes a required label, so an unlabelled icon button is a type error
rather than an accessibility finding.
Relationship to render-control
@inubeapislive/render-control is not a dependency, and that is a decision
rather than an oversight. The two packages solve different problems: render-control renders a flat form
from a schema; this renders a five-level application. Where their scopes overlap, the requirements here
are strictly stronger:
| render-control module | Why it is not reused |
|---|---|
| api-client | Uses the positional {P1} URL scheme that SECURITY.md retires by name, interpolates without encodeURIComponent, and types its result as Promise<unknown \| ApiError> — returning the error as the resolved value, which is finding S11 and exactly what R11 forbids. |
| state | React context plus reducer, so every consumer re-renders on any change. T1.1/T1.2 exist to get field-level subscription: a keystroke must re-render one field. |
| visibility | Five operators. FEATURES.md §1.4 requires sixteen, plus typed coercion from the entity registry and an evaluation trace to drive the rule tester. |
| schema | A flat, single-level FormSchema. The whole point of T0.1 is five levels. |
| path-resolver | Its get/set are genuinely immutable and better than the legacy helper, but lack prototype-key rejection (R14), Result returns (R11), explicit array-index segments, and the array operations the repeater needs. |
| adapters | ControlAdapter is the same idea as ControlDefinition, but mine additionally carries capabilities, propSchema, icon and category because the builder generates the palette and inspector from them. Bolting that on would make render-control pay for a feature its consumers do not use. |
Both packages coexist. If render-control's flat-form use case is eventually absorbed here, that is a migration to plan deliberately, not a coupling to introduce now.
Two irreversible commitments
Both are made in the schema (T0.1) before anything else, because neither can be retrofitted:
- Five levels from day one - app / flow / screen / region / node. The application level cannot be added later; every binding path and navigation reference depends on it existing.
- Scope-qualified bindings from the first commit -
screen.Proposer.Name, neverProposer.Name. An unqualified path is a schema error.
The runtime/builder boundary
The runtime contains no builder code. The builder attaches through a documented editorAdapter prop.
This is enforced three ways: separate subpath exports (./runtime vs ./builder), separate tsup entries,
and a no-restricted-imports lint rule scoped to src/runtime/**.
Scripts
npm run lint # security guardrail scan of src/ (T0.0 CI gate)
npm run typecheck # tsc --noEmit
npm run test # vitest --run
npm run build # tsup, dual ESM/CJS with .d.ts per subpathnpm run lint is the dependency-free half of the T0.0 gate: it fails the build on dynamic code execution,
raw HTML injection, console/debugger, ambient browser access, prototype-unsafe access and blanket lint
suppressions. Run it against dist after a build to catch the S10 failure mode:
node scripts/check-guardrails.mjs dist.eslintrc.json holds the equivalent rule set from SECURITY.md 7.1 for when ESLint is wired into the
workspace.
