Moderator identity — iteration 2, real pixels

Route C (decided 1 Oct 2026) drawn with the shipped stylesheet and the shipped strings, frame by frame, as data state × surface. Where a frame needs something that does not ship yet, the chrome bar says new and the inventory at the end names it. Nothing else is invented.

ArtefactReal pixels: the shipped classes, tokens and strings, in the state the data puts them in. No caption inside the frame; the chrome bar names the surface and whether anything in it is new.
CommentaryWhy the frame looks like that, the rows behind it, and what Phase 2 still has to decide. Deliberately not the product.

The cast. Ward handover app — round 2, six sessions. Three came in from Teams with the platform's own speaker labels (Martin B Storey twice, Kerri Ng once, Jane Smith as an observer); three are recordings dropped in by hand, where nothing names the moderator. The researcher is Martin Storey, whose Mac account name is Martin Storey — not quite the spelling Teams uses, and nothing in the product will try to reconcile the two for him.

Read each frame as a row. A session's moderator slot is one of null · proposed · confirmed. The badge draws the state and nothing else: no origin chip, no "heard in", no sentence. Where a name came from lives in the evidence column and in the picker's behaviour, not on the page.

Part 1 The vocabulary — one badge, three states, two display modes. The split badge is molecules/person-badge.css unchanged; the tentative treatment is atoms/badge.css's .badge-proposed + .badge-action-pill, the AutoCode idiom the researcher already knows.

What a slot looks like

R1Confirmed · proposed · null — and the pill on hover

Hover the second badge (or press Tab into it) to see the pill the product shows. The fourth frame forces the hover for screenshots.

confirmedshipped
m1Martin B Storey
proposednew · 3 CSS lines
m1Martin B Storey
nullshipped
m? o?
hoveredmockup · forced
m2Kerri Ng

Three states, one atom. Confirmed is the split badge as it ships today. Proposed is the same badge wearing .badge-proposed: the 3 s pulse, the dashed edge, and the ✗ | ✓ pill on hover and on keyboard focus — exactly what an AutoCode-proposed tag looks like on a quote card, so the researcher has already learned it. Null is a code lozenge reading m? with no name half; the shipped :last-child rule gives it the full radius without a new selector.

The one new rule. .badge-proposed adds a dashed border, which on a single-box .badge is the whole treatment. The split badge is two boxes that already carry a solid border each, so the class on the wrapper draws a dashed ring around a solid pair. Three lines move the dash onto the halves (see the second <style> block). That is the entire CSS cost of this iteration.

✓ and ✗ mean what they mean on a tag. Accept confirms the slot with the proposed identity. Deny rejects the proposal: the slot goes back to null and the row reads m?; the identity that was minted from the label stays minted (numbers are never reused), it simply covers one session fewer. Titles are the shipped buttons.accept / buttons.deny.

Code-only mode is free. The shipped data-person-display="code" switch (toolbar above) hides every name half; m? and m1 survive it unchanged, which is the point of keeping the gap in the code rather than in a sentence.

Part 2 The Sessions lens — organisms/sessions-grid.css at the width it gets on a laptop with the sidebar closed (the C grid: duration and file stacked, sentiment still shown). The speakers cell decomposes the badge into a code lozenge plus the editable name, as SessionsTable.tsx does; the pencil appears on row hover.

After import, before anyone has touched anything

R2Six sessions: two proposed identities, three empty slots

Sessions 1–3 arrived from Teams with speaker labels; 4–6 are hand-dropped recordings. The "Moderated by" line is the shipped sessions.moderatedBy over the identities that exist. Hover any row for the pencil; the proposed lozenges show the pill on hover.

Sessions lens · ~1100 px content widthnew · composition CSS only

Sessions

Moderated by Martin B Storey and Kerri Ng

Observer: Jane Smith

