Skip to content

Commit d7dc5bb

Browse files
committed
fix(calendar): refine changed event listing (#875)
1 parent cc8c0a9 commit d7dc5bb

9 files changed

Lines changed: 47 additions & 37 deletions

File tree

.agents/skills/gog-calendar/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ gog --readonly --account [email protected] calendar events --today --json --wrap-
3131
| `acl` | List calendar ACL |
3232
| `alias` | Manage calendar aliases |
3333
| `calendars` | List calendars |
34+
| `changed` | List most recently changed events (including deletions) |
3435
| `colors` | Show calendar colors |
3536
| `conflicts` | Find busy-time overlaps across calendars |
3637
| `create` | Create an event |

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
## 0.31.0 - 2026-06-24
66

7+
- Calendar: add `calendar changed` for listing recently modified events, including cancellations, across one or more calendars. (#875) — thanks @sorenisanerd.
78
- Gmail: preserve HTML fragments from `--signature-file` instead of escaping their markup, without broadening HTML detection for message display or reply quoting. (#879) — thanks @kesslerio.
89
- Docs: honor `--tab` when setting document layout so `page-layout --tab` (and `write --pageless --tab`) target the specified tab instead of always the default tab. Page layout is per-tab; previously these silently no-opped on secondary tabs of multi-tab documents. (#878) — thanks @atmasphere.
910
- Auth: recover from corrupt stored OAuth token payloads by routing only classified decode corruption through the normal re-authentication flow while preserving operational keyring errors. (#872, #874) — thanks @KrasimirKralev.

docs/commands.generated.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ Generated from `gog schema --json`.
8686
- [`gog calendar (cal) alias set <alias> <calendarId>`](commands/gog-calendar-alias-set.md) - Set a calendar alias
8787
- [`gog calendar (cal) alias unset <alias>`](commands/gog-calendar-alias-unset.md) - Remove a calendar alias
8888
- [`gog calendar (cal) calendars [flags]`](commands/gog-calendar-calendars.md) - List calendars
89-
- [`gog calendar (cal) changed (recent,modified) [<calendarId> ...] [flags]`](commands/gog-calendar-changed.md) - List most recently changed events
89+
- [`gog calendar (cal) changed [<calendarId>] [flags]`](commands/gog-calendar-changed.md) - List most recently changed events (including deletions)
9090
- [`gog calendar (cal) colors`](commands/gog-calendar-colors.md) - Show calendar colors
9191
- [`gog calendar (cal) conflicts [flags]`](commands/gog-calendar-conflicts.md) - Find busy-time overlaps across calendars
9292
- [`gog calendar (cal) create (add,new) <calendarId> [flags]`](commands/gog-calendar-create.md) - Create an event

docs/commands/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ Generated pages: 700.
138138
- [gog calendar alias set](gog-calendar-alias-set.md) - Set a calendar alias
139139
- [gog calendar alias unset](gog-calendar-alias-unset.md) - Remove a calendar alias
140140
- [gog calendar calendars](gog-calendar-calendars.md) - List calendars
141-
- [gog calendar changed](gog-calendar-changed.md) - List most recently changed events
141+
- [gog calendar changed](gog-calendar-changed.md) - List most recently changed events (including deletions)
142142
- [gog calendar colors](gog-calendar-colors.md) - Show calendar colors
143143
- [gog calendar conflicts](gog-calendar-conflicts.md) - Find busy-time overlaps across calendars
144144
- [gog calendar create](gog-calendar-create.md) - Create an event

docs/commands/gog-calendar-changed.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
> Generated from `gog schema --json`. Do not edit this page by hand; run `make docs-commands`.
44
5-
List most recently changed events
5+
List most recently changed events (including deletions)
66

77
## Usage
88

99
```bash
10-
gog calendar (cal) changed (recent,modified) [<calendarId> ...] [flags]
10+
gog calendar (cal) changed [<calendarId>] [flags]
1111
```
1212

1313
## Parent
@@ -42,7 +42,6 @@ gog calendar (cal) changed (recent,modified) [<calendarId> ...] [flags]
4242
| `--readonly` | `bool` | false | Block mutating API requests at runtime; auth add also requests read-only OAuth scopes |
4343
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
4444
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
45-
| `--show-deleted` | `bool` | | Include deleted events |
4645
| `--since` | `string` | | Lower bound for last-modification time (RFC3339, date, or Go duration: 24h, 168h). Default: 720h (30 days). |
4746
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |
4847
| `--version` | `kong.VersionFlag` | | Print version and exit |

docs/commands/gog-calendar.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ gog calendar (cal) <command> [flags]
1919
- [gog calendar acl](gog-calendar-acl.md) - List calendar ACL
2020
- [gog calendar alias](gog-calendar-alias.md) - Manage calendar aliases
2121
- [gog calendar calendars](gog-calendar-calendars.md) - List calendars
22-
- [gog calendar changed](gog-calendar-changed.md) - List most recently changed events
22+
- [gog calendar changed](gog-calendar-changed.md) - List most recently changed events (including deletions)
2323
- [gog calendar colors](gog-calendar-colors.md) - Show calendar colors
2424
- [gog calendar conflicts](gog-calendar-conflicts.md) - Find busy-time overlaps across calendars
2525
- [gog calendar create](gog-calendar-create.md) - Create an event

internal/cmd/calendar.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type CalendarCmd struct {
2020
ProposeTime CalendarProposeTimeCmd `cmd:"" name:"propose-time" help:"Generate URL to propose a new meeting time (browser-only feature)"`
2121
Colors CalendarColorsCmd `cmd:"" name:"colors" help:"Show calendar colors"`
2222
Conflicts CalendarConflictsCmd `cmd:"" name:"conflicts" help:"Find busy-time overlaps across calendars"`
23-
Changed CalendarChangedCmd `cmd:"" name:"changed" aliases:"recent,modified" help:"List most recently changed events"`
23+
Changed CalendarChangedCmd `cmd:"" name:"changed" help:"List most recently changed events (including deletions)"`
2424
Search CalendarSearchCmd `cmd:"" name:"search" aliases:"find,query" help:"Search events"`
2525
Time CalendarTimeCmd `cmd:"" name:"time" help:"Show server time"`
2626
Users CalendarUsersCmd `cmd:"" name:"users" help:"List workspace users (use their email as calendar ID)"`

internal/cmd/calendar_changed.go

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,15 @@ import (
1414
)
1515

1616
type CalendarChangedCmd struct {
17-
CalendarID []string `arg:"" name:"calendarId" optional:"" help:"Calendar ID (default: primary)"`
18-
Cal []string `name:"cal" help:"Calendar ID or name (can be repeated)"`
19-
Calendars string `name:"calendars" help:"Comma-separated calendar IDs, names, or indices from 'calendar calendars'"`
20-
Since string `name:"since" help:"Lower bound for last-modification time (RFC3339, date, or Go duration: 24h, 168h). Default: 720h (30 days)."`
21-
Max int64 `name:"max" aliases:"limit" help:"Max results" default:"10"`
22-
All bool `name:"all" help:"Fetch from all calendars"`
23-
ShowDeleted bool `name:"show-deleted" help:"Include deleted events"`
24-
FailEmpty bool `name:"fail-empty" aliases:"non-empty,require-results" help:"Exit with code 3 if no results"`
25-
Weekday bool `name:"weekday" help:"Include start/end day-of-week columns"`
26-
Location bool `name:"location" help:"Include event LOCATION column in table output"`
17+
CalendarID string `arg:"" name:"calendarId" optional:"" help:"Calendar ID (default: primary)"`
18+
Cal []string `name:"cal" help:"Calendar ID or name (can be repeated)"`
19+
Calendars string `name:"calendars" help:"Comma-separated calendar IDs, names, or indices from 'calendar calendars'"`
20+
Since string `name:"since" help:"Lower bound for last-modification time (RFC3339, date, or Go duration: 24h, 168h). Default: 720h (30 days)."`
21+
Max int64 `name:"max" aliases:"limit" help:"Max results" default:"10"`
22+
All bool `name:"all" help:"Fetch from all calendars"`
23+
FailEmpty bool `name:"fail-empty" aliases:"non-empty,require-results" help:"Exit with code 3 if no results"`
24+
Weekday bool `name:"weekday" help:"Include start/end day-of-week columns"`
25+
Location bool `name:"location" help:"Include event LOCATION column in table output"`
2726
}
2827

2928
func (c *CalendarChangedCmd) Run(ctx context.Context, flags *RootFlags) error {
@@ -45,10 +44,7 @@ func (c *CalendarChangedCmd) Run(ctx context.Context, flags *RootFlags) error {
4544
return err
4645
}
4746

48-
calendarID, err := normalizeCalendarEventsArgs(c.CalendarID)
49-
if err != nil {
50-
return err
51-
}
47+
calendarID := strings.TrimSpace(c.CalendarID)
5248
calInputs := append([]string{}, c.Cal...)
5349
if strings.TrimSpace(c.Calendars) != "" {
5450
calInputs = append(calInputs, splitCSV(c.Calendars)...)
@@ -64,9 +60,9 @@ func (c *CalendarChangedCmd) Run(ctx context.Context, flags *RootFlags) error {
6460

6561
switch {
6662
case c.All:
67-
cals, err := listCalendarList(ctx, svc)
68-
if err != nil {
69-
return err
63+
cals, listErr := listCalendarList(ctx, svc)
64+
if listErr != nil {
65+
return listErr
7066
}
7167
ids := make([]string, 0, len(cals))
7268
for _, cal := range cals {
@@ -76,9 +72,9 @@ func (c *CalendarChangedCmd) Run(ctx context.Context, flags *RootFlags) error {
7672
}
7773
return c.listChangedMulti(ctx, svc, ids, sinceRFC3339, calendarTimezoneHints(cals))
7874
case len(calInputs) > 0:
79-
ids, err := resolveCalendarIDs(ctx, store, svc, calInputs)
80-
if err != nil {
81-
return err
75+
ids, resolveErr := resolveCalendarIDs(ctx, store, svc, calInputs)
76+
if resolveErr != nil {
77+
return resolveErr
8278
}
8379
if len(ids) == 0 {
8480
return usage("no calendars specified")
@@ -107,7 +103,7 @@ func (c *CalendarChangedCmd) resolveSince() (time.Time, error) {
107103
func (c *CalendarChangedCmd) listChangedSingle(ctx context.Context, svc *calendar.Service, calendarID, since string) error {
108104
calendarTimezone, loc := calendarDisplayTimezone(ctx, svc, calendarID, nil)
109105

110-
items, err := fetchChangedEvents(ctx, svc, calendarID, since, c.ShowDeleted)
106+
items, err := fetchChangedEvents(ctx, svc, calendarID, since)
111107
if err != nil {
112108
return err
113109
}
@@ -135,7 +131,7 @@ func (c *CalendarChangedCmd) listChangedMulti(ctx context.Context, svc *calendar
135131
continue
136132
}
137133
calendarTimezone, loc := calendarDisplayTimezone(ctx, svc, calID, hints)
138-
items, err := fetchChangedEvents(ctx, svc, calID, since, c.ShowDeleted)
134+
items, err := fetchChangedEvents(ctx, svc, calID, since)
139135
if err != nil {
140136
u.Err().Linef("calendar %s: %v", calID, err)
141137
continue
@@ -154,11 +150,13 @@ func (c *CalendarChangedCmd) listChangedMulti(ctx context.Context, svc *calendar
154150
return c.writeOutput(ctx, all, since, true)
155151
}
156152

157-
func fetchChangedEvents(ctx context.Context, svc *calendar.Service, calendarID, since string, showDeleted bool) ([]*calendar.Event, error) {
153+
func fetchChangedEvents(ctx context.Context, svc *calendar.Service, calendarID, since string) ([]*calendar.Event, error) {
158154
fetch := func(pageToken string) ([]*calendar.Event, string, error) {
155+
// Calendar always returns entries deleted since updatedMin. Request them
156+
// explicitly too: deletions are changes and belong in this command's output.
159157
call := svc.Events.List(calendarID).
160158
UpdatedMin(since).
161-
ShowDeleted(showDeleted).
159+
ShowDeleted(true).
162160
OrderBy("updated").
163161
MaxResults(250).
164162
Context(ctx)

internal/cmd/calendar_changed_test.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import (
1212
)
1313

1414
func TestCalendarChanged_JSON(t *testing.T) {
15+
var showDeleted string
1516
svc, closeServer := newCalendarServiceForTest(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1617
if strings.Contains(r.URL.Path, "/calendars/primary/events") && r.Method == http.MethodGet {
18+
showDeleted = r.URL.Query().Get("showDeleted")
1719
w.Header().Set("Content-Type", "application/json")
1820
_ = json.NewEncoder(w).Encode(map[string]any{
1921
"items": []map[string]any{
@@ -31,6 +33,12 @@ func TestCalendarChanged_JSON(t *testing.T) {
3133
"start": map[string]any{"dateTime": "2026-06-20T10:00:00Z"},
3234
"end": map[string]any{"dateTime": "2026-06-20T11:00:00Z"},
3335
},
36+
{
37+
"id": "e3",
38+
"summary": "Deleted event",
39+
"status": "cancelled",
40+
"updated": "2026-06-13T09:00:00Z",
41+
},
3442
},
3543
})
3644
return
@@ -59,15 +67,18 @@ func TestCalendarChanged_JSON(t *testing.T) {
5967
if err := json.Unmarshal(output.Bytes(), &parsed); err != nil {
6068
t.Fatalf("json parse: %v", err)
6169
}
62-
if len(parsed.Events) != 2 {
63-
t.Fatalf("expected 2 events, got %d", len(parsed.Events))
70+
if showDeleted != "true" {
71+
t.Fatalf("showDeleted = %q, want true", showDeleted)
72+
}
73+
if len(parsed.Events) != 3 {
74+
t.Fatalf("expected 3 events, got %d", len(parsed.Events))
6475
}
6576
// Most recently updated event should come first (descending order).
66-
if parsed.Events[0]["id"] != "e2" {
67-
t.Errorf("expected e2 first (more recent updated), got %v", parsed.Events[0]["id"])
77+
if parsed.Events[0]["id"] != "e3" {
78+
t.Errorf("expected deleted e3 first (more recent updated), got %v", parsed.Events[0]["id"])
6879
}
69-
if parsed.Events[1]["id"] != "e1" {
70-
t.Errorf("expected e1 second, got %v", parsed.Events[1]["id"])
80+
if parsed.Events[1]["id"] != "e2" || parsed.Events[2]["id"] != "e1" {
81+
t.Errorf("expected remaining events in descending order, got %v then %v", parsed.Events[1]["id"], parsed.Events[2]["id"])
7182
}
7283
if parsed.Since == "" {
7384
t.Error("expected since field in JSON output")

0 commit comments

Comments
 (0)