feat(format): support Obsidian property types in format syntax (#757)#1367
Conversation
Closes part of #757. Extends the VALUE token's |type: option (previously multiline-only) with: - |type:number -> a validated numeric input (NumberInputPrompt, <input type=number>), so the written 'x: 42' round-trips as an Obsidian Number. - |type:checkbox (alias |type:boolean) -> a forced true/false suggester (the |label becomes its title), so 'x: true' round-trips as a Checkbox. - |type:text -> a normal prompt that marks the value for type-aware YAML quoting (the quoting itself lands in a follow-up commit). Output stays raw-inline (no processFrontMatter / comment-strip); the scalar already inferred correctly, this adds the input affordance + validation. The one-page input form renders checkbox as a true/false dropdown for parity. e2e-verified: number input renders type=number; checkbox shows true/false; seeded runs write 'n: 42' (number) and 'done: true' (boolean).
…g type Closes part of #757 (the inverse coercion footgun). A {{VALUE}} written raw into frontmatter is parsed by Obsidian's YAML, which coerces strings like "0042" -> 42 and "true" -> boolean, silently changing a text property's type. QuickAdd now emits a double-quoted YAML scalar when: - the value is one Obsidian actually coerces (wouldYamlMisCoerce, pinned EMPIRICALLY against Obsidian's parser — yes/no/on/off, underscores and dates are NOT coerced, so they are deliberately left unquoted), AND - the token is the sole value at a frontmatter key:value or list-item position and not already author-quoted, AND - the author used |type:text, OR (under enableTemplatePropertyTypes, in a collection scope) the property is EXPLICITLY declared a Text type. Quoting is inline (no processFrontMatter), so YAML comments are preserved and number/checkbox/date round-tripping is unchanged. New pure yamlScalarQuoting helpers; isListItemPosition added to yamlContext. e2e-verified: id: "0042" (string) with comment kept; hello/dates stay unquoted; flag-off unchanged.
Closes part of #757. The VDATE prompt already opened a calendar; this adds an opt-in time control: - New |time flag (aliases |datetime and |type:datetime) parsed in vdateSyntax; the keyed |type: form is consumed so it never leaks into the natural-language default. Tokens without it stay byte-identical (withTime:false). - datePicker renders an <input type=time> when withTime; a day-click MERGES the selected day with the current time instead of overwriting to midnight, and editing the time after picking a day updates the value. The emitted ISO is an offset-less local 'YYYY-MM-DDTHH:mm:00' so the hour round-trips in every timezone (never toISOString/Z). - |time with no explicit format defaults to 'YYYY-MM-DD HH:mm'. - VDateInputPrompt mounts the picker from the constructor body (not during the super() display() pass) so this.withTime is set first. One-page input form threads withTime to its picker for parity. e2e-verified: time control renders; picking day 15 + 14:30 writes 'when: 2026-06-15T14:30'; the time survives a day-click.
Closes the headline #757 ask (and #621). A new |multi flag turns an option-list VALUE token into a multi-select picker that writes a real YAML list: - New MultiSuggester modal (toggle list + optional custom-add + Skip) returns string[]. |multi|custom adds off-list values; |multi:linklist wraps each pick as [[name]]. - The result is stored as an ARRAY, so it flows through the always-on container collector -> processFrontMatter -> a proper YAML list, INDEPENDENT of the enableTemplatePropertyTypes beta flag. - Guards from adversarial review: |case is dropped on |multi (warn) and the non-collected fallback joins arrays with commas, so |multi|case and body- position |multi can't throw or emit broken YAML; the capture engine warns when |multi can't become a list (non-new-file/append/existing-note captures). - One-page input parity: |multi sets suggesterConfig.multiSelect independently of |custom, and the joined modal value is split back into an array (with linklist wrapping) before it reaches the formatter. e2e-verified with the flag OFF: |multi -> [work, urgent]; |multi:linklist -> ["[[Alice]]","[[Carol]]"]; |multi|custom -> [work, zeta]; body position -> 'a,c' (comma string, no broken YAML).
Document the new format-syntax options in docs/docs (Next/unreleased only, not
the versioned snapshot):
- {{VALUE:x|type:number|checkbox|text}} property-type inputs + the note that
plain numbers/booleans/dates already round-trip and |type:text closes the
text-looks-like-a-number footgun.
- {{VALUE:a,b,c|multi}} multi-select List picker (+ |multi:linklist, |multi
|custom, capture caveat).
- {{VDATE:when,fmt|time}} date & time picker.
Cross-links added from Template Property Types.
High-severity (verified in-vault):
- Drop the AUTO |type:text path. metadataTypeManager.getTypeInfo reports
expected.type 'text' even for undeclared keys (verified), so auto-quoting
under the beta flag would have quoted EVERY coercion-prone scalar — e.g.
an undeclared number property 'rating: 42' -> '"42"'. Now only explicit
|type:text quotes; the flag no longer affects quoting.
- |type:text now ALWAYS quotes at a sole-value front-matter position instead
of only for number/bool/null. A coercion-only predicate missed YAML
indicator values (#todo -> null, [a] -> array, 'a: b' -> broken YAML);
quoting unconditionally there is bulletproof. Replaced wouldYamlMisCoerce
with a pure shouldQuoteTextScalar (position-only); removed the now-unused
getTypeInfo/expectedType plumbing from the collector.
Medium:
- Honor |type:text/number on the anonymous {{VALUE|...}} form (was parsed but
ignored): propagate inputTypeOverride into the value prompt context and quote
|type:text in its replacement.
- One-page input parity: render |type:number as a numeric field, and map
one-page multi-select DISPLAY labels back to option values (so |text:
mappings round-trip) before splitting into the array.
Low:
- Capture |multi warning no longer false-fires on |type:multiline (precise
regex), and named-conflict detection now includes multiSelect/multiEmit.
All e2e re-verified: flag ON + undeclared number/checkbox stay number/boolean;
|type:text quotes 0042/#todo/[a]; anonymous {{VALUE|type:text}} quotes.
- shouldQuoteTextScalar now treats a token followed only by a trailing YAML
comment (` #…`) as the sole value, so `id: {{VALUE:id|type:text}} # keep`
with input `#todo` writes `id: "#todo" # keep` (string, comment preserved)
instead of `id: #todo` (which Obsidian read as null). Shared yamlContext
position helper, so the collector benefits too.
- quoteYamlDouble escapes newlines/tabs/CR so a script-seeded multi-line value
stays valid YAML.
- Tighten the capture |multi warning regex to the exact flag (`|multi`
terminated by :/|/}/end), avoiding false positives on |multi1 / |multi-select.
Documented one-page-form limitation: a comma inside a single |multi option
can't round-trip there (the default picker handles it). Empty |type:text input
intentionally stays null (no value), consistent with all other tokens.
All tests/lint/build green; trailing-comment fix e2e-verified.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (5)
📝 WalkthroughWalkthroughAdds three new format-syntax features to QuickAdd: (1) ChangesEnhanced Template Property Types (issue
Sequence Diagram(s)sequenceDiagram
participant User
participant CompleteFormatter
participant MultiSuggester
participant Formatter
participant toWikiLink
User->>CompleteFormatter: Template with {{VALUE:a,b,c|multi:linklist}}
CompleteFormatter->>Formatter: ensureValueVariableResolved (multiSelect=true)
Formatter->>CompleteFormatter: suggestForValueMulti(["a","b","c"])
CompleteFormatter->>MultiSuggester: Suggest(app, displayItems, items, options)
MultiSuggester-->>CompleteFormatter: ["b","c"] (selected)
CompleteFormatter-->>Formatter: ["b","c"]
Formatter->>toWikiLink: wrap each value (multiEmit=linklist)
toWikiLink-->>Formatter: ["[[b]]","[[c]]"]
Formatter->>Formatter: store array in variables map
Formatter-->>User: YAML list output
sequenceDiagram
participant Formatter
participant parseVDateOptions
participant VDateInputPrompt
participant createDatePicker
Formatter->>parseVDateOptions: parse "tomorrow|time"
parseVDateOptions-->>Formatter: {withTime: true, defaultValue: "tomorrow", optional: false}
Formatter->>Formatter: set dateFormat = "YYYY-MM-DD HH:mm"
Formatter->>VDateInputPrompt: Prompt(app, "Date and time", ..., withTime=true)
VDateInputPrompt->>createDatePicker: mountDatePicker({withTime: true})
createDatePicker-->>VDateInputPrompt: picker with time input row rendered
VDateInputPrompt-->>Formatter: "2025-06-15 14:30"
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying quickadd with
|
| Latest commit: |
43994d5
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://6f3df055.quickadd.pages.dev |
| Branch Preview URL: | https://chhoumann-757-property-types.quickadd.pages.dev |
…rty-types-feasibility # Conflicts: # docs/docs/FormatSyntax.md
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3a281c6032
ℹ️ 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".
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/formatters/formatter.ts (1)
524-547:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRoute this warning through the project logger utility.
This touched path still emits through
console.warn, which bypasses the repository’s logging pipeline.As per coding guidelines:
**/*.{ts,tsx,js,jsx,svelte}should route logging through theloggerutilities for consistent output.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/formatters/formatter.ts` around lines 524 - 547, The warnOnNamedOptionConflict method uses console.warn() for logging, which bypasses the project's logging pipeline. According to the coding guidelines for TypeScript files, all logging must route through the logger utility for consistent output. Replace the console.warn() call in this method with the appropriate logger method from the project's logging utilities to ensure the warning message follows the proper logging pipeline.Source: Coding guidelines
🧹 Nitpick comments (1)
src/formatters/completeFormatter.test.ts (1)
110-112: ⚡ Quick winAdd focused assertions for the new multi-select/typed VALUE branches.
This mock wiring is in place, but a couple of direct tests would prevent regressions:
suggestForValueMulti(done/skip/cancel mapping) and anonymous{{VALUE|type:checkbox}}behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/formatters/completeFormatter.test.ts` around lines 110 - 112, The mock setup for multiSuggester is in place but direct test coverage is missing for the new multi-select and typed VALUE branches. Add focused test cases in the completeFormatter.test.ts file to cover suggestForValueMulti with its done/skip/cancel mapping behavior, and add a separate test for the anonymous VALUE syntax with type:checkbox (e.g., {{VALUE|type:checkbox}}). These tests will prevent regressions on the newly mocked multiSuggesterSuggest functionality.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/formatters/completeFormatter.ts`:
- Around line 388-400: The checkbox type override (|type:checkbox) is currently
only applied to named VALUE tokens in the promptForVariable path, but anonymous
VALUE tokens (those without a variable name) route through promptForValue
instead. To fix this, add the same checkbox type override logic to the
promptForValue function so that anonymous VALUE tokens with the checkbox
modifier also render the forced true/false picker instead of accepting arbitrary
free text input. Apply the same conditional check for context?.inputTypeOverride
=== "checkbox" and GenericSuggester.Suggest call with the same parameters
(["true", "false"] options and appropriate label) in the promptForValue path.
In `@src/gui/date-picker/datePicker.ts`:
- Around line 101-109: The extractTimeFromIso function currently accepts
out-of-range time values as valid. After the NaN checks for hour and minute
(following line 107), add explicit bounds validation to ensure hour is between 0
and 23 and minute is between 0 and 59. If either value falls outside these
ranges, return null instead of returning the invalid TimeParts object.
In `@src/preflight/RequirementCollector.test.ts`:
- Around line 410-447: The "RequirementCollector property types (`#757`)" describe
block and all its test cases use space indentation, which violates the repo's
formatting policy that requires tab indentation for TypeScript files. Replace
all leading spaces with tabs throughout the entire describe block, including the
run helper function definition and all it() test cases ("maps |type:number to a
number field", "maps |type:checkbox to a true/false dropdown", "maps |multi to a
multi-select suggester", and "carries |multi:linklist and |multi|custom
config"). Ensure the entire block uses consistent tab indentation with LF line
endings.
In `@src/preflight/RequirementCollector.ts`:
- Around line 310-315: The issue is that when a date key is reused across
multiple VDATE occurrences with different time specifications (some with |time
or |datetime), the withTime metadata is only captured on first insertion and not
merged when the same key appears later with |time or |datetime. Fix this by
updating the logic in scanDateTokens to check if a date key already exists in
the requirements collection, and if the current occurrence has withTime=true,
upgrade the existing requirement's withTime property to true rather than
skipping it. This ensures that if any VDATE occurrence for a key includes |time
or |datetime, the final requirement reflects that capability. The fix applies to
the code around the parseVDateOptions call in the provided diff (lines 310-315)
and also to the subsequent handling of the same requirement around lines 324-338
where the requirement is stored or updated.
In `@src/preflight/runOnePagePreflight.ts`:
- Around line 109-115: The current implementation in runOnePagePreflight.ts uses
split(",") to parse multi-select values from a comma-delimited string, which
corrupts any label or custom value containing a comma. Instead of parsing the
comma-delimited string with split(","), modify the flow to accept a structured
multi-value payload as a string[] directly from the modal/suggester path. This
means the upstream modal/suggester should pass the selected values as an array,
and the processing logic should skip the string splitting step entirely,
receiving the values already in the correct structured format. Then apply the
displayToValue mapping and filtering on the array of values as received, rather
than deriving them from comma-splitting.
In `@src/utils/valueSyntax.ts`:
- Around line 293-303: The code is using direct console.warn calls for warning
messages, which violates the repository's logging rules that require all logging
to be routed through the shared logger utility. Replace the two console.warn
calls in the unsupported VALUE type warning and the option-list VALUE token
warning with appropriate logger utility calls, ensuring consistency with the
project's logging pathway. This same pattern also appears in another section of
the file (around lines 512-523) and should be updated there as well.
---
Outside diff comments:
In `@src/formatters/formatter.ts`:
- Around line 524-547: The warnOnNamedOptionConflict method uses console.warn()
for logging, which bypasses the project's logging pipeline. According to the
coding guidelines for TypeScript files, all logging must route through the
logger utility for consistent output. Replace the console.warn() call in this
method with the appropriate logger method from the project's logging utilities
to ensure the warning message follows the proper logging pipeline.
---
Nitpick comments:
In `@src/formatters/completeFormatter.test.ts`:
- Around line 110-112: The mock setup for multiSuggester is in place but direct
test coverage is missing for the new multi-select and typed VALUE branches. Add
focused test cases in the completeFormatter.test.ts file to cover
suggestForValueMulti with its done/skip/cancel mapping behavior, and add a
separate test for the anonymous VALUE syntax with type:checkbox (e.g.,
{{VALUE|type:checkbox}}). These tests will prevent regressions on the newly
mocked multiSuggesterSuggest functionality.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 32e02ca6-1c71-49d7-a1fc-6962d3029da3
📒 Files selected for processing (26)
docs/docs/FormatSyntax.mddocs/docs/TemplatePropertyTypes.mdsrc/engine/CaptureChoiceEngine.tssrc/formatters/completeFormatter.test.tssrc/formatters/completeFormatter.tssrc/formatters/formatter-linksection.test.tssrc/formatters/formatter.tssrc/gui/InputPrompt.tssrc/gui/MultiSuggester/multiSuggester.tssrc/gui/NumberInputPrompt/NumberInputPrompt.tssrc/gui/VDateInputPrompt/VDateInputPrompt.tssrc/gui/date-picker/datePicker.test.tssrc/gui/date-picker/datePicker.tssrc/preflight/OnePageInputModal.tssrc/preflight/RequirementCollector.test.tssrc/preflight/RequirementCollector.tssrc/preflight/runOnePagePreflight.tssrc/styles.csssrc/utils/linkWrap.tssrc/utils/valueSyntax.test.tssrc/utils/valueSyntax.tssrc/utils/vdateSyntax.test.tssrc/utils/vdateSyntax.tssrc/utils/yamlContext.tssrc/utils/yamlScalarQuoting.test.tssrc/utils/yamlScalarQuoting.ts
- |type:text now skips the string->structured heuristic, so with the beta flag
ON a comma/bracket value (`a,b`, `[x]`) is quoted as a string instead of
being collected into a YAML List (e2e-verified: id: "a,b").
- Anonymous {{VALUE|type:checkbox}} now gets the same forced true/false picker
as the named form (was free text via promptForValue).
- One-page |multi without |custom drops typed off-list values, matching the
runtime MultiSuggester (was accepting any typed value).
- extractTimeFromIso rejects out-of-range HH:mm (e.g. 99:99) instead of
emitting an invalid datetime.
- scanDateTokens merges withTime across repeated VDATE occurrences so a reused
date with |time on any occurrence renders the time control.
Skipped (with rationale): the comma-inside-a-single-option one-page limitation
(needs an invasive shared-suggester rewrite; documented + the default picker
handles it), console.warn->logger (matches the existing convention in these
pure parser utils), and tab-vs-space in the new test (matches the file's
existing 2-space style).
|
Thanks @coderabbitai — addressed in 43994d5: Fixed
Intentionally not changed (rationale)
|
|
Tip For best results, initiate chat on the files or code changes.
The rationale for the three intentional non-changes all holds:
Everything looks good. 🐇✨ If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
Closes #757.
Full format-syntax support for Obsidian's property types. Everything output stays raw-inline (no
processFrontMatter, so YAML comments are preserved) except multi-select, which reuses the existing always-on array→List pipeline.What's new
{{VALUE:x|type:number}}x: 42→ Number{{VALUE:x|type:checkbox}}(alias|type:boolean)|label:is its title); writesx: true→ Checkbox{{VALUE:x|type:text}}id: "0042") so a number/bool/YAML-indicator-looking string stays Text{{VALUE:a,b,c|multi}}|multi:linklistwraps picks as[[name]];|multi|customadds off-list values{{VDATE:when,fmt|time}}(alias|datetime)Plain
{{VALUE}}already round-trips numbers/booleans/dates to the correct type — these options add the right input widget + validation, and|type:textcloses the inverse footgun (a text value Obsidian would otherwise retype).Also fixes a pre-existing red
tsc/pnpm buildat HEAD (a{{linksection}}test stub didn't implement the abstractsuggestForFileadded by the{{FILE:}}token).How it was verified
Every feature was driven end-to-end in an isolated Obsidian vault (
pnpm run obsidian:e2e), reading back the created note's frontmatter and parsed types — e.g.n: 42→number,done: true→boolean,id: "0042"→string,tags: [work, urgent](flag OFF),when: 2026-06-15T14:30. The "what Obsidian coerces" rules were pinned empirically against Obsidian's own parser.pnpm run build-with-lintgreen; 2328 unit tests pass (Vitest).Review
Hardened across two adversarial review rounds. Notably: an earlier auto-quoting path was removed after verifying
metadataTypeManager.getTypeInfo().expected.typereturns"text"even for undeclared keys (it would have quoted every42/trueunder the beta flag);|type:textnow quotes unconditionally at a sole-value front-matter position (covering#todo/[a]/trailing-comment cases), and only via the explicit token.Known limitations (documented)
|type:textinput writes an empty/nullvalue (consistent with every other token), not"".|multioption; the default one-prompt-at-a-time picker handles it correctly.Docs
docs/docs/FormatSyntax.md+docs/docs/TemplatePropertyTypes.md(Next/unreleased only).Summary by CodeRabbit
Release Notes
New Features
{{VALUE:<options>|multi}}, including YAML list output and optional wikilink emission using|multi:linklist.{{VDATE}}using|time/|datetime(with updated datetime default formatting).|type:number,|type:checkbox/|type:boolean, and|type:text, plus support for number input prompts and checkbox-style prompts.Documentation
|multiand format/token input-type selection.Bug Fixes
|multimay render as a comma-separated string instead of a YAML list.Tests