Add spotlight and unspotlight

Merged
RO
8h ago
+524-6

Basecamp card: https://app.basecamp.com/2914079/buckets/48521764/card_tables/cards/10256947017

What

Two top-level commands for Basecamp's Spotlights, the area at the top of a project's home page:

basecamp spotlight <id|url>      # "Spotlight on project page" in Basecamp
basecamp unspotlight <id|url>    # "Remove Spotlight" in Basecamp

They call Recordings().Spotlight and Unspotlight, which shipped in SDK v0.27.0. Both are idempotent. A comment's URL acts on the item the comment sits on, because comments cannot be spotlighted. A project URL is rejected as an invalid ID instead of sending the project's ID as a recording. When bc3 refuses a spotlight with a bare 422, the error names the rule instead of printing "validation error".

The basecamp skill gets a short Spotlights section, and messages pin / unpin get an agent note that names spotlight.

Why

The card asks for Spotlights in the SDK and CLI. The SDK half is done, so this adds the CLI half.

The skill section is needed because, without it, agents pin instead. The skill's discovery starts from the noun, so "spotlight this message" leads to messages --help, where the closest match is pin. With only the commands added, Claude Code (Sonnet, this branch's plugin) pinned the message in 8 of 8 runs and said Basecamp has no spotlight feature. With the skill section, it ran basecamp spotlight in 8 of 8.

Not included

  • The API has no way to read spotlight state or list spotlights, so there is no show or list.
  • Reordering spotlights (PUT /recordings/:id/spotlight/position) has no SDK operation yet.

Testing

  • spotlight_test.go: each command sends one request with the right method and path (POST/DELETE /recordings/{id}/spotlight.json) for an ID, a URL and a comment URL. Non-recording arguments send no request. A 422 comes back with the hint. e2e/spotlight.bats covers a missing ID.
  • Ran the built binary against local bc3 and checked bc3's database after each step. Spotlight by URL, then again (still spotlighted). Spotlight a to-do list. Unspotlight both, then unspotlight again (still not spotlighted). A pre-existing spotlight was left alone. On main, basecamp spotlight is an unknown command.
  • Recorded Claude Code driving the commands from plain-English requests next to the project page on local bc3, with the page updating after each step. Video attached below.
  • bin/ci passes (includes make check).

Summary by cubic

Adds top-level basecamp spotlight <id|url> and basecamp unspotlight <id|url> commands that put a recording's card in, or take it off, the Spotlights area of its project or template home page, backed by the Recordings().Spotlight/Unspotlight SDK operations from v0.27.0. Both are idempotent and require edit access to the project.

  • URLs that don't name one spotlightable recording are refused before any request: a project or collection URL, an untrusted host, a non-spotlightable type (a person, a Campfire), or an occurrence URL that would act on the whole recurring series. A generic recordings/<id> URL works like a bare ID, and a comment's URL acts on the item it sits on.
  • A URL's account is adopted when none is configured and refused when it differs, so a URL never retargets the configured account.
  • A bc3 refusal on spotlight names the rule instead of surfacing a bare "validation error".
  • The basecamp skill gains a Spotlights section, and messages pin/unpin get agent notes naming spotlight; without them, agents pinned messages instead of spotlighting them (8 of 8 runs in testing).
  • No endpoint reads spotlight state or lists spotlights, and the SDK has no reorder operation, so there is no show, list, or reordering.