From 3c66e110268cf320838df0b379644241a69a6c29 Mon Sep 17 00:00:00 2001 From: sid597 Date: Sat, 3 Jan 2026 23:40:51 +0530 Subject: [PATCH 1/5] zod schema for all the settings, I think --- .../settings/utils/zodSchema.example.ts | 597 ++++++++++++++++++ .../components/settings/utils/zodSchema.ts | 268 ++++++++ 2 files changed, 865 insertions(+) create mode 100644 apps/roam/src/components/settings/utils/zodSchema.example.ts create mode 100644 apps/roam/src/components/settings/utils/zodSchema.ts diff --git a/apps/roam/src/components/settings/utils/zodSchema.example.ts b/apps/roam/src/components/settings/utils/zodSchema.example.ts new file mode 100644 index 000000000..a5dc20b7b --- /dev/null +++ b/apps/roam/src/components/settings/utils/zodSchema.example.ts @@ -0,0 +1,597 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import type { + CanvasSettings, + SuggestiveRules, + DiscourseNodeSettings, + FeatureFlags, + ExportSettings, + PageGroup, + SuggestiveModeGlobalSettings, + LeftSidebarGlobalSettings, + GlobalSettings, + PersonalSection, + LeftSidebarPersonalSettings, + QueryFilter, + QuerySettings, + PersonalSettings, + GithubSettings, + RoamBlock, +} from "./zodSchema"; + +export const exampleCanvasSettings: CanvasSettings = { + color: "#4A90D9", + alias: "CLM", + "key-image": true, + "key-image-option": "query-builder", + "query-builder-alias": "Key Image Query", +}; + +export const exampleSuggestiveRules: SuggestiveRules = { + template: [ + { text: "Summary::", children: [] }, + { text: "Key Points::", children: [] }, + ], + embeddingRef: "((block-uid-123))", + embeddingRefUid: "block-uid-123", + isFirstChild: { + uid: "first-child-uid", + value: true, + }, +}; + +export const exampleDiscourseNodeSettings: DiscourseNodeSettings = { + text: "Claim", + type: "discourse-graph/nodes/claim", + format: "[[CLM]] - {content}", + shortcut: "C", + tag: "#claim", + description: "A statement or assertion that can be supported or refuted", + specification: [ + { + type: "has title", + text: "starts with [[CLM]]", + }, + ], + specificationUid: "spec-uid-123", + template: [ + { text: "Summary::", children: [] }, + { text: "Evidence::", children: [] }, + { text: "Counterarguments::", children: [] }, + ], + templateUid: "template-uid-123", + canvasSettings: { + color: "#4A90D9", + alias: "CLM", + }, + graphOverview: true, + attributes: { + Status: "status-attr-uid", + Confidence: "confidence-attr-uid", + }, + overlay: "Status", + index: [ + { + type: "filter", + condition: "has attribute", + attribute: "Status", + }, + ], + indexUid: "index-uid-123", + suggestiveRules: { + template: [], + embeddingRef: "((embed-ref))", + embeddingRefUid: "embed-ref", + isFirstChild: { + uid: "is-first-child-uid", + value: false, + }, + }, + embeddingRef: "((main-embed-ref))", + embeddingRefUid: "main-embed-ref", + isFirstChild: { + uid: "main-first-child-uid", + value: true, + }, + backedBy: "user", +}; + +export const exampleFeatureFlags: FeatureFlags = { + "Enable Left Sidebar": true, + "Suggestive Mode Enabled": true, + "Reified Relation Triples": false, +}; + +export const defaultFeatureFlags: FeatureFlags = { + "Enable Left Sidebar": false, + "Suggestive Mode Enabled": false, + "Reified Relation Triples": false, +}; + +export const exampleExportSettings: ExportSettings = { + "Remove Special Characters": true, + "Resolve Block References": true, + "Resolve Block Embeds": false, + "Append Referenced Node": true, + "Link Type": "wikilinks", + "Max Filename Length": 128, + Frontmatter: [ + "title: {{page-title}}", + "date: {{date}}", + "tags: {{tags}}", + "type: discourse-node", + ], +}; + +export const examplePageGroup: PageGroup = { + name: "Research Papers", + pages: ["page-uid-1", "page-uid-2", "page-uid-3"], +}; + +export const exampleSuggestiveModeGlobalSettings: SuggestiveModeGlobalSettings = + { + "Include Current Page Relations": true, + "Include Parent And Child Blocks": true, + "Page Groups": [ + { + name: "Research Papers", + pages: ["paper-1-uid", "paper-2-uid"], + }, + { + name: "Meeting Notes", + pages: ["meeting-1-uid", "meeting-2-uid", "meeting-3-uid"], + }, + ], + }; + +export const exampleLeftSidebarGlobalSettings: LeftSidebarGlobalSettings = { + Children: ["daily-notes-uid", "quick-capture-uid", "inbox-uid"], + Settings: { + Collapsable: true, + Folded: false, + }, +}; + +export const exampleGlobalSettings: GlobalSettings = { + Trigger: ";;", + "Canvas Page Format": "Canvas - {date} - {title}", + "Left Sidebar": { + Children: ["daily-notes-uid", "quick-capture-uid", "inbox-uid"], + Settings: { + Collapsable: true, + Folded: false, + }, + }, + Export: { + "Remove Special Characters": true, + "Resolve Block References": true, + "Resolve Block Embeds": false, + "Append Referenced Node": true, + "Link Type": "wikilinks", + "Max Filename Length": 128, + Frontmatter: ["title: {{page-title}}", "date: {{date}}"], + }, + "Suggestive Mode": { + "Include Current Page Relations": true, + "Include Parent And Child Blocks": true, + "Page Groups": [ + { + name: "Research", + pages: ["research-uid-1", "research-uid-2"], + }, + ], + }, +}; + +export const defaultGlobalSettings: GlobalSettings = { + Trigger: "", + "Canvas Page Format": "", + "Left Sidebar": { + Children: [], + Settings: { + Collapsable: false, + Folded: false, + }, + }, + Export: { + "Remove Special Characters": false, + "Resolve Block References": false, + "Resolve Block Embeds": false, + "Append Referenced Node": false, + "Link Type": "alias", + "Max Filename Length": 64, + Frontmatter: [], + }, + "Suggestive Mode": { + "Include Current Page Relations": false, + "Include Parent And Child Blocks": false, + "Page Groups": [], + }, +}; + +export const examplePersonalSection: PersonalSection = { + Children: [ + { Page: "daily-notes-uid", Alias: "Daily Notes" }, + { Page: "inbox-uid", Alias: "Inbox" }, + { Page: "projects-uid", Alias: "" }, + ], + Settings: { + "Truncate-result?": 100, + Folded: false, + }, +}; + +export const exampleLeftSidebarPersonalSettings: LeftSidebarPersonalSettings = { + "My Workspace": { + Children: [ + { Page: "daily-notes-uid", Alias: "Daily Notes" }, + { Page: "inbox-uid", Alias: "Inbox" }, + ], + Settings: { + "Truncate-result?": 75, + Folded: false, + }, + }, + Research: { + Children: [ + { Page: "papers-uid", Alias: "Papers" }, + { Page: "notes-uid", Alias: "Notes" }, + { Page: "ideas-uid", Alias: "Ideas" }, + ], + Settings: { + "Truncate-result?": 50, + Folded: true, + }, + }, +}; + +export const exampleQueryFilter: QueryFilter = { + includes: true, + key: "node-type", + value: "Claim", +}; + +export const exampleQuerySettings: QuerySettings = { + "Hide Query Metadata": true, + "Default Page Size": 25, + "Query Pages": ["query-page-uid-1", "query-page-uid-2"], + "Default Filters": [ + { includes: true, key: "node-type", value: "Claim" }, + { includes: false, key: "status", value: "archived" }, + ], +}; + +export const examplePersonalSettings: PersonalSettings = { + "Left Sidebar": { + "My Workspace": { + Children: [ + { Page: "daily-notes-uid", Alias: "Daily Notes" }, + { Page: "inbox-uid", Alias: "Inbox" }, + ], + Settings: { + "Truncate-result?": 75, + Folded: false, + }, + }, + Research: { + Children: [ + { Page: "papers-uid", Alias: "Papers" }, + { Page: "notes-uid", Alias: "Notes" }, + ], + Settings: { + "Truncate-result?": 50, + Folded: true, + }, + }, + }, + "Personal Node Menu Trigger": ";;", + "Node Search Menu Trigger": "//", + "Discourse Tool Shortcut": "d", + "Discourse Context Overlay": true, + "Suggestive Mode Overlay": true, + "Overlay in Canvas": false, + "Text Selection Popup": true, + "Disable Sidebar Open": false, + "Page Preview": true, + "Hide Feedback Button": false, + "Streamline Styling": true, + "Auto Canvas Relations": true, + "Disable Product Diagnostics": false, + Query: { + "Hide Query Metadata": true, + "Default Page Size": 25, + "Query Pages": ["query-page-uid-1"], + "Default Filters": [{ includes: true, key: "node-type", value: "Claim" }], + }, +}; + +export const defaultPersonalSettings: PersonalSettings = { + "Left Sidebar": {}, + "Personal Node Menu Trigger": "", + "Node Search Menu Trigger": "", + "Discourse Tool Shortcut": "", + "Discourse Context Overlay": false, + "Suggestive Mode Overlay": false, + "Overlay in Canvas": false, + "Text Selection Popup": true, + "Disable Sidebar Open": false, + "Page Preview": false, + "Hide Feedback Button": false, + "Streamline Styling": false, + "Auto Canvas Relations": false, + "Disable Product Diagnostics": false, + Query: { + "Hide Query Metadata": false, + "Default Page Size": 10, + "Query Pages": [], + "Default Filters": [], + }, +}; + +export const exampleGithubSettings: GithubSettings = { + "oauth-github": "ghp_xxxxxxxxxxxxxxxxxxxx", + "selected-repo": "username/repository-name", +}; + +/** + * Query Block (scratch) Structure Reference + * + * The "scratch" block is a child of {{query block}} and contains all query configuration. + * It has three main children: custom, selections, and conditions. + * + * Structure: + * - scratch + * - custom // Custom return node configuration + * - {custom node text} // Optional: custom node identifier + * - enabled // Optional: flag to enable custom node + * - selections // Column selections for results + * - {variable name} // e.g. "node", "Created Date" + * - {label} // Optional: display label for column + * - conditions // Query conditions + * - clause | not | or | not or // Condition type + * - source // Required for clause/not + * - {value} // e.g. "node", node type name + * - relation // Required for clause/not + * - {value} // e.g. "is a", "has title", "references" + * - target // Optional for clause/not + * - {value} // e.g. "Claim", regex pattern + */ + +export const exampleQueryBlockMinimal: RoamBlock = { + text: "scratch", + children: [ + { text: "custom" }, + { text: "selections" }, + { + text: "conditions", + children: [ + { + text: "clause", + children: [ + { text: "source", children: [{ text: "node" }] }, + { text: "relation" }, + ], + }, + ], + }, + ], +}; + +export const exampleQueryBlockSimple: RoamBlock = { + text: "scratch", + children: [ + { text: "custom" }, + { text: "selections" }, + { + text: "conditions", + children: [ + { + text: "clause", + children: [ + { text: "source", children: [{ text: "node" }] }, + { text: "relation", children: [{ text: "is a" }] }, + { text: "target", children: [{ text: "Claim" }] }, + ], + }, + ], + }, + ], +}; + +export const exampleQueryBlockWithSelections: RoamBlock = { + text: "scratch", + children: [ + { text: "custom" }, + { + text: "selections", + children: [ + { text: "node", children: [{ text: "Title" }] }, + { text: "Created Date", children: [{ text: "Created" }] }, + { text: "Author" }, + ], + }, + { + text: "conditions", + children: [ + { + text: "clause", + children: [ + { text: "source", children: [{ text: "node" }] }, + { text: "relation", children: [{ text: "is a" }] }, + { text: "target", children: [{ text: "Claim" }] }, + ], + }, + ], + }, + ], +}; + +export const exampleQueryBlockWithCustom: RoamBlock = { + text: "scratch", + children: [ + { + text: "custom", + children: [{ text: "myCustomNode" }, { text: "enabled" }], + }, + { text: "selections" }, + { + text: "conditions", + children: [ + { + text: "clause", + children: [ + { text: "source", children: [{ text: "node" }] }, + { text: "relation", children: [{ text: "is a" }] }, + { text: "target", children: [{ text: "Evidence" }] }, + ], + }, + ], + }, + ], +}; + +export const exampleQueryBlockMultipleConditions: RoamBlock = { + text: "scratch", + children: [ + { text: "custom" }, + { + text: "selections", + children: [ + { text: "node", children: [{ text: "Claim" }] }, + { text: "target", children: [{ text: "Supporting Evidence" }] }, + ], + }, + { + text: "conditions", + children: [ + { + text: "clause", + children: [ + { text: "source", children: [{ text: "node" }] }, + { text: "relation", children: [{ text: "is a" }] }, + { text: "target", children: [{ text: "Claim" }] }, + ], + }, + { + text: "clause", + children: [ + { text: "source", children: [{ text: "node" }] }, + { text: "relation", children: [{ text: "Supported By" }] }, + { text: "target", children: [{ text: "target" }] }, + ], + }, + { + text: "clause", + children: [ + { text: "source", children: [{ text: "target" }] }, + { text: "relation", children: [{ text: "is a" }] }, + { text: "target", children: [{ text: "Evidence" }] }, + ], + }, + ], + }, + ], +}; + +// Query block with NOT condition +export const exampleQueryBlockWithNot: RoamBlock = { + text: "scratch", + children: [ + { text: "custom" }, + { text: "selections" }, + { + text: "conditions", + children: [ + { + text: "clause", + children: [ + { text: "source", children: [{ text: "node" }] }, + { text: "relation", children: [{ text: "is a" }] }, + { text: "target", children: [{ text: "Claim" }] }, + ], + }, + { + text: "not", + children: [ + { text: "source", children: [{ text: "node" }] }, + { text: "relation", children: [{ text: "has attribute" }] }, + { text: "target", children: [{ text: "Archived" }] }, + ], + }, + ], + }, + ], +}; + +// Query block with OR condition (nested) +export const exampleQueryBlockWithOr: RoamBlock = { + text: "scratch", + children: [ + { text: "custom" }, + { text: "selections" }, + { + text: "conditions", + children: [ + { + text: "clause", + children: [ + { text: "source", children: [{ text: "node" }] }, + { text: "relation", children: [{ text: "is a" }] }, + { text: "target", children: [{ text: "Claim" }] }, + ], + }, + { + text: "or", + children: [ + { + text: "0", + children: [ + { + text: "clause", + children: [ + { text: "source", children: [{ text: "node" }] }, + { text: "relation", children: [{ text: "has attribute" }] }, + { text: "target", children: [{ text: "High Priority" }] }, + ], + }, + ], + }, + { + text: "1", + children: [ + { + text: "clause", + children: [ + { text: "source", children: [{ text: "node" }] }, + { text: "relation", children: [{ text: "has attribute" }] }, + { text: "target", children: [{ text: "Urgent" }] }, + ], + }, + ], + }, + ], + }, + ], + }, + ], +}; + +export const exampleQueryBlockWithRegex: RoamBlock = { + text: "scratch", + children: [ + { text: "custom" }, + { text: "selections" }, + { + text: "conditions", + children: [ + { + text: "clause", + children: [ + { text: "source", children: [{ text: "node" }] }, + { text: "relation", children: [{ text: "has title" }] }, + { text: "target", children: [{ text: "/^\\[\\[CLM\\]\\]/" }] }, + ], + }, + ], + }, + ], +}; diff --git a/apps/roam/src/components/settings/utils/zodSchema.ts b/apps/roam/src/components/settings/utils/zodSchema.ts new file mode 100644 index 000000000..81aa747f6 --- /dev/null +++ b/apps/roam/src/components/settings/utils/zodSchema.ts @@ -0,0 +1,268 @@ +import { z } from "zod"; + +/* eslint-disable @typescript-eslint/naming-convention */ + +export const CanvasSettingsSchema = z.object({ + color: z.string().default(""), + alias: z.string().default(""), + "key-image": z.boolean().default(false), + "key-image-option": z + .enum(["first-image", "query-builder"]) + .default("first-image"), + "query-builder-alias": z.string().default(""), +}); + +export const SuggestiveRulesSchema = z.object({ + template: z.array(z.unknown()).default([]), + embeddingRef: z.string().optional(), + embeddingRefUid: z.string().optional(), + isFirstChild: z + .object({ + uid: z.string(), + value: z.boolean(), + }) + .optional(), +}); + +export const AttributesSchema = z.record(z.string(), z.string()).default({}); + +const stringWithDefault = (defaultVal: string) => + z + .string() + .nullable() + .optional() + .transform((val) => val ?? defaultVal); + +const booleanWithDefault = (defaultVal: boolean) => + z + .boolean() + .nullable() + .optional() + .transform((val) => val ?? defaultVal); + +export const DiscourseNodeSchema = z.object({ + text: z.string(), + type: z.string(), + format: stringWithDefault(""), + shortcut: stringWithDefault(""), + tag: stringWithDefault(""), + description: stringWithDefault(""), + specification: z + .array(z.unknown()) + .nullable() + .optional() + .transform((val) => val ?? []), + specificationUid: stringWithDefault(""), + template: z + .array(z.unknown()) + .nullable() + .optional() + .transform((val) => val ?? []), + templateUid: stringWithDefault(""), + canvasSettings: z + .record(z.string(), z.string()) + .nullable() + .optional() + .transform((val) => val ?? {}), + graphOverview: booleanWithDefault(false), + attributes: z + .record(z.string(), z.string()) + .nullable() + .optional() + .transform((val) => val ?? {}), + overlay: stringWithDefault(""), + index: z.unknown().nullable().optional(), + indexUid: stringWithDefault(""), + suggestiveRules: SuggestiveRulesSchema.nullable().optional(), + embeddingRef: stringWithDefault(""), + embeddingRefUid: stringWithDefault(""), + isFirstChild: z + .object({ + uid: z.string(), + value: z.boolean(), + }) + .nullable() + .optional(), + backedBy: z + .enum(["user", "default", "relation"]) + .nullable() + .transform((val) => val ?? "user"), +}); + +export const FeatureFlagsSchema = z.object({ + "Enable Left Sidebar": z.boolean().default(false), + "Suggestive Mode Enabled": z.boolean().default(false), + "Reified Relation Triples": z.boolean().default(false), +}); + +export const ExportSettingsSchema = z.object({ + "Remove Special Characters": z.boolean().default(false), + "Resolve Block References": z.boolean().default(false), + "Resolve Block Embeds": z.boolean().default(false), + "Append Referenced Node": z.boolean().default(false), + "Link Type": z.enum(["alias", "wikilinks", "roam url"]).default("alias"), + "Max Filename Length": z.number().default(64), + Frontmatter: z.array(z.string()).default([]), +}); + +export const PageGroupSchema = z.object({ + name: z.string(), + pages: z.array(z.string()).default([]), +}); + +export const SuggestiveModeGlobalSettingsSchema = z.object({ + "Include Current Page Relations": z.boolean().default(false), + "Include Parent And Child Blocks": z.boolean().default(false), + "Page Groups": z.array(PageGroupSchema).default([]), +}); + +export const LeftSidebarGlobalSettingsSchema = z.object({ + Children: z.array(z.string()).default([]), + Settings: z + .object({ + Collapsable: z.boolean().default(false), + Folded: z.boolean().default(false), + }) + .default({}), +}); + +export const GlobalSettingsSchema = z.object({ + Trigger: z.string().default(""), + "Canvas Page Format": z.string().default(""), + "Left Sidebar": LeftSidebarGlobalSettingsSchema.default({}), + Export: ExportSettingsSchema.default({}), + "Suggestive Mode": SuggestiveModeGlobalSettingsSchema.default({}), +}); + +export const PersonalSectionSchema = z.object({ + Children: z + .array( + z.object({ + Page: z.string(), + Alias: z.string().default(""), + }), + ) + .default([]), + Settings: z + .object({ + "Truncate-result?": z.number().default(75), + Folded: z.boolean().default(false), + }) + .default({}), +}); + +export const LeftSidebarPersonalSettingsSchema = z + .record(z.string(), PersonalSectionSchema) + .default({}); + +export const QueryFilterSchema = z.object({ + includes: z.boolean().default(true), + key: z.string(), + value: z.string(), +}); + +export const QuerySettingsSchema = z.object({ + "Hide Query Metadata": z.boolean().default(false), + "Default Page Size": z.number().default(10), + "Query Pages": z.array(z.string()).default([]), + "Default Filters": z.array(QueryFilterSchema).default([]), +}); + +export const PersonalSettingsSchema = z.object({ + "Left Sidebar": LeftSidebarPersonalSettingsSchema, + "Personal Node Menu Trigger": z.string().default(""), + "Node Search Menu Trigger": z.string().default(""), + "Discourse Tool Shortcut": z.string().default(""), + "Discourse Context Overlay": z.boolean().default(false), + "Suggestive Mode Overlay": z.boolean().default(false), + "Overlay in Canvas": z.boolean().default(false), + "Text Selection Popup": z.boolean().default(true), + "Disable Sidebar Open": z.boolean().default(false), + "Page Preview": z.boolean().default(false), + "Hide Feedback Button": z.boolean().default(false), + "Streamline Styling": z.boolean().default(false), + "Auto Canvas Relations": z.boolean().default(false), + "Disable Product Diagnostics": z.boolean().default(false), + Query: QuerySettingsSchema.default({}), +}); + +export const GithubSettingsSchema = z.object({ + "oauth-github": z.string().optional(), + "selected-repo": z.string().optional(), +}); + +const RoamBlockNodeSchema: z.ZodType = z.lazy(() => + z.object({ + text: z.string(), + children: z.array(RoamBlockNodeSchema).optional(), + }), +); + +type RoamBlockNode = { + text: string; + children?: RoamBlockNode[]; +}; + +export const QueryClauseSchema = z.object({ + text: z.enum(["clause", "not"]), + children: z.array( + z.object({ + text: z.enum(["source", "relation", "target"]), + children: z.array(z.object({ text: z.string() })).optional(), + }), + ), +}); + +export const QueryNestedConditionSchema = z.object({ + text: z.enum(["or", "not or"]), + children: z.array(z.unknown()), +}); + +export const QuerySelectionSchema = z.object({ + text: z.string(), + children: z.array(z.object({ text: z.string() })).optional(), +}); + +export const QueryCustomSchema = z.object({ + text: z.literal("custom"), + children: z + .tuple([ + z.object({ text: z.string() }), + z.object({ text: z.literal("enabled") }).optional(), + ]) + .optional(), +}); + +export const QueryBlockSchema = z.object({ + text: z.literal("scratch"), + children: z.array(RoamBlockNodeSchema), +}); + +/* eslint-enable @typescript-eslint/naming-convention */ + +export type CanvasSettings = z.infer; +export type SuggestiveRules = z.infer; +export type DiscourseNodeSettings = z.infer; +export type FeatureFlags = z.infer; +export type ExportSettings = z.infer; +export type PageGroup = z.infer; +export type SuggestiveModeGlobalSettings = z.infer< + typeof SuggestiveModeGlobalSettingsSchema +>; +export type LeftSidebarGlobalSettings = z.infer< + typeof LeftSidebarGlobalSettingsSchema +>; +export type GlobalSettings = z.infer; +export type PersonalSection = z.infer; +export type LeftSidebarPersonalSettings = z.infer< + typeof LeftSidebarPersonalSettingsSchema +>; +export type QueryFilter = z.infer; +export type QuerySettings = z.infer; +export type PersonalSettings = z.infer; +export type GithubSettings = z.infer; +export type QueryBlock = z.infer; +export type QueryClause = z.infer; +export type QuerySelection = z.infer; +export type QueryCustom = z.infer; +export type RoamBlock = RoamBlockNode; From 8453f18817eee12b328775765caa8260e8ece7ef Mon Sep 17 00:00:00 2001 From: sid597 Date: Sun, 4 Jan 2026 21:32:22 +0530 Subject: [PATCH 2/5] fix coderabbit --- apps/roam/src/components/settings/utils/zodSchema.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/roam/src/components/settings/utils/zodSchema.ts b/apps/roam/src/components/settings/utils/zodSchema.ts index 81aa747f6..caa92a29d 100644 --- a/apps/roam/src/components/settings/utils/zodSchema.ts +++ b/apps/roam/src/components/settings/utils/zodSchema.ts @@ -59,11 +59,7 @@ export const DiscourseNodeSchema = z.object({ .optional() .transform((val) => val ?? []), templateUid: stringWithDefault(""), - canvasSettings: z - .record(z.string(), z.string()) - .nullable() - .optional() - .transform((val) => val ?? {}), + canvasSettings: CanvasSettingsSchema.partial().nullable().optional(), graphOverview: booleanWithDefault(false), attributes: z .record(z.string(), z.string()) From 42e44b395557fb5bef0bfd5d97d64bb882394d07 Mon Sep 17 00:00:00 2001 From: sid597 Date: Mon, 5 Jan 2026 16:04:01 +0530 Subject: [PATCH 3/5] address example file comments --- .../settings/utils/zodSchema.example.ts | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/apps/roam/src/components/settings/utils/zodSchema.example.ts b/apps/roam/src/components/settings/utils/zodSchema.example.ts index a5dc20b7b..c68c28c1a 100644 --- a/apps/roam/src/components/settings/utils/zodSchema.example.ts +++ b/apps/roam/src/components/settings/utils/zodSchema.example.ts @@ -18,7 +18,7 @@ import type { RoamBlock, } from "./zodSchema"; -export const exampleCanvasSettings: CanvasSettings = { +const canvasSettings: CanvasSettings = { color: "#4A90D9", alias: "CLM", "key-image": true, @@ -26,7 +26,7 @@ export const exampleCanvasSettings: CanvasSettings = { "query-builder-alias": "Key Image Query", }; -export const exampleSuggestiveRules: SuggestiveRules = { +const suggestiveRules: SuggestiveRules = { template: [ { text: "Summary::", children: [] }, { text: "Key Points::", children: [] }, @@ -39,7 +39,7 @@ export const exampleSuggestiveRules: SuggestiveRules = { }, }; -export const exampleDiscourseNodeSettings: DiscourseNodeSettings = { +const discourseNodeSettings: DiscourseNodeSettings = { text: "Claim", type: "discourse-graph/nodes/claim", format: "[[CLM]] - {content}", @@ -95,19 +95,19 @@ export const exampleDiscourseNodeSettings: DiscourseNodeSettings = { backedBy: "user", }; -export const exampleFeatureFlags: FeatureFlags = { +const featureFlags: FeatureFlags = { "Enable Left Sidebar": true, "Suggestive Mode Enabled": true, "Reified Relation Triples": false, }; -export const defaultFeatureFlags: FeatureFlags = { +const defaultFeatureFlags: FeatureFlags = { "Enable Left Sidebar": false, "Suggestive Mode Enabled": false, "Reified Relation Triples": false, }; -export const exampleExportSettings: ExportSettings = { +const exportSettings: ExportSettings = { "Remove Special Characters": true, "Resolve Block References": true, "Resolve Block Embeds": false, @@ -122,12 +122,12 @@ export const exampleExportSettings: ExportSettings = { ], }; -export const examplePageGroup: PageGroup = { +const pageGroup: PageGroup = { name: "Research Papers", pages: ["page-uid-1", "page-uid-2", "page-uid-3"], }; -export const exampleSuggestiveModeGlobalSettings: SuggestiveModeGlobalSettings = +const suggestiveModeGlobalSettings: SuggestiveModeGlobalSettings = { "Include Current Page Relations": true, "Include Parent And Child Blocks": true, @@ -143,7 +143,7 @@ export const exampleSuggestiveModeGlobalSettings: SuggestiveModeGlobalSettings = ], }; -export const exampleLeftSidebarGlobalSettings: LeftSidebarGlobalSettings = { +const leftSidebarGlobalSettings: LeftSidebarGlobalSettings = { Children: ["daily-notes-uid", "quick-capture-uid", "inbox-uid"], Settings: { Collapsable: true, @@ -151,7 +151,7 @@ export const exampleLeftSidebarGlobalSettings: LeftSidebarGlobalSettings = { }, }; -export const exampleGlobalSettings: GlobalSettings = { +const globalSettings: GlobalSettings = { Trigger: ";;", "Canvas Page Format": "Canvas - {date} - {title}", "Left Sidebar": { @@ -182,7 +182,7 @@ export const exampleGlobalSettings: GlobalSettings = { }, }; -export const defaultGlobalSettings: GlobalSettings = { +const defaultGlobalSettings: GlobalSettings = { Trigger: "", "Canvas Page Format": "", "Left Sidebar": { @@ -208,7 +208,7 @@ export const defaultGlobalSettings: GlobalSettings = { }, }; -export const examplePersonalSection: PersonalSection = { +const personalSection: PersonalSection = { Children: [ { Page: "daily-notes-uid", Alias: "Daily Notes" }, { Page: "inbox-uid", Alias: "Inbox" }, @@ -220,7 +220,7 @@ export const examplePersonalSection: PersonalSection = { }, }; -export const exampleLeftSidebarPersonalSettings: LeftSidebarPersonalSettings = { +const leftSidebarPersonalSettings: LeftSidebarPersonalSettings = { "My Workspace": { Children: [ { Page: "daily-notes-uid", Alias: "Daily Notes" }, @@ -244,13 +244,13 @@ export const exampleLeftSidebarPersonalSettings: LeftSidebarPersonalSettings = { }, }; -export const exampleQueryFilter: QueryFilter = { +const queryFilter: QueryFilter = { includes: true, key: "node-type", value: "Claim", }; -export const exampleQuerySettings: QuerySettings = { +const querySettings: QuerySettings = { "Hide Query Metadata": true, "Default Page Size": 25, "Query Pages": ["query-page-uid-1", "query-page-uid-2"], @@ -260,7 +260,7 @@ export const exampleQuerySettings: QuerySettings = { ], }; -export const examplePersonalSettings: PersonalSettings = { +const personalSettings: PersonalSettings = { "Left Sidebar": { "My Workspace": { Children: [ @@ -304,7 +304,7 @@ export const examplePersonalSettings: PersonalSettings = { }, }; -export const defaultPersonalSettings: PersonalSettings = { +const defaultPersonalSettings: PersonalSettings = { "Left Sidebar": {}, "Personal Node Menu Trigger": "", "Node Search Menu Trigger": "", @@ -327,7 +327,7 @@ export const defaultPersonalSettings: PersonalSettings = { }, }; -export const exampleGithubSettings: GithubSettings = { +const githubSettings: GithubSettings = { "oauth-github": "ghp_xxxxxxxxxxxxxxxxxxxx", "selected-repo": "username/repository-name", }; @@ -356,7 +356,7 @@ export const exampleGithubSettings: GithubSettings = { * - {value} // e.g. "Claim", regex pattern */ -export const exampleQueryBlockMinimal: RoamBlock = { +const queryBlockMinimal: RoamBlock = { text: "scratch", children: [ { text: "custom" }, @@ -376,7 +376,7 @@ export const exampleQueryBlockMinimal: RoamBlock = { ], }; -export const exampleQueryBlockSimple: RoamBlock = { +const queryBlockSimple: RoamBlock = { text: "scratch", children: [ { text: "custom" }, @@ -397,7 +397,7 @@ export const exampleQueryBlockSimple: RoamBlock = { ], }; -export const exampleQueryBlockWithSelections: RoamBlock = { +const queryBlockWithSelections: RoamBlock = { text: "scratch", children: [ { text: "custom" }, @@ -425,7 +425,7 @@ export const exampleQueryBlockWithSelections: RoamBlock = { ], }; -export const exampleQueryBlockWithCustom: RoamBlock = { +const queryBlockWithCustom: RoamBlock = { text: "scratch", children: [ { @@ -449,7 +449,7 @@ export const exampleQueryBlockWithCustom: RoamBlock = { ], }; -export const exampleQueryBlockMultipleConditions: RoamBlock = { +const queryBlockMultipleConditions: RoamBlock = { text: "scratch", children: [ { text: "custom" }, @@ -492,8 +492,7 @@ export const exampleQueryBlockMultipleConditions: RoamBlock = { ], }; -// Query block with NOT condition -export const exampleQueryBlockWithNot: RoamBlock = { +const queryBlockWithNot: RoamBlock = { text: "scratch", children: [ { text: "custom" }, @@ -522,8 +521,7 @@ export const exampleQueryBlockWithNot: RoamBlock = { ], }; -// Query block with OR condition (nested) -export const exampleQueryBlockWithOr: RoamBlock = { +const queryBlockWithOr: RoamBlock = { text: "scratch", children: [ { text: "custom" }, @@ -575,7 +573,7 @@ export const exampleQueryBlockWithOr: RoamBlock = { ], }; -export const exampleQueryBlockWithRegex: RoamBlock = { +const queryBlockWithRegex: RoamBlock = { text: "scratch", children: [ { text: "custom" }, From 457933a754845c9c363cc9d5f6aa9f9cfe7867a2 Mon Sep 17 00:00:00 2001 From: sid597 Date: Thu, 8 Jan 2026 00:07:58 +0530 Subject: [PATCH 4/5] address review --- .../settings/utils/zodSchema.example.ts | 423 ++++++------------ .../components/settings/utils/zodSchema.ts | 153 ++++--- 2 files changed, 220 insertions(+), 356 deletions(-) diff --git a/apps/roam/src/components/settings/utils/zodSchema.example.ts b/apps/roam/src/components/settings/utils/zodSchema.example.ts index c68c28c1a..3161a2abf 100644 --- a/apps/roam/src/components/settings/utils/zodSchema.example.ts +++ b/apps/roam/src/components/settings/utils/zodSchema.example.ts @@ -11,11 +11,13 @@ import type { GlobalSettings, PersonalSection, LeftSidebarPersonalSettings, - QueryFilter, + StoredFilters, QuerySettings, PersonalSettings, GithubSettings, - RoamBlock, + QueryCondition, + QuerySelection, + RoamNodeType, } from "./zodSchema"; const canvasSettings: CanvasSettings = { @@ -28,11 +30,10 @@ const canvasSettings: CanvasSettings = { const suggestiveRules: SuggestiveRules = { template: [ - { text: "Summary::", children: [] }, - { text: "Key Points::", children: [] }, + { text: "Summary::", heading: 2 }, + { text: "Key Points::", heading: 2, children: [{ text: "" }] }, ], embeddingRef: "((block-uid-123))", - embeddingRefUid: "block-uid-123", isFirstChild: { uid: "first-child-uid", value: true, @@ -41,24 +42,25 @@ const suggestiveRules: SuggestiveRules = { const discourseNodeSettings: DiscourseNodeSettings = { text: "Claim", - type: "discourse-graph/nodes/claim", + type: "_CLM-node", format: "[[CLM]] - {content}", shortcut: "C", tag: "#claim", description: "A statement or assertion that can be supported or refuted", specification: [ { - type: "has title", - text: "starts with [[CLM]]", + uid: "spec-uid-1", + type: "clause", + source: "Claim", + relation: "has title", + target: "/^\\[\\[CLM\\]\\]/", }, ], - specificationUid: "spec-uid-123", template: [ - { text: "Summary::", children: [] }, - { text: "Evidence::", children: [] }, - { text: "Counterarguments::", children: [] }, + { text: "Summary::", heading: 2 }, + { text: "Evidence::", heading: 2, children: [{ text: "" }] }, + { text: "Counterarguments::", heading: 2, children: [{ text: "" }] }, ], - templateUid: "template-uid-123", canvasSettings: { color: "#4A90D9", alias: "CLM", @@ -76,18 +78,15 @@ const discourseNodeSettings: DiscourseNodeSettings = { attribute: "Status", }, ], - indexUid: "index-uid-123", suggestiveRules: { template: [], embeddingRef: "((embed-ref))", - embeddingRefUid: "embed-ref", isFirstChild: { uid: "is-first-child-uid", value: false, }, }, embeddingRef: "((main-embed-ref))", - embeddingRefUid: "main-embed-ref", isFirstChild: { uid: "main-first-child-uid", value: true, @@ -127,21 +126,20 @@ const pageGroup: PageGroup = { pages: ["page-uid-1", "page-uid-2", "page-uid-3"], }; -const suggestiveModeGlobalSettings: SuggestiveModeGlobalSettings = - { - "Include Current Page Relations": true, - "Include Parent And Child Blocks": true, - "Page Groups": [ - { - name: "Research Papers", - pages: ["paper-1-uid", "paper-2-uid"], - }, - { - name: "Meeting Notes", - pages: ["meeting-1-uid", "meeting-2-uid", "meeting-3-uid"], - }, - ], - }; +const suggestiveModeGlobalSettings: SuggestiveModeGlobalSettings = { + "Include Current Page Relations": true, + "Include Parent And Child Blocks": true, + "Page Groups": [ + { + name: "Research Papers", + pages: ["paper-1-uid", "paper-2-uid"], + }, + { + name: "Meeting Notes", + pages: ["meeting-1-uid", "meeting-2-uid", "meeting-3-uid"], + }, + ], +}; const leftSidebarGlobalSettings: LeftSidebarGlobalSettings = { Children: ["daily-notes-uid", "quick-capture-uid", "inbox-uid"], @@ -244,20 +242,25 @@ const leftSidebarPersonalSettings: LeftSidebarPersonalSettings = { }, }; -const queryFilter: QueryFilter = { - includes: true, - key: "node-type", - value: "Claim", +const storedFilters: StoredFilters = { + includes: { values: ["Claim", "Evidence"] }, + excludes: { values: ["archived"] }, }; const querySettings: QuerySettings = { "Hide Query Metadata": true, "Default Page Size": 25, "Query Pages": ["query-page-uid-1", "query-page-uid-2"], - "Default Filters": [ - { includes: true, key: "node-type", value: "Claim" }, - { includes: false, key: "status", value: "archived" }, - ], + "Default Filters": { + "node-type": { + includes: { values: ["Claim"] }, + excludes: { values: [] }, + }, + status: { + includes: { values: [] }, + excludes: { values: ["archived"] }, + }, + }, }; const personalSettings: PersonalSettings = { @@ -300,7 +303,12 @@ const personalSettings: PersonalSettings = { "Hide Query Metadata": true, "Default Page Size": 25, "Query Pages": ["query-page-uid-1"], - "Default Filters": [{ includes: true, key: "node-type", value: "Claim" }], + "Default Filters": { + "node-type": { + includes: { values: ["Claim"] }, + excludes: { values: [] }, + }, + }, }, }; @@ -323,7 +331,7 @@ const defaultPersonalSettings: PersonalSettings = { "Hide Query Metadata": false, "Default Page Size": 10, "Query Pages": [], - "Default Filters": [], + "Default Filters": {}, }, }; @@ -332,264 +340,115 @@ const githubSettings: GithubSettings = { "selected-repo": "username/repository-name", }; -/** - * Query Block (scratch) Structure Reference - * - * The "scratch" block is a child of {{query block}} and contains all query configuration. - * It has three main children: custom, selections, and conditions. - * - * Structure: - * - scratch - * - custom // Custom return node configuration - * - {custom node text} // Optional: custom node identifier - * - enabled // Optional: flag to enable custom node - * - selections // Column selections for results - * - {variable name} // e.g. "node", "Created Date" - * - {label} // Optional: display label for column - * - conditions // Query conditions - * - clause | not | or | not or // Condition type - * - source // Required for clause/not - * - {value} // e.g. "node", node type name - * - relation // Required for clause/not - * - {value} // e.g. "is a", "has title", "references" - * - target // Optional for clause/not - * - {value} // e.g. "Claim", regex pattern - */ +const clauseCondition: QueryCondition = { + uid: "clause-uid-1", + type: "clause", + source: "node", + relation: "is a", + target: "Claim", +}; -const queryBlockMinimal: RoamBlock = { - text: "scratch", - children: [ - { text: "custom" }, - { text: "selections" }, - { - text: "conditions", - children: [ - { - text: "clause", - children: [ - { text: "source", children: [{ text: "node" }] }, - { text: "relation" }, - ], - }, - ], - }, - ], +const notCondition: QueryCondition = { + uid: "not-uid-1", + type: "not", + source: "node", + relation: "has attribute", + target: "Archived", }; -const queryBlockSimple: RoamBlock = { - text: "scratch", - children: [ - { text: "custom" }, - { text: "selections" }, - { - text: "conditions", - children: [ - { - text: "clause", - children: [ - { text: "source", children: [{ text: "node" }] }, - { text: "relation", children: [{ text: "is a" }] }, - { text: "target", children: [{ text: "Claim" }] }, - ], - }, - ], - }, +const orCondition: QueryCondition = { + uid: "or-uid-1", + type: "or", + conditions: [ + [ + { + uid: "clause-uid-2", + type: "clause", + source: "node", + relation: "has attribute", + target: "High Priority", + }, + ], + [ + { + uid: "clause-uid-3", + type: "clause", + source: "node", + relation: "has attribute", + target: "Urgent", + }, + ], ], }; -const queryBlockWithSelections: RoamBlock = { - text: "scratch", - children: [ - { text: "custom" }, - { - text: "selections", - children: [ - { text: "node", children: [{ text: "Title" }] }, - { text: "Created Date", children: [{ text: "Created" }] }, - { text: "Author" }, - ], - }, - { - text: "conditions", - children: [ - { - text: "clause", - children: [ - { text: "source", children: [{ text: "node" }] }, - { text: "relation", children: [{ text: "is a" }] }, - { text: "target", children: [{ text: "Claim" }] }, - ], - }, - ], - }, +const norCondition: QueryCondition = { + uid: "nor-uid-1", + type: "not or", + conditions: [ + [ + { + uid: "clause-uid-4", + type: "clause", + source: "node", + relation: "is a", + target: "Draft", + }, + ], + [ + { + uid: "clause-uid-5", + type: "clause", + source: "node", + relation: "is a", + target: "Archived", + }, + ], ], }; -const queryBlockWithCustom: RoamBlock = { - text: "scratch", - children: [ - { - text: "custom", - children: [{ text: "myCustomNode" }, { text: "enabled" }], - }, - { text: "selections" }, - { - text: "conditions", - children: [ - { - text: "clause", - children: [ - { text: "source", children: [{ text: "node" }] }, - { text: "relation", children: [{ text: "is a" }] }, - { text: "target", children: [{ text: "Evidence" }] }, - ], - }, - ], - }, - ], +const exampleConditions: QueryCondition[] = [ + clauseCondition, + notCondition, + orCondition, + norCondition, +]; + +const titleSelection: QuerySelection = { + uid: "sel-uid-1", + text: "node", + label: "Title", }; -const queryBlockMultipleConditions: RoamBlock = { - text: "scratch", - children: [ - { text: "custom" }, - { - text: "selections", - children: [ - { text: "node", children: [{ text: "Claim" }] }, - { text: "target", children: [{ text: "Supporting Evidence" }] }, - ], - }, - { - text: "conditions", - children: [ - { - text: "clause", - children: [ - { text: "source", children: [{ text: "node" }] }, - { text: "relation", children: [{ text: "is a" }] }, - { text: "target", children: [{ text: "Claim" }] }, - ], - }, - { - text: "clause", - children: [ - { text: "source", children: [{ text: "node" }] }, - { text: "relation", children: [{ text: "Supported By" }] }, - { text: "target", children: [{ text: "target" }] }, - ], - }, - { - text: "clause", - children: [ - { text: "source", children: [{ text: "target" }] }, - { text: "relation", children: [{ text: "is a" }] }, - { text: "target", children: [{ text: "Evidence" }] }, - ], - }, - ], - }, - ], +const dateSelection: QuerySelection = { + uid: "sel-uid-2", + text: "Created Date", + label: "Created", }; -const queryBlockWithNot: RoamBlock = { - text: "scratch", - children: [ - { text: "custom" }, - { text: "selections" }, - { - text: "conditions", - children: [ - { - text: "clause", - children: [ - { text: "source", children: [{ text: "node" }] }, - { text: "relation", children: [{ text: "is a" }] }, - { text: "target", children: [{ text: "Claim" }] }, - ], - }, - { - text: "not", - children: [ - { text: "source", children: [{ text: "node" }] }, - { text: "relation", children: [{ text: "has attribute" }] }, - { text: "target", children: [{ text: "Archived" }] }, - ], - }, - ], - }, - ], +const exampleSelections: QuerySelection[] = [titleSelection, dateSelection]; + +const simpleNode: RoamNodeType = { + text: "A simple block", }; -const queryBlockWithOr: RoamBlock = { - text: "scratch", - children: [ - { text: "custom" }, - { text: "selections" }, - { - text: "conditions", - children: [ - { - text: "clause", - children: [ - { text: "source", children: [{ text: "node" }] }, - { text: "relation", children: [{ text: "is a" }] }, - { text: "target", children: [{ text: "Claim" }] }, - ], - }, - { - text: "or", - children: [ - { - text: "0", - children: [ - { - text: "clause", - children: [ - { text: "source", children: [{ text: "node" }] }, - { text: "relation", children: [{ text: "has attribute" }] }, - { text: "target", children: [{ text: "High Priority" }] }, - ], - }, - ], - }, - { - text: "1", - children: [ - { - text: "clause", - children: [ - { text: "source", children: [{ text: "node" }] }, - { text: "relation", children: [{ text: "has attribute" }] }, - { text: "target", children: [{ text: "Urgent" }] }, - ], - }, - ], - }, - ], - }, - ], - }, - ], +const nodeWithHeading: RoamNodeType = { + text: "Section Title", + heading: 1, }; -const queryBlockWithRegex: RoamBlock = { - text: "scratch", +const nodeWithChildren: RoamNodeType = { + text: "Steps:", children: [ - { text: "custom" }, - { text: "selections" }, - { - text: "conditions", - children: [ - { - text: "clause", - children: [ - { text: "source", children: [{ text: "node" }] }, - { text: "relation", children: [{ text: "has title" }] }, - { text: "target", children: [{ text: "/^\\[\\[CLM\\]\\]/" }] }, - ], - }, - ], - }, + { text: "First step" }, + { text: "Second step" }, + { text: "Third step" }, ], }; + +const fullTemplateExample: RoamNodeType[] = [ + { text: "Meeting Notes", heading: 1 }, + { text: "Attendees::" }, + { text: "Agenda::", heading: 2, children: [{ text: "" }] }, + { text: "Discussion::", heading: 2 }, + { text: "Action Items::", heading: 2, children: [{ text: "" }] }, +]; diff --git a/apps/roam/src/components/settings/utils/zodSchema.ts b/apps/roam/src/components/settings/utils/zodSchema.ts index caa92a29d..4367dc4e2 100644 --- a/apps/roam/src/components/settings/utils/zodSchema.ts +++ b/apps/roam/src/components/settings/utils/zodSchema.ts @@ -12,19 +12,78 @@ export const CanvasSettingsSchema = z.object({ "query-builder-alias": z.string().default(""), }); -export const SuggestiveRulesSchema = z.object({ - template: z.array(z.unknown()).default([]), - embeddingRef: z.string().optional(), - embeddingRefUid: z.string().optional(), - isFirstChild: z - .object({ +export const SuggestiveRulesSchema = z.lazy(() => + z.object({ + template: z.array(RoamNodeSchema).default([]), + embeddingRef: z.string().optional(), + isFirstChild: z + .object({ + uid: z.string(), + value: z.boolean(), + }) + .optional(), + }), +); + +export const AttributesSchema = z.record(z.string(), z.string()).default({}); + +const QBClauseDataSchema = z.object({ + uid: z.string(), + source: z.string(), + relation: z.string(), + target: z.string(), + not: z.boolean().optional(), +}); + +type Condition = + | (z.infer & { type: "clause" }) + | (z.infer & { type: "not" }) + | { uid: string; type: "or"; conditions: Condition[][] } + | { uid: string; type: "not or"; conditions: Condition[][] }; + +export const ConditionSchema: z.ZodType = z.discriminatedUnion( + "type", + [ + QBClauseDataSchema.extend({ type: z.literal("clause") }), + QBClauseDataSchema.extend({ type: z.literal("not") }), + z.object({ uid: z.string(), - value: z.boolean(), - }) - .optional(), + type: z.literal("or"), + conditions: z.lazy(() => ConditionSchema.array().array()), + }), + z.object({ + uid: z.string(), + type: z.literal("not or"), + conditions: z.lazy(() => ConditionSchema.array().array()), + }), + ], +); + +export const SelectionSchema = z.object({ + uid: z.string(), + text: z.string(), + label: z.string(), }); -export const AttributesSchema = z.record(z.string(), z.string()).default({}); +type RoamNode = { + text: string; + children?: RoamNode[]; + uid?: string; + heading?: 0 | 1 | 2 | 3; + open?: boolean; +}; + +export const RoamNodeSchema: z.ZodType = z.lazy(() => + z.object({ + text: z.string(), + children: RoamNodeSchema.array().optional(), + uid: z.string().optional(), + heading: z + .union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]) + .optional(), + open: z.boolean().optional(), + }), +); const stringWithDefault = (defaultVal: string) => z @@ -48,17 +107,15 @@ export const DiscourseNodeSchema = z.object({ tag: stringWithDefault(""), description: stringWithDefault(""), specification: z - .array(z.unknown()) + .array(ConditionSchema) .nullable() .optional() .transform((val) => val ?? []), - specificationUid: stringWithDefault(""), template: z - .array(z.unknown()) + .array(RoamNodeSchema) .nullable() .optional() .transform((val) => val ?? []), - templateUid: stringWithDefault(""), canvasSettings: CanvasSettingsSchema.partial().nullable().optional(), graphOverview: booleanWithDefault(false), attributes: z @@ -68,10 +125,8 @@ export const DiscourseNodeSchema = z.object({ .transform((val) => val ?? {}), overlay: stringWithDefault(""), index: z.unknown().nullable().optional(), - indexUid: stringWithDefault(""), suggestiveRules: SuggestiveRulesSchema.nullable().optional(), embeddingRef: stringWithDefault(""), - embeddingRefUid: stringWithDefault(""), isFirstChild: z .object({ uid: z.string(), @@ -151,17 +206,16 @@ export const LeftSidebarPersonalSettingsSchema = z .record(z.string(), PersonalSectionSchema) .default({}); -export const QueryFilterSchema = z.object({ - includes: z.boolean().default(true), - key: z.string(), - value: z.string(), +export const StoredFiltersSchema = z.object({ + includes: z.object({ values: z.array(z.string()).default([]) }).default({}), + excludes: z.object({ values: z.array(z.string()).default([]) }).default({}), }); export const QuerySettingsSchema = z.object({ "Hide Query Metadata": z.boolean().default(false), "Default Page Size": z.number().default(10), "Query Pages": z.array(z.string()).default([]), - "Default Filters": z.array(QueryFilterSchema).default([]), + "Default Filters": z.record(z.string(), StoredFiltersSchema).default({}), }); export const PersonalSettingsSchema = z.object({ @@ -187,53 +241,6 @@ export const GithubSettingsSchema = z.object({ "selected-repo": z.string().optional(), }); -const RoamBlockNodeSchema: z.ZodType = z.lazy(() => - z.object({ - text: z.string(), - children: z.array(RoamBlockNodeSchema).optional(), - }), -); - -type RoamBlockNode = { - text: string; - children?: RoamBlockNode[]; -}; - -export const QueryClauseSchema = z.object({ - text: z.enum(["clause", "not"]), - children: z.array( - z.object({ - text: z.enum(["source", "relation", "target"]), - children: z.array(z.object({ text: z.string() })).optional(), - }), - ), -}); - -export const QueryNestedConditionSchema = z.object({ - text: z.enum(["or", "not or"]), - children: z.array(z.unknown()), -}); - -export const QuerySelectionSchema = z.object({ - text: z.string(), - children: z.array(z.object({ text: z.string() })).optional(), -}); - -export const QueryCustomSchema = z.object({ - text: z.literal("custom"), - children: z - .tuple([ - z.object({ text: z.string() }), - z.object({ text: z.literal("enabled") }).optional(), - ]) - .optional(), -}); - -export const QueryBlockSchema = z.object({ - text: z.literal("scratch"), - children: z.array(RoamBlockNodeSchema), -}); - /* eslint-enable @typescript-eslint/naming-convention */ export type CanvasSettings = z.infer; @@ -253,12 +260,10 @@ export type PersonalSection = z.infer; export type LeftSidebarPersonalSettings = z.infer< typeof LeftSidebarPersonalSettingsSchema >; -export type QueryFilter = z.infer; +export type StoredFilters = z.infer; export type QuerySettings = z.infer; export type PersonalSettings = z.infer; export type GithubSettings = z.infer; -export type QueryBlock = z.infer; -export type QueryClause = z.infer; -export type QuerySelection = z.infer; -export type QueryCustom = z.infer; -export type RoamBlock = RoamBlockNode; +export type QueryCondition = z.infer; +export type QuerySelection = z.infer; +export type RoamNodeType = z.infer; From 14525b4a6ca52b28202dabb1cea001e9ed155507 Mon Sep 17 00:00:00 2001 From: sid597 Date: Thu, 8 Jan 2026 10:47:46 +0530 Subject: [PATCH 5/5] address review --- .../components/settings/utils/zodSchema.example.ts | 12 +----------- apps/roam/src/components/settings/utils/zodSchema.ts | 8 ++------ 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/apps/roam/src/components/settings/utils/zodSchema.example.ts b/apps/roam/src/components/settings/utils/zodSchema.example.ts index 3161a2abf..4ed069a73 100644 --- a/apps/roam/src/components/settings/utils/zodSchema.example.ts +++ b/apps/roam/src/components/settings/utils/zodSchema.example.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/no-unused-vars */ import type { CanvasSettings, SuggestiveRules, @@ -49,7 +50,6 @@ const discourseNodeSettings: DiscourseNodeSettings = { description: "A statement or assertion that can be supported or refuted", specification: [ { - uid: "spec-uid-1", type: "clause", source: "Claim", relation: "has title", @@ -341,7 +341,6 @@ const githubSettings: GithubSettings = { }; const clauseCondition: QueryCondition = { - uid: "clause-uid-1", type: "clause", source: "node", relation: "is a", @@ -349,7 +348,6 @@ const clauseCondition: QueryCondition = { }; const notCondition: QueryCondition = { - uid: "not-uid-1", type: "not", source: "node", relation: "has attribute", @@ -357,12 +355,10 @@ const notCondition: QueryCondition = { }; const orCondition: QueryCondition = { - uid: "or-uid-1", type: "or", conditions: [ [ { - uid: "clause-uid-2", type: "clause", source: "node", relation: "has attribute", @@ -371,7 +367,6 @@ const orCondition: QueryCondition = { ], [ { - uid: "clause-uid-3", type: "clause", source: "node", relation: "has attribute", @@ -382,12 +377,10 @@ const orCondition: QueryCondition = { }; const norCondition: QueryCondition = { - uid: "nor-uid-1", type: "not or", conditions: [ [ { - uid: "clause-uid-4", type: "clause", source: "node", relation: "is a", @@ -396,7 +389,6 @@ const norCondition: QueryCondition = { ], [ { - uid: "clause-uid-5", type: "clause", source: "node", relation: "is a", @@ -414,13 +406,11 @@ const exampleConditions: QueryCondition[] = [ ]; const titleSelection: QuerySelection = { - uid: "sel-uid-1", text: "node", label: "Title", }; const dateSelection: QuerySelection = { - uid: "sel-uid-2", text: "Created Date", label: "Created", }; diff --git a/apps/roam/src/components/settings/utils/zodSchema.ts b/apps/roam/src/components/settings/utils/zodSchema.ts index 4367dc4e2..d2f594a11 100644 --- a/apps/roam/src/components/settings/utils/zodSchema.ts +++ b/apps/roam/src/components/settings/utils/zodSchema.ts @@ -28,7 +28,6 @@ export const SuggestiveRulesSchema = z.lazy(() => export const AttributesSchema = z.record(z.string(), z.string()).default({}); const QBClauseDataSchema = z.object({ - uid: z.string(), source: z.string(), relation: z.string(), target: z.string(), @@ -38,8 +37,8 @@ const QBClauseDataSchema = z.object({ type Condition = | (z.infer & { type: "clause" }) | (z.infer & { type: "not" }) - | { uid: string; type: "or"; conditions: Condition[][] } - | { uid: string; type: "not or"; conditions: Condition[][] }; + | { type: "or"; conditions: Condition[][] } + | { type: "not or"; conditions: Condition[][] }; export const ConditionSchema: z.ZodType = z.discriminatedUnion( "type", @@ -47,12 +46,10 @@ export const ConditionSchema: z.ZodType = z.discriminatedUnion( QBClauseDataSchema.extend({ type: z.literal("clause") }), QBClauseDataSchema.extend({ type: z.literal("not") }), z.object({ - uid: z.string(), type: z.literal("or"), conditions: z.lazy(() => ConditionSchema.array().array()), }), z.object({ - uid: z.string(), type: z.literal("not or"), conditions: z.lazy(() => ConditionSchema.array().array()), }), @@ -60,7 +57,6 @@ export const ConditionSchema: z.ZodType = z.discriminatedUnion( ); export const SelectionSchema = z.object({ - uid: z.string(), text: z.string(), label: z.string(), });