added fixes for bug plan 1.0.6RC01
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 5m43s
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 5m43s
This commit is contained in:
242
.github/specs/archive/feat-schedule-enable-disable.md
vendored
Normal file
242
.github/specs/archive/feat-schedule-enable-disable.md
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
# 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 `<div class="schedule-body" :class="{ disabled: !scheduleEnabled }">`
|
||||
- [x] Keep action buttons (Cancel / Save) outside the dim wrapper
|
||||
|
||||
#### Script
|
||||
|
||||
- [x] Add `const scheduleEnabled = ref<boolean>(props.schedule?.enabled ?? true)`
|
||||
- [x] Add `const origEnabled = props.schedule?.enabled ?? true` for dirty tracking
|
||||
- [x] Add `if (scheduleEnabled.value !== origEnabled) return true` to `isDirty` computed
|
||||
- [x] Include `enabled: scheduleEnabled.value` in both `setChoreSchedule` payloads (days + interval modes)
|
||||
|
||||
#### CSS
|
||||
|
||||
- [x] Add toggle row styles (`.schedule-toggle-row`, `.toggle-label`)
|
||||
- [x] Add toggle track/thumb styles (`.toggle-track`, `.toggle-track.on`, `.toggle-thumb`)
|
||||
- [x] Add dim wrapper styles (`.schedule-body`, `.schedule-body.disabled`)
|
||||
|
||||
### Design Tokens — `frontend/vue-app/src/assets/colors.css` (optional)
|
||||
|
||||
- [ ] Optionally add reusable toggle tokens (not needed — existing tokens used directly)
|
||||
|
||||
---
|
||||
|
||||
## Frontend Tests
|
||||
|
||||
### scheduleUtils Tests — `frontend/vue-app/src/__tests__/scheduleUtils.spec.ts`
|
||||
|
||||
- [x] **returns true when enabled is false on a scheduled day (days mode)**: A paused schedule always returns `true` regardless of day
|
||||
- [x] **returns true when enabled is false on an unscheduled day (days mode)**: A paused schedule returns `true` even on days that would normally be skipped
|
||||
- [x] **returns true when enabled is false on a hit day (interval mode)**: A paused interval schedule always returns `true`
|
||||
- [x] **returns true when enabled is false on a non-hit day (interval mode)**: A paused interval schedule returns `true` even on non-hit days
|
||||
- [x] **getDueTimeToday returns null when paused**: A paused schedule's `getDueTimeToday` returns `null` — no deadline, no expiry
|
||||
- [x] **treats enabled=undefined as enabled (backward compat)**: Schedules without `enabled` field behave as enabled
|
||||
|
||||
---
|
||||
|
||||
## CSS Spec
|
||||
|
||||
```css
|
||||
/* ── Enable/Disable Toggle ────────────────────── */
|
||||
|
||||
.schedule-toggle-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.6rem;
|
||||
margin-bottom: 1rem;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.toggle-label {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--form-label, #444);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.toggle-track {
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 24px;
|
||||
border-radius: 999px;
|
||||
border: none;
|
||||
background: var(--form-input-border, #cbd5e1);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toggle-track.on {
|
||||
background: var(--btn-primary, #667eea);
|
||||
}
|
||||
|
||||
.toggle-thumb {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.18);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.toggle-track.on .toggle-thumb {
|
||||
transform: translateX(24px);
|
||||
}
|
||||
|
||||
/* ── Dim wrapper when paused ──────────────────── */
|
||||
|
||||
.schedule-body {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.schedule-body.disabled {
|
||||
opacity: 0.45;
|
||||
pointer-events: none;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria (Definition of Done)
|
||||
|
||||
### Backend
|
||||
|
||||
- [x] `ChoreSchedule` model has `enabled: bool` field, defaulting to `True`
|
||||
- [x] API accepts and persists `enabled` in create/update
|
||||
- [x] Client-side scheduler: `isScheduledToday` returns `true` for paused — chore shows every day
|
||||
- [x] Client-side scheduler: `getDueTimeToday` returns `null` for paused — no deadline or expiry
|
||||
- [x] All backend unit tests pass (28/28)
|
||||
|
||||
### Frontend
|
||||
|
||||
- [x] Toggle row renders in `ScheduleModal` between heading and mode toggle
|
||||
- [x] Toggle uses `--btn-primary` for ON and `--form-input-border` for OFF
|
||||
- [x] Form body dims with `opacity: 0.45` and `pointer-events: none` when paused
|
||||
- [x] `enabled` field is included in save payloads for both modes
|
||||
- [x] Dirty detection tracks `enabled` changes
|
||||
- [x] Toggle meets 44px minimum touch target
|
||||
- [x] All frontend scheduleUtils tests pass (38/38)
|
||||
Reference in New Issue
Block a user