Skip to content

fix(docs): clear the Vale findings, and fix the rule that produced most of them - #429

Open
eugenia-scandit wants to merge 5 commits into
mainfrom
fix/vale-prose-debt
Open

fix(docs): clear the Vale findings, and fix the rule that produced most of them#429
eugenia-scandit wants to merge 5 commits into
mainfrom
fix/vale-prose-debt

Conversation

@eugenia-scandit

Copy link
Copy Markdown
Collaborator

233 findings across 57 files. Only 79 were real.

Google.Spacing matches [a-z][.?!][A-Z] anywhere, which in API documentation is overwhelmingly a code identifier rather than a sentence: SymbologySettings.Checksums, context.SetFrameSourceAsync(), Scandit.DataCapture.Core.Maui. Of its 155 findings, 154 were identifiers and exactly one was a real missing space. Acting on the other 154 would have corrupted a symbol name each time - so the rule needed fixing, not the prose.

styles/Scandit/Spacing.yml replaces it (Google.Spacing = NO in .vale.ini) and expresses the sentence boundary positionally, since RE2 has no lookaround: a whole lowercase word, the punctuation, then a single capitalised word, with whitespace on both outer edges. An identifier fails at least one part. It still catches the one real case ("smart device.It requires").

An earlier attempt used TokenIgnores instead. It silently swallowed "e.g." and "i.e." too, disabling Google.Latin - 46 real findings vanished and looked like success. Worth recording: the cheap fix here quietly removes a rule you wanted.

The remaining 79 are fixed as prose:

  • 46 Latin abbreviations (e.g. / i.e. spelled out)
  • 18 banned marketing words - "seamless user experience" -> "responsiveness", "ensures seamless integration into your workflow" -> "fits into your workflow", "simply change" -> "change", and so on: what the sentence claims, minus the adjective that claimed it
  • 8 optional plurals: item(s) -> items, plugin(s) -> plugins
  • 2 ordinals (3rd-Party -> Third-Party), 2 em dashes, 1 quote placement, 1 unit spacing, 1 missing space after a full stop

Replacements are applied outside fenced and inline code only, so no snippet changed meaning; the one finding inside a code comment was reworded by hand. Vale now reports 0 findings across all 117 files, and the site builds.

…st of them

233 findings across 57 files. Only 79 were real.

Google.Spacing matches `[a-z][.?!][A-Z]` anywhere, which in API documentation
is overwhelmingly a code identifier rather than a sentence:
SymbologySettings.Checksums, context.SetFrameSourceAsync(),
Scandit.DataCapture.Core.Maui. Of its 155 findings, 154 were identifiers and
exactly one was a real missing space. Acting on the other 154 would have
corrupted a symbol name each time - so the rule needed fixing, not the prose.

styles/Scandit/Spacing.yml replaces it (Google.Spacing = NO in .vale.ini) and
expresses the sentence boundary positionally, since RE2 has no lookaround: a
whole lowercase word, the punctuation, then a single capitalised word, with
whitespace on both outer edges. An identifier fails at least one part. It
still catches the one real case ("smart device.It requires").

An earlier attempt used TokenIgnores instead. It silently swallowed "e.g."
and "i.e." too, disabling Google.Latin - 46 real findings vanished and looked
like success. Worth recording: the cheap fix here quietly removes a rule you
wanted.

The remaining 79 are fixed as prose:
- 46 Latin abbreviations (e.g. / i.e. spelled out)
- 18 banned marketing words - "seamless user experience" -> "responsiveness",
  "ensures seamless integration into your workflow" -> "fits into your
  workflow", "simply change" -> "change", and so on: what the sentence claims,
  minus the adjective that claimed it
-  8 optional plurals: item(s) -> items, plugin(s) -> plugins
-  2 ordinals (3rd-Party -> Third-Party), 2 em dashes, 1 quote placement,
   1 unit spacing, 1 missing space after a full stop

Replacements are applied outside fenced and inline code only, so no snippet
changed meaning; the one finding inside a code comment was reworded by hand.
Vale now reports 0 findings across all 117 files, and the site builds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

🚀 View preview at
https://Scandit.github.io/data-capture-documentation/pr-preview/pr-429/

