# Feature: Chore Schedule Enable/Disable Toggle ## Overview **Goal:** Allow parents to pause and resume a chore schedule without deleting it. A paused schedule retains all its configuration (days, intervals, deadlines) but acts as if the chore had no schedule — it shows every day with no deadline or expiry — until re-enabled. **User Story:** As a parent, I want to temporarily pause a chore's schedule so my child still sees it every day but it loses its deadline constraints, without losing the schedule configuration I've set up. **Rules:** follow .github/copilot-instructions.md --- ## UI Design ### Toggle Placement A dedicated row between the `ModalDialog` heading and the mode toggle ("Specific Days" / "Every X Days"): ``` ┌────────────────────────────────────┐ │ 🛏️ Schedule Chore │ heading (unchanged) │ Make your bed │ │ │ │ Schedule ━━━━━━━━━━━━━━━● ON │ ← new toggle row │ │ │ [Specific Days] [Every X Days] │ mode toggle (unchanged) │ ... │ └────────────────────────────────────┘ ``` - Adds ~44px of height; does not modify `ModalDialog.vue`. - Sits above all config, reflecting its role as the highest-priority decision ("Is this schedule even active?"). ### Toggle Widget Clean slider (no text inside the track) + external label: - **ON:** Track = `var(--btn-primary)` (#667eea), label = "Enabled" - **OFF:** Track = `var(--form-input-border)` (#cbd5e1), label = "Paused" - **Thumb:** `#fff` with subtle box-shadow - **Dimensions:** 48×24px track, 20px circular thumb, 44px min-height row (mobile touch target) - Uses `role="switch"` and `aria-checked` for accessibility. ### Dim-on-Disable When the toggle is OFF, the form body (mode toggle + all form fields) receives `opacity: 0.45; pointer-events: none`. The action buttons (Cancel / Save) remain fully interactive so the user can save the "paused" state. ### Color Rationale Green (`--btn-green`) is reserved for "reward" semantics in this app. Brand blue (`--btn-primary`) is the correct choice for a generic on/off state, matching the mode toggle, day chips, and primary CTA button. --- ## Data Model Changes ### Backend Model — `backend/models/chore_schedule.py` - [x] Add field `enabled: bool = True` to `ChoreSchedule` dataclass - [x] Add `enabled=d.get('enabled', True)` to `from_dict()` - [x] Add `'enabled': self.enabled` to `to_dict()` ### Frontend Model — `frontend/vue-app/src/common/models.ts` - [x] Add `enabled?: boolean` to `ChoreSchedule` interface --- ## Backend Implementation ### API — `backend/api/chore_schedule_api.py` - [x] Accept `enabled` field in the schedule create/update endpoint payload - [x] Persist `enabled` value through to the database ### Scheduler Logic - [x] In `scheduleUtils.ts` (`isScheduledToday`), return `true` when `schedule.enabled === false` — a paused chore shows every day, like an unscheduled chore: ```typescript if (schedule.enabled === false) return true; // paused = show always, like an unscheduled chore ``` - [x] In `scheduleUtils.ts` (`getDueTimeToday`), return `null` when `schedule.enabled === false` — a paused chore has no deadline or expiry: ```typescript if (schedule.enabled === false) return null; // paused = no deadline, no expiry ``` > Note: Scheduling is evaluated client-side. The original spec referenced Python; this was updated to reflect the actual architecture. > Note: Behavior was revised from "hide chore" to "show always, no deadline" — paused acts like the chore has no schedule at all. ### SSE Events - [x] Existing `chore_schedule_updated` event is sufficient (the payload includes the full schedule object, which will now contain `enabled`) --- ## Backend Tests ### Unit Tests — `backend/tests/test_chore_schedule_api.py` - [x] **test_create_schedule_enabled_by_default**: Create a schedule without specifying `enabled`; verify the persisted schedule has `enabled=True` - [x] **test_create_schedule_with_enabled_false**: Create a schedule with `enabled=False`; verify it persists correctly - [x] **test_update_schedule_toggle_enabled**: Create a schedule (enabled), update it with `enabled=False`, verify the change persists; toggle back to `True`, verify again - [x] **test_enabled_field_in_get_response**: Fetch a schedule via GET; verify the `enabled` field is present in the JSON response - [x] **test_enabled_invalid_value_returns_400**: Pass a non-boolean `enabled` value; verify 400 response ### Model Tests - [x] **test_chore_schedule_from_dict_defaults_enabled**: Call `ChoreSchedule.from_dict({...})` without `enabled`; assert `enabled is True` - [x] **test_chore_schedule_from_dict_enabled_false**: Call `ChoreSchedule.from_dict({..., 'enabled': False})`; assert `enabled is False` - [x] **test_chore_schedule_to_dict_includes_enabled**: Create a `ChoreSchedule` instance; call `to_dict()`; assert `'enabled'` key is present with correct value --- ## Frontend Implementation ### Component — `frontend/vue-app/src/components/shared/ScheduleModal.vue` #### Template - [x] Add toggle row between `ModalDialog` open and mode toggle - [x] Wrap mode toggle + form content in `