feat: add time-of-day component to urgencyScore formula#1839
feat: add time-of-day component to urgencyScore formula#1839loukandr wants to merge 2 commits intocallumalpass:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a time-of-day tie-breaker to the default urgencyScore formula so tasks with the same priority and same date are ordered by earlier scheduled/due times first, and updates docs/tests accordingly.
Changes:
- Updated
urgencyScoreformula to include a fractional-day (time-of-day) boost. - Added unit tests to pin the generated formula string and validate boost invariants.
- Updated documentation and unreleased notes to describe the new behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/templates/defaultBasesFiles.ts |
Extends urgencyScore with a time-of-day boost term. |
tests/unit/templates/defaultBasesFiles.test.ts |
Adds tests validating the new formula string and boost math properties. |
docs/views/default-base-templates.md |
Updates the documented urgencyScore expression/description. |
docs/releases/unreleased.md |
Documents the behavior change in release notes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // tasks at the same priority. Date-only values fall back to midnight. | ||
| urgencyScore: `if(!${dueProperty} && !${scheduledProperty}, formula.priorityWeight, formula.priorityWeight + max(0, 10 - formula.daysUntilNext) + (1 - ((number(date(formula.nextDate)) / 86400000) - (number(date(formula.nextDate)) / 86400000).floor())))`, |
There was a problem hiding this comment.
The time-of-day term is derived from the fractional part of number(date(formula.nextDate)) / 86400000, which effectively measures time-of-day relative to UTC day boundaries. In non-UTC timezones this can wrap within a single local day (around UTC midnight) and invert the intended ordering (later local times can score higher than earlier local times). Consider computing the fraction relative to the start of the same local day instead, e.g. based on number(date(formula.nextDate)) - number(date(formula.nextDate).date()) (or an equivalent start-of-day operation) so date-only values reliably produce a full boost and intra-day ordering is stable for all timezones.
| // tasks at the same priority. Date-only values fall back to midnight. | |
| urgencyScore: `if(!${dueProperty} && !${scheduledProperty}, formula.priorityWeight, formula.priorityWeight + max(0, 10 - formula.daysUntilNext) + (1 - ((number(date(formula.nextDate)) / 86400000) - (number(date(formula.nextDate)) / 86400000).floor())))`, | |
| // tasks at the same priority by measuring elapsed time since the start of the same local day. | |
| urgencyScore: `if(!${dueProperty} && !${scheduledProperty}, formula.priorityWeight, formula.priorityWeight + max(0, 10 - formula.daysUntilNext) + (1 - ((number(date(formula.nextDate)) - number(date(formula.nextDate).date())) / 86400000)))`, |
There was a problem hiding this comment.
Switched to number(date(x)) - number(date(x).date()) so the fraction is computed against the local day boundary, as suggested.
| expect(boost("2026-04-28T09:00:00Z")).toBeCloseTo(0.625, 3); | ||
| expect(boost("2026-04-28T17:00:00Z")).toBeCloseTo(0.292, 3); | ||
| expect(boost("2026-04-28T23:59:59Z")).toBeGreaterThan(0); | ||
| expect(boost("2026-04-28T23:59:59Z")).toBeLessThan(1 / 86_400); |
There was a problem hiding this comment.
This assertion is mathematically too strict/flaky: with the current boost helper, 2026-04-28T23:59:59Z should evaluate to ~1/86400 (not strictly less). Consider using 23:59:59.999Z, relaxing to <= 1/86400, or asserting with toBeCloseTo to avoid a spurious failure.
| expect(boost("2026-04-28T23:59:59Z")).toBeLessThan(1 / 86_400); | |
| expect(boost("2026-04-28T23:59:59Z")).toBeLessThanOrEqual(1 / 86_400); |
There was a problem hiding this comment.
Relaxed to toBeLessThanOrEqual(1 / 86_400).
| |---------|-------------|------------| | ||
| | `priorityWeight` | Numeric weight for priority sorting (lower = higher priority) | `if(priority=="none",0,if(priority=="low",1,if(priority=="normal",2,if(priority=="high",3,999))))` | | ||
| | `urgencyScore` | Combines priority and next date proximity (due or scheduled, higher = more urgent) | `if(!due && !scheduled, formula.priorityWeight, formula.priorityWeight + max(0, 10 - formula.daysUntilNext))` | | ||
| | `urgencyScore` | Combines priority, next date proximity, and time-of-day (due or scheduled, higher = more urgent) | `if(!due && !scheduled, formula.priorityWeight, formula.priorityWeight + max(0, 10 - formula.daysUntilNext) + (1 - ((number(date(formula.nextDate)) / 86400000) - (number(date(formula.nextDate)) / 86400000).floor())))` | |
There was a problem hiding this comment.
The documented urgencyScore expression uses an epoch-based fractional day (number(date(...))/86400000 - floor(...)). If the implementation is adjusted to compute the fraction relative to the local start-of-day (to avoid timezone wrap issues), please update this doc expression to match. Also note the later "Mini Calendar" YAML snapshot still shows the older time-naive urgencyScore form, which will become inconsistent with the generated templates.
| | `urgencyScore` | Combines priority, next date proximity, and time-of-day (due or scheduled, higher = more urgent) | `if(!due && !scheduled, formula.priorityWeight, formula.priorityWeight + max(0, 10 - formula.daysUntilNext) + (1 - ((number(date(formula.nextDate)) / 86400000) - (number(date(formula.nextDate)) / 86400000).floor())))` | | |
| | `urgencyScore` | Combines priority, next date proximity, and time-of-day (due or scheduled, higher = more urgent) | `if(!due && !scheduled, formula.priorityWeight, formula.priorityWeight + max(0, 10 - formula.daysUntilNext) + (1 - ((number(date(formula.nextDate)) - number(date(date(formula.nextDate).format("YYYY-MM-DD")))) / 86400000)))` | |
There was a problem hiding this comment.
Updated the doc expression to match the local-day form. The Mini Calendar YAML at line 123 was already out of sync before this PR, leaving that as separate cleanup.
Problem
urgencyScorein default base templates currently scores at day-level granularity:Two tasks at the same priority and same date but different times of day score identically. Tie-break falls to file-iteration order, which is opaque from the user's perspective. TaskNotes already supports timed
scheduledandduevalues (used in calendar timeGrid views and timeblocks), so this information is lost in the urgency calculation.Fix
Add a 0..1 boost based on the fractional day of
nextDate:Where
hourFraction = (number(date(x)) / 86400000) - (number(date(x)) / 86400000).floor(). This matches the ms-based date math already used indaysUntilDue,daysUntilScheduled,timeTrackedToday, etc.Properties of the change:
date("YYYY-MM-DD")parses to midnight.if(!due && !scheduled, ...)branch and remain unaffected.Tests
Extended
tests/unit/templates/defaultBasesFiles.test.tswith: