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

This commit is contained in:
2026-03-24 15:31:57 -04:00
parent 81169da05e
commit e9f4343426
27 changed files with 890 additions and 65 deletions

View File

@@ -0,0 +1,125 @@
# Feature: Landing page with details
## Overview
When an unauthenticated user visits the web site, they will be sent to this landing page. The page will contain a hero with various images. A sign up/sign in component. On scrolling, various components will show describing the application features and functionality.
**Goal:** New users are brought to the landing page to learn more about the app.
**User Story:**
As a new user, I should be presented with the landing page that tells me about the app.
**Rules:**
.github/copilot-instructions.md
You know how my app works at a high level
**Discussions:**
- I want each part of the landing page as different components (rows)
- Should these components go into their own directory (components/landing)
1. Row 1
- My logo is resources\logo\full_logo.png
- I plan to have my logo in the center near the top of the landing page
- I'd like to have my hero as resources\logo\hero.png
- I'd like to have my current sign in / sign up logic copied to a new component, but with this hero, it should be modern looking - semi-transparent glassmorphism box
- Should I have sign in or sign up? or compact both together
2. Row 2
- This should describe the problem - getting kids to do chores consistently - Describe how a system benefits:
1. Develops Executive Function: Managing a "to-do list" teaches kids how to prioritize tasks, manage their time, and follow multi-step directions—skills that translate directly to schoolwork and future careers.
2. Fosters a "Can-Do" Attitude: Completing a task provides a tangible sense of mastery. This builds genuine self-esteem because its based on actual competence rather than just empty praise.
3. Encourages Accountability: A system moves chores from "Mom/Dad is nagging me" to "This is my responsibility." It teaches that their actions (or lack thereof) have a direct impact on the people they live with.
4. Normalizes Life Skills: By the time they head out on their own, "adulting" won't feel like a mountain to climb. Cooking, cleaning, and organizing will be second nature rather than a stressful learning curve.
5. Promotes Family Belonging: Contributing to the home makes a child feel like a teammate rather than a guest. It reinforces the idea that a family is a unit where everyone supports one another.
3. Row 3
- This should describe how my app helps to solve the problem by creating the system
1. Chores can be created with customizable points that children have fun collecting.
2. Customized reward offer insentives for a child to set a goal. (saving points, etc)
3. Easy interface for children to see which chores need to be done and what rewards they can afford.
4. Parental controls (hidden) behind a pin that offer powerful tools for managing the system
- In this component we'll show some screen shots of the interface.
4. Row 4
- I'm not sure what else I should add for now
- How should I handle colors - should I follow my current theme in colors.css or should I adopt my logo / hero colors?
- Should my landing page be more modern and sleek with animations? What kind?
- Considerations for mobile should also be handled (whether in portrait or landscape mode)
- ***
## Data Model Changes
### Backend Model
### Frontend Model
---
## Backend Implementation
## Backend Tests
- [ ]
---
## Frontend Implementation
- [x] Copy `resources/logo/full_logo.png` and `hero.png` to `frontend/vue-app/public/images/`
- [x] Add landing CSS variables to `colors.css`
- [x] Create `src/components/landing/LandingPage.vue` — orchestrator with sticky nav
- [x] Create `src/components/landing/LandingHero.vue` — hero section with logo, tagline, glassmorphism CTA card
- [x] Create `src/components/landing/LandingProblem.vue` — 5-benefit problem section
- [x] Create `src/components/landing/LandingFeatures.vue` — 4-feature alternating layout with screenshot placeholders
- [x] Create `src/components/landing/LandingFooter.vue` — dark footer with links
- [x] Update router: add `/` → LandingPage route (meta: isPublic), update auth guard
## Frontend Tests
- [x] Update `authGuard.spec.ts`: fix redirect assertions (`/auth``/`) and add 3 landing-route guard tests
- [x] Create `src/components/landing/__tests__/LandingHero.spec.ts`: renders logo, tagline, buttons; CTA clicks push correct routes
---
## Future Considerations
- Eventually add a pricing section (component)
---
## Acceptance Criteria (Definition of Done)
### Backend
- [ ]
### Frontend
- [x] Unauthenticated user visiting `/` sees the full landing page
- [x] Sign In CTA routes to `/auth/login`
- [x] Get Started CTA routes to `/auth/signup`
- [x] Logged-in user visiting `/` is redirected to `/child` or `/parent`
- [x] Unauthenticated user visiting any protected route is redirected to `/` (landing)
- [x] All 4 rows render: Hero, Problem, Features, Footer
- [x] Sticky nav appears on scroll
- [x] Mobile: hero buttons stack vertically, grid goes single-column
- [x] All colors use `colors.css` variables only
---
## Bugs
### BUG-001 — Landing page instantly redirects to `/auth` on first visit
**Status:** Fixed
**Description:**
Visiting `/` as an unauthenticated user caused a visible flash of the landing page followed by an immediate hard redirect to `/auth`, making the landing page effectively unreachable.
**Root Cause:**
`App.vue` calls `checkAuth()` on mount, which hits `/api/auth/me`. For an unauthenticated user this returns a `401`. The fetch interceptor in `api.ts` (`handleUnauthorizedResponse`) then attempted a token refresh, which also failed, and finally called `window.location.assign('/auth')`. The interceptor only exempted paths already starting with `/auth` — not the landing page at `/`.
**Solution:**
- [`frontend/vue-app/src/common/api.ts`](../frontend/vue-app/src/common/api.ts) — Added `if (window.location.pathname === '/') return` inside `handleUnauthorizedResponse()` so the unauthorized interceptor does not forcibly redirect away from the public landing page.
- [`frontend/vue-app/src/stores/auth.ts`](../frontend/vue-app/src/stores/auth.ts) — Updated the cross-tab logout storage event handler to redirect to `/` instead of `/auth/login`, and skip the redirect entirely if already on `/`.

View 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)