* feat(short-syntax): parse recurrence phrases like @every friday in the add task bar Adds a recurrence layer in front of the chrono-node date parsing: @every <weekday|Nth|day|weekday|week|month|year> (with optional interval and time) and the bare frequency words @daily/@weekly/@monthly/@yearly map onto the existing repeat quick-setting presets of the add task bar, so the created task gets a TaskRepeatCfg through the same code path as the repeat menu. - @every friday [3pm] anchors the due date to the next matching weekday - @every 15th anchors to the next matching day of month - @every 2 weeks carries the interval into the created repeat cfg - recurrence is only parsed in the add task bar (task creation); title edits of existing tasks keep treating the phrase as a plain date - the repeat action button shows the parsed setting and clearing it also strips the phrase from the input Closes #4961 * fix(short-syntax): allow trailing punctuation after recurrence phrases 'water plants @every friday.' fell through to plain-date parsing (chrono tolerates the dot, the recurrence boundary check did not), silently downgrading the repeat to a one-off Friday task. The phrase boundary now accepts trailing punctuation as well as whitespace/end-of-input. * fix(short-syntax): address review findings on recurrence parsing - drop interval support ("@every 2 weeks"): the quick-setting presets all mean an interval of 1 - DAILY + repeatEvery>1 wrongly inherited skipOverdue=true (a missed occurrence would silently vanish), the repeat dialog silently reset the interval to 1 on any save, and MONDAY_TO_FRIDAY intervals produced 7-day-block schedules the app cannot express or display. Interval phrases now fall through to the plain-date path, and shortSyntax() returns a plain repeatQuickSetting instead of a repeatCfg object. - only absorb a chrono match directly adjacent to the recurrence phrase, so "@every friday call mom tomorrow" no longer swallows everything between the phrase and a downstream date expression (up to and including the whole title) - join tolerated trailing punctuation to the preceding word ("Water plants @every friday." -> "Water plants.", not "Water plants .") and avoid a double space when absorbing an adjacent date mid-title - advance the anchor one period when an explicit time is already past ("@every friday 3pm" on a Friday at 17:00 -> next Friday 15:00, matching chrono's forwardDate behavior for the plain "@friday 3pm" form) * feat(add-task-bar): highlight detected short syntax inline in the input Renders a mirror overlay behind the add-task-bar textarea that paints a background under every substring the short-syntax parser consumed (#tag, +project, 30m/1h, @friday, @every friday, !deadline, extracted URLs), so a mis-capture is visible while typing instead of after the title is saved with text silently stripped (#8974, #6493). - shortSyntax() now reports the exact consumed substrings as typed tokens; a pure mapper resolves them to positions in the raw input, skipping anything it cannot locate unambiguously (missing highlight over wrong highlight) - ranges are pinned to the input string they were parsed from, so the async parse can never paint stale highlights on newer text - overlay shares the textarea's typography via a common placeholder and mirrors its scroll position; textarea text stays the visible layer - highlight color derives from --c-accent via color-mix, matching the --task-c-current-bg pattern * fix(add-task-bar): keep highlight overlay text aligned with the textarea Bare text nodes in the overlay picked up the template's indentation as a literal collapsed space, shifting the mirrored text (and every highlight) right by one space width. Render all segments as whitespace-tight spans; whitespace-only nodes between elements are dropped entirely. * fix(add-task-bar): mirror the textarea's computed typography onto the highlight overlay Custom themes are arbitrary user CSS and can style the textarea and the overlay div differently — glass.css for instance sets a font-family on every element — so static CSS cannot keep the two layers metric-identical. Copy all text-layout-affecting computed styles (font, spacing, kerning, ligatures, tab-size) from the textarea onto the overlay on every segments render and on theme switches, with a delayed re-sync to cover the async stylesheet load of built-in themes. * fix(add-task-bar): pin highlight segment spans to inherited text metrics Theme CSS can match the overlay's segment spans directly — glass.css sets font-family on '*' — and a direct rule beats inheritance, so the spans rendered in the theme font while the textarea kept the component font, desyncing every highlight position. Force the spans to inherit all text-metric properties at component specificity, which outranks any non-important theme rule on the spans. * fix(short-syntax): track consumed spans through an offset map instead of recovering them by search mapShortSyntaxTokensToRanges located tokens with rawText.indexOf, which guesses wrong whenever a token's text also occurs inside a tag, URL or word ('#1h retro 1h' highlighted the 1h inside the tag; a URL containing 30m absorbed the estimate highlight and evicted the correct URL range). Every strip site in the parser now routes its edit through TrackedTitle, a per-character map from working-title index to raw-title index, so shortSyntax() returns exact parsedRanges directly and the search plus its overlap heuristic are gone. A token whose consumed text is split by an earlier removal ('Call Bob @tomorrow 1h evening') now yields one range per contiguous run instead of silently dropping the highlight. The 38 spec cases that asserted parsedTokens with jasmine.anything() now assert real ranges, plus regression tests for exactly the ambiguous inputs where searching guessed wrong. * fix(add-task-bar): sync overlay typography per theme change, overlay scroll per render The typography-sync effect read highlightSegments(), forcing a getComputedStyle style recalc plus 15 inline style writes on every keystroke with the task list rendered behind the bar; computed typography only changes on view init and theme switches, so the effect now tracks only those. The scroll sync moves the other way: (scroll) alone fires before the overlay re-renders once the textarea's caret-scroll kicks in past 4 rows, clamping scrollTop against still-short content, so it now also re-syncs after each segments render. Also mirrors direction, text-align and word-break onto the overlay. * fix(short-syntax): derive the clear-repeat removal from the parser grammar The clear-repeat button hand-wrote a second, broader grammar than the parser uses, so it deleted text that was never read as a recurrence: "@every 2 weeks" and "@every 3 days" (documented as unsupported, parsed as plain dates) lost their unit word, "@every quarter" vanished entirely, and a trailing "." the parser deliberately keeps was eaten. Both regexes now come from one grammar source, re-anchored for removal. Also fixes two anchoring bugs found in the same review: - "@weekly"/"@monthly"/"@yearly" plus an already-passed time shifted the recurring day. The presets carry no anchor, so chrono's forwardDate slid the first occurrence to tomorrow and the cycle took its weekday/day-of-month from there — "@weekly 6am" typed Wednesday 10:00 recurred on Thursdays. They now pin to today and roll a whole period, like the explicit "@every wednesday 6am" form already did. - getNextDayOfMonthDate's comment claimed months lacking the anchor day are skipped "matching how the repeat engine clamps" — opposite behaviors. The skip is correct (the engine reads the recurring day off the start date, so clamping "@every 31st" in February would make it recur on the 28th forever); the comment now says why. * fix(short-syntax): keep highlights stable across keystrokes and single-source the removals The highlight ranges are pinned to the text they were parsed from, but the parse is async, so every keystroke painted once with the previous text's ranges and dropped all of them — the overlay blanked for a frame per keystroke. Keep the ranges that end inside the unchanged common prefix instead: a highlight can then never be mispositioned, only at most one keystroke stale. Replace the runtime typography sync with the full property set declared statically on %main-input-typography. A textarea does not inherit typography (the UA `font:` shorthand resets weight/style/variant/stretch/line-height) while the mirror div does, but that is a static problem: declaring the whole set on both layers removes the drift with no JS, no theme-change trigger and no 300ms guess, and also covers the box properties (padding/border/ box-sizing) the sync never touched. Also: - splitTextByRanges skips a range that overlaps the previous one rather than re-emitting its text, which would lengthen the mirror and shift every later highlight. - Markdown-link removal was expressed twice (a .replace() deriving the plain-URL search text, plus tracked.remove() calls doing the real edit). Collapse both into collapseMarkdownLinks(), with the plain-URL scan reading the tracker's text afterwards, and emit ranges for the stripped '[' and '](url)' so markdown links highlight like any other consumed token. * fix(short-syntax): keep URL-less markdown links and pin the last shared text metrics Two follow-ups from the review: Collapse only markdown links that carry a URL. The collapse ran for every `[text](url)` match, but whether it ran at all was decided by the title holding *some* URL — so `Add [Website]() https://a.com` lost the brackets while `Add [Website]()` alone kept them. A URL-less link yields no attachment, so extracting it only deletes the user's characters; skipping it makes that rule unconditional and stops unrelated text elsewhere in the title from deciding it. Add `direction`, `text-align` and `word-break` to the shared typography, and the wrapping/bidi properties to the overlay spans. All were copied by the runtime sync that the static block replaced; without them a theme rule aimed at the textarea alone (or at the spans via `*`) moves the glyphs out from under the highlight backgrounds. `inherit` rather than fixed values, so the app's own RTL from `[dir]` on a shared ancestor still reaches both layers.
9.1 KiB
Executable file
Short Syntax
Super Productivity supports five main short syntax forms for quickly adding metadata to tasks: tags (#), projects (+), time estimates (m/h), due dates (@), and deadlines (!). The due date form also accepts recurrence phrases (@every friday, @daily) in the Add Task Bar. The project, tag, and due/deadline/time forms can be enabled or disabled in 3.02-Settings-and-Preferences#global-settings.Short-Syntax.
Supported Syntax Forms
Tag Syntax (#)
- Symbol:
# - Purpose: Add tags to the task.
- Grammar:
#<tag-name>. A space is required before#. - Parameters: Tag name (case-insensitive).
- Valid:
"Task #urgent","Meeting #work #important". Multiple tags allowed. - Invalid:
"Task#urgent"(no space before#) — the whole short-syntax run returns no changes. If other tokens are valid (e.g."Task#urgent #other"), only the valid tag is applied and the invalid token is left in the title.
Project Syntax (+)
- Symbol:
+ - Purpose: Assign the task to a project.
- Grammar:
+<project-name>. A space is required before+. - Parameters: Project title or partial title; minimum 3 characters. Matching is prefix-based, case-insensitive, with spaces removed for comparison. The shortest matching project title is preferred when several match.
- Valid:
"Task +Important Project","Task +Project"(partial match). Project names containing+,#,@, or similar are supported; the parser uses lookahead so these characters inside the name do not end the token. - Invalid:
"Task+Project"(no space before+) — no project is applied.
Time Estimate Syntax
- Symbols:
m(minutes),h(hours). Optional prefixt. Optional/to separate time spent from time estimate. - Purpose: Set time spent and/or time estimate.
- Grammar:
[t]<number><unit>[ <number><unit>...][/ <number><unit>...]. Units:m,h. Decimal numbers allowed (e.g.1.5h). Before/: time spent on the current day. After/: time estimate. Clusters like1h 30mare supported. - Parameters: Time spent (before
/): e.g.30m,1.5h. Time estimate (after/): e.g.1h,90m. One side can be omitted:30m/sets only time spent;1hsets only estimate. - Valid:
"Task 10m","Task 1h/2h","Task 30m/","Task t1.5h","Task 1h 30m / 2h". - Defaults:
h= 60 minutes.
Due Date Syntax (@)
- Symbol:
@ - Purpose: Set due date/time for the task.
- Grammar:
@<date-time-expression>. No space is allowed between@and the expression. - Parameters: Date/time expressions supported by the Chrono-based parser (e.g.
4pm,friday,tomorrow 19:00,2030-10-12T13:37). - Valid:
"Task @4pm","Task @friday","Task @tomorrow 19:00","Task @2030-10-12T13:37". - Invalid:
"Task @ tomorrow"(space after@) — due date is not applied and the expression is stripped from the title. Two-digit years 50–99 are corrected to the current or next calendar year to avoid Chrono interpreting them as years (e.g.90min a date context).
Recurrence Syntax (@every ..., @daily, ...)
- Symbol:
@, shared with the due date form. A recurrence phrase must come directly after the@. - Purpose: Create the task as a repeating task, equivalent to picking a repeat preset in the Add Task Bar's repeat menu.
- Grammar:
@every <unit>or a bare frequency word:@daily,@weekly,@monthly,@yearly,@annually. Units forevery:day(s),week(s),weekday(s)/workday(s),month(s),year(s), a weekday name (friday,fri,fridays, ...), or a day of the month (15th). An optional time can follow:@every friday 3pm. Intervals (@every 2 weeks) are not supported and are parsed as a plain due date instead. - Mapping:
@every friday→ weekly on Friday (due date set to the next Friday);@every weekday→ Monday through Friday;@every 15th→ monthly on the 15th (due date set to the next 15th);@daily 6am→ daily with a start time. The unanchored words recur on the day you add the task:@weeklyon today's weekday,@monthlyon today's day of the month,@yearlyon today's date. If a time you add has already passed today, the first occurrence moves a whole period ahead so the recurring day stays the same —@weekly 6amadded on a Wednesday at 10:00 is due next Wednesday, not tomorrow. - Valid:
"Water plants @every friday","Journal @daily 6am","Pay rent @every 15th". - Scope: Only in the Add Task Bar (task creation). In title edits of existing tasks the phrase is parsed as a plain due date, since an existing task cannot be converted to a repeating task this way. The parsed repeat shows on the repeat button and can be cleared there, which also removes the phrase from the input.
Deadline Syntax (!)
- Symbol:
! - Purpose: Set a deadline date/time for the task.
- Grammar:
!<date-time-expression>. A space is required before!when it is not at the start of the title. - Parameters: Date/time expressions supported by the Chrono-based parser (e.g.
14:30,friday,next month). - Valid:
"Task !14:30","Task !friday","Task @monday !friday". - Invalid:
"Done!"(no space before!) — the exclamation mark is treated as punctuation and no deadline is applied.
Formal Grammar and Regex Patterns
The parser uses regular expressions for each syntax form. Time: optional t prefix, digits and decimal with m/h, optional / for spent/estimate. Project: + with lookahead so delimiters (#, @, time-like tokens) inside the project name do not terminate the match. Tag: # followed by characters until a special character or whitespace. Due: @ followed by characters until a special character. Deadline: ! followed by characters until a special character. Exact patterns are defined in short-syntax.ts (e.g. SHORT_SYNTAX_TIME_REG_EX, SHORT_SYNTAX_PROJECT_REG_EX, SHORT_SYNTAX_TAGS_REG_EX, SHORT_SYNTAX_DUE_REG_EX, SHORT_SYNTAX_DEADLINE_REG_EX).
Default Values and Behavior
- Time units:
m= minutes,h= 60 minutes. - Time spent only: e.g.
30m/sets time spent on the current day and no estimate. - Time estimate only: e.g.
1hsets estimate without time spent. - Date parsing: Uses a custom Chrono-based parser with year correction for two-digit years 50–99 (current/next year).
- Project matching: Prefix-based, case-insensitive, spaces removed; shortest matching project title wins.
- Tag mode:
combine(default) orreplace; configurable in settings.
Where Short Syntax Is Supported
Supported:
- Add Task Bar (primary place for creating tasks with short syntax).
- Task creation when adding new tasks (e.g. via the shared add-task action).
- Task title updates when the only change is the title (syntax is re-parsed).
Unsupported or disabled:
- Issue-linked tasks: Tasks linked to external issues cannot use project syntax (see 2.07-Manage-Task-Integrations).
- When disabled: Short syntax has three settings groups in 3.02-Settings-and-Preferences#global-settings.Short-Syntax: project, tag, and due/deadline/time. Disabled groups are ignored.
- Archived or hidden projects: Project syntax does not match archived or hidden projects.
Parsing and Evaluation Rules
- Order of processing: Due/deadline/time → Project → Tag.
- Title cleanup: Parsed syntax is removed from the task title after processing.
- Tag creation: Tags that do not exist yet are collected in a
newTagTitlesarray for creation. - Return value: The
shortSyntax()function returns an object with task changes (andnewTagTitles,remindAt,projectId,repeatQuickSetting) orundefinedif nothing was parsed or syntax is disabled.
Error Handling
Malformed syntax:
- Invalid syntax is ignored; the function may return
undefinedor only apply valid parts. - No exception is thrown; invalid tokens are skipped or left in the title as appropriate.
- If a syntax type is disabled in settings, that form is not processed.
Edge cases:
- Project names containing
+,#,@, or time-like patterns are handled so that the project token is parsed correctly. - Two-digit years 50–99 in date expressions are corrected to the current or next year.
- Time estimate tokens (e.g.
90m) in the same title as date expressions are parsed separately so Chrono date parsing does not consume the time estimate.
Add Task Bar Behavior
- Toggle new task position: A toggle in the Add Task Bar controls whether the new task is added at the top or the bottom of the list.
- Autocomplete: The Add Task Bar can suggest tags, projects, and date expressions while typing.
- Inline highlighting: Detected short syntax (tags, project, estimate, due/deadline dates, recurrence, extracted URLs) is highlighted directly in the input while typing, showing which characters will be stripped from the title before the task is created.
Notes
- Short syntax is processed by the
shortSyntax()function; it returns task changes orundefinedwhen no changes are needed. - The five syntax forms are controlled by the global Short Syntax settings for project, tag, and due/deadline/time in 3.02-Settings-and-Preferences.