You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
Written for commit 7489fc6. Summary will update on new commits.
basecamp spotlight <id|url> puts a recording's card in the Spotlights
area of its project or template home page, as "Spotlight on project
page" does in Basecamp. basecamp unspotlight <id|url> takes it off, as
"Remove Spotlight" does. Both are idempotent and need edit access to
the project. spotlight returns the recording; unspotlight returns
{id, spotlighted: false}.
A comment's URL acts on the item the comment sits on, since comments
cannot be spotlighted, and a project URL is refused as an invalid ID
rather than sending the project's ID as a recording. When bc3 refuses
a spotlight (a comment, or an item that is not active), the error says
which recordings can be spotlighted instead of a bare "validation
error".
No endpoint reads spotlight state or lists spotlights, and the SDK has
no operation for reordering them, so neither is offered.
Asked to spotlight a message, an agent following the skill's noun-first
discovery read `messages --agent --help`, then `messages pin --agent
--help`, pinned the message, and reported that Basecamp has no separate
spotlight feature. Nothing on that path named `basecamp spotlight`, a
top-level command that no recording type's group lists.
The skill now has a Spotlights section saying spotlight is distinct from
pin and acts on any spotlightable recording, and the agent notes on
`messages pin` and `messages unpin` name spotlight and unspotlight. A
skill eval case holds the observed path: spotlighting a message must call
`spotlight`, never `messages pin`.
Adds top-level spotlight and unspotlight commands backed by the SDK, allowing the CLI to manage recordings in a project's Spotlights area.
Changes:
Registers both commands with ID/URL handling, output summaries, and spotlight-specific error guidance.
Adds unit, end-to-end, smoke, and agent-behavior coverage.
Updates agent guidance and API coverage to distinguish spotlighting from message pinning.
[!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.
File
Description
skills/basecamp/SKILL.md
Adds spotlight discovery and usage guidance.
skill-evals/cases/spotlight-message.yml
Checks that agents spotlight rather than pin.
internal/commands/spotlight.go
Implements spotlight and unspotlight commands.
internal/commands/spotlight_test.go
Tests requests, arguments, output, and validation errors.
internal/commands/messages.go
Adds agent notes distinguishing pins from spotlights.
The notes repeated the long help (idempotent, needs edit access) and
added that no API reads spotlight state. Agents read that last line as
"the result is unconfirmed" and hedged every successful spotlight. The
missing read is a maintainer fact, already recorded in API-COVERAGE.md,
not something a caller acts on.
Use a disposable recording rather than QA_TODOLIST for these two tests. ensure_todolist can select the first existing list (smoke_helper.bash:208–222). If that list is already spotlighted, the preceding spotlight call is a no-op and this unspotlight call removes its original spotlight. There is no read endpoint to save and restore that state. Create a throwaway to-do, share its ID between the tests, and trash it afterward, following the existing recording-test fixture pattern in this file.
spotlight and unspotlight now refuse, before any request, a URL on a host
that is not Basecamp's, a collection URL, and a URL whose type cannot be
spotlighted (a person, a Campfire), instead of acting on whatever ID the
router pulled from it.
The smoke test spotlights and unspotlights a throwaway to-do in one
sequential test, so it can no longer remove a spotlight someone set or race
under bats -j. The spotlight-message eval accepts 789 only as the
positional recording argument.
Collection and non-recording URLs (thread r4354343765): fixed, thread resolved.
Smoke tests could remove a pre-existing spotlight (review summary only): fixed. The two tests in e2e/smoke/smoke_misc_write.bats are now one sequential test. It creates a throwaway to-do, spotlights it, unspotlights it, and trashes it, so it no longer touches the discovered QA to-do list.
Codex (gpt-6.1-sol, run locally on the diff; it has no GitHub threads)
The smoke tests could race under bats -j: fixed by the same single sequential test.
Collection URLs act on their parent: fixed, same as the Copilot thread.
The skill-eval accept pattern matched spotlight 456 --account 789: fixed. It now accepts 789 only as the positional argument (^spotlight (?!.*--help)(?:-\S+ )*789(?:\s|$)), checked under Ruby's Onigmo.
spotlight and unspotlight dropped the account a URL names, so a URL from
account 88888 acted on recording 456 in the configured 99999. A URL's
account is now adopted when none is configured, and refused with the
CLI's usual mismatch error when it differs. Follow-up breadcrumbs carry
--account when the account will not persist into a fresh process.
An occurrence URL (…/schedule_entries/<id>/occurrences/<date>) carries
the recurring event's ID, so the commands acted on the whole series.
They now refuse it before any request and point to the occurrence's own
ID or the event's.
A generic …/recordings/<id> URL was refused while its bare ID worked.
It is now accepted, and bc3 decides whether the recording can be
spotlighted, as it does for a bare ID.
The URL's account was ignored (thread r4354611316): fixed and resolved. The URL's account is used when none is configured, and a mismatch is refused.
Codex (run locally on the diff; it has no GitHub threads)
An occurrence URL (…/schedule_entries/456/occurrences/<date>) spotlighted the whole recurring series: fixed. It is now refused, with a hint to pass the occurrence's own ID.
A …/recordings/<id> URL was refused although the bare ID worked: fixed. It is now accepted.
Codex round 3, on 7489fc6. Both findings are declined for this PR and filed as follow-ups. Each one lives in a shared helper that every <id|url> command uses, so fixing it inside spotlight would add a one-off check and leave the other commands broken.
A URL with extra path segments acts on its parent item.…/occurrences/<date>/edit parses as the whole series, and …/vaults/111/documents/456 parses as folder 111. I confirmed both with urlarg.Parse. recordings trash|archive|restore behave the same way today. The fix belongs in urlarg.Parse: URL arguments with extra path segments act on the parent item #944.
Suggested follow-up commands drop --profile. This comes from the shared replyAccountArg helper, which every command's breadcrumbs use. It can be fixed there once: Suggested follow-up commands drop --profile #943.
Spotlighting the wrong item is visible and undone with one command, so neither issue needs to block this PR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
They call
Recordings().SpotlightandUnspotlight, 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/unpinget an agent note that namesspotlight.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 ispin. 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 ranbasecamp spotlightin 8 of 8.Not included
showorlist.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.batscovers a missing ID.main,basecamp spotlightis an unknown command.bin/cipasses (includesmake check).Summary by cubic
Adds top-level
basecamp spotlight <id|url>andbasecamp 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 theRecordings().Spotlight/UnspotlightSDK operations from v0.27.0. Both are idempotent and require edit access to the project.recordings/<id>URL works like a bare ID, and a comment's URL acts on the item it sits on.spotlightnames the rule instead of surfacing a bare "validation error".messages pin/unpinget agent notes namingspotlight; without them, agents pinned messages instead of spotlighting them (8 of 8 runs in testing).show,list, or reordering.Written for commit 7489fc6. Summary will update on new commits.