shipped

Spec: F06 — Recurring Tasks

Status: shipped · Audited: 2026-06-10

1. Problem & CEO Job

See clearly. SME operations run on rhythms — weekly debt collection calls, monthly payroll prep, daily cash count. When a rhythm breaks, that’s a CEO-grade signal. Recurring tasks encode the rhythm so missing it becomes visible (an overdue generated task feeds the Brief) instead of silently forgotten.

2. User Stories & Acceptance Criteria

  • As a manager, I want to define “every Friday: chase receivables”, so that it materializes as a real task without anyone remembering.
    • Given an active daily/weekly/monthly template, when the generation run executes for a due date, then exactly one Task is created (source_type=recurring) — never duplicates for the same template+date.
    • Given end_date passed or is_active=false, then no further tasks generate.
  • As a CEO, I want missed recurring work to surface, so that broken rhythms reach the Brief.
    • Given a generated task past due_date, then it appears in Brief section “overdue” like any task (no special-casing needed).
  • As a manager, I want to pause a rhythm without deleting its history.
    • Given is_active=false then re-enabled, then next_run_at recomputes from today, not from the gap.
  • As a manager, I want a temporary refresh outage not to erase my operating rhythms.
    • Given a populated shared Android/iOS recurring list, when refresh fails, then the last committed templates and Run now actions remain visible behind one concise warning with explicit Retry. Repeated failure and create reconciliation retain that snapshot; only an initial empty failure is blocking.
    • Given a populated shared Android/iOS recurring detail, when refresh fails, then the exact template facts and Edit/Delete/Run now actions remain visible behind one concise warning with explicit Retry. Repeated failure retains that snapshot; only an initial empty failure is blocking.
  • As a manager in more than one organization, I want the recurring directory to follow the active organization.
    • Given Android switches organizations, when Work Graph reports recurring work and the user opens Recurring, then the destination creates a fresh organization-scoped model instead of rendering the previous tenant’s cached list.

3. Scope Boundaries

  • In: template CRUD, daily/weekly/monthly (+ rrule string for custom), generation command, active toggle, business-impact inheritance to generated tasks.
  • Out: calendar UI of future occurrences, per-occurrence editing, reminders (Telegram brief covers visibility).

4. Data Model

work.RecurringTask — template fields (template_title, template_description), schedule (frequency, rrule, start_date, end_date, next_run_at, last_run_at), is_active. The persisted auto_create_task field still exists in the model/DTOs for compatibility, but shipped generation ignores it and always auto-materializes active recurring templates. Generated Task carries source_type="recurring".

5. API Contract

MethodPathMin roleNotes
CRUD/api/recurring-tasks/member (write) / viewer (read)Router ViewSet
POST/api/recurring-tasks/<id>/run-now/managerImmediate generation; guards: inactive → 400, past end_date → 400

Generation: management command (invoked by Celery beat hourly), iterates active templates where next_run_at <= today, creates Task, advances next_run_at, stamps last_run_at. Idempotent per (template, date).

6. UI / UX

  • /work/recurring (RecurringPage.tsx) — template list with frequency, next run, active toggle.
  • Mental model for users: “việc lặp lại” = a checklist that writes itself. Creating one looks exactly like creating a task + picking a frequency. No cron syntax ever shown; rrule is power-user/API-only.
  • States: empty (explain the value: “Việc lặp lại sẽ tự tạo task — thử ‘Thu công nợ mỗi thứ 6’”), loading skeletons.
  • Edit + Delete per template (added 2026-06-19): Edit opens a dialog pre-filled with template_title/template_description/frequency/start_date → PATCH /recurring-tasks/:id/; confirmation-gated Delete → DELETE /recurring-tasks/:id/. New useUpdateRecurring/useDeleteRecurring hooks (hooks/queries/useRecurring.ts) + recurringApi.delete. Test ids recurring-edit-{id}/recurring-delete-{id}.
  • Recurring detail shows next_run_at as a localized human-readable fact for every parseable date, with the semantic urgency badge alongside overdue/today values. Tomorrow remains relative; later dates use the shared absolute formatter with the active locale’s month resources. It must never expose the backend ISO date directly.
  • The recurring list uses the same active-locale month resources for later next-run dates; the translated short-date resource owns month/day order (July 14 in English, 14 Tháng 7 in Vietnamese). It must not hard-code English month abbreviations.
  • Shared mobile detail/edit: the detail header keeps Edit and Delete inside the viewport; Edit opens the populated shared form, whose in-flow Save action stays above the software keyboard. Emulator proof is diagnostic only. PM-owned Android floor-device and iPhone 12 evidence remains required.
  • Shared mobile list recovery: populated refresh failures keep the last committed template directory and Run now actions visible behind one compact warning. Retry re-enters refresh without discarding stale rows, and a successful create from stale state appends without dropping existing templates. Initial empty failure remains blocking.
  • Shared mobile detail recovery: populated refresh failures keep the exact recurring template facts plus Edit, Delete, and Run now visible behind one compact warning. Retry re-enters refresh with that same snapshot; initial empty failure remains blocking.
  • Android organization switching: the Recurring route keys its model to the active organization. Switching organizations remounts and reloads the directory, so Work Graph counts and the destination cannot diverge because of a previous tenant snapshot. iOS already constructs the recurring controller with the current organization ID.
  • i18n: recurring.

