Post a hill chart progress update

Merged
RO
2d ago
+2474-55

Adopts POST /{accountId}/todosets/{todosetId}/hills/versions.json from basecamp/bc3#14214, which posts a progress update on a to-do set's hill chart.

basecamp/bc3#14214 merged at 4303f1dafd4. This PR repins bc3 to that commit (25b74b2a): the route table now carries the route (430 to 431 routes), the spec/bc3-route-allowlist.yml waiver that carried it ahead of the pin is deleted, and spec/api-gaps/README.md records the range's triage. The range's other two API changes, bc3#14191 (a 422 for creating under a trashed recording, and moves restoring trashed recordings) and bc3#14176 (a reply_token on a chatbot command's callback_url), add no route or field the SDK models.

Card: https://app.basecamp.com/2914079/buckets/48521764/card_tables/cards/10457799215

The operation

CreateHillChartVersion. Every field is optional:

  • description: rich-text HTML.
  • dots: [{id, position}], where id is a tracked todolist and position runs 0 to 100.
  • subscriptions: the person ids to notify.

It answers 201 with the same HillChartVersion shape GetHillChartVersion returns. It answers 422 {error} when a dot doesn't validate, 403 for clients and 404 for an unknown to-do set. It's a non-idempotent create with the usual inert retry block, so every runtime sends it once (SPEC §7 Gate 2).

Added per language

LanguageAPI
GoHillChartsService.CreateVersion(ctx, todosetID int64, req *CreateHillChartVersionRequest) (*HillChartVersion, error), with new HillChartDotPosition{ID int64; Position int} and CreateHillChartVersionRequest{Description string; Dots []HillChartDotPosition; Subscriptions *[]int64}. req may be nil. A nil Dots is omitted and a non-nil one is sent even when empty. A position outside 0 to 100 is an ErrUsage before any request.
TypeScripthillCharts.createVersion(todosetId, req: CreateVersionHillChartRequest). The request type is exported from the package root.
Rubyhill_charts.create_version(todoset_id:, description: nil, dots: nil, subscriptions: nil)
Pythonhill_charts.create_version(*, todoset_id, description=None, dots=None, subscriptions=None), sync and async
KotlinhillCharts.createVersion(todosetId, CreateHillChartVersionBody(description, dots, subscriptions))
SwifthillCharts.createVersion(todosetId:req: CreateHillChartVersionRequest(...)), with the new HillChartDotPosition model
Rusthill_charts().create_version(todoset_id, &CreateHillChartVersionRequestContent { .. }), with the new HillChartDotPosition type

Also in this PR:

  • Shared fixtures spec/fixtures/hill_charts/create-version{,-request}.json. The request is bc3's documented one.
  • Four conformance cases in conformance/tests/hill_chart_versions.json, dispatched in all seven runners:
    • the documented request asserted as the exact whole body;
    • an empty request sends {};
    • a 422 maps to a validation error with bc3's message;
    • a 503 is not retried.
  • Two corrections:
    • The Go conformance runner's toInt64Slice now reads json.Number. Fixtures decode with UseNumber, so it was silently sending [] for every id array.
    • SPEC.md's non-idempotent count is now 192. It was already stale before this change.

Testing

Unit tests per language:

LanguageTestsResult
Go4 new: exact body, nil and empty request, off-hill positions with no request sent, 42216/16 hill chart tests pass
TypeScript3 new14/14 pass, plus type tests and typecheck
Ruby3 new18 runs, 0 failures
Python4 new13 pass
Rust2 wire tests and a 422 error_case!10/10 hill chart tests pass; the full basecamp-sdk suite passes
Kotlin3 new8/8 pass
Swift3 newNot run locally: no macOS here, and the Swift SDK doesn't build on Linux. CI covers it.

Conformance passed in every runner here, the new cases included:

RunnerPassedSkipped
Go3402
Python3384
Ruby31923
Rust3384
Kotlin3357
TypeScript3956
Swiftnot run (macOS only)

I ran make -k check-targets locally. Everything passed except the Swift targets, which skip on Linux, and four failures:

  • golangci-lint, actionlint and cargo-deny failed only because mise had no version pinned in the worktree. Each passed when rerun on a pinned version: golangci-lint 2.15.0 reported 0 issues, actionlint 1.7.12 was clean, and cargo-deny reported ok.
  • Rust guarantees.rs pinned the operation count at 294. It's now 295.

Semver and release notes

  • Minor, breaking for one narrow case. It's additive in every SDK. The exception is Go code that implements the generated ClientInterface / ClientWithResponsesInterface itself: those interfaces gain CreateHillChartVersion and its WithBody and WithResponse forms. MIGRATING.md has the # Unreleased section, so the PR is labelled breaking.
  • Release-note section: Features (enhancement).

Review

A cross-family review of origin/main...HEAD found one issue, now fixed:

  • Fixed: the Ruby empty-request test asserted with body: {}, which WebMock treats as matching any body, so the test couldn't catch a null or field-of-nulls regression. It now parses the body and compares it to {} (8e586a0e).

There were no other findings and nothing was declined. The local check run separately caught the stale Rust operation count, fixed in df6afc7a.


Summary by cubic

Adds CreateHillChartVersion to all seven SDKs (createVersion / create_version), letting callers post a progress update on a to-do set's hill chart. Every field is optional: a rich-text description, dots moving tracked todolists (position 0–100), and people to notify. It returns 201 with the same HillChartVersion shape as GetHillChartVersion, and 422 for invalid dots or 404 for an unknown to-do set.

Adopts basecamp/bc3#14214, which merged at 4303f1dafd4. This PR repins bc3 to that commit: the route table grows from 430 to 431 routes, the waiver that carried the route ahead of the pin is deleted, and the spec's API-gap triage records that the range's other changes add no route or field.

Breaking change

  • Go's generated ClientInterface / ClientWithResponsesInterface gain CreateHillChartVersion plus its WithBody and WithResponse forms; hand-written implementations must add them.
  • Fixes the Go conformance runner's toInt64Slice to read json.Number, which was silently sending [] for every id array.