Skip to content

Task date marks

This guide is the authoritative display contract for task date-family marks (canonical task dates plus recurrence rules), in the style of docs/freshness.md §11 and the docs/projects.md "Priority marks" section. The JavaScript mirror is api.dateMarks (namespace v1, top-level api v3) in bob-ledger-tools, which stays dates-only; its tests run the conformance vectors below verbatim.

What you see

Every canonical created, scheduled, completion, and cancelled task date renders as a small monochrome icon plus a calendar label instead of a KEY | YYYY-MM-DD Dataview pill, and every readable [repeat:: …] rule renders as a quiet loop plus a spoken cadence:

before: - [ ] Add ability to install agent CLIs  CREATED│2026-08-31  SCHEDULED│2026-09-04
after:  - [ ] Add ability to install agent CLIs  + Aug 31  ⧗ Sep 4

More examples (glyphs approximated in plain text):

- [?] Ship the importer  + yesterday  ⧗ Fri
- [ ] Rename the queue input  + Sep 29  ⧗ today          ← scheduled today reads a touch stronger
- [x] Review the /sase_memory_write skill  + Aug 29  ✓ Sep 3   ← closed tasks rest (faint)
- [-] Old idea  + Mar 2  ⊘ today
- [?] Brush teeth  ⟲ daily  + today  ⧗ tomorrow
- [ ] Pay Kelly salary!  ⟲ monthly · 1st  + Oct 8  ⧗ Nov 1

Hovering any mark shows the exact date and its distance, for example Scheduled Fri, Oct 9 · in 2 days followed by Ctrl+Shift+P to reschedule. Moving the cursor into a mark, or clicking it, reveals [scheduled:: 2026-10-09] for editing.

Principles

Display-only: [key:: YYYY-MM-DD] stays the only stored form; nothing writes a date mark, and the Rust, Tasks, and Dataview semantics are unchanged. Store absolute, show relative: near dates read as words, and the tooltip always has the exact date. Shape carries meaning; color whispers: each field has its own silhouette, ink is monochrome, and emphasis comes from weight and opacity, never hue. Quiet by default, louder only when it matters: created is the quietest mark, and a scheduled date arriving today is the only emphasized state. Reversible: the cursor or a click reveals the raw field, source mode shows raw text, a session toggle restores the pills, and without bob-ledger-tools the vault looks exactly as it does today. One glyph definition (CSS masks) shared by every surface.

Fields and glyphs

The four glyphs are refined, monochrome versions of the Tasks emoji already seen in query results (➕ ⏳ ✅ ❌). The one deliberate change is cancelled: ⊘ replaces ×, because a bare × beside the created + reads as a pair of math operators (and as a close button).

Stored key Tooltip verb Glyph Tasks emoji it replaces
created Created plus ➕
scheduled Scheduled hourglass ⏳
completion Done check ✅
cancelled Cancelled circle-slash (⊘) ❌
repeat — (not a date; see below) loop (⟲) 🔁 (Tasks recurrence)

due and start are out of scope: the vault uses start for clock times and due only 3 times. Adding one later is a new row here, a new glyph, and a new key in the repair-flag selector.

Final geometry (viewBox 0 0 16 16, black stroke, stroke-width 1.9, stroke-linecap/stroke-linejoin round, fill none; used as CSS masks): plus M8 3.25v9.5 M3.25 8h9.5; hourglass M4 1.9h8 M4 14.1h8 with bulbs M5.1 1.9v1.6c0 1.9 1.2 3.1 2.9 4.5c1.7-1.4 2.9-2.6 2.9-4.5V1.9 and M5.1 14.1v-1.6c0-1.9 1.2-3.1 2.9-4.5c1.7 1.4 2.9 2.6 2.9 4.5v1.6; check M3.1 8.6l3.1 3.1l6.7-6.9; circle-slash <circle cx="8" cy="8" r="5.9"/> plus M3.83 3.83l8.34 8.34.

Label grammar

Let Δ be whole days from today (local date) to the field's date.

Δ Label
0 today
+1 tomorrow
−1 yesterday
+2 … +6 short weekday of the date: Mon … Sun
any other Δ, same year Oct 22 (short month, day without padding)
any other Δ, different year Oct 22, 2027

Weekday names are used only for the coming six days: a past weekday would make ⧗ Mon ambiguous, so past dates beyond yesterday use month and day. The weekday rule wins over the year rule (on Dec 30, a Jan 1 date reads Fri). Words are lowercase; month and weekday names are capitalized fixed English, matching the freshness mark. One grammar for all four fields makes a closed task read as a small timeline (+ Aug 29 ✓ Sep 3); the task's age stays one hover away.

Tooltip

The mark carries role="img", aria-label, and data-tooltip-position="top", lines joined with \n, never containing ::. Line 1 is {Verb} {short date} · {relative}, where {short date} is the freshness short date (Fri, Oct 9, year appended across years) and {relative} is today, tomorrow, yesterday, in N days, or N days ago in exact days. Line 2, scheduled only, is Ctrl+Shift+P to reschedule. Examples: Created Tue, Sep 29 · 8 days ago; Scheduled Fri, Oct 9 · in 2 days + reschedule hint; Done Mon, Oct 5 · 2 days ago; Cancelled Tue, Dec 30, 2025 · 281 days ago.

Anatomy and tones

A mark is span.bob-date-mark with span.bob-date-mark-glyph (the mask) and span.bob-date-mark-label (the text), carrying data-field, data-date, data-when (past | today | future), and data-fold-space (data-rendered="true" on rendered-view marks). Metrics match .bob-fresh-mark exactly (interface font at 0.8em, tabular-nums, weight 550, inline-flex with a 0.22em gap, pill padding/margin, vertical-align: 0.05em); the glyph box is 1.08em square; hover is opacity 1 plus a 10% currentColor capsule; transitions off under prefers-reduced-motion.

Situation Ink Opacity / weight
default --bob-date-color-{field}, else --text-muted 0.85
quiet: any created; past scheduled same ink 0.7
emphasis: scheduled with data-when="today" --bob-date-color-today, else --text-normal 1, weight 650
resting: inside a closed task (x, X, -) --text-faint 0.75; wins over every other row

Resting is derived from the nearest li.task-list-item[data-task] or .HyperMD-task-line[data-task] ancestor, exactly like the priority mark. Theme hooks --bob-date-color-{created,scheduled,completion,cancelled,today} are unset by default. There is no red and no "overdue" state: a past scheduled date means the task already resurfaced, not that it is late.

Eligibility and folding

For each key: the occurrence gate counts /(^|[^A-Za-z0-9_-])key\s*::/gi and requires exactly one (so [rescheduled:: …] never counts, and duplicates get no mark). The canonical form is [key:: YYYY-MM-DD] or (key:: YYYY-MM-DD) with a matching bracket pair, a lowercase key touching ::, padding spaces only, and a strict calendar date. Each key is judged on its own. Any canonical field outside code gets a mark: task lines, plain bullets, quotes, continuation lines, paragraphs. Excluded: source mode and code positions in Live Preview; text under code, pre, .dataview.inline-field, .bob-date-mark, .bob-priority-mark, or .bob-fresh-mark in rendered views.

Whitespace-run folding (Live Preview): the decoration starts at the first space of the U+0020 run directly before the field, and the widget restores one uniform gap — Tasks-written double-spaced completion/cancelled fields sit with the same gap as single-spaced ones. A field beginning the line's content never folds. A selection endpoint never sits inside a fold. While a cursor or selection end lies strictly inside the space run, that field's mark folds nothing and the spaces show as text; the fold returns once the cursor leaves the run. Folded spaces still never reveal the field.

Repair flag

While marks are on, any leftover created/scheduled/completion/cancelled/repeat Dataview pill in Live Preview is a field Tasks cannot read (or a guarded repeat): a bad date, a word, a datetime, a duplicate, mismatched brackets, an uppercase key, a space before ::, or the checklist guard. CSS flags it with full opacity and a dashed orange border, matching the freshness and priority flags. Template placeholders in _templates are also flagged: the flag truthfully says Tasks cannot read the value as a date.

Recurrence (not a date)

