Skip to content

feat: calendars that stay current, a daemon that stays running, and the email after the meeting - #14

Merged
meocong merged 1 commit into
masterfrom
feat/complete-features
Aug 14, 2026
Merged

feat: calendars that stay current, a daemon that stays running, and the email after the meeting#14
meocong merged 1 commit into
masterfrom
feat/complete-features

Conversation

@meocong

@meocong meocong commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Six things were on the list of what was missing. All six are here, each built against the running artefact rather than against the tests alone — which is how two of them revealed the bug that mattered.

Calendar subscriptions

Adding a calendar meant typing the path of an .ics file: something a browser cannot produce, and a snapshot besides — the agenda described whatever the calendar looked like on export day and quietly kept describing it.

Now a URL. Google's secret address in iCal format, Apple's public calendar, an Outlook publish link; webcal:// is rewritten rather than refused, because that is the scheme Apple and Outlook actually hand out. The daemon re-fetches on startup and every fifteen minutes, and:

  • keeps the file it already has when a fetch fails — a laptop that woke without WiFi still shows this morning's meetings;
  • shows the failure on the row, because a broken subscription looks exactly like a week with no meetings;
  • refuses file://, ftp:// and bare paths. The daemon fetches this with the user's permissions, and "fetches whatever string it is handed" is how a local-first app becomes a way to read the disk.

No OAuth, on purpose. It would mean a client secret inside an open-source binary anyone can read, plus an account-wide scope, so that a notes app can learn what time the standup is. A URL grants exactly one calendar and is revocable from the calendar's own settings.

"Your meeting is starting — take notes?"

A nudge in the five minutes before an event and the ten after, once per occurrence, never while recording, and only when suggest_on_meeting is on. Its button starts the recording rather than navigating, since walking the user to the record button wastes the minute the prompt exists to save. Nothing here records on its own, and nothing here should be changed so that it can.

A daemon that outlives the terminal

summo serve --background, summo status, summo stop.

Stopping is an HTTP request, not a signal: the daemon can then refuse while a meeting is being recorded (--force overrides), and it is the same code on Windows. engine.json stays the only record of a running daemon, and is removed on the way out rather than left for the next command to find and mistrust.

The follow-up you have to send

An email, a chat message, a recap for people who missed it, or the decisions and actions as a list — written from the confirmed summary, not the raw transcript, because the summary is the version of events the user agreed with.

Nothing is sent. It is editable text with copy, a mailto: built from what is on screen, and keep-as-a-note.

Running it end to end through a stub provider caught the prompt inheriting the summariser's ground rules — so the first draft was a perfectly polite customer email with [t=03:12] in the middle of it.

Notes that start in a shape

Idea, decision, to-do, journal. Blank first, since most notes are.

Driving that in a browser found a data-loss bug that predates it: the notes editor showed only the text above the first ##, and saving wrote that back beside the sections it had never displayed — duplicating them on every save, invisibly. Fixed in note::as_text / note::split_sections, with a round-trip test.

Where a cloud would go

ADR 0007. A relay that stores bytes it cannot read; sync before sharing before teams; cloud models as a setting rather than a tier; and no account required for the local app, ever.

Also

The path-traversal test asserted a case no client can send — every URL parser collapses .. before the request leaves — so it failed in any build with the interface bundled, which CI never builds. It now tests the encoded forms that do arrive intact.

Verified

cargo fmt --check, clippy -D warnings, the whole Rust suite, 297 web tests, and 17 browser suites including e2e/calendar.mjs and e2e/notes.mjs.

And for real, against the built binary: a calendar served over HTTP — subscribed, refreshed, broken mid-flight, unsubscribed; a background daemon started, queried and stopped; an email composed through a stub provider and saved as a note.

🤖 Generated with Claude Code

…he email after the meeting

Six things were listed as missing and are here. Each was built against the running
artefact, and two of them only revealed their real bug that way.

**Calendar subscriptions.** Adding a calendar meant typing the path of an `.ics` file,
which a browser cannot produce and which is a snapshot — the agenda described whatever
the calendar looked like on export day, and kept describing it. Now a URL: Google's
secret iCal address, Apple's public calendar, an Outlook publish link. The daemon
re-fetches every fifteen minutes and on startup, keeps the file it already has when a
fetch fails, and shows the failure on the row, because a subscription that stopped
working looks exactly like a week with no meetings.

Deliberately not OAuth. That would mean a client secret inside an open-source binary
anyone can read, plus an account-wide scope, so a notes app can learn what time the
standup is. A URL grants one calendar and is revocable from the calendar's own settings.
`file://`, `ftp://` and bare paths are refused: the daemon fetches this with the user's
permissions, and "fetches whatever string it is handed" is how a local-first app becomes
a way to read the disk.

**"Your meeting is starting — take notes?"** A nudge in the five minutes before an event
and the ten after, once per occurrence, never while recording, and only when
`suggest_on_meeting` is on. Its button starts the recording instead of navigating, since
walking the user to the record button wastes the minute the prompt exists to save.
Nothing here ever records on its own; the therapy appointment in a work calendar is why.

**A daemon that outlives the terminal.** `summo serve --background`, `summo status`,
`summo stop`. Stopping is an HTTP request rather than a signal, so the daemon can refuse
while a meeting is being recorded (`--force` overrides), and so it is the same code on
Windows. `engine.json` stays the only record of a running daemon, and is removed on the
way out rather than left for the next command to discover as stale.

**The follow-up you have to send.** An email, a chat message, a recap for people who
missed it, or the decisions and actions as a list — from the confirmed summary, not the
raw transcript. Nothing is sent: it is editable text with copy, `mailto:` and keep-as-a-
note. Running it end to end through a fake provider caught the prompt inheriting the
summariser's ground rules, so the first draft was a polite customer email with
`[t=03:12]` in the middle of it.

**Notes that start in a shape.** Idea, decision, to-do, journal — blank first, since most
notes are. Driving that in a browser found a data-loss bug that predates it: the notes
editor showed only the text above the first `##`, and saving wrote that back *beside* the
sections it had never displayed, duplicating them on every save. `note::as_text` and
`note::split_sections` fix it, with a round-trip test.

**Where a cloud would go**, as ADR 0007: a relay that stores bytes it cannot read, sync
before sharing before teams, cloud models as a setting rather than a tier, and no account
for the local app, ever.

Also: the path-traversal test asserted a case no client can send — every URL parser
collapses `..` before the request leaves — so it failed in any build with the interface
bundled, which CI never builds. It now tests the encoded forms that do arrive intact.

Verified: 1300+ Rust tests, 297 web tests, 17 browser suites including two new ones, and
a real run — a calendar served over HTTP, subscribed, refreshed, broken, and unsubscribed;
a background daemon started, queried and stopped; an email composed through a stub
provider and saved as a note.
@meocong
meocong merged commit 398714c into master Aug 14, 2026
10 checks passed
@meocong
meocong deleted the feat/complete-features branch August 14, 2026 06:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant