From d53a191b0837e17601f7219f46c5e11f0f85e97a Mon Sep 17 00:00:00 2001 From: vitaliytv Date: Mon, 27 Jul 2026 10:33:41 +0300 Subject: [PATCH] =?UTF-8?q?fix(owner):=20pending-=D0=BD=D0=B0=D0=B3=D0=B0?= =?UTF-8?q?=D0=B4=D1=83=D0=B2=D0=B0=D0=BD=D0=BD=D1=8F=20=D0=B7=20=D0=B4?= =?UTF-8?q?=D0=B5=D0=B4=D0=BB=D0=B0=D0=B9=D0=BD=D0=BE=D0=BC=20=D0=BD=D0=B5?= =?UTF-8?q?=20=D1=81=D1=8C=D0=BE=D0=B3=D0=BE=D0=B4=D0=BD=D1=96=20=D0=B9?= =?UTF-8?q?=D0=B4=D0=B5=20=D0=B2=20deadline=5Fdue,=20=D0=BD=D0=B5=20person?= =?UTF-8?q?al=5Ftoday?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deriveReminders позначав будь-який pending-вузол у 24-годинному горизонті як personal_today, навіть коли дедлайн ще не настав (наприклад, завтра) — через це "гілка без результату" з дедлайном на завтра хибно показувалась як "твоя задача сьогодні". Тепер personal_today лише для pending із дедлайном сьогодні чи простроченим; решта горизонту йде в deadline_due з заголовком "наближається". Co-Authored-By: Claude Sonnet 5 --- owner/.changes/260727-1014.md | 5 ++++ owner/src/docs/reminders.md | 40 ++++++++++++++++++------------- owner/src/reminders.js | 10 ++++---- owner/src/tests/reminders.test.js | 9 +++++-- 4 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 owner/.changes/260727-1014.md diff --git a/owner/.changes/260727-1014.md b/owner/.changes/260727-1014.md new file mode 100644 index 0000000..1e3009d --- /dev/null +++ b/owner/.changes/260727-1014.md @@ -0,0 +1,5 @@ +--- +bump: patch +section: Changed +--- +`deriveReminders`: pending-вузол дає `personal_today` лише коли дедлайн сьогодні чи прострочений, а не для будь-якого дедлайну в 24-годинному горизонті — pending-вузол з дедлайном "завтра" тепер коректно потрапляє в `deadline_due`. diff --git a/owner/src/docs/reminders.md b/owner/src/docs/reminders.md index e0a4b61..66fb35d 100644 --- a/owner/src/docs/reminders.md +++ b/owner/src/docs/reminders.md @@ -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: не виконує операцій запису (ФС/БД). +- Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати. diff --git a/owner/src/reminders.js b/owner/src/reminders.js index 21789ef..b479c3c 100644 --- a/owner/src/reminders.js +++ b/owner/src/reminders.js @@ -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 воркспейс вузла @@ -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, @@ -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}` } } diff --git a/owner/src/tests/reminders.test.js b/owner/src/tests/reminders.test.js index 412dd9a..f96cceb 100644 --- a/owner/src/tests/reminders.test.js +++ b/owner/src/tests/reminders.test.js @@ -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('чужі й межові вузли нагадувань не породжують — лише мій скоуп', () => {