feat: add chore, kindness, and penalty management components
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m34s

- Implemented ChoreAssignView for assigning chores to children.
- Created ChoreConfirmDialog for confirming chore completion.
- Developed KindnessAssignView for assigning kindness acts.
- Added PenaltyAssignView for assigning penalties.
- Introduced ChoreEditView and ChoreView for editing and viewing chores.
- Created KindnessEditView and KindnessView for managing kindness acts.
- Developed PenaltyEditView and PenaltyView for managing penalties.
- Added TaskSubNav for navigation between chores, kindness acts, and penalties.
This commit is contained in:
2026-02-28 11:25:56 -05:00
parent 65e987ceb6
commit d7316bb00a
61 changed files with 7364 additions and 647 deletions

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -0,0 +1,612 @@
# Feature: Chore Completion Confirmation + Task Refactor
## Overview
**Goal:**
Refactor the "task" concept into three distinct entity types — **Chore**, **Kindness**, and **Penalty** — and implement a chore completion confirmation flow where children can confirm chores and parents approve them.
**User Stories:**
- As a **child**, I can click on a chore and confirm that I completed it. I see a **PENDING** banner (yellow) until a parent confirms.
- As a **child**, I can click an already PENDING chore and cancel my confirmation.
- As a **child**, I see a **COMPLETED** banner (green) on a chore that a parent has approved. That chore is disabled for the rest of the day.
- As a **parent**, I see pending chore confirmations in the **Notifications** tab alongside pending reward requests.
- As a **parent**, I can click a PENDING chore to **approve** it (awarding points) or **reject** it (resetting to available).
- As a **parent**, I can click a non-pending/non-completed chore and award points directly (current behavior). The child view then shows the COMPLETED banner.
- As a **parent**, I can **reset** a completed chore from the kebab menu so the child can confirm it again (points are kept).
- As an **admin**, I can view full tracking history in the database/logs for all confirmation lifecycle events.
**Rules:**
.github/copilot-instructions.md
---
## Design Decisions (Resolved)
### Task Refactor → Chore / Kindness / Penalty
**Decision: Full refactor.**
| Old Concept | New Concept | Behavior |
| -------------------------------------------------------------- | ------------ | ------------------------------------------ |
| Task with `is_good=true` (schedulable) | **Chore** | Scheduled, expirable, confirmable by child |
| Task with `is_good=true` (ad-hoc, e.g. "Child was good today") | **Kindness** | Parent-only award, not confirmable |
| Task with `is_good=false` | **Penalty** | Parent-only deduction |
- The `is_good` field is **removed**. Entity type itself determines point direction.
- The `Task` model is retained in the backend but gains a `type` field: `'chore' | 'kindness' | 'penalty'`.
- `task_api.py` is split into `chore_api.py`, `kindness_api.py`, `penalty_api.py`.
- Existing `is_good=true` tasks are auto-classified as **chore**; `is_good=false` as **penalty**.
- Kindness items must be manually created post-migration (acceptable).
### Merged Pending Table
**Decision: Single `PendingConfirmation` model replaces `PendingReward`.**
- Both pending reward requests and pending chore confirmations live in one `pending_confirmations` table, differentiated by `entity_type`.
- The `/pending-rewards` endpoint is replaced by `/pending-confirmations`.
- `pending_rewards.json` DB file is replaced by `pending_confirmations.json`.
### "Completed Today" Tracking
**Decision: `PendingConfirmation` record with `status='approved'` + `approved_at` timestamp.**
- An approved `PendingConfirmation` record persists (DB-backed, survives restart) and serves as the "completed today" marker.
- The frontend checks if `approved_at` is today to determine the COMPLETED state.
- On **reset**, the record is deleted (status returns to available).
- **Multi-completion history** is preserved via `TrackingEvent` — each confirm/approve/reset cycle generates tracking entries. Query `TrackingEvent` by `child_id + entity_id + date + action='approved'` to count completions per day.
### Navigation
**Decision: Sub-nav under "Tasks" tab.**
- Top-level nav stays 4 items: **Children | Tasks | Rewards | Notifications**
- The "Tasks" tab opens a view with 3 sub-tabs: **Chores | Kindness | Penalties**
- Each sub-tab has its own list view, edit view, and assign view.
- No mobile layout changes needed to the top bar.
### Chore Confirmation Scoping
- Each `PendingConfirmation` is scoped to a **single child**. If a chore is assigned to multiple children, each confirms independently.
- Expired chores **cannot** be confirmed.
- A chore that is already PENDING or COMPLETED today **cannot** be confirmed again (unless reset by parent).
---
## Data Model Changes
### Backend Models
#### `Task` Model (Modified)
File: `backend/models/task.py`
```python
@dataclass
class Task(BaseModel):
name: str
points: int
type: Literal['chore', 'kindness', 'penalty'] # replaces is_good
image_id: str | None = None
user_id: str | None = None
```
- `is_good: bool`**removed**
- `type: Literal['chore', 'kindness', 'penalty']`**added**
- Migration: `is_good=True``type='chore'`, `is_good=False``type='penalty'`
#### `PendingConfirmation` Model (New — replaces `PendingReward`)
File: `backend/models/pending_confirmation.py`
```python
@dataclass
class PendingConfirmation(BaseModel):
child_id: str
entity_id: str # task_id or reward_id
entity_type: str # 'chore' | 'reward'
user_id: str
status: str = "pending" # 'pending' | 'approved' | 'rejected'
approved_at: str | None = None # ISO 8601 UTC timestamp, set on approval
```
- Replaces `PendingReward` (which had `child_id`, `reward_id`, `user_id`, `status`)
- `entity_id` generalizes `reward_id` to work for both chores and rewards
- `entity_type` differentiates between chore confirmations and reward requests
- `approved_at` enables "completed today" checks
#### `TrackingEvent` Model (Extended Types)
File: `backend/models/tracking_event.py`
```python
EntityType = Literal['task', 'reward', 'penalty', 'chore', 'kindness']
ActionType = Literal['activated', 'requested', 'redeemed', 'cancelled', 'confirmed', 'approved', 'rejected', 'reset']
```
New actions:
- `confirmed` — child marks chore as done
- `approved` — parent approves chore completion (points awarded)
- `rejected` — parent rejects chore completion (no point change)
- `reset` — parent resets a completed chore (no point change)
#### `ChildOverride` Model (Extended Types)
File: `backend/models/child_override.py`
```python
entity_type: Literal['task', 'reward'] # → Literal['chore', 'kindness', 'penalty', 'reward']
```
#### `ChildTask` DTO (Modified)
File: `backend/api/child_tasks.py`
```python
class ChildTask:
def __init__(self, name, type, points, image_id, id):
self.id = id
self.name = name
self.type = type # replaces is_good
self.points = points
self.image_id = image_id
```
#### SSE Event Types (New)
File: `backend/events/types/event_types.py`
```python
class EventType(Enum):
# ... existing ...
CHILD_CHORE_CONFIRMATION = "child_chore_confirmation"
```
New payload class — File: `backend/events/types/child_chore_confirmation.py`
```python
class ChildChoreConfirmation(Payload):
# child_id: str
# task_id: str
# operation: 'CONFIRMED' | 'APPROVED' | 'REJECTED' | 'CANCELLED' | 'RESET'
```
#### Error Codes (New)
File: `backend/api/error_codes.py`
```python
class ErrorCodes:
# ... existing ...
CHORE_EXPIRED = "CHORE_EXPIRED"
CHORE_ALREADY_PENDING = "CHORE_ALREADY_PENDING"
CHORE_ALREADY_COMPLETED = "CHORE_ALREADY_COMPLETED"
PENDING_NOT_FOUND = "PENDING_NOT_FOUND"
INSUFFICIENT_POINTS = "INSUFFICIENT_POINTS"
```
### Frontend Models
File: `frontend/vue-app/src/common/models.ts`
```typescript
// Task — modified
export interface Task {
id: string;
name: string;
points: number;
type: "chore" | "kindness" | "penalty"; // replaces is_good
image_id: string | null;
image_url?: string | null;
}
// ChildTask — modified
export interface ChildTask {
id: string;
name: string;
type: "chore" | "kindness" | "penalty"; // replaces is_good
points: number;
image_id: string | null;
image_url?: string | null;
custom_value?: number | null;
schedule?: ChoreSchedule | null;
extension_date?: string | null;
}
// PendingConfirmation — new (replaces PendingReward)
export interface PendingConfirmation {
id: string;
child_id: string;
child_name: string;
child_image_id: string | null;
child_image_url?: string | null;
entity_id: string;
entity_type: "chore" | "reward";
entity_name: string;
entity_image_id: string | null;
entity_image_url?: string | null;
status: "pending" | "approved" | "rejected";
approved_at: string | null;
}
// EntityType — extended
export type EntityType = "chore" | "kindness" | "penalty" | "reward";
// ActionType — extended
export type ActionType =
| "activated"
| "requested"
| "redeemed"
| "cancelled"
| "confirmed"
| "approved"
| "rejected"
| "reset";
// SSE event payload — new
export interface ChildChoreConfirmationPayload {
child_id: string;
task_id: string;
operation: "CONFIRMED" | "APPROVED" | "REJECTED" | "CANCELLED" | "RESET";
}
```
---
## Backend Implementation
### API Changes
#### New Files
| File | Description |
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `backend/api/chore_api.py` | CRUD for chores (type='chore'). Routes: `/chore/add`, `/chore/<id>`, `/chore/<id>/edit`, `/chore/list`, `DELETE /chore/<id>` |
| `backend/api/kindness_api.py` | CRUD for kindness acts (type='kindness'). Routes: `/kindness/add`, `/kindness/<id>`, `/kindness/<id>/edit`, `/kindness/list`, `DELETE /kindness/<id>` |
| `backend/api/penalty_api.py` | CRUD for penalties (type='penalty'). Routes: `/penalty/add`, `/penalty/<id>`, `/penalty/<id>/edit`, `/penalty/list`, `DELETE /penalty/<id>` |
| `backend/models/pending_confirmation.py` | `PendingConfirmation` dataclass |
| `backend/events/types/child_chore_confirmation.py` | SSE payload class |
| `backend/api/pending_confirmation.py` | Response DTO for hydrated pending confirmation |
#### Modified Files
| File | Changes |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `backend/models/task.py` | `is_good``type` field |
| `backend/models/tracking_event.py` | Extend `EntityType` and `ActionType` literals |
| `backend/models/child_override.py` | Extend `entity_type` literal |
| `backend/api/child_tasks.py` | `is_good``type` field in DTO |
| `backend/api/child_api.py` | Add chore confirmation endpoints, replace `/pending-rewards` with `/pending-confirmations`, update trigger-task to set COMPLETED state, update all `is_good` references to `type` |
| `backend/api/task_api.py` | Deprecate/remove — logic moves to entity-specific API files |
| `backend/api/error_codes.py` | Add new error codes |
| `backend/events/types/event_types.py` | Add `CHILD_CHORE_CONFIRMATION` |
| `backend/db/db.py` | Add `pending_confirmations_db`, remove `pending_reward_db` |
| `backend/main.py` | Register new blueprints, remove `task_api` blueprint |
#### New Endpoints (on `child_api.py`)
| Method | Route | Actor | Description |
| ------ | ---------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST` | `/child/<id>/confirm-chore` | Child | Body: `{ task_id }`. Creates `PendingConfirmation(entity_type='chore', status='pending')`. Validates: chore assigned, not expired, not already pending/completed today. Tracking: `action='confirmed'`, `delta=0`. SSE: `CHILD_CHORE_CONFIRMATION` (CONFIRMED). |
| `POST` | `/child/<id>/cancel-confirm-chore` | Child | Body: `{ task_id }`. Deletes the pending confirmation. Tracking: `action='cancelled'`, `delta=0`. SSE: `CHILD_CHORE_CONFIRMATION` (CANCELLED). |
| `POST` | `/child/<id>/approve-chore` | Parent | Body: `{ task_id }`. Sets `status='approved'`, `approved_at=now()`. Awards points (respects overrides). Tracking: `action='approved'`, `delta=+points`. SSE: `CHILD_CHORE_CONFIRMATION` (APPROVED) + `CHILD_TASK_TRIGGERED`. |
| `POST` | `/child/<id>/reject-chore` | Parent | Body: `{ task_id }`. Deletes the pending confirmation. Tracking: `action='rejected'`, `delta=0`. SSE: `CHILD_CHORE_CONFIRMATION` (REJECTED). |
| `POST` | `/child/<id>/reset-chore` | Parent | Body: `{ task_id }`. Deletes the approved confirmation record. Tracking: `action='reset'`, `delta=0`. SSE: `CHILD_CHORE_CONFIRMATION` (RESET). |
| `GET` | `/pending-confirmations` | Parent | Returns all pending `PendingConfirmation` records for the user, hydrated with child/entity names and images. Replaces `/pending-rewards`. |
#### Modified Endpoints
| Endpoint | Change |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `POST /child/<id>/trigger-task` | When a parent triggers a chore directly (no pending), create an approved `PendingConfirmation` so child view shows COMPLETED. Update entity_type references from `'task'` to the actual type. |
| `POST /child/<id>/request-reward` | Create `PendingConfirmation(entity_type='reward')` instead of `PendingReward`. |
| `POST /child/<id>/cancel-request-reward` | Query `PendingConfirmation` by `entity_type='reward'` instead of `PendingReward`. |
| `POST /child/<id>/trigger-reward` | Query/remove `PendingConfirmation` by `entity_type='reward'` instead of `PendingReward`. |
| `GET /child/<id>/list-tasks` | Add `pending_status` and `approved_at` fields to each chore in the response (from `PendingConfirmation` lookup). |
| `PUT /child/<id>/set-tasks` | Accept `type` parameter instead of `type: 'good' | 'bad'`. |
#### Database Migration
A one-time migration script (`backend/scripts/migrate_tasks_to_types.py`):
1. For each record in `tasks.json`: if `is_good=True` → set `type='chore'`, if `is_good=False` → set `type='penalty'`. Remove `is_good` field.
2. For each record in `pending_rewards.json`: convert to `PendingConfirmation` format with `entity_type='reward'`, `entity_id=reward_id`. Write to `pending_confirmations.json`.
3. For each record in `tracking_events.json`: update `entity_type='task'``'chore'` or `'penalty'` based on the referenced task's old `is_good` value.
4. For each record in `child_overrides.json`: update `entity_type='task'``'chore'` or `'penalty'` based on the referenced task's old `is_good` value.
---
## Backend Tests
### Test File: `backend/tests/test_chore_api.py` (New)
- [ ] `test_add_chore` — PUT `/chore/add` with `name`, `points` → 201, type auto-set to `'chore'`
- [ ] `test_add_chore_missing_fields` — 400 with `MISSING_FIELDS`
- [ ] `test_list_chores` — GET `/chore/list` returns only `type='chore'` tasks
- [ ] `test_get_chore` — GET `/chore/<id>` → 200
- [ ] `test_get_chore_not_found` — 404
- [ ] `test_edit_chore` — PUT `/chore/<id>/edit` → 200
- [ ] `test_edit_system_chore_clones_to_user` — editing a `user_id=None` chore creates a user copy
- [ ] `test_delete_chore` — DELETE `/chore/<id>` → 200, removed from children's task lists
- [ ] `test_delete_chore_not_found` — 404
- [ ] `test_delete_chore_removes_from_assigned_children` — cascade cleanup
### Test File: `backend/tests/test_kindness_api.py` (New)
- [ ] `test_add_kindness` — PUT `/kindness/add` → 201, type auto-set to `'kindness'`
- [ ] `test_list_kindness` — returns only `type='kindness'` tasks
- [ ] `test_edit_kindness` — PUT `/kindness/<id>/edit` → 200
- [ ] `test_delete_kindness` — cascade removal
### Test File: `backend/tests/test_penalty_api.py` (New)
- [ ] `test_add_penalty` — PUT `/penalty/add` → 201, type auto-set to `'penalty'`
- [ ] `test_list_penalties` — returns only `type='penalty'` tasks
- [ ] `test_edit_penalty` — PUT `/penalty/<id>/edit` → 200
- [ ] `test_delete_penalty` — cascade removal
### Test File: `backend/tests/test_chore_confirmation.py` (New)
#### Child Confirm Flow
- [ ] `test_child_confirm_chore_success` — POST `/child/<id>/confirm-chore` with `{ task_id }` → 200, `PendingConfirmation` record created with `status='pending'`, `entity_type='chore'`
- [ ] `test_child_confirm_chore_not_assigned` — 400 `ENTITY_NOT_ASSIGNED` when chore is not in child's task list
- [ ] `test_child_confirm_chore_not_found` — 404 `TASK_NOT_FOUND` when task_id doesn't exist
- [ ] `test_child_confirm_chore_child_not_found` — 404 `CHILD_NOT_FOUND`
- [ ] `test_child_confirm_chore_already_pending` — 400 `CHORE_ALREADY_PENDING` when a pending confirmation already exists
- [ ] `test_child_confirm_chore_already_completed_today` — 400 `CHORE_ALREADY_COMPLETED` when an approved confirmation exists for today
- [ ] `test_child_confirm_chore_expired` — 400 `CHORE_EXPIRED` when chore is past its deadline
- [ ] `test_child_confirm_chore_creates_tracking_event` — TrackingEvent with `action='confirmed'`, `delta=0`, `entity_type='chore'`
- [ ] `test_child_confirm_chore_wrong_type` — 400 when task is kindness or penalty (not confirmable)
#### Child Cancel Flow
- [ ] `test_child_cancel_confirm_success` — POST `/child/<id>/cancel-confirm-chore` → 200, pending record deleted
- [ ] `test_child_cancel_confirm_not_pending` — 400 `PENDING_NOT_FOUND`
- [ ] `test_child_cancel_confirm_creates_tracking_event` — TrackingEvent with `action='cancelled'`, `delta=0`
#### Parent Approve Flow
- [ ] `test_parent_approve_chore_success` — POST `/child/<id>/approve-chore` → 200, points increased, `status='approved'`, `approved_at` set
- [ ] `test_parent_approve_chore_with_override` — uses `custom_value` from override instead of base points
- [ ] `test_parent_approve_chore_not_pending` — 400 `PENDING_NOT_FOUND`
- [ ] `test_parent_approve_chore_creates_tracking_event` — TrackingEvent with `action='approved'`, `delta=+points`
- [ ] `test_parent_approve_chore_points_correct``points_before` + task points == `points_after` on child
#### Parent Reject Flow
- [ ] `test_parent_reject_chore_success` — POST `/child/<id>/reject-chore` → 200, pending record deleted, points unchanged
- [ ] `test_parent_reject_chore_not_pending` — 400 `PENDING_NOT_FOUND`
- [ ] `test_parent_reject_chore_creates_tracking_event` — TrackingEvent with `action='rejected'`, `delta=0`
#### Parent Reset Flow
- [ ] `test_parent_reset_chore_success` — POST `/child/<id>/reset-chore` → 200, approved record deleted, points unchanged
- [ ] `test_parent_reset_chore_not_completed` — 400 when no approved record exists
- [ ] `test_parent_reset_chore_creates_tracking_event` — TrackingEvent with `action='reset'`, `delta=0`
- [ ] `test_parent_reset_then_child_confirm_again` — full cycle: confirm → approve → reset → confirm → approve (two approvals tracked)
#### Parent Direct Trigger
- [ ] `test_parent_trigger_chore_directly_creates_approved_confirmation` — POST `/child/<id>/trigger-task` with a chore → creates approved PendingConfirmation so child view shows COMPLETED
#### Pending Confirmations List
- [ ] `test_list_pending_confirmations_returns_chores_and_rewards` — GET `/pending-confirmations` returns both types
- [ ] `test_list_pending_confirmations_empty` — returns empty list when none exist
- [ ] `test_list_pending_confirmations_hydrates_names_and_images` — response includes `child_name`, `entity_name`, image IDs
- [ ] `test_list_pending_confirmations_excludes_approved` — only pending status returned
- [ ] `test_list_pending_confirmations_filters_by_user` — only returns confirmations for the authenticated user's children
### Test File: `backend/tests/test_task_api.py` (Modified)
- [ ] Update all existing tests that use `is_good` to use `type` instead
- [ ] `test_add_task` → split into `test_add_chore`, `test_add_kindness`, `test_add_penalty` (or remove if fully migrated to entity-specific APIs)
- [ ] `test_list_tasks_sorted` → update sort expectations for `type` field
### Test File: `backend/tests/test_child_api.py` (Modified)
- [ ] Update tests referencing `is_good` to use `type`
- [ ] Update `set-tasks` tests for new `type` parameter values (`'chore'`, `'kindness'`, `'penalty'`)
- [ ] Update `list-tasks` response assertions to check for `pending_status` and `approved_at` fields on chores
- [ ] Update `trigger-task` tests to verify `PendingConfirmation` creation for chores
- [ ] Update `request-reward` / `cancel-request-reward` / `trigger-reward` tests to use `PendingConfirmation` model
- [ ] Replace `pending-rewards` endpoint tests with `pending-confirmations`
---
## Frontend Implementation
### New Files
| File | Description |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `src/components/task/ChoreView.vue` | Admin list of chores (type='chore'). Same pattern as current `TaskView.vue` with blue theme. |
| `src/components/task/KindnessView.vue` | Admin list of kindness acts (type='kindness'). Yellow theme. |
| `src/components/task/PenaltyView.vue` | Admin list of penalties (type='penalty'). Red theme. |
| `src/components/task/ChoreEditView.vue` | Create/edit chore form. Fields: name, points, image. No `is_good` toggle. |
| `src/components/task/KindnessEditView.vue` | Create/edit kindness form. Fields: name, points, image. |
| `src/components/task/PenaltyEditView.vue` | Create/edit penalty form. Fields: name, points, image. |
| `src/components/task/TaskSubNav.vue` | Sub-nav component with Chores / Kindness / Penalties tabs. Renders as a tab bar within the Tasks view area. |
| `src/components/child/ChoreAssignView.vue` | Assign chores to child (replaces `TaskAssignView` with `type='good'`). |
| `src/components/child/KindnessAssignView.vue` | Assign kindness acts to child. |
| `src/components/child/PenaltyAssignView.vue` | Assign penalties to child (replaces `TaskAssignView` with `type='bad'`). |
| `src/components/child/ChoreConfirmDialog.vue` | Modal dialog for child to confirm chore completion. "Did you finish [chore name]?" with Confirm / Cancel buttons. |
| `src/components/child/ChoreApproveDialog.vue` | Modal dialog for parent to approve/reject pending chore. Shows chore name, child name, points. Approve / Reject buttons. |
### Modified Files
| File | Changes |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `src/common/models.ts` | Replace `Task.is_good` with `Task.type`, add `PendingConfirmation` interface, extend `EntityType`/`ActionType`, add `ChildChoreConfirmationPayload`, replace `PendingReward` with `PendingConfirmation`. Add `pending_status` and `approved_at` to `ChildTask`. |
| `src/common/backendEvents.ts` | Add `child_chore_confirmation` event listener pattern. |
| `src/common/api.ts` | Add `confirmChore()`, `cancelConfirmChore()`, `approveChore()`, `rejectChore()`, `resetChore()`, `fetchPendingConfirmations()`. Remove `fetchPendingRewards()`. |
| `src/components/child/ChildView.vue` | Add chore tap handler → show `ChoreConfirmDialog`. Add PENDING (yellow) / COMPLETED (green) banner rendering. Handle cancel-confirm on PENDING tap. Filter kindness acts into new scrolling row. Listen for `child_chore_confirmation` SSE events. |
| `src/components/child/ParentView.vue` | Add PENDING/COMPLETED banners on chores. Handle approve/reject on PENDING chore tap. Add "Reset" to kebab menu for completed chores. Add "Assign Kindness" button. Update `trigger-task` to create approved confirmation. Replace `is_good` filters with `type` checks. Listen for `child_chore_confirmation` SSE events. |
| `src/components/notification/NotificationView.vue` | Fetch from `/api/pending-confirmations` instead of `/api/pending-rewards`. Render both pending chores and pending rewards with differentiation (icon/label). Listen for `child_chore_confirmation` events in addition to existing `child_reward_request`. |
| `src/layout/ParentLayout.vue` | "Tasks" nav icon remains, routes to a view housing `TaskSubNav` with sub-tabs. |
| `src/components/task/TaskEditView.vue` | Remove or repurpose. Logic moves to entity-specific edit views (no `is_good` toggle). |
| `src/components/task/TaskView.vue` | Remove or repurpose into the sub-nav container view. |
| `src/components/child/TaskAssignView.vue` | Remove. Replaced by `ChoreAssignView`, `KindnessAssignView`, `PenaltyAssignView`. |
| Router config | Add routes for new views. Update existing task routes to chore/kindness/penalty. |
### Files to Remove
| File | Reason |
| -------------------------------------- | ----------------------------------------- |
| `backend/models/pending_reward.py` | Replaced by `pending_confirmation.py` |
| `backend/api/pending_reward.py` | Replaced by `pending_confirmation.py` DTO |
| `backend/data/db/pending_rewards.json` | Replaced by `pending_confirmations.json` |
### ChildView Chore Tap Flow
```
Child taps chore card
├─ Chore expired? → No action (grayed out, "TOO LATE" stamp)
├─ Chore COMPLETED today? → No action (grayed out, "COMPLETED" stamp)
├─ Chore PENDING? → Show ModalDialog "Cancel confirmation?"
│ └─ Confirm → POST /child/<id>/cancel-confirm-chore
└─ Chore available? → Show ChoreConfirmDialog "Did you finish [name]?"
└─ Confirm → POST /child/<id>/confirm-chore
```
### ParentView Chore Tap Flow
```
Parent taps chore card
├─ Chore PENDING? → Show ChoreApproveDialog
│ ├─ Approve → POST /child/<id>/approve-chore (awards points)
│ └─ Reject → POST /child/<id>/reject-chore (resets to available)
├─ Chore COMPLETED today? → No tap action. Kebab menu has "Reset"
│ └─ Reset → POST /child/<id>/reset-chore
└─ Chore available? → Show TaskConfirmDialog (current behavior)
└─ Confirm → POST /child/<id>/trigger-task (sets COMPLETED)
```
### Banner Styling
| State | Banner Text | Text Color | Background | CSS Variable Suggestion |
| --------- | ----------- | -------------------------- | ----------------------- | ----------------------- |
| Pending | `PENDING` | Yellow (`--color-warning`) | Dark semi-transparent | `--banner-bg-pending` |
| Completed | `COMPLETED` | Green (`--color-success`) | Dark semi-transparent | `--banner-bg-completed` |
| Expired | `TOO LATE` | Red (existing) | Gray overlay (existing) | (existing styles) |
---
## Frontend Tests
### Test File: `components/__tests__/ChoreConfirmDialog.test.ts` (New)
- [ ] `renders chore name in dialog`
- [ ] `emits confirm event on confirm button click`
- [ ] `emits cancel event on cancel button click`
### Test File: `components/__tests__/ChoreApproveDialog.test.ts` (New)
- [ ] `renders chore name and points in dialog`
- [ ] `emits approve event on approve button click`
- [ ] `emits reject event on reject button click`
### Test File: `components/__tests__/TaskSubNav.test.ts` (New)
- [ ] `renders three sub-tabs: Chores, Kindness, Penalties`
- [ ] `highlights active tab based on route`
- [ ] `navigates on tab click`
### Test File: `components/__tests__/ChoreView.test.ts` (New)
- [ ] `fetches and renders chore list`
- [ ] `navigates to edit on item click`
- [ ] `shows delete confirmation modal`
- [ ] `refreshes on task_modified SSE event`
### Test File: `components/__tests__/NotificationView.test.ts` (Modified)
- [ ] `fetches from /api/pending-confirmations`
- [ ] `renders both pending chores and pending rewards`
- [ ] `differentiates chore vs reward with label/icon`
- [ ] `refreshes on child_chore_confirmation SSE event`
- [ ] `refreshes on child_reward_request SSE event`
### Test File: `components/__tests__/ChildView.test.ts` (Modified / New)
- [ ] `shows PENDING banner on chore with pending confirmation`
- [ ] `shows COMPLETED banner on chore completed today`
- [ ] `opens ChoreConfirmDialog on available chore tap`
- [ ] `opens cancel dialog on PENDING chore tap`
- [ ] `does not allow tap on expired chore`
- [ ] `does not allow tap on COMPLETED chore`
- [ ] `renders kindness scrolling row`
- [ ] `refreshes on child_chore_confirmation SSE event`
### Test File: `components/__tests__/ParentView.test.ts` (Modified / New)
- [ ] `shows PENDING banner on chore with pending confirmation`
- [ ] `shows COMPLETED banner on approved chore`
- [ ] `opens ChoreApproveDialog on PENDING chore tap`
- [ ] `opens TaskConfirmDialog on available chore tap`
- [ ] `shows Reset in kebab menu for completed chore`
- [ ] `renders kindness scrolling row`
- [ ] `shows Assign Kindness button`
---
## Future Considerations
- **Recurring chore auto-reset**: Automatically clear completed status on schedule rollover (e.g., daily chores reset at midnight).
- **Chore streaks**: Track consecutive days a child completes a chore using `TrackingEvent` history.
- **Multi-completion analytics dashboard**: Query `TrackingEvent` to show completion counts per chore per day/week.
- **Partial credit**: Allow parents to award fewer points than the chore's value when approving.
- **Chore delegation**: Allow one child to reassign a chore to a sibling.
- **Photo proof**: Child attaches a photo when confirming a chore.
- **Kindness auto-classification**: Suggested classification when creating new items based on name patterns.
---
## Acceptance Criteria (Definition of Done)
### Backend
- [ ] `Task` model uses `type: 'chore' | 'kindness' | 'penalty'``is_good` removed
- [ ] `PendingConfirmation` model created, `PendingReward` model removed
- [ ] `pending_confirmations_db` created in `db.py`, `pending_reward_db` removed
- [ ] Migration script converts existing tasks, pending rewards, tracking events, and overrides
- [ ] `chore_api.py`, `kindness_api.py`, `penalty_api.py` created with CRUD endpoints
- [ ] `task_api.py` removed or deprecated
- [ ] Child chore confirmation endpoints: `confirm-chore`, `cancel-confirm-chore`, `approve-chore`, `reject-chore`, `reset-chore`
- [ ] `GET /pending-confirmations` returns hydrated pending chores and rewards
- [ ] `trigger-task` creates approved `PendingConfirmation` when parent triggers a chore directly
- [ ] Reward request/cancel/trigger endpoints migrated to `PendingConfirmation`
- [ ] `list-tasks` response includes `pending_status` and `approved_at` for chores
- [ ] `TrackingEvent` created for every mutation: confirmed, cancelled, approved, rejected, reset
- [ ] Tracking events logged to rotating file logger
- [ ] SSE event `CHILD_CHORE_CONFIRMATION` sent for every confirmation lifecycle event
- [ ] All new error codes defined and returned with proper HTTP status codes
- [ ] All existing tests updated for `type` field (no `is_good` references)
- [ ] All new backend tests pass
### Frontend
- [ ] `Task` and `ChildTask` interfaces use `type` instead of `is_good`
- [ ] `PendingConfirmation` interface replaces `PendingReward`
- [ ] Sub-nav under "Tasks" with Chores / Kindness / Penalties tabs
- [ ] `ChoreView`, `KindnessView`, `PenaltyView` list views created
- [ ] `ChoreEditView`, `KindnessEditView`, `PenaltyEditView` edit/create views created
- [ ] `ChoreAssignView`, `KindnessAssignView`, `PenaltyAssignView` assign views created
- [ ] `TaskView`, `TaskEditView`, `TaskAssignView` removed or repurposed
- [ ] `ChoreConfirmDialog` — child confirmation modal
- [ ] `ChoreApproveDialog` — parent approve/reject modal
- [ ] `ChildView` — chore tap opens confirm dialog, cancel dialog for pending, banners render correctly
- [ ] `ChildView` — expired and completed chores are non-interactive
- [ ] `ChildView` — kindness scrolling row added
- [ ] `ParentView` — pending chore tap opens approve/reject dialog
- [ ] `ParentView` — available chore tap uses existing trigger flow + creates completion record
- [ ] `ParentView` — kebab menu "Reset" option for completed chores
- [ ] `ParentView` — "Assign Kindness" button added
- [ ] `NotificationView` — fetches from `/pending-confirmations`, renders both types
- [ ] SSE listeners for `child_chore_confirmation` in all relevant components
- [ ] Banner styles: yellow PENDING, green COMPLETED (using CSS variables)
- [ ] All `is_good` references removed from frontend code
- [ ] All frontend tests pass
- [ ] Router updated with new routes