The pane above the divider is the shipped one, structure unchanged. The table is added underneath: the connection is set up once, the projects list is what you come back to check.
Agents only see open projects
Adds Bristlenose as a Claude Desktop extension. Confirm the install, then Allow access to data from other apps.
| Access | Project | Sessions | Last asked |
|---|---|---|---|
| Active | |||
🏉foo
|
3 | Just now | |
🐟IKEA with uxfriendsminimised |
1 | 12 min ago | |
🎓New Project 2
Access turned off |
4 | Never | |
| Available when opened | |||
🌪folder-of-horrors |
42 | Never | |
Last asked covers this session only — no history is kept.
The pane lists only projects that have Agent Access on — so a researcher who has never turned it on sees nothing, and this is the one moment the audit surface has to point at the management surface.
| Key | Shipped today | Proposed | Cost |
|---|---|---|---|
| STRINGS THIS SECTION NEEDS | |||
mcpAgents.groupActive | — | Active | New · 21 |
mcpAgents.groupAvailable | — | Available when opened | New · 21 |
mcpAgents.colAccess | — | Access — headed so it does not read as cloud import's batch-selection column; short so the column stays narrow. The full verb lives in the tooltip. | New · 21 |
menu.project.turnOffAgentAccess | Turn Off Agent Access | reused — tooltip + a11y label on the checkbox, so the column can stay narrow ("Access") while the control still speaks the sidebar's verb | None |
mcpAgents.colSessions | — | Sessions | New · 21 |
mcpAgents.colLastAsked | — | Last asked | New · 21 |
mcpAgents.lastAskedNever | — | Never — an em-dash announces as nothing | New · 21 |
mcpAgents.receiptOff | — | Access turned off — session receipt, gone next open; short to match the column header | New · 21 |
mcpAgents.windowMinimised | — | minimised — explains an Active row with no visible window | DEFERRED 21 Aug 2026 — not seeded, not built `WindowRoster` deliberately holds no window state (its own doc comment declines to become "an approximation of AppKit's mainWindow"), so this needs new per-window observers plumbed into it. And minimised is only one of four reasons an Active row has no visible window — behind Settings, another Space, and a window tab are the others — so singling it out is arbitrary unless it is the common one. The group header already carries the fact that decides exposure. Additive whenever it is wanted. |
mcpAgents.emptyTitle | — | No projects are shared with agents | New · 21 |
mcpAgents.emptyHint | — | Select a project, then choose Project ▸ Turn On Agent Access. — names the menu-bar path, not a gesture: the HIG calls it Control-click or secondary click, never right-click, and requires every context-menu command to exist in the menu bar anyway | New · 21 |
mcpAgents.sessionScopeNote | — | Last asked is remembered only while a project is open. — the times live in memory per serve, so a project asked about yesterday reads Never after a relaunch. Reworded 22 Aug 2026: this used to end “— no history is kept”, which is a product-wide negative claim and is false. Every MCP tool call is logged to the project’s bristlenose.log, and has been since 30 Jul. The clause is cut rather than replaced with a pointer — this line labels a column, it is not the privacy notice. That is SECURITY.md § Agent access record; reasoning in design-mcp-server.md §7a. | New · 21 |
mcpAgents.rollup | — | Open to agents: {{p}} projects · {{s}} sessions — renamed from “Readable now” on 22 Aug 2026. That noun promised reachability while the count is a permission (window open + Agent Access on), so it was true only while the sidecar happened to be healthy. “Open to agents” is immune to serve state and echoes the header sentence above the table. Decision and the rule bounding the predicate: design-mcp-extension.md §5a-ter. | New · 21 · plurals |
| STRINGS THE MODEL MAKES FALSE | |||
mcpAgents.header |
Agents read one project at a time — the one you turned on most recently | Agents only see open projects — shortened again on 21 Aug 2026 and set as body copy: the Agent Access half is the register's tick column, and a heading was competing with the register's own | Reword · 21 shipped string is now false |
mcpAgents.badgeTooltip |
Agent Access is on for this project. Agents read one project at a time. | second sentence must go — it is false under plural scope, and this is the sidebar tooltip | Reword · 21 |
mcpAgents.nowShowing |
Agents are reading: {{project}} | retired — the table replaces the single-project line | Retire · 21 |
| REWRITES OF STRINGS THE MODEL DOES NOT TOUCH — each needs its own reason | |||
menu.quotes.anonymise | Anonymise | unchanged — shared with Export, one word for one concept | — |
menu.quotes.anonymiseHint |
Remove participant names | Agents see speaker codes | Fork · 21 the Quotes menu and Export share this key |
mcpAgents.claudeDesktopHint |
Adds Bristlenose to Claude Desktop as an extension. Claude Desktop will ask you to confirm. | Adds Bristlenose as a Claude Desktop extension. Confirm the install, then Allow access to data from other apps. absorbs the prompt note — two dialogs, in order, one string |
Reword · 21 |
mcpAgents.claudeDesktopPromptNote |
The first time you ask a question, macOS asks once whether Claude may access data from other apps. Click Allow — that permission is how the extension finds Bristlenose. | retired — folded into the hint above | Retire · 21 |
setWindowFrame sizes the window from
view.fittingSize, and an unpinned payload is what makes that size
honest. Because the package runs it only on tab activation,
SettingsWindow.refitToContent() performs the same arithmetic when
the client picker or the register changes shape underneath it. (21 Aug 2026.)fittingSize and animates the
window to match, so an unbounded table makes the window grow with the project
count: fine at four rows, a window taller than the display at twenty-five.
342px holds nine rows plus both group headers. Nine because the common case
should never scroll — past that the sticky headers, which were drawn before
anything could scroll under them, finally do the job they were drawn for.
It is a maximum, not a height — fewer than nine rows and the table is
as tall as its rows, so the pane reports a smaller fittingSize
and the Settings package sizes the window to it. Precisely: the package
calls setWindowFrame from two places only, both tab
activation (measured 21 Aug 2026), so the shrink happens every time you
arrive at the pane and not live while you are looking at it — a row
appearing or disappearing under your eyes compresses rather than resizes.
That does not weaken the rule: a clamped greedy ScrollView would
report 342pt for two rows at activation too, so the pane would open
tall and empty every single time. The switch pays for itself at the only
moment the package measures anything. Watch this in
the build: SwiftUI's ScrollView is greedy along its scroll axis,
so ScrollView { rows }.frame(maxHeight: 342) claims all 342pt
even for two rows and the shrink never happens. Switch on the row count
instead — nine or fewer renders a plain VStack with no scroller
at all, more than nine renders the bounded one. The count is known before
layout, so this needs no measurement, same reasoning as the Sessions grid
being container queries rather than JS width-switching.
This is also what keeps the pane's geometry rule intact: the fixed
170pt payload holds only the client tabs, hint and install row, so switching
Claude Desktop → Claude Code reflows inside that region and the table
below never moves.MCPAgentsSettingsView.swift:153
deliberately reuses the Export strings — the comment reads "Same strings as
Export: one word for one concept." "Agents see speaker codes" is truer here
(it names the result in this context rather than the action), but it needs a new
key in 21 locales and it means the concept has two hints. The label itself stays
Anonymise — I had invented "Hide names", which was not a shipped string and
should not have been drawn.Project carries no description or study-type field — the sidebar's
second line shows state, never prose. Rows are single-line, uniform height, and
the one real window fact that needs saying ("minimised", which explains an Active
row you cannot see) rides as a dimmed suffix.menu.project.turnOffAgentAccess, "Turn Off Agent Access" — the
same words the sidebar's menu item uses. One grammar, two renderings, no new
string, and gruber's two-grammars objection disappears.design-mcp-extension.md:1746 already
settled on 1 Aug: the context menu is the act, the antenna is the audit.
A two-way checkbox would have flattened it, forced every project into this table,
and rebuilt the sidebar under the install instructions.exposedProject
was a single designated slot, so drawing two readable projects would have been
a lie. That slot was replaced by HandshakeExposure on 20 Aug 2026,
hours after this was drawn: scope is derived from the window roster and the
file carries a row per project. The table reads the same two predicates the
handshake does — WindowRoster.shownProjects for the group,
Project.agentAccess for the tick — so a header here cannot
disagree with what an agent can reach.agentActiveNow
and mcp.active mean an agent asked recently, and
PipelineState has its own. Here it means a window is open.
The name is fixed; the collision is worth knowing when the code is written.
Related: state the predicate exactly — serves are lazy, so a window can exist
with no serve behind it.Table would have given free, and is the
strongest argument against the hand-built table; it lost to the pane-shrink
behaviour, which Table cannot provide because it has no intrinsic
height. What was bought back instead: a composed per-row accessibility label
(AgentProjectRegister.accessibilityLabel, commas not middots,
carrying name + group + sessions + last-asked), the glyph hidden, and the
full cell as the checkbox's hit area. Revisit if a cohort tester reaches for
the arrows.Resolved: the sidebar's context menu uses a verb swap ("Turn On / Turn Off Agent Access") and this table uses a checkbox — that is not two grammars for one act, it is being native to each context. macOS context menus carry verbs; AppKit table views carry checkboxes. The shipped verb still supplies the checkbox's tooltip and accessibility label, so the two surfaces name the same act in each surface's own idiom.