feat(todo): the Plan beside Upcoming, with what has no date (ADR 0033, D34)

Upcoming's 3-day grid gets the Plan Today's calendar has, through what
the two tabs now share (PlanHost). Its new last group holds the open
tasks of no day, so a week can be planned from the Inbox by dropping them
on an hour; they aren't counted on the button, which says what is due.

Whats-New: Upcoming's 3-day view has the Plan too, and tasks without a date can be dragged from it onto any day.
Whats-New-De: Auch die 3-Tage-Ansicht unter Demnächst hat den Plan, und Aufgaben ohne Datum lassen sich von dort auf jeden Tag ziehen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-05 19:37:55 +01:00
commit 31ff8d7e91
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
12 changed files with 342 additions and 159 deletions

View file

@ -266,6 +266,7 @@
"planTitle": "Plan",
"planShow": "Plan einblenden",
"planHide": "Plan ausblenden",
"planNoDate": "Ohne Datum",
"planToPlan": "Zu planen",
"planTimeBlocked": "Eingeplant",
"planEmpty": "Nichts zu planen",

View file

@ -861,6 +861,7 @@
"@planHide": {
"description": "Tooltip of Today's Plan button and the Plan's close button while it shows (ADR 0033, D30)."
},
"planNoDate": "No date",
"planToPlan": "To plan",
"@planToPlan": {
"description": "Today's Plan (ADR 0033, D30): the group of today's tasks without a time, to be given one."

View file

@ -1696,6 +1696,12 @@ abstract class AppLocalizations {
/// **'Hide the Plan'**
String get planHide;
/// No description provided for @planNoDate.
///
/// In en, this message translates to:
/// **'No date'**
String get planNoDate;
/// Today's Plan (ADR 0033, D30): the group of today's tasks without a time, to be given one.
///
/// In en, this message translates to:

View file

@ -1047,6 +1047,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get planHide => 'Plan ausblenden';
@override
String get planNoDate => 'Ohne Datum';
@override
String get planToPlan => 'Zu planen';

View file

@ -1047,6 +1047,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get planHide => 'Hide the Plan';
@override
String get planNoDate => 'No date';
@override
String get planToPlan => 'To plan';

View file

@ -85,11 +85,13 @@ List<OpenTask> subTasksOf(List<OpenTask> tasks, String parentId) => [
}
/// What Today's Plan holds (ADR 0033, D30, as Todoist's): the `overdue`
/// tasks, today's without a time `toPlan`, and today's with one `timed`.
/// tasks, today's without a time `toPlan`, today's with one `timed`, and
/// those of no day at all, `noDate` (D34), its sub-tasks staying with theirs.
typedef TodayPlan = ({
List<OpenTask> overdue,
List<OpenTask> toPlan,
List<OpenTask> timed,
List<OpenTask> noDate,
});
/// [TodayPlan] on [today]: the overdue tasks oldest first, today's by
@ -113,12 +115,24 @@ TodayPlan planOf(
for (final task in due)
if (kept(task) && task.content.date?.time != null) task,
],
noDate: [
for (final task in tasks)
if (kept(task) &&
task.content.date == null &&
task.content.parentId == null &&
!task.content.completed)
task,
],
);
}
/// How many tasks the plan holds, as Today's Plan button counts them.
/// How many tasks the plan holds for today, as the Plan button counts them;
/// those of no day wait with no such hurry, and count under their heading.
extension PlanCount on TodayPlan {
int get count => overdue.length + toPlan.length + timed.length;
/// Whether there is nothing in it at all.
bool get isEmpty => count == 0 && noDate.isEmpty;
}
/// What Upcoming shows from [today] on (ADR 0033, D8): the overdue tasks,

View file