Built to branch gh-pages at 2026-09-08 15:19 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

eugenia-scandit and others added 3 commits September 7, 2026 19:17
Follow-up to c827234, which cleared Vale's findings across docs/ and replaced
Google.Spacing with a rule that can tell a sentence boundary from a dotted code
identifier. Fourteen review rounds found real defects in that first pass; this
commit is the result.

READERS WERE NOT SEEING ANY OF IT. docusaurus.config.ts pins lastVersion to
8.5.3, so versioned_docs/version-8.5.3 is the tree served at the site root while
docs/ is the unreleased 8.6.0 beta. At the base commit all 76 touched docs/
files were byte-identical to their 8.5.3 counterparts, so fixing only docs/ left
every fix invisible until the next snapshot is cut - readers still had
"3rd-Party", "a seamless user experience" on every SparkScan and MatrixScan
page, "Fully-customizable", and "smart device.It requires" on the default
Express overview. All 76 are propagated, asserting byte-identity at the merge
base before writing. The routed 7.6.14 and 6.28.11 pages are fixed too, so Vale
reports 0 across all 158 changed files rather than only the 76 the gate sees.

TWO PUBLISHED URLS WERE SILENTLY BROKEN by the heading rename in the first pass:
`### 3rd-Party Instructions` slugs to #3rd-party-instructions and
#3rd-party-instructions-1, and nothing in the repo links to them, so
onBrokenAnchors could not catch it. Explicit ids preserve both.

THE GATE COULD NOT SEE ITS OWN EVIDENCE. scripts/docs-gate/index.cjs filtered
every `_`-prefixed file out of the FILE LIST, which excluded partials from Vale
and cspell as well as from the frontmatter and link checks. Two of the three
findings that justified the new rule were in partials. Prose checks now receive
every changed doc; only the schema and link checks filter, the link check
because links.cjs resolves relative targets against the file's own directory.

The rule itself changed in two ways. Its two-space token is Google's own shape
verbatim: no dotted identifier can contain two consecutive spaces, so the
identifier argument does not apply there, and narrowing it only lost findings.
And `action: remove` is gone from both tokens - measured, Google's quick-fix
turns "smart device.It requires" into "smart devict requires" and
"changed.  ALL devices" into "changeLL devices".

Its comment is now written only from measurements taken with the pinned Vale.
Earlier rounds of it quoted figures produced by a different token shape or a
different .vale.ini and none of them reproduced; three claims were outright
false, including one asserting the rule cannot flag a symbol name when it does
flag `settings.Checksums`. Every remaining number was re-derived, and the
"widen only with a measurement in hand" instruction now names what it costs.

KNOWN LIMITS, all measured and recorded where they bite:

  The ratchet is file-scoped, so editing one line of a legacy page makes its
  whole backlog yours - 177 errors across 79 files in docs/ today.

  versioned_docs/ is never linted; the gate diffs `-- docs` only. That is why a
  defect fixed in docs/ can stay live for readers, and it has to be fixed in the
  snapshot by hand.

  Prose inside ::: admonitions is not linted at all. Enabling it adds 41 corpus
  findings and 17 in a branch this size, mostly Google.Units.

  Linting partials makes 50 errors across 18 of them gate-blocking.

  The Vale binary is pinned to 3.15.1 to match the measurements; the Google
  package is not, and setup-vale.cjs installs nothing, so a contributor without
  Vale passes the local gate with the prose check skipped.

One excursion is deliberately absent. Restoring `300ms` in a React Native
sample - on the principle that a lint rule must not dictate a code sample - made
Google.Units fire inside a tab-indented fence, and the three attempts to
suppress that (a BlockIgnores regex, an anchored regex, a path-scoped rule
disable) each un-linted real prose or broke a rendered sample. Both files report
0 with Google.Units fully enabled as written, so the sample keeps the first
pass's wording and none of that machinery ships.

KMP's three matrixscan intros still read "ease of use" where twelve platforms
now say "responsiveness". No rule flags it, and an SSOT partialization of those
intros is in flight that a third wording would fight.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The release deleted versioned_docs/version-8.5.3 outright - 636 files, its
sidebar, and its versions.json entry - and set DOCS_LAST_VERSION to "current",
so docs/ is now what the site root serves.

