feat(persons): support middle names and a separate print name format - #5313
feat(persons): support middle names and a separate print name format#5313ux-git wants to merge 22 commits into
Conversation
Introduce an optional middle name across every record that carries a person's name: - PersonType.person_data.person_middlename - VisitingSpeakerType.speaker_data.person_middlename - SettingsType cong_settings.circuit_overseer.middlename - SettingsType user_settings.middlename The field is typed optional on purpose. Records arriving from the backend will not carry it until the server side ships, so declaring it required would make the type lie about what is actually in IndexedDB. Reads must stay defensive until that lands. Seed it in every schema literal, including the standalone person object in states/persons.ts, so a newly created person and a restored record have the same shape. No Dexie version bump: person_middlename is not an index, and an absent optional field needs no backfill. Add person_middlename to the persons entry of TABLE_ENCRYPTION_MAP, matching the 'shared' level already used for first and last name. visiting_speakers needs no entry there, as it encrypts speaker_data as a whole.
Add the middle name input to every form that already collects a first
and last name:
- person record (Persons -> Basic information)
- My profile, backed by a new middlenameState atom
- the initial setup wizard
The field is always visible. A middle name is data about a person, not
a display preference, so hiding it behind a name-format setting would
make it impossible to record the name before choosing how to show it.
Display names are deliberately left alone. Their purpose is the compact
form ("B. Mike"), so generateDisplayName keeps its current signature and
middle names never feed into it.
Note that a middle name is not rendered anywhere yet: the two current
name formats are First Last and Last First. Formats that include it are
design work tracked in sws2apps#4448.
The XLSX export gains a Middle name column and the import mapper gains the matching field, so exporting a congregation and importing it back no longer drops the value. changeMiddlename only writes the field. Unlike changeFirstname and changeLastname it does not touch the display name, which stays the compact first-plus-last-initials form.
The three name inputs were added to rows that were never designed for a
third child. Adopt the pattern already used by the congregation create
form: flexWrap 'wrap' on the row and flex '1 0 200px' on each field, so
the inputs reflow to 2+1 and then stack on their own, with no breakpoint
hook at all.
That makes nameFlex redundant, and it was dead anyway: it compared
appLang against 'mg-MG', but appLang holds a threeLettersCode ('mlg'),
never a locale, so neither reverse branch could ever run. Remove the
state, the effect and the now unused useBreakpoints in the two other
forms.
Also drop the duplicated middle name writer in useBasicInfo and call
changeMiddlename from @utils/person instead. Its first and last name
siblings are deliberately left alone: unlike the utils versions they
only regenerate the display name while it is still empty.
My profile wrote user_settings.middlename, but nothing carried it off the device, so it disappeared on the next sign-in: - include it in the sync payload built by the backup worker - map it in TABLE_ENCRYPTION_MAP next to firstname and lastname, or the key falls through the unmapped-key recursion in the encryption service - restore it on a fresh device in the three sign-in paths The sign-in paths only write the field when the server actually sent one. The backend has no middle name yet, so an unconditional write would clear a locally entered value on every sign-in. Declaring it optional on UserLoginResponseType records the contract we need without pretending it already exists. Remove cong_settings.circuit_overseer.middlename again. It had no input, no handler and no reader, and giving the circuit overseer one means a third field in a TwoColumnsRow shared by six settings screens, whose row-reverse branch only makes sense with two children. That is a design decision of its own, so leave the dead field out until it is taken.
Add FIRST_MIDDLE_LAST and LAST_FIRST_MIDDLE to FullnameOption and teach buildPersonFullname to place the middle name for them. The middle name is the fourth parameter: the third is a numeric enum, so appending is the only order in which a mis-placed argument is a type error rather than a silent swap. This is behaviour-neutral. The two existing options never read the new parameter, so every congregation keeps byte-identical output until someone picks one of the new formats. personGetDisplayName, speakerGetDisplayName, personGetFullname and usePerson's getName now read the middle name themselves, which covers their callers without touching a single one of them.
Mechanical fourth argument at the remaining direct buildPersonFullname call sites, plus the logged-in user's own name in fullnameState. Six sites are deliberately left alone: the app access screens and the join request item compose a name from user.profile / request, which the backend serves without a middle name. They will follow once the API carries one. Delete personGetScheduleName. It had no callers anywhere in the tree, so it was migrated by nobody and is better removed than updated.
Each option now renders its name on the first row and a worked example underneath in the smaller grey style, reusing the menu item shape the hours-credit presets already use: a MenuItem with minHeight 56px wrapping a Stack of two Typography lines. The example is not a translated sentence. It is produced by running buildPersonFullname over three sample name parts, so it always shows exactly what the selected format will actually produce and new formats need no new strings. New label keys rather than a reword of tr_formatFirstLast and tr_formatLastFirst: those two carry the example inside the string in about fifty approved translations, several of them deliberately localised, and rewording them re-sources the lot. Select needs renderValue here, otherwise the collapsed field would show both rows stacked inside the input. Same treatment the source language selector already uses.
Add cong_settings.print_fullname_option, per data view like its screen counterpart, and one more Select on the meeting forms card built from the shared name format select. printFullnameOptionState falls back to the screen format whenever a congregation has no print format stored. That keeps printed output byte-identical for everyone who never opens the new setting, so no migration is needed and nobody's PDFs change under them. Also stop the language switcher from discarding a middle name format. It rewrites fullname_option from the language default on every UI language change, which would silently undo the choice; it now maps the language's first-or-last ordering onto the matching middle name variant instead.
Point the document builders at printFullnameOptionState: the midweek, weekend and outgoing talks builders in schedules.ts, the S-21 publisher card and the field service group export. All of them are print-only. schedulesWeekGetAssigned has twenty-eight callers and every one sits inside schedulesMidweekData, schedulesWeekendData or scheduleOutgoingSpeakers, and those three are reached only from useMidweekExport and useWeekendExport, so nothing on screen changes. Because printFullnameOptionState falls back to the screen format until a congregation picks one, this is a no-op for existing data and only takes effect once someone chooses a print format.
The circuit overseer and incoming speaker editors decided field order and the gender icon by comparing against FIRST_BEFORE_LAST, which misplaces things for the two middle name formats. Route both through a shared isLastnameFirst helper instead. Also copy the middle name from the linked person into the outgoing speakers sync payload, next to the first and last name overrides, so receiving congregations get it too.
Two thirds of the dummy persons get a middle name from a small fixed pool, chosen by index so reseeding stays deterministic; every third person keeps an empty one so both shapes stay visible while testing. The incoming dummy speaker Ribeiro Gonzaga gets one as well.
"Last First" without a comma and "Last, First" with one are different conventions. The comma-free options serve languages whose natural order puts the family name first (Japanese, Hungarian, Malagasy - the same languages LANGUAGE_LIST already defaults to LAST_BEFORE_FIRST), where a comma would be wrong. The comma marks the western sorted-list inversion used on rosters and official forms, and is what issue sws2apps#4448 spells out as "Last, First". Keeping them separate also keeps this additive: nobody's existing choice changes its rendering, which is exactly the mistake an earlier attempt made when it put a comma into LAST_BEFORE_FIRST for everyone. The language switcher mapping now preserves both the middle name and the comma choice, collapsing the comma back to natural order only when the new language writes the first name first.
useNameFormat and usePrintNameFormat were structural copies of each other. Both now delegate to a single useNameFormatOption that takes the settings field, which keeps the per-folder hook files as thin wrappers and removes the duplicated persistence logic. Also pick the dummy middle names from gender-matched pools so the demo data reads naturally.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis PR adds optional middle-name fields across person, speaker, and user settings data. It updates fullname construction and display paths, introduces separate print fullname formats, adds middle-name editing and import/export support, and persists the new values through onboarding, profiles, schedules, backups, and settings. ChangesMiddle-name data and persistence
Fullname formatting and propagation
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Editor
participant NameFormatSelect
participant useNameFormatOption
participant Dexie
participant ScheduleExport
participant NameBuilder
Editor->>NameFormatSelect: choose print fullname format
NameFormatSelect->>useNameFormatOption: submit FullnameOption
useNameFormatOption->>Dexie: save print_fullname_option
ScheduleExport->>Dexie: read print format and middle name
ScheduleExport->>NameBuilder: build formatted name
NameBuilder-->>ScheduleExport: return printed fullname
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The circuit overseer now follows the congregation name format like everyone else: an optional Middle name input between First and Last, composed into COFullnameState so schedules and the person selector render it with the selected format. Left blank, nothing changes. The name row switches from the reversing TwoColumnsRow to the same fixed-order wrapping row the person editor uses, since reversing a three-field row would produce Last Middle First, which is not a real name order in any locale.
The CO visit blocks in the midweek and weekend document builders read COFullnameState and COScheduleNameState, which compose with the in-app format, so on a CO visit week the overseer was the only name on the printed schedule following the screen format. New COPrintFullnameState and COPrintScheduleNameState mirror their screen counterparts on top of printFullnameOptionState, and both builders now use them. The screen progress helpers keep the screen variants.
The visiting speaker type carried person_middlename, but only outgoing speakers ever set it via the linked person record; for incoming speakers the field was dead because their editor had no input. Add the Middle name field and its update handler, mirroring the first name one minus the display name regeneration, which middle names never feed. The editor row switches to the fixed-order wrapping pattern used by the person and circuit overseer editors, since reversing a three-field row would produce Last Middle First. The gender icon stays on the first name field. That removes the last isLastnameFirst consumer, so the helper goes too.
Deleting a language group marked its fullname_option record deleted but left a chosen print_fullname_option record behind forever. Mirror the same tombstone block for the print format. Also document in the schema why print_fullname_option is deliberately not seeded: while no record exists the atom follows the in-app format, which is what keeps printed output unchanged for congregations that never touch the new setting. Seeding a default would freeze new congregations on First Last even after they switch the screen format. The TEST congregation circuit overseer gets a middle name so the CO paths are visible with demo data.
Once a print format was chosen there was no way back to inheriting the in-app format, because the fallback only applies while no record is stored. The print select now leads with a "Same as in-app format" entry that tombstones the stored record instead of writing a value, and printFullnameOptionState skips tombstoned records so the fallback applies again. Re-choosing a format revives the tombstoned record. The sentinel is deliberately not a FullnameOption member, so it can never be persisted as a real format; the screen select does not render the entry and is unchanged.
defaultWTStudyConductorNameState composed the fallback conductor name with the in-app format, but its only consumer is the weekend document builder, so the printed schedule could disagree with every other name on the page. Same correction the circuit overseer name already got.
|
|
Tick the box to add this pull request to the merge queue (same as
|



Description
Adds middle name support for persons and a separate name format for printed forms, so congregations can keep short names on screen and in schedules while printing full legal names on official forms.
Fixes #4448
Data model
person_middlenamefield onPersonTypeandVisitingSpeakerType,middlenameonuser_settingsand oncong_settings.circuit_overseer, seeded in every schema literal (including the person literal instates/persons.ts).cong_settings.print_fullname_option, stored per data view with the same record shape asfullname_option. The existingfullname_optionkey is not renamed.TABLE_ENCRYPTION_MAPupdated:person_middlename(persons, visiting_speakers, shared),middlename(user settings, public),print_fullname_option(shared).Name composition
FullnameOptiongains four members:FIRST_MIDDLE_LAST,LAST_FIRST_MIDDLE,LAST_COMMA_FIRST,LAST_COMMA_FIRST_MIDDLE. The two existing options never read the middle name, so every current congregation keeps byte-identical output until it picks a new format.LANGUAGE_LISTalready defaults toLAST_BEFORE_FIRST), where a comma would be wrong, while "Gonzalez, William" is the western sorted-list convention requested in the issue.buildPersonFullnameaccepts the middle name as a fourth optional parameter and every direct call site passes it.personGetDisplayName,speakerGetDisplayName,personGetFullnameandusePerson().getNameread it internally, which covers their callers without touching them.generateDisplayNameis unchanged: display names stay compact and never include the middle name.Entry UI
flexWrap: 'wrap'withflex: '1 0 200px'per field, following the congregation create form pattern, so they reflow without breakpoint hooks.TwoColumnsRowto the same fixed-order wrapping row as the person editor, because reversing a three-field row would render Last Middle First, which is not a real name order. His middle name flows throughCOFullnameStateinto schedules and the person selector with the congregation's format.Settings UI
buildPersonFullnameover three translated sample name parts. The preview therefore always matches real output and cannot drift from the code. This reuses the existing menu item shape from the hours-credit presets (MenuItem with minHeight 56px wrapping a Stack of two Typography lines) andrenderValueso the collapsed field shows only the label.NameFormatSelectcomponent and one shared persistence hook parameterised by settings field. The print select leads with a "Same as in-app format" entry, selected by default; choosing it tombstones the stored print record so the fallback applies again, and choosing a concrete format revives it.printFullnameOptionStatefalls back to the in-app format whenever no print format is stored. No migration is needed and printed output does not change until someone chooses a print format. For the same reason the schema deliberately does not seed a default record; seeding one would freeze new congregations on First Last even after they change the screen format.print_fullname_optionrecord the same way it already tombstonesfullname_option.fullname_optionwith the language default on every UI language change; it now maps the language's first-or-last ordering onto the congregation's middle-name and comma choice instead of discarding it.Print paths
The document builders now read
printFullnameOptionState: the midweek (S-140, S-89), weekend and outgoing talks builders inservices/app/schedules.ts, the S-21 publisher card hook and the field service groups export. The circuit overseer name on CO visit weeks goes through newCOPrintFullnameState/COPrintScheduleNameStateatoms, and the default Watchtower study conductor fallback name follows the print format as well, so every printed name obeys the same setting. All of these are export-only code paths (every caller ofschedulesWeekGetAssignedsits inside the three builders, which are reached only from the midweek and weekend export hooks), so on-screen rendering is unaffected.Sync
user_settings.middlenameis included in the backup worker payload and restored on sign-in only when the server actually sends it, so a missing backend field cannot clear a locally entered value.person_middlenamefrom the linked person record, next to the existing first and last name overrides.person_middlename(persons, visiting speakers),user_settings.middlenameandcong_settings.print_fullname_option. All keys are additive; nothing existing is renamed or removed.Localization
New keys in
en:tr_middlename(onboarding), and in congregation:tr_printNameFormat,tr_nameFormatSameAsApp,tr_nameFormatFirstLast,tr_nameFormatLastFirst,tr_nameFormatFirstMiddleLast,tr_nameFormatLastFirstMiddle,tr_nameFormatLastCommaFirst,tr_nameFormatLastCommaFirstMiddle, and the three sample name partstr_nameFormatSampleFirstname,tr_nameFormatSampleMiddlename,tr_nameFormatSampleLastname. The legacytr_formatFirstLastandtr_formatLastFirststrings were left untouched because their translations embed example names; they become unused once this lands and can be retired after the new keys are sourced in Crowdin.Deliberate limits
Type of change
Checklist: