dsh-modern-web-guidance 0.5.0 → 0.6.0

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/README.md CHANGED
@@ -68,8 +68,9 @@ calls. Usage counters have existed since 0.2.0, but they never leave the machine
68
68
  | Skill | `chrome-extensions` | Manifest V3 engineering and Chrome Web Store submission guidance |
69
69
 
70
70
  The scanner is report-only. `web_guidance_legacy_scan` takes `paths` (1–16 files or directories, no default target), an
71
- optional `rules` list of up to 30 rule ids, and `format: 'summary' | 'json'`; it reads at most 200 files, 512 kB per file
72
- and 8 MB per call, and returns at most 200 findings.
71
+ optional `rules` list of up to 33 rule ids, and `format: 'summary' | 'json'`; it reads at most 200 files, 512 kB per file
72
+ and 8 MB per call, and returns at most 200 findings. It carries 33 rules, 30 of them upstream's; the
73
+ [parity note](docs/notes/legacy-scan-parity.md) accounts for what is still missing and for the one real debt.
73
74
 
74
75
  The catalog covers `css`, `ui-atoms`, `ui-components`, `ui-behaviors`, `forms`, `html`, `js`, `accessibility`,
75
76
  `visual-design`, `security`, `performance`, `built-in-ai`, `pwa`, `wasm`, `webmcp`, `privacy`, and `user-experience`.
@@ -187,11 +188,11 @@ and security budget. The honest split:
187
188
  | Lookup | precomputed assets in `gen/` — quantized word vectors (`query-words.i8`, 7.47 MB) and chunk embeddings, 9.16 MB together | BM25 built in-process from `guides.json` and the guide bodies: no assets, no model download (measured 29/30 top-1 on a 30-query probe) |
188
189
  | Runtime dependencies | Node plus the CLI engines shipped in the package | none |
189
190
  | Network | `npx` fetches the package, then works locally | never — the corpus is inside the package |
190
- | Legacy scan | a 4.99 MB `legacy-scan` engine with a four-step scan → verify → report → modernize workflow | shipped in 0.5.0: a dependency-free regex scanner over 30 rules, explicit paths only, report only |
191
+ | Legacy scan | a 4.99 MB `legacy-scan` engine with a four-step scan → verify → report → modernize workflow | shipped since 0.5.0: a dependency-free regex scanner over 33 rules, 30 of them upstream's, explicit paths only, report only |
191
192
  | Experimental guides | gated inside the CLI | `[experimental]` marker in `web_guidance_search`, warning banner in `web_guidance_retrieve` |
192
193
  | Usage insight | none | local counters, `/guidance-stats`, and the sidebar panel |
193
194
  | Languages | English guides | the same guides; docs in EN/RU/ZH, panel in EN/RU/ZH |
194
- | Size | — | 256 files, 873 kB packed, 2.5 MB unpacked |
195
+ | Size | — | 256 files, 874 kB packed, 2.5 MB unpacked |
195
196
 
196
197
  What this means in practice: if you work outside DeepSeek Harness, use their package — it is the
197
198
  reference implementation and it owns the corpus. Inside Harness you want the tools in the model's tool