7. Test Plan

#CaseTypeWhere
1Template CRUDbackendtests/test_api.py::TestRecurringTask, tests/test_lumi.py::TestRecurringTask
2Generation: creates task, advances next_run, idempotent, respects end_date/is_activebackendtests/test_api.py::TestRecurringGeneration
3run_now guards (inactive → 400, past end_date → 400)backendTestF06RunNowGuards
4Reactivation: next_run_at recomputes from todaybackendTestF06Reactivation
5List + create flowe2edescribe("Recurring Tasks")
6List and detail next-run dates stay localized and humanized for overdue/today/later statesshared JVM + native auditRecurringCardMetaTest · test_f06_recurring_detail_date_policy.py · WRK-07.U3 and WRK-08.U7 device tours
7Mobile detail actions remain visible; edit Save remains keyboard-safeshared JVM source invariant + named-device runtimeRecurringDetailHeaderSourceTest; .maestro/critical/57_recurring_edit_keyboard.yaml mechanics flow; FRM-12 PM walkthrough
8Populated mobile list survives repeated refresh failure and explicit Retry without losing templates or Run now actionsshared JVM + source policy + Android lifecycleRecurringScreenModelTest.repeatedRefreshFailureAndRetryPreserveStaleRecurringDirectory; test_f06_recurring_mobile_recovery_policy.py; .maestro/critical/158_android_recurring_refresh_recovery.yaml
9Populated mobile detail survives repeated refresh failure and explicit Retry without losing the exact template facts or actionsshared JVM + source policy + Android lifecycleRecurringDetailScreenModelTest.repeatedRefreshFailureKeepsRecurringDetailAndExplicitRetryRecovers; test_f06_recurring_mobile_recovery_policy.py; .maestro/critical/158_android_recurring_refresh_recovery.yaml
10Android organization switching remounts the Recurring directory with a fresh model keyed to the active organizationAndroid JVM source contractLumiAppOrgScopeTest.recurringRootCreatesFreshModelInsideEachOrganizationScopedRemember

8. Current Behavior

Shipped. Recurring task definitions and scheduled generation exist, with the Celery beat worker path creating ordinary tasks from daily, weekly, and monthly rules inside the same tenant-scoped work model. Scheduler wired (2026-06-10) — no longer “works when poked.” Reactivation fix (a9b72cc0d) — next_run_at recomputes from today on is_active False→True. run_now guards: inactive → 400, past end_date → 400. The stored auto_create_task boolean is currently compatibility-only; the scheduler does not branch on it. Native recurring list and detail surfaces format every parseable next-run fact through the localized shared date vocabulary and reserve detail-header width for Edit/Delete. Populated list and detail surfaces now preserve committed template context and actions through refresh failure with explicit recovery. Android now remounts the recurring directory for the active organization instead of retaining a process-lifetime tenant snapshot; iOS already creates the controller from the current organization ID. Named-device WRK-07.U3, WRK-08.U7, FRM-12, organization-switch runtime, and cross-platform recovery evidence remain PM-owned and open.

9. Error States

#ErrorResponseNotes
ES1Missing required template_title400 {"template_title": ["This field is required."]}Implicit in CRUD tests
ES2Missing required start_date400 {"start_date": ["This field is required."]}Implicit in CRUD tests
ES3Invalid frequency value400 {"frequency": ["\"yearly\" is not a valid choice."]}test_create_recurring_task_rejects_invalid_frequency
ES4Cross-tenant recurring task access404test_recurring_tenant_isolation
ES5run_now on non-existent template404DRF get_object behavior
ES6end_date before start_date200 — silently succeeds; template never generates (end_date < today guard)test_create_recurring_task_with_end_date_before_start_accepted
ES7Frontend load failuretoast.error(t('error_load'))No E2E test
ES8Frontend create failuretoast.error(t('error_create'))No E2E test
ES9Celery beat not runningSilent failure — tasks stop generating; no error, no alertNo monitoring or health check for beat liveness
ES10Celery worker not runningTask queued but never executed; same silent failureNo monitoring
ES11DB constraint violation on generated taskException bubbles up; template’s next_run_at not advanced; retry on next cycleNot unit testable (requires injecting DB failure into Celery task)
ES12run_now on inactive template400 {"detail": "Cannot run a recurring task that is inactive."}Fixed; TestF06RunNowGuards::test_run_now_inactive_returns_400
ES13run_now past end_date400 {"detail": "Cannot run a recurring task past its end date."}Fixed; TestF06RunNowGuards::test_run_now_end_date_past_returns_400
ES14Populated native list refresh failsKeep last committed templates and Run now actions behind one compact warning; explicit Retry refreshesShared JVM/source policy plus guarded Android flow 158; iOS/named-device/accessibility/store/deployed/production proof open
ES15Android switches organization after Recurring was previously loadedRemount the Recurring route with a fresh model keyed to the selected organizationAndroid source regression; reporter-device organization-switch confirmation remains open

10. Diagrams & Docs Touched

backend/DIAGRAMS.md.

11. Implementation Audit (2026-06-13)

#ClaimStatusEvidence
1Template CRUD, active toggle✅ matchTestRecurringTask; test_recurring_tenant_isolation
2Generation: one task per (template, date); advances next_run_at; respects end_date/is_active✅ matchTestRecurringGeneration::test_generate_recurring_tasks_idempotent; test_end_date_equals_today_generates_task
3Celery beat scheduler wired (hourly); no longer “works when poked”✅ matchCELERY_BEAT_SCHEDULE in config/settings/base.py; apps/work/tasks.py::generate_recurring_tasks — closes §9 blocking gap
4Inactive re-enable: next_run_at recomputes from today (not from gap)✅ matcha9b72cc0d; TestF06Reactivation::test_reactivation_recomputes_next_run_from_today
5run_now inactive → 400 guard✅ matchFixed this PR; TestF06RunNowGuards::test_run_now_inactive_returns_400
6run_now past end_date → 400 guard✅ matchFixed this PR; TestF06RunNowGuards::test_run_now_end_date_past_returns_400
7Monthly frequency: both generate and run_now use relativedelta(months=1) — consistent✅ matchapps/work/tasks.py:53 (relativedelta(months=1)); apps/work/views.py:907 (relativedelta(months=1)); divergence reported in prior audit has been fixed
8Concurrent Celery execution: no select_for_update, no idempotency key on (template, date)⚠️ gapE7; two concurrent workers can produce duplicate tasks; test_generate_recurring_tasks_idempotent covers sequential not concurrent
9auto_create_task remains in the schema/DTO contract as a compatibility field, but shipped recurring generation always auto-creates tasks and does not branch on it.✅ matchRecurringTask.auto_create_task exists; generate_recurring_tasks_for_date() never checks it; test_auto_create_task_false_still_generates proves generation still occurs
10Custom rrule frequency falls through to daily (+1 day advance) — rrule string never parsed⚠️ gapE16; test_custom_frequency_advances_by_one_day; custom rrule is API-only and effectively = daily
11Visual QC: recurring-task create CTA uses explicit task-language in Vietnamese✅ match2026-06-18 screenshot pass; vi/recurring.json uses + Tạo công việc định kỳ; RecurringPage.test.tsx
12Visual QC: recurring templates explain the self-writing checklist model and label schedule inputs plainly✅ match2026-06-18 Recurring pass; RecurringPage.tsx adds helper copy plus visible Tần suất / Ngày bắt đầu labels and chip-style schedule metadata
13Android Recurring directory remounts when the active organization changes✅ matchLumiAppFeatureEntries.kt uses remember(orgScopedScreenKey(currentOrgId, "recurring")); LumiAppOrgScopeTest.recurringRootCreatesFreshModelInsideEachOrganizationScopedRemember

