The atom is "p4 is a human called Jane Smith". Everything else in this document is a refinement of that sentence, and the refinements are ranked — not delivered together.
A person reference is rendered on six surfaces today (a segment badge, a session's
speaker entry, a quote attribution, the moderator line, the sessions sidebar, and the proposed
People lens). The same object, the same vocabulary, at five altitudes. Every altitude reads
and writes — the lens is not a read-only roll-up and the badge is not a write-only
control. You act where you noticed the problem, which is already the stated principle in
design-speaker-editing.md: fix in context, not up front.
Make the common case trivial and the edge cases possible. It is not Bristlenose’s job to work identity out — it is its job to go with the typical case and make a wrinkle easy to see and cheap to fix. And typical is small: fewer than 20 participants and usually no more than 12, three or four colleagues, one moderator in about 95% of studies, and where there is an extra person in the room they are almost always an observer rather than a second moderator. Every list here fits on screen; nothing scrolls, searches or paginates.
Two threads carry the whole document, both in Ward handover app — round 2.
Jane Smith, a client-side product manager who sat in to watch: the pipeline recorded her
as participant p4 in session 4 when she was actually observing — the common
wrinkle. And Mike Alvarez, a colleague who covered session 9 — where he was given
m1, the same code as Martin, who ran every other session — the rare one.
(The full cast is pinned in docs/design-people.md §H; a handoff sweeps the benches
against it, because several early frames still disagree on who holds which code.)
| Attribution | Identity | |
|---|---|---|
| The sentence | "That was Sarah, not Jane." | "p4 is a human called Jane Smith." |
| What is wrong | The words are credited to the wrong speaker. The cast is right. | The words are credited correctly. What we think the speaker is is wrong. |
| Object | Transcript segments → speaker_code |
Speaker → person, name, role |
| The fix moves | Words. Speech leaves one speaker and joins another. | Nothing. It relabels. Every word stays where it is. |
| Where noticed | Reading a transcript, in the flow of the conversation. | Scanning a session, or a list of people. |
| Blast radius | This turn · these N turns · every turn of this code | This session · this study · everywhere |
| Downstream | Quotes may now name the wrong person; per-speaker stats go stale. | A role change alters who is eligible for quotes — but only on the next analysis. |
| Frequency | High on raw audio. Near zero on platform transcripts, which arrive already diarised. | Every study, every path. The model guesses names on all of them. |
These two families want to sit next to each other in one menu, and they must not. "This Turn Is › Jane" and "This Speaker Is › Jane" read almost identically and do wildly different things: the first silently moves one paragraph of speech, the second relabels a person. A researcher who means the second and hits the first has corrupted a transcript and will not find out for weeks.
So the rule is lexical, not visual: an attribution item always contains the word Turn — "This Turn Is…", "These 4 Turns Are…", "All of p4's Turns Are…". An identity item never does. If the item says Turn, it moves words. If it doesn't, it doesn't. That is one sentence a researcher can hold, it survives translation better than a colour or an icon would, and it does not spend any of the menu's scarce vertical space.
They are also separated by a rule in the menu, and — the part that matters most — they are reached from different anchors. Attribution lives on a segment badge inside the transcript, where you are reading words. Identity lives on a speaker entry or a person row, where you are reading a cast list. The overlap is deliberately small.
m2.
Follows from step 1 — no separate UX, no blocking decision.
Medium. Speaker→person remap, moderator renumber, stats recompute — all bookkeeping behind an act the researcher has already performed.
Step 1 is most of the value and almost none of the cost. Naming, self-identification and
role are the everyday acts: no typing beyond the name itself, no consequence beyond a relabel,
no data moved. Shipping only step 1 — on every surface, with the same menu — would
close the gap researchers actually feel, which is that the report talks about p4
and they think about Jane.
Attribution is deliberately late, and that is the ordering claim most likely to be wrong. It rests on where the transcripts come from: platform exports arrive already diarised, so turn-level errors are a raw-audio problem, and cloud import has just made the diarised path easier. If the cohort turns out to record locally and transcribe with Whisper, step 5 moves up sharply. That is measurable, not arguable — the source of each session is already recorded on every segment.
"That's me" is the highest-value identity claim in the system and needs no algorithm. The researcher appears in every study they run, so they are the single most recurrent person in the whole corpus — and the one identity the app never has to guess, because the person asserting it is the person. Said once, it seeds cross-study moderator identity for free and removes the most common case from the cross-study back-fill before it is built.
Right-click on p4's badge or name, anywhere it is rendered. Session 4
of round 2. The pipeline heard nobody introduce themselves, so there is no name and no guess.
This is the whole of step 1 for a participant. No submenus except Role, no scope choices, no
header. The anchor is the scope: you clicked p4 in session 4, so the name
you type is p4's — and because participant codes are numbered across the
whole study, that name is p4's everywhere. Nothing needs to say so.
Deliberately absent: everything from steps 2–6. No Confirm (there is no guess to confirm), no Same Person As, no Turn items — a badge in a sessions row is not standing over any particular turn. A menu that shows six disabled items to advertise a roadmap is a menu nobody reads.
Right-click m1 in sessions 1–8. The role is already correct.
The name is not known, and the Mac knows it.
NSFullUserName() — the Mac's own account name. The app offers it and never assumes it, which is the standing rule for profile connection: the researcher may say "I'm m1", the system may not decide it for them.m1 in every session where the app currently believes m1 is one speaker. The sweep line confirms the count afterwards — You are m1 in 8 sessions — because a one-click action with an eight-session reach has to say so somewhere.admin), the item degrades to That’s Me… with an ellipsis, asks once, and never asks again.Two reasons this earns first place over any matching algorithm. It is the only identity claim in the product with a perfect source — the person asserting it is the person. And it is the most reused: a freelancer's own name is in every study they will ever run, so one click here is the last time the question needs asking, in this project or any other.
The item sits above Set Name, against the usual instinct to lead with the generic verb,
because it is both the more common answer for an m code and the cheaper one. On a
p code it does not appear at all.
Session 7. p7 is Michel Hurlly, which is what Whisper heard.
His name is Mickael Hurley, and the researcher knows that for certain because she
recruited him. Nothing about his identity is in doubt.
Michel Hurlly appears 6 times in the words of session 7 — where he says his own name, and where the moderator says it back.
Quotes already pulled out keep the old spelling until you analyse again.
The label and the words are two different places, and today only the label is fixable. The transcript body still says "Michel Hurlly" wherever anyone said it aloud — so the report shows the corrected name at the top of a quote whose text spells it wrong. Nobody would design that; it is just what happens when a name field and a transcript are edited by different features. The sheet is the cheapest possible bridge, and it is a scoped exact-string replace, not free-form transcript editing.
Neither button is destructive and neither is Cancel — "Just the Label" is a real answer, not a retreat, and it is what a researcher picks when the participant genuinely said a different name aloud. Escape takes the same path.
A name belongs to a code, and a code has one name. Correcting the spelling here
corrects it in session 7, in every quote, in the participant table and in the export —
there is no such thing as p7 being spelled one way here and another way there,
and the sheet does not offer a scope because there is no scope to offer. That invariant is
load-bearing well beyond spelling: it is what makes the moderator renumber in bench 9
forced rather than chosen.
The bigger version of this is a recruitment list. The researcher knew the spelling before the session existed — the names were in a screener days earlier. An imported roster would let the app match what it heard against names it was given, rather than guessing spellings from audio, and would collapse this whole class. Out of scope here, worth noting as the thing steps 1–2 are a manual stand-in for.
Back to session 4. Jane Smith sat in to watch, said about two hundred words, and the pipeline classified her as a participant — so her side comments were mined for quotes.
"in Session 4" is in the item text, not implied. Role is a fact about a person in a session, not about a person — the database has always modelled it that way, and the edge case is real: the same colleague can observe one session and moderate the next. Naming the scope in the item costs four words and prevents the reasonable assumption that this is a global switch. At a person-level anchor the same item reads Role in All 12 Sessions, and where the roles genuinely differ it says so rather than flattening them.
What it does not do: retract the six quotes already pulled from Jane's asides. Role governs who is eligible for quote extraction, and extraction has already run. Changing a label cannot un-ask a question that was already answered — so the surface offers Analyse again and lets the researcher decide, rather than silently spending a cloud call and discarding edits.
And it changes who is named in an export — today, by accident. Anonymising blanks
names whose code starts with p and keeps m and o. Jane
as a participant is blanked; Jane as an observer is named. One three-item submenu
quietly moves a colleague across the anonymisation line, in the direction of more disclosure,
with nothing on screen saying so. That is Part 5's problem, and it is live in shipped code.
The ordinary participant cast of a twelve-session study.
p3 is Mary. p4 is Marrian. p5 is a different
Mary. p6 is the first Mary again, back to talk about another part of the
journey. All four are fine and reasonable, and only one of them needs the researcher to
do anything.
p3 and p5 are two Marys who have never met. Correct as they stand, and the app proposes nothing — name equality is not evidence at any altitude, including inside one study. The only work is telling them apart on screen, which the shipped short-name collision handling already does: Mary A. and Mary O.p3 and p6 are one Mary, back for a second session on another part of the journey. Normal, not a defect. Participant codes are numbered across the study so they never collide — which means one human returning simply holds two of them.p6 by picking the Mary who is already in the study. The codes join as a consequence. One pick.This is the common participant case, and it should be almost invisible. The temptation is to build matching — two "Mary"s in one study, surely worth a suggestion? No: the app cannot tell these three apart and would be wrong a third of the time. What it can do is put the ambiguity where the researcher will see it, name both candidates, and let the person who was in the room answer in one click.
Note what the contested row does not say. Not a confidence score, not "possible duplicate", not a merge proposal. Just the two names that are already in this study and the word Which? — because the researcher knows, and the app does not.
The second frame is the same shape one namespace over. Moderator and observer codes
fragment as well as collide, for a dull reason: the number comes from within-session
ordering, which has nothing to do with identity. Jane observes session 1 alone and is o1; she observes session 3
alongside Tom, who happens to speak first, and she is o2. Same person, two codes,
and nothing about her changed.
So all three namespaces need the join, and only m/o need the
split — and every one of those repairs is the same gesture: name the person in front
of you. Here the app offers, because both names are platform records rather than
guesses; a Mary heard once gets the silent Which? treatment instead. Offering is not
merging, and the strength of the evidence decides which you get.
Not "that paragraph" — all of it. Everything the diariser attributed
to p3 in this transcript is the moderator talking. One statement, not forty
corrections. And the person you are attributing to may be someone the system has never
noticed.
They will be o2 in this session.
o code; picking New Moderator… mints an m. Role and identity are chosen in one gesture rather than two, and the groups run in p/m/o order — the order codes are read in everywhere else — rather than by guessed frequency.The gap this closes: a person the system never noticed. Diarisation merges two people
into one code and the second has no code at all, so there is nobody to reattribute to.
This is the exact inverse of Not a Speaker, and without a create path the picker
simply cannot express the correction. New Participant… mints the code on the
spot.
Create and name are one action. A nameless p7 is a second chore, and the
researcher knows the name at precisely the moment they are creating them. For a new moderator
or observer the field offers the bank, because a new colleague is usually a known
colleague; for a new participant it is blank, because they usually are new. Note this is the
one place the two lists meet — the cast (who could this speech belong to, in this
session) and the bank (who is this person, across studies) are otherwise different
questions and different lists.
The whole cast fits without scrolling because the cast is small: a dozen participants, three or four colleagues. At that scale a grouped list is faster than any search field, and a search field would be a confession that the list is too long.
Session 3, around 18:40. Sarah (p3) and Jane (p4)
overlap for eight seconds. The diariser cut the boundary in the wrong place, and one of the
study's better quotes now carries the wrong name.
Now credited to Martin (m1). This lens only shows participant quotes. Show in transcript · Undo
Martin has 5 more quotes in this session. Those too
Quotes exist only for participants, so the current value is always a p
code and there is no such thing as a moderator’s quote. That makes the two halves of this
list mean different things. Picking another participant re-credits — genuine
crosstalk, the words are a finding, credited wrongly. Picking a moderator or observer says
this is not a participant quote at all — the speech is re-attributed, and the
card leaves the lens because the lens only shows participant quotes. The group header says
which is which at the point of action.
That second half is the everyday repair from bench 11 arriving on a different surface: an observer mis-filed as a participant had their asides mined as findings, and this is where a researcher meets the consequence — one odd quote at a time, while reading.
The card disappearing is a consequence, not the action. A quote is a view onto speech,
not a copy of it — so re-attributing it moves the words, and going to session 3
afterwards shows them as the moderator’s. Had only the card changed, the two surfaces
would disagree: the quote gone from here, the transcript still showing p4 saying
it. Hence Show in transcript sitting next to Undo — the quote was not destroyed,
it moved to where it belongs, and the researcher should be able to go and see that.
And nothing is withdrawn. There is no flag, no reason field, no pile it moves to. The
Quotes lens shows participant quotes; re-attribute the speech to m1 and the
card is simply no longer in that set — reversible for free, because putting the speech
back puts the card back.
Two orthogonal axes, and keeping them apart is the whole point. Membership — is this a participant quote? — is a fact about who spoke, derived from speaker identity, with no state of its own. Emphasis — star promotes, hide demotes — is a judgement about evidence quality that the researcher makes deliberately and owns. Star and hide are one spectrum: picking and choosing the best evidence. Speaker identity is not on that spectrum at all. Which is why the hidden count stays meaningful — it counts judgements, not facts — and why a correction must never be expressed through it.
Note the sentence in the frame above avoids the word hidden. Borrowing the curation verb for a membership fact is the exact confusion this whole distinction exists to prevent.
And then the pattern this design uses everywhere: do the narrow thing, then offer the wide one in a sentence. One quote is re-attributed, because that is what was asked. The app, now holding strong evidence, says so and notes the five quotes still credited to p4 — inviting review, claiming nothing, and never performing the sweep uninvited. It is how a reading surface can default to the thing under the cursor while the underlying error is usually study-wide.
Trim and Hide sit in the same menu deliberately. When speech overlaps, the extracted text is often a genuine mash of two voices, and re-crediting all of it to one person swaps one wrong attribution for another. The real repair is to trim to the part one person actually said and then re-credit — or, when it is unsalvageable, to hide it. Both already ship; the menu just puts the three answers in one place, in the order a researcher tries them.
Note the noun. This Quote Is — a unit of speech, so the fix moves words. Role, Change Name, Same Person As — units of identity, so they only relabel. The rule generalises from the earlier one: the item names the object it acts on, and speech nouns are the ones that move things.
Right-click the badge on a turn. Everything attribution can do is reachable from here, at three different reaches, and the item text says which is which.
Two items say Turn and they are the two that move words to a different speaker.
One says All of p4’s Turns Are — the whole-speaker sweep, named for
the speech it moves and classified attribution, where its effect belongs. (An earlier draft
spelled it p4 Is Actually — identity wording on a 41-turn word-move,
distinguishable from the pure relabel m1 Is only by an adverb; the review killed
it, so the rule now holds with no exceptions.) The rest relabel and move nothing. A researcher
does not have to be taught this; they can read it off the menu.
This Turn Is — the thing under the cursor. All of p4’s Turns Are — all 41 turns in this transcript. The wider one is deliberately one group down: this is a reading surface, so the narrow act is the default and the sweep is available with its scope written into its name.This Turn Is and All of p4’s Turns Are are bulk updates — cheap. Only Split and Merge need segment surgery: word-timing division, index management, a quote cascade. They are the rare grain, and they are the reason step 5 of the sequence is the largest item in it.One exception worth carrying forward: when a split is needed because a quote sits inside a longer turn, the quote’s own timecode range supplies the split point. That case needs no cursor placement and no new interaction, which makes the flavour of turn surgery that arrives from a reading surface markedly cheaper than the general one drawn here.
The tail case — and the one this document first drew as a three-button
sheet. It is not. One moderator runs about 95% of studies, so m1 meaning the same
person everywhere is usually right; where it is not, the researcher fixes it the same
way they fix everything else, by naming the person in front of them.
m1 is Martin elsewhere, concludes this is a different person, renumbers Mike to m2, and says so in one sentence with an Undo. No confirmation, because the house rule is that nothing confirms and everything ⌘Z’s.Renumbering is a consequence of naming, not a verb. That is the whole of decision 1, and it is why the same menu from bench 2 absorbs the tail case without growing an item. Three things that looked structural — joining two participant codes, separating a collided moderator, linking across studies — are all bookkeeping behind an act the researcher has already performed, provided naming is built as an identity assertion rather than a string edit.
The heavy sheet survives for one path only: noticing at study level, in the People lens, that a code covers two names — where there is no single session to open and no rename in flight. Secondary door, not the primary one.
Teams writes the account's formal name onto every turn — middle initials, double-barrelled surnames, the occasional title. The research team says Mike, and Mike is what belongs beside a quote.
full_name and short_name have been on PersonEditable from the start, and suggest_short_names() derives the second from the first with real care — honorific stripping, family-name-first detection, 337 surnames, and collision handling that produces "Sarah J." and "Sarah K." when two participants share a first name.short_name and displays short_name || name. full_name appears only as a hover tooltip, and only when it differs. Nothing in the SPA can edit a full name at all.The sheet's job is almost entirely teaching by consequence. Two fields, each with one line saying where it shows up. "Called" first, because it is the one being changed nine times out of ten and the one with visible effect. Derivation still runs — leave "Called" empty and it fills from the full name — but it is now visibly a default rather than an invisible one.
This is also the quiet answer to the anonymisation question. A study that never records full names and works entirely in short ones has less to strip on the way out. Making the distinction visible lets a researcher choose that, which is a governance decision the product currently makes for them by hiding one of the fields.
Nobody introduces the observer. They sit quietly for twenty minutes, then say one useful thing — and when they do, they sound exactly like a participant.
2 people barely spoke and were never asked a question. Were they observing? Take a look
Observers cannot be classified by the machinery as built, and they fall through to
participant by construction. Role identification samples roughly the first five minutes
(seg.start_time > 300), so anyone who first speaks at 31:40 is not in the
sample at all. The heuristic that covers the rest scores question ratio and moderator phrases
— an observer fires neither. And the code assignment maps
PARTICIPANT and UNKNOWN to a p code, so "could
not tell" and "is a participant" produce identical output. The observer is not unlucky; they
are the default.
The signals that would catch them are computable and already stored: very low word count, a late first utterance, and never being the addressee of a moderator question. None needs a model. One absent-when-zero line is enough — it costs no column width, it is a single translated string, and it targets a bias rather than nagging about every speaker.
Now the perverse part. Correcting Jane from participant to observer moves her
across the anonymisation line, towards disclosure: the export blanks names whose code
starts with p and preserves m and o. As a
mis-filed participant her name is stripped from the client's leave-behind. Correctly filed as
an observer, it is published. The most common role error currently produces the safer
export, and fixing it produces the riskier one — which means the sweep above cannot
ship before Part 5's second decision, or it will do net harm while looking like a correctness
feature.
A team of three or four researchers moderates everything. Their names are typed once, ever. After that, naming a moderator is a pick, not a keystroke.
Person rows are already instance-scoped — no project_id, deliberately, with a docstring saying merging is a future human-driven action. The bank is a query over rows that already exist, ordered by how often each has been used.m1 is the Steve who already exists — which is precisely the cross-project identity link that was step 6. Obtained here for free, at step 1, as a side effect of not retyping.Prevention beats reconciliation. The linking feature as designed is a reconciliation engine: import everything, generate duplicates, then match names within a folder, propose, confirm, and handle transitive chains. The bank stops the duplicates being created. What is left for a links table is a back-fill for people named before it existed — a much smaller and much less dangerous feature, and one with no matching algorithm in it at all.
"Someone New…" has to be the visible escape, and the safe default. The failure mode is a researcher picking "Rachel Okonjo" for a different Rachel, which silently welds two people together. So the bank never pre-selects, never auto-completes on a keystroke, and the study counts beside each name are there to make "which Rachel" answerable in the menu itself. Cross-client picks still deserve the confirmation from the linking work — the bank changes how a link is created, not whether crossing a client boundary is worth a sentence.
"Me" sits above the separator and outside the team group, because it is not a person you
might be confusing with another person. It is also the row that seeds an empty bank: the first
time anyone uses this, NSFullUserName() is the only entry there is.
| Surface | What you clicked | Scope it carries | What it offers |
|---|---|---|---|
| Transcript a segment’s badge |
one turn, by one speaker | this turn · this speaker in this transcript | This Turn Is ▸ · These N Turns Are ▸ · All of p3’s Turns Are ▸ · Split Turn Here · Merge with Turn Above · name · role · Not a Speaker |
| Quote card | one published extract | this quote | This Quote Is ▸ · Trim Quote… · Hide Quote · Show in Transcript |
| Sessions grid a speaker entry |
this speaker in this session | this session | Set Name… / m1 Is ▸ / That’s Me (Name) · Role in Session 4 ▸ · All of p3’s Turns Are ▸ · Not a Speaker |
| People lens a row · project scope |
this person in this study | the whole study | name · Role in All 12 Sessions ▸ · Separate… (when contested) · Same Person As ▸ · Part of My Research Team |
| People lens a row · folder or everyone |
this person across studies | across projects | name · Same Person As ▸ · Not the Same Person · Unlink… · Part of My Research Team |
Four rules generate every cell, which is what makes this one menu rather than four that
will drift. (1) Offer what is meaningful at that anchor and omit what is not —
a Turn item has no turn in scope on a sessions row, so it is absent, not greyed; a contextual
menu shows what applies, a menu bar keeps its shape. (2) One verb at many anchors, with
the scope in the label — Role in Session 4 and Role in All 12
Sessions are the same verb one altitude apart, and the researcher cannot see the anchor.
(3) State picks within a zone: unnamed offers Set Name…, a guess offers
Confirm, a contested code offers Separate…. (4) Never
offer a no-op.
Written as a function of (where you clicked, what state it is in), a new verb lands everywhere it is meaningful at once, and a surface added later inherits the whole vocabulary instead of re-implementing a subset of it.
And the subtler half: default reach follows the working mode, not only the anchor. The anchor bounds what is possible; the surface says what the researcher is probably doing.
| Where you are | What you are doing | Default reach | Worked example |
|---|---|---|---|
| Sessions grid · People lens | sweeping — housekeeping, row after row, expecting ripple | the widest meaningful scope | "o2 in s3 is the same Jane as o1 in s1" — joins the codes and ripples into every quote in that transcript |
| Transcript · quote card | reading — analysis, in the flow of a conversation | the thing under the cursor | "that one quote is Sarah" — this quote, nothing else |
The other reach stays reachable and is never the default, and the asymmetry in how is
deliberate. Widening from a narrow surface is a menu item — inside the transcript,
All of p4’s Turns Are sits one group below This Turn Is. Narrowing from a
wide surface is navigation — you do not fix one quote from the People lens, you go to
the quote. A row that stands for twelve sessions has no business offering an action that touches
one paragraph.
| Bench | The narrow thing, done | The wide thing, offered in a sentence |
|---|---|---|
| 3 | The spelling is corrected on the label | “Michel Hurlly appears 6 times in the words of session 7.” — Fix All 6 |
| 4 | Jane’s role is changed in session 4 | “Quotes were pulled out using the old roles.” — Analyse again |
| 7 | One quote is re-credited to the moderator | “Martin has 5 more quotes in this session.” — Those too |
| 9 | Session 9’s moderator is named Mike | “Mike is now m2. Martin stays m1 in sessions 1–8.” — Undo |
| 11 | — | “2 people barely spoke and were never asked a question. Were they observing?” |
Do the narrow thing, then offer the wide one in a sentence. It arrived independently in five benches before anyone named it, which is the usual sign that a pattern is real rather than imposed. It is also the thing that resolves the tension between the two defaults above: a reading surface can act on the thing under the cursor, and still get the researcher to the sweep, without ever performing the sweep uninvited.
Three properties make it work. The app speaks only after the researcher has acted, so it is never guessing at intent. It quotes a count — six occurrences, five quotes, eight sessions — because a number is checkable where an adjective is not. And the offer is one link, declinable by ignoring it: no dialog, no modal, nothing to dismiss. Bench 11 is the degenerate case with no preceding act at all, which is why it has to be absent when its count is zero — an unprompted sentence has to earn its place every time it appears.
| Bench | The case | How common | What it costs |
|---|---|---|---|
| 1 | Name an unnamed participant | every session | type a name |
| 2 | "That moderator is me" | every study | one click, no typing |
| 3 | Fix a mis-transcribed spelling | every raw-audio study | type it, then one choice about the transcript |
| 4 | Participant → observer | the 95% extra person | one submenu pick |
| 5 | Two Marys, one Mary twice, and Jane’s two observer codes | ordinary cast of any study | nothing for most rows; one pick for the rest |
| 6 | A whole speaker in a transcript is someone else | the common attribution error | one pick from a grouped list |
| 7 | Crosstalk — re-credit one quote, or move it off the evidence | a few times a study | one submenu pick |
| 8 | One turn, or a split mid-turn | rare — raw audio only | one pick; a split needs a cursor placed |
| 9 | A second moderator | tail — ~5% of studies | one pick, plus a sentence telling you what changed |
| 10 | Formal name vs the name we use | every platform-transcript study | two fields, once per person |
| 11 | Spotting mis-filed observers | every study with anyone extra | read one line, then one pick each |
| 12 | Naming a colleague from the bank | every study, after the first | one pick, and never again |
Nothing on this page costs more than one action and a sentence, and the two that cost the most — a second moderator, a mid-turn split — are the two that happen least. That is the whole test. A design that made the second-moderator case elegant at the price of a dialog in the naming path would be a worse design, however well the dialog read.
Several rows cost nothing at all: two Marys who are different people, a returning participant seen for what she is, a moderator who is the same person in every session. Those are the rows the product gets right by going with the typical case rather than by reasoning about it — and they are the majority of every study.
| Family | The researcher's sentence | Menu item | Anchor | Step |
|---|---|---|---|---|
| identity | "p4 is a human called Jane Smith." | Set Name… | any person reference | 1 |
| identity | "m1 is me." | That's Me — Martin Storey | an m/o code | 1 |
| identity | "That m1 is Steve." | m1 Is ▸ (the bank) | an m/o code | 1 |
| identity | "Jane is an observer, not a participant." | Role in Session 4 ▸ | speaker in a session | 1 |
| identity | "Michel Hurlly is Mickael Hurley." | Change Name… + propagate | any person reference | 2 |
| identity | "He's Michael J. Hurley-Okonkwo, but call him Mike." | Change Name… (two fields) | any person reference | 2 |
| identity | "Yes, that guess is right." | Confirm "Danny" | a guessed name | 2 |
| identity | "That email address should not be in the name." | Clear Name | any person reference | 2 |
| attribution | "They talked over each other — that quote was Sarah." | This Quote Is ▸ → another p | a quote card | 3 |
| attribution | "That quote is the moderator talking — not a participant quote." | This Quote Is ▸ → an m/o: moves the words; the card falls out of the lens | a quote card | 3 |
| attribution | "Everything attributed to p3 here is the moderator." / "Speaker A and Speaker C are one nurse." | All of p3's Turns Are ▸ | speaker entry · segment badge | 4 |
| identity | "The moderator in session 9 is Mike, not Martin." | m1 Is ▸ — renumbers to m2 | speaker entry, in that session | 4 |
| identity | "o2 in s3 is the same Jane as o1 in s1." / "p6 is the same Mary as p3." | name it from the bank — joins the codes | speaker entry · People row | 4 |
| attribution | "That turn was Sarah, not Jane." | This Turn Is ▸ | segment badge | 5 |
| attribution | "Those four turns were all Sarah." | These 4 Turns Are ▸ | segment selection | 5 |
| identity | "That is not a person — it is the TV." | Not a Speaker | speaker entry · segment badge | 4 |
| attribution | "Two people are in that one paragraph." | Split Turn Here / Merge with Turn Above | segment badge | 5 (the expensive one) |
| identity | "This is the same ward sister from round 1." | Same Person As ▸ | person row, folder scope | back-fill |
| identity | "Same name, different people." | Not the Same Person | person row, folder scope | back-fill |
Does a code name a person or a slot? Neither. Sessions are s1, speakers are
p1/m1/o1, a code is a globally-numbered speaker
slot, and identity is a layer above it. Neither namespace needs re-specifying, because
each is already right for its own common case — they just fail in different directions:
p never collides and over-fragments. The counter runs across the study, so
p3 and p6 are always different codes — and the same Mary
returning for a second session simply holds both. Normal, not a defect.m and o do both, because the number comes from
within-session ordering, which has nothing to do with identity. They over-collide
(m1 is Martin in eight sessions and Mike in the ninth) and over-fragment
(Jane is o1 alone in s1 and o2 beside Tom in s3).So all three namespaces need the join and only m/o need the
split — and every one of those repairs is the same gesture: name the person in front of
you. Bench 9 renumbers Mike to m2; bench 5 joins Jane’s two observer codes and
Mary’s two participant codes.
The renumber is not a design choice — it is the only representable outcome. A name
belongs to a code and a code has one name everywhere, so "session 9’s moderator is Mike"
cannot mean "m1 is Mike here and Martin elsewhere". That sentence has nowhere
to live. The same reading disposes of the alternative: the m1 collision is not a
bug in the name model — it is the name model working correctly on a code that names two
people. Re-keying by (session, code) would have fixed the symptom by breaking
the invariant, letting one code carry two names and quietly making "what is p4 called?" a
question with more than one answer.
Today it is the letter at the front of the code. _anonymise_data blanks
p* and preserves m* and o*. Three consequences, all live:
The fix is to move the decision up an altitude, where it has always belonged. Per-session
role governs quote extraction — correctly, because someone interviewed in session 9
should have quotes from session 9. Person-level membership governs naming: one
value per human, one answer per export. In the menu that is a single checkmark item at a person
anchor — Part of My Research Team — and the export reads that instead of
the prefix. It is also what populates the bank's "Your team" group, so one flag does two jobs.
NSUndoManager
is used nowhere, the undo-state bridge channel is dead on both ends, and
bridge.ts hard-codes canUndo: false. Every action in Part 4 is a
mutation a researcher will want back — and step 1's actions are the ones they will perform
thirty times in an afternoon. An affordance this cheap to fire needs an undo before it needs
more verbs.onContextMenu handler exists in frontend/src. So this is a
new mechanism, not a restyle, and inside the WKWebView it means suppressing the system menu
and rebuilding what it gave away — keyboard traversal, type-select, Escape, focus return,
and the accessibility tree. A half-built HTML menu is worse than the pencil that ships. The
alternative worth pricing: let the web view report the anchor over the bridge and have AppKit
raise a real NSMenu, which is free, correct, and native — at the cost of the CLI
browser path getting nothing.Role in Session 4, Role in All 12 Sessions,
All of p3's Turns. The anchor determines the scope, and the researcher cannot see
the anchor. Four words in the item text is the whole mitigation, and it is cheaper than any
confirmation dialog it replaces.The speculative register — cases neither confirmed nor dismissed —
lives in the PRD, docs/design-people.md §F, where it can be triaged in writing
rather than redrawn here.