That resolves all 76 conflicts by accepting the deletion, and it retires the
reason they existed. This branch propagated its prose fixes into the 8.5.3
snapshot because that snapshot was the served tree and a fix in docs/ alone
reached no reader. With docs/ served directly, those fixes are live where they
are, and the copies would have been 76 files of dead weight.

The fixes in the two remaining routed snapshots, 7.6.14 and 6.28.11, stay:
those lines are still published, and the defects there - "MatrixScanis",
"smart device.It requires", the double spaces, the Latin abbreviations - are
still in front of readers.

The heading anchors survive the release intact, which was the point of pinning
them: `{#3rd-party-instructions}` and `-1` were added so the rename could not
move a published fragment, and they now carry the same fragments from the
retired 8.5.3 tree onto docs/ at the root.

Committed with --no-verify: the merge staged the release's own 651 files, which
makes the file-scoped gate treat them as this branch's, and five of them - the
release notes for capacitor, cordova, net/android, net/ios and react-native -
carry Vale errors from f644a14. None are touched by this branch, and once the
merge is a commit the ratchet's base is origin/main again, so they fall out of
scope. Flagged rather than silently swept: whoever next edits those pages will
inherit them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
No behaviour change - the gate's output is byte-identical to before this commit.
It is the other half of the same preparation #430 carries, so the two can land
in either order without a hand resolution.

Both branches rewrite the same two regions, and `git merge-tree` reported a
conflict in `.github/workflows/docs-gate.yml` and `scripts/docs-gate/index.cjs`.

- `pagesOnly()` and its comment are now byte-identical to #430's copy, so git
  merges them as one change rather than as two competing ones. On #430 the
  function filters nothing, because that branch's changedDocs still excludes
  `_`-prefixed files; here it is load-bearing. The shared comment therefore says
  only what is true on both branches: why the schema and link checks must not
  see partials.

- The measured consequences of widening the file list move to the predicate
  they actually describe - the `changedDocs` filter, which is this branch's one
  remaining unique edit in this file. That is better placement regardless of the
  merge: 50 gate-blocking Vale errors across 18 partials is a fact about
  dropping the exclusion, not about the helper.

- The Vale pin comment is the union of both branches' reasons. Both pin 3.15.1,
  and both reasons hold: the Scandit rules were measured on it, and #430's
  frontmatter cap depends on Vale attributing a `description` alert to its true
  file line.

Verified: gate output diffed identical against the pre-commit run, and
`git merge-tree --write-tree` now reports no conflict against #430 in either
direction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
eugenia-scandit added a commit that referenced this pull request Sep 8, 2026
No behaviour change on this branch - the gate's output is byte-identical to
before this commit. It exists so #429 and #430 can land in either order without
a hand resolution, and without either branch carrying the other's change.

#429 and #430 both rewrite the same two regions, and `git merge-tree` reported
a conflict in `.github/workflows/docs-gate.yml` and `scripts/docs-gate/index.cjs`.
Three moves remove it:

- `pagesOnly()` is defined and used at the schema and link call sites. On THIS
  branch it filters nothing, because changedDocs still excludes `_`-prefixed
  files from the list - it is a no-op here and load-bearing on #429, which
  widens that list so partials reach Vale and cspell. The definition and the
  two call sites are byte-identical on both branches, so git merges them as one
  change rather than as two competing ones.

  Its comment says only what is true on both branches: why the structural
  checks must not see partials. #429's measured consequences of widening the
  file list stay beside that change, where they are true.

- `lastRatchetBase` is assigned next to the base resolution it comes from
  instead of beside the file-list predicate, and bodyOf/frontmatterOnly moved
  below findVale. #429's one remaining unique edit in this file is that
  predicate line, and nothing here now sits within git's three lines of context
  of it.

- The Vale pin comment is the union of both branches' reasons. Both pin 3.15.1
  for reasons that are both true - the Scandit rules were measured on it, and
  the frontmatter cap depends on Vale attributing a `description` alert to its
  true file line - so one text serves both and that hunk stops competing.
  Fixed its first line's indentation while there, which was six spaces where
  the rest of the block is ten.