@ -0,0 +1,161 @@
import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_todo_flutter/l10n/app_localizations.dart';
import 'package:halkyon_todo_flutter/src/app/todo_dependencies.dart';
import 'package:halkyon_todo_flutter/src/feature/task/bloc/todo_cubit.dart';
import 'package:halkyon_todo_flutter/src/feature/task/model/task.dart';
import 'package:halkyon_todo_flutter/src/feature/task/model/task_views.dart';
import 'package:halkyon_todo_flutter/src/tabs/pane_widths.dart';
import 'package:halkyon_todo_flutter/src/tabs/plan_panel.dart';
import 'package:halkyon_todo_flutter/src/tabs/side_pane.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:material_ui/material_ui.dart';
/// A tab whose hours have the Plan ([PlanPanel]) (ADR 0033, D30, D34): its
/// app bar's button shows or hides it, beside the hours from a medium width
/// on (remembered with the person's settings, for every calendar alike),
/// over their foot on a phone, stepping down out of the way while one of
/// its rows is dragged onto an hour.
mixin PlanHost<T extends StatefulWidget> on State<T> {
/// The projects the Plan shows, for as long as the app runs (none: all).
Set<String> _projects = const {};
/// Keeps the hours where they were scrolled as the Plan comes and goes.
final _grid = GlobalKey();
/// Whether the Plan stands beside the hours, as the body's width allows,
/// rather than over their foot (as last laid out).
bool _wide = false;
/// Whether the phone's Plan is open over the hours' foot.
bool _sheet = false;
/// Whether the phone's Plan has stepped down, one of its rows dragged.
bool _lowered = false;
/// The key of the app bar's Plan button.
Key get planButtonKey;
/// The key of the phone's Plan over the hours' foot.
Key get planSheetKey;
/// Whether the phone's Plan is open, where the add button makes way.
bool get planSheetOpen => !_wide && _sheet;
/// The Plan shown or hidden: beside the hours, remembered with the
/// person's settings; on a phone, its sheet.
Future<void> _togglePlan(TodoState state) async {
if (!_wide) {
setState(() => _sheet = !_sheet);
return;
}
final todo = PersonScope.of<TodoPerson>(context);
final view = state.settings.today;
await todo.settings.write(
state.settings.copyWith(today: view.copyWith(plan: !view.plan)),
);
}
/// The app bar's Plan button, counting what the Plan holds of [today].
Widget planButton(BuildContext context, TodoState state, Day today) {
final l10n = AppLocalizations.of(context);
final shown = _wide ? state.settings.today.plan : _sheet;
final planned = planOf(state.tasks, today, projectIds: _projects).count;
return Tooltip(
message: shown ? l10n.planHide : l10n.planShow,
child: TextButton.icon(
key: planButtonKey,
icon: Icon(
shown ? LucideIcons.panelRightClose : LucideIcons.panelRight,
),
label: Text(l10n.planButton(planned)),
onPressed: () => _togglePlan(state),
),
);
}
/// The hours [grid] makes (with its key and how much room to leave at its
/// foot) beside or under the Plan, beside them where [shown].
Widget plannedCalendar(
BuildContext context, {
required bool shown,
required Widget Function(Key key, double foot) grid,
}) {
void projects(Set<String> ids) => setState(() => _projects = ids);
return LayoutBuilder(
builder: (context, constraints) {
final wide = WindowSize.fromWidth(constraints.maxWidth).isAtLeastMedium;
if (wide != _wide) {
_wide = wide;
// The app bar's Plan button, built before, says what it does now.
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {});
}
});
}
if (_wide) {
return shown
? SidePane(
kind: SidePaneKind.plan,
widths: PersonScope.of<TodoPerson>(context).paneWidths,
fallback: 340,
main: grid(_grid, 88),
pane: PlanPanel(
projectIds: _projects,
onProjects: projects,
),
)
: grid(_grid, 88);
}
// A phone's Plan over the hours' foot, above the floating bar; held
// rows lower it to its heading, the hours free behind it.
final cover = FloatingNavigationBar.coverOf(context);
final height = constraints.maxHeight * 0.5;
const heading = 56.0;
return Stack(
children: [
Positioned.fill(child: grid(_grid, _sheet ? height : 88)),
if (_sheet)
Positioned(
left: 0,
right: 0,
bottom: 0,
height: height + cover,
child: AnimatedSlide(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
offset: Offset(
0,
_lowered ? (height - heading) / (height + cover) : 0,
),
child: Material(
key: planSheetKey,
elevation: 8,
color: Theme.of(context).colorScheme.surfaceContainerLow,
clipBehavior: Clip.antiAlias,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(16),
),
),
child: PlanPanel(
projectIds: _projects,
onProjects: projects,
onClose: () => setState(() => _sheet = false),
onHeaderTap: () => setState(() => _lowered = false),
onDragging: (dragging) {
if (dragging != _lowered) {
setState(() => _lowered = dragging);
}
},
),
),
),
),
],
);
},
);
}
}

