feat(tasks): виджет «Идёт сейчас» в сайдбаре - #22
Merged
Conversation
added 6 commits
August 13, 2026 13:04
getActiveTasksAt answers "which tasks does the calendar's red line cross right now". `now` is an argument rather than read from the clock, so the rule is pure and testable without faking timers. The end bound is exclusive so two back-to-back tasks never appear together. toTimerTask centralises the task -> startTask mapping that was copy-pasted in TasksView and TaskDetailDialog. It keeps `||` rather than `??` on purpose: the call sites it replaces collapse a stored 0 into the default, and a silent switch to `??` would start a zero-length pomodoro. A test pins that.
Both call sites built the startTask argument by hand with inline 25/5/15/4 literals. They now share the helper, so the defaults live in one place and a third copy cannot drift. Behaviour is unchanged — the helper keeps the same `||` coercion.
Shows the tasks whose scheduled window contains now, with a start button on the pomodoro ones. Hidden entirely when nothing is running, so the sidebar stays quiet for most of the day. sectionExtraRegistry holds one component per section, so the widget and the project tree are composed in a TasksSidebarSection wrapper rather than by teaching the registry about lists — it has exactly one consumer. The wrapper also lands in both section-extra slots at once, so the widget shows up in the mobile sidebar without extra wiring. defineAsyncComponent moves up to the wrapper, keeping the sidebar a single lazy chunk. The widget reads taskStore.tasks directly rather than going through tasksToCalendarEvents: the latter also yields virtual recurring instances, whose `__virtual__` ids do not exist on the backend and would 404 on the first pomodoro increment.
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
В сайдбаре секции задач, над списком проектов, появился блок «Идёт сейчас»: задачи, чьё временное окно содержит текущий момент, с остатком времени, прогрессом и запуском помодоро. Когда активных задач нет — блок исчезает целиком, чтобы не занимать место бо́льшую часть суток.
Что показывает строка
Остаток времени, долю пройденного окна и счётчик помидоров. Виджет отвечает не только на «чем я занят», но и на «сколько ещё» — иначе за этим всё равно пришлось бы идти в календарь. Левая полоса — эхо линии текущего времени в календаре, чтобы блок не читался как ещё один список.
Окно задачи:
dueDate…dueDate + computeTaskDurationMinutes(task). Граница конца исключающая, иначе две смежные задачи показывались бы вдвоём в момент стыка. Из выборки выпадают задачи безdueDate, all-day (время ровно00:00, как вcalendar-events.ts), выполненные иisOverdue.Состояние таймера в строке
toggleTimerstartTaskКнопка убрана в «идёт» не ради вида:
startTaskначинает сессию с первой фазы, поэтому случайный клик по идущей задаче стирал бы уже отработанные помидоры. Такая возможность существовала до этого PR.При этом в «паузе» кнопка обязана остаться.
startTaskтолько взводит сессию — тикать она начинает отдельно, так же ведут себя обе существующие кнопки в приложении. Значит сразу после клика по кнопке виджета строка попадает именно в «паузу», и если бы кнопка там пропадала, запустить таймер из виджета стало бы невозможно.Для этого
timer-storeначал отдаватьactiveTaskId: без него строка не отличает «идёт» от «идёт по другой задаче».Решения, которые стоит отметить на ревью
Виджет читает
taskStore.tasksнапрямую, а не черезtasksToCalendarEvents. Последний отдаёт ещё и виртуальные инстансы повторяющихся задач с id видаid__virtual__ts, которых нет на бэке — первый же инкремент помидора по такой задаче упал бы с 404. Отсекать на источнике данных надёжнее, чем не забывать проверять флагisVirtual.sectionExtraRegistryдержит один компонент на секцию, поэтому виджет и дерево проектов скомпонованы вTasksSidebarSection.vue, а не через расширение реестра до массива — у него ровно один потребитель. Обёртка попутно закрывает оба слотаsection-extraразом, так что виджет работает и в мобильном сайдбаре без отдельной проводки.defineAsyncComponentпереехал на обёртку — сайдбар остался одним ленивым чанком.Вся логика — в чистых функциях, компоненты тонкие.
nowпередаётся аргументом, а не читается из часов, поэтому правила тестируются без подмены таймеров. Окно считается в одном приватномgetWindow(), на котором построены и фильтр активных задач, и расчёт прогресса — разойтись они не могут.Попутный рефакторинг
Маппинг задачи в аргумент
startTaskбыл скопирован вTasksViewиTaskDetailDialog, оба раза с инлайновыми25/5/15/4вместо лежащего рядомPOMODORO_DEFAULTS. Виджет стал бы третьей копией — вместо этого вынесенtoTimerTask, оба места переведены на него.Там намеренно оставлен
||, а не??: исходные вызовы схлопывали нулевую длительность в дефолт, и тихая замена оператора запускала бы помидор нулевой длины. Тест наpomodoroDuration: 0 → 25написан до перевода вызовов, чтобы поймать такую подмену.Проверено
mainбыло 312),vue-tscчисто,npm run buildпроходитВ светлой теме строки изначально были почти невидимы —
bg-sidebar-accent/40давалoklch(0.968)против фона сайдбараoklch(0.985), разница меньше двух процентов. Замерено в браузере, добавлен бордер. Вторичный текст проверен отдельно: контраст ≈5:1.Кнопка визуально 32px, но тач-таргет расширен до 44×44 через
::before, чтобы не разрежать плотный сайдбар. Прогресс-бар и пульсация уважаютprefers-reduced-motion, прогресс скрыт от скринридеров — текст рядом говорит то же самое.Осознанно не сделано
dueDate, для которой календарь рисует призрак на сегодня, в виджет не попадёт — следствие решения брать только реальные задачи.HourGridпродолжает держать свойsetIntervalвместо новогоuseNow: инвариант задачи запрещал трогать календарь.computeTaskDurationMinutesберёт дефолты через??, аtoTimerTask— через||. Для задачи сpomodoroCount: 0это расходится, но на достижимых путях не проявляется: такая задача не попадает в виджет вовсе. Файл пре-существует и задачей не затрагивался.Дизайн, план, результаты проверки и разбор — в
docs/tasks/sidebar-current-task-widget.md.