signalk-maintenance-tracker 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,50 @@
1
+ # v1.5.0
2
+
3
+ The tracker learns the difference between maintenance you repeat and jobs you
4
+ do once: one-off **todo** items now live alongside recurring tasks.
5
+
6
+ ## Highlights
7
+
8
+ - **One-off todos.** Every task now carries an `is_recurring` flag. Recurring
9
+ tasks work as before (and must have at least one interval — runtime or
10
+ time); a todo has no schedule at all, just an optional due date. An open
11
+ todo wears a blue **Todo** badge, sorts above `ok` (it's actionable), and
12
+ raises no notification until its due date makes it due-soon or overdue.
13
+ Completing a todo archives it — unarchive to reopen it. A **New Todo**
14
+ button sits next to **New Task**, opening the same form with the new
15
+ "Recurring task" toggle off; toggling an existing task to todo clears its
16
+ schedule fields.
17
+ - **`info` is now `pending`.** The old catch-all `info` status split in two:
18
+ schedule-less tasks became todos, and `pending` now means exactly one
19
+ thing — a recurring task that can't compute its status yet (an interval
20
+ with no completion ever logged, or a runtime path with no reading seen).
21
+ Sort order is `overdue → due_soon → todo → ok → pending → archived`.
22
+
23
+ ## Smaller changes and fixes
24
+
25
+ - Modals grow to 800px wide on displays 900px and up, so long forms like the
26
+ task editor stop feeling cramped; narrow displays keep the 400px layout.
27
+ - Form errors now scroll into view when they appear. On a long form the error
28
+ banner could sit above the fold, making a failed save look like a dead
29
+ button. All five modals share the new error component, which also announces
30
+ itself to screen readers.
31
+ - Paired form fields (interval, warning window, seed values) stack into a
32
+ single column on displays 640px and narrower instead of squeezing
33
+ side by side.
34
+
35
+ ## Migration notes
36
+
37
+ - Migration 7 backfills the flag: tasks with a runtime or time interval stay
38
+ recurring, everything else becomes a todo (with any runtime path/warning
39
+ cleared). If one of those was really a recurring "tracker" task, give it an
40
+ interval — or just unarchive it whenever the work comes around again.
41
+ - **API:** the `info` status value is gone; `todo` and `pending` are new
42
+ (`?status=info` bookmarks and filters need updating). `is_recurring` is a
43
+ boolean on task bodies; omitted on create, it is inferred from whether an
44
+ interval is present, so pre-v1.5 API calls keep working. Todos reject
45
+ intervals/runtime paths, recurring tasks reject having neither interval
46
+ (`400 invalid_recurring`).
47
+
1
48
  # v1.4.0
2
49
 
3
50
  Retired gear can now retire its tasks too, and the task list is quicker to
@@ -85,6 +85,12 @@ function mountApi(router, getServices) {
85
85
  const entry = await s.service.addLog(req.params.slug, req.body ?? {}, (0, auth_1.getRequestUser)(req), forwardableHeaders(req));
86
86
  res.status(201).json(entry);
87
87
  }));
