Skip to content

Find end tags in place instead of in a lowercased copy - #151

Merged
collin-thomas merged 1 commit into
mainfrom
fix-lowercase-offsets
Nov 12, 2026
Merged

collin-thomas merged 1 commit into
mainfrom
fix-lowercase-offsets

Conversation

@collin-thomas

Copy link
Copy Markdown
Collaborator

For the 1.0.0 release. Found while fixing the flaky language-server linear-time test, which had the same pattern in its block scanner (that fix is on its own branch, pre-release-20261112).

The bug

Five places lowercased a whole page or text, searched the lowercased copy, and then used the positions they found on the original. That only works while both strings are the same length, and Unicode lowercasing can make a string longer: "İ".toLowerCase() is two characters. After every İ, the positions point one character further past what they name.

Where Effect
pkg/src/lib/preserved-targets.ts and its identical copy in lsp/src/ The end of a <title>, <style>, <script> or <textarea> lands too far, so classes and ids on the tags right after it can be skipped. This feeds the compiler's scoping split between preserved and scoped classes, and the editor's view of it. Twelve İ in a component's <title> were enough to drop the <p class="kept"> after it.
pkg/src/lib/generated-id-classes.ts An #id collision found in a <style> element is reported at the wrong line and column.
docs/src/components/docs-search/search-logic.ts The search result snippet can slide past the match.
skills/bascik/scripts/check-links.mjs, skills/bascik-seo/scripts/audit-seo.mjs The end of a script, style or other masked element lands too far, skipping the links, headings and images after it.

İ (U+0130) is the only character whose lowercase is longer. Nothing in the repo uppercases whole documents, where many more characters change length (ß becomes SS).

The fix

Tag names are compared in place, in ASCII case, which is how an HTML parser compares them, and no copy of the page is made. The two preserved-targets.ts copies stay byte-identical (a test checks that).

The docs search still matches what a reader types without regard to case, Unicode included, and maps the match position back to the original text. It only does the extra work when lowercasing changed the length.

Not changed

  • component-props.ts lowercases a template only for a yes/no includes pre-check and never uses positions from it, so it isn't affected.
  • .agents/skills/ holds installed, hash-locked copies of the skills scripts. They pick up this fix with the next skills refresh.

Tests

Each fix has a test with İ before the affected tag or text. I confirmed all five fail without the fix. The preserved-targets test also covers mixed-case raw-text tags (<TITLE>…</Title >).

Verification

  • typecheck (all workspaces), format, spelling: clean
  • pkg unit 8,779; language-server unit 2,698; docs unit 1,300; skills unit 982: pass
  • pkg integration 809; pkg linear-time 475; language-server linear-time 94: pass

🤖 Generated with Claude Code

Five places lowercased a whole page or text, searched the copy, and used
the offsets they found on the original. Unicode lowercasing can lengthen a
string ("İ" becomes two characters), so after such a character every
offset pointed past the text it named:

- preserved-targets (compiler and language server): the end of a <title>,
  <style>, <script> or <textarea> landed past the next tag, so its classes
  and ids were left out of the preserved/scoped split.
- generated-id-classes: an #id collision in a <style> was reported at the
  wrong line and column.
- docs search: the result snippet could slide past the match.
- the skills' link checker and SEO audit: the end of a masked element
  landed past the next tag, which skipped its links, headings and images.

Tag names are now compared in place, in ASCII case, as an HTML parser
compares them. The docs search keeps Unicode-aware matching for what a
reader types and maps the match back to the original text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Shop preview: https://bascik-shop-pr-151.collin-inbox.workers.dev

Stripe sandbox payments are enabled. Paid orders create unconfirmed Printful drafts only. Production confirmation and customer email are blocked in test mode. No live domain changes.

@github-actions

Copy link
Copy Markdown

Site Previews

Built from c1713a2 on fix-lowercase-offsets. Preview URLs stay the same for this pull request and show its newest successful build. Their hostname prefix is pr-<number> (or the branch name when there is no pull request), not the hosting provider. None of them is production.

Site Hosting resource Links
Bascik docs (Cloudflare branch preview) bascik / · /components · /deployment/cloudflare
Cloudflare adapter (branch preview) cloudflare-adapter / · /catalog · /catalog-stream
Netlify adapter (branch preview) Netlify: bascik-netlify-adapter / · /catalog · /catalog-stream

Deploy run

@collin-thomas
collin-thomas merged commit a5e0213 into main Nov 12, 2026
84 checks passed
@collin-thomas
collin-thomas deleted the fix-lowercase-offsets branch November 12, 2026 16:30

This branch was successfully deployed

1 active deployment
shop-preview — c1713a28 Deployed Nov 12, 2026 by collin-thomas via deploy-preview #165
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