A recurrence mark is span.bob-date-mark[data-field="repeat"] with data-rule (the rule as written, whitespace-collapsed), data-when-done (true/false), and data-fold-space (data-rendered="true" on rendered views). It contains the usual span.bob-date-mark-glyph + span.bob-date-mark-label. It has no data-date and no data-when, and keeps role="img", aria-label, and data-tooltip-position="top".

Glyph: a rectangular two-arrow loop, a refined monochrome 🔁. It deliberately avoids the circular arrow, which already means ⟳ Review and due freshness. Geometry (viewBox 0 0 16 16, black stroke, stroke-width 1.8, round caps and joins): M2.6 9.4V6.6A2.6 2.6 0 0 1 5.2 4h8.2M11 1.6 13.4 4 11 6.4M13.4 6.6v2.8A2.6 2.6 0 0 1 10.8 12H2.6M5 9.6 2.6 12 5 14.4. Defined as --bob-date-glyph-repeat.

Ink is always the quiet tone: --bob-date-color-repeat, else --text-muted, at opacity 0.7 (like created). There is no emphasis state and no hue. Closed rows rest faint via explicit li.task-list-item[data-task=x|X|-] .bob-date-mark[data-field="repeat"] and .HyperMD-task-line[...] rules, because the existing resting selector requires [data-when].

Validity gate (all must hold, else the native pill stays): exactly one /(^|[^A-Za-z0-9_-])repeat\s*::/gi on the line or text node; [repeat:: …] or (repeat:: …) with matching brackets, a lowercase key touching ::, and ASCII space padding only; the raw value matches Tasks' charset ^[a-zA-Z0-9, !]+$ before repeated spaces are collapsed, and starts with every plus a space and more text (case-insensitive); tabs, non-breaking spaces, and other whitespace are invalid rather than normalized; code positions are excluded exactly as for dates; and the checklist guard below does not fire. Known limit: the charset check is necessary but not sufficient — every blursday gets a verbatim label, which is still truthful text.

Checklist guard: no mark when the line is a checklist member (checklistKindForText non-null), the task is open (status not x, X, -), and the rule lacks when done. This mirrors the checklist_repeat_not_when_done lint scope (open members), so the display never hides what the lint flags. Live Preview reads the whole line plus freshnessTaskStatus; rendered views read the nearest li.task-list-item ancestor's own a.tag anchors (skipping nested lists and code) and its data-task, lazily and only for text that contains a repeat source.

Label grammar ("say it the way a person would"): normalize by trimming, collapsing whitespace, and stripping a trailing when done (case-insensitive). Labels are lowercase except weekday and month names, use · (U+00B7 with spaces) between cadence and detail, and are never truncated. day/weekday → daily/weekdays; week/month/year (or 1 week etc.) → weekly/monthly/yearly; N days/weeks/months/years (N ≥ 2) → every N days (and so on); <Weekday> or week on <Weekday> → Mondays; week on <Weekday list> → weekly · Tue, Fri; N weeks on <Weekday list> (N ≥ 2) → every N weeks · Fri; month on the <ordinal>/last/last <Weekday>/<ordinal> <Weekday> → monthly · 1st/monthly · last/monthly · last Fri/monthly · 2nd Wed; N months on the <ordinal>[ <Weekday>] (N ≥ 2) → every N months · 1st; <Month> on the <ordinal> → yearly · Jan 15; anything else stays verbatim (collapsed, minus when done). Ordinals keep their written token, lowercased. The grammar covers every rule in the vault today.

Tooltip: two lines, never containing ::. Line 1 is Repeats <rule as written, collapsed>; line 2 is Next date counts from completion when whenDone, else Next date follows the schedule. relabelRenderedDateMarks already skips repeat marks because dateMarkModel returns null for repeat. api.dateMarks v1 stays dates-only: fields is unchanged and model("repeat", …) is null.

Surfaces and interaction

Live Preview uses a Prec.highest ViewPlugin emitting one Decoration.replace per eligible field, in line order. The mark hides while any selection overlaps its own field span, so editing created never reveals scheduled. A selection endpoint never sits inside a fold: a cursor resting in the folded spaces unfolds that field's mark to fold 0. Mousedown places the cursor at the field start and focuses the editor; it never writes. Widget equality includes the label, when, and tooltip, so a new day always re-renders. Rebuilds happen on doc, viewport, or selection changes, Live Preview or file switches, and the dateMarksRefresh effect. The rendered-view post-processor (sort order 50, before Dataview's pass at 100) splits each matching text node into text / mark / text / mark / … for every eligible field, covering reading view, embeds, hover previews, Dataview TASK views, and Tasks descriptions with a non-trailing field. A duplicate split across separate text nodes is not detected (the accepted priority limit).

