skills/allenlin90/eridu-services/task-template-builder

task-template-builder

SKILL.md

Task Template Builder Pattern

This skill documents the architecture of the Task Template Builder in erify_studios.

Core Architecture

1. Schema Alignment (Single Source of Truth)

The Task Template Builder uses a Shared Zod Schema to ensure frontend and backend are always in sync.

  • Source: packages/api-types/src/task-management/template-definition.schema.ts
  • Frontend Usage: import { FieldItemSchema } from '@eridu/api-types/task-management'
  • Backend Usage: import { TemplateSchemaValidator } from '@eridu/api-types/task-management'

Crucial Rule: Never duplicate validation logic. If you need a new field or rule, update api-types first.

2. Draft Storage (IndexedDB)

To prevent data loss, drafts are saved to IndexedDB using idb-keyval.

Why IndexedDB over localStorage?

  • Capacity: Task templates can be large (HTML descriptions, many fields). localStorage (5MB) runs out quickly.
  • Async: Prevents blocking the main thread during auto-save of large objects.

Implementation:

const DRAFT_KEY = 'task_template_draft';

// Load
useEffect(() => {
  get(DRAFT_KEY).then(saved => setTemplate(saved || defaultTemplate));
}, []);

// Save (Debounced)
const debouncedSave = useDebounceCallback((data) => {
  set(DRAFT_KEY, data);
}, 1000);

3. Drag and Drop (@dnd-kit)

We use @dnd-kit/core and @dnd-kit/sortable for the field list.

Key Components:

  • DndContext: Wraps the list.
  • SortableContext: Wraps the items.
  • SortableFieldItem: Individual item component using useSortable.

Constraint:

  • dnd-kit requires a stable id for every item.
  • We generate a frontend-only id (crypto.randomUUID()) for every field.
  • IMPORTANT: This id must be stripped before sending to the backend!

4. Advanced Validation Logic (require_reason)

The builder supports complex conditional validation based on field type.

Structure:

require_reason: z.union([
  z.enum(['always', 'on-true', 'on-false']), // Primitive (checkbox)
  z.array(z.object({                         // Complex (number, date, select)
    op: z.enum(['lt', 'eq', 'in', ...]),
    value: z.any()
  }))
])

Supported Operators per Type:

  • Number: lt, lte, gt, gte, eq, neq
  • Date/Datetime: lt (Before), gt (After), eq (On)
  • Select: eq (Is), neq (Is Not)
  • Multiselect: in (Is One Of), not_in (Is Not One Of)

5. Payload Transformation

Before submitting to the API, the frontend payload must be transformed:

  1. Include IDs: Keep the stable id fields as they are part of the shared schema.
  2. Nest Items: specific API structure requires { schema: { items: [...] } }.
  3. Filter Empty: Remove empty options or invalid rules.
const payload = {
  name: data.name,
  schema: {
    items: data.items.map((item) => ({
      ...item,
      options: item.options?.filter(o => o.value)
    }))
  }
};

Checklist

  • Field validation uses shared Zod schema from @eridu/api-types/task-management
  • Drafts are persisted to IndexedDB (not localStorage)
  • Auto-save uses debounced writes (1s)
  • @dnd-kit items have stable id from crypto.randomUUID()
  • Payload is transformed before API submission (empty options filtered)
  • require_reason operators match field type (number/date/select/multiselect)
  • No duplicate validation logic between frontend and backend
Weekly Installs
4
GitHub Stars
1
First Seen
4 days ago
Installed on
mcpjam4
claude-code4
replit4
junie4
windsurf4
zencoder4