88
+ router.post('/api/logs', withServices((s, req, res) => {
89
+ // standalone (non-task) entry; logged_by from the principal (§9.1)
90
+ res
91
+ .status(201)
92
+ .json(s.service.addStandaloneLog(req.body ?? {}, (0, auth_1.getRequestUser)(req)));
93
+ }));
88
94
  router.get('/api/logs', withServices((s, req, res) => {
89
95
  const { page, pageSize } = parsePaging(req);
90
96
  const sort = pickEnum(req.query.sort, [
@@ -163,7 +169,7 @@ function parseTaskListQuery(req) {
163
169
  return {
164
170
  search: str(req.query.search),
165
171
  tags: csv(req.query.tags),
166
- status: csv(req.query.status)?.filter((s) => ['overdue', 'due_soon', 'ok', 'info', 'archived'].includes(s)),
172
+ status: csv(req.query.status)?.filter((s) => ['overdue', 'due_soon', 'todo', 'ok', 'pending', 'archived'].includes(s)),
167
173
  sort: pickEnum(req.query.sort, [
168
174
  'name',
169
175
  'remaining_runtime',
@@ -1 +1 @@
1
- {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/api/router.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/api/router.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LogsRepo = void 0;
4
- const LOG_COLUMNS = `l.id, l.task_id, l.maintenance_date, l.runtime_hours, l.notes,
4
+ const LOG_COLUMNS = `l.id, l.task_id, l.title, l.maintenance_date, l.runtime_hours, l.notes,
5
5
  l.logged_by, l.created_at`;
6
6
  class LogsRepo {
7
7
  db;
@@ -10,9 +10,9 @@ class LogsRepo {
10
10
  }
11
11
  insert(entry, nowIso) {
12
12
  const result = this.db
13
- .prepare(`INSERT INTO log_entries (task_id, maintenance_date, runtime_hours, notes, logged_by, created_at)
14
- VALUES (?, ?, ?, ?, ?, ?)`)
15
- .run(entry.task_id, entry.maintenance_date, entry.runtime_hours, entry.notes, entry.logged_by, nowIso);
13
+ .prepare(`INSERT INTO log_entries (task_id, title, maintenance_date, runtime_hours, notes, logged_by, created_at)
14
+ VALUES (?, ?, ?, ?, ?, ?, ?)`)
15
+ .run(entry.task_id, entry.title, entry.maintenance_date, entry.runtime_hours, entry.notes, entry.logged_by, nowIso);
16
16
  return this.get(Number(result.lastInsertRowid));
17
17
  }
18
18
  get(id) {
@@ -22,8 +22,8 @@ class LogsRepo {
22
22
  }
23
23
  update(id, fields) {
24
24
  this.db
25
- .prepare(`UPDATE log_entries SET maintenance_date = ?, runtime_hours = ?, notes = ? WHERE id = ?`)
26
- .run(fields.maintenance_date, fields.runtime_hours, fields.notes, id);
25
+ .prepare(`UPDATE log_entries SET title = ?, maintenance_date = ?, runtime_hours = ?, notes = ? WHERE id = ?`)
26
+ .run(fields.title, fields.maintenance_date, fields.runtime_hours, fields.notes, id);
27
27
  }
28
28
  delete(id) {
29
29
  this.db.prepare(`DELETE FROM log_entries WHERE id = ?`).run(id);
@@ -43,7 +43,8 @@ class LogsRepo {
43
43
  /** Task ids that have at least one log note matching the LIKE pattern. */
44
44
  taskIdsWithNotesLike(pattern) {
45
45
  const rows = this.db
46
- .prepare(`SELECT DISTINCT task_id AS id FROM log_entries WHERE notes LIKE ?`)
46
+ .prepare(`SELECT DISTINCT task_id AS id FROM log_entries
47
+ WHERE task_id IS NOT NULL AND notes LIKE ?`)
47
48
  .all(pattern);
48
49
  return new Set(rows.map((r) => r.id));
49
50
  }
@@ -60,26 +61,28 @@ class LogsRepo {
60
61
  return row.n;
61
62
  }
62
63
  listMaster(q) {
64
+ // LEFT JOIN: standalone entries have no task row — they list under their
65
+ // own title, which stands in for the task name in search and sort too.
63
66
  const where = [];
64
67
  const params = [];
65
68
  if (q.search) {
66
69
  const like = `%${q.search}%`;
67
- where.push(`(l.notes LIKE ? OR t.name LIKE ? OR l.logged_by LIKE ?)`);
68
- params.push(like, like, like);
70
+ where.push(`(l.notes LIKE ? OR t.name LIKE ? OR l.title LIKE ? OR l.logged_by LIKE ?)`);
71
+ params.push(like, like, like, like);
69
72
  }
70
73
  const whereSql = where.length ? `WHERE ${where.join(' AND ')}` : '';
71
74
  const sortCol = q.sort === 'task'
72
- ? 't.name COLLATE NOCASE'
75
+ ? 'COALESCE(t.name, l.title) COLLATE NOCASE'
73
76
  : q.sort === 'runtime_hours'
74
77
  ? 'l.runtime_hours'
75
78
  : 'l.maintenance_date';
76
79
  const orderSql = q.order === 'asc' ? 'ASC' : 'DESC';
77
80
  const totalRow = this.db
78
- .prepare(`SELECT COUNT(*) AS n FROM log_entries l JOIN tasks t ON t.id = l.task_id ${whereSql}`)
81
+ .prepare(`SELECT COUNT(*) AS n FROM log_entries l LEFT JOIN tasks t ON t.id = l.task_id ${whereSql}`)
79
82
  .get(...params);
80
83
  const data = this.db
81
84
  .prepare(`SELECT ${LOG_COLUMNS}, t.slug AS task_slug, t.name AS task_name
82
- FROM log_entries l JOIN tasks t ON t.id = l.task_id
85
+ FROM log_entries l LEFT JOIN tasks t ON t.id = l.task_id
83
86
  ${whereSql}
84
87
  ORDER BY ${sortCol} ${orderSql}, l.id DESC
85
88
  LIMIT ? OFFSET ?`)
@@ -1 +1 @@
1
- {"version":3,"file":"logs.repo.js","sourceRoot":"","sources":["../../src/db/logs.repo.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"logs.repo.js","sourceRoot":"","sources":["../../src/db/logs.repo.ts"],"names":[],"mappings":"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"}
@@ -108,5 +108,51 @@ exports.migrations = [
108
108
  db.exec(`ALTER TABLE tasks ADD COLUMN is_archived INTEGER NOT NULL DEFAULT 0;`);
109
109
  },
110
110
  },
111
+ {
112
+ version: 6,
113
+ up(db) {
114
+ // Non-task ("standalone") log entries: work worth recording that isn't
115
+ // tied to any maintenance task. They carry a display title instead of a
116
+ // task_id — exactly one of the two is set, per the CHECK. SQLite can't
117
+ // drop NOT NULL in place, so rebuild the table; ids are preserved and
118
+ // the rename carries the AUTOINCREMENT sequence along.
119
+ db.exec(`
120
+ CREATE TABLE log_entries_new (
121
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
122
+ task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
123
+ title TEXT,
124
+ maintenance_date TEXT NOT NULL,
125
+ runtime_hours REAL,
126
+ notes TEXT,
127
+ logged_by TEXT,
128
+ created_at TEXT NOT NULL,
129
+ CHECK ((task_id IS NULL) <> (title IS NULL))
130
+ );
131
+ INSERT INTO log_entries_new
132
+ (id, task_id, maintenance_date, runtime_hours, notes, logged_by, created_at)
133
+ SELECT id, task_id, maintenance_date, runtime_hours, notes, logged_by, created_at
134
+ FROM log_entries;
135
+ DROP TABLE log_entries;
136
+ ALTER TABLE log_entries_new RENAME TO log_entries;
137
+ CREATE INDEX idx_log_task_date ON log_entries (task_id, maintenance_date DESC);
138
+ `);
139
+ },
140
+ },
141
+ {
142
+ version: 7,
143
+ up(db) {
144
+ // v1.5: one-off "todo" items vs recurring maintenance tasks. Existing
145
+ // tasks keeping any interval stay recurring; the rest become todos.
146
+ // A todo carries no runtime tracking, so the runtime path (and its
147
+ // warning override) is cleared on demoted rows — the service layer
148
+ // enforces the same invariant on every write.
149
+ db.exec(`ALTER TABLE tasks ADD COLUMN is_recurring INTEGER NOT NULL DEFAULT 1;`);
150
+ db.exec(`
151
+ UPDATE tasks
152
+ SET is_recurring = 0, runtime_path = NULL, runtime_warning_hours = NULL
153
+ WHERE runtime_interval IS NULL AND time_interval IS NULL;
154
+ `);
155
+ },
156
+ },
111
157
  ];
112
158
  //# sourceMappingURL=migrations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":";;;AAOa,QAAA,UAAU,GAAgB;IACrC;QACE,OAAO,EAAE,CAAC;QACV,EAAE,CAAC,EAAE;YACH,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6CP,CAAC,CAAC;QACL,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,EAAE,CAAC,EAAE;YACH,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;OAmBP,CAAC,CAAC;QACL,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,EAAE,CAAC,EAAE;YACH,qEAAqE;YACrE,yEAAyE;YACzE,8CAA8C;YAC9C,EAAE,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;QACzD,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,EAAE,CAAC,EAAE;YACH,uEAAuE;YACvE,yEAAyE;YACzE,mEAAmE;YACnE,wEAAwE;YACxE,wEAAwE;YACxE,EAAE,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;YACpE,EAAE,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;QAClE,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,EAAE,CAAC,EAAE;YACH,mEAAmE;YACnE,mEAAmE;YACnE,kEAAkE;YAClE,EAAE,CAAC,IAAI,CACL,sEAAsE,CACvE,CAAC;QACJ,CAAC;KACF;CACF,CAAC"}
1
+ {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":";;;AAOa,QAAA,UAAU,GAAgB;IACrC;QACE,OAAO,EAAE,CAAC;QACV,EAAE,CAAC,EAAE;YACH,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6CP,CAAC,CAAC;QACL,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,EAAE,CAAC,EAAE;YACH,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;OAmBP,CAAC,CAAC;QACL,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,EAAE,CAAC,EAAE;YACH,qEAAqE;YACrE,yEAAyE;YACzE,8CAA8C;YAC9C,EAAE,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;QACzD,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,EAAE,CAAC,EAAE;YACH,uEAAuE;YACvE,yEAAyE;YACzE,mEAAmE;YACnE,wEAAwE;YACxE,wEAAwE;YACxE,EAAE,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;YACpE,EAAE,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;QAClE,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,EAAE,CAAC,EAAE;YACH,mEAAmE;YACnE,mEAAmE;YACnE,kEAAkE;YAClE,EAAE,CAAC,IAAI,CACL,sEAAsE,CACvE,CAAC;QACJ,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,EAAE,CAAC,EAAE;YACH,uEAAuE;YACvE,wEAAwE;YACxE,uEAAuE;YACvE,sEAAsE;YACtE,uDAAuD;YACvD,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;OAmBP,CAAC,CAAC;QACL,CAAC;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,EAAE,CAAC,EAAE;YACH,sEAAsE;YACtE,oEAAoE;YACpE,mEAAmE;YACnE,mEAAmE;YACnE,8CAA8C;YAC9C,EAAE,CAAC,IAAI,CACL,uEAAuE,CACxE,CAAC;YACF,EAAE,CAAC,IAAI,CAAC;;;;OAIP,CAAC,CAAC;QACL,CAAC;KACF;CACF,CAAC"}
@@ -4,7 +4,8 @@ exports.TasksRepo = void 0;
4
4
  const COLUMNS = `id, slug, name, description, runtime_interval, time_interval,
5
5
  time_interval_unit, runtime_path, due_date, runtime_warning_hours,
6
6
  time_warning_days, last_maintenance, last_runtime,
7
- seed_last_maintenance, seed_last_runtime, is_archived, created_at, updated_at`;
7
+ seed_last_maintenance, seed_last_runtime, is_archived, is_recurring,
8
+ created_at, updated_at`;
8
9
  class TasksRepo {
9
10
  db;
10
11
  constructor(db) {
@@ -15,9 +16,10 @@ class TasksRepo {
15
16
  .prepare(`INSERT INTO tasks (slug, name, description, runtime_interval, time_interval,
16
17
  time_interval_unit, runtime_path, due_date, runtime_warning_hours,
17
18
  time_warning_days, last_maintenance, last_runtime,
18
- seed_last_maintenance, seed_last_runtime, is_archived, created_at, updated_at)
19
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
20
- .run(t.slug, t.name, t.description, t.runtime_interval, t.time_interval, t.time_interval_unit, t.runtime_path, t.due_date, t.runtime_warning_hours, t.time_warning_days, t.last_maintenance, t.last_runtime, t.seed_last_maintenance, t.seed_last_runtime, t.is_archived, nowIso, nowIso);
19
+ seed_last_maintenance, seed_last_runtime, is_archived, is_recurring,
20
+ created_at, updated_at)
21
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
22
+ .run(t.slug, t.name, t.description, t.runtime_interval, t.time_interval, t.time_interval_unit, t.runtime_path, t.due_date, t.runtime_warning_hours, t.time_warning_days, t.last_maintenance, t.last_runtime, t.seed_last_maintenance, t.seed_last_runtime, t.is_archived, t.is_recurring, nowIso, nowIso);
21
23
  return this.getById(Number(result.lastInsertRowid));
22
24
  }
23
25
  getById(id) {
@@ -54,9 +56,9 @@ class TasksRepo {
54
56
  due_date = ?, runtime_warning_hours = ?, time_warning_days = ?,
55
57
  last_maintenance = ?, last_runtime = ?,
56
58
  seed_last_maintenance = ?, seed_last_runtime = ?, is_archived = ?,
57
- updated_at = ?
59
+ is_recurring = ?, updated_at = ?
58
60
  WHERE id = ?`)
59
- .run(t.slug, t.name, t.description, t.runtime_interval, t.time_interval, t.time_interval_unit, t.runtime_path, t.due_date, t.runtime_warning_hours, t.time_warning_days, t.last_maintenance, t.last_runtime, t.seed_last_maintenance, t.seed_last_runtime, t.is_archived, nowIso, id);
61
+ .run(t.slug, t.name, t.description, t.runtime_interval, t.time_interval, t.time_interval_unit, t.runtime_path, t.due_date, t.runtime_warning_hours, t.time_warning_days, t.last_maintenance, t.last_runtime, t.seed_last_maintenance, t.seed_last_runtime, t.is_archived, t.is_recurring, nowIso, id);
60
62
  }
61
63
  setLast(id, lastMaintenance, lastRuntime) {
62
64
  this.db
@@ -67,6 +69,12 @@ class TasksRepo {
67
69
  clearDueDate(id) {
68
70
  this.db.prepare(`UPDATE tasks SET due_date = NULL WHERE id = ?`).run(id);
69
71
  }
72
+ /** Flip the archived flag (a completed todo archives itself). */
73
+ setArchived(id, archived) {
74
+ this.db
75
+ .prepare(`UPDATE tasks SET is_archived = ? WHERE id = ?`)
76
+ .run(archived, id);
77
+ }
70
78
  delete(id) {
71
79
  this.db.prepare(`DELETE FROM tasks WHERE id = ?`).run(id);
72
80
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tasks.repo.js","sourceRoot":"","sources":["../../src/db/tasks.repo.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tasks.repo.js","sourceRoot":"","sources":["../../src/db/tasks.repo.ts"],"names":[],"mappings":"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"}
@@ -35,7 +35,7 @@ function addMonths(d, n) {
35
35
  }
36
36
  function mostUrgent(statuses) {
37
37
  if (statuses.length === 0)
38
- return 'info';
38
+ return 'pending';
39
39
  return statuses.reduce((a, b) => (types_1.STATUS_RANK[a] <= types_1.STATUS_RANK[b] ? a : b));
40
40
  }
41
41
  /**
@@ -65,8 +65,8 @@ function computeTask(task, currentRuntime, now, cfg) {
65
65
  remaining_time_ms: null,
66
66
  time_fraction: null,
67
67
  time_status: null,
68
- status: 'info',
69
- status_rank: types_1.STATUS_RANK.info,
68
+ status: 'pending',
69
+ status_rank: types_1.STATUS_RANK.pending,
70
70
  urgency: -Infinity,
71
71
  };
72
72
  // Elapsed-since-last-service figures stand on their own: they need only a
@@ -95,7 +95,7 @@ function computeTask(task, currentRuntime, now, cfg) {
95
95
  : 'ok';
96
96
  }
97
97
  else {
98
- out.runtime_status = 'info';
98
+ out.runtime_status = 'pending';
99
99
  }
100
100
  }
101
101
  // Recurring time-interval dimension: configured when magnitude + unit are set.
@@ -110,7 +110,7 @@ function computeTask(task, currentRuntime, now, cfg) {
110
110
  out.scheduled_status = dateStatus(out.scheduled_remaining_ms, timeLead);
111
111
  }
112
112
  else {
113
- out.scheduled_status = 'info';
113
+ out.scheduled_status = 'pending';
114
114
  }
115
115
  }
116
116
  // One-time due-date dimension: the stored deadline. Fraction runs from task
@@ -126,7 +126,7 @@ function computeTask(task, currentRuntime, now, cfg) {
126
126
  // Merged "time" dimension = the more urgent of the two: whichever has the
127
127
  // lower remaining drives remaining_time_ms + time_fraction; time_status is
128
128
  // the most-urgent of the sub-statuses (so a configured-but-uncomputable
129
- // recurring dimension still surfaces as info).
129
+ // recurring dimension still surfaces as pending).
130
130
  const timeSubs = [
131
131
  { remaining: out.scheduled_remaining_ms, fraction: out.scheduled_fraction },
132
132
  { remaining: out.due_date_remaining_ms, fraction: out.due_date_fraction },
@@ -144,8 +144,23 @@ function computeTask(task, currentRuntime, now, cfg) {
144
144
  // Archived overrides whatever the dimensions say: the task keeps its
145
145
  // computed figures (the detail page still shows them) but its status —
146
146
  // what lists filter on, sort by, and notifications publish — is 'archived',
147
- // ranked after 'info' so archived tasks sink to the end of the default sort.
148
- out.status = task.is_archived ? 'archived' : mostUrgent(dims);
147
+ // ranked last so archived tasks sink to the end of the default sort.
148
+ //
149
+ // A non-recurring task (todo) only ever has the due-date dimension, and an
150
+ // open todo is actionable rather than healthy or unknown — so 'ok' (due
151
+ // date not close yet) and "no dimensions at all" both read as 'todo';
152
+ // overdue/due_soon pass through. Recurring tasks with no computable
153
+ // dimension read as 'pending' instead.
154
+ if (task.is_archived) {
155
+ out.status = 'archived';
156
+ }
157
+ else if (task.is_recurring === 0) {
158
+ const urgent = mostUrgent(dims);
159
+ out.status = urgent === 'ok' || urgent === 'pending' ? 'todo' : urgent;
160
+ }
161
+ else {
162
+ out.status = mostUrgent(dims);
163
+ }
149
164
  out.status_rank = types_1.STATUS_RANK[out.status];
150
165
  const fractions = [out.runtime_fraction, out.time_fraction].filter((f) => f != null);
151
166
  out.urgency = fractions.length ? Math.max(...fractions) : -Infinity;
@@ -1 +1 @@
1
- {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/domain/status.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/domain/status.ts"],"names":[],"mappings":"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"}
package/dist/service.js CHANGED
@@ -65,6 +65,7 @@ class MaintenanceService {
65
65
  last_maintenance: row.last_maintenance,
66
66
  last_runtime: row.last_runtime,
67
67
  is_archived: row.is_archived !== 0,
68
+ is_recurring: row.is_recurring !== 0,
68
69
  created_at: row.created_at,
69
70
  updated_at: row.updated_at,
70
71
  consumables: consumables.map((c) => ({
@@ -162,6 +163,16 @@ class MaintenanceService {
162
163
  throw new ApiError(400, 'invalid_name', 'Task name is required');
163
164
  this.validateIntervals(body.runtime_interval, body.time_interval, body.time_interval_unit);
164
165
  this.validateWarningWindows(body.runtime_warning_hours, body.time_warning_days);
166
+ // Omitted is_recurring is inferred from the schedule so pre-v1.5 API
167
+ // calls keep working: an interval makes it recurring, none makes it a
168
+ // todo. Explicit values are checked against the schedule instead.
169
+ const isRecurring = this.validateRecurring(body.is_recurring) ??
170
+ (body.runtime_interval != null || body.time_interval != null);
171
+ this.enforceRecurringInvariants(isRecurring, {
172
+ runtime_interval: body.runtime_interval ?? null,
173
+ time_interval: body.time_interval ?? null,
174
+ runtime_path: body.runtime_path?.trim() || null,
175
+ });
165
176
  let slug;
166
177
  if (body.slug != null && body.slug.trim() !== '') {
167
178
  slug = (0, slug_1.slugify)(body.slug);
@@ -188,6 +199,7 @@ class MaintenanceService {
188
199
  seed_last_maintenance: body.last_maintenance ?? null,
189
200
  seed_last_runtime: body.last_runtime ?? null,
190
201
  is_archived: this.validateArchived(body.is_archived) ? 1 : 0,
202
+ is_recurring: isRecurring ? 1 : 0,
191
203
  };
192
204
  const row = this.tasks.create(seed, nowIso);
193
205
  if (body.tags)
@@ -199,6 +211,7 @@ class MaintenanceService {
199
211
  }
200
212
  updateTask(slug, body) {
201
213
  const row = this.requireTask(slug);
214
+ const isRecurring = this.validateRecurring(body.is_recurring) ?? row.is_recurring !== 0;
202
215
  const merged = {
203
216
  slug: row.slug,
204
217
  name: body.name !== undefined ? (body.name ?? '').trim() : row.name,
@@ -233,11 +246,31 @@ class MaintenanceService {
233
246
  ? 1
234
247
  : 0
235
248
  : row.is_archived,
249
+ is_recurring: isRecurring ? 1 : 0,
236
250
  };
237
251
  if (!merged.name)
238
252
  throw new ApiError(400, 'invalid_name', 'Task name is required');
239
253
  this.validateIntervals(merged.runtime_interval, merged.time_interval, merged.time_interval_unit);
240
254
  this.validateWarningWindows(merged.runtime_warning_hours, merged.time_warning_days);
255
+ if (isRecurring) {
256
+ this.enforceRecurringInvariants(true, merged);
257
+ }
258
+ else {
259
+ // Explicitly scheduling a todo in the same request is a contradiction
260
+ // and rejected; a schedule the task already had (recurring → todo
261
+ // toggle) is simply cleared.
262
+ this.enforceRecurringInvariants(false, {
263
+ runtime_interval: body.runtime_interval ?? null,
264
+ time_interval: body.time_interval ?? null,
265
+ runtime_path: body.runtime_path !== undefined
266
+ ? body.runtime_path?.trim() || null
267
+ : null,
268
+ });
269
+ merged.runtime_interval = null;
270
+ merged.time_interval = null;
271
+ merged.time_interval_unit = null;
272
+ merged.runtime_path = null;
273
+ }
241
274
  // Slug change: normalize, uniqueness-check, remember old slug so the
242
275
  // notification path can be migrated (§6.4).
243
276
  let clearedSlug;
@@ -299,6 +332,7 @@ class MaintenanceService {
299
332
  try {
300
333
  entry = this.logs.insert({
301
334
  task_id: task.id,
335
+ title: null,
302
336
  maintenance_date: date,
303
337
  runtime_hours: body.runtime_hours ?? null,
304
338
  notes: body.notes ?? null,
@@ -310,6 +344,11 @@ class MaintenanceService {
310
344
  // next due date is set again by editing the task.)
311
345
  if (task.due_date != null)
312
346
  this.tasks.clearDueDate(task.id);
347
+ // Completing a one-off todo is what finishes it: archive in the same
348
+ // transaction so it drops out of the open lists immediately. Unarchive
349
+ // reopens it.
350
+ if (task.is_recurring === 0 && task.is_archived === 0)
351
+ this.tasks.setArchived(task.id, 1);
313
352
  this.db.exec('COMMIT');
314
353
  }
315
354
  catch (err) {
@@ -372,6 +411,26 @@ class MaintenanceService {
372
411
  }
373
412
  return warnings;
374
413
  }
414
+ /**
415
+ * Standalone (non-task) log entry: work worth recording that isn't tied to
416
+ * any maintenance task. It carries a title in place of a task, so there is
417
+ * no denormalization to recompute, no due date to clear, and no stock to
418
+ * consume.
419
+ */
420
+ addStandaloneLog(body, loggedBy) {
421
+ const title = this.validateTitle(body.title);
422
+ const date = this.validateDate(body.maintenance_date, 'maintenance_date');
423
+ const entry = this.logs.insert({
424
+ task_id: null,
425
+ title,
426
+ maintenance_date: date,
427
+ runtime_hours: body.runtime_hours ?? null,
428
+ notes: body.notes ?? null,
429
+ logged_by: loggedBy,
430
+ }, this.now().toISOString());
431
+ this.emit();
432
+ return this.redactLog(entry);
433
+ }
375
434
  updateLog(id, body) {
376
435
  const existing = this.logs.get(id);
377
436
  if (!existing)
@@ -379,16 +438,23 @@ class MaintenanceService {
379
438
  const date = body.maintenance_date !== undefined
380
439
  ? this.validateDate(body.maintenance_date, 'maintenance_date')
381
440
  : existing.maintenance_date;
441
+ // Only standalone entries own a title — on a task-linked entry the field
442
+ // stays null (CHECK constraint) and any title in the body is ignored.
443
+ const title = existing.task_id === null && body.title !== undefined
444
+ ? this.validateTitle(body.title)
445
+ : existing.title;
382
446
  this.db.exec('BEGIN');
383
447
  try {
384
448
  this.logs.update(id, {
449
+ title,
385
450
  maintenance_date: date,
386
451
  runtime_hours: body.runtime_hours !== undefined
387
452
  ? body.runtime_hours
388
453
  : existing.runtime_hours,
389
454
  notes: body.notes !== undefined ? body.notes : existing.notes,
390
455
  });
391
- this.recomputeDenorm(existing.task_id);
456
+ if (existing.task_id !== null)
457
+ this.recomputeDenorm(existing.task_id);
392
458
  this.db.exec('COMMIT');
393
459
  }
394
460
  catch (err) {
@@ -405,7 +471,8 @@ class MaintenanceService {
405
471
  this.db.exec('BEGIN');
406
472
  try {
407
473
  this.logs.delete(id);
408
- this.recomputeDenorm(existing.task_id);
474
+ if (existing.task_id !== null)
475
+ this.recomputeDenorm(existing.task_id);
409
476
  this.db.exec('COMMIT');
410
477
  }
411
478
  catch (err) {
@@ -503,6 +570,33 @@ class MaintenanceService {
503
570
  check(runtimeWarningHours, 'runtime_warning_hours');
504
571
  check(timeWarningDays, 'time_warning_days');
505
572
  }
573
+ /** is_recurring must be a real boolean when present; null/undefined =
574
+ * "not provided" — the caller infers (create) or keeps the row's value
575
+ * (update). */
576
+ validateRecurring(value) {
577
+ if (value == null)
578
+ return undefined;
579
+ if (typeof value !== 'boolean')
580
+ throw new ApiError(400, 'invalid_recurring', 'is_recurring must be a boolean');
581
+ return value;
582
+ }
583
+ /**
584
+ * The recurring/todo invariant (§6.3): a recurring task must have at least
585
+ * one interval to ever come due; a todo has no schedule at all — no
586
+ * intervals and no runtime path (its only date dimension is the optional
587
+ * one-time due_date).
588
+ */
589
+ enforceRecurringInvariants(isRecurring, fields) {
590
+ if (isRecurring) {
591
+ if (fields.runtime_interval == null && fields.time_interval == null)
592
+ throw new ApiError(400, 'invalid_recurring', 'A recurring task needs a runtime or time interval');
593
+ }
594
+ else if (fields.runtime_interval != null ||
595
+ fields.time_interval != null ||
596
+ fields.runtime_path != null) {
597
+ throw new ApiError(400, 'invalid_recurring', 'A todo cannot have intervals or a runtime path — set is_recurring to true to schedule it');
598
+ }
599
+ }
506
600
  /** is_archived must be a real boolean when present; null/undefined = false
507
601
  * (create) or "leave unchanged" (update, handled at the call site). */
508
602
  validateArchived(value) {
@@ -522,6 +616,13 @@ class MaintenanceService {
522
616
  return null;
523
617
  return this.validateDate(value, 'due_date');
524
618
  }
619
+ /** A standalone log entry's title is its whole identity — required. */
620
+ validateTitle(value) {
621
+ const title = typeof value === 'string' ? value.trim() : '';
622
+ if (!title)
623
+ throw new ApiError(400, 'invalid_title', 'title is required for a non-task log entry');
624
+ return title;
625
+ }
525
626
  validateDate(value, field) {
526
627
  if (!value || Number.isNaN(new Date(value).getTime()))
527
628
  throw new ApiError(400, 'invalid_date', `${field} must be a valid ISO-8601 timestamp`);