Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/releases/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ Example:
```

-->

## Changed

- Updated the `urgencyScore` formula in default base templates to factor in time-of-day, so timed values that are earlier within a day rank above later ones at the same priority and date
- Adds a 0..1 boost computed from the fractional day of `nextDate`, keeping priority weight and the days-until-next term as the dominant signals
- Resolves a tie-break that previously depended on file-iteration order for same-priority same-date timed tasks
- Date-only values fall back to midnight, so they sit at the top of their day bucket
2 changes: 1 addition & 1 deletion docs/views/default-base-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ These formulas work with either due date or scheduled date, useful for finding t
| Formula | Description | Expression |
|---------|-------------|------------|
| `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)) - number(date(formula.nextDate).date())) / 86400000)))` |

### Display formulas

Expand Down
7 changes: 4 additions & 3 deletions src/templates/defaultBasesFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,9 +381,10 @@ function generateAllFormulas(plugin: TaskNotesPlugin): Record<string, string> {

// === SORTING/SCORING FORMULAS ===

// Urgency score: combines priority weight and days until next date (due or scheduled)
// Higher score = more urgent. Overdue tasks get bonus, no date gets just priority
urgencyScore: `if(!${dueProperty} && !${scheduledProperty}, formula.priorityWeight, formula.priorityWeight + max(0, 10 - formula.daysUntilNext))`,
// Urgency score: combines priority weight, days until next date (due or scheduled), and time-of-day.
// Higher = more urgent. The 0..1 time-of-day term ranks earlier-in-day tasks above later same-day
// 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)) - number(date(formula.nextDate).date())) / 86400000)))`,

// === DISPLAY FORMULAS ===

Expand Down
38 changes: 38 additions & 0 deletions tests/unit/templates/defaultBasesFiles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,42 @@ describe("defaultBasesFiles", () => {
expect((template.match(/column: tasknotes_manual_order/g) ?? []).length).toBe(3);
expect(template).toContain('name: "Projects"');
});

it("includes a time-of-day component in urgencyScore so earlier values rank higher", () => {
// Without the time-of-day term, two tasks at the same priority and same date but
// different times scored identically and tie-broke on file-iteration order. The
// 0..1 boost (1 - hourFraction(nextDate)) ranks earlier values above later ones
// while staying smaller than the priority and days components so cross-day order
// is preserved.
const template = generateBasesFileTemplate("open-tasks-view", createMockPlugin() as any);

expect(template).toContain(
`urgencyScore: 'if(!due && !scheduled, formula.priorityWeight, formula.priorityWeight + max(0, 10 - formula.daysUntilNext) + (1 - ((number(date(formula.nextDate)) - number(date(formula.nextDate).date())) / 86400000)))'`
);

// Guard against the time-naive form returning
expect(template).not.toMatch(
/urgencyScore: 'if\(!due && !scheduled, formula\.priorityWeight, formula\.priorityWeight \+ max\(0, 10 - formula\.daysUntilNext\)\)'/
);
});

it("time-of-day boost is monotonic and bounded in [0, 1]", () => {
// Verifies the math invariant the formula relies on, independent of YAML shape.
// boost = 1 - fractional_day(ms_since_epoch). A given Date earlier in its day
// must yield a strictly larger boost than the same date later in the day.
const boost = (iso: string) => {
const ms = Date.parse(iso);
const dayMs = ms / 86_400_000;
return 1 - (dayMs - Math.floor(dayMs));
};

expect(boost("2026-04-28T00:00:00Z")).toBe(1);
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")).toBeLessThanOrEqual(1 / 86_400);

// Monotonic: earlier in day → larger boost.
expect(boost("2026-04-28T09:00:00Z")).toBeGreaterThan(boost("2026-04-28T17:00:00Z"));
});
});