Skip to content

Add spotlight and unspotlight - #941

Merged
robzolkos merged 5 commits into
mainfrom
spotlight-recordings
Oct 22, 2026
Merged

robzolkos merged 5 commits into
mainfrom
spotlight-recordings

Conversation

@robzolkos

@robzolkos robzolkos commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

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.

Written for commit 7489fc6. Summary will update on new commits.

View guided diff Turn on auto-fix

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`.
Copilot AI balanced review requested due to automatic review settings October 22, 2026 22:44
@robzolkos
robzolkos requested a review from a team as a code owner October 22, 2026 22:44
@github-actions github-actions Bot added commands CLI command implementations tests Tests (unit and e2e) skills Agent skills docs labels Oct 22, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

URL validation discards resource metadata, allowing non-recording or collection URLs to target unintended recordings.

1 open issue
What changed in this PR

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.
internal/​commands/​commands.go Adds both commands to the catalog.
internal/​commands/​commands_test.go Registers commands in catalog tests.
internal/​cli/​root.go Registers both commands in the CLI.
e2e/​spotlight.bats Tests missing-ID errors.
e2e/​smoke/​smoke_misc_write.bats Adds live spotlight and unspotlight checks.
API-COVERAGE.md Records coverage of both SDK operations.
.surface Updates the command and flag snapshot.

Review effort: Balanced

Comment thread internal/commands/spotlight.go Outdated
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.
Copilot AI balanced review requested due to automatic review settings October 22, 2026 23:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

URL host validation is missing, and the smoke tests can remove a pre-existing spotlight.

2 open issues
1 previously missed issue

In code that hasn't changed since last review

Medium severity Use a disposable recording in spotlight tests

e2e/​smoke/​smoke_misc_write.bats:102

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.

Review effort: Balanced

Comment thread internal/commands/spotlight.go Outdated
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.
Copilot AI balanced review requested due to automatic review settings October 22, 2026 23:27
@robzolkos

Copy link
Copy Markdown
Collaborator Author

Review round on 7ed24c0, addressed in bbef814.

Copilot

  • Untrusted URL host (thread r4354475673): fixed, thread resolved.
  • 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.

bin/ci passes on bbef814.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Both commands ignore the account encoded in URLs and can send mutations under a different configured account.

1 open issue
2 resolved since last review

Review effort: Balanced

Comment thread internal/commands/spotlight.go Outdated
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.
Copilot AI balanced review requested due to automatic review settings October 22, 2026 23:48
@robzolkos

Copy link
Copy Markdown
Collaborator Author

Round 2, on bbef814, addressed in 7489fc6.

Copilot

  • 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.

bin/ci passes on 7489fc6.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approved

No unresolved blocking issues were identified in the implementation, integration, or supplied regression coverage.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@robzolkos

Copy link
Copy Markdown
Collaborator Author

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.

@robzolkos
robzolkos merged commit 3c14d96 into main Oct 22, 2026
33 checks passed
@robzolkos
robzolkos deleted the spotlight-recordings branch October 22, 2026 23:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands CLI command implementations docs skills Agent skills tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants