Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 2m30s
- Updated ChoreSchedule model to include anchor_date and interval_has_deadline. - Refactored interval scheduling logic in scheduleUtils to use anchor_date. - Introduced DateInputField component for selecting anchor dates in ScheduleModal. - Enhanced ScheduleModal to include a stepper for interval days and a toggle for deadline. - Updated tests for ScheduleModal and scheduleUtils to reflect new interval scheduling logic. - Added DateInputField tests to ensure proper functionality and prop handling.
239 lines
9.9 KiB
Markdown
239 lines
9.9 KiB
Markdown
# Feature: Daily chore scheduler refactor phase 2
|
||
|
||
## Overview
|
||
|
||
**Parent Feature:** .github/feat-calenar-chore/feat-calendar-chore.md
|
||
|
||
**Goal:** UI refactor of the 'Every X Days' portion of the chore scheduler so that it is not so complicated and mobile friendly
|
||
|
||
**User Story:**
|
||
|
||
- As a parent, I will be able to select an assigned chore and configure it to occur on 'Every X Days' as before, except I will be presented with a much easier to use interface.
|
||
|
||
**Rules:**
|
||
|
||
- Follow instructions from .github/copilot-instructions.md
|
||
|
||
**Design:**
|
||
|
||
- Do not modify 'Specific Days' pattern or UI. However, reuse code if necessary
|
||
|
||
**Architecture:**
|
||
|
||
1. Shared Logic & Previewer Architecture
|
||
The "Brain" remains centralized, but the output is now focused on the immediate next event to reduce cognitive clutter.
|
||
|
||
State Variables:
|
||
|
||
interval: Integer (Default: 1, Min: 1). "Frequency"
|
||
|
||
anchorDate: Date Object (Default: Today). "Start Date"
|
||
|
||
deadlineTime: String or Null (Default: null). "Deadline"
|
||
|
||
The "Next 1" Previewer Logic:
|
||
|
||
Input: anchorDate + interval.
|
||
|
||
Calculation: Result = anchorDate + (interval days).
|
||
|
||
Formatting: Returns a human-readable string (e.g., "Next occurrence: Friday, Oct 24").
|
||
|
||
Calendar Constraints:
|
||
|
||
Disable Past Dates: Any date prior to "Today" is disabled (greyed out and non-clickable) to prevent scheduling chores in the past.
|
||
|
||
2. Mobile Specification: Bottom Sheet Calendar
|
||
Design & Calendar Details
|
||
Interface: A full-width monthly grid inside a slide-up panel.
|
||
|
||
Touch Targets: Each day cell is a minimum of 44x44 pixels to meet accessibility standards.
|
||
|
||
Month Navigation: Uses large left/right chevron buttons at the top of the sheet.
|
||
|
||
Visual Indicators:
|
||
|
||
Current Selection: A solid primary-colored circle.
|
||
|
||
Today’s Date: A subtle outline or "dot" indicator.
|
||
|
||
Disabled Dates: 30% opacity with a "forbidden" cursor state if touched.
|
||
|
||
Architecture
|
||
Gesture Control: The Bottom Sheet can be dismissed by swiping down on the "handle" at the top or tapping the dimmed backdrop.
|
||
|
||
Performance: The calendar should lazy-load months to ensure the sheet slides up instantly without lag.
|
||
|
||
The Flow
|
||
When the user taps the "Starting on" row...
|
||
|
||
Then the sheet slides up. The current anchorDate is pre-selected and centered.
|
||
|
||
When the user taps a new date...
|
||
|
||
Then the sheet slides down immediately (Auto-confirm).
|
||
|
||
When the sheet closes...
|
||
|
||
Then the main UI updates the Next 1 Previewer text.
|
||
|
||
3. PC (Desktop) Specification: Tethered Popover Calendar
|
||
Design & Calendar Details
|
||
Interface: A compact monthly grid (approx. 250px–300px wide) that floats near the input.
|
||
|
||
Month Navigation: Small chevrons in the header. Includes a "Today" button to quickly jump back to the current month.
|
||
|
||
Day Headers: Single-letter abbreviations (S, M, T, W, T, F, S) to save space.
|
||
|
||
Hover States: As the mouse moves over valid dates, a light background highlight follows the cursor to provide immediate feedback.
|
||
|
||
Architecture
|
||
Tethering: The popover is anchored to the bottom-left of the input field. If the browser window is too small, it intelligently repositions to the top-left.
|
||
|
||
Keyboard Support: \* Arrow Keys: Move selection between days.
|
||
|
||
Enter: Confirm selection and close.
|
||
|
||
Esc: Close without saving changes.
|
||
|
||
Focus Management: When the popover opens, focus shifts to the calendar grid. When it closes, focus returns to the "Starting on" input.
|
||
|
||
The Flow
|
||
When the user clicks the "Starting on" field...
|
||
|
||
Then the popover appears. No backdrop dimming is used.
|
||
|
||
When the user clicks a date...
|
||
|
||
Then the popover disappears.
|
||
|
||
When the user clicks anywhere outside the popover...
|
||
|
||
Then the popover closes (Cancel intent).
|
||
|
||
4. Reusable Time Picker Reference
|
||
Referenced from the 'Specific Days' design. TimePickerPopover.vue
|
||
|
||
Logic: 15-minute intervals (00, 15, 30, 45).
|
||
|
||
Mobile: Implemented via a Bottom Sheet with three scrollable columns.
|
||
|
||
PC: Implemented via a Tethered Popover with three clickable columns.
|
||
|
||
## Clear Action: Both versions must include a "Clear" button to set the deadline to null (Anytime).
|
||
|
||
## Data Model Changes
|
||
|
||
### Backend Models
|
||
|
||
`ChoreSchedule` changes:
|
||
|
||
- Remove `anchor_weekday: int = 0`
|
||
- Add `anchor_date: str = ""` — ISO date string (e.g. `"2026-02-25"`). Empty string means "use today" (backward compat for old DB records).
|
||
- Add `interval_has_deadline: bool = True` — when `False`, deadline is ignored ("Anytime").
|
||
- Change `interval_days` valid range from `[2, 7]` to `[1, 7]`.
|
||
|
||
`from_dict` defaults: `anchor_date` defaults to `""`, `interval_has_deadline` defaults to `True` for backward compat with existing DB records.
|
||
|
||
### Frontend Models
|
||
|
||
`ChoreSchedule` interface changes:
|
||
|
||
- Remove `anchor_weekday: number`
|
||
- Add `anchor_date: string`
|
||
- Add `interval_has_deadline: boolean`
|
||
|
||
---
|
||
|
||
## Frontend Design
|
||
|
||
- `DateInputField.vue` — new shared component at `frontend/vue-app/src/components/shared/DateInputField.vue`
|
||
- Props: `modelValue: string` (ISO date string), `min?: string` (ISO date, for disabling past dates), emits `update:modelValue`
|
||
- Wraps a native `<input type="date">` with styling matching the `TimePickerPopover` button: `--kebab-menu-border` border, `--modal-bg` background, `--secondary` text color
|
||
- Passes `min` to the native input so the browser disables past dates (no custom calendar needed)
|
||
- Fully scoped styles using CSS variables from `colors.css`
|
||
|
||
- `ScheduleModal.vue` — "Every X Days" section fully replaced; "Specific Days" section unchanged
|
||
|
||
---
|
||
|
||
## Backend Implementation
|
||
|
||
- `backend/models/chore_schedule.py`
|
||
- Remove `anchor_weekday: int = 0`
|
||
- Add `anchor_date: str = ""`
|
||
- Add `interval_has_deadline: bool = True`
|
||
- Update `from_dict` to default new fields for backward compat
|
||
|
||
- `backend/api/chore_schedule_api.py`
|
||
- Change `interval_days` validation from `[2, 7]` to `[1, 7]`
|
||
- Accept `anchor_date` (string, ISO format) instead of `anchor_weekday`
|
||
- Accept `interval_has_deadline` (boolean)
|
||
|
||
---
|
||
|
||
## Backend Tests
|
||
|
||
- [x] Update existing interval-mode tests to use `anchor_date` instead of `anchor_weekday`
|
||
- [x] Add test: `interval_days: 1` is now valid (was previously rejected)
|
||
- [x] Add test: `interval_has_deadline: false` is accepted and persisted
|
||
- [x] Add test: old DB records without `anchor_date` / `interval_has_deadline` load with correct defaults
|
||
|
||
---
|
||
|
||
## Frontend Implementation
|
||
|
||
- [x] Created `frontend/vue-app/src/components/shared/DateInputField.vue`
|
||
- Props: `modelValue: string` (ISO date), `min?: string`, emits `update:modelValue`
|
||
- Styled to match `TimePickerPopover` button (border, background, text color)
|
||
- Passes `min` to native `<input type="date">` to disable past dates
|
||
- Fully scoped styles using `colors.css` variables
|
||
- [x] Refactored `ScheduleModal.vue` — "Every X Days" section
|
||
- Removed `anchorWeekday` state; added `anchorDate: ref<string>` (default: today ISO) and `hasDeadline: ref<boolean>` (default: `true`)
|
||
- Changed `intervalDays` min from 2 → 1
|
||
- Replaced `<input type="number">` with a `−` / value / `+` stepper, capped 1–7, styled with Phase 1 chip/button variables
|
||
- Replaced `<select>` anchor weekday with `DateInputField` (min = today's ISO date)
|
||
- Replaced `TimeSelector` with `TimePickerPopover` (exact reuse from Phase 1)
|
||
- Added "Anytime" toggle link below the deadline row; when active, hides `TimePickerPopover` and sets `hasDeadline = false`; when inactive, shows `TimePickerPopover` and sets `hasDeadline = true`
|
||
- Added "Next occurrence: [Weekday, Mon DD]" computed label (pure frontend, `Intl.DateTimeFormat`): starting from `anchorDate`, add `intervalDays` days repeatedly until result ≥ today; displayed as subtle italic label beneath the form rows (same style as Phase 1's "Default (HH:MM AM/PM)" label)
|
||
- Load logic: read `schedule.anchor_date` (default to today if empty), `schedule.interval_has_deadline`, `schedule.interval_days` (clamped to ≥1)
|
||
- Save logic: write `anchor_date`, `interval_has_deadline`; always write `interval_hour`/`interval_minute` (backend ignores them when `interval_has_deadline=false`)
|
||
- "Specific Days" mode left unchanged
|
||
|
||
---
|
||
|
||
## Frontend Tests
|
||
|
||
- [x] `DateInputField.vue`: renders the formatted date value; emits `update:modelValue` on change; `min` prop prevents selection of past dates
|
||
- [x] `ScheduleModal.vue` (Every X Days): stepper clamps to 1–7 at both ends; "Anytime" toggle hides the time picker and sets flag; restoring deadline shows the time picker; save payload contains `anchor_date`, `interval_has_deadline`, and correct `interval_days`; next occurrence label updates correctly when interval or anchor date changes; loading an existing schedule restores all fields including `anchor_date` and `interval_has_deadline`
|
||
|
||
---
|
||
|
||
## Future Considerations
|
||
|
||
- A fully custom calendar (bottom sheet on mobile, tethered popover on desktop) could replace `DateInputField` in a future phase for a more polished mobile experience.
|
||
- `TimePickerPopover` could similarly gain a bottom-sheet variant for mobile.
|
||
|
||
---
|
||
|
||
## Acceptance Criteria (Definition of Done)
|
||
|
||
### Backend
|
||
|
||
- [x] `anchor_weekday` removed; `anchor_date` (string) added with empty-string default for old records
|
||
- [x] `interval_has_deadline` (bool) added, defaults to `True` for old records
|
||
- [x] `interval_days` valid range updated to `[1, 7]`
|
||
- [x] All existing and new backend tests pass
|
||
|
||
### Frontend
|
||
|
||
- [x] New `DateInputField` component: styled native date input, respects `min`, emits ISO string
|
||
- [x] "Every X Days" mode shows `−`/`+` stepper for interval (1–7), `DateInputField` for anchor date, `TimePickerPopover` for deadline
|
||
- [x] "Anytime" toggle clears the deadline (sets `interval_has_deadline = false`) and hides the time picker
|
||
- [x] "Next occurrence" label computes and displays the next date ≥ today based on anchor + interval
|
||
- [x] Past dates are disabled in the date input (via `min`)
|
||
- [x] Existing schedules load correctly — `anchor_date` restored, `interval_has_deadline` restored
|
||
- [x] Save payload is valid and consumed by the existing API unchanged
|
||
- [x] "Specific Days" mode is unchanged
|
||
- [x] Frontend component tests written and passing for `DateInputField` and the refactored `ScheduleModal` interval section
|