task-template-builder
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-typesfirst.
2. Draft Storage (IndexedDB)
Note: IndexedDB draft persistence is not yet implemented in the template builder. The builder currently holds state in React only (lost on unmount). This section documents the intended pattern for future implementation.
idb-keyvalIS used elsewhere in the codebase — for task execution and action sheet drafts (task-execution-sheet.tsx,studio-task-action-sheet.tsx) — so the pattern is established and proven.
Why IndexedDB over localStorage (when implemented)?
- 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.
Intended 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 usinguseSortable.
Constraint:
dnd-kitrequires a stableidfor every item.- We generate a frontend-only
id(crypto.randomUUID()) for every field. - IMPORTANT: This
idmust 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:
- Include IDs: Keep the stable
idfields as they are part of the shared schema. - Nest Items: specific API structure requires
{ schema: { items: [...] } }. - 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)
}))
}
};
6. Shared Fields Insertion (Studio Settings Integration)
Task template authors can insert studio-managed shared fields directly from the builder.
- Source endpoint:
GET /studios/:studioId/settings/shared-fields - Admin shortcut route in
erify_studios:/studios/$studioId/shared-fields - Read access:
ADMINandMANAGERcan load the shared-field catalog for template authoring. - Canonical shared-field insertion uses exact shared key/type and sets
standard: true. - Repeated insertions (for loop-specific moderation data collection) should generate unique keys and be treated as loop-scoped template fields unless the key is exactly the canonical shared key.
- UI must lock shared-field
key/typeediting (label/description remain editable). - Only active shared fields (
is_active: true) should appear in the insertion picker. - Template create/edit pages must revalidate shared fields on mount (
refetchOnMount: 'always') to avoid stale picker options after settings updates. - Shared-field settings mutations must invalidate shared-field query keys so downstream routes immediately observe updates.
- If shared fields fail to load, template pages must show a visible warning that shared-field insertion is temporarily unavailable.
- Admin-only settings shortcuts must not be shown to manager users; non-admin authors should see guidance to ask a studio admin to create shared fields when the catalog is empty.
This keeps template payloads compatible with backend validation that enforces:
standard: truekey must exist in studio shared fields- shared-field type must match studio shared-field type exactly
Checklist
- Field validation uses shared Zod schema from
@eridu/api-types/task-management - Drafts are persisted to IndexedDB (not localStorage) — not yet implemented in builder, see §2
- Auto-save uses debounced writes (1s)
-
@dnd-kititems have stableidfromcrypto.randomUUID() - Payload is transformed before API submission (empty options filtered)
-
require_reasonoperators match field type (number/date/select/multiselect) - Shared-field insertions use
standard: truewith locked key/type semantics - Shared-field queries are revalidated on template page mount and invalidated after settings mutations
- Shared-field load failures are explicitly surfaced in template create/edit UI
- No duplicate validation logic between frontend and backend
More from allenlin90/eridu-services
service-pattern-nestjs
Comprehensive NestJS service implementation patterns. This skill should be used when implementing Model Services, Orchestration Services, or business logic with NestJS decorators.
8erify-authorization
Patterns for implementing authorization in erify_api with current StudioMembership + AdminGuard behavior, plus planned RBAC references
6data-validation
Provides comprehensive guidance for input validation, data serialization, and ID management in backend APIs. This skill should be used when designing validation schemas, transforming request/response data, mapping database IDs to external identifiers, and ensuring type safety across API boundaries.
6code-quality
Provides general code quality and best practices guidance applicable across languages and frameworks. Focuses on linting, testing, and type safety.
6repository-pattern-nestjs
Comprehensive Prisma repository implementation patterns for NestJS. This skill should be used when implementing repositories that extend BaseRepository or use Prisma delegates.
6jsonb-analytics-snapshot
Defines the JSONB Analytics Snapshot Pattern. This skill should be used when implementing analytics, dashboards, or any feature requiring aggregation of historical/immutable data where high read performance is required.
6