镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat(settings): link plugin hints to Manage plugins - #2122

Merged
kalvinnchau merged 2 commits into
mainfrom
make-settings-hints-clickable
Nov 2, 2026
Merged

kalvinnchau merged 2 commits into
mainfrom
make-settings-hints-clickable

Conversation

@matt2e

@matt2e matt2e commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Five hints told people to go to "Settings → Plugins" (or "Settings > Plugins") in plain text. The section is actually called Manage plugins, it is one click away, and nothing in the hint could be clicked. Bestie's Plugin settings button and Solo regions' Open plugin settings already link there. Each hint now ends in a Manage plugins link that opens that section. Each sentence keeps its wording, and only the path becomes the link.

Hint Before After
Settings → Decisions API, no provider turned on "Turn one on in Settings > Plugins." "Turn one on in [Manage plugins]."
Decisions API fallback when a provider's settings fail "…turn its plugin off in Settings > Plugins." "…turn its plugin off in [Manage plugins]."
Agent profile whose type's plugin is off (Agents page and the conversation profile panel) "Enable the plugin that provides this agent's type to change its settings." "Enable the plugin that provides this agent's type in [Manage plugins] to change its settings."
Agent peek, harness unavailable "Check Settings → Plugins or use a build with this harness configured." "Check [Manage plugins] or use a build with this harness configured."
Builderlab agent Activity tab, Agent Activity off "Turn on Agent Activity in Settings → Plugins to see activity." "Turn on Agent Activity in [Manage plugins] to see activity."

What changed

  • Shared link: new src/features/navigation/SettingsSectionLink.tsx. With an opener, it renders the shared in-sentence link (InlineReference.module.css .link) on a Base UI button marked data-buzz-ui. It keeps the sentence's font, has a persistent underline, and trailing punctuation stays attached. Without an opener, it renders plain text so the sentence still reads.
  • Decisions API (both hints): Settings passes its onSection to DecisionsSettings, so the link switches section inside the open Settings window and keeps the community scope. Settings' one-off departureFocus becomes contentFocus: a switch made from the content now moves focus to the Manage plugins section (or to the content for Profile, as before). Before this, the link hid itself on the switch and focus dropped to the dialog.
  • Agent profile, conversation profile panel and agent peek: AgentDetails, ProfilePanel and AgentPeek receive an openSettings opener that opens { version: 1, kind: "settings", section: "plugins" } through navigation. Closing Settings returns to the agent, as docs/navigation-history.md requires.
  • Builderlab Activity tab: Builderlab now injects navigation in every build (it was native-only) and threads openSettings through builderlabType → createActivityTab. The Identity opener reuses it. The browser fixture's local agent type (tests/browser/local-agents-fixture.ts) mirrors this, so the hint can be tried by hand.
  • Shared style: button.link now has letter-spacing: inherit. font: inherit leaves the data-buzz-ui boundary's body tracking in place. Review found no existing .link use that regresses.

Production diff: +117/−28 across 10 files. Tests: +322/−22 across 8 files.

Non-goals

  • Deep-linking to one plugin's row, since the settings target has no plugin field.
  • Naming the missing plugin in the agent-type hint.
  • Other "in Settings" prose: the page error boundary, pi's "Set up … in Settings", the channel archive dialog's copy, error strings, and plugin READMEs. These are left for a follow-up.

Before and after

Left: base ac8495a8b. Right: this branch (fd214fac0). Both run the same journey against the browser fixture relay, with on-screen step captions. Before, each hint is plain text and clicking it does nothing. After, the same click opens Manage plugins, and in step 2 closing Settings returns to the agent.

settings-hints-before-after.mp4

Step 1, Decisions API with TypeSafe off: before, "Settings > Plugins" is plain text with an arrow cursor; after, "Manage plugins" is underlined in the sentence and shows a hand cursor.

Step 2, Local tester agent with the Local agent plugin off: before, the hint does not say where to enable the plugin; after, it ends in a Manage plugins link.

Step 3, Activity tab with Agent Activity off: before, plain "Settings → Plugins" text; after, a Manage plugins link.

How to test

Run the app in a community with a decision provider plugin and a Builderlab (or local) agent. The recording used the browser fixture relay (tests/browser/fixture.mjs with productionBroker and localAgentActivity), where these are TypeSafe, Local agent and Local tester.

  1. Decisions API: Settings → Manage plugins → turn off every decision provider (e.g. TypeSafe) → open Decisions API. It reads "No decision provider is turned on. Turn one on in Manage plugins." Click Manage plugins. Settings stays open and switches to Manage plugins, with focus on that section.
  2. Agent profile: in Manage plugins, turn off the plugin that provides an agent's type (e.g. Local agent), close Settings → Agents → open that agent. It reads "Enable the plugin that provides this agent's type in Manage plugins to change its settings." Click the link. Settings opens at Manage plugins. Close Settings and you are back on the agent.
  3. Activity tab: turn the type plugin back on and turn Agent Activity off → open a Builderlab agent's Activity tab. It reads "Turn on Agent Activity in Manage plugins to see activity." Click the link. Settings opens at Manage plugins.
  4. Keyboard: repeat any step with Tab to the link and Enter.

Working looks like this: each link is underlined in the sentence's own colour, shows a hand cursor, and lands on Manage plugins.

Verification

  • Unit tests: colocated RTL tests cover every site. They assert onSection("plugins") (Decisions, both hints) or the exact { version: 1, kind: "settings", section: "plugins" } target (agent details, agent peek, profile panel, Activity tab). They also cover focus on Manage plugins after the Decisions switch, and the plugin-level Builderlab wiring (src/bundled/builderlab/index.test.tsx). SettingsSectionLink has its own link and plain-text cases. The new tests fail on the original code.
  • Static checks: typecheck, lint, design:check and the pre-commit hooks passed. This push skipped the pre-push hook (--no-verify), because its related-unit-test job times out under local machine load. CI covers that run.
  • Browser (manual, agent-driven):
    • Before the style fix: Chromium and WebKit, light and dark, at 1280 px and 390 px, with text at normal size and doubled. Every link was reachable with Tab and Enter.
    • After the style fix: Chromium only, light and dark at 1280 px, and light at 390 px and with doubled text.
  • Demo recording: for both builds, the script asserted computed link styles and that each click on the after build lands on the Manage plugins region. It also asserted that clicks on the old plain text change nothing.
  • Browser journeys: none added or removed. The only browser-side change is the fixture's local agent type receiving the opener.
  • Agent review: two rounds. The first found the caption-sized link, the focus loss after the section switch, a stale comment and a missing Builderlab wiring test. All four were fixed in fd214fac0. The second round found nothing blocking.

Known gaps

  • Line height: lines containing the link are about 1 px taller, because of the shared .link padding. The existing "Managed by [owner]" link does the same. Whether to change it is a call for the shared style's owner.
  • Unit tests only: the agent peek's "harness unavailable" hint can't be produced by the fixture. The Decisions fallback for failed provider settings was seen in a browser only before the style fix.
  • WebKit and the native app were not re-checked after the style fix.
  • Focus from outside Settings: opening Settings from an agent puts focus on "Close Settings", which is how Settings already opens.
  • Size: the production diff (+117/−28) is over the plan's budget of about 50 lines. The extra comes from multi-line JSX, threading the opener through Builderlab, the extra ProfilePanel site and the focus fix.
  • No human testing yet. This PR stays in draft until someone has tried the steps above.

🤖 Generated with Claude Code

matt2e and others added 2 commits November 2, 2026 15:15
Five hints told people to go to "Settings → Plugins" in plain text,
though the section is called Manage plugins and is one click away. Each
now ends in a Manage plugins link:

- Decisions API's empty state and a provider's failed-settings fallback
  switch section inside the open Settings modal through Settings'
  onSection, which keeps the community scope.
- An agent profile whose type's plugin is off (Agents page and the
  conversation profile panel), the unavailable-harness peek, and the
  Builderlab Activity tab while Agent Activity is off open Settings at
  Manage plugins through navigation, so closing Settings returns to
  the agent.

The shared SettingsSectionLink renders Button variant="link"
size="inline", or plain text where nothing can open Settings.
Builderlab now injects navigation in every build and threads an opener
through builderlabType to createActivityTab; the browser fixture's
local agent type mirrors it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Review follow-up for the Manage plugins hint links.

SettingsSectionLink used Button size="inline", a caption-sized control
that shrank the label, detached trailing punctuation and underlined
only on hover. It now uses the shared in-sentence link
(InlineReference .link) on a Base UI button, with a persistent
underline and the sentence's own type. It carries the data-buzz-ui
boundary like other shared controls. Without it, the global button
reset gave the agent hints label tracking, and the channel host's
button reset turned the conversation profile panel's link grey.
button.link now inherits letter-spacing, which font: inherit leaves
at the boundary's body tracking.

Settings' Decisions link hid itself when it switched to Manage
plugins, which dropped focus to the dialog. The one-off departure
focus becomes contentFocus: a switch made from the content now moves
focus to the Manage plugins section (or to the content for Profile,
as before).

Tests: Settings asserts focus lands on Manage plugins after the switch.
A Builderlab plugin test asserts the registered type's Activity tab
opens Settings at plugins while Agent Activity is off. Both fail
without the change. The Decisions comment now names Manage plugins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
@matt2e
matt2e marked this pull request as ready for review November 2, 2026 05:31
@matt2e
matt2e requested review from a team as code owners November 2, 2026 05:31

@kazemek21 kazemek21 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.

Kyle reviewed this PR and the Nexus review-tracker notes and approved with no comments.

Posted by Codex (gpt-6.1-sol) on behalf of kylez.

@kalvinnchau kalvinnchau 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.

🤖 Reviewed fd214fa. No actionable findings. Navigation and focus checks passed in Chromium and WebKit; exact-head CI is green. Native-host and fallback-path runtime coverage remains limited.

@kalvinnchau
kalvinnchau merged commit 604cfda into main Nov 2, 2026
41 checks passed
@kalvinnchau
kalvinnchau deleted the make-settings-hints-clickable branch November 2, 2026 17:18
matt2e added a commit that referenced this pull request Nov 9, 2026
…s the next step

