feat(campfires): add UpdateCampfireLine operation#295
Conversation
Spec Change Impact
|
There was a problem hiding this comment.
Pull request overview
Adds a new Campfires operation, UpdateCampfireLine (PUT /{accountId}/chats/{campfireId}/lines/{lineId}), to let SDK consumers edit existing campfire lines without delete-and-repost. This change updates the Smithy source of truth, regenerates the OpenAPI + all SDKs, and adds language-specific naming/config glue plus tests.
Changes:
- Extend the Smithy spec (and tags overlay) with
UpdateCampfireLine, including idempotency + retry metadata, and regenerateopenapi.json/ derived artifacts. - Regenerate TypeScript/Ruby/Python/Swift/Kotlin SDKs to expose
updateLine/update_linemethods and update service-generator configs to map the new operation name correctly. - Add a hand-written Go
CampfiresService.UpdateLine(...)wrapper (re-fetching the updated line after a 204) and add new tests (Go/TS/Ruby).
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Reviewed changes
Copilot reviewed 17 out of 35 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| typescript/tests/services/campfires.test.ts | Adds MSW-backed tests for campfires.updateLine (204 + validation). |
| typescript/src/generated/services/campfires.ts | Adds generated updateLine method + request interface. |
| typescript/src/generated/schema.d.ts | Updates OpenAPI-derived TS types to include PUT for campfire lines. |
| typescript/src/generated/path-mapping.ts | Registers PUT campfire-line route → UpdateCampfireLine. |
| typescript/src/generated/openapi-stripped.json | Updates stripped OpenAPI with new PUT operation + schema. |
| typescript/src/generated/metadata.json | Adds retry/idempotency metadata entry for UpdateCampfireLine. |
| typescript/scripts/generate-services.ts | Updates service splits + method name override to include UpdateCampfireLine → updateLine. |
| typescript/README.md | Documents the new campfires.updateLine method in the service list. |
| swift/Sources/BasecampGenerator/ServiceGrouper.swift | Adds UpdateCampfireLine to Campfires grouping for generation. |
| swift/Sources/BasecampGenerator/MethodNaming.swift | Adds method name override UpdateCampfireLine → updateLine. |
| swift/Sources/Basecamp/Generated/Services/CampfiresService.swift | Adds generated updateLine(...) API in Swift service. |
| swift/Sources/Basecamp/Generated/Models/UpdateCampfireLineRequest.swift | Adds generated request model for update line. |
| swift/Sources/Basecamp/Generated/Metadata.swift | Adds retry config entry for UpdateCampfireLine. |
| spec/overlays/tags.smithy | Tags UpdateCampfireLine as Campfire. |
| spec/basecamp.smithy | Defines UpdateCampfireLine operation + input/output in Smithy. |
| ruby/test/basecamp/services/campfires_service_test.rb | Adds Ruby tests for update_line (with/without content_type). |
| ruby/scripts/generate-services.rb | Adds operation to Campfires service split + method name override. |
| ruby/lib/basecamp/generated/types.rb | Refreshes generation timestamp header. |
| ruby/lib/basecamp/generated/services/campfires_service.rb | Adds generated update_line(...) method implementation. |
| ruby/lib/basecamp/generated/metadata.json | Adds retry/idempotency metadata entry for UpdateCampfireLine. |
| python/src/basecamp/generated/types.py | Adds TypedDict for UpdateCampfireLineRequestContent. |
| python/src/basecamp/generated/services/campfires.py | Adds sync/async update_line(...) methods. |
| python/src/basecamp/generated/metadata.json | Adds operation metadata entry for retry/idempotency. |
| python/scripts/generate_services.py | Adds operation to Campfires service list + name override. |
| openapi.json | Adds PUT operation + request schema for UpdateCampfireLine. |
| kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/Types.kt | Adds UpdateCampfireLineBody model. |
| kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/campfires.kt | Adds generated updateLine(...) method. |
| kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/Metadata.kt | Adds retry metadata for UpdateCampfireLine. |
| kotlin/generator/src/main/kotlin/com/basecamp/sdk/generator/Config.kt | Updates service split + method name override for UpdateCampfireLine. |
| go/README.md | Documents Campfires().UpdateLine in the Go service list. |
| go/pkg/generated/client.gen.go | Regenerates Go client with UpdateCampfireLine request/response plumbing. |
| go/pkg/basecamp/campfires.go | Adds hand-written CampfiresService.UpdateLine(...) wrapper + options. |
| go/pkg/basecamp/campfires_test.go | Adds Go tests covering UpdateLine validation + request shape + refetch. |
| behavior-model.json | Adds retry/idempotency behavior model entry for UpdateCampfireLine. |
| AGENTS.md | Updates operation count and generated-operations description. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
f58f166 to
75f73ca
Compare
Addresses Copilot review on basecamp/basecamp-sdk#295: align UpdateLineOptions ContentType doc with the rest of the campfires service (API defaults to plain text).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 35 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
75f73ca to
26d71f1
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 35 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Adds an UpdateCampfireLine operation across all six SDKs, generated from
the Smithy spec. Maps PUT /chats/{campfireId}/lines/{lineId}, which the
Basecamp API accepts but the spec did not previously expose.
- Smithy: new operation with Campfire tag, registered in service operations
- Generated clients regenerated for Go, TypeScript, Ruby, Python, Swift, Kotlin
- Service generator mappings (TS, Ruby, Python, Swift, Kotlin) updated to
rename UpdateCampfireLine -> updateLine / update_line
- Go service wrapper UpdateLine returns the re-fetched line (API responds 204)
- Go, Ruby, TypeScript tests cover happy path and validation
- AGENTS.md operation count refreshed (175 -> 204)
Verified: PUT /chats/{c}/lines/{l} with {content,content_type} returns 204
against a real Basecamp account; the line content updates as expected.
49712f5 to
3841ee1
Compare
The server ignores content_type on line updates: Chats::LinesController#update
strips it (update_params = line_params.except(:content_type)) and coerces every
edited line to rich text via becomes(Chat::Lines::RichText). Only the line's
creator may edit, and only text/rich-text lines are editable
(Person::Ability#can_edit_chat_line?). Publicly documented in
doc/api/sections/campfires.md ("Update a Campfire line", bc3-api af143f25;
registered as the campfire-line-edit api-gap by basecamp#390).
Drop the dead content_type parameter from UpdateCampfireLineInput and
regenerate all six SDKs; document the rich-text coercion and creator-only
constraint on the operation. Simplify the Go wrapper to
UpdateLine(ctx, campfireID, lineID, content) — no options struct — matching
DeleteLine's shape. The operation is unreleased, so no compatibility concern.
Endpoint semantics were verified against bc3 through 13c84145 via a full
drift audit of e52453ff..13c84145; the provenance pin itself advanced
separately on main (basecamp#390, to ba105ba7), so this commit no longer touches it.
Test adjustments: drop the Go option-validation tests with the options struct,
assert the update body carries only content; drop the Ruby content_type
variant; add 422 mapping coverage for update_line in Ruby and TypeScript.
Backfill service tests for create_line/get_line/update_line/delete_line. update_line gets happy-path (204 -> None, body carries only content) and 422 ValidationError cases in both the sync and async clients.
Backfill CampfiresServiceTest: createLine/getLine/deleteLine happy paths, updateLine PUT with a content-only body, and 422 -> Validation mapping.
Backfill GeneratedServiceTests: createLine/getLine/deleteLine happy paths, updateLine PUT with a content-only body, and 422 -> .validation mapping.
behavior-model.json now carries 204 operations (66 idempotent, 138 not); the retry-pattern claims (three (max, base_delay_ms) shapes, all retry_on [429, 503]) were re-verified against the model and still hold.
…ssions Flip spec/api-gaps/campfire-line-edit.md to absorbed-in-sdk with the UpdateCampfireLine Smithy ref, per the absorption plan basecamp#390 registered. While absorbing, refresh DeleteCampfireLine's doc comment with the newly documented creator-or-admin / 403 contract from the same section of doc/api/sections/campfires.md.
3841ee1 to
4de7624
Compare
Adds 'basecamp chat update <id|url> [content]' for editing existing
campfire lines in place via PUT /chats/{c}/lines/{l}, backed by the
SDK's CampfiresService.UpdateLine.
Content handling has four explicit modes, validated before any request:
- default: Markdown converted to HTML unconditionally, then @mentions
resolved (edits always render as rich text server-side);
- --content-type text/html: HTML supplied as-is, then @mentions resolved;
- --content-type text/plain: serialized verbatim via richtext.PlainToHTML
(escaped, line breaks preserved as <br>), mention resolution skipped;
- any other value: rejected with a usage error.
--content-type is applied client-side only; the SDK PUT carries just the
content, and the server coerces every edit to rich text.
URL arguments route through the shared urlarg parser (which gains a
CampfireID field for the chat-line route) plus a hostutil trusted-host
gate, so a pasted card/todo URL, a boundary-evading line ID, or a
look-alike on an untrusted host is rejected rather than silently
misinterpreted; a URL whose account differs from the configured account
is refused. The 204 PUT is followed by a GetLine re-fetch so the response
carries the canonical post-update line.
Bumps the SDK to CampfiresService.UpdateLine (basecamp/basecamp-sdk#295,
merge commit 09f7b326), pinned to the merge commit pending a release tag.
Wires up the surface snapshot, catalog, e2e + smoke coverage, and the
basecamp skill documentation.
Adds 'basecamp chat update <id|url> [content]' for editing existing
campfire lines in place via PUT /chats/{c}/lines/{l}, backed by the
SDK's CampfiresService.UpdateLine.
Content handling has four explicit modes, validated before any request:
- default: Markdown converted to HTML unconditionally, then @mentions
resolved (edits always render as rich text server-side);
- --content-type text/html: HTML supplied as-is, then @mentions resolved;
- --content-type text/plain: serialized verbatim via richtext.PlainToHTML
(escaped, line breaks preserved as <br>), mention resolution skipped;
- any other value: rejected with a usage error.
--content-type is applied client-side only; the SDK PUT carries just the
content, and the server coerces every edit to rich text.
URL arguments route through the shared urlarg parser (which gains a
CampfireID field for the chat-line route) plus a hostutil trusted-host
gate, so a pasted card/todo URL, a boundary-evading line ID, or a
look-alike on an untrusted host is rejected rather than silently
misinterpreted; a URL whose account differs from the configured account
is refused. The 204 PUT is followed by a GetLine re-fetch so the response
carries the canonical post-update line.
Bumps the SDK to CampfiresService.UpdateLine (basecamp/basecamp-sdk#295,
merge commit 09f7b326), pinned to the merge commit pending a release tag.
Wires up the surface snapshot, catalog, e2e + smoke coverage, and the
basecamp skill documentation.
Adds 'basecamp chat update <id|url> [content]' for editing existing
campfire lines in place via PUT /chats/{c}/lines/{l}, backed by the
SDK's CampfiresService.UpdateLine.
Content handling has four explicit modes, validated before any request:
- default: Markdown converted to HTML unconditionally, then @mentions
resolved (edits always render as rich text server-side);
- --content-type text/html: HTML supplied as-is, then @mentions resolved;
- --content-type text/plain: serialized verbatim via richtext.PlainToHTML
(escaped, line breaks preserved as <br>), mention resolution skipped;
- any other value: rejected with a usage error.
--content-type is applied client-side only; the SDK PUT carries just the
content, and the server coerces every edit to rich text.
URL arguments route through the shared urlarg parser (which gains a
CampfireID field for the chat-line route) plus a hostutil trusted-host
gate, so a pasted card/todo URL, a boundary-evading line ID, or a
look-alike on an untrusted host is rejected rather than silently
misinterpreted; a URL whose account differs from the configured account
is refused. The 204 PUT is followed by a GetLine re-fetch so the response
carries the canonical post-update line.
Bumps the SDK to CampfiresService.UpdateLine (basecamp/basecamp-sdk#295,
merge commit 09f7b326), pinned to the merge commit pending a release tag.
Wires up the surface snapshot, catalog, e2e + smoke coverage, and the
basecamp skill documentation.
Adds 'basecamp chat update <id|url> [content]' for editing existing
campfire lines in place via PUT /chats/{c}/lines/{l}, backed by the
SDK's CampfiresService.UpdateLine.
Content handling has four explicit modes, validated before any request:
- default: Markdown converted to HTML unconditionally, then @mentions
resolved (edits always render as rich text server-side);
- --content-type text/html: HTML supplied as-is, then @mentions resolved;
- --content-type text/plain: serialized verbatim via richtext.PlainToHTML
(escaped, line breaks preserved as <br>), mention resolution skipped;
- any other value: rejected with a usage error.
--content-type is applied client-side only; the SDK PUT carries just the
content, and the server coerces every edit to rich text.
URL arguments route through the shared urlarg parser (which gains a
CampfireID field for the chat-line route) plus a hostutil trusted-host
gate, so a pasted card/todo URL, a boundary-evading line ID, or a
look-alike on an untrusted host is rejected rather than silently
misinterpreted; a URL whose account differs from the configured account
is refused. The 204 PUT is followed by a GetLine re-fetch so the response
carries the canonical post-update line.
Bumps the SDK to CampfiresService.UpdateLine (basecamp/basecamp-sdk#295,
merge commit 09f7b326), pinned to the merge commit pending a release tag.
Wires up the surface snapshot, catalog, e2e + smoke coverage, and the
basecamp skill documentation.
Adds 'basecamp chat update <id|url> [content]' for editing existing
campfire lines in place via PUT /chats/{c}/lines/{l}, backed by the
SDK's CampfiresService.UpdateLine.
Content handling has four explicit modes, validated before any request:
- default: Markdown converted to HTML unconditionally, then @mentions
resolved (edits always render as rich text server-side);
- --content-type text/html: HTML supplied as-is, then @mentions resolved;
- --content-type text/plain: serialized verbatim via richtext.PlainToHTML
(escaped, line breaks preserved as <br>), mention resolution skipped;
- any other value: rejected with a usage error.
--content-type is applied client-side only; the SDK PUT carries just the
content, and the server coerces every edit to rich text.
URL arguments route through the shared urlarg parser (which gains a
CampfireID field for the chat-line route) plus a hostutil trusted-host
gate, so a pasted card/todo URL, a boundary-evading line ID, or a
look-alike on an untrusted host is rejected rather than silently
misinterpreted; a URL whose account differs from the configured account
is refused. The 204 PUT is followed by a GetLine re-fetch so the response
carries the canonical post-update line.
Bumps the SDK to CampfiresService.UpdateLine (basecamp/basecamp-sdk#295,
merge commit 09f7b326), pinned to the merge commit pending a release tag.
Wires up the surface snapshot, catalog, e2e + smoke coverage, and the
basecamp skill documentation.
Adds 'basecamp chat update <id|url> [content]' for editing existing
campfire lines in place via PUT /chats/{c}/lines/{l}, backed by the
SDK's CampfiresService.UpdateLine.
Content handling has four explicit modes, validated before any request:
- default: Markdown converted to HTML unconditionally, then @mentions
resolved (edits always render as rich text server-side);
- --content-type text/html: HTML supplied as-is, then @mentions resolved;
- --content-type text/plain: serialized verbatim via richtext.PlainToHTML
(escaped, line breaks preserved as <br>), mention resolution skipped;
- any other value: rejected with a usage error.
--content-type is applied client-side only; the SDK PUT carries just the
content, and the server coerces every edit to rich text.
URL arguments route through the shared urlarg parser (which gains a
CampfireID field for the chat-line route) plus a hostutil trusted-host
gate, so a pasted card/todo URL, a boundary-evading line ID, or a
look-alike on an untrusted host is rejected rather than silently
misinterpreted; a URL whose account differs from the configured account
is refused. The 204 PUT is followed by a GetLine re-fetch so the response
carries the canonical post-update line.
Bumps the SDK to CampfiresService.UpdateLine (basecamp/basecamp-sdk#295,
merge commit 09f7b326), pinned to the merge commit pending a release tag.
Wires up the surface snapshot, catalog, e2e + smoke coverage, and the
basecamp skill documentation.
Adds 'basecamp chat update <id|url> [content]' for editing existing
campfire lines in place via PUT /chats/{c}/lines/{l}, backed by the
SDK's CampfiresService.UpdateLine.
Content handling has four explicit modes, validated before any request:
- default: Markdown converted to HTML unconditionally, then @mentions
resolved (edits always render as rich text server-side);
- --content-type text/html: HTML supplied as-is, then @mentions resolved;
- --content-type text/plain: serialized verbatim via richtext.PlainToHTML
(escaped, line breaks preserved as <br>), mention resolution skipped;
- any other value: rejected with a usage error.
--content-type is applied client-side only; the SDK PUT carries just the
content, and the server coerces every edit to rich text.
URL arguments route through the shared urlarg parser (which gains a
CampfireID field for the chat-line route) plus a hostutil trusted-host
gate, so a pasted card/todo URL, a boundary-evading line ID, or a
look-alike on an untrusted host is rejected rather than silently
misinterpreted; a URL whose account differs from the configured account
is refused. The 204 PUT is followed by a GetLine re-fetch so the response
carries the canonical post-update line.
Bumps the SDK to CampfiresService.UpdateLine (basecamp/basecamp-sdk#295,
merge commit 09f7b326), pinned to the merge commit pending a release tag.
Wires up the surface snapshot, catalog, e2e + smoke coverage, and the
basecamp skill documentation.
Adds 'basecamp chat update <id|url> [content]' for editing existing
campfire lines in place via PUT /chats/{c}/lines/{l}, backed by the
SDK's CampfiresService.UpdateLine.
Content handling has four explicit modes, validated before any request:
- default: Markdown converted to HTML unconditionally, then @mentions
resolved (edits always render as rich text server-side);
- --content-type text/html: HTML supplied as-is, then @mentions resolved;
- --content-type text/plain: serialized verbatim via richtext.PlainToHTML
(escaped, line breaks preserved as <br>), mention resolution skipped;
- any other value: rejected with a usage error.
--content-type is applied client-side only; the SDK PUT carries just the
content, and the server coerces every edit to rich text.
URL arguments route through the shared urlarg parser (which gains a
CampfireID field for the chat-line route) plus a hostutil trusted-host
gate, so a pasted card/todo URL, a boundary-evading line ID, or a
look-alike on an untrusted host is rejected rather than silently
misinterpreted; a URL whose account differs from the configured account
is refused. The 204 PUT is followed by a GetLine re-fetch so the response
carries the canonical post-update line.
Bumps the SDK to CampfiresService.UpdateLine (basecamp/basecamp-sdk#295,
merge commit 09f7b326), pinned to the merge commit pending a release tag.
Wires up the surface snapshot, catalog, e2e + smoke coverage, and the
basecamp skill documentation.
Adds 'basecamp chat update <id|url> [content]' for editing existing
campfire lines in place via PUT /chats/{c}/lines/{l}, backed by the
SDK's CampfiresService.UpdateLine.
Content handling has four explicit modes, validated before any request:
- default: Markdown converted to HTML unconditionally, then @mentions
resolved (edits always render as rich text server-side);
- --content-type text/html: HTML supplied as-is, then @mentions resolved;
- --content-type text/plain: serialized verbatim via richtext.PlainToHTML
(escaped, line breaks preserved as <br>), mention resolution skipped;
- any other value: rejected with a usage error.
--content-type is applied client-side only; the SDK PUT carries just the
content, and the server coerces every edit to rich text.
URL arguments route through the shared urlarg parser (which gains a
CampfireID field for the chat-line route) plus a hostutil trusted-host
gate, so a pasted card/todo URL, a boundary-evading line ID, or a
look-alike on an untrusted host is rejected rather than silently
misinterpreted; a URL whose account differs from the configured account
is refused. The 204 PUT is followed by a GetLine re-fetch so the response
carries the canonical post-update line.
Bumps the SDK to CampfiresService.UpdateLine (basecamp/basecamp-sdk#295,
merge commit 09f7b326), pinned to the merge commit pending a release tag.
Wires up the surface snapshot, catalog, e2e + smoke coverage, and the
basecamp skill documentation.
Summary
Adds
UpdateCampfireLine(PUT/{accountId}/chats/{campfireId}/lines/{lineId}) so callers can edit existing campfire messages instead of delete-and-repost. The endpoint is accepted by the Basecamp API but was not previously surfaced in the spec.Verified live against a real Basecamp account: PUT returns 204 and the line content updates as expected.
Changes
spec/basecamp.smithy+spec/overlays/tags.smithy: new operation, taggedCampfire, registered in service operationsopenapi.jsonregenerated viamake smithy-buildmake ts-generate ts-generate-services rb-generate rb-generate-services py-generate swift-generate kt-generate-servicesUpdateCampfireLine → updateLine/update_line:typescript/scripts/generate-services.tsruby/scripts/generate-services.rbpython/scripts/generate_services.pykotlin/generator/.../Config.ktswift/Sources/BasecampGenerator/{MethodNaming,ServiceGrouper}.swiftgo/pkg/basecamp/campfires.go: hand-writtenCampfiresService.UpdateLine(ctx, campfireID, lineID, content, *UpdateLineOptions). The API returns 204; the wrapper returns only an error (mirrorsDeleteLine) so a transient post-mutation read can not make a successful update appear to fail. Callers that need the canonical post-update line follow up withGetLine.go/pkg/basecamp/campfires_test.go), 2 Ruby (ruby/test/.../campfires_service_test.rb), 2 TypeScript (typescript/tests/services/campfires.test.ts)typescript/README.md,go/README.md,AGENTS.mdoperation count refreshed (175 → 204)Test plan
make go-check— Go tests + lint passmake ts-check— 618 tests passmake rb-check— 601 tests passmake py-check— 209 tests passmake swift-check— 166 tests passmake kt-check— Kotlin build + tests passmake go-check-drift/make py-check-drift/make kt-check-drift— no driftmake conformance— 64 passed, 0 failedRelated
CLI consumer that exercises this op: basecamp/basecamp-cli#462
Maintainer update (jeremy, 2026-07-22)
The description above reflects the original submission; the branch has since been completed to house standard (details in this comment). Contract-relevant deltas:
content_typewas dropped from the update operation — the server ignores it and coerces every edited line to rich text (HTML). The Go wrapper is nowUpdateLine(ctx, campfireID, lineID, content) errorwith no options struct.main; bc3 provenance pin advanced after a full drift audit (API version2026-07-21).content; campfire line ops added to Python (sync + async), Kotlin, and Swift suites.Summary by cubic
Adds UpdateCampfireLine so clients can edit Campfire messages via PUT
/{accountId}/chats/{campfireId}/lines/{lineId}. Updates are always rich text, and only the line’s creator can edit text/rich‑text lines.2026-07-21; tags/behavior model updated; total operations now 204. Delete docs now state creator‑or‑admin with 403 otherwise.contentrequired;content_typeremoved; docs note rich‑text coercion and creator‑only edits.updateLine/update_line. GoUpdateLine(ctx, campfireID, lineID, content) -> error; TypeScriptupdateLine(...) -> void; both reject empty content; others return void/Unit.content. Live PUT verified (204).Written for commit 4de7624. Summary will update on new commits.