polytypo 1.6.0 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d51bb2ff87f0296fb0e541eb321c03045e3ea6ccee4ff983cccbfa39469d5714
4
- data.tar.gz: 77cdd431d046c4e3708053a651712363e5bef21c2f9c245fd203f74c7f68917a
3
+ metadata.gz: cf167fa991d92f3a4e63e03b4b4f96882a5842a2c80759941533cde64b0cf5a4
4
+ data.tar.gz: 8337a2ad5508d5d698b5130329753eacf8534ec5b7ead365981f8d42fa85931f
5
5
  SHA512:
6
- metadata.gz: 5ff10230c85bb817a99c225d36fc3525b3690017fb157c78ef208a8a5a9f94df0bfa220c633a9f02a72c8dd5b393eac1768cdc65b57ce0cfbb8524d0cd23b512
7
- data.tar.gz: a1ddd083a482adf73af4009b3df9dea11809f21163bb95503bcc7abec611cdaa5ef1dfad40189295e28b1ee3280efe4a40b6d620a2a002c439128bd99051cac1
6
+ metadata.gz: d408de52fe21124d92a29ef3492fccaeca452ae11c80924a2e96dd6f69509d23b21263efb4635db3d7b41b79d02798eb261eb8be87b72d24ad8f1f139a13cb59
7
+ data.tar.gz: a9a451994c2b99626c5eaa702c50bd2d6b4cb2c68ee754b9711343cb51a0cf59893c3e9443203483f33adf5513a3e5431cc72d274f089004759034570610084b
@@ -0,0 +1,4 @@
1
+ # Paths under this directory that this repository authors itself, so
2
+ # check-vendored-spec.sh must not look for them in canonical polytypo's spec/.
3
+ # Everything else here is compared against canonical at tag spec-v$(cat VERSION).
4
+ README.md
@@ -1,10 +1,12 @@
1
1
  # Vendored spec subset
2
2
 
3
- This directory is a manually-synced copy of the subset of `polytypo/polytypo`'s canonical `spec/`
4
- that this gem needs at runtime: `locales/`, `fixtures/`, `rules/order.json`, `rules/dashes.md`,
5
- `schema/`, `VERSION`, `UNICODE`. It is **not** the canonical spec — the rest of the normative
6
- prose (`spec/rules/*.md` beyond `dashes.md`) and `validate-spec.mjs` live only in
7
- `polytypo/polytypo`.
3
+ This directory is a manually-synced copy of a subset of `polytypo/polytypo`'s canonical `spec/`:
4
+ `locales/`, `fixtures/`, the whole of `rules/`, `schema/`, `VERSION` and `UNICODE`. The gem and its
5
+ specs read only part of that — the locale and fixture data, `rules/order.json` and
6
+ `rules/dashes.md`; the other twelve rule documents are carried as the normative prose for the
7
+ behaviour the data drives, next to the data. It is **not** the canonical spec: `validate-spec.mjs`
8
+ lives only in `polytypo/polytypo`, and so does the authority — a change starts there and arrives
9
+ here by re-copying, never the other way round.
8
10
 
9
11
  Named `lib/polytypo/data/`, not `vendor/polytypo-spec/` like the JS/Python ports: a Ruby
10
12
  gemspec's `files` list can include any path directly, so there is no need for a separate
@@ -14,7 +16,23 @@ one copy, shipped as part of the gem's own `lib/` payload and loaded via `File.r
14
16
  `__dir__` at runtime — never a network or external-filesystem read.
15
17
 
16
18
  Editing a file here does not change the spec; it only drifts this copy from canonical. When
