TUI Calendar: open a read-only event card on Enter - #419
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Closing shortcuts are currently ineffective, and event metadata, time-zone labeling, and opened URL schemes need safer handling.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a read-only calendar event card for Day, Week, and Year views.
Changes:
- Opens event details with Enter.
- Supports link opening, editing, closing, and note scrolling.
- Documents and tests the new interactions.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or rungh pr ready --undo.
Click "Ready for review" or rungh pr readyto reengage.
File summaries
| File | Description |
|---|---|
internal/tui/event_detail.go |
Implements the event card. |
internal/tui/calendar.go |
Integrates card interaction and lifecycle. |
internal/tui/calendar_test.go |
Tests opening, editing, and links. |
docs/tui.md |
Documents calendar controls. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 4
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| link := strings.TrimSpace(v.detail.event.Link) | ||
| if link == "" { | ||
| return nil | ||
| } | ||
| if v.vc.openAttachment == nil { | ||
| return nil | ||
| } | ||
| if err := v.vc.openAttachment(link); err != nil { |
There was a problem hiding this comment.
Addressed in cae22f1: openableLink hands the launcher only an http or https link. TestTheEventCardWillNotOpenANonWebLink covers a file:///etc/passwd link — shown on the card, never opened, and the help bar drops o for it.
| if v.detail != nil { | ||
| switch msg.String() { | ||
| case "o": | ||
| return v.openEventLink() | ||
| case "e": | ||
| event := v.detail.event | ||
| v.detail = nil | ||
| return v.startEventForm(eventFormEdit, event) | ||
| } | ||
| return v.detail.update(msg) | ||
| } |
There was a problem hiding this comment.
Addressed in cae22f1: the card takes esc and q in handleContentKey before anything reaches its viewport, so neither leaks through. TestModelClosesTheEventCardOnEscape drives esc through the model rather than the view, and TestEnterOpensTheEventCardInsideAYearCell checks the second esc still steps out of the year cell once the card is gone.
| line := starts.Format("Monday, January 2") + " · " + clockTime(starts, d.use24) | ||
| switch { | ||
| case ends.IsZero() || !ends.After(starts): | ||
| case sameDay(starts, ends): | ||
| line += "–" + clockTime(ends, d.use24) | ||
| default: | ||
| line += " – " + ends.Format("Monday, January 2") + " · " + clockTime(ends, d.use24) | ||
| } | ||
| if zone := d.event.StartsAtZone; zone != "" { | ||
| line += " (" + zone + ")" |
There was a problem hiding this comment.
Addressed in cae22f1: when() formats Starts()/Ends() on the reader's clock with no zone suffix, which is the same conversion the grid draws by. A zoned event no longer gets relabelled with a zone its shown time is not in. There is no dedicated test for a named-zone event — the suffix code is gone rather than conditional, so there is nothing left to pin.
In the calendar's Day and Week views Enter did nothing on a highlighted event, and inside a Year cell it did nothing either, while the content help bar advertised "enter open" the whole time (the generic rowContent binding, live in Mail and dead here). The only way to see an event's notes, location, link or guests was to open the edit form with `e`. Enter now opens a read-only detail card over the grid, the way Contacts opens a contact on Enter and leaves `e` for editing. The card is built from the selected Recording alone -- the grid read already carries Notes, Location, Link and Attendees -- so nothing is fetched. From the card `o` opens the link, `e` swaps in the edit form on the same event, esc/q closes it, and the arrows and page keys scroll the notes. The card is an inputCapturer, so it handles esc itself and the help bar shows its keys instead of the generic "enter open". Two safeguards on what the card shows and does: - `o` only hands an http/https link to the OS launcher. Event links are server data and the edit form accepts any URI with a host, so a shared event could carry a file:// path or an application scheme; those are shown on the card but not opened, and `o` is not offered for them. - The when-and-where line shows the reader's local clock, the same conversion Recording.Starts/Ends and the grid make, rather than labelling a converted time with the event's original zone. The calendar name is sanitized like every other view of server metadata. Year view keeps its two stages: Enter steps into a cell, and only once inside does it open the selected event. Fixes basecamp#418 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6Y2KeiHB6QNWmHdgqbzD1
6cda55d to
cae22f1
Compare
What
In the calendar's Day and Week views,
Enterdid nothing on a highlighted event; inside a Year cell it did nothing either. Meanwhile the content help bar advertisedenter openthe whole time — the genericrowContentbinding fromupdateHelpBindings, which is live in Mail and dead in the calendar. The only way to see an event's notes, location, link or guests was to open the edit form withe.Enternow opens a read-only detail card over the grid — the same shape as Contacts, whereEnterviews a contact andeedits it.How
Recordingalone — the grid read already carriesNotes,Location,LinkandAttendees(kept on the model for exactly this reason), so there is no extra request.oopens the link throughviewContext.openAttachment(the samexdg-open/open/ Windows handler attachments use),ecloses the card and opens the edit form on the same event,esc/qcloses it, and the arrows / page keys scroll the notes.calendarView.detailjoinsCapturingInput(), so the model routes every key to the card (it handlesesc/qitself, not viaCancelPendingDetail) and the help bar showso/e/escinstead of the misleading genericenter open.oonly openshttp/httpslinks. Event links are server data and the edit form accepts any URI with a host, so a shared event could carry afile://path or an application scheme — the card shows those but never hands them to the OS launcher, and does not offerofor them.Recording.Starts/Endsand the grid make), rather than labelling a converted time with the event's original zone. The calendar name is sanitized like every other view of server metadata.Enterwith no cell open still steps into the cell; only onceinYearCelldoesEnteropen the selected event.Changes
internal/tui/event_detail.gointernal/tui/calendar.godetailfield;openEventDetail/openEventLink/calendarName; hooks inhandleContentKey,handleArrowKey,View,HelpBindings,CapturingInput,Resize,Restyle,CancelPendingDetailinternal/tui/calendar_test.goesc/qclose it),oopens an http link,orefuses a non-web link, a model-levelescregression test,eswaps in the edit form, Enter opens the card inside a Year celldocs/tui.mdTesting
go build ./...,go vet ./...,golangci-lint run(v2.11.1) andgo test ./...all pass.Fixes #418
🤖 Generated with Claude Code
https://claude.ai/code/session_01C6Y2KeiHB6QNWmHdgqbzD1