@@ -397,8 +398,8 @@ dialog", "view transition", "container query"), not sentences. If a query still
397
398
  is volatile in one engine — see [Experimental guides](#experimental-guides). The guide is still the
398
399
  right shape; feature-detect it before shipping.
399
400
 
400
- **What can the legacy scanner actually do?** It is a regex marker scan, not a parser: 30 rules matched
401
- line by line, covering 30 of the 208 guides (14.4 %), with no AST, no data-flow analysis and no autofix —
401
+ **What can the legacy scanner actually do?** It is a regex marker scan, not a parser: 33 rules matched
402
+ line by line, covering 33 of the 208 guides (15.9 %), with no AST, no data-flow analysis and no autofix —
402
403
  it reports and stops. It makes no parity claim with Chrome's own `legacy-scan`, a 4.99 MB engine with a
403
404
  bundled TypeScript compiler; this one is a dependency-free pass that can produce false positives and
404
405
  misses, so treat a finding as a lead to verify, not as a verdict.
@@ -422,7 +423,8 @@ work on their own; the panel additionally needs the host's `slots`, `locale` and
422
423
  | --- | --- | --- |
423
424
  | 0.4.0 | shipped | the upstream v1.0.0 corpus (208 guides) plus experimental marking |
424
425
  | 0.4.1 | shipped | docs: navigation, the comparison, the catalog map |
425
- | 0.5.0 | this release | `legacy-scan` as the fourth tool: 30 regex rules, explicit paths only, report only — `web_guidance_legacy_scan`, with a fourth counter and a findings total in the panel. Design and verdict: [docs/notes/legacy-scan-feasibility.md](docs/notes/legacy-scan-feasibility.md) |
426
+ | 0.5.0 | shipped | `legacy-scan` as the fourth tool: 30 regex rules, explicit paths only, report only — `web_guidance_legacy_scan`, with a fourth counter and a findings total in the panel. Design and verdict: [docs/notes/legacy-scan-feasibility.md](docs/notes/legacy-scan-feasibility.md) |
427
+ | 0.6.0 | this release | three more rules (`:host-context()`, text balancing, `select:invalid`) take the table to 33 rules, 30 of them upstream's, and a guide-hash guard keeps a rule honest after a corpus refresh |
426
428
 
427
429
  Deliberately **not** planned: shipping upstream's 9.16 MB embedding tables, or its 4.99 MB
428
430
  `legacy-scan` engine with a bundled TypeScript compiler. Both would break "zero runtime dependencies,
package/README.ru.md CHANGED
@@ -69,7 +69,9 @@ API платформы (нативные `<dialog>`/popover, anchor positioning,
69
69
 
70
70
  Сканер работает только на чтение. `web_guidance_legacy_scan` принимает `paths` (1–16 файлов или каталогов, цели по
71
71
  умолчанию нет), необязательный список `rules` до 30 id правил и `format: 'summary' | 'json'`; за вызов он читает не
72
- более 200 файлов, до 512 kB на файл и 8 MB суммарно, и возвращает не более 200 находок.
72
+ более 200 файлов, до 512 kB на файл и 8 MB суммарно, и возвращает не более 200 находок. В таблице 33 правила, из них
73
+ 30 — правила апстрима; чего до сих пор не хватает и в чём единственный настоящий долг — в
74
+ [заметке о соответствии](docs/notes/legacy-scan-parity.md).
73
75
 
74
76
  Каталог покрывает `css`, `ui-atoms`, `ui-components`, `ui-behaviors`, `forms`, `html`, `js`, `accessibility`,
75
77
  `visual-design`, `security`, `performance`, `built-in-ai`, `pwa`, `wasm`, `webmcp`, `privacy` и `user-experience`.
@@ -185,11 +187,11 @@ DeepSeek Harness: инструменты для модели, боковую п
185
187
  | Поиск | предвычисленные ассеты в `gen/` — квантованные векторы слов (`query-words.i8`, 7.47 MB) и эмбеддинги чанков, 9.16 MB вместе | BM25 строится в процессе из `guides.json` и тел гайдов: без ассетов, без скачивания модели (измерено 29/30 top-1 на пробе из 30 запросов) |
186
188
  | Зависимости рантайма | Node плюс движки CLI, поставляемые в пакете | нет |
187
189
  | Сеть | `npx` скачивает пакет, дальше всё локально | никогда — корпус лежит внутри пакета |
188
- | Сканер legacy | движок `legacy-scan` на 4.99 MB с четырёхшаговым процессом scan → verify → report → modernize | вышел в 0.5.0: сканер регулярными выражениями без зависимостей, 30 правил, только явные пути, только отчёт |
190
+ | Сканер legacy | движок `legacy-scan` на 4.99 MB с четырёхшаговым процессом scan → verify → report → modernize | поставляется с 0.5.0: сканер регулярными выражениями без зависимостей, 33 правила, из них 30 — правила апстрима, только явные пути, только отчёт |
189
191
  | Экспериментальные гайды | закрыты внутри CLI | маркер `[experimental]` в `web_guidance_search`, предупреждающий баннер в `web_guidance_retrieve` |
190
192
  | Анализ использования | нет | локальные счётчики, `/guidance-stats` и боковая панель |
191
193
  | Языки | гайды на английском | те же гайды; документация на EN/RU/ZH, панель на EN/RU/ZH |
192
- | Размер | — | 256 файлов, 873 kB в упаковке, 2.5 MB распакованного |
194
+ | Размер | — | 256 файлов, 874 kB в упаковке, 2.5 MB распакованного |
193
195
 
194
196
  Что это значит на практике: если вы работаете вне DeepSeek Harness, берите их пакет — это референсная реализация, и
195
197
  корпус принадлежит им. Внутри Harness нужны инструменты в списке инструментов модели, установка без сборки и без сети и
@@ -396,8 +398,8 @@ CLI через `npx`, сохраняет заметки про фреймвор
396
398
  движке — см. [Экспериментальные гайды](#experimental-guides). Гайд всё равно задаёт правильную форму; проверьте её
397
399
  фича-детектом, прежде чем отгружать.
398
400
 
399
- **Что сканер legacy вообще умеет?** Это маркерный скан регулярными выражениями, а не парсер: 30 правил проверяются
400
- построчно и покрывают 30 из 208 гайдов (14,4 %), без AST, без анализа потока данных и без автоисправлений — он
401
+ **Что сканер legacy вообще умеет?** Это маркерный скан регулярными выражениями, а не парсер: 33 правила проверяются
402
+ построчно и покрывают 33 из 208 гайдов (15,9 %), без AST, без анализа потока данных и без автоисправлений — он
401
403
  отчитывается и останавливается. Паритета с собственным `legacy-scan` Chrome он не заявляет: там движок на 4.99 MB со
402
404
  встроенным компилятором TypeScript, здесь — проход без зависимостей, который может и ошибиться, и пропустить; считайте
403
405
  находку зацепкой для проверки, а не вердиктом.
@@ -421,7 +423,8 @@ CLI через `npx`, сохраняет заметки про фреймвор
421
423
  | --- | --- | --- |
422
424
  | 0.4.0 | вышел | корпус апстрима v1.0.0 (208 гайдов) плюс пометка экспериментальных |
423
425
  | 0.4.1 | вышел | документация: навигация, сравнение, карта каталога |
424
- | 0.5.0 | этот выпуск | `legacy-scan` четвёртым инструментом: 30 правил-регулярок, только явные пути, только отчёт — `web_guidance_legacy_scan`, с четвёртым счётчиком и итогом по находкам в панели. Дизайн и вердикт: [docs/notes/legacy-scan-feasibility.md](docs/notes/legacy-scan-feasibility.md) |
426
+ | 0.5.0 | вышел | `legacy-scan` четвёртым инструментом: 30 правил-регулярок, только явные пути, только отчёт — `web_guidance_legacy_scan`, с четвёртым счётчиком и итогом по находкам в панели. Дизайн и вердикт: [docs/notes/legacy-scan-feasibility.md](docs/notes/legacy-scan-feasibility.md) |
427
+ | 0.6.0 | этот выпуск | ещё три правила (`:host-context()`, балансировка текста, `select:invalid`) — в таблице 33 правила, 30 из них апстрима, плюс страховка по хешам гайдов: после обновления корпуса правило не устареет молча |
425
428
 
426
429
  Намеренно **не** планируется: поставлять таблицы эмбеддингов апстрима на 9.16 MB или его движок `legacy-scan` на
427
430
  4.99 MB со встроенным компилятором TypeScript. И то и другое сломало бы «ноль зависимостей рантайма, никакой сети,
package/README.zh.md CHANGED
@@ -68,7 +68,7 @@
68
68
 
69
69
  扫描器只读不写。`web_guidance_legacy_scan` 接受 `paths`(1–16 个文件或目录,没有默认目标)、可选的 `rules`(最多
70
70
  30 个规则 id)和 `format: 'summary' | 'json'`;每次调用最多读 200 个文件、单文件 512 kB、合计 8 MB,最多返回
71
- 200 条发现。
71
+ 200 条发现。表里一共 33 条规则,覆盖上游 35 条中的 30 条;剩下没覆盖的规则、以及唯一真正的一笔技术债,都记在[对照说明](docs/notes/legacy-scan-parity.md)里。
72
72
 
73
73
  目录覆盖 `css`、`ui-atoms`、`ui-components`、`ui-behaviors`、`forms`、`html`、`js`、`accessibility`、
74
74
  `visual-design`、`security`、`performance`、`built-in-ai`、`pwa`、`wasm`、`webmcp`、`privacy` 和
@@ -182,11 +182,11 @@ web_guidance_search({"query": "view transition"}) → 取回之前的排序候
182
182
  | 检索方式 | `gen/` 里预计算好的资源 —— 量化词向量(`query-words.i8`,7.47 MB)与分块 embedding,合计 9.16 MB | 在进程内用 `guides.json` 和指南正文构建 BM25:没有资源文件,不下载模型(在 30 条查询的探针上测得 29/30 的 top-1) |
183
183
  | 运行时依赖 | Node 加上包里附带的 CLI 引擎 | 无 |
184
184
  | 网络 | `npx` 先取包,之后本地运行 | 从不联网 —— 语料就在包里 |
185
- | legacy-scan 扫描 | 4.99 MB 的 `legacy-scan` 引擎,带扫描 → 校验 → 报告 → 现代化四步工作流 | 0.5.0 起内置:零依赖的正则扫描器,30 条规则,只接受显式路径,只输出报告 |
185
+ | legacy-scan 扫描 | 4.99 MB 的 `legacy-scan` 引擎,带扫描 → 校验 → 报告 → 现代化四步工作流 | 0.5.0 起内置:零依赖的正则扫描器,33 条规则(其中 30 条对应上游规则),只接受显式路径,只输出报告 |
186
186
  | 实验性指南 | 在 CLI 内部做门控 | `web_guidance_search` 结果里的 `[experimental]` 标记,`web_guidance_retrieve` 开头的警告横幅 |
187
187
  | 使用情况洞察 | 无 | 本地计数、`/guidance-stats` 和侧边栏面板 |
188
188
  | 语言 | 英文指南 | 同样的指南;文档 EN/RU/ZH,面板 EN/RU/ZH |
189
- | 体积 | — | 256 个文件,打包 873 kB,解包 2.5 MB |
189
+ | 体积 | — | 256 个文件,打包 874 kB,解包 2.5 MB |
190
190
 
191
191
  实际意味着:如果你在 DeepSeek Harness 之外工作,就用他们的包 —— 它是参考实现,语料也归它所有。在 Harness
192
192
  里面,你要的是工具出现在模型的工具列表里、安装不需要构建步骤也不联网,以及一个能看出指南到底有没有人在用的
@@ -374,8 +374,8 @@ web_guidance_search({"query": "view transition"}) → 取回之前的排序候
374
374
  **为什么结果里写着 `[experimental]`?** 指南讲的平台特性挂在 Origin Trial 后面,或者只在单一引擎里实现、仍
375
375
  不稳定 —— 见[实验性指南](#experimental-guides)。指南本身仍然是正确的方向;上线前先做特性检测。
376
376
 
377
- **legacy 扫描器到底能做什么?** 它是正则标记扫描,不是解析器:30 条规则逐行匹配,覆盖 208 篇指南中的 30 篇
378
- (14.4 %),没有 AST、没有数据流分析、也不自动修复 —— 它只报告,然后停下。它不声称与 Chrome 自己的
377
+ **legacy 扫描器到底能做什么?** 它是正则标记扫描,不是解析器:33 条规则逐行匹配,覆盖 208 篇指南中的 33 篇
378
+ (15.9%),没有 AST、没有数据流分析、也不自动修复 —— 它只报告,然后停下。它不声称与 Chrome 自己的
379
379
  `legacy-scan` 等价:那边是 4.99 MB、内置 TypeScript 编译器的引擎,这里是零依赖的扫描过程,既可能误报也可能漏报,
380
380
  所以把发现当成待核实的线索,而不是结论。
381
381
 
@@ -396,7 +396,8 @@ web_guidance_search({"query": "view transition"}) → 取回之前的排序候
396
396
  | --- | --- | --- |
397
397
  | 0.4.0 | 已发布 | 上游 v1.0.0 语料(208 篇指南)加实验性标记 |
398
398
  | 0.4.1 | 已发布 | 文档:导航、对比表、目录地图 |
399
- | 0.5.0 | 本次发布 | `legacy-scan` 作为第四个工具:30 条正则规则,只接受显式路径,只输出报告 —— `web_guidance_legacy_scan`,面板里多一个计数器和一个发现总数。设计与结论:[docs/notes/legacy-scan-feasibility.md](docs/notes/legacy-scan-feasibility.md) |
399
+ | 0.5.0 | 已发布 | `legacy-scan` 作为第四个工具:30 条正则规则,只接受显式路径,只输出报告 —— `web_guidance_legacy_scan`,面板里多一个计数器和一个发现总数。设计与结论:[docs/notes/legacy-scan-feasibility.md](docs/notes/legacy-scan-feasibility.md) |
400
+ | 0.6.0 | 本次发布 | 再多三条规则(`:host-context()`、文本平衡、`select:invalid`),规则表增至 33 条,其中 30 条对应上游规则;另加指南哈希校验,语料更新后规则不会悄悄过期 |
400
401
 
401
402
  刻意**不**做的:内置上游那 9.16 MB 的 embedding 表,或者它那个附带 TypeScript 编译器的 4.99 MB `legacy-scan`
402
403
  引擎。两者都会破坏“零运行时依赖、不联网、不生成代码”,而换来的收益在词法索引上根本看不出来。
package/index.js CHANGED
@@ -51,14 +51,22 @@ const LIST_DESCRIPTION = [
51
51
  'Use it when search phrasing did not match and you want to see what the catalog covers.',
52
52
  ].join(' ')
53
53
 
54
- const SCAN_DESCRIPTION = [
55
- 'Scan the files you name for legacy web-platform patterns (jQuery-era DOM helpers, execCommand, document.write,',
56
- 'deprecated APIs) and map every finding to the Modern Web Guidance guide that replaces it. Pass 1-16 relative paths',
57
- 'inside the session workspace: directories are walked, and the scan stops at 200 files, 512 kB per file and 8 MB',
58
- 'total. This is a line-based marker scanner over 30 rules, not an AST analyzer, so treat each finding as a lead and',
59
- 'load the guide with web_guidance_retrieve before changing code. Report-only: it never writes, never spawns a',
60
- 'process and never touches the network.',
61
- ].join(' ')
54
+ /**
55
+ * The scanner's model-facing description. The rule count is interpolated rather than written
56
+ * out: the table grows, and a hand-written number would quietly start lying about its width.
57
+ * @param {number} ruleCount how many rules the table carries.
58
+ * @returns {string} the description the tool registers with.
59
+ */
60
+ function scanDescription(ruleCount) {
61
+ return [
62
+ 'Scan the files you name for legacy web-platform patterns (jQuery-era DOM helpers, execCommand, document.write,',
63
+ 'deprecated APIs) and map every finding to the Modern Web Guidance guide that replaces it. Pass 1-16 relative paths',
64
+ 'inside the session workspace: directories are walked, and the scan stops at 200 files, 512 kB per file and 8 MB',
65
+ `total. This is a line-based marker scanner over ${ruleCount} rules, not an AST analyzer, so treat each finding as a lead and`,
66
+ 'load the guide with web_guidance_retrieve before changing code. Report-only: it never writes, never spawns a',
67
+ 'process and never touches the network.',
68
+ ].join(' ')
69
+ }
62
70
 
63
71
  function textBlock(text) {
64
72
  return [{ type: 'text', text }]
@@ -146,13 +154,27 @@ function formatBytes(bytes) {
146
154
  return `${(size / (1024 * 1024)).toFixed(1)} MB`
147
155
  }
148
156
 
149
- function renderScan(_args, value) {
157
+ /**
158
+ * The id range a report quotes as "valid ids run …". Derived from the tool's own rule list so a
159
+ * new rule cannot leave a stale span in the text.
160
+ * @param {readonly string[]} ids rule ids in table order.
161
+ * @returns {string} `ls-01 … ls-33`, or an empty string when the list is empty.
162
+ */
163
+ function ruleSpan(ids) {
164
+ if (!Array.isArray(ids) || ids.length === 0) return ''
165
+ return `${ids[0]} … ${ids[ids.length - 1]}`
166
+ }
167
+
168
+ /** Fallback id list for a rendered refusal that arrived without one: the shipped table. */
169
+ const ALL_RULE_IDS = RULES.map((rule) => rule.id)
170
+
171
+ function renderScan(_args, value, ruleIds = ALL_RULE_IDS) {
150
172
  // A refusal is rendered as a result: the model needs the code and what to pass instead, not a stack.
151
173
  if (value?.error !== undefined) {
152
174
  if (value.error === 'unknown-rules') {
153
175
  const unknown = Array.isArray(value.unknown) ? value.unknown : []
154
- const ids = Array.isArray(value.rules) ? value.rules : []
155
- const span = ids.length > 0 ? `${ids[0]} … ${ids[ids.length - 1]}` : 'ls-01 … ls-30'
176
+ const ids = Array.isArray(value.rules) && value.rules.length > 0 ? value.rules : ruleIds
177
+ const span = ruleSpan(ids)
156
178
  return textBlock([
157
179
  `Error: unknown-rules — unknown rule id(s): ${unknown.join(', ')}.`,
158
180
  `Valid ids run ${span}. Omit "rules" to apply every rule.`,
@@ -316,7 +338,7 @@ function buildScanTool(scanner, ruleSet) {
316
338
  const allRuleIds = ruleSet.RULES.map((rule) => rule.id)
317
339
  return {
318
340
  name: 'web_guidance_legacy_scan',
319
- description: SCAN_DESCRIPTION,
341
+ description: scanDescription(allRuleIds.length),
320
342
  parameters: {
321
343
  type: 'object',
322
344
  properties: {
@@ -326,13 +348,13 @@ function buildScanTool(scanner, ruleSet) {
326
348
  maxItems: 16,
327
349
  items: { type: 'string', maxLength: 512 },
328
350
  },
329
- rules: { type: 'array', maxItems: 30, items: { type: 'string', maxLength: 40 } },
351
+ rules: { type: 'array', maxItems: allRuleIds.length, items: { type: 'string', maxLength: 40 } },
330
352
  format: { type: 'string', enum: ['summary', 'json'] },
331
353
  },
332
354
  required: ['paths'],
333
355
  additionalProperties: false,
334
356
  },
335
- output: { schema: { type: 'object' }, render: renderScan },
357
+ output: { schema: { type: 'object' }, render: (args, value) => renderScan(args, value, allRuleIds) },
336
358
  async execute(args, exec) {
337
359
  exec?.signal?.throwIfAborted?.()
338
360
  const cwd = exec?.agent?.session?.header?.cwd ?? process.cwd()
package/lib/scan-rules.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The legacy-scan rule table for `web_guidance_legacy_scan`.
3
3
  *
4
- * Thirty hand-written markers, each pointing at the guide that explains the modern
4
+ * Thirty-three hand-written markers, each pointing at the guide that explains the modern
5
5
  * replacement. This is deliberately not a port of upstream's analyzer: upstream detects
6
6
  * these patterns with the TypeScript compiler, so its rules cannot be reused as data —
7
7
  * see `docs/notes/legacy-scan-feasibility.md` for the measurement behind that claim.
@@ -16,7 +16,7 @@
16
16
 
17
17
  /**
18
18
  * @typedef {object} ScanRule
19
- * @property {string} id stable rule id, `ls-01` … `ls-30`
19
+ * @property {string} id stable rule id, `ls-01` … `ls-33`
20
20
  * @property {string | null} guideId guide in `data/guides.json`, or null when no guide owns the marker
21
21
  * @property {'high' | 'medium' | 'low'} confidence `high` when the marker alone is conclusive
22
22
  * @property {string} message one imperative sentence naming the modern replacement
@@ -27,7 +27,7 @@
27
27
  export const CONFIDENCE_ORDER = Object.freeze(['high', 'medium', 'low'])
28
28
 
29
29
  /**
30
- * Ordered rule table. Ids run `ls-01` … `ls-30`; CSS markers come first, then HTML/DOM
30
+ * Ordered rule table. Ids run `ls-01` … `ls-33`; CSS markers come first, then HTML/DOM
31
31
  * structure, then the JavaScript library and API markers, so a report reads top-down by
32
32
  * layer. Every non-null `guideId` is a real catalog entry — `test/scan-rules.test.mjs`
33
33
  * fails if a rename ever breaks that.
@@ -89,7 +89,7 @@ export const RULES = Object.freeze([
89
89
  guideId: 'validate-input-after-interaction',
90
90
  confidence: 'medium',
91
91
  message: 'Use :user-invalid so error styling appears only after interaction instead of styling :invalid (including :not(:focus):invalid workarounds).',
92
- source: ':invalid\\b',
92
+ source: '(?:input|textarea)\\b[^;{},]*?:invalid\\b',
93
93
  },
94
94
  {
95
95
  id: 'ls-09',
@@ -186,8 +186,8 @@ export const RULES = Object.freeze([
186
186
  id: 'ls-22',
187
187
  guideId: 'equal-width-text-lines',
188
188
  confidence: 'high',
189
- message: 'Use text-wrap: balance or text-wrap: pretty instead of fitty, fitText, balanceText or Balancer.',
190
- source: '\\b(?:fitty|fitText|balanceText|Balancer)\\s*\\(',
189
+ message: 'Use text-wrap: balance or text-wrap: pretty instead of fitty or fitText measuring text in JavaScript.',
190
+ source: '\\b(?:fitty|fitText)\\s*\\(',
191
191
  },
192
192
  {
193
193
  id: 'ls-23',
@@ -245,6 +245,27 @@ export const RULES = Object.freeze([
245
245
  message: 'Define custom elements with customElements.define() instead of the removed document.registerElement().',
246
246
  source: 'document\\.registerElement\\s*\\(',
247
247
  },
248
+ {
249
+ id: 'ls-31',
250
+ guideId: 'styling-web-components',
251
+ confidence: 'high',
252
+ message: 'Use inherited CSS custom properties or container style queries instead of the non-standard :host-context(), which Firefox and Safari do not implement.',
253
+ source: ':host-context\\s*\\(',
254
+ },
255
+ {
256
+ id: 'ls-32',
257
+ guideId: 'improve-text-layout-and-legibility',
258
+ confidence: 'high',
259
+ message: 'Use CSS text-wrap: balance or text-wrap: pretty instead of JavaScript line balancing with balanceText() or <Balancer>.',
260
+ source: '\\bbalanceText\\s*\\(|<Balancer[\\s>/]',
261
+ },
262
+ {
263
+ id: 'ls-33',
264
+ guideId: 'select-menu-interaction',
265
+ confidence: 'high',
266
+ message: 'Use select:user-invalid so a required dropdown only shows error styling after the user interacts with it.',
267
+ source: 'select\\b[^;{},]*?:invalid\\b',
268
+ },
248
269
  ])
249
270
 
250
271
  /** Id → rule lookup, so `ruleById` and `selectRules` stay linear. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-modern-web-guidance",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Modern web platform guidance for DeepSeek Harness agents: Google Chrome's 208-guide catalog with offline search and retrieve tools, a report-only legacy-pattern scanner, plus a Chrome extensions (MV3) skill.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",