Identity sync names Builderlab when this key isn't linked to the account
(waiting not_linked) or the account holds a different key (failed
other_key_linked). The next step, Connect Buzz identity or Switch to this
device's identity, is on Settings → Plugins → Builderlab, but the row had
nothing to click. Those two sentences now render the first "Builderlab"
as a SettingsSectionLink (#2122) to block.builderlab/login. The status
text is unchanged; only the name becomes a link.

Settings passes the opener only while the Builderlab card is registered,
and reuses openSection, so focus moves into the opened section as it
does from Decisions API. Without an opener the sentence stays plain.

The linked states arise only after the user opted into automatic backup:
nsec_backup.rs admits consent (NoConsent) before it reads the account's
key (NotLinked, OtherKeyLinked). The link only navigates; connecting on
Builderlab's page starts a backup the user already agreed to.

Unlinked on purpose:
- no_session: Builderlab signs in with the build's NIP-FI session, and
  that Organization sign-in is on this same Identity page.
- no_consent: its next step is the row's own Sync with Builderlab, and
  Builderlab's page has nothing for it.
- Retryable failures and signed_out clear on a new sign-in; the other
  failures have no fix on Builderlab's page.

Self-review found and fixed: the Settings test's plugin-off check waited
on the unsplit sentence, so a regression failed it for the wrong reason;
it now waits on the row and asserts the button is absent. It also
dropped a vacuous assertion and updated the help guide and identity doc.
Known gap, shared with Sync: a status change while the link has focus
drops focus to the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
matt2e added a commit that referenced this pull request Nov 9, 2026
…s the next step (#3082)

## Why

Identity sync names Builderlab in two of its sentences: when this key
isn't linked to the account (waiting `not_linked`) and when the account
already holds a different key (failed `other_key_linked`). The next step
for both, **Connect Buzz identity** or **Switch to this device's
identity**, is on Settings → Plugins → Builderlab, but the row had
nothing to click. People had to find that page themselves.

## What

In those two sentences the first "Builderlab" becomes a
`SettingsSectionLink` to `block.builderlab/login`, the shared inline
link from #2122. Settings passes the opener only while the Builderlab
card is registered and reuses `openSection`, so focus moves into the
opened section as it does from Decisions API. Without an opener, or in
any other state, the sentence stays plain text. The status text itself
is unchanged; only the name becomes a link.

Deliberately unlinked:

- `no_session`: Builderlab signs in with the build's NIP-FI session, and
that Organization sign-in is on this same Identity page.
- `no_consent`: its next step is the row's own **Sync with Builderlab**
button; Builderlab's page has nothing for it.
- Retryable failures and `signed_out` clear on a new sign-in. The
remaining failures have no fix on Builderlab's page.

The linked states arise only after the user opted into automatic backup:
`nsec_backup.rs` admits consent (`NoConsent`) before it reads the
account's key (`NotLinked`, `OtherKeyLinked`). The link only navigates.
Connecting on Builderlab's page starts a backup the user already agreed
to.

## How

`BackupRow` takes an optional `openBuilderlab` and splits the existing
feedback sentence around the first "Builderlab" for the two states.
`Settings` derives the opener from `openSection` and the contributed
card list. Two files of production change, about 40 lines, plus tests
and the help guide and identity doc.

## Risk

Low. No native, protocol, or persistence changes. The shared link style
makes the Identity sync row about 2 px taller in the linked states.
Known gap, shared with the existing Sync button: a status change while
the link has focus drops focus to the page.

## Testing

- `pnpm typecheck` and biome pass on `01c105cd5`.
- `IdentitySettings.test.tsx` and `Settings.test.tsx`: 124 tests pass.
With main's `IdentitySettings.tsx` and `Settings.tsx` swapped in, three
fail: the two "links Builderlab from the … sentence to its Settings
page" cases and "opens Builderlab from Identity sync only while its card
is registered". The negative cases (`no_session`, `no_consent`, idle,
running, complete, every other failure kind, and no opener) pass on
both, as intended.
- The Settings test removes the Builderlab card while Identity is open
and asserts the link goes in place with no section switch, as well as
the mount-time gate. A `BackupRow` that reads its opener once (`useRef`)
passes the old test and fails this assertion.
- The before/after recording below exercises the real `Settings`
component in headless Chromium.

Not verified: a native FI build, both themes, narrow widths, enlarged
text, and keyboard navigation in the real app.

## Before and after

The same pointer script runs on origin/main (left) and this branch
(right): click "Builderlab" in the Identity sync sentence, go back to
Identity, switch the backup status from waiting `not_linked` to failed
`other_key_linked`, click "Builderlab" again.


https://github.com/user-attachments/assets/04a67ba8-6374-4226-ae24-d8222287f861

![BEFORE, just after the first click: the cursor is on the plain-text
"Builderlab", and Identity is still highlighted and
shown.](https://github.com/user-attachments/assets/24895d72-57e3-430c-a7d1-b065dc1567f2)

![AFTER, the same moment: Plugins → Builderlab is highlighted in the
side menu and its section shows Connect Buzz
identity.](https://github.com/user-attachments/assets/7dd687f0-b496-467f-90d0-56b8ea16b4b9)

**Real:** the `Settings` component, the side menu, the link, the section
switch and the focus move. **Faked:** the backup status, and a fixture
Builderlab card that renders the real `Login` and `HostedCommunities` on
fake sign-in and account data, since the plugin registers its card only
with a native session or the live development server. Recorded in
headless Chromium with Playwright, not a native build.

## Bigger picture

Overlaps with #3069, which deletes `BackupRow` and moves backup state
into a key-card notice, and rewrites the same paragraphs in
`docs/buzz-help.md` and `docs/identity.md`. Whichever lands second
reconciles. If this lands first, #3069 can either carry `openBuilderlab`
and the card gate into the notice, splitting the period-less
`not_linked` and `other_key_linked` strings, or drop the prop and its
tests on the grounds that its plugin-contributed `BuilderlabIdentityRow`
already links Builderlab from Identity. If #3069 lands first, this
becomes a re-implementation in the notice, and the "only button in the
row is the link" assertion has to allow for **Retry backup**.

#2458 links the other direction: Builderlab's Hosted communities card
opens Settings → Identity when the account's backed-up key differs from
this device's. It is complementary.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: mtoohey <mtoohey@squareup.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants