feat: support for nested objects in frontmatter#546
Open
isabellabrookes wants to merge 27 commits intoMake-md:mainfrom
Open
feat: support for nested objects in frontmatter#546isabellabrookes wants to merge 27 commits intoMake-md:mainfrom
isabellabrookes wants to merge 27 commits intoMake-md:mainfrom
Conversation
The 's' (dotAll) regex flag used by textCacher requires es2018+.
parseProperty now normalizes object values via a recursive walker that
flattens single-key {path: ...} link shapes and serializes the rest as
JSON. The text/tag/option/image fallback also stringifies object values
so cell renderers never receive a raw object that React would coerce
to '[object Object]'.
inferCellTypeForValue wraps detectPropertyType, mapping 'unknown' to 'text' so undeclared values still get a cell renderer. deriveSchemaFromValue builds an ObjectType-shape schema from a sample object, recursing into nested objects and arrays-of-objects so deep sub-keys also get typed entries.
- Drop custom array/object chrome; route nested values through DataPropertyView so each sub-field gets its inferred cell type (links, dates, multi-options, etc.) and recurses through the existing cell pipeline. - Memoize allProperties to avoid cascading invalidations in deeply nested ObjectCells. - Move the collapse toggle into the property row (between field and value columns) and default cells to collapsed. - Add an inline '+ Property' button at the bottom of every editor level to add sub-fields; remove the redundant button in the value column of DataPropertyView. - Add 'Change Type' to the per-field menu, reusing showNewPropertyMenu with a new 'name' prop so the existing field name is pre-filled. - Wire object-multi items as draggable via @dnd-kit/sortable. Drag handle is the group header only; a per-instance namespace stamps drag data so reorders don't bleed across nested cells.
Remove the gate that prevented 'object' properties from showing the 'Add Property to Context' option. When syncing, derive the column's schema recursively from the actual value (parsing JSON-stringified frontmatter values first), so nested sub-fields propagate to other files in the space. If the column already exists, update it instead of duplicating.
widenObjectSchemasForPath additively merges any new sub-keys observed in a file's value into the column's schema (never overwrites or removes). Hooks into updateContextWithProperties so every frontmatter edit widens the schema as new sub-keys appear in any file. backfillObjectSchemasForSpace iterates every row in a space's table and applies per-path widening; runs once per session per space from contextReloaded to populate columns that were synced before the live hook existed.
defaultValueForType now returns {} for object and [] for object-multi
so ObjectEditor's '+ Property' seeds the right shape — without this,
'' was used and the live widening hook inferred 'text' for the entry.
…y multi - Persist collapsed state in a module-level Map keyed by path+column name so it survives remounts. Without this, editing a sub-field collapsed the whole cell on save (parent refreshData rebuilds cols on every save which remounts DataPropertyView). - Switch DataPropertyView keys in the children map from positional index to f.name so React preserves component identity across cols changes. - '+ Property' inside ObjectEditor now uses defaultValueForType to seed the new field's value, so object and object-multi sub-fields carry the right shape into widening. - Add an inline '+ <typeName>' button at the bottom of an object-multi cell so empty arrays can be seeded — without this, an empty object-multi rendered nothing and had no add affordance. - Make insertMultiValue defensive against undefined type or value.
mergeObjectSchema now upgrades an existing 'text' entry to a more specific inferred type when the live value clearly demands it (boolean, number, date, link, object, etc.). Non-text types are preserved — explicit user choices remain sticky.
Track backfilled object columns by '${spacePath}::${colName}' so
adding a brand-new object column to a space we've already loaded
this session still triggers a backfill scan. Previously, the
once-per-space gate skipped backfill for new columns added after
initial space load.
Cell renderers bubble values up as strings; coerceStringToType lifts them back to native primitives based on the column type tag so YAML stays clean and downstream inference doesn't misread them. tryParseJSON is a non-throwing JSON.parse wrapper used wherever we need to detect-and-parse a JSON-ish string.
Repeated saves of nested objects could leave values double-stringified in frontmatter. unwrapStringifiedJSON peels back up to 5 layers so the widening pass sees the real shape and infers types correctly. Applied both to the top-level column value and to each sub-key in mergeObjectSchema.
- Stringify primitives (booleans, numbers, dates) when passing initialValue to DataPropertyView so cell renderers that compare on string equality (BooleanCell etc.) read them correctly. - handleUpdate now calls tryParseJSON for object/object-multi sub-fields to avoid cascading stringifications, and coerceStringToType for primitive sub-fields so a checkbox toggle writes a real boolean back into frontmatter instead of the string "false".
- saveFieldValue now passes the outer value with just the inner field replaced — without this, changing an inner cell wiped every sibling key in the parent object. - Drop the editMode gate on showPropertyMenu so sub-property rename/change-type/delete works regardless of the host cell's edit mode (matches the +Property button which is also un-gated). - Strip [[wikilink]] brackets via parseLinkString before passing the value to LinkCell, and pass the file path as source so PathCrumb can resolve relative paths and surface friendly names. - Peel a once-serialized JSON-array string for link/option/object multi sub-fields so the unwrap applies uniformly even after a single round-trip through parseProperty.
mergeObjectSchema now upgrades a generic existing entry (text, option, option-multi) when the live value clearly demands something more specific (link, link-multi, etc.). Non-generic types are still preserved so explicit user choices stay sticky. Also exports mergeObjectSchema so unit tests can exercise the merge logic directly.
Vertical alignment of the field icon, collapse toggle, and value cell in mk-path-context-row reads better with center than flex-start now that the row can host the new collapse chevron.
- jest.config.js with ts-jest, src as a module root so the same baseUrl-style imports work in tests. - Tests for inferCellTypeForValue, deriveSchemaFromValue, coerceStringToType, tryParseJSON, defaultValueForType. - Tests for parseProperty's object/text/tag fallbacks (covers the '[object Object]' regression and recursive serialization). - Tests for mergeObjectSchema: additive merge, text-to-specific and option-multi-to-link-multi upgrades, explicit-type preservation, recursive nested merging.
Adds a GitHub Actions workflow that installs deps and runs jest on every PR and on pushes to main.
Resolves to current npm-compatible versions within the existing package.json ranges. No package.json changes; this just locks the versions used locally so CI's npm ci has a matching lockfile.
Replace the New-Property-style modal with a direct type-picker that fires saveType on each option click (saveOptions doesn't reliably fire for single-select pickers). Also writes a default value for the new type — mirrors PropertiesView.selectType so inference picks up the new type even when the file's m_fields write is a no-op (orphaned properties without a backing mdb).
Previously seeded the new type's default unconditionally on every type change, wiping any prior data. Now we only seed when the field is empty — existing values are kept and let the new cell renderer coerce on read (BooleanCell parses 'true', NumberCell etc.).
When changing a sub-field's type, try to convert the existing value into a shape that inference will recognise as the new type. Falls back to the new type's default only when the field is empty or the value can't be reasonably represented (e.g. "blah" → number). Caveat: date strings always infer as date even after switching to text — without persistent schema (orphaned properties), inference wins and there's no way to override that without breaking the value.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds proper rendering, editing, and space-schema synchronization for nested objects in frontmatter. Previously, nested objects rendered as
[object Object]in cells, couldn't be edited inline, and their schemas didn't propagate when a property was synced to a space.Before
After
demo.mp4
What changed
Rendering
[object Object]— values are recursed throughDataPropertyViewso each sub-field gets the right cell type (link, date, boolean, multi-select, etc.) inferred from the live value.Editing
+ Propertybutton at the bottom of every editor (top-level and nested) to add new sub-fields at any depth.Space synchronization
objectproperties from being added to a space (propertiesMenu).Tests + CI
main.Test plan
[object Object].+ Property→ pick a type → field is added to that level only.object-multicell, drag an item header to reorder.Notes
tsconfig.jsontargettoes2018(needed for thesregex flag used intextCacher).text,option,option-multi) to more specific inferred types when the live value warrants it (e.g. a column inferred asoption-multiupgrades tolink-multionce the values are wikilinks). It never overrides a non-generic explicit type, so user choices via Change Type are sticky.ObjectCellcoerces them back to the native primitive shape (boolean / number) before storing so YAML stays clean."blah"→ number).settings.notifications.verified) is not supported in this PR — the filter pipeline still operates on top-level cell values. Filed separately.