ID
Speakers
StartUser journey
Sentiment
Duration
▶
p1Sarah ChenSarahm1Martin B StoreyMartin
12 Jan 2026, 10:00
48m
▶
p2Dr Amara NwosuDrm1Martin B StoreyMartin
13 Jan 2026, 14:00
52m
▶
p3Mary AdeyemiMarym2Kerri NgKerrio1Jane SmithJane
14 Jan 2026, 11:30
1h 3m
▶
p4Marrian BoatengMarrianm?ModeratorModerator
19 Jan 2026, 09:00
44m
▶
p5Mary OkaforMarym?ModeratorModerator
20 Jan 2026, 15:00
39m
▶
p6Mickael HurleyMickaelm?ModeratorModerator
21 Jan 2026, 10:30
57m

What the grid is saying. Three rows carry a dashed m1 or m2: the system read a label and minted an identity for it, and is showing its work as tentative. Three rows carry m? with the shipped Moderator placeholder beside it — the same muted-italic role word the grid already shows for any speaker without a name. The researcher can leave all six exactly like this and the data is honest.

Nothing asks. There is no banner, no count of "unidentified", no "is this you?". The heading line names the identities that exist and stops. The null rows are visible because they are rows, not because anything draws attention to them.

The rows behind it (the shape decided as I6; person_id nullable, plus state and evidence):

session_speakers persons s1 m person_id=1 proposed platform_label:"Martin B Storey" 1 m1 "Martin B Storey" origin=platform me=false s2 m person_id=1 proposed platform_label:"Martin B Storey" 2 m2 "Kerri Ng" origin=platform me=false s3 m person_id=2 proposed platform_label:"Kerri Ng" 3 o1 "Jane Smith" origin=platform me=false s3 o person_id=3 proposed platform_label:"Jane Smith" s4 m person_id=NULL null — s5 m person_id=NULL null — s6 m person_id=NULL null —

Why "Martin B Storey" and not "Martin Storey". The identity was minted from the label, so it carries the label's spelling. P3: the product does not infer that this is the account holder. He can right-click the badge and say so (R5), or rename it in place, or leave it. All three are fine.

R3Hovering a proposed lozenge in the grid

Row 1's moderator with the pill shown. The pencil is the row's own hover affordance and is drawn here too, as the product would at this moment.

Sessions lens · row 1 hovered · the pill overlaps the name — see commentarymockup · forced :hover
▶
p1Sarah ChenSarahm1Martin B StoreyMartin
12 Jan 2026, 10:00
48m

The first thing real pixels found. The pill is positioned to hang past the badge's right edge by 1 rem (atoms/badge.css: ✗ sits where a tag's delete × sits, ✓ overhangs). On a quote card that overhang is whitespace. In the grid the name starts 0.35 rem to the right of the lozenge, so the ✓ compartment lands on the first letter of Martin. Drawn as-is on purpose: it is the true consequence of composing two shipped idioms, and the storyboard's hand-drawn frames could not have shown it. Phase 2 has two cheap fixes — hang the pill to the left inside .bn-session-speaker-entry (the thumbnail cell's padding is empty ground), or open the entry's gap to 1.4 rem while the pill is visible. One rule either way; the recommendation is the first, so the name never moves.

Two controls, two families. The pill is on the code lozenge and acts on the slot (accept or reject this proposal). The pencil is on the name and acts on the identity (retype "Martin B Storey" as "Martin Storey", everywhere it appears). §B2's rule — rename and reassign never share a control — falls out of the grid's existing decomposition without a new element.

Keyboard. The pill's compartments are real buttons with the house inset focus ring; a / d can be bound as on quote cards. The lozenge is focusable (tabindex="0"), so :focus-within reveals the pill without a mouse.

Part 3 The picker — the cast list of §B9, drawn with the shipped dropdown idiom (.export-dropdown-menu and its rows from organisms/global-nav.css). On the Mac the same list is an NSMenu raised over the bridge; see Part 6.

Picking who it was

R4Opened from an empty slot (session 4)