View file

@ -22,15 +22,16 @@ enum _PlanGroup() {
overdue,
toPlan,
timed,
noDate,
}
/// Today's Plan (ADR 0033, D30, as Todoist's desktop Today): beside the day
/// of hours from medium widths on, a sheet over its foot on a phone. Its
/// tasks ([planOf]) in three groups, each folding under its heading with its
/// count: *Overdue* with Reschedule, *To plan* (today's without a time) and
/// *Time-blocked* (today's with one), narrowed to the projects [projectIds]
/// tasks ([planOf]) in groups, each folding under its heading with its
/// count: *Overdue* with Reschedule, *To plan* (today's without a time),
/// *Time-blocked* (today's with one) and *No date* (D34), narrowed to the projects [projectIds]
/// names (none: all), picked from *All projects* ([onProjects]). A row held
/// and dropped on an hour of the day goes there, today; a block of the day
/// and dropped on an hour goes there, on that hour's day; a block of today
/// dropped on the Plan loses its time and stays today ([moveTask], which can
/// be undone). [onDragging] hears when one of its rows is being dragged, as
/// the phone's sheet then steps aside; [onClose] closes it where given.
@ -85,6 +86,7 @@ class _PlanPanelState() extends State<PlanPanel> {
(_PlanGroup.overdue, l10n.overdue, plan.overdue),
(_PlanGroup.toPlan, l10n.planToPlan, plan.toPlan),
(_PlanGroup.timed, l10n.planTimeBlocked, plan.timed),
(_PlanGroup.noDate, l10n.planNoDate, plan.noDate),
];
return DragTarget<TaskDrag>(
key: PlanPanel.dropKey,
@ -107,7 +109,7 @@ class _PlanPanelState() extends State<PlanPanel> {
onTap: widget.onHeaderTap,
),
Expanded(
child: plan.count == 0
child: plan.isEmpty
? Center(
child: Text(
l10n.planEmpty,

View file

@ -15,9 +15,7 @@ import 'package:halkyon_todo_flutter/src/feature/task/widget/task_drag.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_refresh.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_tile.dart';
import 'package:halkyon_todo_flutter/src/tabs/hour_grid.dart';
import 'package:halkyon_todo_flutter/src/tabs/pane_widths.dart';
import 'package:halkyon_todo_flutter/src/tabs/plan_panel.dart';
import 'package:halkyon_todo_flutter/src/tabs/side_pane.dart';
import 'package:halkyon_todo_flutter/src/tabs/plan_host.dart';
import 'package:halkyon_todo_flutter/src/tabs/tab_screen.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:intl/intl.dart';
@ -30,9 +28,7 @@ import 'package:material_ui/material_ui.dart';
/// overdue, folding under its heading, with Reschedule moving all of it to
/// a day at once, then today's, headed by the day alone; each with its
/// project. As a calendar, today's hours ([HourGrid], one day) and the Plan
/// ([PlanPanel]) its app bar's Plan button shows or hides: beside the day
/// from a medium width on (remembered), over its foot on a phone, stepping
/// down out of the way while one of its rows is dragged onto an hour. A
/// ([PlanHost]). A
/// button adds a task for today. A reset of the person's keys waits above
/// it (`KeysResetBanner`).
class const TodayTab({super.key}) extends StatefulWidget {
@ -46,39 +42,15 @@ class const TodayTab({super.key}) extends StatefulWidget {
State<TodayTab> createState() => _TodayTabState();
}
class _TodayTabState() extends State<TodayTab> {
class _TodayTabState() extends State<TodayTab> with PlanHost<TodayTab> {
/// Whether what is overdue is folded away.
bool _overdueFolded = false;
/// The projects the Plan shows, for as long as the app runs (none: all).
Set<String> _projects = const {};
@override
Key get planButtonKey => TodayTab.planKey;
/// Keeps the day where it was scrolled as the Plan comes and goes.
final _grid = GlobalKey();
/// Whether the Plan stands beside the day, as the body's width allows,
/// rather than over its foot (as last laid out).
bool _wide = false;
/// Whether the phone's Plan is open over the day's foot.
bool _sheet = false;
/// Whether the phone's Plan has stepped down, one of its rows dragged.
bool _lowered = false;
/// The Plan shown or hidden: beside the day, remembered with the
/// person's settings; on a phone, its sheet.
Future<void> _togglePlan(TodoState state) async {
if (!_wide) {
setState(() => _sheet = !_sheet);
return;
}
final todo = PersonScope.of<TodoPerson>(context);
final view = state.settings.today;
await todo.settings.write(
state.settings.copyWith(today: view.copyWith(plan: !view.plan)),
);
}
@override
Key get planSheetKey => TodayTab.planSheetKey;
@override
Widget build(BuildContext context) {
@ -91,30 +63,15 @@ class _TodayTabState() extends State<TodayTab> {
final count = overdue.length + due.length;
final view = state.settings.today;
final calendar = view.layout == TodayLayout.calendar;
final planned = planOf(state.tasks, today, projectIds: _projects).count;
final planShown = _wide ? view.plan : _sheet;
return TabScreen(
title: l10n.shellTabToday,
subtitle: state.loaded && count > 0 ? l10n.todayCount(count) : null,
actions: [
if (calendar)
Tooltip(
message: planShown ? l10n.planHide : l10n.planShow,
child: TextButton.icon(
key: TodayTab.planKey,
icon: Icon(
planShown
? LucideIcons.panelRightClose
: LucideIcons.panelRight,
),
label: Text(l10n.planButton(planned)),
onPressed: () => _togglePlan(state),
),
),
if (calendar) planButton(context, state, today),
DisplayButton(target: todayDisplay),
const SelectTasksMenu(),
],
floatingActionButton: calendar && !_wide && _sheet
floatingActionButton: calendar && planSheetOpen
? null
: AddTaskButton(onPressed: () => showQuickAdd(context, day: today)),
body: Column(
@ -146,91 +103,18 @@ class _TodayTabState() extends State<TodayTab> {
],
busy: const {},
);
// The day's last hours can be scrolled above a phone's Plan.
Widget grid({double foot = 88}) => HourGrid(
key: _grid,
days: 1,
foot: foot,
coloring: TaskColoring.priority,
// The hour before now at the top, as the day's rest is to plan.
firstHour: clock.now().hour - 1,
calendarFor: calendarFor,
);
void projects(Set<String> ids) => setState(() => _projects = ids);
return LayoutBuilder(
builder: (context, constraints) {
final wide = WindowSize.fromWidth(constraints.maxWidth).isAtLeastMedium;
if (wide != _wide) {
_wide = wide;
// The app bar's Plan button, built before, says what it does now.
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {});
}
});
}
if (_wide) {
return view.plan
? SidePane(
kind: SidePaneKind.plan,
widths: PersonScope.of<TodoPerson>(context).paneWidths,
fallback: 340,
main: grid(),
pane: PlanPanel(
projectIds: _projects,
onProjects: projects,
),
)
: grid();
}
// A phone's Plan over the day's foot, above the floating bar; held
// rows lower it to its heading, the day free behind it.
final cover = FloatingNavigationBar.coverOf(context);
final height = constraints.maxHeight * 0.5;
const heading = 56.0;
return Stack(
children: [
Positioned.fill(child: grid(foot: _sheet ? height : 88)),
if (_sheet)
Positioned(
left: 0,
right: 0,
bottom: 0,
height: height + cover,
child: AnimatedSlide(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
offset: Offset(
0,
_lowered ? (height - heading) / (height + cover) : 0,
),
child: Material(
key: TodayTab.planSheetKey,
elevation: 8,
color: Theme.of(context).colorScheme.surfaceContainerLow,
clipBehavior: Clip.antiAlias,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(16),
),
),
child: PlanPanel(
projectIds: _projects,
onProjects: projects,
onClose: () => setState(() => _sheet = false),
onHeaderTap: () => setState(() => _lowered = false),
onDragging: (dragging) {
if (dragging != _lowered) {
setState(() => _lowered = dragging);
}
},
),
),
),
),
],
);
},
return plannedCalendar(
context,
shown: view.plan,
grid: (key, foot) => HourGrid(
key: key,
days: 1,
foot: foot,
coloring: TaskColoring.priority,
// The hour before now at the top, as the day's rest is to plan.
firstHour: clock.now().hour - 1,
calendarFor: calendarFor,
),
);
}

View file

@ -13,6 +13,7 @@ import 'package:halkyon_todo_flutter/src/feature/task/widget/task_drag.dart';
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_refresh.dart';
import 'package:halkyon_todo_flutter/src/tabs/day_agenda.dart';
import 'package:halkyon_todo_flutter/src/tabs/hour_grid.dart';
import 'package:halkyon_todo_flutter/src/tabs/plan_host.dart';
import 'package:halkyon_todo_flutter/src/tabs/tab_screen.dart';
import 'package:halkyon_todo_flutter/src/tabs/week_strip.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
@ -21,15 +22,30 @@ import 'package:material_ui/material_ui.dart';
/// Upcoming (ADR 0033, D2, D8): every task of the person's from today on
/// that its Display keeps (D17, in their settings), as an agenda
/// ([DayAgenda], its project at the end of each row) or three days side by
/// side ([HourGrid]); the add button adds a task for the day picked.
/// side ([HourGrid]), with the Plan ([PlanHost], D34); the add button adds a
/// task for the day picked.
class const UpcomingTab({super.key}) extends StatefulWidget {
/// The key of the app bar's Plan button (D34).
static const planKey = ValueKey('upcoming-plan');
/// The key of the phone's Plan over the hours' foot (D34).
static const planSheetKey = ValueKey('upcoming-plan-sheet');
@override
State<UpcomingTab> createState() => _UpcomingTabState();
}
class _UpcomingTabState() extends State<UpcomingTab> {
class _UpcomingTabState()
extends State<UpcomingTab>
with PlanHost<UpcomingTab> {
Day? _selected;
@override
Key get planButtonKey => UpcomingTab.planKey;
@override
Key get planSheetKey => UpcomingTab.planSheetKey;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
@ -58,18 +74,22 @@ class _UpcomingTabState() extends State<UpcomingTab> {
return TabScreen(
title: l10n.shellTabUpcoming,
actions: [
if (upcoming.layout == UpcomingLayout.threeDay)
planButton(context, state, today),
DisplayButton(target: upcomingDisplay),
const SelectTasksMenu(),
],
floatingActionButton: AddTaskButton(
onPressed: () => showQuickAdd(
context,
day: switch (_selected) {
final day? when !(day < Day.of(clock.now())) => day,
_ => Day.of(clock.now()),
},
),
),
floatingActionButton: planSheetOpen
? null
: AddTaskButton(
onPressed: () => showQuickAdd(
context,
day: switch (_selected) {
final day? when !(day < Day.of(clock.now())) => day,
_ => Day.of(clock.now()),
},
),
),
body: !state.loaded
? const Center(child: AppLoadingIndicator())
: switch (upcoming.layout) {
@ -80,10 +100,16 @@ class _UpcomingTabState() extends State<UpcomingTab> {
calendarFor: calendarFor,
),
),
UpcomingLayout.threeDay => HourGrid(
coloring: upcoming.coloring,
onStart: select,
calendarFor: calendarFor,
UpcomingLayout.threeDay => plannedCalendar(
context,
shown: state.settings.today.plan,
grid: (key, foot) => HourGrid(
key: key,
foot: foot,
coloring: upcoming.coloring,
onStart: select,
calendarFor: calendarFor,
),
),
},
);

View file

@ -36,6 +36,7 @@ import 'package:halkyon_todo_flutter/src/tabs/hour_grid.dart';
import 'package:halkyon_todo_flutter/src/tabs/plan_panel.dart';
import 'package:halkyon_todo_flutter/src/tabs/today_icon.dart';
import 'package:halkyon_todo_flutter/src/tabs/today_tab.dart';
import 'package:halkyon_todo_flutter/src/tabs/upcoming_tab.dart';
import 'package:halkyon_todo_flutter/src/tabs/week_strip.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:intl/intl.dart';
@ -1133,6 +1134,44 @@ void main() {
expect(find.text('Far'), findsOneWidget);
});
testWidgets('its Plan shows in 3-day alone; a task of no date dropped on '
"a later day's hour gets that day and time (D34)", (tester) async {
await start(tester, usedBefore: true);
await seed(
tester,
TaskContent(title: 'Someday', projectId: inbox()),
);
await goTab(tester, 'Upcoming');
expect(find.byKey(UpcomingTab.planKey), findsNothing);
await threeDay(tester);
expect(find.byKey(UpcomingTab.planKey), findsOneWidget);
tester.view.physicalSize = const Size(1400, 900);
await tester.pumpAndSettle();
expect(find.text('No date'), findsOneWidget);
final today = Day.of(DateTime.now());
final row = tester.getCenter(find.text('Someday'));
final column = tester.getTopLeft(
find.byKey(HourGrid.columnKey(today.plus(1))),
);
final gesture = await tester.startGesture(row);
await tester.pump(kLongPressTimeout + const Duration(milliseconds: 50));
final to = column + const Offset(30, 11 * 60 + 5);
for (var step = 1; step <= 10; step++) {
await gesture.moveTo(Offset.lerp(row, to, step / 10)!);
await tester.pump(const Duration(milliseconds: 16));
}
await gesture.up();
await tester.pumpAndSettle();
final date = (await tester.runAsync(
() => tasks().all(),
))!.single.content!.date!;
expect(date.day, today.plus(1));
expect(date.time, inInclusiveRange(10 * 60 + 30, 11 * 60 + 30));
expect(date.time! % 15, 0);
});
testWidgets(
'a click adds a task at that quarter hour, for half an hour',
(tester) async {

View file

@ -135,6 +135,7 @@ void main() {
expect(plan.toPlan.map((t) => t.id), ['open']);
expect(plan.timed.map((t) => t.id), ['eight', 'nine']);
expect(plan.count, 4);
expect(plan.noDate.map((t) => t.id), ['undated']);
final home = planOf(tasks, today, projectIds: {'home'});
expect(home.overdue, isEmpty);
@ -142,4 +143,35 @@ void main() {
expect(home.timed.map((t) => t.id), ['eight']);
expect(home.count, 2);
});
test('the Plan keeps the tasks of no day apart, uncounted (D34)', () {
const today = Day(2026, 11, 5);
OpenTask task(String id, {String? parentId, bool completed = false}) => (
id: id,
content: TaskContent(
title: id,
projectId: id == 'other' ? 'home' : 'inbox',
parentId: parentId,
completedAt: completed ? DateTime(2026, 11, 4) : null,
),
);
final tasks = [
task('free'),
task('other'),
task('sub', parentId: 'free'),
task('done', completed: true),
];
final plan = planOf(tasks, today);
expect([for (final task in plan.noDate) task.id], ['free', 'other']);
expect(plan.count, 0);
expect(plan.isEmpty, isFalse);
expect(
[
for (final task in planOf(tasks, today, projectIds: {'home'}).noDate)
task.id,
],
['other'],
);
});
}