Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions packages/todo/CHARTER.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,27 @@ It is the canonical *"hello world that isn't a toy"* for ObjectStack.

| Capability | How |
|---|---|
| Multi-object schema with relationships | `project` → `task`, `task` ↔ `label` (junction) |
| Object + lookup relationship | `task` with a multi-value `label` lookup |
| State machine | `task.status`: `todo → doing → done` (+ `cancelled`) |
| Validation rules | due-date sanity, sub-task ownership |
| Workflow / flow | overdue notification, status-change side effects |
| Approval | high-priority tasks require lead approval |
| Sharing rule | project-level row sharing |
| Cube + Report | task throughput, overdue tasks |
| Validation rules | due-date sanity; urgent tasks require a due date |
| Workflow / flow | overdue notification (delta-guarded), assignment notification |
| Dashboard | "My work" landing page |
| Pages & Views | list, kanban, record page with related lists |
| Permission set | `contributor` vs `lead` |
| i18n | English (single locale; this is a starter, fork to add) |
| Seed data | one demo project, a few tasks, two labels |
| i18n | Multi-locale: English + 简体中文 + 日本語 + Español |
| Seed data | a set of demo tasks across states, plus labels |

> **Roadmap (not yet shipped):** a `project` grouping object, a `task_label`
> junction, an approval process for urgent tasks, and a project-scoped sharing
> rule. The schema and caps leave room for them; they are intentionally absent
> from the minimal starter. Earlier charter drafts described them as shipped —
> they are not.

## What it deliberately does NOT do

- **No re-implementation of platform objects.** Comments, attachments, activity feed, users, teams, roles, audit logs, notifications, emails — all come from `sys_*` objects shipped by the platform. The template *uses* them via the polymorphic pattern (`parent_object` / `parent_id` or `thread_id = "{object}:{id}"`).
- **No tutorials.** The template assumes you already read the docs. Comments in code stay short.
- **No fictional "advanced" features** that aren't in `@objectstack/spec@5.2`. If the spec doesn't validate it, it doesn't ship.
- **No second locale.** Users fork-and-localize; multi-locale is demonstrated by `hotcrm`.
- **No fictional "advanced" features** that aren't in the spec. If the spec doesn't validate it, it doesn't ship.
- **No fake AI flair.** Where AI helps a real workflow (e.g. summarise an overdue task), it appears as an `*.action.ts`. No "AI assistant" shells.

## Hard limits
Expand All @@ -44,13 +46,13 @@ These exist so the template stays a **template**, not a half-finished product:

| Metric | Cap | Rationale |
|---|---|---|
| Business objects | ≤ 4 (`project`, `task`, `label`, `task_label`) | Above this and it becomes a domain app |
| Business objects | ≤ 4 (`task`, `label` ship; `project` + `task_label` are roadmap, within cap) | Above this and it becomes a domain app |
| Total `.ts` LOC under `src/` | ≤ 2,500 | Readable in one sitting |
| Locales | 1 (`en`) | Forkable starting point |
| Locales | 4 (`en`, `zh-CN`, `ja-JP`, `es-ES`) | Demonstrates the i18n bundle layout |
| Dashboards | ≤ 2 | Demonstrates, doesn't overwhelm |
| Flows | ≤ 3 | Same |
| Approval processes | 1 | Same |
| Sharing rules | 1 | Same |
| Approval processes | ≤ 1 | Roadmap (none ship today) |
| Sharing rules | ≤ 1 | Roadmap (none ship today) |
| Permission sets | 2 | `contributor` + `lead` |

If you find yourself exceeding any of these, the right move is to **fork into a new template** (e.g. `helpdesk`), not bloat `todo`.
Expand Down
19 changes: 15 additions & 4 deletions packages/todo/src/flows/task_overdue.flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ import type * as Automation from '@objectstack/spec/automation';
type Flow = Automation.Flow;