17
- canonical's `spec/` changes, re-copy the affected files here. How this vendoring will work
19
+ canonical's `spec/` changes, re-copy the affected files here.
20
+
21
+ CI checks that it was done. `script/check-vendored-spec.sh` compares every file in this
22
+ directory against canonical `polytypo/polytypo` at tag `spec-v` + this directory's own
23
+ `VERSION`, and fails on any difference. Three details. The files this repository authors
24
+ itself are listed in `.not-canonical` and skipped — and a listed path that canonical *does*
25
+ have is an error, so that list cannot be used to keep a forked copy of a canonical file. A
26
+ `locales/*.json` file is compared in full when it carries its `sources` array, and against
27
+ canonical minus that array when it does not, which is the shipped form three of the five
28
+ runtimes vendor; the script reads which case it is off the file rather than being told. And a
29
+ file here with no canonical counterpart is a failure, so a canonical rename cannot pass
30
+ unnoticed. Completeness is deliberately not checked — each runtime vendors its own subset,
31
+ and the subsets differ.
32
+
33
+ Before that check existed, this half of the tree went stale unnoticed in four of the five
34
+ ports at once: the data half is proved by the test suite, and nothing at all read the prose.
35
+ See `polytypo/polytypo` issue #56. How this vendoring will work
18
36
  long-term (submodule, per-ecosystem spec package, or something else) is an open decision tracked
19
37
  in `polytypo/polytypo`'s roadmap; this is the interim, manually-synced form — the same status
20
38
  every other port's vendored copy has.
