Window menu — naming options & edge cases

What the window list at the foot of the Window menu should read, for a Bristlenose that can open more than one window. Rows are drawn at macOS metrics; the entry text is AppKit's own composition of NSWindow.title and NSWindow.subtitle.

Artefact — pixels as they would ship Commentary — not part of the product

Decided 15 Aug 2026. Scheme B — title + count, no lens name. Where an edge case below is drawn in C's grammar (Quotes · 312 shown), B's single-phrase form is what shipped.

Not decided, still open: the video pop-out's title (today s3 — Bristlenose), and moving ⇧⌘O to File ▸ New Viewer Window — both wait on E4's multi-window work.

1 · What ships today

Unchanged code, three real states. The composition is already Mail-shaped.

no project selected the bug
project · sessions lens ships
project · quotes lens · player open ships
reference · mail.app

The mechanism is not missing — it is already correct. ContentView sets the title to the project name and the subtitle to a lens-contextual count, and AppKit renders the pair as Title (Subtitle). That is precisely Mail's shape.

Two things are wrong, and neither is the naming scheme:

2 · The options

Two windows on one project, on different lenses — the case the scheme exists to serve.

A · title only
B · title + count ships today
C · title + lens + count proposed
D · path-style withdrawn

A fails the one job — two windows, one name.

B distinguishes them, but only by inference: you read 312 quotes and deduce Quotes. It breaks outright on the two lenses that share a subtitle — Project and Sessions both render 16 Sessions · 18h 23m.

C names the lens and keeps the count. Same grammar as Mail's scope (context · state), and it is a one-line change to the existing subtitle builder rather than a new mechanism. Recommended.

D is withdrawn — the type prefix names the window's kind in a menu already called Window, and the colons read as an old-Mac path.

3 · Edge cases — handled by the scheme

E1 No project selected

proposed needs a locale key

No subtitle — the welcome screen has no count to report, and navigationSubtitle already returns empty here. Two welcome windows stay identical, which is correct: they are identical.

E2 Project with no analysis yet

imported, not yet run

Falls back to bare title, no parentheses — the existing behaviour when the session count is nil. Nothing to decide.

E3 Non-English locale

de
ja

Subtitle localises, project name does not — it is the researcher's own data. Already true today; adding the lens name adds one more translated token per locale, which the existing lensSubtitle helpers already own.

4 · Edge cases — these force a decision

E4 Two windows, same project, same lens shipped — ordinal kept

unresolvable by naming
alternative · ordinal suffix

Corrected 18 Aug 2026 — the recommendation below was reasoned from a premise that was only ever temporary, and the code shipped the other way. The ordinal stays.

The argument turned on "these are two views of one project, not two projects". That described Stage 3a — one serve, one study at a time — and not the product. The end goal is explicitly both: docs/design-workspace.md §"Stage 3b" is "two projects visible at once ⇒ two sidecars", and the memory objection that was holding it open was measured and retired on 16 Aug (~140 MB per additional project, flat from 1 to 8 windows).

Under the real model the ordinal fires far more narrowly than this card assumed — two different studies are told apart by name, two lenses by subtitle, so the suffix only ever appears when two rows are genuinely byte-identical. That is precisely the case Mail, Finder and Safari leave you stuck in, and "numbering implies the second is a copy" is weakest exactly where the ordinal actually fires — because in that case the two rows really are indistinguishable.

Two things settled since, both worth not re-deriving. Measured: AppKit's automatic Window-menu path renders Title (Subtitle); the manual addWindowsItem path renders title only. So keying the duplicate group on the rendered subtitle is correct by construction, not by luck. And master/child makes this common rather than exotic: a child is a second window on the same study, so two children at one lens is the ordinary shape of the feature, not an edge.

Superseded text, kept because the reasoning is worth seeing:

Mail, Finder and Safari all permit duplicate rows and do nothing about it. The ordinal suffix is the document convention (Untitled 2) and it is wrong here — these are two views of one project, not two projects, and numbering implies the second is a copy.

Recommendation: accept the duplicate. If the two windows genuinely diverge — different filter, different scroll — the subtitle already diverges with them, because it is computed from live filter state.

E5 Two projects, same name, different folders decide

without folder
folder as disambiguator only
folder always

This is the only case where the client folder earns its place — and note it earns it in the subtitle, not as a title prefix.

Conditional inclusion is tempting and I would not do it: a subtitle that changes shape depending on what other windows are open is unpredictable, and the researcher cannot learn the rule. Either always or never. Given the counts already differ in the failing case, never is defensible and cheaper — revisit only if a real cohort project hits it.

E6 Zero-count lens decide

literal
drop the count at zero

Zero happens routinely — an over-narrow filter, a tag with nothing under it. Reporting 0 shown in a window title is noise about a state the researcher can already see on screen. Dropping to the bare lens name keeps the window identifiable and stops the title commenting on the filter. Prefer the second.

E7 Analysis running decide

state in the subtitle
state stays on the row

Mail does put live state in the title (the unread count moves). But Bristlenose already decided where run status lives: on the project's sidebar row, because status lives where its subject lives. A title that narrates the run duplicates that in a place the researcher is not looking, and it churns the Window menu once a second.

Recommendation: leave it out. The exception worth considering is a window whose project is running while the sidebar is hidden — but that argues for the toolbar, not the title.

E8 Long project name decide

60-char name, untruncated

The menu grows to fit; there is no wrapping. Mail's own entry in the reference above is 67 characters, so a wide Window menu is within precedent rather than a defect — and the folder name from E5 would push it further.

The researcher named the project, so truncating their words to preserve our count is the wrong trade. If anything is dropped at width, drop the subtitle.

5 · Already in this menu, independent of the scheme

E9 The full menu, as it would read

proposed · everything open

Three things resolved by drawing it whole:

  • The stray Bristlenose command above the tab items goes away. Today it is openWindow(id: "main") against a WindowGroup, which spawns a window rather than reopening one — it is a New Window command wearing a reopen label, and it belongs in File as New Viewer Window with the ⇧⌘O that is currently dead.
  • The player pop-out becomes Ben — IKEA Study (Video): participant, study, kind. Its current s3 — Bristlenose shows a speaker code no one outside the pipeline reads, and appends the app name in a list where every row is this app.
  • Auxiliary windows (System Health, and the Diagnostics tools) sit here already and need nothing — they are single-instance and their names are unambiguous.

Sequencing. Everything above describes labels for windows that cannot yet differ: selection, the bridge and the serve target are all app-level singletons, so a second window today shows the same project through the same web view. The naming is the last part of that feature, not the first.

Two pieces are independent of it and can land now: the Welcome fallback, and the player pop-out's title.