/**
* Overdue notification — fires when a task crosses its due date while still
* open. Sends an in-app notification + email to the assignee via the
* platform's notification/email services (no per-app email infra).
* Overdue notification — fires when a task *enters* the overdue-and-open state
* (a past due date is set, or an overdue task is reopened to todo/doing). The
* `previous.*` delta guard makes it fire ONCE on that transition instead of on
* every subsequent edit of an already-overdue task — otherwise any field change
* re-spams the assignee.
*
* Limitation: a task that crosses its due date purely by time passing produces
* no update event, so this event-driven flow won't catch it. For unattended
* "it's now overdue" detection, fork this into a scheduled flow that scans open
* past-due tasks daily. Sends an in-app notification + email via the platform's
* notification/email services (no per-app email infra).
*/
export const TaskOverdueFlow: Flow = {
name: 'todo_task_overdue_notify',
Expand All @@ -24,7 +32,10 @@ export const TaskOverdueFlow: Flow = {
config: {
objectName: 'todo_task',
triggerType: 'record-after-update',
condition: "record.due_date < today() && record.status in ['todo', 'doing']",
// Fire only on the transition INTO overdue-and-open (delta guard),
// not on every edit of an already-overdue task.
condition:
"record.due_date != null && record.due_date < today() && record.status in ['todo', 'doing'] && (previous.due_date == null || previous.due_date >= today() || !(previous.status in ['todo', 'doing']))",
},
},
{
Expand Down
9 changes: 9 additions & 0 deletions packages/todo/src/objects/todo_task.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,15 @@ export const Task = ObjectSchema.create({
message: 'completed_at is set automatically when status becomes done.',
condition: P`record.status != "done" && record.completed_at != null`,
},
{
// Urgent work needs a deadline. (The `due_date_required_for_urgent`
// message already shipped in translations; this is the missing rule.)
name: 'due_date_required_for_urgent',
type: 'script',
severity: 'error',
message: 'Urgent tasks must have a due date.',
condition: P`record.priority == "urgent" && record.due_date == null && record.status != "done" && record.status != "cancelled"`,
},
],

// `started_at` / `completed_at` are stamped by `todo_task.hook.ts` on
Expand Down
4 changes: 0 additions & 4 deletions packages/todo/src/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,6 @@ export const en: TranslationData = {
title: 'Done This Week',
description: 'Tasks you completed since the start of the week',
},
recent_overdue_list: {
title: 'Overdue Tasks',
description: 'Your open tasks past their due date, sorted by oldest first',
},
},
},
},
Expand Down
4 changes: 0 additions & 4 deletions packages/todo/src/translations/es-ES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,6 @@ export const esES: TranslationData = {
title: 'Completadas esta semana',
description: 'Tareas completadas desde el inicio de la semana',
},
recent_overdue_list: {
title: 'Tareas vencidas',
description: 'Tus tareas vencidas ordenadas de más antigua a más reciente',
},
},
},
},
Expand Down
4 changes: 0 additions & 4 deletions packages/todo/src/translations/ja-JP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,6 @@ export const jaJP: TranslationData = {
},
my_overdue: { title: '期限超過', description: '期限を過ぎた自分の未完了タスク' },
done_this_week: { title: '今週の完了', description: '今週の始めから完了したタスク' },
recent_overdue_list: {
title: '期限超過タスク',
description: '古い順にソートされた期限超過タスク',
},
},
},
},
Expand Down
1 change: 0 additions & 1 deletion packages/todo/src/translations/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ export const zhCN: TranslationData = {
my_open_tasks: { title: '我的进行中任务', description: '分配给你的待办或进行中任务' },
my_overdue: { title: '逾期', description: '已超过截止日期的进行中任务' },
done_this_week: { title: '本周完成', description: '本周以来你完成的任务' },
recent_overdue_list: { title: '逾期任务', description: '按最久优先排序的逾期任务列表' },
},
},
},
Expand Down
Loading