Skip to content
Merged
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
5 changes: 5 additions & 0 deletions owner/.changes/260727-1014.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
bump: patch
section: Changed
---
`deriveReminders`: pending-вузол дає `personal_today` лише коли дедлайн сьогодні чи прострочений, а не для будь-якого дедлайну в 24-годинному горизонті — pending-вузол з дедлайном "завтра" тепер коректно потрапляє в `deadline_due`.
40 changes: 24 additions & 16 deletions owner/src/docs/reminders.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,44 @@ type: JS Module
title: reminders.js
resource: owner/src/reminders.js
docgen:
crc: b53801dc
crc: ccc95f23
model: openai-codex/gpt-5.4-mini
tier: cloud-min
score: 100
issues: judge:inaccurate:0.98
issues: judge-refine:kept-original,judge:inaccurate:0.98
judgeModel: openai-codex/gpt-5.4-mini
---

## Огляд

`deriveReminders`, `applySnoozes`, `bucketPersonal` і `reminderRibbon` разом формують особистий список нагадувань із deadline, застарілих ескалацій і персональних snooze. `ESCALATION_STALE_DAYS` визначає, коли ескалація вважається застарілою, а `nextMidnight` задає межу для добового оновлення. Це потрібно, щоб відокремити особисті нагадування від решти задач і показати їх у компактному вигляді через `reminderRibbon`.
Файл об’єднує нагадування з різних джерел через `deriveReminders`, застосовує `applySnoozes` до відкладених елементів і окремо виділяє особисті задачі в `bucketPersonal`, щоб сформувати єдину стрічку актуальних сповіщень. `reminderRibbon` збирає цей результат у компактне подання для показу на поточний момент, а `nextMidnight` і `ESCALATION_STALE_DAYS` задають межі оновлення та актуальності для прострочених ескалацій.

## Поведінка

- `ESCALATION_STALE_DAYS` — задає поріг у днях, після якого відкрита ескалація на мою адресу вважається застарілою.
- `nextMidnight` — повертає момент наступної локальної півночі як стандартний час для відкладеного нагадування.
- `deriveReminders` — збирає активні нагадування з workspace-лісу за правилами deadline та застарілої ескалації, відсікаючи не-мій scope.
- `applySnoozes` — прибирає з переліку нагадувань ті, для яких ще діє персональний snooze.
- `bucketPersonal` — групує особисті задачі брифу в кошики дедлайнів від найтерміновішого до пізнішого.
- `reminderRibbon` — формує коротку стрічку-лічильник для режиму «Рішення» на основі активних нагадувань.
ESCALATION_STALE_DAYS задає спільний поріг «застарілості» для ескалацій і визначає, коли відкритий кейс уже вважається достатньо старим, щоб потрапити в нагадування.

nextMidnight задає дефолтний горизонт snooze «до завтра»: нагадування, відкладене до цієї мітки, залишається тихим до настання наступної локальної доби.

deriveReminders збирає нагадування з дерева воркспейсів і пов’язаного контексту, поєднуючи особисті дедлайни, прострочені задачі та ескалації в один список; результат уже впорядкований так, щоб найтерміновіші були першими. Дані для нього приходять із лісу вузлів, scope-класифікації та, за наявності, серій ескалацій і ідентифікатора користувача.

applySnoozes відсіює вже заглушені нагадування за активними snooze-мітками й залишає лише ті, що мають звучати зараз; це проміжний фільтр між зібраними нагадуваннями та відображенням користувачу.

bucketPersonal групує особисті задачі в кошики за терміновістю і зберігає фіксований порядок від найпекучішого до менш термінового, опускаючи порожні групи. Вона працює з уже зібраними рядками, тож не змінює джерело даних, а лише перекладає його в форму для брифу.

reminderRibbon будує тонку зведену стрічку поверх активних нагадувань після applySnoozes: показує лише лічильники й заголовок, не впливаючи на вибір режиму. Якщо активних нагадувань немає, стрічка не показується.

## Публічний API