Tasks query results

Tasks 8.4.0 always renders query-result date components with its emoji serializer: span.task-created|task-scheduled|task-done|task-cancelled wrapping an inner span whose text is " ⏳ 2026-10-09" (full mode) or " ⏳" (short mode). The DOM contract, confirmed against the installed obsidian-tasks-plugin/main.js (renderTaskLine, taskToHtml, tasks-layout-short-mode):

  • Each date component is a direct child span of li.plugin-tasks-list-item > .tasks-list-text, with its text in a single inner span. done maps to task-done.
  • The li receives data-task only at the end of renderTaskLine, so it is the completion signal.
  • Short-mode containers carry tasks-layout-short-mode.
  • Date spans keep Tasks' click (date editor) and contextmenu (postpone) listeners.

CSS cannot split the emoji from the date, so unlike the priority mark (whose Tasks host is CSS-only), full mode needs a small, narrow JS frame pass — the narrowest JS that degrades to Tasks' native rendering. scheduleTasksResultDateMarks(el) runs at the end of renderDateMarksIn: Tasks renders every description through MarkdownRenderer.render, so it fires once per result row. It queues el when el has a result-row ancestor or is still detached, keeps at most one pending frame (requestAnimationFrame, falling back to a 16ms timeout), and schedules nothing when marks are off or for a connected non-Tasks element. runTasksResultDateMarkFrame resolves each entry to its row — a still-detached element is retried for up to 3 frames, a row without data-task for up to 60 — deduplicates rows with a WeakSet, and decorates complete rows with decorateTasksResultDates(li, today). Exhausted entries are dropped silently, leaving Tasks' native emoji dates; in the common case the pass lands before the next paint, so the emoji dates never flash. Each task-created, task-scheduled, task-done, and task-cancelled span without data-bob-date-mark whose inner text ends in a canonical ISO date gets the same mark (foldSpace: true, rendered: true, data-host="tasks") appended inside the span, which is then flagged data-bob-date-mark="true"; short-mode spans (no date), invalid dates, task-due, and task-start are untouched. The pass is idempotent, clicking a mark still opens Tasks' own date editor (the click bubbles to Tasks' span), and right-click postpone still works. relabelRenderedDateMarks already covers Tasks-host marks through data-rendered="true", so they roll over at midnight like every other rendered mark. In short mode (rotten.md, crowded.md), CSS alone turns each date span into a 1.08em glyph box drawing the shared field mask at the default ink, resting from the row's data-task.

Recurring Tasks results use span.task-recurring as a fifth Tasks host. In full mode the frame pass strips the leading 🔁 (with an optional variation selector), validates the remaining rule with the shared recurrence grammar, and appends the same loop mark used elsewhere. The mark carries data-host="tasks"; the native inner span is clipped while Tasks keeps its event listeners. Unknown shapes, invalid rules, and open PRE/POST checklist members whose rule lacks when done keep the native rule visible. Checklist membership comes only from the row's .task-description tag pills, and the guard applies only to open rows. In short mode, CSS replaces the recurring component with the shared loop mask and rests it faintly for closed rows. With date marks off, the existing Tasks-host cover hides stale marks until Tasks redraws.

Rollover and toggle

The existing minute interval calls refreshDateMarksForRollover(now): when the local date changed, it dispatches dateMarksRefresh to every markdown editor and relabels every .bob-date-mark[data-rendered="true"] in place (label, aria-label, data-when). Live Preview widget DOM is never edited in place. The command "Toggle task date marks" (id toggle-date-marks) is session-only and on by default: it flips body.bob-date-marks (which also gates the repair flag), dispatches the refresh effect, triggers the Tasks re-render event, and shows Date marks on / Date marks off. When off, JS creates no marks: a Tasks-result frame that runs after toggle-off drops its queue without writing, and onunload disables marks and clears the Tasks queue/pending/dedup state so late callbacks cannot decorate after unload. Re-enabling decorates new rows normally.

api.dateMarks v1

Frozen and additive (top-level api stays v3): { version: 1, fields: ["created", "scheduled", "completion", "cancelled"], model(field, dateText), render(host, field, dateText, options) }. Both are synchronous and never throw. render appends to host and returns the element, or null for an unknown field or a non-canonical date. options.decorative renders aria-hidden with no tooltip; options.inheritColor makes the ink currentColor. Nothing consumes the namespace yet; it exists so a later Task Card or notice can show ⧗ Fri with the same glyph.

Rejected alternatives

Changing storage to Tasks emoji or relative text breaks the Tasks Dataview format and Dataview's field index, rewrites thousands of vault fields plus the bob-cli parsers and writers, and relative text rots overnight. A CSS-only restyle of the Dataview pill cannot read the value, so the ISO date would stay with no today/Fri and no repair semantics. A CSS-only restyle of Tasks query results was rejected for the same reason in full mode — CSS cannot split Tasks' emoji from its date — while a global MutationObserver over Tasks' DOM was rejected as wider than needed: the description post-processor already fires once per result row, so one bounded frame pass that degrades to Tasks' native emoji dates is enough, and short mode stays CSS-only. An age voice for created (5w) adds a second grammar and breaks the closed-task timeline. Weekday names for past dates are ambiguous for scheduled. × for cancelled reads as math next to + and as a close button. Per-field hues clash with the status colors, and a past scheduled date is not overdue. Hiding created on closed tasks drops data; the resting tone quiets it instead. One lifecycle chip per task loses per-field reveal. Click never opens a card or picker: display surfaces never act. due and start are out of scope (see above).

Non-goals: storage, parser, Rust CLI/TUI, bob query, Bob Mac Capture output; Task Card, notice, and picker rows (the api enables them later); italic dates in Schedule/Work/Cancel Log entries; Tasks group headings; project-note frontmatter properties; memory notes.

Conformance vectors

Today is 2026-10-07 (Wednesday) unless noted. ⏎ separates tooltip lines. fold N is foldLength in Live Preview.