Summary: 11/13 match; 2 gaps (concurrent Celery race, rrule never parsed). Monthly relativedelta divergence (formerly row 7 ⚠️) confirmed fixed — both paths use relativedelta(months=1).

Edge Cases Catalog (formerly §13, 2026-06-12; E1–E16): Key items integrated above. Remaining: E1 past next_run_at catch-up (one task per cycle, not batch); E2 monthly edge via run_now (FIXED — row 7, both paths now use relativedelta(months=1)); E3 timezone crossing midnight (no timezone awareness, UTC server date); E4 end_date in the past on create (silently succeeds, never generates — ES6); E5 end_date == next_run_at today (generates, correct — end_date < today check); E6 task creation failure mid-batch (exception bubbles, template retries — ES11); E8 auto_create_task=False remains a compatibility-only field rather than a separate shipped mode (row 9); E9 inactive re-enable (FIXED — row 4); E10 run_now on inactive (FIXED — row 5); E11 run_now past end_date (FIXED — row 6); E12 owner SET_NULL (tested); E13 project/team SET_NULL (tested); E14 monthly timedelta(days=30) divergence (FIXED — row 7); E15 next_run_at=NULL never generates (tested). Full catalog in git history (2026-06-12 changelog entry).

12. Open Questions

  • Custom rrule is API-only — leave undocumented in UI until someone needs more than daily/weekly/monthly.

13. Bug / Change Log

2026-08-11 — Android recurring directory follows the active organization

Type: Tenant-state consistency bug fix (Android)

Summary:

  • Work Graph already rebuilt its summary model after an organization switch, but the Recurring destination reused one lazy process-lifetime model and could still show the previous tenant’s empty snapshot.
  • The Android route now creates the recurring model inside an organization-scoped remember, so switching organizations remounts and reloads the directory before it is compared with the new Work Graph count.
  • A focused Android host regression protects both the factory and route-key ownership. Sentry lumi-android had no matching exception at the 2026-08-11 pre-fix check; reporter-device runtime confirmation remains open.

2026-08-10 — Mobile recurring detail keeps its exact rhythm through refresh failure

Type: Recovery UX bug fix (shared Android/iOS)

Summary:

  • The detail model stored the last recurring template in LoadState.Error.last, but the UI treated every error as blocking; Retry used load() and could discard that stored snapshot.
  • Populated failures now keep the exact template facts and Edit/Delete/Run now actions visible behind one compact one-line warning. Explicit Retry refreshes with that same snapshot, including across repeated failure. Initial empty failures remain blocking.
  • Focused shared tests and exact-artifact guarded Android flow 158 cover repeated exact-detail outage, explicit recovery, template custody, evidence retention, cleanup, and runtime shutdown. iOS, named-device, screen-reader-service, store, deployed, and production evidence remain open.

2026-08-10 — Mobile recurring refresh recovery preserves operating rhythms

Type: Recovery UX bug fix (shared Android/iOS)

Summary:

  • The model retained populated templates in LoadState.Error.last, but the list replaced them with a blocking error; Retry and create reconciliation then read only successful state.
  • Populated failures now keep templates and Run now actions visible behind one concise warning. Explicit Retry refreshes with the same snapshot, and create reconciliation appends to current-or-stale data. Initial empty failures remain blocking.
  • Focused shared tests and exact-artifact guarded Android flow 158 cover repeated outage, explicit recovery, template/frequency/active custody, evidence retention, cleanup, and runtime shutdown. iOS, named-device, screen-reader-service, store, deployed, and production evidence remain open.

2026-08-03 — Localize later-date labels in the recurring list (WRK-07.U3)

Type: Bug Fix (shared mobile presentation)

Summary:

  • Replaced the recurring list’s hard-coded English Jan–Dec table with the existing localized month resources and a translated month/day ordering resource.
  • English later dates now read July 14; Vietnamese reads 14 Tháng 7. Relative today/tomorrow, weekday labels, overdue urgency, and the detail fact table keep their existing behavior.

