The suffix view space-joins two adjacent SUFFIX tokens when the continuation carries the stable "joined" tag, and comma-joins them otherwise. Which tokens get the tag is currently decided by reasoning about segment shape: tail-by-index (tail_start = 1 after a suffix comma, 2 after a family comma), ORed since #429 with a content predicate, and gated per piece by whether the piece lands in SUFFIX.
That is an indirect way to ask a question the input answers directly: did the writer put a comma between these two words?
The proposal
Derive the entry boundary from the written text — two adjacent SUFFIX tokens render ", "-separated iff a comma stands between their spans, and space-joined otherwise.
Simulated over 1175 corpus names, this reproduces every shape the current machinery gets right, including the two regressions found in review of #429:
| input |
today |
comma-derived |
Smith, MD PhD |
MD PhD |
MD PhD ✓ |
Smith, MD, PhD |
MD, PhD |
MD, PhD ✓ |
Smith, MD Dr. PhD |
MD PhD |
MD PhD ✓ |
Smith Jr., Mr. Jr. |
Jr., Jr. |
Jr., Jr. ✓ |
Smith, Ph. D. Jr. |
Ph. D. Jr. |
Ph. D. Jr. ✓ |
Doe,, Jr., Smith, V MD |
— |
✓ |
It also dissolves two asymmetries the current design records as deliberate:
14 of 1175 corpus names move, so this is a behavior change needing a release-log classification and rules across three ledger tiers.
Why it is a simplification, not just a different rule
The interleaved-title stickiness and the glue-across-a-written-comma bug that review caught are not two rules needing reconciliation. They are the same question — "is there a comma in the gap?" — asked indirectly, which is why the first draft got it wrong in both directions at once. Deriving it directly deletes one_entry, in_entry, entry_open, the tail/seg_cores split, and group's only reason to read _segment_holds_no_name.
Two constraints, both confirmed
It must run after assign. It needs roles: a blanket span rule would join A and B in John A B Smith into one middle_list element. _post_rules already declares pieces and structure as inputs, so a tail pass there is precedented.
The delimiter-core case needs an explicit boundary mark. Smith, MD - PhD - FACS under extra_suffix_delimiters renders PhD, FACS today and would wrongly space-join, because the core is dropped before the render sees it — two of the 14 movers are this. The honest shape is mechanisms.md#MARK-DONT-STRIP: mark the entry boundary at the drop site, where the fact is known, and let the render join everything the writer did not separate. That inverts today's polarity — currently a continuation is marked, from a guess about segment shape — and puts each mark at its decision site.
Keep the "joined" tag as the recorded form. _facade.__setstate__ synthesises span-less Tokens and marks continuations "joined"; the tag is the persisted representation of an entry, and it is what makes list → pickle → list the identity. A render-time span scan cannot work on an unpickled name. So the tag stays and is derived once and recorded, not replaced.
Do not make the tag role-aware. One tag carries two unrelated jobs and only one is suffix-specific. Within a piece it is genuinely role-blind and correct for every role — Smith, Ph. D. Smith routes the merged credential to GIVEN and gives first_list == ['Ph. D.']. Only the between-piece half is a suffix concept, and it is the half this proposal removes.
Cost
Roughly 35 lines out, 20 in, plus 14 corpus movers across three ledger tiers and a release-log entry. PR-sized on its own. Closes #436 if it lands.
Came out of the cleanup review of #435.
The suffix view space-joins two adjacent
SUFFIXtokens when the continuation carries the stable"joined"tag, and comma-joins them otherwise. Which tokens get the tag is currently decided by reasoning about segment shape:tail-by-index (tail_start= 1 after a suffix comma, 2 after a family comma), ORed since #429 with a content predicate, and gated per piece by whether the piece lands inSUFFIX.That is an indirect way to ask a question the input answers directly: did the writer put a comma between these two words?
The proposal
Derive the entry boundary from the written text — two adjacent
SUFFIXtokens render", "-separated iff a comma stands between their spans, and space-joined otherwise.Simulated over 1175 corpus names, this reproduces every shape the current machinery gets right, including the two regressions found in review of #429:
Smith, MD PhDMD PhDMD PhD✓Smith, MD, PhDMD, PhDMD, PhD✓Smith, MD Dr. PhDMD PhDMD PhD✓Smith Jr., Mr. Jr.Jr., Jr.Jr., Jr.✓Smith, Ph. D. Jr.Ph. D. Jr.Ph. D. Jr.✓Doe,, Jr.,Smith, V MDIt also dissolves two asymmetries the current design records as deliberate:
John Smith MD PhD→MD PhD(todayMD, PhD— this is parse("John Smith MD PhD").suffix returns "MD, PhD" — a run without a comma renders with one #436)MD PhD Jr., John→PhD Jr.(todayPhD, Jr., pinned asfamily_comma_segment_zero_is_not_the_run)14 of 1175 corpus names move, so this is a behavior change needing a release-log classification and rules across three ledger tiers.
Why it is a simplification, not just a different rule
The interleaved-title stickiness and the glue-across-a-written-comma bug that review caught are not two rules needing reconciliation. They are the same question — "is there a comma in the gap?" — asked indirectly, which is why the first draft got it wrong in both directions at once. Deriving it directly deletes
one_entry,in_entry,entry_open, thetail/seg_coressplit, andgroup's only reason to read_segment_holds_no_name.Two constraints, both confirmed
It must run after
assign. It needs roles: a blanket span rule would joinAandBinJohn A B Smithinto onemiddle_listelement._post_rulesalready declarespiecesandstructureas inputs, so a tail pass there is precedented.The delimiter-core case needs an explicit boundary mark.
Smith, MD - PhD - FACSunderextra_suffix_delimitersrendersPhD, FACStoday and would wrongly space-join, because the core is dropped before the render sees it — two of the 14 movers are this. The honest shape ismechanisms.md#MARK-DONT-STRIP: mark the entry boundary at the drop site, where the fact is known, and let the render join everything the writer did not separate. That inverts today's polarity — currently a continuation is marked, from a guess about segment shape — and puts each mark at its decision site.Keep the
"joined"tag as the recorded form._facade.__setstate__synthesises span-lessTokens and marks continuations"joined"; the tag is the persisted representation of an entry, and it is what makeslist → pickle → listthe identity. A render-time span scan cannot work on an unpickled name. So the tag stays and is derived once and recorded, not replaced.Do not make the tag role-aware. One tag carries two unrelated jobs and only one is suffix-specific. Within a piece it is genuinely role-blind and correct for every role —
Smith, Ph. D. Smithroutes the merged credential toGIVENand givesfirst_list == ['Ph. D.']. Only the between-piece half is a suffix concept, and it is the half this proposal removes.Cost
Roughly 35 lines out, 20 in, plus 14 corpus movers across three ledger tiers and a release-log entry. PR-sized on its own. Closes #436 if it lands.
Came out of the cleanup review of #435.