on_the_money 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.7.2] — 2026-07-20
8
+
9
+ ### Changed
10
+
11
+ - **HTML-106 promoted from warn to error tier.** The production trial closed with zero false positives across both consumers and one true positive (a `the()` write projecting into a slot that doesn't exist — dead state the deletion test exists to catch). Runs that previously reported-and-passed now fail on unconsumed global keys; consume the key or delete the write. The warn-tier machinery remains for future rules. (#136)
12
+ - **HTML-107 pairs by CSS reference, not name coupling** (landed post-0.7.1 as #144): the state key revealing a span may be named anything — a reveal rule necessarily references the span it reveals, so the reference is the proof. Kills the false-positive class that condemned functional cross-named wiring. (#143)
13
+
7
14
  ## [0.7.1] — 2026-07-19
8
15
 
9
16
  Port feedback, same day: the first downstream migrations onto the 0.7.0 batteries surfaced three regressions. Issues #132–#135, #137; PRs #138–#142.
package/README.md CHANGED
@@ -763,8 +763,8 @@ npx otm-lint --check ./src
763
763
  | **HTML-103** | `data-i18n-{var}` attr has no matching `{var}` placeholder in the dictionary template | The token is silently dropped at runtime. Either remove the unused attr or add `{var}` to the template. |
764
764
  | **HTML-104** | A global `the("key", ...)` write collides with a `data-text="key"` slot inside a `<template>` | Global writes walk the whole document and clobber every cloned instance. Rename the template slot key or the global key. |
765
765
  | **HTML-105** | `data-action` values and `on()` `[data-action="..."]` selector literals disagree | Both directions: a `data-action` no handler references is a dead control; a selector no element carries is an orphan handler. A generic `[data-action]` dispatch selector in JS waives the dead-control direction; `data-otm-dynamic` opts an element out. |
766
- | **HTML-106** *(warn)* | A globally written state key nothing consumes | The deletion test as lint: no CSS attribute selector, `[data-text]` slot, `"data-key"` JS string literal (e.g. MutationObserver `attributeFilter`), or `the("key")` read touches it — dead state or a missing CSS rule. Warn-level: reported, never fails the run. |
767
- | **HTML-107** | A `data-K-key="V"` reveal span with no `[data-K="V"]` state-CSS rule, or a CSS `[data-K-key="V"]` reference with no span | Either half missing means the message silently never shows (or the rule is dead wiring). Add the missing half or delete the orphan; `data-otm-dynamic` opts a span out of the span→CSS direction. |
766
+ | **HTML-106** | A globally written state key nothing consumes | The deletion test as lint: no CSS attribute selector, `[data-text]` slot, `"data-key"` JS string literal (e.g. MutationObserver `attributeFilter`), or `the("key")` read touches it — dead state or a missing CSS rule. Promoted from warn to error after a zero-false-positive production trial. |
767
+ | **HTML-107** | A `data-K-key="V"` reveal span no state-CSS rule references, or a CSS `[data-K-key="V"]` reference no element carries | Pairing is by CSS reference the state key revealing a span may be named anything (`body[data-form-error="V"] [data-error-key="V"]` is fine). Either half missing means the message silently never shows (or the rule is dead wiring); `data-otm-dynamic` opts a span out of the span→CSS direction. |
768
768
 
769
769
  `otm-lint` walks `.html`, `.ejs` (cross-file rules only — per-file rules stay `.html`), `.js`, `.css`, and locale `.json` files. HTML files get the per-file rules above (template content included); `.js` files contribute `$.clone` references, `the()` write/read keys, and `data-action` selectors; `.css` files contribute attribute-selector consumption for HTML-106; locale dicts get loaded per HTML file's `<meta name="i18n">` for HTML-102/103. Default excludes: `node_modules`, `dist`, `.git`, dotdirs. Exit code is nonzero when error-level violations are found.
770
770
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "on_the_money",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Opinionated, attribute-driven, standards-oriented modern framework. Native browser APIs only. See README.md for the authoring context.",
5
5
  "type": "module",
6
6
  "main": "src/core/index.js",
@@ -371,7 +371,6 @@ export default class Linter {
371
371
  { line: writer.line, column: 1 },
372
372
  "HTML-106",
373
373
  `State key "${key}" is written via the() but never consumed by a CSS attribute selector, a [data-text] slot, or a JS data-attribute reference — dead state or a missing CSS rule (the deletion test).`,
374
- "warn",
375
374
  );
376
375
  }
377
376
 
@@ -405,13 +404,18 @@ export default class Linter {
405
404
  }
406
405
 
407
406
  // Inline <style> blocks ride along in the raw HTML sources.
407
+ // Pairing is by CSS *reference*, never by name coupling: the state key
408
+ // that reveals a span may be named anything (body[data-form-error="V"]
409
+ // revealing [data-error-key="V"] is fully functional) — a reveal rule
410
+ // necessarily references the span it reveals, so the ref IS the proof.
408
411
  const styleTexts = [...cssSources, ...htmlSources];
409
- const stateRules = new Set();
410
412
  const spanRefs = [];
413
+ const refPairs = new Set();
411
414
  for (const { file, source } of styleTexts) {
412
415
  for (const m of source.matchAll(
413
416
  /\[data-([a-z0-9-]+?)-key=["']?([\w-]+)["']?\]/g,
414
417
  )) {
418
+ refPairs.add(`${m[1]} ${m[2]}`);
415
419
  spanRefs.push({
416
420
  group: m[1],
417
421
  value: m[2],
@@ -419,21 +423,16 @@ export default class Linter {
419
423
  line: Linter.#lineOf(source, m.index),
420
424
  });
421
425
  }
422
- for (const m of source.matchAll(
423
- /\[data-([a-z0-9-]+)=["']?([\w-]+)["']?\]/g,
424
- )) {
425
- if (!m[1].endsWith("-key")) stateRules.add(`${m[1]} ${m[2]}`);
426
- }
427
426
  }
428
427
 
429
428
  for (const span of revealSpans) {
430
- if (stateRules.has(`${span.group} ${span.value}`)) continue;
429
+ if (refPairs.has(`${span.group} ${span.value}`)) continue;
431
430
  Linter.#addViolation(
432
431
  violations,
433
432
  span.file,
434
433
  { line: span.line, column: span.column },
435
434
  "HTML-107",
436
- `Reveal span data-${span.group}-key="${span.value}" has no state-CSS rule matching [data-${span.group}="${span.value}"] — the message can never show. Add the reveal rule, or delete the span, or mark it data-otm-dynamic.`,
435
+ `Reveal span data-${span.group}-key="${span.value}" is referenced by no state-CSS rule the message can never show. Add a reveal rule selecting [data-${span.group}-key="${span.value}"], or delete the span, or mark it data-otm-dynamic.`,
437
436
  );
438
437
  }
439
438