Reason:

  • The list localized weekday names but leaked English month abbreviations for dates at least 14 days away, so WRK-07/08 failed the universal language gate even when the account language was Vietnamese.

Scope boundary:

  • Source and shared-JVM proof close the hard-coded-copy defect. Named Android/iPhone rendering and PM score promotion remain open; this change does not claim device evidence.

Tests:

  • F06RecurringDetailDatePolicyTests.test_list_path_uses_localized_compact_date_format
  • RecurringCardMetaTest.short_date_uses_locale_owned_month_and_day_order
  • Guarded full :shared:jvmTest suite

2026-07-15 — Mobile: restore reachable recurring detail actions

Type: User-facing UX bug fix (shared KMP detail layout)

Summary:

  • LumiPageTitle fills available width by design. The recurring detail header omitted the established Modifier.weight(1f) constraint, so Edit and Delete were laid out beyond the right edge and users could not enter the edit form.
  • Weighted the title inside the action-bearing row and added a focused source invariant.
  • Exact-APK testing then exposed a second layer: the Android phone shell applied WindowInsets.safeDrawing to persistent bottom navigation. Because safeDrawing includes the IME, the bottom bar grew to keyboard height and collapsed the routed edit surface. The shell now reserves navigation-bar and display-cutout insets only and leaves Scaffold measurement while the IME is visible, placing the in-flow action at the form-owned keyboard edge.
  • Kept FRM-12 named-device keyboard proof explicit rather than inferring it from an emulator.

Tests:

  • RecurringDetailHeaderSourceTest
  • PhoneNavigationInsetsSourceTest
  • Exact-APK Android Maestro .maestro/critical/57_recurring_edit_keyboard.yaml

2026-07-14 — Mobile: keep Edit Recurring Save above the keyboard (FRM-12)

Type: UX fix (shared KMP form layout)

Summary:

  • Edit Recurring now uses a weighted scroll region with an in-flow Save CTA so the keyboard cannot cover the action.
  • Added stable screen/action tags and a source guard against bottom-overlay Save regressions.

Scope boundary:

  • Source hardening only. FRM-12 populated edit, validation, and named-device keyboard evidence remain PM-owned mobile-audit work.

Tests:

  • git diff --check
  • PYENV_VERSION=system python3 scripts/check_docs_truth.py
  • bash scripts/check-platform-parity.sh

2026-07-16 — Mobile: remove raw ISO dates from recurring detail (WRK-08.U7)

Type: Bug Fix (mobile presentation)

Summary:

  • Recurring detail previously rendered next_run_at with take(10) for overdue and today states, leaking values such as 2026-07-14 beside an urgency badge.
  • The detail path now uses the localized shared absolute-date formatter for every parseable date while preserving the existing tomorrow-relative and malformed-input behavior.

Reason:

  • The earlier raw-date class sweep fixed the list and most detail surfaces, but its source invariant did not catch this local-variable render path. A device-caught U7 regression remained possible.

Impact:

  • Users see a human-readable date in recurring detail; no API or persistence behavior changes. PM must still re-verify WRK-08 in English and Vietnamese on named devices.

Tests:

  • RecurringCardMetaTest covers overdue, today, tomorrow, later, and malformed dates.
  • scripts/check-platform-parity.sh and the guarded shared JVM suite.

2026-07-13 — Contract Graph migration and recurring-task authorization boundary

Type: Contract and authorization traceability

Summary:

  • Added the canonical F06 CUE contract for the seven template CRUD/run-now endpoint surfaces and attached executable proof IDs for CRUD, invalid frequency, tenant isolation, generation, guards, and role boundaries.
  • Contract proof collection found that RecurringTaskViewSet applied only IsOrgMember to every action, allowing viewers to mutate templates and any active member to call manager-only run_now. Added IsRecurringWriter for template mutations and IsOrgManager for run_now.
  • Generation remains linked to the same contract through generate_recurring_tasks_for_date; custom rrule parsing, concurrent idempotency, scheduler liveness, and named runtime evidence remain gaps.

Impact: Viewer recurring-template access is now read-only; run_now requires effective manager capabilities; existing active non-viewer template writes remain allowed.