@@ -1 +1 @@
1
- 1.6.0
1
+ 1.6.2
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "cs",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "de-CH",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "de-DE",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "el",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "en-GB",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "en-US",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "es",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "fi",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "fr-CA",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "fr",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "it",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "cases": [
4
4
  {
5
5
  "id": "exact-en-us",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "nl",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "pl",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "pt-BR",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "pt-PT",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "ru",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "sv",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "tr",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "locale": "uk",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "$comment": "Locale resolution input. Algorithm is specified in spec/rules/locale-resolution.md and is identical in every runtime — never delegate it to a platform locale-negotiation library. \"spec\" here must track spec/VERSION exactly — it is not itself the global version source; scripts/validate-spec.mjs enforces the match.",
4
4
  "locales": [
5
5
  "en-US",
@@ -571,12 +571,37 @@ layouts and in text pasted from older systems. Converting them is not authorised
571
571
  gives `A ‘quoted’’s meaning`: the closing quotation mark and the possessive end up flush, and
572
572
  at text size the pair reads as one double quote.
573
573
 
574
- **The authority names both the remedy and the character.** `CMOS`'s own editors describe the
575
- fix as adding a space between the contiguous marks, and enumerate it by code point — U+00A0,
576
- or a thin space U+2009 or hair space U+200A in print, or U+202F, which *CMOS* Online itself
577
- now sets between a quotation mark and an apostrophe (18th ed. §6.11, as described in *CMOS
578
- Shop Talk*, "When Quotation Marks and Apostrophes Collide", updated
579
- 2025-12-16).
574
+ The mark case 2a adds is always U+2019; the mark it abuts is whichever `CLOSEDELIM` member
575
+ the locale closed with, and three of that class's four quotation members are reachable as a
576
+ locale's primary close, so this is **not an `en-GB` phenomenon**. Grouped by `quotes.primary.close`, seventeen of the
577
+ nineteen locales reach it through case 2a: `’’s` in `en-GB`; `”’s` in `en-US`, `fi`, `sv`,
578
+ `nl`, `pl`, `pt-BR` and `tr`; `»’s` in `de-CH`, `fr`, `fr-CA`, `ru`, `el`, `es`, `it`,
579
+ `pt-PT` and `uk`. Only the first two are plausibly confusable, and **which pairs those are is
580
+ a property of the glyph pair, not of the language** — so no existing locale field expresses
581
+ the distinction a separator rule would have to make.
582
+
583
+ **`de-DE` and `cs` look like this and are not it.** They close with U+201C, which §3.1 puts
584
+ in `OPENISH` and deliberately *not* in `CLOSEDELIM`, so `A „quoted“'s meaning` reaches its
585
+ U+2019 through **case 4** (leading elision: `OPENISH` left, `ALNUM` right), which is 0.4.1
586
+ vintage — the same U+201C-is-not-closeish asymmetry case 3a records for its own reason.
587
+ Measured on the published packages: 1.4.0 and 1.6.0 both emit `A „quoted“’s meaning`, while
588
+ `en-US` emits `A “quoted”'s meaning` on 1.4.0 and `A “quoted”’s meaning` on 1.6.0. The
589
+ `“’s` shape predates case 2a and is unchanged by it, so it is not this item's accepted cost.
590
+
591
+ **What the authority says, and what it does not.** The remedy and the character are both
592
+ named, but by a *CMOS Shop Talk* post — "When Quotation Marks and Apostrophes Collide",
593
+ published 2020-01-14, updated 2025-12-16 — and what that post describes
594
+ is the typesetting *CMOS* Online applies **to its own website**, not a rule stated for English
595
+ text. It enumerates the separator by code point (U+00A0, thin space U+2009, hair space U+200A
596
+ in print, U+202F online) and cross-references 18th ed. §6.11. **That cross-reference does not
597
+ survive a check against §6.11's own text, which this repository already holds.**
598
+ `spec/locales/en-US.json` quotes it verbatim: “When single quotation marks are nested within
599
+ double quotation marks, and two of the marks appear next to each other, a space between the
600
+ two marks, though not strictly required, aids legibility.” That is about *nested quotation
601
+ marks*, not an apostrophe, and it is permissive (“not strictly required”) rather than
602
+ prescriptive. The 18th edition's index files apostrophe adjacency somewhere else entirely,
603
+ at `apostrophes: other punctuation with, 6.126`; §6.126 and §6.128 are behind the
604
+ subscription and unread.
580
605
 
581
606
  **polytypo emits the right character and inserts nothing.** This rule *cannot* insert: §1 and
582
607
  §4 make every edit one code point replacing one code point at the same index, and that is
@@ -585,11 +610,28 @@ layouts and in text pasted from older systems. Converting them is not authorised
585
610
  between them is unaddressed by every rule in `order.json` — recorded here as a decision
586
611
  rather than left as an omission, in the standing of items 2 and 3.
587
612
 
588
- **A future `nbsp` sub-rule would need its own citation, not this one.** The attested passage
589
- is one mark-order away from the shape case 2a produces: it separates a title's *own* trailing
590
- apostrophe from a following closing quotation mark — its example is the song title *Ain't
591
- Misbehavin'* set in single quotation marks, so the two marks there are the title's own
592
- apostrophe and then the closing quote, apostrophe first. The possessive ordering — closing mark, then apostrophe, then `s` —
593
- is addressed by nothing retrieved, and `CMOS` §7.29 ("Possessive with italicized or quoted
594
- terms"), the paragraph that governs it, is behind a subscription and unread. Borrowing the
595
- citation across that difference is exactly the move this project settles by evidence instead.
613
+ **No separator is inserted, and that is now a decision rather than an open question
614
+ (2026-09-24).** No normative source addresses the ordering case 2a produces — closing mark,
615
+ then apostrophe, then `s`. What exists is one mark-order away from it and is not normative
616
+ anyway: the attested passage separates a title's *own* trailing apostrophe from a *following*
617
+ closing quotation mark (the song title *Ain't Misbehavin'* in single quotation marks —
618
+ apostrophe first), and it describes a publisher's rendering practice for its own website. The
619
+ paragraph that would govern the possessive ordering, `CMOS` §7.29 ("Possessive with
620
+ italicized or quoted terms"), is behind a subscription. `MHRA` was read in full and is silent;
621
+ Kotus names no code points at all; the Swedish genitive takes no apostrophe, so the shape is
622
+ not Swedish. **Operator decision: where no exact answer exists, choose one behaviour and pin
623
+ it rather than leave the rule undefined — consistency across the five runtimes is the
624
+ product, not conformance to a style guide nobody can read.** The pinned choice is *no
625
+ separator*: the marks sit flush.
626
+
627
+ **What makes that the cheap side of the choice.** The pair a reader could actually misread as
628
+ one mark is `’’`, and it is **unreachable from ordinary typing** — exhaustive sweep over the
629
+ alphabet `'` `"` `a` `s` space, 370,975 inputs to length 6 across all nineteen locales and
630
+ 1,464,825 to length 8 across `en-GB`, `en-US` and `fi`: zero produce it. Two straight marks
631
+ are vetoed by `quotes` V1, and a straight `"…"` pair in a `NARROW`-primary locale is declined
632
+ by the certification gate for the same adjacency, so `’’` needs a closing mark that was
633
+ *already* U+2019 in the source. The pairs reachable from real input — `”’s`, `“’s`, `»’s`
634
+ — are legible as two marks. Inserting a space would mean a new `nbsp` insertion *position*
635
+ class, which `nbsp.md` §5's `I₆` and `quotes.md` §5's Lemma B would both have to be
636
+ re-derived for, in exchange for a cosmetic gain on text someone has already typeset. Recorded
637
+ as the standing answer; do not reopen it on a citation hunt.
@@ -900,7 +900,10 @@ never reaches a digit-flanked token at all.
900
900
  ### `el` — `parenthetical: "none"` (`range` is now [ranges.md](ranges.md)'s field, also `"none"`, not this rule's)
901
901
 
902
902
  The first locale with `"none"` on **both** fields, so the rule is a **total no-op** for it in the
903
- same provable sense `hyphen` is a no-op for a locale with empty lists. Tokens are still
903
+ same provable sense `hyphen` is a no-op for a locale with empty lists. It is no longer the only
904
+ one: `es` (spec 1.3.0) and `tr` (spec 1.6.0) are both-`none` too, each for its own reason and each
905
+ recorded in its own locale file. This section stays written about `el` because its two fields are
906
+ `"none"` for two *different* reasons, which is what makes it worth reading. Tokens are still
904
907
  classified — §3.3's "a range token is never reconsidered as a parenthetical" still holds — but no
905
908
  classification has an emission to make. The rows below are therefore all "no change" rows by
906
909
  construction, and they are worth pinning precisely because nothing else in the suite exercises
@@ -193,7 +193,7 @@ that "no tag was supplied at all" is expressible as a fixture rather than only a
193
193
  raise the coded error rather than a native `TypeError` — which matters because a `TypeError` is
194
194
  not in the taxonomy (ARCHITECTURE.md §4.6) and would differ in each of the five runtimes.
195
195
 
196
- Every row is a fixture, not a candidate: **35 resolution cases run today**, covering these rows
196
+ Every row is a fixture, not a candidate: **50 resolution cases run today**, covering these rows
197
197
  and the malformed-tag rejections. `fixtures.schema.json` was extended with a case shape carrying
198
198
  no `mode` and no `out`, whose expected result is a locale id or a thrown code. An earlier
199
199
  revision of this paragraph said such rows "cannot be expressed in the existing fixture format";
@@ -210,11 +210,11 @@ that was true when written, and is why the schema was changed.
210
210
  language polytypo does not support yet). I have mapped both to
211
211
  `POLYTYPO_UNKNOWN_LOCALE` because inventing a code would change a documented contract.
212
212
  Recommend adding `POLYTYPO_INVALID_LOCALE`; operator decision.
213
- 2. *(Closed.)* Resolution **is** fixture-covered: 35 resolution cases run today. The gap this
213
+ 2. *(Closed.)* Resolution **is** fixture-covered: 50 resolution cases run today. The gap this
214
214
  item reported — that `fixtures.schema.json` could not express a case with no `mode`, no `out`
215
215
  and an expected locale id or thrown code — was closed by extending the schema, and the §5
216
216
  table's rows are those cases. (The item also miscounted the pipeline as seven rules; it is
217
- eight, since `hyphen` was added at order 35.)
217
+ nine — `hyphen` was added at order 35 and `ranges` at 37.)
218
218
 
219
219
  3. **`sv-FI` (Finland Swedish) silently resolves to `sv`.** The two genuinely differ in some
220
220
  conventions, and PLAN.md §7 already flags Swedish quote practice as uncertain. The
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.6.0",
2
+ "spec": "1.6.2",
3
3
  "$comment": "Single source of truth for pipeline order. Rules run in ascending `order`. Disabling a rule removes it from the sequence and never reorders the rest. Rule ids are public API (see docs/ARCHITECTURE.md section 5). \"spec\" here must track spec/VERSION exactly — it is not itself the global version source; scripts/validate-spec.mjs enforces the match.",
4
4
  "rules": [
5
5
  {
@@ -595,14 +595,58 @@ typesets as `A ’stray mark here.` / `Then ‘inner’ text.`, and appending `S
595
595
  re-pairs the first mark with the new one, making the middle pair nested and changing a line the
596
596
  edit did not touch. Measured on 1.3.1 and unchanged by this veto.
597
597
 
598
- **This is issue #53 §3, tracked as issue #54, and it stays open.** The report's own trigger lines are
598
+ **This is issue #53 §3, and as of 2026-09-24 it is decided rather than open: the behaviour
599
+ stands.** The report's own trigger lines are
599
600
  `` `quotes`' locale data `` and `` `dashes`' own admissibility `` — plural possessives, exactly
600
601
  this shape — so the cross-paragraph effect it documents in a real article is *not* repaired by
601
602
  spec 1.4.0. Measured on this change, `markdown`/`commonmark`, `en-GB`:
602
603
  `It is 'the `` `quotes`' `` locale data' here.` gives
603
604
  `It is ‘the `` `quotes`’ `` locale data’ here.` — the plural possessive closes the quotation two
604
605
  words early and the author's own closing mark is left to `apostrophe` as a stray U+2019. §1 of
605
- that issue is closed and §3 is not; the two must not be conflated when the issue is triaged.
606
+ that issue is closed by the veto above; §3 is not, and is not going to be.
607
+
608
+ **Why §3 is decided and not merely unfixed.** Two measurements, both on a published package.
609
+
610
+ First, **the U+2019 at the possessive's own position is produced by the pairing, and nothing else
611
+ can produce it.** `apostrophe` cannot reach that mark: its left neighbour is the inline `MARKER`,
612
+ which §3.1 deliberately keeps out of [apostrophe.md](apostrophe.md) §3.1's `CLOSEDELIM`, and its
613
+ right neighbour is a space, so no case in that rule's ladder fires. Measured on published 1.6.1,
614
+ `markdown`/`commonmark`, `en-GB`: `` The `xs`' printer works. `` comes back **unchanged** — an
615
+ unmatched span-boundary plural possessive keeps its U+0027. So the cost of this behaviour is a
616
+ consumed *pairing*, and the visible symptom of that appears elsewhere (a stray U+0027 at the real
617
+ closing mark, or a middle paragraph re-nested from `‘ ’` to `“ ”`) — but declining the mark does
618
+ not buy the right glyph in exchange. It moves the straight mark rather than removing it. An
619
+ earlier revision of this paragraph claimed the glyph was "correct either way"; it is not, and the
620
+ measurement above is what replaced the claim.
621
+
622
+ Second, **separating the two readings was implemented and measured, and it is a trade rather than
623
+ a fix.** The narrowest local veto that can do it — decline a `NARROW` mark whose literal
624
+ neighbour is `MARKER` and whose attaching `LETTER` run is empty — breaks **6 of the 1366 fixture
625
+ cases** (7 tests in polytypo-js's runner, which counts each case's idempotency re-run separately;
626
+ the denominator to reproduce is the suite's case count in [CONFORMANCE.md](../CONFORMANCE.md),
627
+ never a runtime's test total). Five are `tr` cases that are correct today:
628
+ `tr-html-span-boundary-suffix-declined`,
629
+ `tr-markdown-commonmark-span-boundary-suffix-declined`,
630
+ `tr-html-span-boundary-quotation-outside-the-span`,
631
+ `tr-html-span-boundary-non-ascii-initial-fragment` and `tr-html-span-boundary-ordinal-fragment`.
632
+
633
+ The sixth is this section's own fixture, `en-gb-markdown-commonmark-span-boundary-plural-possessive-not-closed`,
634
+ and it is the one worth reading: the veto does exactly what it is aimed at and still comes out
635
+ wrong. `He says 'avoid `` `xs`' `` printer.' Done.` typesets today as
636
+ `He says ‘avoid `` `xs`’ `` printer.' Done.` — pair closed two words early, author's own mark left
637
+ straight. Under the veto it becomes `He says ‘avoid `` `xs`' `` printer.’ Done.` — the pair now
638
+ closes at the author's mark, which is right, and the typewriter apostrophe has moved to the
639
+ possessive, which is not, for the reason the first measurement gives. That is this section's own
640
+ claim — no test over these neighbours can separate a plural possessive from a closing mark after a
641
+ span — turned from an argument into a number, and into a second output nobody would fixture.
642
+
643
+ **Operator decision (2026-09-24): where no exact answer exists, choose one behaviour and pin it
644
+ rather than leave the rule undefined.** Five runtimes agreeing byte-for-byte is the product; an
645
+ undecided rule is worse than an arbitrary decided one. The pinned behaviour is the one specified
646
+ here and fixtured at §6 row S5. A future change would have to be a **pairing-preference**
647
+ mechanism, not a classification test, and §3.3's three-outcome partition is the premise §5's
648
+ certification theorem rests on — so it replaces that premise rather than extending it, and needs
649
+ its own idempotency argument. Nobody should spend that on this without a new reason.
606
650
 
607
651
  **V1 — same-V1-identity adjacency veto** (both widths):
608
652
 
@@ -1330,7 +1374,7 @@ what a port should be able to re-derive from §3.2 alone.
1330
1374
  | S2 | `fr` | `Il dit 'l'<em>idée</em> est bonne.' Fin.` | `Il dit «⍽l’<em>idée</em> est bonne.⍽» Fin.` | the mirror direction: `Rlit` is the `MARKER`, the run left of the mark is `l`, a cited `before` entry. Through 1.3.1 this gave `Il dit «⍽l⍽»<em>idée</em> est bonne.' Fin.` — guillemets around one letter, the real closing mark abandoned |
1331
1375
  | S3 | `fr` | `Il dit 'jusqu'<em>ici</em> tout va bien.' Fin.` | `Il dit «⍽jusqu’<em>ici</em> tout va bien.⍽» Fin.` | the run is compared **whole**: it is `jusqu`, so an entry `qu` could not match it. This is why `jusqu`, `lorsqu`, `puisqu` and `quoiqu` are listed in their own right |
1332
1376
  | S4 | `fr` | `Il dit <em>'oui'</em> ici.` | `Il dit <em>«⍽oui⍽»</em> ici.` | the negative control the mechanism exists to preserve. `Llit` is the `MARKER` here too — what separates this from S2 is only that `oui` is not a listed fragment |
1333
- | S5 | `en-GB` | `` He says 'avoid `xs`' printer.' Done. `` | `` He says ‘avoid `xs`’ printer.' Done. `` | **not closed.** The plural possessive's run is empty, so neither test applies, and these code points are also a closing mark after a span. The second mark takes the pairing, the third is abandoned as U+0027. §4, §7 item 10, issue #54 |
1377
+ | S5 | `en-GB` | `` He says 'avoid `xs`' printer.' Done. `` | `` He says ‘avoid `xs`’ printer.' Done. `` | **not closed.** The plural possessive's run is empty, so neither test applies, and these code points are also a closing mark after a span. The second mark takes the pairing, the third is abandoned as U+0027. **Decided behaviour, not a pending fix** — §3.2. §4, §7 item 10, issue #54 |
1334
1378
  | S6 | `fr` | `Il dit 'QU'<em>il</em> vienne.' Fin.` | `Il dit «⍽QU⍽»<em>il</em> vienne.' Fin.` | **not closed.** The fold reaches only the run's first code point, and only ASCII `A`–`Z`, so `QU` does not match `qu` and the inversion survives. Same limit as the listed veto's context words |
1335
1379
  | S7 | `en-US` | `He said <em>'s'</em> loudly.` | `He said <em>’s’</em> loudly.` | the accepted false positive: a quotation inside a span whose whole content is a listed fragment. Narrower than the medial-`n` veto's own accepted `The letter 'n' is common.`, since it needs the boundary as well |
1336
1380
  | S8 | `nl` | `Hij zegt 'ik kom <em>vroeg </em>'s avonds terug.' Klaar.` | `Hij zegt “ik kom <em>vroeg </em>’s avonds terug.” Klaar.` | `'s` is a word-*initial* omission, so its run lies to the mark's right and the entry is an `after` one — the clearest case for reading both lists positionally (§2) |
@@ -1480,7 +1524,8 @@ Ordered by how much this matters.
1480
1524
  rule. Two shapes stay open in **every** locale, both for the same reason: the attaching
1481
1525
  fragment is not there to read. A plural possessive after a span (`` `xs`' ``) has an empty
1482
1526
  run and is byte-identical to a closing mark after a span (§4) — **this is issue #53 §3, the
1483
- cross-paragraph witness that motivated the report; it is open and tracked as issue #54**; and a fragment written in
1527
+ cross-paragraph witness that motivated the report, and it is decided rather than open — §3.2
1528
+ gives the two measurements and issue #54 records the close**; and a fragment written in
1484
1529
  capitals (`QU'<em>il</em>`) fails the first-code-point folding this rule shares with item 8's
1485
1530
  listed veto. Neither is a candidate for a wider mechanism: widening the folding is
1486
1531
  `ARCHITECTURE.md` §4.4's banned territory, and the plural possessive has no local evidence at
@@ -1539,7 +1584,8 @@ consulted only when the mark is flush against an inline span boundary. It closes
1539
1584
  §1** — a possessive or elision written against a span (`` `x`'s ``, `l'<em>idée</em>`) was
1540
1585
  classified as a quotation candidate, took the pairing from the author's own mark inside a
1541
1586
  quotation, and inverted the pair in every locale. **It does not close issue #53 §3**, the
1542
- cross-paragraph damage that motivated the report, which is tracked separately as issue #54: that witness is a *plural* possessive after a
1587
+ cross-paragraph damage that motivated the report, which was tracked separately as issue #54 and
1588
+ is decided rather than fixed (2026-09-24, §3.2): that witness is a *plural* possessive after a
1543
1589
  span, and §3.2 records why no veto over these neighbours can reach it. The simpler design — letting the boundary marker satisfy the
1544
1590
  medial-elision veto's `ALNUM` test — was implemented, measured, and rejected before any release:
1545
1591
  it breaks a quotation that legitimately begins or ends at a span boundary, including the
@@ -1547,3 +1593,15 @@ released fixture `en-us-markdown-commonmark-boundary-nested-quotes` and the `NAR
1547
1593
  `modes.md` §3.3's normative rows — not their `WIDE` forms, which both vetoes leave alone. §3.2 records that measurement, the accepted false positive (a quotation whose
1548
1594
  whole content is one listed fragment), and the one shape no veto over these neighbours can
1549
1595
  reach — the plural possessive, which is byte-identical to a closing mark after a span.
1596
+
1597
+ 1.6.2 corrects §3.2's own measurements of the decision above; no behaviour, fixture or locale
1598
+ field changes. Two errors, both introduced with the decision record on 2026-09-24 and both
1599
+ overstating how cheap the decided behaviour is. The first claimed the glyph at the possessive's
1600
+ position was "correct either way" — it is not: `apostrophe` cannot reach a mark whose left
1601
+ neighbour is the inline `MARKER`, so declining the pairing leaves U+0027 there, measured on the
1602
+ published 1.6.1 package. The second counted **7 of 2801 conformance cases** where 2801 is
1603
+ polytypo-js's vitest test total, a figure no other runtime can reproduce; the cost is **6 of the
1604
+ 1366 fixture cases**, and the sixth — this section's own `en-GB` fixture — is now described
1605
+ rather than left in the count, because the veto moves the pair to the author's mark correctly and
1606
+ moves the typewriter apostrophe to the possessive at the same time. A conformance count in this
1607
+ document is a count of fixture cases, never of one runner's tests.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polytypo
4
- VERSION = "1.6.0"
4
+ VERSION = "1.6.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polytypo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Iurii Rogulia
@@ -35,6 +35,7 @@ files:
35
35
  - LICENSE
36
36
  - README.md
37
37
  - lib/polytypo.rb
38
+ - lib/polytypo/data/.not-canonical
38
39
  - lib/polytypo/data/README.md
39
40
  - lib/polytypo/data/UNICODE
40
41
  - lib/polytypo/data/VERSION