Click or right-click the m? lozenge. Moderators the study already knows, then the two ways to introduce someone. The study has two identities, neither of them Me yet.

Sessions lens · picker open on row 4new · two strings; menu placement
▶
p4Marrian BoatengMarrianm?ModeratorModerator
19 Jan 2026, 09:00
44m
▶
p5Mary OkaforMarym?ModeratorModerator
20 Jan 2026, 15:00
39m
▶
p6Mickael HurleyMickaelm?ModeratorModerator
21 Jan 2026, 10:30
57m

Every row is a shipped class. The list is .export-dropdown-menu; the group label is .export-dropdown-group-label over the shipped word Moderators (transcript.moderators); people are .export-dropdown-scope rows carrying the split badge; the separator and the two plain items are the same classes the Export menu uses. The only placement difference is the anchor — a badge rather than the nav's right edge — which is a Phase 2 call (a popover positioned by the badge, or the row's context menu).

The two strings the owner writes. That's Me (Martin Storey) is §I3's top-level form, seeded from the account name (I4). The create item is drawn in §B9's spelling, New Moderator…; the storyboard used Someone New…, and H1 lists the choice as open — one key, the owner's wording. Both are placeholders here, flagged as such.

Picking a row sets session_speakers(s4).person_id and state=confirmed — one row, this session only (narrow reach). Picking That's Me mints the Me identity on first use (m3 Martin Storey, me=true) and assigns it. Picking New Moderator… turns the row's name field into the inline editor (R6) with an empty value; a heard name would pre-fill it on a proposed slot.

R5Opened from a proposed slot (session 1)

The same list with the current proposal marked. This is where "that's me" happens for the Teams spelling.

Sessions lens · picker open on row 1new · two strings; menu placement
▶
p1Sarah ChenSarahm1Martin B StoreyMartin
12 Jan 2026, 10:00
48m
▶
p2Dr Amara NwosuDrm1Martin B StoreyMartin
13 Jan 2026, 14:00
52m
▶
p3Mary AdeyemiMarym2Kerri NgKerrio1Jane SmithJane
14 Jan 2026, 11:30
1h 3m

The check mark is the shipped .export-dropdown-check gutter the Export menu uses for its checkbox row, hung in the same indent. It marks the identity this slot currently holds, proposed or confirmed. Picking the checked row confirms it (the same act as ✓ on the pill).

That's Me on a proposed slot adopts, it does not duplicate. Recommendation for Phase 2, stated here so it can be disagreed with: when the slot's current identity is a proposed one and no Me identity exists, That's Me marks that identity as Me (me=true), keeps its heard name, and confirms the slot. One human, one m1, and the Teams spelling survives until he retypes it. Anywhere else — a confirmed slot, or a study where Me already has a code — That's Me assigns the Me identity, minting it on first use. Lists then show the §I3 form, Martin B Storey (Me), in the badge's name half, and the top-level item is no longer offered because its row is in the list.

R6New Moderator… — the inline editor, mid-type (session 5)

The shipped editing state of .bn-speaker-editable-name: accent edge, page-tint fill, no pencil while editing. Commit mints the identity and assigns it in one act.

Sessions lens · row 5 editingshipped
▶
p4Marrian BoatengMarrianm1Martin B StoreyMartin
19 Jan 2026, 09:00
44m
▶
p5Mary OkaforMarym?Mike AlvarezMike
20 Jan 2026, 15:00
39m
▶
p6Mickael HurleyMickaelm?ModeratorModerator
21 Jan 2026, 10:30
57m

Typing into an empty slot is the Someone-New path. There is no identity to rename, so the field can only mean "a person you are introducing". Enter commits: mint m3 Mike Alvarez (origin=typed), set the slot to confirmed. Escape leaves the slot null. The same keystrokes on a named row are a rename of that identity — the field's meaning follows from what the row holds, which is why the grid needs no second control.

Nothing about this frame is new: the editing tint is molecules/editable-text.css's idiom 3 (a name on neutral ground tints), keyed on [contenteditable="true"]; the pencil is hidden while editing by the component, as today.

R7After the picks: five settled, one left alone

Martin confirmed sessions 1, 2 and 4 as himself; Kerri's proposal was accepted; Mike was typed in for 5. Session 6 was never touched and still says so.

Sessions lensshipped

Sessions

Moderated by Martin B Storey, Kerri Ng, and Mike Alvarez

Observer: Jane Smith

ID
Speakers
StartUser journey
Sentiment
Duration
▶
p1Sarah ChenSarahm1Martin B StoreyMartin
12 Jan 2026, 10:00
48m
▶
p2Dr Amara NwosuDrm1Martin B StoreyMartin
13 Jan 2026, 14:00
52m
▶
p3Mary AdeyemiMarym2Kerri NgKerrio1Jane SmithJane
14 Jan 2026, 11:30
1h 3m
▶
p4Marrian BoatengMarrianm1Martin B StoreyMartin
19 Jan 2026, 09:00
44m
▶
p5Mary OkaforMarym3Mike AlvarezMike
20 Jan 2026, 15:00
39m
▶
p6Mickael HurleyMickaelm?ModeratorModerator
21 Jan 2026, 10:30
57m

Every badge is plain now except one. No dashes: the slots are confirmed. Session 6 keeps m? because nobody said otherwise, and the heading line does not mention it. Three sessions share m1 because one human was picked three times — not because the system assumed it.

session_speakers persons s1 m person_id=1 confirmed pick 1 m1 "Martin B Storey" origin=platform me=true s2 m person_id=1 confirmed pick 2 m2 "Kerri Ng" origin=platform me=false s3 m person_id=2 confirmed accept 3 o1 "Jane Smith" origin=platform me=false s3 o person_id=3 proposed platform_label 4 m3 "Mike Alvarez" origin=typed me=false s4 m person_id=1 confirmed pick s5 m person_id=4 confirmed typed s6 m person_id=NULL null

Re-analyse cannot undo any of this. The importer runs at the end of every pipeline run (I7) and never overwrites a confirmed row; the Re-analyse loss sheet therefore has nothing to count here — "renamed speakers" is a people.yaml-era line, and Phase 1 retires the reason for it.

Part 4 The other places a moderator is drawn — transcript page, session sidebar, Project lens. Each already renders the role; each gets the state for free because it reads the same rows.

The same rows, on every surface

R8Transcript page: the sticky header and the roles line

Session 4 after the pick, then session 3 (confirmed moderator, proposed observer), then session 6 untouched. TranscriptPage.tsx's renderSessionRoles with the badge in each state.

Transcript · session 4shipped
p4Marrian Boateng
Moderator m1Martin B Storey
[00:00:04]m1So before we start, can you tell me a bit about how handover works on the ward today?[00:00:19]p4It's mostly verbal. We have the sheet, but by the time the night team arrives half of it's out of date.
Transcript · session 3new · composition CSS only
p3Mary Adeyemi
Moderator m2Kerri Ng, observer o1Jane Smith
[00:00:06]m2Thanks for making time. Jane's just listening in from the product side, if that's alright.[00:00:14]p3That's fine. Honestly anything that gets this fixed.
Transcript · session 6shipped
p6Mickael Hurley
Moderator m?
[00:00:02]m?Okay, we're recording. Walk me through yesterday's late shift.[00:00:11]p6Yesterday was a bad one, actually. Two agency staff, neither had seen the app.

The roles line already says "Moderator" and draws a badge after it — so a null slot renders as Moderator m? with no new string. The segment gutter shows the same code, because within a session the gutter's m is a tag for "the moderator's turns", not an identity; it reads m? until the slot is filled and m1 after, from the same row.

The header is the Mac's too. Under the macOS toggle the type ladder changes (SF Pro, tokens-desktop.css) and the disclosure caret goes — embedded mode keeps the plain Session 4 label because the native session switcher in the titlebar owns switching. Nothing else differs.

Right-click on any of these badges raises the same picker as R4/R5 (web popover, native NSMenu); the transcript page needs no control of its own.

R9The session sidebar

SessionsSidebar.tsx shows the moderator badge beside the first participant only when sessions disagree about who moderated. Left: after import (R2). Right: after the picks (R7).

Plain .badge, plain .badge-proposed. The sidebar never used the split badge, so the proposed state here is the AutoCode treatment with zero composition CSS. The panel box around the list is drawn by the mockup (.toc-sidebar is reveal-gated by .layout, per theme/CLAUDE.md); everything inside it is the shipped entry.

One rule to carry into Phase 2: hasMultipleModerators counts distinct names today. It should count distinct identities, with null counting as one — otherwise a study with one moderator and three untouched sessions hides the m? badges entirely, and the sidebar is the one place the gap would be invisible.

R10Project lens: the line above the session table

The server builds moderator_header from the names that exist. Table elided.

Project lens · dashboard session pane (table elided)shipped

Moderated by Martin B Storey, Kerri Ng, Mike Alvarez

Observer: Jane Smith

Nothing to design here — the line already lists names and omits what has none. Two things to carry: the dashboard joins with commas where the Sessions lens uses an Oxford list (a pre-existing drift, not this project's); and under route C both lines should read identities, not people[code], which Phase 1's backward-compatible GET /people gives them for free.

Part 5 Outside the app — the exported HTML and the MCP overview. P1: a proposed slot reads plain in the export; nothing in a deliverable pulses.

What leaves the building

R11Export HTML — the Sessions lens and a roles line, read-only

templates/export.css applied (.bn-export-mode): no pencils, no pill, no thumbnails. Export is taken from the R2 state on purpose — the proposals are still proposals, and the export states them as fact.

Exported report · Sessionsshipped

Sessions

Moderated by Martin B Storey and Kerri Ng

Observer: Jane Smith

ID
Speakers
StartUser journey
Sentiment
Duration
Interviews
p1Sarah ChenSarahm1Martin B StoreyMartin
12 Jan 2026, 10:00
48m
s1-sarah-chen.mp4
p2Dr Amara NwosuDrm1Martin B StoreyMartin
13 Jan 2026, 14:00
52m
s2-amara-nwosu.mp4
p3Mary AdeyemiMarym2Kerri NgKerrio1Jane SmithJane
14 Jan 2026, 11:30
1h 3m
s3-mary-adeyemi.mp4
p4Marrian BoatengMarrianm?ModeratorModerator
19 Jan 2026, 09:00
44m
s4-marrian-boateng.mov
p5Mary OkaforMarym?ModeratorModerator
20 Jan 2026, 15:00
39m
s5-mary-okafor.mov
p6Mickael HurleyMickaelm?ModeratorModerator
21 Jan 2026, 10:30
57m
s6-mickael-hurley.m4a
Exported report · transcript 1 roles lineshipped
Moderator m1Martin B Storey
Exported report · transcript 6 roles linenew · one string, owner writes
Moderator not identified

The proposed treatment is simply not applied in export mode. The SPA's isExportMode() gate drops the class the same way it drops the pencil, so a client reading the leave-behind sees m1 Martin B Storey as a statement. The grid's m? rows keep their code and the Moderator placeholder: the export shows the same grid the researcher saw, minus the controls.

The one sentence. A null slot on a transcript page in a deliverable reads better as a sentence than as a code, so the roles line takes Moderator not identified — the default recorded on 1 Oct, drawn here as a placeholder for the owner's wording. If the owner would rather the export say Moderator m? like the app, delete the key and the frame on the left is the answer for both.

R12MCP — get_project_overview, the shape an agent reads

The shipped tool lists speakers by code with the gated name map. A null slot is a session fact, so it lands on the session row.

MCP · get_project_overview (excerpt)new · one key per session row; one INVARIANTS line
"sessions": {
  "count": 6,
  "items": [
    {"session_id": "s1", "session_date": "2026-01-12T10:00:00", "duration_seconds": 2880, "moderator": "m1"},
    {"session_id": "s3", "session_date": "2026-01-14T11:30:00", "duration_seconds": 3780, "moderator": "m2", "observers": ["o1"]},
    {"session_id": "s6", "session_date": "2026-01-21T10:30:00", "duration_seconds": 3420, "moderator": null}
  ]
},
"participants": {
  "count": 6,
  "speakers": [
    {"code": "m1", "role": "researcher", "name": "Martin B Storey"},
    {"code": "m2", "role": "researcher", "name": "Kerri Ng"},
    {"code": "o1", "role": "observer",   "name": "Jane Smith"},
    {"code": "p1", "role": "participant", "name": "Sarah Chen"}
  ]
}

A proposed identity is a plain name here too (P1). The agent does not get state; it gets what the researcher would say if asked. A null slot is "moderator": null on the session — present and null, never absent, so an agent cannot read "no key" as "no moderator". One line joins INVARIANTS in grounding.py saying that null means unidentified, not unmoderated; its wording is the owner's.

Export and MCP never need the picker. Both are readers of the confirmed-or-proposed name; both fall silent on null. That is the whole of their involvement.

Part 6 Two more states the plan has to draw — a study upgraded from 0.31.x, and the Mac, where the picker is native.

Migration, and the native twin

R13A study in the field after the upgrade

Round 1 under Rachel, three sessions. people.yaml said m1: Rachel Okonjo and that is all anyone ever knew. I2: every session becomes proposed with origin inherited, and it looks exactly like R2 — the frame has no way to show you the origin, on purpose.

Sessions lens · migrated studynew · composition CSS only

Sessions

Moderated by Rachel Okonjo

ID
Speakers
StartUser journey
Sentiment
Duration
▶
p1Priya RamanPriyam1Rachel OkonjoRachel
6 Oct 2025, 10:00
41m
▶
p2Owen HaleOwenm1Rachel OkonjoRachel
7 Oct 2025, 13:00
46m
▶
p3Lena FischerLenam1Rachel OkonjoRachel
8 Oct 2025, 11:00
38m

The old name stays everywhere it was shown, drawn tentative. The one-time legacy read turns m1: Rachel Okonjo into one identity (origin=inherited) proposed on every session that carried the code — which, under the old one-name-per-code rule, is all of them. If Rachel really did moderate all three, three ✓ clicks (or none: proposed is honest data). If session 2 was actually Steve, that row's pill, then New Moderator…. Nothing has been lost and nothing has been asserted.

session_speakers persons s1 m person_id=9 proposed inherited:people.yaml 9 m1 "Rachel Okonjo" origin=inherited me=false s2 m person_id=9 proposed inherited:people.yaml s3 m person_id=9 proposed inherited:people.yaml

No chip, no "inherited" word on the page. The storyboard's Part 5b drew origin marks; this iteration drops them. The state is what the researcher acts on, and the badge already draws the state. Origin stays queryable in the evidence column for a future provenance surface, if one ever earns its place.

R14The Mac: same frames, native menu

Flip the Platform toggle: every frame above re-renders on the SF Pro ladder, which is what the WKWebView shows. The picker is the one thing the Mac draws itself. I3: wiring first, visual parity later in a Diagnostics side-by-side.

Not drawn, named. The native twin is an NSMenu raised by the host when the webview reports a right-click on a person badge over the bridge (the mechanism §B10 settled for every person menu). Its items are the same strings, in the same order, so the two pickers cannot drift in vocabulary:

Web popover rowNSMenu itemString
group label Moderatorssection header (disabled item)transcript.moderators
split badge m1 Martin B Storey · checkeditem, state = .on, title "m1 Martin B Storey"— (data)
split badge m2 Kerri Ngitem "m2 Kerri Ng"— (data)
separatorNSMenuItem.separator()—
That's Me (Martin Storey)item, name from NSFullUserName()people.thatsMe new
New Moderator…item; on select the webview opens the inline editor (R6)people.newModerator new

What parity means later. A native menu cannot draw a split badge, so the row's title is text in the §B9 shape — code, two spaces, name — and that is correct for a menu. The Diagnostics side-by-side (Phase 3) exists to tune the web popover toward the menu's rhythm, not the other way round.

The badge in the native titlebar session switcher is unchanged: it shows participants, never moderators, so route C does not reach it.

Part 7 The inventory — what is reused, what is new, what the owner writes, and what Phase 2 still decides. This is the list the /usual-suspects pass reads first.

Inventory

Reused verbatim (class → source; nothing restyled):

ThingShipped homeUsed for
split badgemolecules/person-badge.css · PersonBadge.tsxevery code + name; m? is the code-only form
proposed treatment + pillatoms/badge.css (.badge-proposed, .badge-action-pill)a proposed slot; ✓ confirms, ✗ returns to null
speakers cellorganisms/sessions-grid.css · SessionsTable.tsxcode lozenge + editable name + pencil, unchanged
unnamed placeholder.bn-speaker-editable-name.unnamed · sessions.speakerPlaceholder.moderatorthe word beside m?
editing statemolecules/editable-text.css ([contenteditable="true"])New Moderator… / rename, inline
dropdown idiomorganisms/global-nav.css (.export-dropdown-*)the picker's list, group label, check gutter, separator
heading + count lineatoms/section-heading.css · sessions.moderatedBy · sessions.observer_*the line names identities and omits nulls
roles linetemplates/transcript.css · transcript.moderator(s) · transcript.observer(s)Moderator m? with no new string
sidebar entryorganisms/sidebar.css (.session-entry*)plain .badge states
export gatetemplates/export.css · isExportMode()pencil, pill and proposed class dropped in the leave-behind
display mode[data-person-display="code"]code-only survives every state
Accept / Deny titlesbuttons.accept · buttons.denythe pill's compartments

New, and flagged in the frames:

ItemSizeWhere it goes
composition rule3 lines of CSSmolecules/person-badge.css: dashes on the halves, no ring, when .badge-proposed sits on .bn-person-badge
picker placementa Phase 2 callpopover anchored to the badge vs. the row's context menu; the rows themselves are shipped classes either way
session moderator keywire shapeget_project_overview session items: a code or null; plus observers
strings4 keysbelow — the owner writes the English; this file carries placeholders

String plan — four keys, housed in the people block §I4 already reserves, seeded into the 21 full locales per the i18n rules (zh-Hant-HK gets none):

KeyPlaceholder drawn hereWhereNotes
people.thatsMeThat's Me ({{name}})picker, top level; NSMenu§I3's settled form; name from NSFullUserName / GECOS. That's Me… when the account has no name
people.me{{name}} (Me)picker rows, once Me has a code§I3's list form; rendered in the badge's name half
people.newModeratorNew Moderator…picker, after the separator; NSMenu§B9's spelling. The storyboard used Someone New…; H1 carries the choice. One key either way; fr/ca/es gender-marking per H1
export.moderatorNotIdentifiedModerator not identifiedexported transcript roles line; the m? lozenge's aria-labelthe 1 Oct default. Delete it and the export says Moderator m? like the app

Open for Phase 2 (each has a recommendation in the frame it belongs to):

Human-only checks, once built (nothing in pytest or vitest can see these):

Companions. docs/design-people.md §H H9 carries the six-phase delivery plan this iteration precedes; moderator-identity-failure-states.html Part 5b is the argument and the twelve decisions, and its E-frames are superseded by these for anything about pixels. The register row is in docs/mockups/STATUS.md.