Danvas
Danvas
DashboardSupportWelcome

👤 USER DOCS

User Guides

Getting Started

Getting StartedDashboard & OnboardingApp Settings

Tutorials

Tutorial: Setting Up Shift Tasks & ClosersTutorial: Managing Incidents in the InboxTutorial: Tracking Compliance & Sync StatusTutorial: Operational Workflows with the AI AssistantTutorial: Building & Deploying Custom Checklists

Daily Operations (Staff)

Shift Workspace & TasksService Day SetupDaily Line-UpStaff Service Day ReportsForms

Communication & Chat

Messages & AnnouncementsUnified Operations InboxAI Assistant

Manager & Admin Guides

Daily Line-Up SetupStaff SchedulingManaging LocationsNPS and Guest FeedbackCouponsContacts and Guest HistoryManager CloseoutsDaily Line-Up & ComplianceAnalyticsIncident ReportingWhistleblower Concerns & FeedbackAdmin Tools

⚙️ DEVELOPER DOCS

Getting Started

Getting StartedDevelopmentDeployment Guide

Architecture

Architecture OverviewExplanation: AI Integration & Tenant SecurityExplanation: Dynamic Forms Engine DesignExplanation: Compliance Ledger DesignExplanation: Live Sync & Data FreshnessData FlowArchitecture Decision Records

Core Domain

Core DomainDatabase ReferenceLocations DomainAuth & RBACScheduling DomainReports DomainIncidents DomainUnified Operations InboxLive Sync Data FreshnessToast Sync PipelineNotifications DomainCoupons and Guest NPSAudit Log & Compliance ArchitectureDesign Audit FindingsAI Chat IntegrationAnalytics & Tips Integration

Frontend

Frontend ArchitectureFormsLoading SkeletonsComponentsPWA & Offline ShellScreenshots

API Reference

API Reference

Endpoints

POS Sales APIOptimization Data APISchedule Shifts APIEmployee Export APIReports APIIncidents APIAI Chat APIPush Notifications APIWebhooks APICron API

Contributing

ContributingCode Examples

Security

Security & Compliance

Release Notes

What's New

Explanation: Dynamic Forms Engine Design

Architectural explanation of JSONB schemas, dynamic runtime Zod validation, and local draft persistence in the forms engine

The Dynamic Forms Engine is the core component that allows restaurant managers to design, deploy, and analyze location-specific checklists (e.g., Temperature Logs, Opening Checklists, Incident Reports). This document explains the architecture of the engine, the selection of PostgreSQL JSONB schemas, dynamic runtime Zod validation, and stateful form autosaving.


Why JSONB over Normalized Relational Tables?

Checks and checklists in restaurants evolve rapidly. A manager might add a "Walk-in Temp" number field today, modify it to a checkbox tomorrow, or attach location-specific parameters.

Implementing this under a standard normalized relational model would require:

  1. Creating a form_fields table, form_field_options table, and an EAV (Entity-Attribute-Value) model for submissions.
  2. High query complexity (multiple joins) to render a single form or extract submissions.
  3. Complex migrations when structural properties of field validation parameters change.

JSONB Schema Architecture

Instead, forms and their dynamic field definitions are stored as JSONB directly in the forms table.

CREATE TABLE forms (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  description TEXT,
  fields JSONB NOT NULL, -- Array of FormField schema definitions
  is_active BOOLEAN DEFAULT true,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

This structural decision provides:

  • Zero Migration Overhead: Modifying, adding, or deleting fields is a simple JSON write. No database schema changes are required.
  • Schema Versioning: Submissions store the snapshot of the form schema at the moment of submission, guaranteeing that modifying a form definition does not break historical reporting.

Dynamic Zod Schema Generator

Since form fields are dynamic, we cannot compile a static TypeScript schema for validation. Instead, the form renderer parses the JSONB field definitions and constructs a Zod Schema at Runtime.

The parser maps each JSON field definition to its corresponding Zod validation primitive:

// apps/app/src/app/(authenticated)/forms/[formId]/form-submission-client.tsx
import { z } from "zod";

export function generateZodSchema(fields: FormField[]) {
  const schemaShape: Record<string, z.ZodTypeAny> = {};

  for (const field of fields) {
    let fieldSchema: z.ZodTypeAny;

    switch (field.type) {
      case "number":
        fieldSchema = z.number({
          invalid_type_error: `${field.label} must be a number`,
        });
        if (field.validation?.min !== undefined) {
          fieldSchema = (fieldSchema as z.ZodNumber).min(field.validation.min);
        }
        if (field.validation?.max !== undefined) {
          fieldSchema = (fieldSchema as z.ZodNumber).max(field.validation.max);
        }
        break;

      case "multiselect":
        fieldSchema = z.array(z.string()).min(field.required ? 1 : 0, {
          message: `Select at least one option for ${field.label}`,
        });
        break;

      case "checkbox":
        fieldSchema = z.boolean();
        break;

      case "date":
        fieldSchema = z.string().datetime({
          message: `${field.label} must be a valid date`,
        });
        break;

      case "email":
        fieldSchema = z.string().email({
          message: `${field.label} must be a valid email`,
        });
        break;

      default:
        // text and textarea
        fieldSchema = z.string();
        if (field.required) {
          fieldSchema = (fieldSchema as z.ZodString).min(1, {
            message: `${field.label} is required`,
          });
        }
        if (field.validation?.max) {
          fieldSchema = (fieldSchema as z.ZodString).max(field.validation.max);
        }
    }

    if (!field.required && field.type !== "checkbox" && field.type !== "multiselect") {
      fieldSchema = fieldSchema.optional().nullable();
    }

    schemaShape[field.id] = fieldSchema;
  }

  return z.object(schemaShape);
}

This dynamically constructed schema is passed directly into React Hook Form via the @hookform/resolvers/zod resolver.


State Lifecycle & autosave

Dynamic forms integrate React Hook Form state with the browser's localStorage to handle network disruptions or accidental closures common in busy restaurant environments.

graph TD
    A[User Opens Form] --> B(Draft in localStorage?)
    B -- Yes --> C[Restore Form State]
    B -- No --> D[Empty Form State]
    C --> E[User Types / Interacts]
    D --> E
    E --> F[Throttle Auto-save: 30s]
    F --> G[Save to localStorage]
    E --> H[Submit Button Clicked]
    H --> I[Zod Validation Run]
    I -- Valid --> J[Execute Server Action]
    J --> K[Clear localStorage Draft]
    J --> L[Redirect to List]
    I -- Invalid --> M[Render Form Errors]

Attachments

Dynamic forms do not support photo or file uploads. Use Shift Reports or Incidents for attachments.

Explanation: AI Integration & Tenant Security

Architectural explanation of the Vercel AI Gateway tool-calling agent, context window packing, and tenant isolation boundaries

Explanation: Compliance Ledger Design

Architectural explanation of the decoupled compliance ledger, service week boundaries, and dual filing obligations

On this page

Why JSONB over Normalized Relational Tables?JSONB Schema ArchitectureDynamic Zod Schema GeneratorState Lifecycle & autosaveAttachments