# Input Expected
DM1 - [ ] #task Buy milk [created:: 2026-10-07] created · today · when today · Created Wed, Oct 7 · today · fold 1 · quiet tone
DM2 - [ ] #task Call mom [created::2026-10-06] ^call created · yesterday · Created Tue, Oct 6 · yesterday
DM3 - [?] #task Ship [scheduled:: 2026-10-08] scheduled · tomorrow · Scheduled Thu, Oct 8 · tomorrow⏎Ctrl+Shift+P to reschedule
DM4 [scheduled:: 2026-10-09] Fri · Scheduled Fri, Oct 9 · in 2 days⏎Ctrl+Shift+P to reschedule
DM5 [scheduled:: 2026-10-13] Tue (Δ 6) · Scheduled Tue, Oct 13 · in 6 days⏎…
DM6 [scheduled:: 2026-10-14] Oct 14 (Δ 7) · Scheduled Wed, Oct 14 · in 7 days⏎…
DM7 - [ ] #task Rename [scheduled:: 2026-10-07] today · when today (emphasis tone) · Scheduled Wed, Oct 7 · today⏎…
DM8 - [ ] #task Install [scheduled:: 2026-09-04] Sep 4 · when past (quiet tone) · Scheduled Fri, Sep 4 · 33 days ago⏎…
DM9 - [x] #task Report [completion:: 2026-10-05] completion · Oct 5 (never a past weekday) · Done Mon, Oct 5 · 2 days ago · resting (CSS)
DM10 - [-] #task Old [cancelled:: 2025-12-30] cancelled · Dec 30, 2025 · Cancelled Tue, Dec 30, 2025 · 281 days ago
DM11 [scheduled:: 2027-01-02] Jan 2, 2027 · Scheduled Sat, Jan 2, 2027 · in 87 days⏎…
DM12 today 2026-12-30, [scheduled:: 2027-01-01] Fri (Δ 2 across the year) · Scheduled Fri, Jan 1, 2027 · in 2 days⏎…
DM13 - [x] #task Review skill [created:: 2026-08-29] [completion:: 2026-09-03] ^review two marks in order: created Aug 29 fold 1; completion Sep 3 fold 2
DM14 - [ ] #task A [fresh:: 2026-10-05] [created::2026-09-29] [scheduled:: 2026-10-09] created Sep 29, scheduled Fri; both ranges disjoint from the fresh mark's range
DM15 - [ ] #task B (scheduled:: 2026-10-09) Fri
DM16 - [x] #task C [ completion:: 2026-10-07 ] today
DM17 - Idea for later [created::2026-07-03] created Jul 3 (plain bullet)
DM18 > - [ ] #task Quoted [created:: 2026-10-01] Oct 1 · fold 1
DM19 - [ ] #task D x[created:: 2026-10-01] Oct 1 · fold 0
DM20 - [created:: 2026-10-01] starts the bullet Oct 1 · fold 0 (the field begins the content)
DM21 Paragraph text [scheduled:: 2026-10-09] Fri (non-list line)
DM22 - [ ] #task E `[created:: 2026-10-01]` untouched (code)
DM23 - [ ] #task G [rescheduled:: 2026-10-09] [scheduled:: 2026-10-10] scheduled Sat; rescheduled untouched and not counted as a duplicate
DM24 - [ ] #task [created:: 2026-10-07], cursor 12 (between the two spaces) created today; cursor in the run unfolds to fold 0 ([13, 35)); cursor 11 keeps fold 2; after typing b (cursor 13) fold 1
DM25 - [x] #task Review skill [created:: 2026-08-29] [completion:: 2026-09-03] ^review, cursor 48 completion fold 0 ([49, 74)); created stays fold 1 ([24, 47))
DN1 [scheduled:: 2026-13-01] no mark (repair pill)
DN2 [scheduled:: tomorrow] no mark (repair pill)
DN3 - [ ] #task F [scheduled:: 2026-09-10] (scheduled:: 2026-09-11) no mark on either (repair pills)
DN4 [Created:: 2026-10-01] no mark (key case)
DN5 [created:: 2026-10-01) no mark (mismatched brackets)
DN6 [created :: 2026-10-01] no mark (key must touch ::)
DN7 [created:: 2026-10-01T09:30] no mark
DN8 [due:: 2026-10-09] / [start::1700] untouched, and never repair-flagged
DN9 - [ ] #task H [scheduled:: 2026-13-01] [created:: 2026-10-01] created Oct 1 still marks; scheduled is a repair pill
RO1 a rendered Fri mark (date 2026-10-09) when the day becomes 2026-10-08, then 2026-10-09 relabels in place to tomorrow, then today (data-when="today"); Live Preview editors get the refresh effect
T1 a complete li (data-task="") with span.task-scheduled > span text " ⏳ 2026-10-09" a Fri mark appended inside the span, which gets data-bob-date-mark="true"
T2 span.task-done > span text " ✅ 2026-10-05" a completion mark Oct 5
T3 short-mode span.task-created > span text " ➕" JS leaves it untouched
T4 an li without data-task retried each frame, decorated once the attribute appears, left untouched after 60 frames
T5 a second pass adds nothing
T6 span.task-due and span.task-start untouched
T7 " ⏳ 2026-13-01" untouched
T8 marks off; a non-Tasks connected el nothing is scheduled
T9 a Tasks-host Fri mark on rollover relabels to tomorrow
RT1 complete li with span.task-recurring > span text " 🔁 every day when done" a daily mark appended inside the span; span flagged; second pass adds nothing
RT2 " 🔁 every week on Monday when done" / " 🔁 every month on the 1st" Mondays / monthly · 1st
RT3 short-mode span.task-recurring > span text " 🔁" JS leaves it untouched; CSS draws the loop
RT4 open li whose description has #gtd + #pre pills, rule " 🔁 every day" untouched (checklist guard)
RT5 " 🔁 every day; x" / an li without data-task untouched / retried, then decorated or dropped as for dates
RT6 marks off nothing scheduled; a stale Tasks-host repeat mark is hidden by the cover rule
RM1 - [?] #task #gtd #pre Brush teeth [repeat:: every day when done] [created:: 2026-10-21] daily, fold 2, quiet; Repeats every day when done⏎Next date counts from completion; created mark unaffected and disjoint
RM2 - [ ] #task Water plants [repeat:: every day] daily; …⏎Next date follows the schedule (not a checklist row, so no guard)
RM3 every week on Monday when done / every week on Sunday / every Friday Mondays / Sundays / Fridays
RM4 every month on the 1st / every month on the last Friday / every month on the 2nd Wednesday monthly · 1st / monthly · last Fri / monthly · 2nd Wed
RM5 every 30 days when done / every 28 days every 30 days / every 28 days
RM6 every 1 week / every 3 weeks on Friday / every January on the 15th weekly / every 3 weeks · Fri / yearly · Jan 15
RM7 every week on Tuesday, Friday / every week on Tuesday and Friday / every weekday weekly · Tue, Fri (both) / weekdays
RM8 every April and December on the 1st and 24th; every constructor; every week on constructor; every constructor on the 1st each unknown valid-charset rule is verbatim; when done is omitted from the label and retained in the tooltip
RM9 (repeat:: every week) / [ repeat:: every day ] weekly / daily
RM10 Every Day When Done daily; tooltip Repeats Every Day When Done⏎Next date counts from completion
RM11 cursor inside the field / in its folded run reveals that field only / unfolds to fold 0
RM12 - [x] #task #gtd #pre Old [repeat:: every day] (closed member) mark shown (guard covers open rows only), resting tone
RN1 [repeat:: weekly], [Repeat:: every day], [repeat :: every day], [repeat:: every day; when done], [repeat:: every\tday], [repeat:: every day], tab padding, two repeat:: fields, [repeat:: every day) no mark (pill; repair-flag selector covers repeat); repeated ASCII spaces and uppercase cadence remain valid
RN2 - [?] #task #gtd #pre Brush [repeat:: every day] / - [ ] #task Brush #gtd #post [repeat:: every day] no mark on either (the guard follows membership, not the glyph slot); the pill keeps the dashed orange flag
RN3 - [ ] `[repeat:: every day]` untouched (code)
RR1 reading view postprocessor invoked on a PRE/POST li root or containing wrapper; open li text holds [repeat:: every day] / … when done; closed li holds [repeat:: every day] no mark / daily mark for permitted when done and closed rows, regardless of root shape
RO2 rollover over a rendered repeat mark untouched; no error
RA1 api.dateMarks.fields / api.dateMarks.model("repeat", "every day") unchanged four fields / null

