Skip to content

Fix attribute bugs found with htmx, and name classes by their styles - #2

Merged
iko-soy merged 2 commits into
masterfrom
claude/project-thread-l86ywd
Sep 27, 2026
Merged

iko-soy merged 2 commits into
masterfrom
claude/project-thread-l86ywd

Conversation

@iko-soy

@iko-soy iko-soy commented Sep 27, 2026

Copy link
Copy Markdown
Owner

Before: htmx attributes compiled, but several mistakes passed silently or with only a vague warning. @el [$live, hx-trigger=click] wrote hx-trigger twice, so the browser kept the bundle's. hx-trigger=load, every 2s turned every 2s into CSS. hx-get /items became CSS. @link [hx-get=/panel] Open panel got href="Open". A bare hx-preserve was an error. A ] in alert(']') ended the list. Classes were numbered per file (hl-a, hl-b, ...), so a fragment swapped into a page restyled the page's elements. htmlang serve answered a POST with the GET body and added its reload script to fragments.

After: the later attribute wins and is written once. hx- names are HTML attributes like aria- and data- ones, and a bare hx-/data- word is a flag. A comma that cuts a value short is reported with the value written with \, (split-value). A ] inside '...' is reported (unbalanced-quote). Link text taken as the URL gets a warning (text-as-url). A class is named by a hash of its style (hl-2k9xq0m), so one style has one name in every file, and htmlang build reports two files whose different styles got one name (class-collision). The dev server answers GET and HEAD, gives 405 to other methods, and leaves fragments alone.

This adds no new syntax. It is the "fix either way" part of the htmx investigation, and it helps anyone who swaps htmlang output into a page by any means.

How: vocab::is_html_attribute and vocab::is_html_flag replace the scattered aria-/data- checks. Codegen writes each HTML attribute name once, keeping the last. The attribute check now knows which attribute came before, so it can report a split value. AttrList::quoted_close marks the bracket case. StyleCollector names a class with stable_hash (FNV-1a plus SplitMix64's finalizer) of the style's text. It takes more digits when a name is already taken in the file, and generate_with_classes hands the names to build. The integration tests read the output with classes numbered in order of appearance (tests/common), so the 118 snapshots are unchanged. Checked in Chromium: a fragment swapped into a page restyled 3 page elements before and 0 after, and it now looks the same as it does on its own.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Hca8AvqJAmkTymf66PBCGk


Generated by Claude Code

- An attribute given twice (by a bundle and after it, by a function and
  its call) is written once, with the later value. The browser keeps the
  first of two, so the bundle's used to win.
- `hx-` names are HTML attributes, like `aria-` and `data-` ones:
  `hx-get /items` is an error that shows `hx-get=/items`, and a bare
  `hx-` or `data-` word is a flag (`hx-preserve`, `data-open`).
- Right after a `key=value`, a name htmlang doesn't know is reported as
  the rest of that value, which a comma ended (`split-value`), with the
  value written with `\,`.
- A `]` that ends a list inside a `'...'` in a value (`alert(']')`) is
  reported (`unbalanced-quote`).
- A leading argument that reads as text (`@link [hx-get=/x] Open panel`)
  is taken as the URL with a warning (`text-as-url`).
- `htmlang serve` answers GET and HEAD, gives 405 to other methods, and
  adds its reload script only to pages, not to fragments.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hca8AvqJAmkTymf66PBCGk
A class was named by its place in the file (`hl-a`, `hl-b`, ...), so a
fragment's `hl-a` and a page's `hl-a` were two different styles, and a
fragment swapped into a page (htmx, or any server-rendered partial)
restyled the page's elements and lost its own layout.

- A class is `hl-` + 7 base-36 digits of a stable hash of its style
  (FNV-1a with SplitMix64's finalizer), so one style has one name in
  every file. Within a file, a style whose name is taken takes more
  digits.
- A function's scoped `@style` is `hl-fn-NAME-HASH`, the hash of the
  stylesheet, so two functions of one name in two files don't meet.
- `htmlang build` reports two files whose different styles got one name
  (`class-collision`).
- The integration tests read the output with its classes numbered in
  the order they appear (tests/common), so the snapshots are unchanged
  and change only when styles do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hca8AvqJAmkTymf66PBCGk
@iko-soy
iko-soy marked this pull request as ready for review September 27, 2026 13:41
@iko-soy
iko-soy merged commit 5ea0c40 into master Sep 27, 2026
4 checks passed
@iko-soy
iko-soy deleted the claude/project-thread-l86ywd branch September 27, 2026 15:28
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.

2 participants