From 72e962ef256530fe016033352a282c4edf5686f6 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Tue, 8 Sep 2026 13:05:13 -0400 Subject: [PATCH] ENG-2188 Node type settings are spread across seven inner tabs; put them on one page A node type's settings become one vertical page: Identity (Index first, then Description, Tag, Color, Format), Creation, Canvas, Legacy (Specification), the sync-gated Suggestive mode, and Attributes last. Index and Template are drill-down rows on the ENG-2186 primitive, each with its own sub-page. Specification's toggle reads "Enabled" (its lowercase block text stays the storage key via blockKey) and the query builder renders only while it is on. Format loses its DEPRECATED note: Specification is the one in migration. Co-Authored-By: Claude Fable 5.1 --- .../settings/DiscourseNodeSpecification.tsx | 27 +- .../settings/DiscourseNodeSuggestiveRules.tsx | 49 ++- .../components/settings/GrammarNodesRoute.tsx | 26 +- .../src/components/settings/NodeConfig.tsx | 382 ++++++++---------- .../src/components/settings/NodeIndexPage.tsx | 26 ++ .../components/settings/NodeTemplatePage.tsx | 27 ++ .../components/SettingsDrillDownRow.tsx | 31 ++ .../settings/utils/settingsNavigation.ts | 5 + apps/roam/src/styles/settingsStyles.css | 7 + 9 files changed, 326 insertions(+), 254 deletions(-) create mode 100644 apps/roam/src/components/settings/NodeIndexPage.tsx create mode 100644 apps/roam/src/components/settings/NodeTemplatePage.tsx create mode 100644 apps/roam/src/components/settings/components/SettingsDrillDownRow.tsx diff --git a/apps/roam/src/components/settings/DiscourseNodeSpecification.tsx b/apps/roam/src/components/settings/DiscourseNodeSpecification.tsx index ea32cd0e9..e387f6ec3 100644 --- a/apps/roam/src/components/settings/DiscourseNodeSpecification.tsx +++ b/apps/roam/src/components/settings/DiscourseNodeSpecification.tsx @@ -155,7 +155,8 @@ const NodeSpecification = ({ -
- -
+ {enabled && ( +
+ +
+ )} ); }; diff --git a/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx b/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx index 67a546c92..5af703692 100644 --- a/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx +++ b/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx @@ -1,5 +1,5 @@ import React, { useState, useMemo } from "react"; -import { Button, Intent } from "@blueprintjs/core"; +import { Button, Collapse, Intent } from "@blueprintjs/core"; import DualWriteBlocksPanel from "./components/EphemeralBlocksPanel"; import getSubTree from "roamjs-components/util/getSubTree"; import { DiscourseNode } from "~/utils/getDiscourseNodes"; @@ -17,6 +17,7 @@ import { TEMPLATE_SETTING_KEYS, } from "~/components/settings/utils/settingKeys"; import { RenderRoamBlock } from "~/utils/roamReactComponents"; +import Description from "~/components/settings/SettingsDescription"; import { ROAM_DOCS, withDocsLink } from "./utils/docs"; const DiscourseNodeSuggestiveRules = ({ @@ -42,6 +43,7 @@ const DiscourseNodeSuggestiveRules = ({ }).uid || "", [nodeUid], ); + const [isTemplateOpen, setIsTemplateOpen] = useState(false); const [isUpdating, setIsUpdating] = useState(false); @@ -68,18 +70,39 @@ const DiscourseNodeSuggestiveRules = ({ }; return ( -
- +
+
+
+
+ {/* Collapse unmounts its children, so the buffer block only exists while open. */} + +
+ +
+
+
= { + [nodeConfigSegmentIds.index]: "Index", + [nodeConfigSegmentIds.template]: "Template", +}; + const GrammarNodesRoute = ({ onloadArgs, }: { @@ -18,7 +26,7 @@ const GrammarNodesRoute = ({ const { segments, goToDepth } = useSettingsNav(); const nodes = getDiscourseNodes().filter(excludeDefaultNodes); - const [nodeTypeUid] = segments; + const [nodeTypeUid, subPage] = segments; const node = nodeTypeUid ? nodes.find((n) => n.type === nodeTypeUid) : undefined; @@ -29,8 +37,10 @@ const GrammarNodesRoute = ({ if (isStalePath) goToDepth(0); }, [isStalePath, goToDepth]); - const resolveLabel = (segment: string): string => - nodes.find((n) => n.type === segment)?.text ?? segment; + const resolveLabel = (segment: string, segmentIndex: number): string => + segmentIndex === 0 + ? (nodes.find((n) => n.type === segment)?.text ?? segment) + : (SUB_PAGE_LABELS[segment] ?? segment); return (
@@ -40,12 +50,16 @@ const GrammarNodesRoute = ({ resolveLabel={resolveLabel} />
- {node ? ( - - ) : ( + {!node ? (
+ ) : subPage === nodeConfigSegmentIds.index ? ( + + ) : subPage === nodeConfigSegmentIds.template ? ( + + ) : ( + )}
diff --git a/apps/roam/src/components/settings/NodeConfig.tsx b/apps/roam/src/components/settings/NodeConfig.tsx index b4114b63b..4f0672a86 100644 --- a/apps/roam/src/components/settings/NodeConfig.tsx +++ b/apps/roam/src/components/settings/NodeConfig.tsx @@ -1,13 +1,9 @@ import React, { useState, useCallback, useEffect, useRef } from "react"; import getDiscourseNodes, { DiscourseNode } from "~/utils/getDiscourseNodes"; -import DualWriteBlocksPanel from "./components/EphemeralBlocksPanel"; import { getSubTree } from "roamjs-components/util"; import Description from "~/components/settings/SettingsDescription"; import { Label, - Tabs, - Tab, - TabId, InputGroup, ControlGroup, Tooltip, @@ -18,8 +14,6 @@ import DiscourseNodeAttributes from "./DiscourseNodeAttributes"; import DiscourseNodeCanvasSettings, { formatHexColor, } from "./DiscourseNodeCanvasSettings"; -import DiscourseNodeIndex from "./DiscourseNodeIndex"; -import { OnloadArgs } from "roamjs-components/types"; import setInputSetting from "roamjs-components/util/setInputSetting"; import { getDiscourseNodeSetting, @@ -30,7 +24,6 @@ import { CANVAS_KEYS, DISCOURSE_NODE_KEYS, SPECIFICATION_KEYS, - TEMPLATE_SETTING_KEYS, } from "~/components/settings/utils/settingKeys"; import DiscourseNodeSuggestiveRules from "./DiscourseNodeSuggestiveRules"; import { getNodeTagStyles } from "~/utils/getDiscourseNodeColors"; @@ -40,6 +33,10 @@ import { DiscourseNodeSelectPanel, } from "./components/BlockPropSettingPanels"; import { ROAM_DOCS, withDocsLink } from "./utils/docs"; +import { SettingsGroup } from "./components/SettingsHeadings"; +import SettingsDrillDownRow from "./components/SettingsDrillDownRow"; +import { useSettingsNav } from "./navigation/SettingsNavContext"; +import { nodeConfigSegmentIds } from "./utils/settingsNavigation"; export const getCleanTagText = (tag: string): string => { return tag.replace(/^#+/, "").trim().toUpperCase(); @@ -168,13 +165,7 @@ const generateTagPlaceholder = (node: DiscourseNode): string => { return `#${nodeTextPrefix}-candidate`; // Evidence = #evi-candidate }; -const NodeConfig = ({ - node, - onloadArgs, -}: { - node: DiscourseNode; - onloadArgs: OnloadArgs; -}) => { +const NodeConfig = ({ node }: { node: DiscourseNode }) => { const getUid = (key: string) => getSubTree({ parentUid: node.type, @@ -184,19 +175,17 @@ const NodeConfig = ({ const descriptionUid = getUid("Description"); const shortcutUid = getUid("Shortcut"); const tagUid = getUid("Tag"); - const templateUid = getUid("Template"); const overlayUid = getUid("Overlay"); const canvasUid = getUid("Canvas"); const graphOverviewUid = getUid("Graph Overview"); const specificationUid = getUid("Specification"); - const indexUid = getUid("Index"); const suggestiveRulesUid = getUid("Suggestive Rules"); const attributeNode = getSubTree({ parentUid: node.type, key: "Attributes", }); - const [selectedTabId, setSelectedTabId] = useState("general"); + const nav = useSettingsNav(); const [tagError, setTagError] = useState(""); const [formatError, setFormatError] = useState(""); const [shortcutError, setShortcutError] = useState(""); @@ -302,220 +291,169 @@ const NodeConfig = ({ ); return ( - <> - setSelectedTabId(id)} - selectedTabId={selectedTabId} - renderActiveTabPanelOnly={true} - > - - - -
- -
- -
- } - /> - + + - -
- } + description={`The saved list of all ${node.text} pages \u2014 which pages appear and which columns show.`} + buttonText={`See all ${node.text} nodes`} + onClick={() => nav.push(nodeConfigSegmentIds.index)} + /> + + + - - - - - } + description={withDocsLink( + `The format ${node.text} pages should have.`, + ROAM_DOCS.grammarNodes, + )} + settingKeys={[DISCOURSE_NODE_KEYS.format]} + initialValue={node.format} + error={formatError} + onChange={setFormatValue} + order={3} + parentUid={node.type} + uid={formatUid} + /> + + + + - - - - } + description={withDocsLink( + `The template that auto fills ${node.text} page when generated.`, + ROAM_DOCS.creatingNodes, + )} + buttonText="Edit template" + onClick={() => nav.push(nodeConfigSegmentIds.template)} /> - - >( - node.type, - [DISCOURSE_NODE_KEYS.attributes], - )} - /> - c.text)} - initialValue={ - getDiscourseNodeSetting(node.type, [ - DISCOURSE_NODE_KEYS.overlay, - ]) ?? "" - } - order={0} - parentUid={node.type} - uid={overlayUid} - /> - - } + + + + + + + + + + + + {isSyncEnabled() && ( + + + + )} + + + >( + node.type, + [DISCOURSE_NODE_KEYS.attributes], + )} /> - - - - + c.text)} + initialValue={ + getDiscourseNodeSetting(node.type, [ + DISCOURSE_NODE_KEYS.overlay, + ]) ?? "" } + order={0} + parentUid={node.type} + uid={overlayUid} /> - {isSyncEnabled() && ( - - - - } - /> - )} - - + + ); }; diff --git a/apps/roam/src/components/settings/NodeIndexPage.tsx b/apps/roam/src/components/settings/NodeIndexPage.tsx new file mode 100644 index 000000000..c39558a82 --- /dev/null +++ b/apps/roam/src/components/settings/NodeIndexPage.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { getSubTree } from "roamjs-components/util"; +import { OnloadArgs } from "roamjs-components/types"; +import { DiscourseNode } from "~/utils/getDiscourseNodes"; +import DiscourseNodeIndex from "./DiscourseNodeIndex"; + +const NodeIndexPage = ({ + node, + onloadArgs, +}: { + node: DiscourseNode; + onloadArgs: OnloadArgs; +}): JSX.Element => { + const indexUid = getSubTree({ parentUid: node.type, key: "Index" }).uid; + return ( +
+ +
+ ); +}; + +export default NodeIndexPage; diff --git a/apps/roam/src/components/settings/NodeTemplatePage.tsx b/apps/roam/src/components/settings/NodeTemplatePage.tsx new file mode 100644 index 000000000..94947976d --- /dev/null +++ b/apps/roam/src/components/settings/NodeTemplatePage.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { getSubTree } from "roamjs-components/util"; +import { DiscourseNode } from "~/utils/getDiscourseNodes"; +import DualWriteBlocksPanel from "./components/EphemeralBlocksPanel"; +import { TEMPLATE_SETTING_KEYS } from "~/components/settings/utils/settingKeys"; +import { ROAM_DOCS, withDocsLink } from "./utils/docs"; + +const NodeTemplatePage = ({ node }: { node: DiscourseNode }): JSX.Element => { + const templateUid = getSubTree({ parentUid: node.type, key: "Template" }).uid; + return ( +
+ +
+ ); +}; + +export default NodeTemplatePage; diff --git a/apps/roam/src/components/settings/components/SettingsDrillDownRow.tsx b/apps/roam/src/components/settings/components/SettingsDrillDownRow.tsx new file mode 100644 index 000000000..1bb0b2c84 --- /dev/null +++ b/apps/roam/src/components/settings/components/SettingsDrillDownRow.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { Button, Intent, Label } from "@blueprintjs/core"; +import Description from "~/components/settings/SettingsDescription"; + +const SettingsDrillDownRow = ({ + title, + description, + buttonText, + onClick, +}: { + title: string; + description: React.ReactNode; + buttonText: string; + onClick: () => void; +}): JSX.Element => ( + +); + +export default SettingsDrillDownRow; diff --git a/apps/roam/src/components/settings/utils/settingsNavigation.ts b/apps/roam/src/components/settings/utils/settingsNavigation.ts index 2d27b08da..bcef6a444 100644 --- a/apps/roam/src/components/settings/utils/settingsNavigation.ts +++ b/apps/roam/src/components/settings/utils/settingsNavigation.ts @@ -14,6 +14,11 @@ export type SettingsNavAction = | { type: "pop" } | { type: "truncate"; depth: number }; +export const nodeConfigSegmentIds = { + index: "index", + template: "template", +} as const; + export const rootPath = (tabId: string): SettingsPath => [tabId]; export const tabIdOf = (path: SettingsPath): string => path[0] ?? ""; diff --git a/apps/roam/src/styles/settingsStyles.css b/apps/roam/src/styles/settingsStyles.css index 4a76d35a3..c6d345e00 100644 --- a/apps/roam/src/styles/settingsStyles.css +++ b/apps/roam/src/styles/settingsStyles.css @@ -81,3 +81,10 @@ .dg-settings-page-header__crumb { cursor: pointer; } + +.dg-settings-node-page { + display: flex; + flex-direction: column; + gap: 44px; + padding: 2px; +}