| 20 |
20 |
|
- `--db` defaults to the desktop app's database (`com.goingson.app`'s
|
| 21 |
21 |
|
`app_data_dir`). The database must already exist; run GoingsOn once to create
|
| 22 |
22 |
|
the schema and the single desktop user.
|
| 23 |
|
- |
- Reads (`list_projects`, `list_tasks`, `get_task`) are always callable.
|
|
23 |
+ |
- Reads (`list_projects`, `list_tasks`, `get_task`, `list_events`, `get_event`)
|
|
24 |
+ |
are always callable.
|
| 24 |
25 |
|
- Writes are refused unless their capability is granted. Grant them with
|
| 25 |
26 |
|
`--grant go.task.bulk_import` (repeatable) or `--grant-all` for a fully-trusted
|
| 26 |
27 |
|
local session.
|
| 38 |
39 |
|
row marked `truncated`; `get_task` has the full text. The reply carries
|
| 39 |
40 |
|
`total` and, while pages remain, `next_offset`. Walk until it is absent.
|
| 40 |
41 |
|
- `get_task(id)`: one task with subtasks and annotations.
|
|
42 |
+ |
- `list_events(from?, to?, project?, recurring_only?, expand?, limit?, offset?)`:
|
|
43 |
+ |
calendar events in a window, paged on the same terms as `list_tasks`. Defaults
|
|
44 |
+ |
to the next 30 days. See "Events and recurrence" below for what `expand` does.
|
|
45 |
+ |
- `get_event(id)`: one event with its full description and recurrence rule.
|
| 41 |
46 |
|
- `list_problems(source?, status?, project_id?)`: problems ranked by
|
| 42 |
47 |
|
`painhours`, most urgent first. Defaults to `Open`; pass `status: "all"` for
|
| 43 |
48 |
|
settled ones too.
|
| 55 |
60 |
|
| `report_problems([...], source?)`, the `/audit` and `/fuzz` primitive | `go.problem.report` |
|
| 56 |
61 |
|
| `promote_problem(id, description?, priority?)`, problem to task | `go.problem.promote` |
|
| 57 |
62 |
|
| `update_problem(id, {status?, project?})`, triage state and attribution | `go.problem.update` |
|
|
63 |
+ |
| `create_event({title, start, end?, all_day?, project?, location?, recurrence?, block_type?, reminders?})` | `go.event.create` |
|
|
64 |
+ |
| `bulk_import_events([...], source?)`, deduped on `(source, source_ref)` | `go.event.bulk_import` |
|
|
65 |
+ |
| `update_event(id, fields)`, overlays only the fields you pass | `go.event.update` |
|
|
66 |
+ |
| `delete_event(id)` | `go.event.delete` |
|
| 58 |
67 |
|
|
| 59 |
68 |
|
`bulk_import_tasks` dedupes on a `source:` provenance tag (e.g.
|
| 60 |
69 |
|
`source:todo.md:42`), so re-running a migration wave does not double-insert.
|
| 61 |
70 |
|
Projects referenced by name are resolved, and created if absent.
|
| 62 |
71 |
|
|
|
72 |
+ |
## Events and recurrence
|
|
73 |
+ |
|
|
74 |
+ |
A recurring event is stored once, as a series carrying a rule. The occurrences
|
|
75 |
+ |
it implies are not rows: they are expanded on demand for a window, with
|
|
76 |
+ |
synthetic ids that exist only inside the reply that produced them.
|
|
77 |
+ |
|
|
78 |
+ |
`list_events` exposes both views and `expand` picks between them. Off (the
|
|
79 |
+ |
default), each series is one row with its rule under `recurrence`, including a
|
|
80 |
+ |
rendered `display` string like "Every 2 weeks on Mon, Wed". On, you get the
|
|
81 |
+ |
calendar laid out: every occurrence in the window as its own row marked
|
|
82 |
+ |
`is_recurring_instance`, pointing back at the real row through
|
|
83 |
+ |
`recurrence_parent_id`.
|
|
84 |
+ |
|
|
85 |
+ |
Series-by-default is deliberate. A session filling in a calendar wants to know
|
|
86 |
+ |
which rules exist, not to read 52 copies of the standup. Feed a synthetic
|
|
87 |
+ |
instance id to `get_event` and it will refuse it, naming the parent id instead.
|
|
88 |
+ |
|
|
89 |
+ |
The window is not a plain SQL range. A series that started two years ago and
|
|
90 |
+ |
still recurs every Monday has no stored row inside next month, so a range query
|
|
91 |
+ |
alone would report it absent and a session would enter a second copy of it.
|
|
92 |
+ |
Both modes union the range query with the recurring series whose rule actually
|
|
93 |
+ |
reaches the window.
|
|
94 |
+ |
|
|
95 |
+ |
`bulk_import_events` is the primitive for loading a calendar in bulk. It dedupes
|
|
96 |
+ |
on `(source, source_ref)` through the same external-ref columns an `.ics` import
|
|
97 |
+ |
uses, with `source_ref` defaulting to the title. An item that already exists is
|
|
98 |
+ |
**skipped, not rewritten**: unlike `report_problems`, where a re-run is meant to
|
|
99 |
+ |
refresh a finding's score, a re-run here would clobber whatever you edited in the
|
|
100 |
+ |
app since the last import. Change one deliberately with `update_event`.
|
|
101 |
+ |
|
|
102 |
+ |
`update_event` overlays only the fields you pass. Passing `start` without `end`
|
|
103 |
+ |
moves the event and carries its duration, rather than leaving the old end behind
|
|
104 |
+ |
and inverting the span. An event that is currently all-day stays all-day across a
|
|
105 |
+ |
move unless you say otherwise. Events synced from an external calendar are
|
|
106 |
+ |
read-only and are refused.
|
|
107 |
+ |
|
|
108 |
+ |
There is no way to edit a single occurrence of a series. An occurrence is not a
|
|
109 |
+ |
row, so `update_event` on a series rewrites the whole series, and `delete_event`
|
|
110 |
+ |
on one deletes every occurrence it implies.
|
|
111 |
+ |
|
|
112 |
+ |
Rules have no end condition: `RecurrenceRule` carries a pattern, an interval,
|
|
113 |
+ |
weekdays, and a monthly spec, but no `until` or `count`. Every series recurs
|
|
114 |
+ |
forever. Expansion is windowed so this costs nothing to read, but a series that
|
|
115 |
+ |
really does end has to be edited or deleted when it does.
|
|
116 |
+ |
|
|
117 |
+ |
Dates cross the wire as RFC 3339 timestamps or bare `YYYY-MM-DD`, which means
|
|
118 |
+ |
**local** midnight in the system zone, not UTC. That differs from `due` on a
|
|
119 |
+ |
task, where a bare date is midnight UTC. A deadline barely cares about the zone;
|
|
120 |
+ |
a day on a calendar does, and reading it as UTC files it on the wrong day for
|
|
121 |
+ |
anyone west of Greenwich.
|
|
122 |
+ |
|
| 63 |
123 |
|
## Problems
|
| 64 |
124 |
|
|
| 65 |
125 |
|
GoingsOn is the list of solutions. A *problem* is a candidate for work that came
|