feat: name the channel, the date and the local time in the protocol - #34
Merged
Conversation
Three things a reader of a meeting protocol looks for and could not find. **Times were written in UTC.** The protocol is read by the people who were in the room, so the times in it are theirs. UTC was wrong in the way that is hardest to catch: 15:08 reads as a plausible meeting time whether or not it is the right one. `timezone` is now per-guild configuration defaulting to Europe/Berlin, and an unusable value falls back to UTC with a warning naming the guild -- a wrong offset is recoverable, a missing protocol is not. **The channel was missing entirely.** The worker writes the document and has no Discord connection, so it knew only the id. The bot records the name when it opens the session (migration 0005, nullable): capturing it there also means a channel renamed afterwards does not rewrite the protocols of meetings held under the old name. The heading links the channel either way, falling back to the id when no name was captured. **The date is now an Outline date mention**, `mention://date/YYYY-MM-DD`, which Outline renders as a chip. Date only, deliberately: the deployed version's parser accepts no time component, so an ISO datetime would degrade to a plain link. The heading also carries the start time, the duration and the participant count. `ChannelRef` is a separate value object rather than a field on `Transcript`: it describes the meeting, not what was said, and the name is a snapshot rather than a live lookup.
TheMeinerLP
added a commit
that referenced
this pull request
Aug 20, 2026
Two restructurings of `SturnusClient` met here: main's reconfiguration engine (#35) and this branch's capture pipeline. Main's structure is the trunk; this branch's behaviour is re-expressed inside it. The one decision worth naming: the rejoin guard moved out of `on_voice_state_update` and into `_sync_participants`. On this branch the event handler was the only way a session could start, so the guard was correct there. Under main's engine it no longer is -- `_build`, `_retarget` and `_apply_pending` reach `_sync_participants` too, so a deferred channel change landing on the same tick as a capture failure would have smuggled a session past a guard sitting in the handler. One check at the funnel covers every path. The cooldown is armed between `_return_to_idle` and `_apply_pending`, not after, for the same reason. It lapses in `_tick_guild` after `_reconcile`, so the recount uses the configuration the reconcile just settled on. Also merged: `RecordingService.retarget()` now takes the channel name alongside the id (#34 + #35), and the capture-pipeline fakes follow the `open_session` signature that gained `channel_name`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three things a reader of a meeting protocol looks for and could not find. Before and after:
Times were written in UTC
The protocol is read by the people who were in the room, so the times in it are theirs. UTC was wrong in the way that is hardest to catch: 15:08 reads as a plausible meeting time whether or not it is the right one. Nothing about the document would have looked broken.
timezoneis now per-guild configuration, defaulting toEurope/Berlin. An unusable value falls back to UTC with a warning naming the guild rather than failing the job — a wrong offset is recoverable, a missing protocol is not.Checked before relying on it: the
python:3.14-slimimage does carry the zoneinfo database, soZoneInfo("Europe/Berlin")resolves in the container and 13:08 UTC renders as 15:08 CEST. A slim image without tzdata would have failed only in production.The channel was missing entirely
The worker writes the document and holds no Discord connection, so it knew only the id. The bot records the name when it opens the session — migration
0005, nullable.Capturing it at open time rather than at render time is deliberate: a channel renamed afterwards should not rewrite the protocols of meetings held under the old name. The heading links the channel either way, falling back to the id when no name was captured, so sessions recorded before this column existed still get a working link.
The date is now an Outline date chip
@[20.08.2026](mention://date/2026-08-20)—MentionType.Dateexists in the deployed version (v1.9.1), so Outline renders it as a chip rather than text.Date only, deliberately. v1.9.1's
parseMentionUrlmatches[a-z0-9-]+for the value; the datetime form (…T15:08) only arrives with the regex onmain, which carries the comment "The modelId is a UUID, a date (2024-02-03) or a datetime (2024-02-03T13:00)". An ISO datetime would silently degrade to a plain link here.The heading also carries the start time, duration and participant count.
Design note
ChannelRefis a separate value object rather than a field onTranscript: it describes the meeting, not what was said, and the name is a snapshot rather than a live lookup.render_transcript'schannelparameter is optional so a caller that cannot resolve it still gets a protocol — the heading just omits that line.Verification
369 tests pass. New tests cover: times rendered in the configured zone (22:00 Berlin, not 20:00 UTC), an unusable zone still producing a document, the channel link with and without a name, the date mention's exact form, a protocol rendering without a channel at all, and the name reaching the session row when it opens.
Checked against the defect, not just the fix: replacing the timezone lookup with a hardcoded
UTCturns one of the new tests red.One cosmetic thing I left alone: the channel name renders as
Meeting\-Raum— the Markdown escaper escapes the hyphen. Outline renders it back as-, and the escaping is there because a channel name is attacker-controllable text, so I did not weaken it for looks.