Tests: The Contract Graph verifier collects and passes all 10 F06 pytest proof records. F06 remains partial because the known scheduler/rrule/runtime gaps are explicit in contracts/features/F06.cue.

2026-07-02 — Documentation truth: §11 row 7 ⚠️ → ✅ (monthly relativedelta divergence fixed)

Type: Documentation Change

Summary:

  • §11 row 7 claimed run_now uses timedelta(days=30) while generate uses relativedelta(months=1) — a divergence that would cause incorrect next_run dates in February. Verified against live code: apps/work/tasks.py:53 and apps/work/views.py:907 both import dateutil.relativedelta.relativedelta and use relativedelta(months=1). The prior ⚠️ gap no longer applies. Row 7 upgraded to ✅; §11 summary updated from 9/12 to 10/12 match.
  • Edge Cases Catalog note updated to mark E2 and E14 as FIXED.

Reason:

  • grep -n "relativedelta\|timedelta.*30\|days=30\|months=1" on both files confirmed the fix is already shipped. The divergence was a code bug that was corrected at an earlier date; the spec was never updated.

Impact:

  • Documentation only. No backend, frontend, or mobile behavior changed.

Tests:

  • None (documentation change; code fix already merged).

2026-07-02 — Clarify auto_create_task as compatibility-only

Type: Documentation Change

Summary:

  • Corrected §4, §8, and §11 so F06 no longer overstates auto_create_task as an active behavioral switch.
  • Documented the real shipped contract: recurring templates always auto-materialize tasks when due; the stored boolean remains in the schema/DTO surface but the generator does not branch on it.
  • Reclassified audit row 9 from spec drift to an explicit compatibility note so the remaining gaps focus on real scheduler debt.

Reason:

  • generate_recurring_tasks_for_date() does not inspect auto_create_task, and the existing regression test already proves auto_create_task=False still generates a task.

Impact:

  • Documentation only. No backend, frontend, or mobile behavior changed.

Tests:

  • None (documentation change).

2026-06-19 — Add recurring-template Edit + Delete

Type: Feature (CRUD completion)

Summary: RecurringTaskViewSet (ModelViewSet) supported PATCH/DELETE but the UI had only create + run-now — no edit or delete, and no mutation hooks existed. Added useUpdateRecurring/useDeleteRecurring (new hooks/queries/useRecurring.ts), recurringApi.delete, and per-template Edit dialog + confirmation Delete in RecurringPage.tsx. i18n keys added to en/vi. See §6.

2026-06-18 — Visual QC: make recurring templates read like self-writing checklists

Type: UX Fix

Summary:

  • The Recurring Tasks page was technically correct but visually flat: the form controls were unlabeled in the visible UI, and each template card collapsed all schedule metadata into one washed-out line.
  • Added a short helper band that explains the core value, made Tần suất and Ngày bắt đầu visible labels in the create form, and restyled schedule metadata into clearer chips so each template reads like a checklist that writes itself.

Verification:

  • frontend/screenshots/08-recurring-tasks/01-recurring-tasks-list.png
  • frontend/screenshots/08-recurring-tasks/02-add-recurring-task-form.png
  • pnpm test -- src/test/RecurringPage.test.tsx
  • pnpm exec playwright test tests/e2e/screenshot-flow.spec.ts --project=chromium --reporter=list -g "08-recurring-tasks"
  • ./node_modules/.bin/tsc --noEmit

2026-06-18 — Visual QC: clarify recurring-task creation copy

Type: UX Fix

Summary:

  • The Vietnamese Recurring Tasks screenshot showed + Tạo định kỳ, a clipped CTA beside the page title Công việc định kỳ.
  • The CTA now says + Tạo công việc định kỳ, matching the page’s mental model: a task checklist that writes itself.

Verification:

  • frontend/screenshots/08-recurring-tasks/01-recurring-tasks-list.png
  • pnpm test -- src/test/RecurringPage.test.tsx
  • pnpm exec playwright test tests/e2e/screenshot-flow.spec.ts --project=chromium --reporter=list -g "08-recurring-tasks"

2026-06-15 — Frontend route namespaced

Frontend route namespaced: /recurring -> /work/recurring (app-wide IA restructure; API paths unchanged).

2026-06-14 — Spec restructured to TEMPLATE.md canonical order (§1–§13)

Type: Documentation Change

Summary:

  • §3 Competitive Positioning and §4 Success Metrics moved to §14/§15 appendix positions.
  • §5 Scope → §3, §6 Data Model → §4, §7 API → §5, §8 UI → §6, §9 Test Plan → §7, §10 Current Behavior → §8, §11 Error States → §9, §12 Diagrams → §10, §13 Implementation Audit → §11, §14 OQ → §12, §15 BL → §13.
  • Updated §11 row 3 reference: “closes §11 blocking gap” → “closes §9 blocking gap.”

Reason:

  • Align with TEMPLATE.md canonical section order per SP fleet restructuring pass.

Impact:

  • Spec authors and future implementation agents.

2026-06-13 — F06 run_now guards: inactive + past end_date (E10, E11)

Type: Bug Fix

Summary:

  • run_now now returns 400 for inactive templates (E10) and for templates past their end_date (E11).
  • Tests: TestF06RunNowGuards — test_run_now_inactive_returns_400, test_run_now_end_date_past_returns_400, regression guards.

2026-06-13 — Reactivation recomputes next_run_at from today (E9, a9b72cc0d)

Type: Bug Fix

Summary:

  • RecurringTask.save() detects is_active False→True and sets next_run_at = max(today, start_date).
  • Tests: TestF06Reactivation in test_red_r33_h.py.

2026-06-12 — Add Edge Cases (E1–E16) and Error States (ES1–ES11) to F06 spec

Type: Documentation Change

2026-06-10 — Wire Celery beat scheduler for recurring task generation

Type: Feature

Summary:

  • Added apps/work/tasks.py::generate_recurring_tasks (Celery shared task), registered hourly in CELERY_BEAT_SCHEDULE.
  • Added config/celery.py, worker/beat services in docker-compose.yml, make worker/make beat targets.
  • Closes §9 blocking gap: “a rhythm feature that depends on a human running a management command is a broken promise.”

14. Competitive Positioning (Appendix)

vs Trello (no recurrence)

Trello has no native recurring task functionality. Users must manually duplicate cards each cycle or install Butler power-up with per-board rules. Neither surfaces when a recurring task is missed — the CEO must remember to check. Lumi’s pulse-linked recurrence auto-generates tasks from a one-field template and surfaces skipped runs in the Brief without anyone asking.

vs ClickUp (over-engineered repetition)

ClickUp offers recurrence on every task with granular options (repeat every N days/weeks/months, skip weekends, end after X occurrences). This is feature-rich but requires a trained user to set up correctly. For an SME manager who wants “check receivables every Friday”, ClickUp’s UI is intimidating. Lumi asks for one frequency pick (daily/weekly/monthly) and one optional end date. The template hides all cron/RRULE complexity behind a simple “việc lặp lại” mental model.

vs Asana (timeline-only repetition)

Asana recurrence is tied to timeline/project schedules — it does not connect to daily check-ins or CEO signal extraction. Lumi’s recurring tasks are cross-project and cross-team: a “bi-monthly cash count” pattern generates tasks regardless of which project it belongs to, and the CEO sees the overdue generated task in the morning Brief alongside pulse-extracted risks.

Lumi differentiator: pulse-linked recurrence

Lumi is the only tool where a recurring task can be discovered from a pulse: a team member submits “cần nhắc khách công nợ mỗi tuần” and a manager converts it into a RecurringTask template with one click. The generated task becomes a first-class Task (source_type=recurring) with business impact, due date, and owner. The Brief does not distinguish between “task created by human” and “task created by recurrence template.” Missed runs surface identically.

15. Success Metrics (Appendix)

MetricTargetWhy it matters
Recurrence setup time<30s after task-like name entryOne field (template_title) + one frequency picker — no cron, no rrule
Auto-generation success rate>99% of scheduled runs create a taskGeneration is idempotent per (template, date); Celery beat fires hourly
CEO interaction rate>40% of generated recurring tasks viewed or acted on via BriefSilent generation is waste
Template activation toggle usage>60% of templates toggled at least once per quarterPause/resume is the primary management action
No-show generation alerts100% of missed generation cycles produce an alertCelery beat/worker downtime must be monitored
Pulse-to-template conversion rate>5% of relevant pulse entries become RecurringTask templatesSignal that recurring tasks are discovered from team input