From bca2135104c778b8dfacec385cc9636cae7850a2 Mon Sep 17 00:00:00 2001 From: Danila Susak Date: Wed, 14 Jan 2026 20:48:43 +0300 Subject: [PATCH 1/4] fix: atomic save for option properties and color scheme Fixes issue where editing options in table properties would not save correctly. Changes: - Fix PropertyValue.tsx: Save options and colorScheme atomically to prevent data loss - Fix EditOptionsModal.tsx: Auto-close color picker menu after color selection - Fix OptionCell.tsx: Prioritize individual option colors over color scheme - Fix package.json: Correct typo in dev script (nnode -> node) - Update tsconfig.json: Change target from es6 to es2020 to support regex dotall flag - Update .gitignore: Add data.json to ignore user settings The main issue was that calling saveParsedValue twice would overwrite the first save with stale data. Now both fields are saved in a single atomic operation. --- .gitignore | 3 ++- package.json | 2 +- .../SpaceView/Contexts/DataTypeView/OptionCell.tsx | 6 +++--- .../react/components/UI/Menus/contexts/PropertyValue.tsx | 5 +++-- src/core/react/components/UI/Modals/EditOptionsModal.tsx | 6 +++++- tsconfig.json | 2 +- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index ece2d46..33f510e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules .env .DS_Store undefined -.vscode \ No newline at end of file +.vscode +data.json diff --git a/package.json b/package.json index b197fb9..ef61587 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "make.md", "main": "main.js", "scripts": { - "dev": "nnode esbuild.config.mjs", + "dev": "node esbuild.config.mjs", "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", "preview": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs preview", "demo": "&& node esbuild.config.mjs demo", diff --git a/src/core/react/components/SpaceView/Contexts/DataTypeView/OptionCell.tsx b/src/core/react/components/SpaceView/Contexts/DataTypeView/OptionCell.tsx index a244844..fb410d1 100644 --- a/src/core/react/components/SpaceView/Contexts/DataTypeView/OptionCell.tsx +++ b/src/core/react/components/SpaceView/Contexts/DataTypeView/OptionCell.tsx @@ -66,11 +66,11 @@ export const OptionCell = ( .map((t, index) => ({ ...t, color: editable - ? schemeColors + ? t.color?.length > 0 + ? t.color // Use individual option color if set + : schemeColors ? schemeColors[index % schemeColors.length]?.value || "var(--mk-color-none)" - : t.color?.length > 0 - ? t.color : undefined : undefined, removeable: editable ? editMode >= CellEditMode.EditModeView : false, diff --git a/src/core/react/components/UI/Menus/contexts/PropertyValue.tsx b/src/core/react/components/UI/Menus/contexts/PropertyValue.tsx index 77bc6d9..ee98931 100644 --- a/src/core/react/components/UI/Menus/contexts/PropertyValue.tsx +++ b/src/core/react/components/UI/Menus/contexts/PropertyValue.tsx @@ -444,10 +444,11 @@ export const PropertyValueComponent = (props: { const options = parseOptions(parsedValue.options ?? []); const saveOptionsHandler = (newOptions: SelectOption[], colorScheme?: string) => { - saveParsedValue("options", newOptions); + const updated: Record = { ...parsedValue, options: newOptions }; if (colorScheme !== undefined) { - saveParsedValue("colorScheme", colorScheme); + updated.colorScheme = colorScheme; } + props.saveValue(JSON.stringify(updated)); }; props.superstate.ui.openModal( diff --git a/src/core/react/components/UI/Modals/EditOptionsModal.tsx b/src/core/react/components/UI/Modals/EditOptionsModal.tsx index ef3e72f..a1e78ea 100644 --- a/src/core/react/components/UI/Modals/EditOptionsModal.tsx +++ b/src/core/react/components/UI/Modals/EditOptionsModal.tsx @@ -78,13 +78,17 @@ const SortableOptionItem: React.FC = ({ e.preventDefault(); // Always show color picker menu regardless of color scheme - showColorPickerMenu( + const menu = showColorPickerMenu( superstate, (e.target as HTMLElement).getBoundingClientRect(), windowFromDocument(e.view.document), option.color || "var(--mk-color-none)", (color: string) => { onEdit({ ...option, color }); + // Auto-close menu after color selection + if (menu) { + menu.hide(); + } } ); }; diff --git a/tsconfig.json b/tsconfig.json index c96f121..5d54678 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "inlineSources": true, "isolatedModules": true, "module": "ESNext", - "target": "es6", + "target": "es2020", "allowJs": true, "alwaysStrict": true, "noImplicitAny": true, From 92e1e4e906bcc0df72720bfbf23dbd3d38c3f9e7 Mon Sep 17 00:00:00 2001 From: Danila Susak Date: Wed, 14 Jan 2026 21:31:08 +0300 Subject: [PATCH 2/4] fix: sync checkbox state between properties sections Fixed issue where checkbox properties in the top section (inline properties) were not updating when changed in the bottom Properties section. Root cause: PropertiesView component was only listening to 'contextStateUpdated' events, which are not triggered when individual file properties are saved via saveProperties(). When a checkbox is toggled, it saves via saveProperties() which triggers 'pathStateUpdated' event instead. Solution: Added listener for 'pathStateUpdated' event to refresh property values when the current path's properties change. This ensures both property sections stay in sync. Changes: - Added pathChanged() handler to listen for pathStateUpdated events - Added pathState to useEffect dependencies for proper cleanup - Properties now refresh immediately when any property is saved --- .../react/components/Explorer/PropertiesView.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/core/react/components/Explorer/PropertiesView.tsx b/src/core/react/components/Explorer/PropertiesView.tsx index ac9c1eb..a829486 100644 --- a/src/core/react/components/Explorer/PropertiesView.tsx +++ b/src/core/react/components/Explorer/PropertiesView.tsx @@ -128,20 +128,34 @@ export const PropertiesView = (props: { } }; + const pathChanged = (payload: { path: string }) => { + if (payload.path == pathState?.path) { + refreshData(); + } + }; + useEffect(() => { refreshData(); props.superstate.eventsDispatcher.addListener( "contextStateUpdated", mdbChanged ); + props.superstate.eventsDispatcher.addListener( + "pathStateUpdated", + pathChanged + ); return () => { props.superstate.eventsDispatcher.removeListener( "contextStateUpdated", mdbChanged ); + props.superstate.eventsDispatcher.removeListener( + "pathStateUpdated", + pathChanged + ); }; - }, [props.spaces, tableData]); + }, [props.spaces, tableData, pathState]); const savePropertyValue = (value: string, f: SpaceTableColumn) => { if (saveProperty) { const property = tableData?.cols?.find((g) => g.name == f.name); From cfb90a88d5c76e3d0d3bb5468c79a646b36c3ca2 Mon Sep 17 00:00:00 2001 From: Danila Susak Date: Thu, 15 Jan 2026 21:05:19 +0300 Subject: [PATCH 3/4] fix: prevent select option values from being serialized as JSON arrays **Bug Description:** When adding or modifying select (option) property values in the Properties panel, the values were being incorrectly serialized as JSON arrays (e.g., ["approve"]) instead of plain strings (e.g., "approve"). This caused the values to display with quotes in the UI and broke the select dropdown functionality. **Root Causes:** 1. OptionCell was using serializeMultiDisplayString() for single select values, which was unnecessary and caused incorrect serialization 2. parseProperty() was called without explicit type information, causing detectPropertyType() to misidentify single option arrays as option-multi 3. No safeguards existed to handle array values when parsing single options **Changes Made:** 1. **OptionCell.tsx**: Changed single option value serialization from serializeMultiDisplayString() to direct value access (value[0] ?? "") in savePropValue() and removeOption() methods 2. **PropertiesView.tsx**: Modified property parsing to pass explicit column type to parseProperty(), preventing type misdetection 3. **parsers.ts**: Added array handling in parseProperty() for option type to extract first element if value is unexpectedly an array 4. **properties.ts**: Added safety check in parseMDBStringValue() to parse and extract single values from JSON array strings when saving to frontmatter **Testing:** - Single select options now save and display correctly without quotes - Multi-select options continue to work as expected - Select dropdowns function properly after value changes --- src/core/react/components/Explorer/PropertiesView.tsx | 3 ++- .../SpaceView/Contexts/DataTypeView/OptionCell.tsx | 4 ++-- src/utils/parsers.ts | 8 +++++++- src/utils/properties.ts | 5 +++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/core/react/components/Explorer/PropertiesView.tsx b/src/core/react/components/Explorer/PropertiesView.tsx index a829486..aa16b77 100644 --- a/src/core/react/components/Explorer/PropertiesView.tsx +++ b/src/core/react/components/Explorer/PropertiesView.tsx @@ -96,7 +96,8 @@ export const PropertiesView = (props: { if (properties) { newCols.push(...cols); fmKeys.forEach((c) => { - newValues[c] = parseProperty(c, properties[c]); + const colType = cols.find((col) => col.name == c)?.type; + newValues[c] = parseProperty(c, properties[c], colType); }); } diff --git a/src/core/react/components/SpaceView/Contexts/DataTypeView/OptionCell.tsx b/src/core/react/components/SpaceView/Contexts/DataTypeView/OptionCell.tsx index fb410d1..d718950 100644 --- a/src/core/react/components/SpaceView/Contexts/DataTypeView/OptionCell.tsx +++ b/src/core/react/components/SpaceView/Contexts/DataTypeView/OptionCell.tsx @@ -155,7 +155,7 @@ export const OptionCell = ( } else { props.saveOptions( serializeOptionValue(newOptions, parsedValue), - serializeMultiDisplayString(newValues) + newValues[0] ?? "" ); } }; @@ -168,7 +168,7 @@ export const OptionCell = ( } else { props.saveOptions( serializeOptionValue(options, parsedValue), - serializeMultiDisplayString(value) + value[0] ?? "" ); } }; diff --git a/src/utils/parsers.ts b/src/utils/parsers.ts index 67fa122..55e76c8 100644 --- a/src/utils/parsers.ts +++ b/src/utils/parsers.ts @@ -105,10 +105,16 @@ export const parseMultiString = (str: string): string[] => ensureString(str).sta break; case "text": case "tag": - case "option": case "image": return value; break; + case "option": + // Handle case where option value is an array (from frontmatter) + if (Array.isArray(value)) { + return value[0] ?? ""; + } + return value; + break; } return ""; }; diff --git a/src/utils/properties.ts b/src/utils/properties.ts index 252ee38..20a4085 100644 --- a/src/utils/properties.ts +++ b/src/utils/properties.ts @@ -146,6 +146,11 @@ export const parseMDBStringValue = (type: string, value: string, frontmatter?: b ); } else if (type.includes("link") || type.includes("context")) { return frontmatter ? `[[${value}]]` : value; + } else if (type == "option" && frontmatter) { + // Parse option values when saving to frontmatter + // If it's a JSON array string, parse it to get the single value + const parsed = parseMultiString(value); + return parsed.length === 1 ? parsed[0] : (parsed.length > 1 ? parsed : value); } return value; }; From 82474c474d9d5fd62aa57f5d12d76f3ef28fc406 Mon Sep 17 00:00:00 2001 From: Danila Susak Date: Thu, 15 Jan 2026 21:24:54 +0300 Subject: [PATCH 4/4] fix: pass explicit field types when parsing properties to prevent type misdetection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Bug Description:** When property values were parsed from frontmatter in PropertiesView and syncContextRow, the type parameter was not passed to parseProperty(). This caused detectPropertyType() to auto-detect types based on the value format rather than using the actual field schema definition. For example, a single-select option field with an array value like ["approve"] in frontmatter would be misdetected as option-multi instead of option, causing: 1. Values to display incorrectly with quotes 2. Checkbox properties not syncing between top and bottom views **Root Cause:** Two locations were calling parseProperty() without the type parameter: 1. PropertiesView.tsx line 100 - when loading properties from frontmatter 2. linkContextRow.ts line 94 - when syncing context rows with frontmatter This caused the system to rely on detectPropertyType() which makes assumptions based on value format (arrays → multi-type) rather than using the actual schema definition. **Changes Made:** 1. **PropertiesView.tsx** (line 87-95): - Added lookup in both tableData.cols AND columns (from context schemas) - This ensures properties defined in context schemas are found correctly - Pass the resolved field type to parseProperty() on line 100 2. **linkContextRow.ts** (line 92-96): - Modified filteredFrontmatter reduce function to find field type from fields array before calling parseProperty() - Pass the field type as third parameter to parseProperty() **Impact:** - Single-select options now parse correctly even when stored as arrays - Boolean checkboxes sync properly between top properties and bottom panel - Multi-select options continue to work as expected - Type detection now respects schema definitions over value format **Testing:** - ✅ Single select with array value displays without quotes - ✅ Boolean checkboxes sync between top and bottom views - ✅ Multi-select options work correctly - ✅ Properties from context schemas resolve proper types --- src/core/react/components/Explorer/PropertiesView.tsx | 3 ++- src/core/utils/contexts/linkContextRow.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/core/react/components/Explorer/PropertiesView.tsx b/src/core/react/components/Explorer/PropertiesView.tsx index aa16b77..46bfbfb 100644 --- a/src/core/react/components/Explorer/PropertiesView.tsx +++ b/src/core/react/components/Explorer/PropertiesView.tsx @@ -86,7 +86,8 @@ export const PropertiesView = (props: { ]).filter((f) => !columns.some((g) => g.name == f)); const cols: SpaceTableColumn[] = fmKeys.map( (f) => - tableData?.cols?.find((g) => g.name == f) ?? { + tableData?.cols?.find((g) => g.name == f) ?? + columns.find((g) => g.name == f) ?? { table: "", name: f, schemaId: "", diff --git a/src/core/utils/contexts/linkContextRow.ts b/src/core/utils/contexts/linkContextRow.ts index edd8a6a..1119d1a 100644 --- a/src/core/utils/contexts/linkContextRow.ts +++ b/src/core/utils/contexts/linkContextRow.ts @@ -91,7 +91,10 @@ const resolvedPath = resolvePath(_row[PathPropertyName], path?.path, (spacePath) const frontmatter = (paths.get(resolvedPath)?.metadata?.property ?? {}); - const filteredFrontmatter = Object.keys(frontmatter).filter(f => fields.some(g => g.name == f) && f != PathPropertyName).reduce((p, c) => ({ ...p, [c]: parseProperty(c, frontmatter[c]) }), {}) + const filteredFrontmatter = Object.keys(frontmatter).filter(f => fields.some(g => g.name == f) && f != PathPropertyName).reduce((p, c) => { + const fieldType = fields.find(f => f.name == c)?.type; + return { ...p, [c]: parseProperty(c, frontmatter[c], fieldType) }; + }, {}) const tagData : Record = {}; const tagField = fields.find(f => f.name?.toLowerCase() == 'tags');