Skip to content

feat: add time-of-day component to urgencyScore formula#1839

Open
loukandr wants to merge 2 commits intocallumalpass:mainfrom
loukandr:feat/urgency-time-of-day
Open

feat: add time-of-day component to urgencyScore formula#1839
loukandr wants to merge 2 commits intocallumalpass:mainfrom
loukandr:feat/urgency-time-of-day

Conversation

@loukandr
Copy link
Copy Markdown

Problem

urgencyScore in default base templates currently scores at day-level granularity:

priorityWeight + max(0, 10 - daysUntilNext)

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 scheduled and due values (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:

priorityWeight + max(0, 10 - daysUntilNext) + (1 - hourFraction(nextDate))

Where hourFraction = (number(date(x)) / 86400000) - (number(date(x)) / 86400000).floor(). This matches the ms-based date math already used in daysUntilDue, daysUntilScheduled, timeTrackedToday, etc.

Properties of the change:

  • Range: 0..1, smaller than priority weight (0..3) and the days component (0..11+), so cross-day and cross-priority ordering is preserved.
  • Date-only values yield boost=1 (top of bucket), since date("YYYY-MM-DD") parses to midnight.
  • No-date tasks fall through the existing outer if(!due && !scheduled, ...) branch and remain unaffected.

Tests

Extended tests/unit/templates/defaultBasesFiles.test.ts with:

  • A positive pin on the new formula string and a negative regex against the time-naive form.
  • A pure-JS math invariant test verifying the boost is in [0, 1) and earlier-in-day yields strictly larger boost.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 urgencyScore formula 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.

Comment thread src/templates/defaultBasesFiles.ts Outdated
Comment on lines +386 to +387
// 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())))`,
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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)))`,

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
expect(boost("2026-04-28T23:59:59Z")).toBeLessThan(1 / 86_400);
expect(boost("2026-04-28T23:59:59Z")).toBeLessThanOrEqual(1 / 86_400);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relaxed to toBeLessThanOrEqual(1 / 86_400).

Comment thread docs/views/default-base-templates.md Outdated
|---------|-------------|------------|
| `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())))` |
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
| `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)))` |

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants