mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
fix(ui): close the holes the audit found in this work's own guards
Every item here is a check that could pass while the thing it checks is broken, or a margin the sweep's codemod could not reach. Guards: - The access-payload matcher accepted `.level` on ANY object. A chart's zoom level or a log record's level would have satisfied it and reported a consumer that does not exist. It now names the bindings the entry actually arrives under; a third binding is a deliberate edit rather than a silent widening. Re-falsified: rename both readers, it goes red. - The `pl-7` sweep skipped any line CONTAINING `//`, so a class string sharing a line with a URL was waved through. Only the part after a real line-comment marker is exempt now. Proven by putting `pl-7` on a line with an `https://` comment: caught. - `LEGIT_IDENTICAL` in the English-leak guard had no staleness check, unlike both of its siblings, and carried two dead keys — one from a removal long before this work, one from the duplicated version fields that came off `/admin`. Both pruned, the check added, falsified with a made-up key. - `split-description.mjs` split inside `z. B.`, `d. h.`, `u. a.` and `p. ex.` and produced a head ending mid-abbreviation. Both halves have to be caught, which is why the rule is a shape (single letter + stop) rather than a list, and the merge re-tests its own tail. Exercised against eight cases including the ones that used to break. Margins the codemod could not see, because the child lives in a different component from the card that spaces it: six form roots under managed record settings (one of which also carried the banned `space-y-5` step), the low-stock status line, the avatar card's explainer, and the backups restore report. Each was adding a margin on top of the card's own gap. Two facts the text diet cut too close, both restored inside the guideline: that an excluded injection site wins over a medication that prefers it, and that the privacy card's second button leads to a full health record.
This commit is contained in:
1 parent
23eae2823e
commit
272c426bea
8 files changed
+112
-20
No files matched your search
@@ -21,6 +21,63 @@ import { readFileSync, writeFileSync } from "node:fs";
|
||||
const LOCALES = ["de", "en", "es", "fr", "it", "pl"];
|
||||
const BOUNDARY = /(?<=[.!?])\s+(?=[A-ZÄÖÜÀÂÉÈÊÎÔÙÛÇŁŚŹŻĄĆĘŃÓ0-9„"„«])/u;
|
||||
|
||||
/**
|
||||
* Abbreviations whose full stop is not a sentence end.
|
||||
*
|
||||
* The boundary above demands an upper-case letter after the space, which
|
||||
* already rejects `z. B.` mid-sentence in German (`B.` continues lower-case
|
||||
* more often than not) — but not reliably: "… z. B. Blutdruck." splits after
|
||||
* `z.`, because `B` IS upper-case. The same hole exists for `d. h.`, `u. a.`,
|
||||
* `e.g.` / `i.e.` before a capitalised noun, and French `p. ex.`. A split
|
||||
* there produces a head that ends mid-abbreviation and a tail that starts
|
||||
* with a fragment, in a file nobody re-reads afterwards.
|
||||
*/
|
||||
const ABBREVIATIONS = [
|
||||
"bzw.",
|
||||
"ca.",
|
||||
"vgl.",
|
||||
"usw.",
|
||||
"e.g.",
|
||||
"i.e.",
|
||||
"cf.",
|
||||
"etc.",
|
||||
"ej.",
|
||||
"es.",
|
||||
"np.",
|
||||
"tzn.",
|
||||
"ecc.",
|
||||
"ex.",
|
||||
];
|
||||
|
||||
/**
|
||||
* A single letter followed by a full stop is an abbreviation part, never a
|
||||
* sentence: `z. B.`, `d. h.`, `u. a.`, `p. ex.`. Both halves have to be
|
||||
* caught, which is why this is a shape rather than a list — catching only
|
||||
* `z.` still splits after the `B.`.
|
||||
*/
|
||||
const SINGLE_LETTER = /(^|\s)\p{L}\.$/u;
|
||||
|
||||
/** Split on sentence boundaries, skipping the ones an abbreviation created. */
|
||||
function splitSentences(text) {
|
||||
const rough = text.split(BOUNDARY);
|
||||
const out = [];
|
||||
for (const part of rough) {
|
||||
const previous = out[out.length - 1];
|
||||
const lower = previous?.toLowerCase() ?? "";
|
||||
const endsInAbbreviation =
|
||||
previous !== undefined &&
|
||||
(SINGLE_LETTER.test(previous) ||
|
||||
ABBREVIATIONS.some(
|
||||
(abbr) => lower.endsWith(` ${abbr}`) || lower === abbr,
|
||||
));
|
||||
// Merge, then let the NEXT iteration re-test the merged tail: `z. B.`
|
||||
// needs two merges, and a single pass would stop after the first.
|
||||
if (endsInAbbreviation) out[out.length - 1] = `${previous} ${part}`;
|
||||
else out.push(part);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function get(bundle, path) {
|
||||
return path
|
||||
.split(".")
|
||||
@@ -56,7 +113,7 @@ for (const locale of LOCALES) {
|
||||
console.log(`${locale} ${key}: MISSING`);
|
||||
continue;
|
||||
}
|
||||
const parts = value.split(BOUNDARY);
|
||||
const parts = splitSentences(value);
|
||||
if (parts.length < 2) {
|
||||
console.log(`${locale} ${key}: single sentence, untouched`);
|
||||
continue;
|
||||
|
||||
Reference in new issue
Block a user