From bec0d2f215b5b664e9919960e7c6f401a39dee28 Mon Sep 17 00:00:00 2001 From: Blake Bertuccelli-Booth <46652+bbertucc@users.noreply.github.com> Date: Fri, 23 Oct 2026 12:16:00 -0400 Subject: [PATCH 1/4] fix(demo): never label a PDF field with its raw name when labels repeat (#593) Co-Authored-By: Claude Opus 5.5 --- public/demo.html | 23 +- test/demo-pdf-form.test.ts | 28 +- test/fixtures/sc100-fields.json | 1078 +++++++++++++++++++++++++++++++ 3 files changed, 1121 insertions(+), 8 deletions(-) create mode 100644 test/fixtures/sc100-fields.json diff --git a/public/demo.html b/public/demo.html index 397fa3a1..5fb4d8f7 100644 --- a/public/demo.html +++ b/public/demo.html @@ -453,15 +453,26 @@

About

.replace(/^./, (c) => c.toUpperCase()); const xfa = (n) => (/\[\d+\]/.test(n) ? words(n.split('.').pop().replace(/\[\d+\]/g, '')) : ''); const named = (f) => (typeof f.label === 'string' && f.label.trim()) || (f.created ? words(f.name) : xfa(f.name) || f.name); - // Two fields must not read the same: a repeat gets its page, and if that still - // repeats, its full name. + // Two fields must not read the same, and none may read as its raw name (#593: SC-100's + // page 2 has four fields labelled "city"). A repeat is read from its name instead + // (`Plaintiff mailing city 1`), then given its page, then more of its name, then a number. const shown = fields.filter((f) => !f.readonly); const count = (xs) => xs.reduce((m, s) => m.set(s, (m.get(s) || 0) + 1), new Map()); + const tail = (f, k) => words(f.name.split('.').map((p) => p.replace(/\[\d+\]/g, '')).filter(Boolean).slice(-k).join(' ')); + const page = (f) => (f.page ? ` (page ${f.page})` : ''); let texts = shown.map(named); - let seen = count(texts); - texts = texts.map((s, i) => (seen.get(s) > 1 && shown[i].page ? `${s} (page ${shown[i].page})` : s)); - seen = count(texts); - texts = texts.map((s, i) => (seen.get(s) > 1 ? shown[i].name : s)); + const retell = (to) => { + const seen = count(texts); + texts = texts.map((s, i) => (seen.get(s) > 1 ? to(shown[i], s) : s)); + }; + retell((f, s) => tail(f, 1) || s); + retell((f, s) => s + page(f)); + for (let k = 2; k <= 4; k++) retell((f, s) => tail(f, k) + page(f) || s); + const nth = new Map(); + retell((f, s) => { + nth.set(s, (nth.get(s) || 0) + 1); + return `${s} (${nth.get(s)})`; + }); const text = new Map(shown.map((f, i) => [f, texts[i]])); const label = (f) => text.get(f) + req(f); let n = 0; diff --git a/test/demo-pdf-form.test.ts b/test/demo-pdf-form.test.ts index 2559ef8e..385fee03 100644 --- a/test/demo-pdf-form.test.ts +++ b/test/demo-pdf-form.test.ts @@ -106,11 +106,35 @@ test("an acronym keeps its capitals, and two fields that would read the same are "DOB field", "Case number (page 1)", "Case number (page 2)", - "F[0].P2[0].A[0].Name[0]", - "F[0].P2[0].B[0].Name[0]", + "A name (page 2)", + "B name (page 2)", ]); }); +test("no label on SC-100's real form is a raw name or a repeat (#593)", () => { + // The field list iris-pdf returned for SC-100 on UIC: page 2 labels four fields "city". + const fields = JSON.parse(readFileSync(join(repoRoot, "test", "fixtures", "sc100-fields.json"), "utf8")) as Field[]; + const doc = new JSDOM('t
').window.document; + const list = doc.getElementById("f")!; + assert.equal(renderFields(doc, list, fields), 95); + // Each control's own label: a text box's