feat(auto-properties): description, multi-select, and learn-as-you-go values - #123
Conversation
Add optional `description` (issue #59) and `type: Single | Multi` (issue #40) to AutoProperty. Both are optional so existing data.json upgrades cleanly: a missing type is treated as Single, a missing description shows nothing. Introduce src/autoProperties.ts with pure, Obsidian-free helpers (multi-mode resolution that unions the per-property type with the global EditMode, choice normalization, current-value merging, and new-choice detection) plus full unit coverage, so the feature logic is testable in the jsdom-free node suite.
Rebuild the Auto Properties settings editor as themed property cards. Adds a Description field (issue #59) and a Single/Multi type dropdown (issue #40); existing entries with no type render as Single. Replaces the emoji buttons and the <br>-in-<table> spacing hack with native Obsidian clickable icons (setIcon), uses theme CSS variables so it works in dark mode (the old hardcoded light colors did not), and adds an empty state.
…-select, and learn-as-you-go Route Auto Property edits through a new AutoPropertyValueModal instead of the generic prompt, so Auto Properties own their value-entry UX end to end: - show the property description above the choices (#59) - a Multi auto property writes a real list and pre-checks current values; the per-property Multi type composes with the global EditMode via OR, so it works even under All Single (#40) - typing a value that is not a choice offers 'Use "x"' (one-off) and an explicit 'Save "x" as a choice' that persists it, so the list learns new values without silently collecting typos (#43) - a real placeholder and single-Enter confirm remove the old double-Enter and empty-placeholder friction (#30) editMetaElement intercepts active auto properties before the EditMode split so the single/multi decision lives in one place. handleAutoProperties now returns string | string[] | null and resolves null on cancel instead of rejecting. Covered by live Obsidian E2E tests for each behaviour plus back-compat with type-less entries.
Note the new description and Single/Multi options in the feature list, and that autoprop now returns string | string[] (Multi) or null.
Fold in opposing-model review findings: - addYamlProp no longer re-wraps an already-array value under global Multi mode, which previously produced a nested [[...]] list when creating a new Multi auto property (regression-tested live). - createNewProperty and editTag check for an active auto property up front, so cancelling the value prompt aborts instead of falling through to a second generic prompt (null no longer conflates cancel with no-auto-property). - type is now authoritative: explicit Single/Multi overrides the global EditMode in both directions; a type-less (pre-existing) entry still inherits EditMode. - multiSelectOptions lists current values first so editing preserves the user's existing list order and appends new selections instead of reshuffling. - persistAutoPropertyChoices appends to the live settings entry, so a concurrent settings-tab edit is not overwritten by a stale snapshot. - saving a learned choice is awaited and guarded, removing a fire-and-forget unhandled rejection. - splitMultiValue now delegates to the shared toValueArray helper. All unit + live Obsidian E2E tests green.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5ed7e9c933
ℹ️ 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".
| if (!Array.isArray(propValue) && | ||
| (settings.EditMode.mode === EditMode.AllMulti || | ||
| (settings.EditMode.mode === EditMode.SomeMulti && settings.EditMode.properties.contains(propName)))) { |
There was a problem hiding this comment.
Honor Single auto properties when adding YAML fields
When adding a new YAML property, this branch still wraps every non-array value in a list under All Multi or matching Some Multi, even if the value just came from an Auto Property explicitly configured as type: "Single". In that scenario, creating a new status auto property under global All Multi writes status:\n - done instead of the scalar status: done, while editing an existing auto property correctly lets the per-property Single type override the global mode. The wrapping needs to skip explicit Single auto properties here too.
Useful? React with 👍 / 👎.
5ed7e9c to
8b7c6e4
Compare
# [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
Makes Auto Properties genuinely useful and pleasant, end to end - both where you
define them (settings) and where you use them (the value prompt).
Closes #59, closes #43, closes #40, and addresses the Auto Properties items in #30.
Why this shape
Each issue title is a proposed solution; the table above restates the problem
behind it. A few deliberate design decisions:
AutoPropertyValueModal. All four asks live in the value-entryexperience, which previously reused the generic text prompt and so couldn't
show a description, learn values, or multi-select. Auto Properties now own that
UX in one cohesive, self-contained component.
editMetaElementroutes activeauto properties to it before the EditMode single/multi split, so the
single-vs-multi decision lives in exactly one place.
typeis authoritative, and composes with EditMode instead of fighting it.Explicit
Single/Multioverrides the global Edit Mode in both directions; anentry with no
type(data from before this PR) inherits Edit Mode, preservingprior behaviour on upgrade. This avoids the "two sources of truth" trap while
still letting you make one property multi without touching global settings.
would fill the list with typos and casing variants and would give the public
autoprop()API a hidden write side effect. Instead, persisting is a distinct,labelled action shown only when the value is new.
with native Obsidian icons (replacing the emoji buttons and the
<br>-in-<table>spacing hack) and uses theme CSS variables, so it renderscorrectly in dark mode (the old hardcoded light colors did not). Adds the
Description field and Type dropdown.
Scope decisions
not in this PR: it concerns the property-list suggester, not Auto
Properties, and belongs with the suggester work (Any chance we get the autocomplete for tags to work in the update window? #61). The other Couple UX improvements #30 items
(placeholder, single-Enter) are delivered.
lets a Multi property serialize as a real YAML list rather than a comma string.
Validation
All checks green locally (CI runs test + build + lint):
pnpm run test- 78 unit tests (new pure-logic suite insrc/autoProperties.test.ts).pnpm run build,pnpm run lint- clean (no new warnings).pnpm run test:e2e- 13 live Obsidian E2E tests pass, including 6 newAuto Properties tests driving the real modal via the DOM and asserting on-disk
results + an empty runtime-error baseline:
All Single(Add a Multi type to auto properties #40)[[...]]underAll Multi(Add a Multi type to auto properties #40 regression)nothing, and the settings editor persists name/description/type/add/remove to
data.json.The design and the implementation diff each went through an adversarial review on
the opposite model (3 design reviewers, 2 implementation reviewers). Their
high/medium findings were folded in - notably the nested-array fix, the
cancel-vs-no-match
nulldisambiguation, authoritativetypesemantics,order-preserving multi options, and appending learned choices to the live
settings entry so a concurrent settings-tab edit isn't overwritten.
Migration / compatibility
AutoPropertygains optionaldescription?andtype?- existingdata.jsonupgrades with no migration code (missing
typeinherits Edit Mode; missingdescriptionshows nothing).autoprop()API now returnsstring | string[] | null(a Multiproperty returns an array; it returned only strings before). README updated.