Today for RM/RN/RR/RO vectors is 2026-10-21 unless noted; ⏎ separates tooltip lines.

Live verification (Bryan, in Obsidian)

  • [ ] All four glyphs and every label form look right in light and dark themes
  • [ ] The cursor or a click reveals only that field's raw text
  • [ ] Vim cw on the word before a mark (and Backspacing that word without vim) types in order, before the mark
  • [ ] Tasks-written double-spaced [completion:: …] fields sit with the same gap as single-spaced ones
  • [ ] Created reads quiet, scheduled-today reads stronger, and closed tasks rest
  • [ ] A hand-broken [scheduled:: 2026-13-01] shows the dashed repair pill
  • [ ] The toggle restores the pills and then turns the marks back on
  • [ ] Reading view, embeds, and hover previews show the marks
  • [ ] Labels roll over after midnight without a reload
  • [ ] Fresh, priority, and date marks sit together cleanly on one line
  • [ ] Metadata Menu does not double-decorate
  • [ ] Mobile (iOS) renders the glyphs
  • [ ] dash.md and blocked.md Tasks results show date marks
  • [ ] Clicking one opens Tasks' date editor, and right-click postpone works
  • [ ] rotten.md and crowded.md (short mode) show glyphs instead of emoji
  • [ ] Toggling off restores Tasks' emoji dates
  • [ ] gtd_daily.md, recur.md, and cash.md rows show daily, Mondays, monthly · 1st in Live Preview and reading view
  • [ ] A hand-typed #task #gtd #pre … [repeat:: every day] without when done keeps the dashed orange repair pill
Tested rolling snapshot bob-cli 41afa14 bob-mac-capture 556bb67 bob-plugins af1e691 Built 2026-10-24T22:34:13Z (zensical) build.json