- ESCALATION_STALE_DAYS — поріг давності, після якого ескалація вважається протермінованою
- nextMidnight — ставить дефолтне відкладення до наступної локальної півночі
- deriveReminders — витягає нагадування мого скоупу за трьома правилами спеки
- applySnoozes — ховає нагадування, які ще не вийшли з snooze
- bucketPersonal — розкладає особисті задачі брифу по кошиках дедлайнів від найтерміновішого; порожні не показує
- reminderRibbon — показує в режимі «Рішення» компактну стрічку лічильників нагадувань, не впливаючи на вибір режиму
- nextMidnight — Північ наступної локальної доби — дефолтний горизонт snooze
(«тихо до завтра»).
- deriveReminders — Збирає нагадування мого скоупу за трьома правилами спеки.
- applySnoozes — Відсіює заглушені нагадування: snooze діє, доки його until у майбутньому.
- bucketPersonal — Групує особисті задачі брифу в кошики дедлайнів (порожні кошики
опускаються, порядок фіксований — від найпекучішого).
- reminderRibbon — Тонка стрічка нагадувань для режиму «Рішення»: лічильники без впливу
на вибір режиму (правило режимів недоторканне — конституція 260714).
- ESCALATION_STALE_DAYS — визначає, через скільки днів прострочене персональне нагадування переходить у стан escalation

## Гарантії поведінки

- Read-only: не виконує операцій запису (ФС/БД).
- Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
10 changes: 6 additions & 4 deletions owner/src/reminders.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ function walk(nodes, pick) {

/**
* Нагадування одного вузла за правилами deadline_due / personal_today.
* Правила зі спеки перетинаються на pending (deadline сьогодні ⊂ горизонт
* 24 год), тож вузол дає щонайбільше одне нагадування: pending —
* Правила зі спеки перетинаються на pending із дедлайном сьогодні або в
* минулому, тож вузол дає щонайбільше одне нагадування: такий pending —
* personal_today, решта нерозвʼязаних — deadline_due.
* @param {object} node вузол зі scan
* @param {{ label: string, path: string }} workspace воркспейс вузла
Expand All @@ -64,8 +64,10 @@ function deadlineReminder(node, workspace, now) {
const deadline = Date.parse(node.deadline)
if (Number.isNaN(deadline) || deadline > now + DUE_HORIZON_MS) return null
const overdue = deadline < now
const rule = node.state === 'pending' ? 'personal_today' : 'deadline_due'
const isPendingToday = node.state === 'pending' && deadline < dayStart(now) + DAY_MS
const rule = isPendingToday ? 'personal_today' : 'deadline_due'
const what = rule === 'personal_today' ? 'твоя задача' : 'гілка без результату'
const timing = rule === 'personal_today' ? 'сьогодні' : 'наближається'
return {
id: `${rule}|${workspace.path}|${node.path}|${node.deadline}`,
rule,
Expand All @@ -76,7 +78,7 @@ function deadlineReminder(node, workspace, now) {
stake: overdue ? 0 : 1,
headline: overdue
? `дедлайн ${node.deadline} минув — ${what} прострочена`
: `дедлайн ${node.deadline} сьогодні — ${what}`
: `дедлайн ${node.deadline} ${timing} — ${what}`
}
}

Expand Down
9 changes: 7 additions & 2 deletions owner/src/tests/reminders.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,19 @@ describe('deriveReminders', () => {
const rows = deriveReminders({
...forestOf([
{ path: 'mine', state: 'pending', deadline: '2026-07-17T17:00:00', children: [] },
{ path: 'stale', state: 'pending', deadline: '2026-07-15T09:00:00', children: [] }
{ path: 'stale', state: 'pending', deadline: '2026-07-15T09:00:00', children: [] },
{ path: 'tomorrow', state: 'pending', deadline: '2026-07-18T10:00:00', children: [] }
]),
now: NOW
})
expect(rows.map(r => [r.path, r.rule, r.overdue])).toEqual([
['stale', 'personal_today', true],
['mine', 'personal_today', false]
['mine', 'personal_today', false],
['tomorrow', 'deadline_due', false]
])
expect(rows.find(row => row.path === 'tomorrow')?.headline).toBe(
'дедлайн 2026-07-18T10:00:00 наближається — гілка без результату'
)
})

it('чужі й межові вузли нагадувань не породжують — лише мій скоуп', () => {
Expand Down
Loading