import { z } from "zod"; export const CreatePageDto = z.object({ title: z.string().max(200).default(""), // TASK-0100 S5: the web creates pages with `parentId: null` (sidebar "New // page", command palette). `.optional()` alone rejects explicit null (Zod), // which 400'd every top-level page create. Align with the sibling DTOs // (UpdatePageDto / ListPagesQuery both use .nullable().optional()) and the // service, which already maps `parentId ?? null`. parentId: z.string().min(1).nullable().optional(), // T-167: `whiteboard` creates a `Page.kind="whiteboard"` page whose canvas // lives in the 1:1 `whiteboards` sidecar (A-001). Like `database`, it only // gets the title block (the canvas IS the content — no body blocks). type: z.enum(["page", "database", "whiteboard"]).default("page"), teamId: z.string().min(1).optional(), templateId: z.string().min(1).optional() }); export type CreatePageInput = z.infer; export const UpdatePageDto = z.object({ title: z.string().max(200).optional(), parentId: z.string().nullable().optional(), teamId: z.string().nullable().optional(), archived: z.boolean().optional(), icon: z.string().max(20).nullable().optional(), coverUrl: z.string().url().max(2048).nullable().optional(), description: z.string().max(500).nullable().optional() }); export type UpdatePageInput = z.infer; /** Coerce a query-string `"true"`/`"1"` into a boolean (Zod has no * boolean query coercion of its own). Anything else (including omitted) * → undefined. */ const queryBool = (raw: unknown) => raw === "true" || raw === true || raw === "1" ? true : undefined; export const ListPagesQuery = z.object({ parentId: z.string().nullable().optional(), cursor: z.string().optional(), limit: z.string().optional(), // docs/api/pages.md §3 (authoritative): `trashed=true` lists ONLY the // workspace trash (Admin/Owner only — enforced in the service); // `favorites=true` lists ONLY the caller's favorite pages (ordered by // favorite position). Mutually exclusive with each other. trashed: z.preprocess(queryBool, z.boolean().optional()), favorites: z.preprocess(queryBool, z.boolean().optional()) }); export type ListPagesInput = z.infer; export const CreateTemplateDto = z.object({ name: z.string().min(1).max(100), icon: z.string().max(20).optional(), // T-141: directory grouping + search. category: z.string().min(1).max(50).optional(), description: z.string().max(500).optional(), // T-142: typed variable definitions (validated by the shared engine). variables: z.array(z.record(z.unknown())).max(50).optional() }); export type CreateTemplateInput = z.infer; export const ApplyTemplateDto = z.object({ workspaceId: z.string().min(1).optional(), parentId: z.string().min(1).nullable().optional(), title: z.string().max(200).optional(), // T-142: instantiation values for variable templates. values: z.record(z.unknown()).optional() }); export type ApplyTemplateInput = z.infer; // ---------- T-138: duplicate ---------- export const DuplicatePageDto = z.object({ parentId: z.string().min(1).nullable().optional(), title: z.string().max(200).optional() }); export type DuplicatePageInput = z.infer; // ---------- T-149: cross-workspace move ---------- export const MovePageDto = z.object({ toWorkspaceId: z.string().min(1), toParentId: z.string().min(1).nullable().optional() }); export type MovePageInput = z.infer; // ---------- T-150: version compare ---------- export const CompareVersionsQuery = z.object({ from: z.coerce.number().int().min(1), to: z.coerce.number().int().min(1) }); export type CompareVersionsInput = z.infer; // ---------- T-139: bulk actions ---------- export const BulkPagesDto = z.object({ action: z.enum(["trash", "archive", "unarchive", "favorite", "unfavorite"]), ids: z.array(z.string().min(1)).min(1).max(200) }); export type BulkPagesInput = z.infer; // ---------- T-171: whiteboard → page/task conversion ---------- /** * Convert a whiteboard SELECTION into a real page (or task page, A-006). * The caller passes element ids + a `target`; the server re-reads the canvas * (never trusts the caller's element contents), maps text-bearing elements to * blocks, and skips the rest (shapes/connectors/empty stickies). Element ids * the canvas does not know are dropped, not an error — a selection can go * stale between load and convert. */ export const ConvertSelectionDto = z.object({ elementIds: z.array(z.string().min(1)).min(1).max(100), target: z.enum(["page", "task"]).default("page"), title: z.string().max(200).default(""), parentId: z.string().min(1).nullable().optional() }); export type ConvertSelectionInput = z.infer;