diff --git a/AGENTS.md b/AGENTS.md index f8d4c279e..9b980fb40 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -271,6 +271,8 @@ Update the `revision` and `date` fields, then `make provenance-sync`. This is no The Smithy service version is derived from the shared provenance date. Run `make sync-spec-version` (or `make smithy-build`, which does this automatically) after updating provenance. +**Pin semantics.** The pin is the conformance baseline as of the last sync — it asserts that all upstream drift up to that revision has been *triaged*, not that every contract in it has been *absorbed*. Upstream contracts shipped past the SDK's modeled surface are tracked in `spec/api-gaps/` (status `addressed-in-bc3-pr-N`) until an absorption PR lands. A repin is valid exactly when every drift item in `pin..HEAD` is either absorbed into the spec or registered in `spec/api-gaps/`; it is not blocked on absorption itself. The pin never moves backward. The `compatibility.*` pins mark the last **verified API-surface state** of their branch, not a last-glanced timestamp — refresh one only when re-verifying that branch's API surface, and record verification dates in the PR that did the checking. + ### Pre-sync Use `make sync-status` to see upstream diffs since last sync. diff --git a/COORDINATION.md b/COORDINATION.md index bde908154..0f9126c74 100644 --- a/COORDINATION.md +++ b/COORDINATION.md @@ -7,6 +7,15 @@ live BC5 by the #11629 tooling. The historical server-side audit lived on the `five+api` branch under `tmp/api_audit.md` (generated by the BC3 plan's `script/api/audit`). +The SDK's conformance baseline is the pin in +[`spec/api-provenance.json`](spec/api-provenance.json) — `bc3` `master` +`ba105ba7` as of the 2026-07-22 sync. Contracts documented past the SDK's +modeled surface are registered in [`spec/api-gaps/`](spec/api-gaps/) until +absorbed (see AGENTS.md §Provenance is Mandatory for the pin semantics). +The `compatibility.bc3-four` pin stays at `9d73959a` (2026-06-12): the +`four` branch was re-verified on 2026-07-22 with **zero API drift** since — +no changes under `doc/api/` or the API controllers/views. + ## Division of labor - **BC3 plan owns**: server-side audit, jbuilder + controller + doc diff --git a/behavior-model.json b/behavior-model.json index 7ff08ace4..e126ec6bc 100644 --- a/behavior-model.json +++ b/behavior-model.json @@ -2594,7 +2594,8 @@ "$.avatar_url" ], "OutOfOfficePerson": [ - "$.name" + "$.name", + "$.avatar_url" ], "Person": [ "$.name", diff --git a/go/pkg/basecamp/api-provenance.json b/go/pkg/basecamp/api-provenance.json index ad2ef0b78..94869882a 100644 --- a/go/pkg/basecamp/api-provenance.json +++ b/go/pkg/basecamp/api-provenance.json @@ -1,8 +1,8 @@ { "bc3": { "branch": "master", - "revision": "e52453ff35a6292473eb095a8568649aa132fef9", - "date": "2026-07-17" + "revision": "ba105ba7d7e48bd97afdc98305e9fb8a63a88beb", + "date": "2026-07-22" }, "compatibility": { "bc3-four": { diff --git a/go/pkg/basecamp/people.go b/go/pkg/basecamp/people.go index 57e80458f..4cb2ddf68 100644 --- a/go/pkg/basecamp/people.go +++ b/go/pkg/basecamp/people.go @@ -534,18 +534,22 @@ type UpdateMyPreferencesRequest struct { } // OutOfOffice represents out-of-office status for a person. +// When out of office is not enabled, Enabled is false and StartDate, +// EndDate, and BackOnDate are omitted from the response. type OutOfOffice struct { - Enabled bool `json:"enabled,omitempty"` - StartDate string `json:"start_date,omitempty"` - EndDate string `json:"end_date,omitempty"` - Ongoing bool `json:"ongoing,omitempty"` - Person OutOfOfficePerson `json:"person,omitempty"` + Enabled bool `json:"enabled,omitempty"` + StartDate string `json:"start_date,omitempty"` + EndDate string `json:"end_date,omitempty"` + BackOnDate string `json:"back_on_date,omitempty"` + Ongoing bool `json:"ongoing,omitempty"` + Person OutOfOfficePerson `json:"person,omitempty"` } // OutOfOfficePerson represents the person associated with an out-of-office status. type OutOfOfficePerson struct { - ID int64 `json:"id"` - Name string `json:"name,omitempty"` + ID int64 `json:"id"` + Name string `json:"name,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` } // EnableOutOfOfficeRequest specifies the parameters for enabling out-of-office. diff --git a/go/pkg/basecamp/people_test.go b/go/pkg/basecamp/people_test.go index d29f25187..0546c38d4 100644 --- a/go/pkg/basecamp/people_test.go +++ b/go/pkg/basecamp/people_test.go @@ -440,3 +440,57 @@ func TestPeopleService_UpdateMyProfileClearsField(t *testing.T) { t.Errorf("expected bio to be empty string, got %v", bio) } } + +func TestOutOfOffice_Unmarshal(t *testing.T) { + data := loadPeopleFixture(t, "out-of-office.json") + + var ooo OutOfOffice + if err := json.Unmarshal(data, &ooo); err != nil { + t.Fatalf("failed to unmarshal out-of-office.json: %v", err) + } + + if !ooo.Enabled { + t.Error("expected enabled to be true") + } + if !ooo.Ongoing { + t.Error("expected ongoing to be true") + } + if ooo.StartDate != "2026-07-20" { + t.Errorf("expected start_date '2026-07-20', got %q", ooo.StartDate) + } + if ooo.EndDate != "2026-07-26" { + t.Errorf("expected end_date '2026-07-26', got %q", ooo.EndDate) + } + if ooo.BackOnDate != "2026-07-27" { + t.Errorf("expected back_on_date '2026-07-27', got %q", ooo.BackOnDate) + } + if ooo.Person.ID != 1049715913 { + t.Errorf("expected person id 1049715913, got %d", ooo.Person.ID) + } + if ooo.Person.Name != "Victor Cooper" { + t.Errorf("expected person name 'Victor Cooper', got %q", ooo.Person.Name) + } + if ooo.Person.AvatarURL == "" { + t.Error("expected non-empty person avatar_url") + } +} + +func TestOutOfOffice_UnmarshalDisabled(t *testing.T) { + data := []byte(`{"person":{"id":1049715913,"name":"Victor Cooper"},"enabled":false,"ongoing":false}`) + + var ooo OutOfOffice + if err := json.Unmarshal(data, &ooo); err != nil { + t.Fatalf("failed to unmarshal disabled out-of-office: %v", err) + } + + if ooo.Enabled { + t.Error("expected enabled to be false") + } + if ooo.Ongoing { + t.Error("expected ongoing to be false") + } + if ooo.StartDate != "" || ooo.EndDate != "" || ooo.BackOnDate != "" { + t.Errorf("expected omitted dates to be empty, got start=%q end=%q back_on=%q", + ooo.StartDate, ooo.EndDate, ooo.BackOnDate) + } +} diff --git a/go/pkg/basecamp/version.go b/go/pkg/basecamp/version.go index ee5c4bcab..799ecccfb 100644 --- a/go/pkg/basecamp/version.go +++ b/go/pkg/basecamp/version.go @@ -4,4 +4,4 @@ package basecamp const Version = "0.8.0" // APIVersion is the Basecamp API version this SDK targets. -const APIVersion = "2026-07-17" +const APIVersion = "2026-07-22" diff --git a/go/pkg/generated/client.gen.go b/go/pkg/generated/client.gen.go index dfa83a262..8fac510d3 100644 --- a/go/pkg/generated/client.gen.go +++ b/go/pkg/generated/client.gen.go @@ -781,7 +781,8 @@ type EnableOutOfOfficeRequestContent struct { OutOfOffice OutOfOfficePayload `json:"out_of_office"` } -// EnableOutOfOfficeResponseContent defines model for EnableOutOfOfficeResponseContent. +// EnableOutOfOfficeResponseContent When out of office is not enabled, `enabled` is `false` and +// `start_date`, `end_date`, and `back_on_date` are omitted. type EnableOutOfOfficeResponseContent = OutOfOffice // Event defines model for Event. @@ -1047,7 +1048,8 @@ type GetMyPreferencesResponseContent = Preferences // GetMyProfileResponseContent defines model for GetMyProfileResponseContent. type GetMyProfileResponseContent = Person -// GetOutOfOfficeResponseContent defines model for GetOutOfOfficeResponseContent. +// GetOutOfOfficeResponseContent When out of office is not enabled, `enabled` is `false` and +// `start_date`, `end_date`, and `back_on_date` are omitted. type GetOutOfOfficeResponseContent = OutOfOffice // GetOverdueTodosResponseContent defines model for GetOverdueTodosResponseContent. @@ -1473,24 +1475,31 @@ type Notification struct { ReadAt time.Time `json:"read_at,omitempty"` ReadableIdentifier string `json:"readable_identifier,omitempty"` ReadableSgid string `json:"readable_sgid,omitempty"` - Section string `json:"section,omitempty"` - Subscribed bool `json:"subscribed,omitempty"` - SubscriptionUrl string `json:"subscription_url,omitempty"` - Title string `json:"title,omitempty"` - Type string `json:"type,omitempty"` - UnreadAt time.Time `json:"unread_at,omitempty"` - UnreadCount int32 `json:"unread_count,omitempty"` - UnreadUrl string `json:"unread_url,omitempty"` - UpdatedAt time.Time `json:"updated_at"` -} - -// OutOfOffice defines model for OutOfOffice. + + // Section The notification category: `inbox`, `chats`, `pings`, `bubbles`, + // or `mentions`. + Section string `json:"section,omitempty"` + Subscribed bool `json:"subscribed,omitempty"` + SubscriptionUrl string `json:"subscription_url,omitempty"` + Title string `json:"title,omitempty"` + Type string `json:"type,omitempty"` + UnreadAt time.Time `json:"unread_at,omitempty"` + UnreadCount int32 `json:"unread_count,omitempty"` + UnreadUrl string `json:"unread_url,omitempty"` + UpdatedAt time.Time `json:"updated_at"` +} + +// OutOfOffice When out of office is not enabled, `enabled` is `false` and +// `start_date`, `end_date`, and `back_on_date` are omitted. type OutOfOffice struct { - Enabled bool `json:"enabled,omitempty"` - EndDate string `json:"end_date,omitempty"` - Ongoing bool `json:"ongoing,omitempty"` - Person OutOfOfficePerson `json:"person,omitempty"` - StartDate string `json:"start_date,omitempty"` + // BackOnDate First working day after the out-of-office window ends. + // Omitted when out of office is not enabled. + BackOnDate string `json:"back_on_date,omitempty"` + Enabled bool `json:"enabled,omitempty"` + EndDate string `json:"end_date,omitempty"` + Ongoing bool `json:"ongoing,omitempty"` + Person OutOfOfficePerson `json:"person,omitempty"` + StartDate string `json:"start_date,omitempty"` } // OutOfOfficePayload defines model for OutOfOfficePayload. @@ -1504,8 +1513,9 @@ type OutOfOfficePayload struct { // OutOfOfficePerson defines model for OutOfOfficePerson. type OutOfOfficePerson struct { - Id int64 `json:"id"` - Name string `json:"name,omitempty"` + AvatarUrl string `json:"avatar_url,omitempty"` + Id int64 `json:"id"` + Name string `json:"name,omitempty"` } // PauseQuestionResponseContent defines model for PauseQuestionResponseContent. @@ -1554,6 +1564,8 @@ type Preferences struct { AppUrl string `json:"app_url,omitempty"` FirstWeekDay string `json:"first_week_day,omitempty"` TimeFormat string `json:"time_format,omitempty"` + + // TimeZoneName Returned as a Rails-style name (e.g. "Central Time (US & Canada)"). TimeZoneName string `json:"time_zone_name,omitempty"` Url string `json:"url,omitempty"` } @@ -1566,7 +1578,10 @@ type PreferencesPayload struct { // TimeFormat Time display format: twelve_hour or twenty_four_hour TimeFormat string `json:"time_format,omitempty"` - // TimeZoneName Time zone name (e.g. "America/Chicago", "London", "UTC") + // TimeZoneName Time zone name. Accepts any valid Rails time zone name (e.g. + // "London", "UTC") as well as IANA identifiers (e.g. + // "America/Chicago"), which are normalized to the matching + // Rails-style name before saving. TimeZoneName string `json:"time_zone_name,omitempty"` } @@ -2520,11 +2535,14 @@ type Vault struct { // Webhook defines model for Webhook. type Webhook struct { - Active bool `json:"active,omitempty"` - AppUrl string `json:"app_url"` - CreatedAt time.Time `json:"created_at"` - Id int64 `json:"id"` - PayloadUrl string `json:"payload_url"` + Active bool `json:"active,omitempty"` + AppUrl string `json:"app_url"` + CreatedAt time.Time `json:"created_at"` + Id int64 `json:"id"` + PayloadUrl string `json:"payload_url"` + + // RecentDeliveries Up to the 25 most recent delivery exchanges, most recent first. + // Empty when the webhook hasn't delivered anything yet. RecentDeliveries []WebhookDelivery `json:"recent_deliveries,omitempty"` Types []string `json:"types,omitempty"` UpdatedAt time.Time `json:"updated_at"` diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/BasecampConfig.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/BasecampConfig.kt index dde0b8db4..6fcf9f468 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/BasecampConfig.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/BasecampConfig.kt @@ -29,7 +29,7 @@ data class BasecampConfig( ) { companion object { const val VERSION = "0.8.0" - const val API_VERSION = "2026-07-17" + const val API_VERSION = "2026-07-22" const val DEFAULT_BASE_URL = "https://3.basecampapi.com" const val DEFAULT_USER_AGENT = "basecamp-sdk-kotlin/$VERSION (api:$API_VERSION)" const val DEFAULT_MAX_RETRIES = 3 diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/reports.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/reports.kt index cbce2557b..0f9b9d77f 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/reports.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/reports.kt @@ -41,7 +41,7 @@ class ReportsService(client: AccountClient) : BaseService(client) { } /** - * Get upcoming schedule entries within a date window + * Get upcoming schedule entries and assignable items within a date window. * @param options Optional query parameters and pagination control */ suspend fun upcoming(options: GetUpcomingScheduleOptions? = null): JsonElement { diff --git a/openapi.json b/openapi.json index 44ca048eb..332c0f13e 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "Basecamp", - "version": "2026-07-17", + "version": "2026-07-22", "description": "Basecamp API", "contact": { "name": "Basecamp", @@ -8790,7 +8790,7 @@ }, "/{accountId}/my/readings.json": { "get": { - "description": "Get the current user's notification inbox (the \"Hey!\" menu).\nNotifications are grouped into unreads, reads, and memories.\nReads are paginated (50 per page). Unreads are capped at 100.", + "description": "Get the current user's notification inbox (the \"Hey!\" menu).\nNotifications are grouped into unreads, reads, bubble-ups, and\nscheduled bubble-ups (`memories` remains as an always-empty\nplaceholder on BC5). Reads are paginated (50 per page). Unreads are\ncapped at 100. Bubble-ups are capped per `limit_bubble_ups`.", "operationId": "GetMyNotifications", "parameters": [ { @@ -14950,7 +14950,7 @@ }, "/{accountId}/reports/schedules/upcoming.json": { "get": { - "description": "Get upcoming schedule entries within a date window", + "description": "Get upcoming schedule entries and assignable items within a date window.\nThis endpoint is preserved as the canonical API path on BC5;\nthe BC5 `/calendar` web view is HTML-only.", "operationId": "GetUpcomingSchedule", "parameters": [ { @@ -24654,7 +24654,8 @@ "x-go-type-skip-optional-pointer": true }, "section": { - "type": "string" + "type": "string", + "description": "The notification category: `inbox`, `chats`, `pings`, `bubbles`,\nor `mentions`." }, "unread_count": { "type": "integer", @@ -24758,6 +24759,7 @@ }, "OutOfOffice": { "type": "object", + "description": "When out of office is not enabled, `enabled` is `false` and\n`start_date`, `end_date`, and `back_on_date` are omitted.", "properties": { "person": { "$ref": "#/components/schemas/OutOfOfficePerson" @@ -24773,6 +24775,10 @@ }, "end_date": { "type": "string" + }, + "back_on_date": { + "type": "string", + "description": "First working day after the out-of-office window ends.\nOmitted when out of office is not enabled." } } }, @@ -24804,6 +24810,10 @@ "name": { "type": "string", "format": "password" + }, + "avatar_url": { + "type": "string", + "format": "password" } }, "required": [ @@ -24976,7 +24986,8 @@ "type": "string" }, "time_zone_name": { - "type": "string" + "type": "string", + "description": "Returned as a Rails-style name (e.g. \"Central Time (US & Canada)\")." }, "first_week_day": { "type": "string" @@ -24991,7 +25002,7 @@ "properties": { "time_zone_name": { "type": "string", - "description": "Time zone name (e.g. \"America/Chicago\", \"London\", \"UTC\")" + "description": "Time zone name. Accepts any valid Rails time zone name (e.g.\n\"London\", \"UTC\") as well as IANA identifiers (e.g.\n\"America/Chicago\"), which are normalized to the matching\nRails-style name before saving." }, "first_week_day": { "type": "string", @@ -27880,7 +27891,8 @@ "type": "array", "items": { "$ref": "#/components/schemas/WebhookDelivery" - } + }, + "description": "Up to the 25 most recent delivery exchanges, most recent first.\nEmpty when the webhook hasn't delivered anything yet." } }, "required": [ diff --git a/python/src/basecamp/_version.py b/python/src/basecamp/_version.py index ef313c6a3..4f73de5b9 100644 --- a/python/src/basecamp/_version.py +++ b/python/src/basecamp/_version.py @@ -1,2 +1,2 @@ VERSION = "0.8.0" -API_VERSION = "2026-07-17" +API_VERSION = "2026-07-22" diff --git a/python/src/basecamp/generated/types.py b/python/src/basecamp/generated/types.py index 49082a158..17e13ab91 100644 --- a/python/src/basecamp/generated/types.py +++ b/python/src/basecamp/generated/types.py @@ -919,6 +919,7 @@ class Notification(TypedDict): class OutOfOffice(TypedDict): + back_on_date: NotRequired[str] enabled: NotRequired[bool] end_date: NotRequired[str] ongoing: NotRequired[bool] @@ -932,6 +933,7 @@ class OutOfOfficePayload(TypedDict): class OutOfOfficePerson(TypedDict): + avatar_url: NotRequired[str] id: int name: NotRequired[str] diff --git a/ruby/lib/basecamp/generated/services/reports_service.rb b/ruby/lib/basecamp/generated/services/reports_service.rb index bc7cae28b..d26f620dd 100644 --- a/ruby/lib/basecamp/generated/services/reports_service.rb +++ b/ruby/lib/basecamp/generated/services/reports_service.rb @@ -15,7 +15,7 @@ def progress() end end - # Get upcoming schedule entries within a date window + # Get upcoming schedule entries and assignable items within a date window. # @param window_starts_on [String, nil] window starts on # @param window_ends_on [String, nil] window ends on # @return [Hash] response data diff --git a/ruby/lib/basecamp/generated/types.rb b/ruby/lib/basecamp/generated/types.rb index 7f048b2f5..d551d1bea 100644 --- a/ruby/lib/basecamp/generated/types.rb +++ b/ruby/lib/basecamp/generated/types.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Auto-generated from OpenAPI spec. Do not edit manually. -# Generated: 2026-07-22T02:53:58Z +# Generated: 2026-07-22T07:50:03Z require "json" require "time" @@ -2168,9 +2168,10 @@ def to_json(*args) # OutOfOffice class OutOfOffice include TypeHelpers - attr_accessor :enabled, :end_date, :ongoing, :person, :start_date + attr_accessor :back_on_date, :enabled, :end_date, :ongoing, :person, :start_date def initialize(data = {}) + @back_on_date = data["back_on_date"] @enabled = parse_boolean(data["enabled"]) @end_date = data["end_date"] @ongoing = parse_boolean(data["ongoing"]) @@ -2180,6 +2181,7 @@ def initialize(data = {}) def to_h { + "back_on_date" => @back_on_date, "enabled" => @enabled, "end_date" => @end_date, "ongoing" => @ongoing, @@ -2223,7 +2225,7 @@ def to_json(*args) # OutOfOfficePerson class OutOfOfficePerson include TypeHelpers - attr_accessor :id, :name + attr_accessor :id, :avatar_url, :name # @return [Array] def self.required_fields @@ -2232,12 +2234,14 @@ def self.required_fields def initialize(data = {}) @id = parse_integer(data["id"]) + @avatar_url = data["avatar_url"] @name = data["name"] end def to_h { "id" => @id, + "avatar_url" => @avatar_url, "name" => @name, }.compact end diff --git a/ruby/lib/basecamp/version.rb b/ruby/lib/basecamp/version.rb index 6e069f88c..20b30a59e 100644 --- a/ruby/lib/basecamp/version.rb +++ b/ruby/lib/basecamp/version.rb @@ -2,5 +2,5 @@ module Basecamp VERSION = "0.8.0" - API_VERSION = "2026-07-17" + API_VERSION = "2026-07-22" end diff --git a/ruby/test/basecamp/services/people_service_test.rb b/ruby/test/basecamp/services/people_service_test.rb index 3a4d67264..50ff663f9 100644 --- a/ruby/test/basecamp/services/people_service_test.rb +++ b/ruby/test/basecamp/services/people_service_test.rb @@ -98,4 +98,44 @@ def test_list_assignable assert_kind_of Array, result assert_equal 2, result.length end + + def test_get_out_of_office + stub_get("/12345/people/1/out_of_office.json", response_body: { + "person" => { + "id" => 1049715913, + "name" => "Victor Cooper", + "avatar_url" => "https://example.com/avatar" + }, + "enabled" => true, + "ongoing" => true, + "start_date" => "2026-07-20", + "end_date" => "2026-07-26", + "back_on_date" => "2026-07-27" + }) + + result = @account.people.get_out_of_office(person_id: 1) + + assert result["enabled"] + assert result["ongoing"] + assert_equal "2026-07-20", result["start_date"] + assert_equal "2026-07-26", result["end_date"] + assert_equal "2026-07-27", result["back_on_date"] + assert_equal "https://example.com/avatar", result["person"]["avatar_url"] + end + + def test_get_out_of_office_disabled_omits_dates + stub_get("/12345/people/1/out_of_office.json", response_body: { + "person" => { "id" => 1049715913, "name" => "Victor Cooper" }, + "enabled" => false, + "ongoing" => false + }) + + result = @account.people.get_out_of_office(person_id: 1) + + assert_not result["enabled"] + assert_not result["ongoing"] + assert_not result.key?("start_date") + assert_not result.key?("end_date") + assert_not result.key?("back_on_date") + end end diff --git a/spec/api-gaps/README.md b/spec/api-gaps/README.md index ad8ff9dab..73a428374 100644 --- a/spec/api-gaps/README.md +++ b/spec/api-gaps/README.md @@ -48,10 +48,14 @@ making the absorption journey publicly auditable. | [recording-bubbleupable-field](recording-bubbleupable-field.md) | no-json-contract | 3e | low | | [todoset-completed-list-visibility](todoset-completed-list-visibility.md) | ambiguous | 3a | low | | [memories-emptied-regression](memories-emptied-regression.md) | addressed-in-bc3-pr-11628 | launch | high | +| [campfire-line-edit](campfire-line-edit.md) | addressed-in-bc3-pr-12359 | post-train | medium | +| [todoset-direct-todo-create](todoset-direct-todo-create.md) | addressed-in-bc3-pr-12359 | post-train | medium | +| [schedule-recurrence-writes](schedule-recurrence-writes.md) | addressed-in-bc3-pr-12359 | post-train | medium | > Statuses reflect how BC3's **BC5 API train** actually shipped (8 PRs merged > to `master`, 2026-07-18..21); BC3 #10947 closed unmerged, superseded by the -> train. `memories-emptied-regression` is a *subtractive* delta (a field BC4 +> train. Entries with plan phase `post-train` track contracts documented +> after the train (BC3 #12359, merged 2026-07-22). `memories-emptied-regression` is a *subtractive* delta (a field BC4 > populates that BC5 emptied), settled as **permanently empty by documented > contract** — its `addressed-in-bc3-pr-11628` records the PR that codified > the empty-placeholder contract, not a repopulation; see the entry. diff --git a/spec/api-gaps/campfire-line-edit.md b/spec/api-gaps/campfire-line-edit.md new file mode 100644 index 000000000..a97c2ff81 --- /dev/null +++ b/spec/api-gaps/campfire-line-edit.md @@ -0,0 +1,75 @@ +--- +gap: campfire-line-edit +status: addressed-in-bc3-pr-12359 +detected: 2026-07-22 +sdk_demand: medium +bc3_pr: 12359 +bc3_refs: + introduced_in: five + routes: + - "PUT /:account_id/chats/:chat_id/lines/:line_id.json" + - "PUT /:account_id/buckets/:project_id/chats/:chat_id/lines/:line_id.json (legacy project-scoped)" + controllers: + - app/controllers/chats/lines_controller.rb + related_existing_api: + - GetCampfireLine + - CreateCampfireLine + - DeleteCampfireLine +--- + +# Campfire line edit + +## What's missing + +SDK absorption only — the contract shipped via BC3 **#12359** (merged +2026-07-22, post-train follow-up to the BC5 API docs). "Update a Campfire +line" in `doc/api/sections/campfires.md` on `master` is the contract of +record: + +- `PUT /chats/:chat_id/lines/:line_id.json` (canonical account-scoped form; + the project-scoped `PUT /buckets/:project_id/chats/:chat_id/lines/:line_id.json` + is documented under Legacy project-scoped routes). +- **Required parameter**: `content` — the new body for the line. +- Only text and rich text lines can be edited, and **only by their creator**. +- The new content is treated as rich text: the line becomes a rich text line + even if it was originally plain text, and `content` may include the HTML + tags covered in the Rich text guide. +- Returns `204 No Content` on success, `403 Forbidden` if the current user + isn't allowed to edit the line. + +The same PR also tightened the documented **delete** contract (already +modeled as `DeleteCampfireLine`): lines can be deleted by their creator *or +by an admin*, and `403 Forbidden` is now documented for the disallowed case. +That's a doc-comment refinement on the existing operation, not a new gap. + +## Why it matters + +Line editing is a BC5 capability with no BC4 analog. Without it, an SDK +consumer that posts Campfire lines (bots, bridges, notifiers) can't correct +a message after the fact — the only recourse is delete + repost, which loses +position and reads as churn in the room. + +## Suggested API shape + +`UpdateCampfireLine` operation: `PUT /{accountId}/chats/{chatId}/lines/{lineId}.json` +with a required `content` body member, `204` response (no output payload), +and `ForbiddenError` in the error list. Input mirrors `CreateCampfireLine`'s +content member; no response-shape work needed. + +## Implementation notes for BC3 + +Shipped — nothing pending. `chats/lines_controller.rb` serves the route; +the doc documents request/response and the permission model (creator-only +edit; creator-or-admin delete). + +## SDK absorption plan when this lands + +- **SDK PR #295 (open) already models `UpdateCampfireLine`** and is the + likely absorbing PR; the §Q absorption queue's PR-2 build-ahead pair is + the fallback vehicle if #295 stalls. +- Status flips to `absorbed-in-sdk` with the absorbing PR (which adds the + Smithy refs). +- While absorbing, refresh `DeleteCampfireLine`'s doc comment with the + creator-or-admin / 403 language from the same doc section. +- Pairwise check: route 404s on BC4 (no line editing), succeeds on BC5 — + additive-only, no invariant violation. diff --git a/spec/api-gaps/schedule-recurrence-writes.md b/spec/api-gaps/schedule-recurrence-writes.md new file mode 100644 index 000000000..822b16882 --- /dev/null +++ b/spec/api-gaps/schedule-recurrence-writes.md @@ -0,0 +1,96 @@ +--- +gap: schedule-recurrence-writes +status: addressed-in-bc3-pr-12359 +detected: 2026-07-22 +sdk_demand: medium +bc3_pr: 12359 +bc3_refs: + introduced_in: five + routes: + - "POST /:account_id/schedules/:schedule_id/entries.json (existing — recurrence params additive)" + - "PUT /:account_id/schedule_entries/:id.json (existing — recurrence params additive)" + controllers: + - app/controllers/schedules/entries_controller.rb + related_existing_api: + - CreateScheduleEntry + - UpdateScheduleEntry + - GetScheduleEntry +--- + +# Schedule entries — recurrence write parameters + +## What's missing + +Write-side recurrence for schedule entries. The read surface +(`recurrence_schedule` in Get a schedule entry) has long been documented; +BC3 **#12359** (merged 2026-07-22, post-train follow-up; cURL example +aligned by #12363) documents the **write** contract in +`doc/api/sections/schedule_entries.md` on `master`, as additive parameters +on the existing create/update endpoints: + +- `recurrence_schedule` — object that makes the entry recurring: + + | Param | Applies to | Contract | + |---|---|---| + | `frequency` | required in the object | one of `every_day`, `every_weekday`, `every_week`, `every_other_week`, `every_month`, `every_day_of_month`, `every_year`, `custom_week`, `custom_month` | + | `days` | `every_day`; `custom_month` without `week_instance` | for `every_day`: days of week as integers `0` (Sunday)–`6` (Saturday), omit to recur every day; for `custom_month`: day of month `1`–`31`. Derived from `starts_at` for the other frequencies | + | `week_instance` | `every_month`, `custom_month` | which week of the month, `1`–`4`, or `-1` for the last week | + | `week_interval` | `custom_week` | repeat every `2`–`12` weeks | + | `month_interval` | `custom_month` | repeat every `2`–`12` months | + +- `recurs_until` — top-level (sibling of `recurrence_schedule`), ISO 8601 + date the recurrence ends; omit to recur indefinitely. Reflected as + `recurrence_schedule.end_date` in the response. +- The remaining `recurrence_schedule` attributes shown in the read payload + (`hour`, `minute`, `start_date`, `duration`, `end_date`) are **derived** + from `starts_at`/`ends_at`/`recurs_until` and **ignored on input**. +- An **invalid `recurrence_schedule` is silently discarded on create**: the + entry is created without recurring (no validation error). +- Update: adding a `recurrence_schedule` makes a non-recurring entry recur. + An entry that **already recurs can't be changed** through the update + endpoint — it redirects to the entry's first occurrence, like Get a + schedule entry does. + +**Server-hang warning**: never send `week_instance: 0`. BC3 currently spins +computing occurrences for it (open BC3 **#12362**, "Reject recurrence +schedules whose occurrences can't be computed", adds the rejection). Until +#12362 merges, an SDK-side guard or enum constraint must make `0` +unrepresentable. + +## Why it matters + +Recurring events are table stakes for calendar integrations. The SDK can +read `recurrence_schedule` but can't create or update recurring entries, so +any integration syncing an external calendar into Basecamp has to fan a +recurring series out into individual entries — which then don't behave as a +series in the product. + +## Suggested API shape + +Additive optional members on the existing `CreateScheduleEntry` / +`UpdateScheduleEntry` inputs: a `recurrence_schedule` structure (enum-typed +`frequency`; integer list `days`; bounded integers per the table) plus +top-level `recurs_until: ISO8601Date`. Response shapes unchanged (the read +side already models `recurrence_schedule`). + +## Implementation notes for BC3 + +- Shipped in docs — `schedules/entries_controller.rb` handles the params. +- #12362 (open) is the server-side guard for uncomputable schedules + (`week_instance: 0` et al.); silent-discard-on-invalid on create is + documented, not a bug. + +## SDK absorption plan when this lands + +- Vehicle: the §Q absorption queue's **PR-5**. Docs are merged, so the doc + gate is cleared, but the **wire-echo probe remains a pre-modeling gate**: + before modeling, probe a live BC5 create with each frequency family and + diff the echoed `recurrence_schedule` against the doc (the derived-fields + and silent-discard semantics make doc-only modeling risky). +- Model `frequency` as an enum (nine values above); constrain or guard + `week_instance` so `0` can't be sent while BC3 #12362 is open. +- Status flips to `absorbed-in-sdk` with the absorption PR (which adds the + Smithy refs). +- Pairwise check: BC4 accepts the same create/update routes but recurrence + behavior on BC4 is unverified — probe both rails before asserting + pairwise invariants. diff --git a/spec/api-gaps/search-filter-additions.md b/spec/api-gaps/search-filter-additions.md index 6c0f774f8..645302c62 100644 --- a/spec/api-gaps/search-filter-additions.md +++ b/spec/api-gaps/search-filter-additions.md @@ -19,18 +19,27 @@ bc3_refs: ## What's missing -Docs shipped, params not final — **do not absorb yet**. Search filter -documentation landed on `master` with the BC5 API train (2026-07-18..21), but -open BC3 **#12361** (search params rework) is actively reshaping the filter -parameter surface. The status stays `partial-coverage` until #12361 settles: -absorbing the current param list would model a contract BC3 has already -queued for change. +Docs shipped, params not final — **do not absorb yet**. As of the 2026-07-22 +sync, `doc/api/sections/search.md` on `master` documents the filter surface: -The filter families in play (subject to #12361's rework — re-derive the final -list from `doc/api/sections/search.md` once it merges): +- `type_names[]` — array of recording types to include (`key` values from + the metadata endpoint's `recording_search_types`); the metadata endpoint + prose now advertises `type_names[]` as the discovery target. +- `bucket_ids[]` — array of project IDs. +- `creator_ids[]` — array of creator person IDs. +- `since` — time-range filter: `last_7_days`, `last_30_days`, `last_90_days`, + `last_12_months`, or `forever` (the default); unrecognized values + normalize to `forever`. +- `sort` — `best_match` (default, relevance with a recency boost) or + `recency` (strictly newest first); unrecognized values fall back to + recency ordering. +- Deprecated-but-retained singulars for older clients: `type`, `bucket_id`, + `creator_id` (prefer the plural array forms). -- recording-type filtering, creator/person filtering, project scoping, -- chat exclusion, file-type filtering, and result ordering. +The hold stands because open BC3 **#12361** ("Search API: query-faithful +params and root cache") is still reshaping this parameter surface. The +status stays `partial-coverage` until #12361 settles: absorbing the current +param list would model a contract BC3 has already queued for change. The `timelines/searches` route is the timeline-scoped variant; covered here since it shares the input shape. @@ -54,7 +63,8 @@ unchanged. controller actions, no new partials. - #12361 (open) is the deciding PR for the final param names/semantics; `doc/api/sections/search.md` follows it. -- Document defaults explicitly (e.g. the default sort). +- Defaults are now documented (`since=forever`, `sort=best_match`), along + with the fallback behavior for unrecognized values. ## SDK absorption plan when this lands diff --git a/spec/api-gaps/todoset-direct-todo-create.md b/spec/api-gaps/todoset-direct-todo-create.md new file mode 100644 index 000000000..1c8ac777b --- /dev/null +++ b/spec/api-gaps/todoset-direct-todo-create.md @@ -0,0 +1,66 @@ +--- +gap: todoset-direct-todo-create +status: addressed-in-bc3-pr-12359 +detected: 2026-07-22 +sdk_demand: medium +bc3_pr: 12359 +bc3_refs: + introduced_in: five + routes: + - "POST /:account_id/buckets/:project_id/todosets/:todoset_id/todos.json" + controllers: + - app/controllers/todos_controller.rb + related_existing_api: + - CreateTodo + - GetTodoset +--- + +# To-do set — create a to-do directly (outside any list) + +## What's missing + +SDK absorption only — the contract shipped via BC3 **#12359** (merged +2026-07-22, post-train follow-up to the BC5 API docs). "Create a to-do" in +`doc/api/sections/todos.md` on `master` now documents a second create form: + +- `POST /buckets/:project_id/todosets/:todoset_id/todos.json` creates a + to-do **directly under the to-do set**, outside of any to-do list. +- **This form is only available project-scoped** — there is no account-scoped + variant (unlike most BC5 canonical routes). +- Parameters and response match the existing to-do-list create: required + `content`, optional `description`/`assignee_ids`/`completion_subscriber_ids`/ + `notify`/`due_on`/`starts_on`, returns `201 Created` with the Todo payload. +- Find a project's to-do set via the existing Get to-do set endpoint + (`GetTodoset` in the SDK). + +## Why it matters + +Loose to-dos (to-dos that live directly on the to-do set rather than in a +list) are a BC5 feature. SDK consumers can see them in list/read surfaces +but can't create them — the modeled `CreateTodo` requires a to-do list id, +so integrations that mirror BC5's "add a to-do without picking a list" flow +have no API path. + +## Suggested API shape + +`CreateTodosetTodo` operation: +`POST /{accountId}/buckets/{projectId}/todosets/{todosetId}/todos.json`, +reusing `CreateTodo`'s payload members and the `Todo` output shape. `201` +with the created Todo. + +## Implementation notes for BC3 + +Shipped — nothing pending. Routed via `resources :todosets do resources +:todos, only: %i[index create]` in the bucket scope, served by +`todos_controller.rb`; the doc documents the create form. + +## SDK absorption plan when this lands + +- Vehicle: the §Q absorption queue's **PR-2 build-ahead pair** + (`UpdateCampfireLine` + `CreateTodosetTodo`), pre-approved. +- New Smithy operation `CreateTodosetTodo` sharing payload/output shapes + with `CreateTodo`; registers on the existing Todos (or Todosets) service. +- Status flips to `absorbed-in-sdk` with the absorption PR (which adds the + Smithy refs). +- Pairwise check: BC4 has no loose to-dos — expect 404 on BC4, 201 on BC5; + additive-only. diff --git a/spec/api-provenance.json b/spec/api-provenance.json index ad2ef0b78..94869882a 100644 --- a/spec/api-provenance.json +++ b/spec/api-provenance.json @@ -1,8 +1,8 @@ { "bc3": { "branch": "master", - "revision": "e52453ff35a6292473eb095a8568649aa132fef9", - "date": "2026-07-17" + "revision": "ba105ba7d7e48bd97afdc98305e9fb8a63a88beb", + "date": "2026-07-22" }, "compatibility": { "bc3-four": { diff --git a/spec/basecamp.smithy b/spec/basecamp.smithy index 4f6b03451..b185f0441 100644 --- a/spec/basecamp.smithy +++ b/spec/basecamp.smithy @@ -51,7 +51,7 @@ use basecamp.traits#basecampAuthRoutableUrl /// Basecamp API @restJson1 service Basecamp { - version: "2026-07-17" + version: "2026-07-22" rename: { "smithy.api#Document": "JsonDocument" } @@ -5822,6 +5822,9 @@ structure Webhook { url: String @required app_url: String + + /// Up to the 25 most recent delivery exchanges, most recent first. + /// Empty when the webhook hasn't delivered anything yet. recent_deliveries: WebhookDeliveryList } @@ -7223,7 +7226,9 @@ structure GetOverdueTodosOutput { over_three_months_late: TodoItems } -/// Get upcoming schedule entries within a date window +/// Get upcoming schedule entries and assignable items within a date window. +/// This endpoint is preserved as the canonical API path on BC5; +/// the BC5 `/calendar` web view is HTML-only. @readonly @basecampRetry(maxAttempts: 3, baseDelayMs: 1000, backoff: "exponential", retryOn: [429, 503]) @http(method: "GET", uri: "/{accountId}/reports/schedules/upcoming.json") @@ -8163,8 +8168,10 @@ list MyAssignmentAssigneeList { // ===== Notification Operations ===== /// Get the current user's notification inbox (the "Hey!" menu). -/// Notifications are grouped into unreads, reads, and memories. -/// Reads are paginated (50 per page). Unreads are capped at 100. +/// Notifications are grouped into unreads, reads, bubble-ups, and +/// scheduled bubble-ups (`memories` remains as an always-empty +/// placeholder on BC5). Reads are paginated (50 per page). Unreads are +/// capped at 100. Bubble-ups are capped per `limit_bubble_ups`. @readonly @basecampRetry(maxAttempts: 3, baseDelayMs: 1000, backoff: "exponential", retryOn: [429, 503]) @http(method: "GET", uri: "/{accountId}/my/readings.json") @@ -8246,6 +8253,9 @@ structure Notification { created_at: ISO8601Timestamp @required updated_at: ISO8601Timestamp + + /// The notification category: `inbox`, `chats`, `pings`, `bubbles`, + /// or `mentions`. section: String unread_count: Integer unread_at: ISO8601Timestamp @@ -8391,18 +8401,25 @@ structure DisableOutOfOfficeOutput {} // ===== Out of Office Shapes ===== +/// When out of office is not enabled, `enabled` is `false` and +/// `start_date`, `end_date`, and `back_on_date` are omitted. structure OutOfOffice { person: OutOfOfficePerson enabled: Boolean ongoing: Boolean start_date: ISO8601Date end_date: ISO8601Date + + /// First working day after the out-of-office window ends. + /// Omitted when out of office is not enabled. + back_on_date: ISO8601Date } structure OutOfOfficePerson { @required id: PersonId name: PersonName + avatar_url: AvatarUrl } // ============================================================================= @@ -8453,7 +8470,10 @@ structure UpdateMyPreferencesInput { } structure PreferencesPayload { - /// Time zone name (e.g. "America/Chicago", "London", "UTC") + /// Time zone name. Accepts any valid Rails time zone name (e.g. + /// "London", "UTC") as well as IANA identifiers (e.g. + /// "America/Chicago"), which are normalized to the matching + /// Rails-style name before saving. time_zone_name: String /// First day of the week: Sunday, Monday, Tuesday, etc. @@ -8473,6 +8493,8 @@ structure UpdateMyPreferencesOutput { structure Preferences { url: String app_url: String + + /// Returned as a Rails-style name (e.g. "Central Time (US & Canada)"). time_zone_name: String first_week_day: String time_format: String diff --git a/spec/fixtures/people/out-of-office.json b/spec/fixtures/people/out-of-office.json new file mode 100644 index 000000000..e510806a7 --- /dev/null +++ b/spec/fixtures/people/out-of-office.json @@ -0,0 +1,12 @@ +{ + "person": { + "id": 1049715913, + "name": "Victor Cooper", + "avatar_url": "https://3.basecampapi.com/195539477/people/BAhpBMlkkT4=--5fe7b70fbee7a7f0e2e1e19df7579e5d880c753d/avatar" + }, + "enabled": true, + "ongoing": true, + "start_date": "2026-07-20", + "end_date": "2026-07-26", + "back_on_date": "2026-07-27" +} diff --git a/swift/Sources/Basecamp/BasecampConfig.swift b/swift/Sources/Basecamp/BasecampConfig.swift index b977e5b31..c6d76c5d6 100644 --- a/swift/Sources/Basecamp/BasecampConfig.swift +++ b/swift/Sources/Basecamp/BasecampConfig.swift @@ -27,7 +27,7 @@ public struct BasecampConfig: Sendable { public static let version = "0.8.0" /// Basecamp API version this SDK targets. - public static let apiVersion = "2026-07-17" + public static let apiVersion = "2026-07-22" /// Default User-Agent header value. public static let defaultUserAgent = "basecamp-sdk-swift/\(version) (api:\(apiVersion))" diff --git a/swift/Sources/Basecamp/Generated/Models/OutOfOffice.swift b/swift/Sources/Basecamp/Generated/Models/OutOfOffice.swift index ca0a0170d..fcc738d8e 100644 --- a/swift/Sources/Basecamp/Generated/Models/OutOfOffice.swift +++ b/swift/Sources/Basecamp/Generated/Models/OutOfOffice.swift @@ -2,6 +2,7 @@ import Foundation public struct OutOfOffice: Codable, Sendable { + public var backOnDate: String? public var enabled: Bool? public var endDate: String? public var ongoing: Bool? diff --git a/swift/Sources/Basecamp/Generated/Models/OutOfOfficePerson.swift b/swift/Sources/Basecamp/Generated/Models/OutOfOfficePerson.swift index 963ab2bd3..53fdfbe5a 100644 --- a/swift/Sources/Basecamp/Generated/Models/OutOfOfficePerson.swift +++ b/swift/Sources/Basecamp/Generated/Models/OutOfOfficePerson.swift @@ -3,10 +3,12 @@ import Foundation public struct OutOfOfficePerson: Codable, Sendable { public let id: Int + public var avatarUrl: String? public var name: String? - public init(id: Int, name: String? = nil) { + public init(id: Int, avatarUrl: String? = nil, name: String? = nil) { self.id = id + self.avatarUrl = avatarUrl self.name = name } } diff --git a/typescript/src/client.ts b/typescript/src/client.ts index c7b51b22d..7105dd888 100644 --- a/typescript/src/client.ts +++ b/typescript/src/client.ts @@ -220,7 +220,7 @@ export interface BasecampClientOptions { } export const VERSION = "0.8.0"; -export const API_VERSION = "2026-07-17"; +export const API_VERSION = "2026-07-22"; const DEFAULT_USER_AGENT = `basecamp-sdk-ts/${VERSION} (api:${API_VERSION})`; /** diff --git a/typescript/src/generated/openapi-stripped.json b/typescript/src/generated/openapi-stripped.json index 28a0e1ab2..d652d76a9 100644 --- a/typescript/src/generated/openapi-stripped.json +++ b/typescript/src/generated/openapi-stripped.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "Basecamp", - "version": "2026-07-17", + "version": "2026-07-22", "description": "Basecamp API", "contact": { "name": "Basecamp", @@ -7804,7 +7804,7 @@ }, "/my/readings.json": { "get": { - "description": "Get the current user's notification inbox (the \"Hey!\" menu).\nNotifications are grouped into unreads, reads, and memories.\nReads are paginated (50 per page). Unreads are capped at 100.", + "description": "Get the current user's notification inbox (the \"Hey!\" menu).\nNotifications are grouped into unreads, reads, bubble-ups, and\nscheduled bubble-ups (`memories` remains as an always-empty\nplaceholder on BC5). Reads are paginated (50 per page). Unreads are\ncapped at 100. Bubble-ups are capped per `limit_bubble_ups`.", "operationId": "GetMyNotifications", "parameters": [ { @@ -13258,7 +13258,7 @@ }, "/reports/schedules/upcoming.json": { "get": { - "description": "Get upcoming schedule entries within a date window", + "description": "Get upcoming schedule entries and assignable items within a date window.\nThis endpoint is preserved as the canonical API path on BC5;\nthe BC5 `/calendar` web view is HTML-only.", "operationId": "GetUpcomingSchedule", "parameters": [ { @@ -22330,7 +22330,8 @@ "x-go-type-skip-optional-pointer": true }, "section": { - "type": "string" + "type": "string", + "description": "The notification category: `inbox`, `chats`, `pings`, `bubbles`,\nor `mentions`." }, "unread_count": { "type": "integer", @@ -22434,6 +22435,7 @@ }, "OutOfOffice": { "type": "object", + "description": "When out of office is not enabled, `enabled` is `false` and\n`start_date`, `end_date`, and `back_on_date` are omitted.", "properties": { "person": { "$ref": "#/components/schemas/OutOfOfficePerson" @@ -22449,6 +22451,10 @@ }, "end_date": { "type": "string" + }, + "back_on_date": { + "type": "string", + "description": "First working day after the out-of-office window ends.\nOmitted when out of office is not enabled." } } }, @@ -22480,6 +22486,10 @@ "name": { "type": "string", "format": "password" + }, + "avatar_url": { + "type": "string", + "format": "password" } }, "required": [ @@ -22652,7 +22662,8 @@ "type": "string" }, "time_zone_name": { - "type": "string" + "type": "string", + "description": "Returned as a Rails-style name (e.g. \"Central Time (US & Canada)\")." }, "first_week_day": { "type": "string" @@ -22667,7 +22678,7 @@ "properties": { "time_zone_name": { "type": "string", - "description": "Time zone name (e.g. \"America/Chicago\", \"London\", \"UTC\")" + "description": "Time zone name. Accepts any valid Rails time zone name (e.g.\n\"London\", \"UTC\") as well as IANA identifiers (e.g.\n\"America/Chicago\"), which are normalized to the matching\nRails-style name before saving." }, "first_week_day": { "type": "string", @@ -25556,7 +25567,8 @@ "type": "array", "items": { "$ref": "#/components/schemas/WebhookDelivery" - } + }, + "description": "Up to the 25 most recent delivery exchanges, most recent first.\nEmpty when the webhook hasn't delivered anything yet." } }, "required": [ diff --git a/typescript/src/generated/schema.d.ts b/typescript/src/generated/schema.d.ts index b2a4fff27..387afa9c6 100644 --- a/typescript/src/generated/schema.d.ts +++ b/typescript/src/generated/schema.d.ts @@ -1104,8 +1104,10 @@ export interface paths { }; /** * @description Get the current user's notification inbox (the "Hey!" menu). - * Notifications are grouped into unreads, reads, and memories. - * Reads are paginated (50 per page). Unreads are capped at 100. + * Notifications are grouped into unreads, reads, bubble-ups, and + * scheduled bubble-ups (`memories` remains as an always-empty + * placeholder on BC5). Reads are paginated (50 per page). Unreads are + * capped at 100. Bubble-ups are capped per `limit_bubble_ups`. */ get: operations["GetMyNotifications"]; put?: never; @@ -1848,7 +1850,11 @@ export interface paths { path?: never; cookie?: never; }; - /** @description Get upcoming schedule entries within a date window */ + /** + * @description Get upcoming schedule entries and assignable items within a date window. + * This endpoint is preserved as the canonical API path on BC5; + * the BC5 `/calendar` web view is HTML-only. + */ get: operations["GetUpcomingSchedule"]; put?: never; post?: never; @@ -3580,6 +3586,10 @@ export interface components { id: number; created_at: string; updated_at: string; + /** + * @description The notification category: `inbox`, `chats`, `pings`, `bubbles`, + * or `mentions`. + */ section?: string; /** Format: int32 */ unread_count?: number; @@ -3616,12 +3626,21 @@ export interface components { /** @description Custom image URL (pings only) */ image_url?: string; }; + /** + * @description When out of office is not enabled, `enabled` is `false` and + * `start_date`, `end_date`, and `back_on_date` are omitted. + */ OutOfOffice: { person?: components["schemas"]["OutOfOfficePerson"]; enabled?: boolean; ongoing?: boolean; start_date?: string; end_date?: string; + /** + * @description First working day after the out-of-office window ends. + * Omitted when out of office is not enabled. + */ + back_on_date?: string; }; OutOfOfficePayload: { /** @description Start date in ISO 8601 format (YYYY-MM-DD) */ @@ -3634,6 +3653,8 @@ export interface components { id: number; /** Format: password */ name?: string; + /** Format: password */ + avatar_url?: string; }; PauseQuestionResponseContent: { paused?: boolean; @@ -3686,12 +3707,18 @@ export interface components { Preferences: { url?: string; app_url?: string; + /** @description Returned as a Rails-style name (e.g. "Central Time (US & Canada)"). */ time_zone_name?: string; first_week_day?: string; time_format?: string; }; PreferencesPayload: { - /** @description Time zone name (e.g. "America/Chicago", "London", "UTC") */ + /** + * @description Time zone name. Accepts any valid Rails time zone name (e.g. + * "London", "UTC") as well as IANA identifiers (e.g. + * "America/Chicago"), which are normalized to the matching + * Rails-style name before saving. + */ time_zone_name?: string; /** @description First day of the week: Sunday, Monday, Tuesday, etc. */ first_week_day?: string; @@ -4515,6 +4542,10 @@ export interface components { types?: string[]; url: string; app_url: string; + /** + * @description Up to the 25 most recent delivery exchanges, most recent first. + * Empty when the webhook hasn't delivered anything yet. + */ recent_deliveries?: components["schemas"]["WebhookDelivery"][]; }; /** @description Reference to a copied/moved recording in copy events. */ diff --git a/typescript/src/generated/services/reports.ts b/typescript/src/generated/services/reports.ts index 45fc6a819..1791bb91d 100644 --- a/typescript/src/generated/services/reports.ts +++ b/typescript/src/generated/services/reports.ts @@ -84,7 +84,7 @@ export class ReportsService extends BaseService { } /** - * Get upcoming schedule entries within a date window + * Get upcoming schedule entries and assignable items within a date window. * @param options - Optional query parameters * @returns The upcoming_schedule * diff --git a/typescript/tests/services/people.test.ts b/typescript/tests/services/people.test.ts index 42acd4899..173b89915 100644 --- a/typescript/tests/services/people.test.ts +++ b/typescript/tests/services/people.test.ts @@ -139,4 +139,52 @@ describe("PeopleService", () => { expect(people[0]!.id).toBe(1); }); }); + + describe("outOfOffice", () => { + it("should return out-of-office status including back_on_date", async () => { + server.use( + http.get(`${BASE_URL}/people/1/out_of_office.json`, () => { + return HttpResponse.json({ + person: { + id: 1049715913, + name: "Victor Cooper", + avatar_url: "https://example.com/avatar", + }, + enabled: true, + ongoing: true, + start_date: "2026-07-20", + end_date: "2026-07-26", + back_on_date: "2026-07-27", + }); + }) + ); + + const status = await client.people.outOfOffice(1); + expect(status.enabled).toBe(true); + expect(status.ongoing).toBe(true); + expect(status.start_date).toBe("2026-07-20"); + expect(status.end_date).toBe("2026-07-26"); + expect(status.back_on_date).toBe("2026-07-27"); + expect(status.person?.avatar_url).toBe("https://example.com/avatar"); + }); + + it("should omit dates when out of office is not enabled", async () => { + server.use( + http.get(`${BASE_URL}/people/1/out_of_office.json`, () => { + return HttpResponse.json({ + person: { id: 1049715913, name: "Victor Cooper" }, + enabled: false, + ongoing: false, + }); + }) + ); + + const status = await client.people.outOfOffice(1); + expect(status.enabled).toBe(false); + expect(status.ongoing).toBe(false); + expect(status.start_date).toBeUndefined(); + expect(status.end_date).toBeUndefined(); + expect(status.back_on_date).toBeUndefined(); + }); + }); });