fix: edit YAML lists (incl. tags) as native lists instead of collapsing them to a string (#94) - #128
Conversation
…string (#94) A real YAML array is inherently multi-value, so it is now always edited with the element-aware list editor regardless of the global EditMode. Previously the default All Single mode routed a `tags`/`aliases` list through the single-line editor, which flattened it into a comma-joined string and lost the list type. The list editor is now element-preserving: it operates on the original typed array and replaces only the element the user touched, so every untouched element keeps its exact type, order, and spelling. This drops the old join-then-resplit round-trip that shredded elements containing commas (`Smith, John`) or brackets (`[[Home]]`). Add/insert sentinels are matched exactly so a real element that merely contains "cmd" is not mistaken for a command. standardMode is unchanged and still handles scalars. The routing/list logic lives in a new pure `src/multiValue.ts`, unit-tested in the jsdom-free node environment.
Drive the real suggester + prompt UI and the processFrontMatter write path: - #94: editing a YAML `tags` list in the default All Single mode keeps it a native list - #51: adding a tag via "Add to end" grows the native list - editing a comma-bearing element does not shred the others - a 'cmd'-containing element is edited, not mistaken for a command - a YAML scalar still uses the single-line editor (no regression) - #36: api.update with an array writes a native YAML list
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f3efbf39e8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const isAddCommand = | ||
| selectedOption === ADD_FIRST_ELEMENT || | ||
| selectedOption === ADD_TO_BEGINNING || | ||
| selectedOption === ADD_TO_END; |
There was a problem hiding this comment.
Avoid treating literal command sentinels as edits
When a YAML array is now routed into this list editor from All Single mode, an existing element whose literal value is exactly cmd:addfirst, cmd:beg, or cmd:end collides with the internal add sentinels returned by GenericSuggester. Selecting that real element is interpreted as an add command instead of a replacement, and cmd:addfirst in a non-empty list will replace the whole list with the prompted value, dropping the other elements; track the selected row/index or use non-colliding item identities rather than reserving user-visible strings.
Useful? React with 👍 / 👎.
| return false; | ||
| // YAML persists a real list (round-trips as a native YAML array); an | ||
| // inline/tag field stores the comma-joined string per the inline convention. | ||
| const newValue: unknown = property.type === MetaType.YAML ? newList : newList.join(", "); |
There was a problem hiding this comment.
Incorrect condition for determining output format. A YAML scalar edited in AllMulti mode will be written as an array instead of a string.
What breaks: When a YAML scalar property (e.g., status: open) is edited via multiValueMode in AllMulti or SomeMulti mode, the condition checks property.type === MetaType.YAML and writes newList (an array) directly, converting the scalar to an array ["new-value"].
Root cause: The condition uses property.type instead of editsArray. A YAML scalar reaches multiValueMode when EditMode is AllMulti/SomeMulti, where editsArray is false (scalar was split on commas), but property.type is still MetaType.YAML.
Fix:
const newValue: unknown = editsArray ? newList : newList.join(", ");This ensures only properties that were originally arrays (editsArray === true) are written back as arrays, while scalars (even YAML ones) are joined back to strings.
| const newValue: unknown = property.type === MetaType.YAML ? newList : newList.join(", "); | |
| const newValue: unknown = editsArray ? newList : newList.join(", "); |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
# [1.9.0](1.8.4...1.9.0) (2026-07-01) ### Bug Fixes * address review findings from PRs [#119](https://github.com/chhoumann/MetaEdit/issues/119)-[#128](https://github.com/chhoumann/MetaEdit/issues/128) ([adf0b57](adf0b57)) * **api:** preserve reserved keys when cloning values ([#162](#162)) ([cff6076](cff6076)) * **automators:** count only [x]/[X] tasks complete; null-guard Kanban lane display ([#145](#145)) ([21da53a](21da53a)) * **bulk:** reject __proto__/constructor as a bulk property key ([#148](#148)) ([f03eacc](f03eacc)) * **bulk:** serialize bulk frontmatter writes through the controller write queue ([#147](#147)) ([3eb8236](3eb8236)) * **controller:** guard __proto__/constructor in all frontmatter writes ([#159](#159)) ([9d99fae](9d99fae)) * **controller:** make inline-field writes fence-aware ([5f8e713](5f8e713)) * **core,api:** safe command no-op, block-list deletion, presence-based property lookups ([#143](#143)) ([1702d08](1702d08)) * edit YAML lists (incl. tags) as native lists instead of collapsing them to a string ([#94](#94)) ([#128](#128)) ([79f6ee0](79f6ee0)), closes [#51](#51) [#36](#36) * harden frontmatter metadata writes ([add2e90](add2e90)) * Kanban helper syncs only the card's leading link, not trailing date/reference links ([#126](#126)) ([52aec1e](52aec1e)) * **kanban:** don't write to an ambiguous same-named note ([#158](#158)) ([7f9655b](7f9655b)) * **kanban:** single notice for a card missing the board property ([9b1c88a](9b1c88a)) * parse inline fields behind list/quote markers and brackets ([#122](#122)) ([a62ef21](a62ef21)), closes [#119](#119) [#18](#18) [#121](#121) [#78](#78) [#84](#84) * **parser:** preserve [[wikilinks]] in multi-value inline edits ([c331adb](c331adb)) * **settings:** serialize Auto Property choice persistence to prevent lost updates ([#154](#154)) ([2066b3e](2066b3e)) * **settings:** settings tab no longer clobbers concurrently-added Auto Property choices ([5759534](5759534)) * **settings:** validate auto-properties before queueing and make rollbacks compare-and-restore ([#156](#156)) ([8cbacdc](8cbacdc)), closes [#154](#154) * stop inline-field updates appending a stray bracket ([#127](#127)) ([d2617f5](d2617f5)), closes [#67](#67) [#121](#121) * **suggester:** center row action icons flush-right ([#166](#166)) ([c00795e](c00795e)) * **suggester:** use native icons + tooltips for row actions ([70903cc](70903cc)) * **suggester:** well-formed duplicate Notice, robust modal close, cleaner name suggestions ([#144](#144)) ([915dd6b](915dd6b)), closes [#143](#143) * **tags:** correct and clarify tag editing ([#142](#142)) ([b837899](b837899)), closes [#49](#49) * tolerate malformed YAML frontmatter when parsing note metadata ([#132](#132)) ([b835f97](b835f97)), closes [#130](#130) ### Features * adopt modern Obsidian APIs and raise baseline ([5131664](5131664)) * **api:** append Dataview field instances without replacing ([42c78a8](42c78a8)), closes [#91](#91) * **api:** expand public integration surface ([#120](#120)) ([bbf0a7a](bbf0a7a)) * assist property-value entry with autocomplete and a native date picker ([#125](#125)) ([cd86235](cd86235)), closes [#61](#61) [#74](#74) [#61](#61) [#61](#61) * **auto-properties:** description, multi-select, and learn-as-you-go values ([#123](#123)) ([4b24296](4b24296)), closes [#59](#59) [#40](#40) [#59](#59) [#40](#40) [#59](#59) [#40](#40) [#43](#43) [#30](#30) * **auto-properties:** paste a list to split into choices ([#139](#139)) ([0245f7a](0245f7a)) * bulk edit metadata across folders and selected notes ([#124](#124)) ([c8d91e2](c8d91e2)), closes [#64](#64) [#20](#20) * **create:** fluid, type-aware native YAML property creation ([#170](#170)) ([59b4c5d](59b4c5d)) * **editor:** edit properties with Obsidian's native widgets; raise minAppVersion to 1.12.7 ([#168](#168)) ([73b37e0](73b37e0)) * hide file tags from the Edit Meta menu ([#46](#46), [#90](#90)) ([#131](#131)) ([2c07937](2c07937)), closes [#tag](https://github.com/chhoumann/MetaEdit/issues/tag) [#tags](https://github.com/chhoumann/MetaEdit/issues/tags) * support nested YAML path editing ([e9fce98](e9fce98))
|
🎉 This PR is included in version 1.9.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
The multi-value cluster (#94, #51, #31, #36) is one defect: MetaEdit lost the YAML list type when editing array properties (especially
tags). Editing atags/aliaseslist through the default All Single edit mode collapsed it into a comma-joined string.Root cause: a real YAML list is inherently multi-value, but the editor chose its mode from the global single/multi flag, not from the value's structure. In All Single mode a list went through the single-line text editor, which flattened it. The element-aware list editor also round-tripped lists through
join(", ")+ re-split, shredding elements that contain commas or[[wikilinks]].Fix
shouldUseMultiValueEditor).standardModeis untouched and still handles scalars.applyMultiValueEdit), so every untouched element keeps its exact type, order, and spelling. No morejoin+resplit. YAML persists a real list viaprocessFrontMatter(Obsidian's native block-list format); inline fields keep the comma-string convention."cmd"(e.g.cmd:build) is edited normally instead of collapsing the list (caught in adversarial review).src/multiValue.tsholds the routing + list logic so it is unit-tested in the jsdom-free node env.What was already fixed (verified, not rebuilt)
On today's master, #36 (API
updatewith an array), #51 (the write half), and #31 (list format) are already handled by theprocessFrontMatterwrite path from #119 - given a real array, Obsidian writes a native YAML list. Live evidence:api.update("foo", ["a","c"])onfoo: [a, b]->foo:\n - a\n - c. The remaining defect was #94's edit-flow collapse. Regression tests lock all of them.Closes
Closes #94. Addresses #51, #31, #36 (each reproduced live; the write-path ones were already fixed and are now covered by regression tests).
Validation
pnpm run lint(0 errors),pnpm run build,pnpm run test-> 171 unit tests pass (incl. newsrc/multiValue.test.tsand aparser.test.tsread-back case).tests/e2e/multi-value.test.ts, 6 tests) driving the real suggester + prompt UI andprocessFrontMatter:tags:\n - state/inprogress\n - ...-> native liststate/finishedapplied, read back as a real array.tagsYAML front matter, can we get a list of tags and add multiple tags? #51: "Add to end" grows the native list.Smith, John) and[[wikilinks]]survive an edit of a sibling element.cmd:buildelement is edited, not collapsed.updateing a multi value property does not work. #36:api.updatewith an array writes a native YAML list.obsidian-CLI timeout flakiness unrelated to this change - a different non-mine test times out on different runs and passes on retry).dev:errorsclean. Obsidian 1.x, isolated worktree vault.Adversarial review
Design and implementation were each reviewed by 3 opposing-model (Codex) reviewers. The design review rejected an initial
toValueArraysplit-back approach (it corrupts commas/brackets/types); the current structure-drives-editor + element-preserving design is the result. The implementation review caught thecmd-substring collapse (fixed). Remaining reviewer notes are pre-existing, out-of-scope behaviors, surfaced below.Out of scope (surfaced, not folded - separate features/workers)
updatewith a string into an existing array stays a scalar - the documented "you get the type you pass" contract (maintainer'supdateing a multi value property does not work. #36 position).addYamlProp/addDataviewFieldcomma handling each have their own type semantics in separate (recently merged) features.GenericSuggesterlimitation (returns the value, not the row); not regressed.Release impact
No settings/storage/API changes. Behavior change: editing a YAML list now always opens the list editor (correct for a list); scalar editing is unchanged.