Skip to content

Visible history and state restoration

FormBuilder records every schema mutation in a history isolated by builder-id. The History panel turns the internal undo/redo stack into a user-facing timeline.

The current schema, steps, columns and fields remain serializable. History is runtime in-memory data and is never added to exported JSON.

Control the panel

vue
<script setup lang="ts">
const historyOpen = ref(false)
const historyDepth = ref(50)
</script>

<template>
  <FormBuilder
    builder-id="customer-form"
    v-model:history-open="historyOpen"
    v-model:history-depth="historyDepth"
  />
</template>

Related props:

PropDefaultPurpose
history-openfalseControls whether the panel is open.
history-depth50Maximum retained states, clamped between 2 and 500.
history-shortcutstrueEnables undo/redo keyboard shortcuts.

Depth changes are applied immediately. Lowering the value removes the oldest states while preserving the current state.

Timeline and business labels

Each entry contains:

  • a stable id;
  • a sequence number;
  • a timestamp;
  • a typed business action;
  • serializable context;
  • an independent schema snapshot;
  • an optional custom label and merge key.

The timeline can display labels such as:

  • “Field added: email”;
  • “Column moved: contact details”;
  • “Step duplicated: Review”;
  • “Schema imported”;
  • “State #12 restored”.

Labels are resolved when rendered with the active locale. History therefore does not store a frozen translation.

Keyboard shortcuts

When history-shortcuts is true:

ShortcutAction
Ctrl+Z or Cmd+ZUndo.
Ctrl+Shift+Z or Cmd+Shift+ZRedo.
Ctrl+YRedo.

The handler ignores events originating from input, textarea, select, contenteditable editors or elements with role="textbox". A shortcut typed inside a field keeps editing that field instead of changing the builder schema.

When multiple builders share one page, the last builder activated by pointer or focus owns the shortcuts. This prevents one Ctrl+Z from changing several instances.

Undo, redo and branches

Undo moves the pointer to the previous state. Redo moves it to the next state.

When a change is made after an undo, the previous future states are removed and a new branch starts from the current state, as in a conventional editor.

Rapid edits of the same property use a mergeKey and are coalesced within a short window. Typing several characters in a field label therefore does not create one entry per keystroke.

Explicitly restore a state

The Restore this state action requires confirmation. Restoration does not silently move the pointer to an old entry: it appends a new history-restore entry to the timeline.

The restoration itself therefore remains undoable.

Public API

vue
<script setup lang="ts">
import type {
  FormBuilderPublicApi,
  QFormBuilderHistoryChangeEvent,
  QFormBuilderHistoryNavigationEvent,
} from '@vevedh/qform-builder-layer/types'

const builder = ref<FormBuilderPublicApi | null>(null)

function restoreFirstState() {
  const history = builder.value?.getHistory()
  const first = history?.entries[0]
  if (first) builder.value?.restoreHistoryEntry(first.id)
}

function handleHistoryChange(event: QFormBuilderHistoryChangeEvent) {
  console.log(event.builderId, event.snapshot.pointer)
}

function handleUndo(event: QFormBuilderHistoryNavigationEvent) {
  console.log('Active state', event.entry.sequence)
}
</script>

<template>
  <FormBuilder
    ref="builder"
    builder-id="history-api-demo"
    @history-change="handleHistoryChange"
    @history-undo="handleUndo"
  />

  <QBtn label="Restore first state" @click="restoreFirstState" />
</template>

Exposed methods:

ts
getHistory(): QFormBuilderHistorySnapshot
undo(): QFormBuilderHistoryEntry | null
redo(): QFormBuilderHistoryEntry | null
restoreHistoryEntry(entryId: string): QFormBuilderHistoryEntry | null
clearHistory(): void
setHistoryDepth(depth: number): number
openHistory(): void
closeHistory(): void

Events:

  • history-change whenever entries, pointer or depth change;
  • history-undo after a successful undo;
  • history-redo after a successful redo;
  • history-restore after explicit restoration.

Public helpers

The history subpath exposes the pure engine for tests and advanced integrations:

ts
import {
  appendQFormBuilderHistory,
  createQFormBuilderHistoryState,
  formatQFormBuilderHistoryLabel,
  normalizeQFormBuilderHistoryDepth,
  restoreQFormBuilderHistory,
} from '@vevedh/qform-builder-layer/history'

Persistence and limits

History intentionally stays in memory:

  • autosave does not write it to localStorage;
  • schema export does not include it;
  • it is not automatically sent to the NFZ forms service;
  • it disappears when the instance is permanently unmounted.

For durable auditing, persist business revisions on the server in a dedicated Feathers/NFZ service. Do not confuse editor convenience history with a regulatory audit trail.

Best practices

  • Use a stable builder-id.
  • Keep a depth between 30 and 100 for most forms.
  • Disable history-shortcuts when the host application already owns global shortcuts.
  • Require explicit confirmation before restoring a timeline state.
  • Never use client history as an authorization, persistence or audit-proof mechanism.

QForm Builder — Nuxt 4, Quasar and FormKit layer for dynamic forms.