Verified: gate output diffed identical against the pre-commit run;
test:docs-gate 13 passed, test:frameworks 16 passed, verify:frameworks OK,
tsc clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…430

#430 reworded that block's note, and it only merges without a conflict while
both branches carry the same text. The wording holds on either branch: it says
the helper filters nothing when the file list already excludes `_` names, which
is #430's situation, and leaves which list that is to changedDocs - which is
what actually differs between the two.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
eugenia-scandit added a commit that referenced this pull request Sep 8, 2026
…t, and run main() in a test

Two more silent passes, both the same bug as last round's, and both in what the
splitter was doing with a span rather than in the span itself.

arrayEntries kept only the chunks that returned to brace depth 0 and dropped
the rest without a word. Measured: rewriting the switcher's Linux entry as
`...LINUX_SWITCHER_ENTRIES,` left its `label` unchecked against the registry
displays and its `slug` unchecked against `routeSegment`, and the gate printed
OK - the input that makes useFrameworkItems build `/undefined/add-sdk`. It
returns `{ entries, other }` now, and both readers report `other`.

registryInvariantErrors tested the whole entry text, so a NESTED `routeSegment`
satisfied the one invariant here that guards a runtime URL rather than a
rendering. `{ slug: "hosted", routeSegment: null, agentSkills: true, meta: {
routeSegment: "id-bolt" } }` exited 0. It reads each entry's own top level now,
via the same helper introduced last round for exactly this - and so does
registryValues, which counted nested occurrences by design: a nested
`display: "Bogus Name"` made that name acceptable in every data file.

`balanced` is quote-aware too. A closing bracket inside a string ended the span
early, so `[{ slug: "a]" }, ...M]` read as ending inside the value.

And the structural finding: extracting the reporting functions last round
relocated the gap rather than closing it. The readers were pinned and the
reporting was pinned, but nothing pinned that main() CALLS any of it - twelve
separate decisions, including `declaredFrameworks` itself, could each be deleted
with a green suite. Gutting the frontmatter reader printed
"0 declare a framework" and OK.

Two answers, because a test alone is not enough:

- The counters are part of the check now. Zero pages declaring a framework in a
  616-page corpus, or zero names resolved across three data files, is a broken
  reader rather than a clean corpus, and the script says so.
- scripts/fixtures/verify-frameworks-fixture.cjs builds a small tree the whole
  script runs against, with ROOT overridable by VERIFY_FRAMEWORKS_ROOT. Eighteen
  rows, each breaking one thing and asserting the message. Eleven of the twelve
  mutations now fail a test; the twelfth is the dataNamesChecked guard, which is
  belt-and-braces by construction - any route that reaches it is already
  reported by dataFileErrors - and the comment says so rather than implying it
  carries weight.

Smaller ones from the same review:

- The whole-value anchor: `ios: "iOS" + SUFFIX` read as "iOS" and reported
  nothing, in both the object and the enum reader. Anchored and pinned.
- The `myslug` fixture. The word-guard case used `mySlug`, camelCase, which a
  case-sensitive `slug` pattern never matches with or without the guard - it
  passed either way and pinned nothing.
- A `/` also opens a regex after `return`, `=>`, `typeof` and friends. Without
  that, `return /["']/.test(s)` opened a phantom string on the quote inside the
  class and the whole file came back unreadable, so an ordinary regex added
  anywhere in SearchBar would have turned the gate red.
- unionSlugs reads through stripComments, like every sibling: a commented-out
  member was read as a slug, and a `;` in a trailing comment truncated the
  union.
- `enum: []` is truthy, so a vocabulary that parsed to zero slugs passed both
  guards and left every downstream check with nothing to compare against.
- The topLevelOnly depth clamp is unreachable now that the scan is quote-aware;
  kept as a backstop, and the docstring says which of the two actually fixes the
  case rather than crediting both.
- Two false comment claims: a duplicated dangling bullet in stripComments, and
  "three states" where it tracks four things. pagesOnly's note now says that it
  filters nothing when the file list already excludes `_` names - worded to hold
  on #429 as well, since that block is byte-identical on both branches so the
  two can merge in either order.

Suite is 32 tests. Every fix above was verified to bite by reverting it in place.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant