diff --git a/backend/api/chore_schedule_api.py b/backend/api/chore_schedule_api.py
index 3dd65d7..9d58352 100644
--- a/backend/api/chore_schedule_api.py
+++ b/backend/api/chore_schedule_api.py
@@ -60,6 +60,7 @@ def set_chore_schedule(child_id, task_id):
return jsonify({'error': 'day_configs must be a list', 'code': ErrorCodes.INVALID_VALUE}), 400
default_hour = data.get('default_hour', 8)
default_minute = data.get('default_minute', 0)
+ default_has_deadline = data.get('default_has_deadline', True)
schedule = ChoreSchedule(
child_id=child_id,
task_id=task_id,
@@ -67,6 +68,7 @@ def set_chore_schedule(child_id, task_id):
day_configs=day_configs,
default_hour=default_hour,
default_minute=default_minute,
+ default_has_deadline=default_has_deadline,
)
else:
interval_days = data.get('interval_days', 2)
diff --git a/backend/models/chore_schedule.py b/backend/models/chore_schedule.py
index 5a748ee..f0bd97c 100644
--- a/backend/models/chore_schedule.py
+++ b/backend/models/chore_schedule.py
@@ -35,6 +35,7 @@ class ChoreSchedule(BaseModel):
day_configs: list = field(default_factory=list) # list of DayConfig dicts
default_hour: int = 8 # master deadline hour for 'days' mode
default_minute: int = 0 # master deadline minute for 'days' mode
+ default_has_deadline: bool = True # False = 'Anytime', no expiry for 'days' mode
# mode='interval' fields
interval_days: int = 2 # 1–7
@@ -52,6 +53,7 @@ class ChoreSchedule(BaseModel):
day_configs=d.get('day_configs', []),
default_hour=d.get('default_hour', 8),
default_minute=d.get('default_minute', 0),
+ default_has_deadline=d.get('default_has_deadline', True),
interval_days=d.get('interval_days', 2),
anchor_date=d.get('anchor_date', ''),
interval_has_deadline=d.get('interval_has_deadline', True),
@@ -71,6 +73,7 @@ class ChoreSchedule(BaseModel):
'day_configs': self.day_configs,
'default_hour': self.default_hour,
'default_minute': self.default_minute,
+ 'default_has_deadline': self.default_has_deadline,
'interval_days': self.interval_days,
'anchor_date': self.anchor_date,
'interval_has_deadline': self.interval_has_deadline,
diff --git a/frontend/vue-app/src/__tests__/ScheduleModal.spec.ts b/frontend/vue-app/src/__tests__/ScheduleModal.spec.ts
index ecf401e..92b9361 100644
--- a/frontend/vue-app/src/__tests__/ScheduleModal.spec.ts
+++ b/frontend/vue-app/src/__tests__/ScheduleModal.spec.ts
@@ -450,18 +450,18 @@ describe('ScheduleModal save — interval mode', () => {
// Interval form — next occurrence label
// ---------------------------------------------------------------------------
describe('ScheduleModal interval form — next occurrence', () => {
- it('shows next occurrences label in interval mode', async () => {
+ it('shows next occurrence label in interval mode', async () => {
const w = mountModal()
await w.findAll('.mode-btn')[1].trigger('click')
await nextTick()
expect(w.find('.next-occurrence-label').exists()).toBe(true)
})
- it('next occurrences label contains "Next occurrences:"', async () => {
+ it('next occurrence label contains "Next occurrence:"', async () => {
const w = mountModal()
await w.findAll('.mode-btn')[1].trigger('click')
await nextTick()
- expect(w.find('.next-occurrence-label').text()).toContain('Next occurrences:')
+ expect(w.find('.next-occurrence-label').text()).toContain('Next occurrence:')
})
})
diff --git a/frontend/vue-app/src/common/models.ts b/frontend/vue-app/src/common/models.ts
index 649dddf..55342b1 100644
--- a/frontend/vue-app/src/common/models.ts
+++ b/frontend/vue-app/src/common/models.ts
@@ -23,6 +23,7 @@ export interface ChoreSchedule {
day_configs: DayConfig[]
default_hour?: number // master deadline hour; present when mode='days'
default_minute?: number // master deadline minute; present when mode='days'
+ default_has_deadline?: boolean // false = 'Anytime', no expiry for 'days' mode
// mode='interval'
interval_days: number // 1–7
anchor_date: string // ISO date string e.g. "2026-02-25"; "" means use today
diff --git a/frontend/vue-app/src/common/scheduleUtils.ts b/frontend/vue-app/src/common/scheduleUtils.ts
index dde6ef4..a2c3364 100644
--- a/frontend/vue-app/src/common/scheduleUtils.ts
+++ b/frontend/vue-app/src/common/scheduleUtils.ts
@@ -66,6 +66,8 @@ export function getDueTimeToday(
localDate: Date,
): { hour: number; minute: number } | null {
if (schedule.mode === 'days') {
+ // default_has_deadline === false means 'Anytime' — no expiry for scheduled days
+ if (schedule.default_has_deadline === false) return null
const todayWeekday = getLocalWeekday(localDate)
const dayConfig = schedule.day_configs.find((dc: DayConfig) => dc.day === todayWeekday)
if (!dayConfig) return null
diff --git a/frontend/vue-app/src/components/shared/DateInputField.vue b/frontend/vue-app/src/components/shared/DateInputField.vue
index d43445f..ce457af 100644
--- a/frontend/vue-app/src/components/shared/DateInputField.vue
+++ b/frontend/vue-app/src/components/shared/DateInputField.vue
@@ -1,8 +1,22 @@
-
+
+
+
+
diff --git a/frontend/vue-app/src/components/shared/ScheduleModal.vue b/frontend/vue-app/src/components/shared/ScheduleModal.vue
index 64289f9..1fc9614 100644
--- a/frontend/vue-app/src/components/shared/ScheduleModal.vue
+++ b/frontend/vue-app/src/components/shared/ScheduleModal.vue
@@ -28,28 +28,38 @@
Deadline
-
+
+ Anytime
+
{{ DAY_LABELS[idx] }}
-
- exceptions.set(idx, val)"
- />
-
-
-
- {{ formatDefaultTime }}
-
-
+
+
+ exceptions.set(idx, val)"
+ />
+
+
+
+ {{ hasDefaultDeadline ? formatDefaultTime : 'Anytime' }}
+
+
+
@@ -91,6 +101,11 @@
+
+
+ Next occurrence: {{ nextOccurrence }}
+
+
@@ -104,12 +119,6 @@
{{ hasDeadline ? 'Clear (Anytime)' : 'Set deadline' }}
-
-
-
- Next occurrences:
- {{ d }}
-
{{ errorMsg }}
@@ -185,6 +194,7 @@ const { days: _days, base: _base, exMap: _exMap } = initDaysState(props.schedule
const selectedDays = ref>(_days)
const defaultTime = ref(_base)
const exceptions = ref