Skip to content

style(chat): custom Claude spinner verbs from ~/.claude/settings.json#356

Open
mcginnessjack wants to merge 2 commits into
plmbr:mainfrom
mcginnessjack:claude-spinner-verbs
Open

style(chat): custom Claude spinner verbs from ~/.claude/settings.json#356
mcginnessjack wants to merge 2 commits into
plmbr:mainfrom
mcginnessjack:claude-spinner-verbs

Conversation

@mcginnessjack
Copy link
Copy Markdown
Contributor

@mcginnessjack mcginnessjack commented Jun 2, 2026

Summary

  • Reads spinnerVerbs.verbs from ~/.claude/settings.json and cycles them in the generating label when Claude Mode is active, replacing the static "Generating" text
  • Fisher-Yates shuffle with random 4–7s delay per verb; no immediate repeats across shuffle passes
  • Separates visible label (aria-hidden) from a hidden aria-live div containing only the verb, so screen readers announce on verb changes only — not on every elapsed-seconds tick

Settings shape consumed

{
  "spinnerVerbs": {
    "mode": "replace",
    "verbs": ["Doing Pull Ups", "Caffeinating", "Kwisatz-haderaching"]
  }
}

mode: "replace" is the only supported mode. Falls back to "Generating" if Claude Mode is off, verbs are absent, or the file can't be read.

Test plan

  • Enable Claude Mode, ensure ~/.claude/settings.json has spinnerVerbs.verbs
  • Send a chat message — verify custom verbs appear and cycle with varied timing
  • Verify cycling resumes from a randomized verb on each new request (no mid-sequence start)
  • Disable Claude Mode — verify fallback to "Generating"
  • Remove spinnerVerbs from settings — verify fallback to "Generating"
  • Screen reader: verify announcements fire on verb change only, not every second

🤖 Generated with Claude Code

Evidence:

image

Reads spinnerVerbs.verbs from ~/.claude/settings.json and cycles through
them in the generating label instead of the static "Generating" text when
Claude Mode is active.

- Backend: _read_claude_spinner_verbs() reads ~/.claude/settings.json
  safely and exposes spinner_verbs in the capabilities response
- Frontend: NBIConfig.spinnerVerbs getter; ChatResponse cycles through
  verbs using a Fisher-Yates shuffle with random 4-7s delay per verb,
  no immediate repeats across passes
- Accessibility: separates visible label (aria-hidden) from a sr-only
  aria-live div containing only the verb text, so screen readers
  announce on verb changes only, not on every elapsed-seconds tick

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@pjdoland pjdoland added the enhancement New feature or request label Jun 2, 2026
@pjdoland
Copy link
Copy Markdown
Collaborator

pjdoland commented Jun 2, 2026

This is absolutely unnecessary.

But it's fun and I love it.

Let Claude be Claude.

@mcginnessjack
Copy link
Copy Markdown
Contributor Author

This is absolutely unnecessary.

But it's fun and I absolutely love it.

If you aren't helping sisyphus with his boulder or watching LeBron highlights, you are Clauding wrong

@mcginnessjack mcginnessjack marked this pull request as ready for review June 2, 2026 01:11
@pjdoland pjdoland removed the enhancement New feature or request label Jun 2, 2026
@mcginnessjack mcginnessjack changed the title feat(chat): custom Claude spinner verbs from ~/.claude/settings.json style(chat): custom Claude spinner verbs from ~/.claude/settings.json Jun 2, 2026
@pjdoland
Copy link
Copy Markdown
Collaborator

pjdoland commented Jun 2, 2026

Really nice work on this, it's a fun feature and clearly built with care. A few things stood out as I read through it.

A couple of things I appreciated:

  • The accessibility split is genuinely thoughtful. Moving the elapsed-seconds suffix into the visible (aria-hidden) label and leaving only the verb in the aria-live region means screen readers announce on verb changes instead of re-reading the label every second. That actually improves the default "Generating" experience too, not just the custom-verbs case.
  • Initializing the shuffle synchronously in useState so the first paint shows the right verb (rather than a useEffect that flashes verbs[0] before correcting) is a nice touch, and the no-repeat-at-the-wrap-point detail in the shuffle is a careful bit of polish.
  • On the backend, returning only the spinnerVerbs subtree rather than the whole settings.json keeps any API keys in that file off the wire, which is the right call.

Three small things I'd suggest before merge:

  1. A malformed verbs entry can break the chat render. The guard checks that verbs is a non-empty array, but not that each entry is a string. If someone's settings.json has an object in the list (say verbs: [{...}]), React throws "Objects are not valid as a React child" when it renders the verb, and since there's no error boundary around the chat sidebar, that can take the whole render down rather than just the spinner. A plain number or stray string wouldn't trigger it; it specifically needs an object entry, so it's an unlikely self-inflicted case. Still, it would be nice if a malformed config couldn't break the UI. A quick verbs.every(v => typeof v === 'string') added to the hasCustomVerbs check would cover it. Validating the shape on the backend (returning the verbs only when they're well-formed) would be an even tidier home for it, and it would match how the other capabilities fields are shaped server-side.

  2. _read_claude_spinner_verbs is missing a return type annotation. The neighboring helpers (_scrub_credentials_for_wire, _build_setting_locks_response, and so on) all annotate their return, so adding -> Optional[dict] would keep it consistent. Purely a convention thing, nothing enforced in CI.

  3. Redundant import json. There's an import json inside _read_claude_spinner_verbs, but json is already imported at the top of extension.py, so that line can come out.

One note on CI: the only failing check is Prettier formatting on src/chat-sidebar.tsx. It's purely mechanical, nothing to do with the logic. A quick jlpm lint (or jlpm prettier:base --write src/chat-sidebar.tsx) and a re-push should turn the build green.

None of these are blockers, the feature works and reads well. Thanks for putting it together, the spinner verbs are a delight.

@pjdoland pjdoland added the enhancement New feature or request label Jun 2, 2026
- Validate spinnerVerbs shape fully on the backend: only return the
  object when mode == "replace", verbs is a non-empty list, and every
  entry is a string — malformed configs now silently fall back to
  "Generating" rather than potentially crashing the chat render
- Add Optional return type annotation to _read_claude_spinner_verbs
- Remove redundant `import json` inside the function (already imported
  at module level)
- Fix Prettier formatting on chat-sidebar.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mcginnessjack mcginnessjack force-pushed the claude-spinner-verbs branch from e733907 to 55fa320 Compare June 3, 2026 00:26
@pjdoland pjdoland added this to the 5.1.x milestone Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants