US-1155: Settings performance — memoization & lazy loading

This commit is contained in:
Brad Groux 2026-01-28 03:09:02 -06:00
parent 38252d8860
commit fa33c43368
12 changed files with 1117 additions and 46 deletions

View file

@ -490,3 +490,24 @@
{"type":"task.created","taskId":"task_20260128_W9Henc","status":"todo","id":"evt_t9G3qZADwCc7","timestamp":"2026-01-28T09:05:42.993Z"}
{"type":"task.created","taskId":"task_20260128_3SrSde","status":"todo","id":"evt_ljpYJmVetnxw","timestamp":"2026-01-28T09:05:43.001Z"}
{"type":"task.archived","taskId":"task_20260128_3SrSde","status":"todo","id":"evt_5l0jQ9llHLbY","timestamp":"2026-01-28T09:05:43.002Z"}
{"type":"task.status_changed","taskId":"task_20260128_6SbOnv","project":"veritas-kanban","status":"done","previousStatus":"in-progress","id":"evt_EK-e6y771Zso","timestamp":"2026-01-28T09:06:09.459Z"}
{"type":"task.status_changed","taskId":"task_20260128_-KNOy3","project":"veritas-kanban","status":"in-progress","previousStatus":"todo","id":"evt_M7RMF26KsnnP","timestamp":"2026-01-28T09:06:28.250Z"}
{"type":"task.status_changed","taskId":"task_20260128_CnogQO","project":"veritas-kanban","status":"in-progress","previousStatus":"todo","id":"evt_wtwQY0KgQwda","timestamp":"2026-01-28T09:07:23.934Z"}
{"type":"task.created","taskId":"task_20260128_g6jt0d","project":"my-project","status":"todo","id":"evt_OQJ7GpkKhgHw","timestamp":"2026-01-28T09:08:42.546Z"}
{"type":"task.created","taskId":"task_20260128_xefor9","status":"todo","id":"evt_jSQYKFtnuBFY","timestamp":"2026-01-28T09:08:42.549Z"}
{"type":"task.created","taskId":"task_20260128_Lfcgty","status":"todo","id":"evt_p9rXJODNAsFc","timestamp":"2026-01-28T09:08:42.551Z"}
{"type":"task.created","taskId":"task_20260128_fpL_Dd","status":"todo","id":"evt_ZlwfeOttZC0i","timestamp":"2026-01-28T09:08:42.553Z"}
{"type":"task.status_changed","taskId":"task_20260128_fpL_Dd","status":"in-progress","previousStatus":"todo","id":"evt_JL9t1-cWuX3_","timestamp":"2026-01-28T09:08:42.566Z"}
{"type":"task.created","taskId":"task_20260128_jaRqQ3","status":"todo","id":"evt_YprCvvQJN2SO","timestamp":"2026-01-28T09:08:42.568Z"}
{"type":"task.created","taskId":"task_20260128_qZPdbi","status":"todo","id":"evt_XIFHX_Iua532","timestamp":"2026-01-28T09:08:42.570Z"}
{"type":"task.created","taskId":"task_20260128_OFy8h-","status":"todo","id":"evt_-XOvuKgW52ea","timestamp":"2026-01-28T09:08:42.576Z"}
{"type":"task.archived","taskId":"task_20260128_OFy8h-","status":"todo","id":"evt_raxP0Uqjj_ej","timestamp":"2026-01-28T09:08:42.577Z"}
{"type":"task.created","taskId":"task_20260128_p3tQz-","project":"my-project","status":"todo","id":"evt_sv4JD80RG0Mp","timestamp":"2026-01-28T09:08:43.780Z"}
{"type":"task.created","taskId":"task_20260128_lYDQGj","status":"todo","id":"evt_V9D7YuRSiXlx","timestamp":"2026-01-28T09:08:43.783Z"}
{"type":"task.created","taskId":"task_20260128_U1AXxF","status":"todo","id":"evt_IkOyt779Bmx8","timestamp":"2026-01-28T09:08:43.785Z"}
{"type":"task.created","taskId":"task_20260128_u-jBCg","status":"todo","id":"evt_BrnjWauTH2NB","timestamp":"2026-01-28T09:08:43.787Z"}
{"type":"task.status_changed","taskId":"task_20260128_u-jBCg","status":"in-progress","previousStatus":"todo","id":"evt_vPgTJQzmY2-S","timestamp":"2026-01-28T09:08:43.800Z"}
{"type":"task.created","taskId":"task_20260128_Tp9HXB","status":"todo","id":"evt_3hurmcP_cfd4","timestamp":"2026-01-28T09:08:43.803Z"}
{"type":"task.created","taskId":"task_20260128_isj7oa","status":"todo","id":"evt_udJP0CW5DOjq","timestamp":"2026-01-28T09:08:43.805Z"}
{"type":"task.created","taskId":"task_20260128_mY2lH4","status":"todo","id":"evt_NK1qNmhq8BVa","timestamp":"2026-01-28T09:08:43.810Z"}
{"type":"task.archived","taskId":"task_20260128_mY2lH4","status":"todo","id":"evt_xt1yJ4fnooxC","timestamp":"2026-01-28T09:08:43.811Z"}

View file

@ -1,4 +1,48 @@
[
{
"id": "activity_1769591243934_a59soco89",
"type": "status_changed",
"taskId": "task_20260128_CnogQO",
"taskTitle": "US-1157: Sanitize settings import against prototype pollution",
"details": {
"from": "todo",
"status": "in-progress"
},
"timestamp": "2026-01-28T09:07:23.934Z"
},
{
"id": "activity_1769591188250_6ff1szybq",
"type": "status_changed",
"taskId": "task_20260128_-KNOy3",
"taskTitle": "US-1155: Settings performance — memoization & lazy loading",
"details": {
"from": "todo",
"status": "in-progress"
},
"timestamp": "2026-01-28T09:06:28.250Z"
},
{
"id": "activity_1769591169459_ugp657565",
"type": "status_changed",
"taskId": "task_20260128_6SbOnv",
"taskTitle": "US-1154: Add test coverage for settings features",
"details": {
"from": "in-progress",
"status": "done"
},
"timestamp": "2026-01-28T09:06:09.459Z"
},
{
"id": "activity_1769591162356_cdvv56zb6",
"type": "comment_added",
"taskId": "task_20260128_6SbOnv",
"taskTitle": "US-1154: Add test coverage for settings features",
"details": {
"author": "Veritas",
"preview": "Fixed test discovery (worktrees excluded), fixed e..."
},
"timestamp": "2026-01-28T09:06:02.356Z"
},
{
"id": "activity_1769590946523_ekw6m4yic",
"type": "task_created",

View file

@ -3,10 +3,28 @@ import { ConfigService } from '../services/config-service.js';
import { getTelemetryService } from '../services/telemetry-service.js';
import { getAttachmentService } from '../services/attachment-service.js';
import type { FeatureSettings } from '@veritas-kanban/shared';
import { FeatureSettingsPatchSchema } from '../schemas/feature-settings-schema.js';
const router: RouterType = Router();
const configService = new ConfigService();
// Simple rate limiter
const rateLimiter = new Map<string, { count: number; resetAt: number }>();
const RATE_LIMIT = 10;
const RATE_WINDOW_MS = 60_000;
function checkRateLimit(ip: string): boolean {
const now = Date.now();
const entry = rateLimiter.get(ip);
if (!entry || now > entry.resetAt) {
rateLimiter.set(ip, { count: 1, resetAt: now + RATE_WINDOW_MS });
return true;
}
if (entry.count >= RATE_LIMIT) return false;
entry.count++;
return true;
}
/**
* Sync feature settings to affected server-side services.
* Called on PATCH and on server startup.
@ -43,12 +61,27 @@ router.get('/features', async (_req, res) => {
// PATCH /api/settings/features — deep merge partial updates
router.patch('/features', async (req, res) => {
try {
const patch = req.body;
if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
return res.status(400).json({ error: 'Body must be a JSON object with feature settings' });
// Rate limiting
const clientIp = req.ip || req.socket.remoteAddress || 'unknown';
if (!checkRateLimit(clientIp)) {
return res.status(429).json({ error: 'Too many requests. Max 10 settings updates per minute.' });
}
// Validate with Zod — strips unknown keys, rejects dangerous ones
const parseResult = FeatureSettingsPatchSchema.safeParse(req.body);
if (!parseResult.success) {
return res.status(400).json({
error: 'Invalid settings payload',
details: parseResult.error.issues.map(i => i.message),
});
}
const patch = parseResult.data;
if (Object.keys(patch).length === 0) {
return res.status(400).json({ error: 'No valid settings provided' });
}
const updated = await configService.updateFeatureSettings(patch);
// Sync settings to server-side services
syncSettingsToServices(updated);
res.json(updated);
} catch (error) {

View file

@ -0,0 +1,74 @@
import { z } from 'zod';
// Dangerous keys check
const DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
function hasDangerousKeys(obj: unknown): boolean {
if (typeof obj !== 'object' || obj === null) return false;
for (const key of Object.keys(obj)) {
if (DANGEROUS_KEYS.includes(key)) return true;
if (hasDangerousKeys((obj as any)[key])) return true;
}
return false;
}
const BoardSettingsSchema = z.object({
showDashboard: z.boolean().optional(),
showArchiveSuggestions: z.boolean().optional(),
cardDensity: z.enum(['normal', 'compact']).optional(),
showPriorityIndicators: z.boolean().optional(),
showProjectBadges: z.boolean().optional(),
showSprintBadges: z.boolean().optional(),
enableDragAndDrop: z.boolean().optional(),
}).strict().optional();
const TaskBehaviorSettingsSchema = z.object({
enableTimeTracking: z.boolean().optional(),
enableSubtaskAutoComplete: z.boolean().optional(),
enableDependencies: z.boolean().optional(),
enableAttachments: z.boolean().optional(),
attachmentMaxFileSize: z.number().int().min(1024).max(100*1024*1024).optional(),
attachmentMaxPerTask: z.number().int().min(1).max(100).optional(),
attachmentMaxTotalSize: z.number().int().min(1024).max(500*1024*1024).optional(),
enableComments: z.boolean().optional(),
defaultPriority: z.enum(['none','low','medium','high','critical']).optional(),
}).strict().optional();
const AgentBehaviorSettingsSchema = z.object({
timeoutMinutes: z.number().int().min(5).max(480).optional(),
autoCommitOnComplete: z.boolean().optional(),
autoCleanupWorktrees: z.boolean().optional(),
enablePreview: z.boolean().optional(),
}).strict().optional();
const TelemetrySettingsSchema = z.object({
enabled: z.boolean().optional(),
retentionDays: z.number().int().min(7).max(365).optional(),
enableTraces: z.boolean().optional(),
enableActivityTracking: z.boolean().optional(),
}).strict().optional();
const NotificationSettingsSchema = z.object({
enabled: z.boolean().optional(),
onTaskComplete: z.boolean().optional(),
onAgentFailure: z.boolean().optional(),
onReviewNeeded: z.boolean().optional(),
channel: z.string().max(200).optional(),
}).strict().optional();
const ArchiveSettingsSchema = z.object({
autoArchiveEnabled: z.boolean().optional(),
autoArchiveAfterDays: z.number().int().min(1).max(365).optional(),
}).strict().optional();
export const FeatureSettingsPatchSchema = z.object({
board: BoardSettingsSchema,
tasks: TaskBehaviorSettingsSchema,
agents: AgentBehaviorSettingsSchema,
telemetry: TelemetrySettingsSchema,
notifications: NotificationSettingsSchema,
archive: ArchiveSettingsSchema,
}).strict().refine(val => !hasDangerousKeys(val), {
message: 'Payload contains forbidden keys (__proto__, constructor, prototype)',
});
export type FeatureSettingsPatch = z.infer<typeof FeatureSettingsPatchSchema>;

View file

@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, memo, useEffect } from 'react';
import type { ManagedListItem } from '@veritas-kanban/shared';
import { Button } from '../ui/button';
import { Input } from '../ui/input';
@ -52,7 +52,7 @@ interface SortableItemProps<T extends ManagedListItem> {
canDeleteCheck?: (id: string) => Promise<{ allowed: boolean; referenceCount: number; isDefault: boolean }>;
}
function SortableItem<T extends ManagedListItem>({
const SortableItem = memo(function SortableItem<T extends ManagedListItem>({
item,
onUpdate,
onDelete,
@ -194,7 +194,7 @@ function SortableItem<T extends ManagedListItem>({
</AlertDialog>
</>
);
}
}) as <T extends ManagedListItem>(props: SortableItemProps<T>) => React.JSX.Element;
export function ManagedListManager<T extends ManagedListItem>({
title,
@ -210,6 +210,7 @@ export function ManagedListManager<T extends ManagedListItem>({
}: ManagedListManagerProps<T>) {
const [newItemLabel, setNewItemLabel] = useState('');
const [isCreating, setIsCreating] = useState(false);
const [localItems, setLocalItems] = useState(items);
const sensors = useSensors(
useSensor(PointerSensor),
@ -218,17 +219,30 @@ export function ManagedListManager<T extends ManagedListItem>({
})
);
const handleDragEnd = async (event: DragEndEvent) => {
// Sync local state with incoming items
useEffect(() => {
setLocalItems(items);
}, [items]);
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
if (over && active.id !== over.id) {
const oldIndex = items.findIndex((item) => item.id === active.id);
const newIndex = items.findIndex((item) => item.id === over.id);
const oldIndex = localItems.findIndex((item) => item.id === active.id);
const newIndex = localItems.findIndex((item) => item.id === over.id);
const reordered = arrayMove(items, oldIndex, newIndex);
const reordered = arrayMove(localItems, oldIndex, newIndex);
const orderedIds = reordered.map((item) => item.id);
await onReorder(orderedIds);
// Optimistic update
setLocalItems(reordered);
// Fire onReorder in background
onReorder(orderedIds).catch((error) => {
// Rollback on error
console.error('Failed to reorder items:', error);
setLocalItems(items);
});
}
};
@ -261,10 +275,10 @@ export function ManagedListManager<T extends ManagedListItem>({
onDragEnd={handleDragEnd}
>
<SortableContext
items={items.map((item) => item.id)}
items={localItems.map((item) => item.id)}
strategy={verticalListSortingStrategy}
>
{items.map((item) => (
{localItems.map((item) => (
<SortableItem
key={item.id}
item={item}

View file

@ -1,4 +1,4 @@
import { useState, useRef, useCallback } from 'react';
import { useState, useRef, useCallback, lazy, Suspense } from 'react';
import {
Dialog,
DialogContent,
@ -24,6 +24,7 @@ import {
AlertDialogTrigger,
} from '@/components/ui/alert-dialog';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Skeleton } from '@/components/ui/skeleton';
import {
useFeatureSettings,
useDebouncedFeatureUpdate,
@ -34,17 +35,32 @@ import {
} from 'lucide-react';
import { DEFAULT_FEATURE_SETTINGS } from '@veritas-kanban/shared';
import { cn } from '@/lib/utils';
import {
GeneralTab,
BoardTab,
TasksTab,
AgentsTab,
DataTab,
NotificationsTab,
ManageTab,
} from './tabs';
import { SettingsErrorBoundary } from './shared';
// Lazy-load tab components
const LazyGeneralTab = lazy(() => import('./tabs/GeneralTab').then(m => ({ default: m.GeneralTab })));
const LazyBoardTab = lazy(() => import('./tabs/BoardTab').then(m => ({ default: m.BoardTab })));
const LazyTasksTab = lazy(() => import('./tabs/TasksTab').then(m => ({ default: m.TasksTab })));
const LazyAgentsTab = lazy(() => import('./tabs/AgentsTab').then(m => ({ default: m.AgentsTab })));
const LazyDataTab = lazy(() => import('./tabs/DataTab').then(m => ({ default: m.DataTab })));
const LazyNotificationsTab = lazy(() => import('./tabs/NotificationsTab').then(m => ({ default: m.NotificationsTab })));
const LazyManageTab = lazy(() => import('./tabs/ManageTab').then(m => ({ default: m.ManageTab })));
// ============ Tab Skeleton ============
function TabSkeleton() {
return (
<div className="space-y-4">
<Skeleton className="h-6 w-32" />
<div className="space-y-3">
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
</div>
</div>
);
}
// ============ Tab Configuration ============
type TabId = 'general' | 'board' | 'tasks' | 'agents' | 'data' | 'notifications' | 'manage';
@ -149,22 +165,31 @@ export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) {
}, [activeTab]);
const renderTab = () => {
switch (activeTab) {
case 'general':
return <SettingsErrorBoundary tabName="General"><GeneralTab /></SettingsErrorBoundary>;
case 'board':
return <SettingsErrorBoundary tabName="Board"><BoardTab /></SettingsErrorBoundary>;
case 'tasks':
return <SettingsErrorBoundary tabName="Tasks"><TasksTab /></SettingsErrorBoundary>;
case 'agents':
return <SettingsErrorBoundary tabName="Agents"><AgentsTab /></SettingsErrorBoundary>;
case 'data':
return <SettingsErrorBoundary tabName="Data"><DataTab /></SettingsErrorBoundary>;
case 'notifications':
return <SettingsErrorBoundary tabName="Notifications"><NotificationsTab /></SettingsErrorBoundary>;
case 'manage':
return <SettingsErrorBoundary tabName="Manage"><ManageTab /></SettingsErrorBoundary>;
}
return (
<Suspense fallback={<TabSkeleton />}>
{activeTab === 'general' && (
<SettingsErrorBoundary tabName="General"><LazyGeneralTab /></SettingsErrorBoundary>
)}
{activeTab === 'board' && (
<SettingsErrorBoundary tabName="Board"><LazyBoardTab /></SettingsErrorBoundary>
)}
{activeTab === 'tasks' && (
<SettingsErrorBoundary tabName="Tasks"><LazyTasksTab /></SettingsErrorBoundary>
)}
{activeTab === 'agents' && (
<SettingsErrorBoundary tabName="Agents"><LazyAgentsTab /></SettingsErrorBoundary>
)}
{activeTab === 'data' && (
<SettingsErrorBoundary tabName="Data"><LazyDataTab /></SettingsErrorBoundary>
)}
{activeTab === 'notifications' && (
<SettingsErrorBoundary tabName="Notifications"><LazyNotificationsTab /></SettingsErrorBoundary>
)}
{activeTab === 'manage' && (
<SettingsErrorBoundary tabName="Manage"><LazyManageTab /></SettingsErrorBoundary>
)}
</Suspense>
);
};
return (

View file

@ -1,7 +1,8 @@
import { memo } from 'react';
import { Input } from '@/components/ui/input';
import { SettingRow } from './SettingRow';
export function NumberRow({ label, description, value, onChange, min, max, unit }: {
export const NumberRow = memo(function NumberRow({ label, description, value, onChange, min, max, unit }: {
label: string;
description?: string;
value: number;
@ -31,4 +32,4 @@ export function NumberRow({ label, description, value, onChange, min, max, unit
</div>
</SettingRow>
);
}
});

View file

@ -1,4 +1,6 @@
export function SettingRow({ label, description, children }: {
import { memo } from 'react';
export const SettingRow = memo(function SettingRow({ label, description, children }: {
label: string;
description?: string;
children: React.ReactNode;
@ -14,4 +16,4 @@ export function SettingRow({ label, description, children }: {
<div className="flex-shrink-0">{children}</div>
</div>
);
}
});

View file

@ -1,7 +1,8 @@
import { memo } from 'react';
import { Switch } from '@/components/ui/switch';
import { SettingRow } from './SettingRow';
export function ToggleRow({ label, description, checked, onCheckedChange }: {
export const ToggleRow = memo(function ToggleRow({ label, description, checked, onCheckedChange }: {
label: string;
description?: string;
checked: boolean;
@ -12,4 +13,4 @@ export function ToggleRow({ label, description, checked, onCheckedChange }: {
<Switch checked={checked} onCheckedChange={onCheckedChange} />
</SettingRow>
);
}
});

741
web/src/lib/template-schema.d.ts vendored Normal file
View file

@ -0,0 +1,741 @@
/**
* Zod validation schema for task templates
* Enforces strict validation, size limits, and security checks
*/
import { z } from 'zod';
/**
* Task template schema with strict validation
*/
export declare const TaskTemplateSchema: z.ZodEffects<z.ZodObject<{
id: z.ZodOptional<z.ZodString>;
name: z.ZodEffects<z.ZodString, string, string>;
description: z.ZodEffects<z.ZodOptional<z.ZodString>, string | undefined, string | undefined>;
category: z.ZodEffects<z.ZodOptional<z.ZodString>, string | undefined, string | undefined>;
version: z.ZodOptional<z.ZodNumber>;
taskDefaults: z.ZodObject<{
type: z.ZodOptional<z.ZodString>;
priority: z.ZodOptional<z.ZodString>;
project: z.ZodOptional<z.ZodString>;
descriptionTemplate: z.ZodOptional<z.ZodString>;
agent: z.ZodOptional<z.ZodString>;
}, "strict", z.ZodTypeAny, {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
}, {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
}>;
subtaskTemplates: z.ZodOptional<z.ZodArray<z.ZodObject<{
title: z.ZodEffects<z.ZodString, string, string>;
order: z.ZodOptional<z.ZodNumber>;
}, "strict", z.ZodTypeAny, {
title: string;
order?: number | undefined;
}, {
title: string;
order?: number | undefined;
}>, "many">>;
blueprint: z.ZodOptional<z.ZodArray<z.ZodObject<{
refId: z.ZodEffects<z.ZodString, string, string>;
title: z.ZodEffects<z.ZodString, string, string>;
taskDefaults: z.ZodOptional<z.ZodObject<{
type: z.ZodOptional<z.ZodString>;
priority: z.ZodOptional<z.ZodString>;
project: z.ZodOptional<z.ZodString>;
descriptionTemplate: z.ZodOptional<z.ZodString>;
agent: z.ZodOptional<z.ZodString>;
}, "strict", z.ZodTypeAny, {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
}, {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
}>>;
subtaskTemplates: z.ZodOptional<z.ZodArray<z.ZodObject<{
title: z.ZodEffects<z.ZodString, string, string>;
order: z.ZodOptional<z.ZodNumber>;
}, "strict", z.ZodTypeAny, {
title: string;
order?: number | undefined;
}, {
title: string;
order?: number | undefined;
}>, "many">>;
blockedByRefs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
}, "strict", z.ZodTypeAny, {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}, {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}>, "many">>;
created: z.ZodOptional<z.ZodString>;
updated: z.ZodOptional<z.ZodString>;
}, "strict", z.ZodTypeAny, {
name: string;
taskDefaults: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
};
id?: string | undefined;
description?: string | undefined;
created?: string | undefined;
updated?: string | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
category?: string | undefined;
blueprint?: {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}[] | undefined;
version?: number | undefined;
}, {
name: string;
taskDefaults: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
};
id?: string | undefined;
description?: string | undefined;
created?: string | undefined;
updated?: string | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
category?: string | undefined;
blueprint?: {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}[] | undefined;
version?: number | undefined;
}>, {
name: string;
taskDefaults: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
};
id?: string | undefined;
description?: string | undefined;
created?: string | undefined;
updated?: string | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
category?: string | undefined;
blueprint?: {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}[] | undefined;
version?: number | undefined;
}, {
name: string;
taskDefaults: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
};
id?: string | undefined;
description?: string | undefined;
created?: string | undefined;
updated?: string | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
category?: string | undefined;
blueprint?: {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}[] | undefined;
version?: number | undefined;
}>;
/**
* Schema for importing templates (single or array)
*/
export declare const TemplateImportSchema: z.ZodUnion<[z.ZodEffects<z.ZodObject<{
id: z.ZodOptional<z.ZodString>;
name: z.ZodEffects<z.ZodString, string, string>;
description: z.ZodEffects<z.ZodOptional<z.ZodString>, string | undefined, string | undefined>;
category: z.ZodEffects<z.ZodOptional<z.ZodString>, string | undefined, string | undefined>;
version: z.ZodOptional<z.ZodNumber>;
taskDefaults: z.ZodObject<{
type: z.ZodOptional<z.ZodString>;
priority: z.ZodOptional<z.ZodString>;
project: z.ZodOptional<z.ZodString>;
descriptionTemplate: z.ZodOptional<z.ZodString>;
agent: z.ZodOptional<z.ZodString>;
}, "strict", z.ZodTypeAny, {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
}, {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
}>;
subtaskTemplates: z.ZodOptional<z.ZodArray<z.ZodObject<{
title: z.ZodEffects<z.ZodString, string, string>;
order: z.ZodOptional<z.ZodNumber>;
}, "strict", z.ZodTypeAny, {
title: string;
order?: number | undefined;
}, {
title: string;
order?: number | undefined;
}>, "many">>;
blueprint: z.ZodOptional<z.ZodArray<z.ZodObject<{
refId: z.ZodEffects<z.ZodString, string, string>;
title: z.ZodEffects<z.ZodString, string, string>;
taskDefaults: z.ZodOptional<z.ZodObject<{
type: z.ZodOptional<z.ZodString>;
priority: z.ZodOptional<z.ZodString>;
project: z.ZodOptional<z.ZodString>;
descriptionTemplate: z.ZodOptional<z.ZodString>;
agent: z.ZodOptional<z.ZodString>;
}, "strict", z.ZodTypeAny, {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
}, {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
}>>;
subtaskTemplates: z.ZodOptional<z.ZodArray<z.ZodObject<{
title: z.ZodEffects<z.ZodString, string, string>;
order: z.ZodOptional<z.ZodNumber>;
}, "strict", z.ZodTypeAny, {
title: string;
order?: number | undefined;
}, {
title: string;
order?: number | undefined;
}>, "many">>;
blockedByRefs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
}, "strict", z.ZodTypeAny, {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}, {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}>, "many">>;
created: z.ZodOptional<z.ZodString>;
updated: z.ZodOptional<z.ZodString>;
}, "strict", z.ZodTypeAny, {
name: string;
taskDefaults: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
};
id?: string | undefined;
description?: string | undefined;
created?: string | undefined;
updated?: string | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
category?: string | undefined;
blueprint?: {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}[] | undefined;
version?: number | undefined;
}, {
name: string;
taskDefaults: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
};
id?: string | undefined;
description?: string | undefined;
created?: string | undefined;
updated?: string | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
category?: string | undefined;
blueprint?: {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}[] | undefined;
version?: number | undefined;
}>, {
name: string;
taskDefaults: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
};
id?: string | undefined;
description?: string | undefined;
created?: string | undefined;
updated?: string | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
category?: string | undefined;
blueprint?: {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}[] | undefined;
version?: number | undefined;
}, {
name: string;
taskDefaults: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
};
id?: string | undefined;
description?: string | undefined;
created?: string | undefined;
updated?: string | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
category?: string | undefined;
blueprint?: {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}[] | undefined;
version?: number | undefined;
}>, z.ZodArray<z.ZodEffects<z.ZodObject<{
id: z.ZodOptional<z.ZodString>;
name: z.ZodEffects<z.ZodString, string, string>;
description: z.ZodEffects<z.ZodOptional<z.ZodString>, string | undefined, string | undefined>;
category: z.ZodEffects<z.ZodOptional<z.ZodString>, string | undefined, string | undefined>;
version: z.ZodOptional<z.ZodNumber>;
taskDefaults: z.ZodObject<{
type: z.ZodOptional<z.ZodString>;
priority: z.ZodOptional<z.ZodString>;
project: z.ZodOptional<z.ZodString>;
descriptionTemplate: z.ZodOptional<z.ZodString>;
agent: z.ZodOptional<z.ZodString>;
}, "strict", z.ZodTypeAny, {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
}, {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
}>;
subtaskTemplates: z.ZodOptional<z.ZodArray<z.ZodObject<{
title: z.ZodEffects<z.ZodString, string, string>;
order: z.ZodOptional<z.ZodNumber>;
}, "strict", z.ZodTypeAny, {
title: string;
order?: number | undefined;
}, {
title: string;
order?: number | undefined;
}>, "many">>;
blueprint: z.ZodOptional<z.ZodArray<z.ZodObject<{
refId: z.ZodEffects<z.ZodString, string, string>;
title: z.ZodEffects<z.ZodString, string, string>;
taskDefaults: z.ZodOptional<z.ZodObject<{
type: z.ZodOptional<z.ZodString>;
priority: z.ZodOptional<z.ZodString>;
project: z.ZodOptional<z.ZodString>;
descriptionTemplate: z.ZodOptional<z.ZodString>;
agent: z.ZodOptional<z.ZodString>;
}, "strict", z.ZodTypeAny, {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
}, {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
}>>;
subtaskTemplates: z.ZodOptional<z.ZodArray<z.ZodObject<{
title: z.ZodEffects<z.ZodString, string, string>;
order: z.ZodOptional<z.ZodNumber>;
}, "strict", z.ZodTypeAny, {
title: string;
order?: number | undefined;
}, {
title: string;
order?: number | undefined;
}>, "many">>;
blockedByRefs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
}, "strict", z.ZodTypeAny, {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}, {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}>, "many">>;
created: z.ZodOptional<z.ZodString>;
updated: z.ZodOptional<z.ZodString>;
}, "strict", z.ZodTypeAny, {
name: string;
taskDefaults: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
};
id?: string | undefined;
description?: string | undefined;
created?: string | undefined;
updated?: string | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
category?: string | undefined;
blueprint?: {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}[] | undefined;
version?: number | undefined;
}, {
name: string;
taskDefaults: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
};
id?: string | undefined;
description?: string | undefined;
created?: string | undefined;
updated?: string | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
category?: string | undefined;
blueprint?: {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}[] | undefined;
version?: number | undefined;
}>, {
name: string;
taskDefaults: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
};
id?: string | undefined;
description?: string | undefined;
created?: string | undefined;
updated?: string | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
category?: string | undefined;
blueprint?: {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}[] | undefined;
version?: number | undefined;
}, {
name: string;
taskDefaults: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
};
id?: string | undefined;
description?: string | undefined;
created?: string | undefined;
updated?: string | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
category?: string | undefined;
blueprint?: {
title: string;
refId: string;
taskDefaults?: {
type?: string | undefined;
project?: string | undefined;
priority?: string | undefined;
agent?: string | undefined;
descriptionTemplate?: string | undefined;
} | undefined;
subtaskTemplates?: {
title: string;
order?: number | undefined;
}[] | undefined;
blockedByRefs?: string[] | undefined;
}[] | undefined;
version?: number | undefined;
}>, "many">]>;
/**
* Type exports
*/
export type ValidatedTemplate = z.infer<typeof TaskTemplateSchema>;
export type TemplateImport = z.infer<typeof TemplateImportSchema>;

View file

@ -0,0 +1,114 @@
/**
* Zod validation schema for task templates
* Enforces strict validation, size limits, and security checks
*/
import { z } from 'zod';
// Dangerous keys that could lead to prototype pollution
const DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
/**
* Check if a string contains dangerous keys
*/
function hasDangerousKeys(str) {
return DANGEROUS_KEYS.some(key => str.includes(key));
}
/**
* Subtask template schema
*/
const SubtaskTemplateSchema = z.object({
title: z.string()
.min(1, 'Subtask title cannot be empty')
.refine(val => !hasDangerousKeys(val), {
message: 'Subtask title contains forbidden keys',
}),
order: z.number().int().min(0).optional(),
}).strict();
/**
* Blueprint task schema
*/
const BlueprintTaskSchema = z.object({
refId: z.string()
.min(1, 'Blueprint task refId cannot be empty')
.refine(val => !hasDangerousKeys(val), {
message: 'Blueprint refId contains forbidden keys',
}),
title: z.string()
.min(1, 'Blueprint task title cannot be empty')
.refine(val => !hasDangerousKeys(val), {
message: 'Blueprint task title contains forbidden keys',
}),
taskDefaults: z.object({
type: z.string().optional(),
priority: z.string().optional(),
project: z.string().optional(),
descriptionTemplate: z.string().optional(),
agent: z.string().optional(),
}).strict().optional(),
subtaskTemplates: z.array(SubtaskTemplateSchema).optional(),
blockedByRefs: z.array(z.string()).optional(),
}).strict();
/**
* Task template schema with strict validation
*/
export const TaskTemplateSchema = z.object({
id: z.string().optional(), // Optional for imports
name: z.string()
.min(1, 'Template name is required')
.max(100, 'Template name must be 100 characters or less')
.refine(val => !hasDangerousKeys(val), {
message: 'Template name contains forbidden keys',
}),
description: z.string()
.max(500, 'Template description must be 500 characters or less')
.optional()
.refine(val => !val || !hasDangerousKeys(val), {
message: 'Template description contains forbidden keys',
}),
category: z.string()
.optional()
.refine(val => !val || !hasDangerousKeys(val), {
message: 'Template category contains forbidden keys',
}),
version: z.number().int().min(0).optional(),
taskDefaults: z.object({
type: z.string().optional(),
priority: z.string().optional(),
project: z.string().optional(),
descriptionTemplate: z.string().optional(),
agent: z.string().optional(),
}).strict(),
subtaskTemplates: z.array(SubtaskTemplateSchema)
.max(50, 'Maximum 50 subtask templates allowed')
.optional(),
blueprint: z.array(BlueprintTaskSchema)
.max(20, 'Maximum 20 blueprint tasks allowed')
.optional(),
created: z.string().optional(),
updated: z.string().optional(),
}).strict()
.refine((data) => {
// Validate blueprint dependency references
if (!data.blueprint || data.blueprint.length === 0) {
return true;
}
const refIds = new Set(data.blueprint.map(task => task.refId));
for (const task of data.blueprint) {
if (task.blockedByRefs) {
for (const ref of task.blockedByRefs) {
if (!refIds.has(ref)) {
return false;
}
}
}
}
return true;
}, {
message: 'Blueprint dependency references must point to valid refIds within the same blueprint',
});
/**
* Schema for importing templates (single or array)
*/
export const TemplateImportSchema = z.union([
TaskTemplateSchema,
z.array(TaskTemplateSchema),
]);
//# sourceMappingURL=template-schema.js.map

View file

@ -0,0 +1 @@
{"version":3,"file":"template-schema.js","sourceRoot":"","sources":["template-schema.ts"],"names":[],"mappings":"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"}