npm.io
0.18.15 • Published yesterdayCLI

@domphy/doctor

Licence
MIT
Version
0.18.15
Deps
1
Size
23.5 MB
Vulns
0
Weekly
0
Stars
2

@domphy/doctor

domphy.com · Docs · npm

A static analyzer for Domphy element trees. It walks the plain-object tree and flags non-idiomatic patterns, giving humans — and especially AI agents — a feedback loop to self-correct generated code.

Because Domphy UIs are plain objects, the doctor can inspect them directly (no parser, no build step), including the output of reactive (listener) => … functions.

Install

npm install @domphy/doctor @domphy/core

@domphy/core is a peer dependency (the doctor reads its tag tables).

Usage

import { diagnose, format } from "@domphy/doctor"

const App = {
  div: [
    { p: "Hello", style: { fontSize: "20px" } },   // inline typography
    { input: "oops" },                              // void tag with content
    { dvi: "typo" },                                // unknown tag
  ],
}

const issues = diagnose(App)
console.log(format(issues))
// ⚠ [inline-typography] div > p
//   Inline `fontSize` — avoid inline typography styles.
//   → Use a typography patch (paragraph()/heading()/…) via $.
// ✗ [void-content] div > input
//   Void tag "input" must have null content (got string).
// ⚠ [unknown-tag] div
//   "dvi" is not a known HTML/SVG tag — likely a typo.

diagnose(element, options?) returns Diagnostic[]:

interface Diagnostic {
  rule: string          // one of the 18 rule ids below, e.g. "inline-typography"
  severity: "error" | "warning" | "info"
  category?: string     // "structure" | "key" | "theme" | "typography" | "data-attr" | "visual"
  path: string          // "div > ul > li"
  message: string
  hint?: string
}

Rules

The doctor implements 18 rules:

Rule Severity Catches
missing-key warning a dynamic list (from a reactive function) of element children missing _key
unstable-key warning a dynamic list whose _keys are the array index (0, 1, 2, …) — unstable across reorders
duplicate-key error two sibling elements sharing the same _key value
unknown-tag warning an element whose first key isn't a valid HTML/SVG tag (typo)
void-content error a void tag (input, img, br, …) with non-null content
inline-typography warning fontSize/lineHeight/fontWeight/letterSpacing/fontFamily/textDecoration literals in style — use a typography patch
raw-theme-value info a literal color (#hex, rgb()/rgba(), CSS named colors like "red") in a color style prop — use themeColor()
raw-spacing-value info a literal rem/em/px spacing value in a spacing style prop — use themeSpacing()
low-opacity warning/info style.opacity < 0.6 on a control; info if hover-restore pattern (&:hover: { opacity: '1' }) is detected
tone-background-inherit warning style.backgroundColor resolves to a fixed shifted tone var instead of the inherit tone — use dataTone to shift the surface, not backgroundColor
missing-color warning element uses themeColor() for at least one styled prop but has no style.color — text color won't re-evaluate when the tone context shifts
low-contrast warning style.color and style.backgroundColor are both reactive theme vars but their shift-step gap is < 9 (insufficient contrast)
dataTone-surface-contract warning element sets dataTone but is missing backgroundColor and/or color — a tone context surface must declare both so children can guarantee readable contrast
color-shift-minimum warning element with dataTone sets style.color to a tone step < 9 — below the minimum for legible body text
unknown-tone warning a dataTone that isn't valid tone grammar, or whose offset is out of the 18-step ramp (0–17). Valid grammar includes the semantic aliases surface/hover/border/border-strong/muted/text
middle-surface-anchor warning a dataTone of shift-4shift-13 (mid-ramp surface anchor) that may collapse child contrast
unknown-density warning/error a dataDensity that isn't valid grammar, or whose offset is out of the 5-step range (0–4)
unknown-size warning/error a dataSize that isn't valid grammar, or whose offset is out of the 8-step range (0–7)

By default the doctor invokes reactive content functions with a no-op listener to inspect their output (this is how missing-key is detected). Pass { runReactive: false } if your reactive functions have side effects.

CLI

@domphy/doctor ships a domphy-doctor binary that scans .ts/.tsx/.js/.mjs files or directories, extracts every exported Domphy element, and runs diagnose() on each:

npx domphy-doctor src/

Flags: --only <rules>, --exclude <rules>, --no-reactive, --no-output (skip Layer 4, see below), --format text|json. Exit code 1 when any error-severity diagnostic is found, 2 on a CLI/usage error. See the configuration docs for the full flag reference.

Layer 4: HTML/CSS output linting

auditOutput(node, options?) is an optional fourth layer: it builds the real HTML/CSS a Domphy ElementNode would render and runs it through htmlhint (structural/a11y) and stylelint (CSS quality). Both linters are optional peer deps — npm install --save-dev htmlhint stylelint to enable them; auditOutput() silently skips a linter that isn't installed. The domphy-doctor CLI calls this automatically (disable with --no-output).

import { ElementNode } from "@domphy/core"
import { auditOutput, type Layer4Options } from "@domphy/doctor"

const diags = await auditOutput(new ElementNode(MyApp), { path: "MyApp" })

For AI agents

Run diagnose() on generated Domphy code and feed format() back to the model — it will fix the issues itself. This is the self-correction loop that lets agents write correct Domphy despite having little training data for it. See the repo AGENTS.md and llms.txt for the rules the doctor enforces.

Keywords