You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add an optional due date action to the Create Task screen so users can choose when a task is due. Not every task has a due date, so the default state should be no due date.
This extends the core create task flow from #121. The UI should not behave like a required text field. It should be a discreet label/button-style action that says "Add due date" when no date is selected. Tapping it opens the date picker. After selection, the chosen date is displayed and persisted into Task.dueDate, represented as kotlin.time.Instant from #120.
The visual design of this action must be reviewed as part of the ticket to ensure it fits the Create Task screen layout before implementation is considered complete.
Scope
In scope: optional due date UI action, "Add due date" empty state, date picker interaction, selected date display, clearing/removing the selected due date if supported by the reviewed design, saving the selected value into Task.dueDate, string resources, design review, and ViewModel tests for null default and date changes.
Out of scope: priority, URL, location, recurring tasks, reminders, custom calendar component, and date-based list grouping.
Acceptance criteria
GIVEN the user opens the Create Task screen
WHEN the form is displayed
THEN the due date is unset by default
GIVEN no due date is selected
WHEN the Create Task screen is displayed
THEN the UI shows an "Add due date" label/button-style action, not a required text field
GIVEN the user taps "Add due date"
WHEN the action is triggered
THEN a date picker is shown
GIVEN the date picker is shown
WHEN the user selects a date
THEN the selected date is shown in the due date action area
GIVEN the user selects a due date and saves the task
WHEN the task is persisted
THEN dueDate stores the selected day as a kotlin.time.Instant
GIVEN the user does not select a due date
WHEN they save the task
THEN dueDate is persisted as null
GIVEN the due date UI has been implemented
WHEN the ticket is reviewed
THEN the design is explicitly reviewed for placement, visual hierarchy, tap target, empty state, and selected-date state
If selectedDate == null, show the placeholder text and do not show the clear button.
If selectedDate != null, show the selected date text and a trailing X icon button.
Tapping the card opens a modal bottom sheet.
Selecting a date and confirming calls onDateSelected(selectedInstant) and closes the sheet.
Tapping the X icon calls onDateSelected(null).
Tapping the X icon must not open the bottom sheet.
UI Details
Card Layout
The component should be visually structured as:
label on top
value row below
selected date or placeholder on the leading side
clear X icon button on the trailing side when there is a selected date
Recommended style:
full width
same horizontal padding as the title and description fields
subtle visual hierarchy
entire card is the tap target
clear button remains visually secondary
Interaction Detail
The clear button must consume its own click event. Pressing X must clear the date only. It must not also trigger the card tap and open the bottom sheet.
Bottom Sheet Details
Sheet Content
The bottom sheet will contain:
a Material 3 DatePicker
a dismiss button
a confirm button
Recommended implementation details:
keep sheet visibility state local to FoliaryDatePicker
use DatePicker and rememberDatePickerState
initialize picker state from selectedDate
enable confirm only when a date is selected in the picker
close the sheet after confirm or cancel
Sheet State Initialization
When the card opens and there is already a selected date:
convert the stored Instant to the current system local date
use that local date to initialize the picker selection
This avoids timezone drift and keeps the selected day stable.
Persistence Semantics
Chosen Rule
Store the picked day as the end of the selected local day, converted to Instant.
Recommended rationale:
the app currently determines overdue tasks using dueDate < now
if the selected day is stored as start-of-day, a task becomes overdue at the beginning of its due day
storing the end of the selected day better matches user expectation
Conversion Behavior
When confirming from the picker:
read the selected picker day
convert it to local date in the current system timezone
convert that local date to the last instant of that local day
persist that final Instant
The conversion should use TimeZone.currentSystemDefault().
Preselecting A Saved Date In The Picker
When a saved due date already exists:
convert the stored Instant to local date
convert that local date to a picker-compatible day value
feed it into the DatePicker initial state
Selected Date Display Format
There is no existing shared date formatting utility in the project and no new libraries may be added without explicit permission.
Recommended initial formatting approach:
convert Instant to local date using TimeZone.currentSystemDefault()
render the local date using LocalDate.toString()
This produces a stable YYYY-MM-DD display without new dependencies.
If a more human-friendly localized format is desired later, it should be handled in a separate formatting improvement task.
Create Task Screen Changes
Update CreateTaskScreen to render FoliaryDatePicker.
Activity
MessiasLima commentedon Jul 26, 2026
/oc
Issue #122 Plan: Create Task Due Date
Goal
Add an optional due date to the Create Task flow using a reusable
FoliaryDatePickercomponent.The due date must:
Xicon button when a date is selectedonDateSelected(null)when theXbutton is tappedTask.dueDateaskotlin.time.InstantExisting State
Current Create Task Flow
CreateTaskScreencurrently renders the title field, description field, and save button.File:
foliary/src/commonMain/kotlin/dev/appoutlet/foliary/feature/createtask/CreateTaskScreen.ktCurrent Create Task State And Events
CreateTaskViewDatacurrently containsid,title,description, andsaveButtonEnabled.CreateTaskEventcurrently containsTitleChanged,DescriptionChanged,SaveClicked, andBackClicked.dueDate = null.File:
foliary/src/commonMain/kotlin/dev/appoutlet/foliary/feature/createtask/CreateTaskViewModel.ktCurrent Task Model
Taskalready supports due dates, so no schema or repository shape change is needed.Task.dueDate: Instant?already exists.TaskRepository.save(task)already persists it.Files:
foliary/src/commonMain/kotlin/dev/appoutlet/foliary/data/task/database/entity/Task.ktfoliary/src/commonMain/kotlin/dev/appoutlet/foliary/data/task/TaskRepository.ktfoliary/src/commonMain/kotlin/dev/appoutlet/foliary/data/task/TaskRepositoryImpl.ktfoliary/src/commonMain/kotlin/dev/appoutlet/foliary/data/task/database/TaskDao.ktExisting UI Patterns
FoliaryCardalready provides the visual base for the new component.ModalBottomSheet.Files:
foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/card/FoliaryCard.ktfoliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/scene/BottomSheetScene.ktChosen Implementation Approach
Component
Create a new reusable component named
FoliaryDatePicker.Recommended file:
foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/datepicker/FoliaryDatePicker.ktThis component will:
FoliaryCardDatePickerXicon button when a date is selectedonDateSelected(null)Why This Approach
Component Contract
Recommended public API:
Behavior Rules
selectedDate == null, show the placeholder text and do not show the clear button.selectedDate != null, show the selected date text and a trailingXicon button.onDateSelected(selectedInstant)and closes the sheet.Xicon callsonDateSelected(null).Xicon must not open the bottom sheet.UI Details
Card Layout
The component should be visually structured as:
Xicon button on the trailing side when there is a selected dateRecommended style:
Interaction Detail
The clear button must consume its own click event. Pressing
Xmust clear the date only. It must not also trigger the card tap and open the bottom sheet.Bottom Sheet Details
Sheet Content
The bottom sheet will contain:
DatePickerRecommended implementation details:
FoliaryDatePickerDatePickerandrememberDatePickerStateselectedDateSheet State Initialization
When the card opens and there is already a selected date:
Instantto the current system local dateThis avoids timezone drift and keeps the selected day stable.
Persistence Semantics
Chosen Rule
Store the picked day as the end of the selected local day, converted to
Instant.Recommended rationale:
dueDate < nowConversion Behavior
When confirming from the picker:
InstantThe conversion should use
TimeZone.currentSystemDefault().Preselecting A Saved Date In The Picker
When a saved due date already exists:
Instantto local dateDatePickerinitial stateSelected Date Display Format
There is no existing shared date formatting utility in the project and no new libraries may be added without explicit permission.
Recommended initial formatting approach:
Instantto local date usingTimeZone.currentSystemDefault()LocalDate.toString()This produces a stable
YYYY-MM-DDdisplay without new dependencies.If a more human-friendly localized format is desired later, it should be handled in a separate formatting improvement task.
Create Task Screen Changes
Update
CreateTaskScreento renderFoliaryDatePicker.File:
foliary/src/commonMain/kotlin/dev/appoutlet/foliary/feature/createtask/CreateTaskScreen.ktPlacement
Recommended placement:
Wiring
Pass the current state and emit changes through the existing event channel.
Expected integration shape:
Test Tags
Recommended tags:
CreateTaskScreen:DueDatePickerFoliaryDatePicker:CardFoliaryDatePicker:ValueFoliaryDatePicker:ClearButtonThese will help UI tests stay stable.
ViewModel Changes
Update
CreateTaskViewModel.kt.File:
foliary/src/commonMain/kotlin/dev/appoutlet/foliary/feature/createtask/CreateTaskViewModel.ktState Changes
Add a new property to
CreateTaskViewData:Event Changes
Add a new event:
Reducer Changes
Handle the new event by updating
state.dueDate.Recommended handler:
Save Changes
Update the saved
Taskconstruction to use:dueDate = state.dueDateinstead of:
No Changes Expected
These parts should not need behavior changes:
String Resources
Add the following strings to:
foliary/src/commonMain/composeResources/values/strings.xmlRequired strings:
create_task_due_date_labelcreate_task_due_date_placeholdercreate_task_due_date_clear_a11ydate_picker_confirmdate_picker_cancelRecommended English copy:
create_task_due_date_label:Due datecreate_task_due_date_placeholder:Add due datecreate_task_due_date_clear_a11y:Clear due datedate_picker_confirm:Confirmdate_picker_cancel:CancelNo hardcoded UI strings should be introduced anywhere in the screen or component.
Icon Choice
Use the existing Lucide icon set.
Recommended icon:
Lucide.XReason:
Accessibility
The component should include:
create_task_due_date_clear_a11yXicon buttonInternal Helper Logic
Keep helper logic private to the new component file unless reuse becomes necessary.
Recommended private helpers:
Instantto local dateInstantInstantfor displayThis keeps the new behavior self-contained and avoids premature abstraction.
Test Plan
1. ViewModel Tests
Update:
foliary/src/commonTest/kotlin/dev/appoutlet/foliary/feature/createtask/CreateTaskViewModelTest.ktAdd coverage for:
dueDate == nullDueDateChanged(selectedDate)updates stateDueDateChanged(null)clears statenullwhen untouched2. Screen Tests
Update:
foliary/src/commonTest/kotlin/dev/appoutlet/foliary/feature/createtask/CreateTaskScreenTest.ktAdd coverage for:
viewData.dueDateis presentCreateTaskEvent.DueDateChanged(null)3. Component Tests
Add:
foliary/src/commonTest/kotlin/dev/appoutlet/foliary/core/ui/component/datepicker/FoliaryDatePickerTest.ktRecommended coverage:
selectedDate == nullselectedDate != nullonDateSelected(null)4. Scope Of Verification
Follow repo rules:
Recommended commands after implementation:
./gradlew foliary:jvmTest --tests "dev.appoutlet.foliary.feature.createtask.CreateTaskViewModelTest"./gradlew foliary:jvmTest --tests "dev.appoutlet.foliary.feature.createtask.CreateTaskScreenTest"./gradlew foliary:jvmTest --tests "dev.appoutlet.foliary.core.ui.component.datepicker.FoliaryDatePickerTest"Design Review Checklist
The issue requires explicit design review. The implementation is not complete until this review happens.
Review checklist:
Non-Goals
This ticket does not include:
Risks And Edge Cases
Timezone Conversion
Risk:
InstantMitigation:
TimeZone.currentSystemDefault()Overdue Semantics
Risk:
Mitigation:
InstantClick Handling
Risk:
Mitigation:
IconButtonDate Formatting
Risk:
Mitigation:
YYYY-MM-DDdisplay for nowFile Impact Summary
New file:
foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/datepicker/FoliaryDatePicker.ktUpdated files:
foliary/src/commonMain/kotlin/dev/appoutlet/foliary/feature/createtask/CreateTaskScreen.ktfoliary/src/commonMain/kotlin/dev/appoutlet/foliary/feature/createtask/CreateTaskViewModel.ktfoliary/src/commonMain/composeResources/values/strings.xmlfoliary/src/commonTest/kotlin/dev/appoutlet/foliary/feature/createtask/CreateTaskViewModelTest.ktfoliary/src/commonTest/kotlin/dev/appoutlet/foliary/feature/createtask/CreateTaskScreenTest.ktRecommended new test file:
foliary/src/commonTest/kotlin/dev/appoutlet/foliary/core/ui/component/datepicker/FoliaryDatePickerTest.ktAcceptance Criteria Mapping
CreateTaskViewData(dueDate = null).Add due dateshown when empty: handled byFoliaryDatePickerplaceholder state.FoliaryDatePicker.CreateTaskViewModelsavingstate.dueDate.null: handled by default state and clear behavior.Xbutton callingonDateSelected(null).Final Recommendation
Proceed with a minimal implementation that:
FoliaryDatePickeras a reusable card-based componentFoliaryCardunchangedInstantYYYY-MM-DDXicon button that emitsnullopencode-agent commentedon Jul 26, 2026
Created PR #197
opencode session | github run