Blinx is a headless, model-driven UI framework that renders forms and tables from a shared schema. The core runtime stays agnostic of any design system and delegates DOM concerns to adapters (lib/blinx.form.js, lib/blinx.table.js, or custom renderers). This separation keeps rendering fast, predictable, and portable across Tailwind, Chakra, Headless UI, Radix UI, or any bespoke component library while reusing validation, diffing, and persistence semantics.
What ships today:
- Dynamic forms and tables generated from the combination of model metadata and view descriptions.
- Two-way binding between UI and dataset via the store.
- Field rendering driven by datatype, constraints, and view preferences (readonly, required, min/max, CSS hints).
- Layout definitions that allow form grouping to evolve independently from the data model.
- Client-side runtime with pagination, diff tracking, and interceptor hooks.
- Keep the runtime headless so any adapter can own DOM concerns.
- Provide schema-aware rendering to avoid hand-written forms/tables.
- Funnel every action (
save,reset,next,prev,create,delete) through a shared interceptor pipeline. - Stay client-first and performance-conscious without leaning on SSR.
Core (Headless)
├─ Model engine (types, constraints, preferences)
├─ View engine (layout, grouping, visibility)
├─ Store (two-way binding, diff, reset/commit/add/remove)
├─ Validation lifecycle
└─ Runtime orchestrator
UI Adapters
├─ Default HTML adapter (Tailwind-friendly)
├─ Chakra adapter (React)
└─ Headless UI / Radix UI adapters
- Model (
model/product.model.js) describes fields, datatypes, constraints, and defaults. - View descriptors outline sections, column layouts, and visibility rules.
- Store (
lib/blinx.store.js) hydrates model + dataset, exposes mutation APIs, and emits granular events. - UI adapters (
lib/blinx.form.js,lib/blinx.table.js, or customs) build DOM widgets using model + view metadata.
This layering keeps validation, diffing, and messaging logic reusable while adapters focus on look-and-feel.
- Snapshot cloning keeps
originalandcurrentarrays isolated so diffing remains cheap JSON comparisons. - Every mutator (
setField,addRecord,removeRecords,update,updateIndex,commit,reset) routes throughnotify, which publishes both structured paths and payloads for downstream listeners. diff()walks records index-first and only inspects keys that exist on the record, making commit payloads small and easy to surface in status messages.- Store consumers should never mutate returned records in-place; use the provided setters to keep notifications and diffs accurate.
update(index, record)replaces the record in-place and emits anupdateevent with both the index and record, whileupdateIndex(index)replays the same event when the object was mutated externally and only needs to be re-announced.
- Sections render into a
DocumentFragment, then flush to the DOM in one pass to minimize layout thrash. - Fields delegate to the active adapter through
ui.createField(...), which returns{ el, ... }so widgets can manage their own lifecycle. - Navigation (
Next/Prev) is purely index-driven; the form rebinds itself whenever the selected record changes. - Indicator and status nodes stay optional and are wired up through injected
controls, letting integrators map them to any markup. - Interceptors wrap every command, receiving a
processorobject with state, DOM references, and an idempotentproceed()so middleware chains cannot double-run defaults.
- Save, Reset, Next, Previous, Create, Delete buttons.
- Record indicator.
- Status message region.
- Client-side pagination (default page size 20) maintains a shared
pagepointer and updates Prev/Next controls. - Checkbox selection tracks row indexes inside a
Set. Interceptors can readprocessor.state.selectedbefore destructive actions. ui.formatCell(value, fieldDef)lets adapters decide how to visualize primitives (chips, currency, badges, etc.).- The table subscribes to store events and rebuilds the visible page whenever the dataset mutates.
- Pagination controls.
- Create button.
- Delete Selected button.
- Status message region.
- Selection checkboxes.
validateFieldencapsulates the rule set (required, enum, length, min/max, etc.).- Form validation runs on demand inside
doSave, short-circuiting on the first failing section. - Failed validations push a status message and keep the user on the same record; adapters are free to paint inline errors.
- Save/Reset/Delete actions surface user-friendly messages through the injected
statusDOM node using semantic colors (#2f855asuccess,#e53e3eerrors, slate for neutral states). - Form and table share this pattern so embedding apps can style a single CSS class to affect both widgets.
- Store events are granular:
add,update, andresetfire once per record, whileremovebatches all removed records into a single payload andcommitships the entire dataset snapshot plus the store reference for query access. - The form listens for
resetto rebuild UI, and forremoveto adjust index, rebuild sections, and clear status. - Table refreshes rows automatically on store mutations, so
doDelete()relies on event-driven updates instead of manual DOM surgery. - Interceptors provide hooks for analytics, confirmations, or API orchestration without touching core logic.
DocumentFragmentbatching reduces layout thrash across both form and table renders.- Pagination keeps table work bounded; virtualization remains on the roadmap for larger datasets.
- Diff-aware commits skip writes when no changes exist and clear
saveStatuson record navigation.
- Interceptors: Pre/post logic without mutating core behavior.
- Adapters: Override
createFieldandformatCellto integrate with any component library. - View schema: Extend sections/columns with conditional visibility, spans, or custom renderers.
- Store subscribe: External listeners can sync with remote APIs, websockets, or optimistic UI flows.
- Dynamic form rendering from model + view.
- Dynamic table rendering with pagination.
- Two-way data binding via the store.
- Click-to-edit from table rows.
- Record navigation (Next/Prev, direct index) plus record indicator.
- Reset & Save with diff tracking and messaging.
- Create/Delete operations for both form and table.
- Selection in the table for bulk delete.
- Interceptor pattern for every action with access to state, controls, and
proceed(). - Event-driven UI refresh so actions stay slim and predictable.
- Conditional display rules baked into the view schema.
- Undo/Redo stack in the store.
- Soft delete flows (mark inactive instead of removal).
- Bulk create with prefilled values.
- "No records" placeholder state when the dataset is empty.
- React/Chakra adapter for enterprise-grade UI.
- Virtualized table rendering for very large datasets.
- How should conditional visibility rules be declared so both form and table understand them?
- Can adapter-level theming tokens keep Tailwind, Chakra, and other libraries aligned?
- Should diff batching understand array moves (drag-and-drop) rather than treating them as delete+add pairs?
- Server-side rendering (SSR); focus is on client-first experiences.
- Real-time collaboration; store events remain local.
- Built-in persistence; Blinx surfaces diffs, but host applications choose how and where to store them.
- Added Next/Prev navigation and the record indicator.
- Introduced the interceptor pattern for all actions with idempotent
proceed(). - Added Create/Delete buttons for both form and table.
- Implemented table selection for bulk delete.
- Moved
formatCellinto adapters for pluggable rendering. - Optimized rendering with
DocumentFragment. - Enhanced
store.diff()to detect add/remove events. - Let the form listen to
removeevents for auto-refresh. - Cleared
saveStatuson record navigation. - Simplified
doDelete()to rely on event-driven refresh.