npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@uimatch/selector-anchors

v0.1.1

Published

Selector resolution plugin for uiMatch using AST-based anchors

Readme

@uimatch/selector-anchors

Selector resolution plugin for uiMatch using AST-based anchors.

Installation

Install both @uimatch/selector-anchors (this package) and @uimatch/selector-spi:

npm install @uimatch/selector-anchors @uimatch/selector-spi
# or
pnpm add @uimatch/selector-anchors @uimatch/selector-spi
# or
bun add @uimatch/selector-anchors @uimatch/selector-spi

Requirements:

  • Node.js: >=20.19 or >=22.12
  • Bun: Compatible with Bun 1.x (all scripts use Bun)
  • TypeScript: This package includes TypeScript as a runtime dependency for AST-based selector resolution
    • Why runtime dependency? TypeScript is required at runtime for AST parsing and analysis, not just for type checking
  • Module System: ESM only (CommonJS is not supported)
    • Dynamic import is supported: import('@uimatch/selector-anchors')
    • require() will not work

Overview

This package provides intelligent selector resolution by analyzing source code (TypeScript/JSX/HTML) and maintaining anchor points that survive code refactoring and line number changes.

System Flow

flowchart LR
  CLI["uimatch compare (plugin invocation)"] --> PL["@uimatch/selector-anchors (SPI)"]
  PL --> A0["Load anchors.json"]
  A0 --> M1["selectBestAnchor(initialSelector)"]
  M1 --> Q1{"snippetHash exists?"}
  Q1 -- Yes --> S1["findSnippetMatch(±radius, fuzzy)"]
  Q1 -- No --> AST
  S1 --> AST["AST/HTML resolution (tiered fallback)"]
  AST --> C1["Candidate selectors"]
  C1 --> L1["Liveness check via probe.check()"]
  L1 --> SC["calculateStabilityScore()"]
  SC --> P1["Select best selector"]
  P1 --> W1{"writeBack enabled?"}
  W1 -- Yes --> U1["Update anchors.json resolvedCss/lastSeen/lastKnown"]
  P1 --> R1["Resolution { selector, score, reasons }"]
  R1 --> CORE["To @uimatch/core compareImages()"]

Features

  • AST-based Resolution: Extract semantic selectors from TypeScript/JSX and HTML
  • Snippet Hash Matching: Detect code movement using fuzzy matching
  • Liveness Checking: Verify selectors work in the browser
  • Stability Scoring: Calculate selector quality (0-100)
  • SPI Compliance: Pluggable architecture via SPI interface

Health Check

The plugin provides a healthCheck() method to verify runtime dependencies:

  • TypeScript compilation is required (returns healthy=false if tsc fails)
  • parse5 (HTML parsing) is optional by default (warnings are logged but healthy=true)
  • When UIMATCH_HEALTHCHECK_STRICT_HTML=true is set, parse5 becomes required and healthy=false will be returned if unavailable

This allows TypeScript-only projects to use the plugin without HTML parsing capabilities.

Usage

CLI Tool

The package provides a command-line tool for adding anchors to anchors.json:

# Add a new anchor
npx uimatch-anchors --file src/components/Button.tsx --line 10 --column 2 --id button-root

# Overwrite existing anchor
npx uimatch-anchors --file src/components/Button.tsx --line 10 --column 2 --id button-root --force

# Custom output file
npx uimatch-anchors --file src/components/Button.tsx --line 10 --column 2 --id button-root --output custom.json

# Show help
npx uimatch-anchors --help

Note: The CLI automatically generates snippet hashes from the specified source code location. Once anchors are created, you should commit anchors.json to your repository for team collaboration.

As a Plugin (Phase 3+)

uimatch compare \
  --selectors anchors.json \
  --selectors-plugin @uimatch/selector-anchors

Direct Usage

import plugin from '@uimatch/selector-anchors';

const resolution = await plugin.resolve({
  url: 'http://localhost:3000',
  initialSelector: '.my-button',
  anchorsPath: './anchors.json',
  probe: myProbeImplementation,
});

console.log(resolution.selector); // Best selector found
console.log(resolution.stabilityScore); // Quality score 0-100
console.log(resolution.reasons); // Selection reasoning

How It Works

  1. Load anchors JSON (source locations + hints)
  2. Match code snippets (fuzzy match if code moved)
  3. Extract selectors from AST/HTML
  4. Verify liveness via Probe interface
  5. Score stability and return best match

Fuzzy Matching: When original line number no longer matches, searches nearby lines in the same file using partial match scoring (80% token match + 20% char match). Default threshold: 0.6 (configurable via UIMATCH_SNIPPET_FUZZY_THRESHOLD). Exact match only (without original snippet).

Stability Scoring

Stability scores (0-100) are calculated using weighted components:

graph LR
  HQ["Hint quality<br/>(testid/role/text/css)"] --> SUM[Weighted Sum]
  SM[Snippet match] --> SUM
  LV["Liveness<br/>(probe.check)"] --> SUM
  SP["Specificity<br/>(data-testid > role > id > text)"] --> SUM
  SUM --> SCORE["Stability (0-100)"]

Default Weights:

  • Hint Quality (0.4): Strategy preference quality (testid=1.0, role=0.8, text=0.5, css=0.3)
  • Snippet Match (0.2): Whether code snippet hash matched (1.0=matched, 0.0=not matched)
  • Liveness (0.3): Browser validation result (1.0=alive, 0.5=not checked, 0.0=dead)
  • Specificity (0.1): Selector specificity (data-testid=1.0, role[name]=0.9, id=0.6, text[5-24]=0.6, ...)

Custom Weights:

You can customize weights via the options parameter:

const resolution = await plugin.resolve({
  // ... other options
  stabilityScoreOptions: {
    weights: {
      hintQuality: 0.3,
      snippetMatch: 0.3,
      liveness: 0.3,
      specificity: 0.1,
    },
  },
});

Environment Variable Configuration:

For production tuning, weights can be adjusted via environment variables:

export UIMATCH_STABILITY_HINT_WEIGHT=0.3
export UIMATCH_STABILITY_SNIPPET_WEIGHT=0.3
export UIMATCH_STABILITY_LIVENESS_WEIGHT=0.3
export UIMATCH_STABILITY_SPECIFICITY_WEIGHT=0.1

These variables are checked at runtime and override default values, enabling post-deployment tuning without code changes.

Weight Normalization:

The plugin automatically normalizes weights to ensure they sum to 1.0. You can specify any positive numbers, and they will be proportionally adjusted:

# These weights (sum=10) will be normalized to (0.4, 0.2, 0.3, 0.1)
export UIMATCH_STABILITY_HINT_WEIGHT=4
export UIMATCH_STABILITY_SNIPPET_WEIGHT=2
export UIMATCH_STABILITY_LIVENESS_WEIGHT=3
export UIMATCH_STABILITY_SPECIFICITY_WEIGHT=1

Priority: Programmatic options (via stabilityScoreOptions) > Environment variables > Default values

Configuration

Timeout Settings

The plugin uses configurable timeouts for various operations. You can adjust these via environment variables:

AST Parsing Timeouts:

The AST resolution uses a tiered fallback strategy with three timeout levels:

flowchart TB
  START[resolveFromTypeScript] --> FAST["Fast path (tag / data-testid / id)\n< 300ms"]
  FAST -- "Candidates found" --> DONE[return selectors]
  FAST -- "Empty/timeout" --> ATTR["Attr-only (all attrs, no text)\n< 600ms"]
  ATTR -- "Candidates found" --> DONE
  ATTR -- "Empty/timeout" --> FULL["Full parse (including text)\n< 900ms"]
  FULL -- "Candidates found" --> DONE
  FULL -- "Empty/timeout" --> H["Heuristics (regex-based)\n~ 50ms"]
  H --> DONE
# Fast path timeout (tag + data-testid/id only) - default: 300ms
export UIMATCH_AST_FAST_PATH_TIMEOUT_MS=300

# Attribute-only parsing timeout (all attributes, no text) - default: 600ms
export UIMATCH_AST_ATTR_TIMEOUT_MS=600

# Full parse timeout (everything including text) - default: 900ms
export UIMATCH_AST_FULL_TIMEOUT_MS=900

Other Timeouts:

# Liveness probe timeout - default: 600ms
export UIMATCH_PROBE_TIMEOUT_MS=600

# HTML parsing timeout - default: 300ms
export UIMATCH_HTML_PARSE_TIMEOUT_MS=300

# Snippet hash matching timeout - default: 50ms
export UIMATCH_SNIPPET_MATCH_TIMEOUT_MS=50

Snippet Matching Configuration:

# Maximum search radius for snippet matching (lines) - default: 400
export UIMATCH_SNIPPET_MAX_RADIUS=400

# High confidence threshold for early exit (0.0-1.0) - default: 0.92
export UIMATCH_SNIPPET_HIGH_CONFIDENCE=0.92

# Fuzzy matching threshold (0.0-1.0) - default: 0.6
export UIMATCH_SNIPPET_FUZZY_THRESHOLD=0.6

Debug Logging:

Enable debug logging via the DEBUG environment variable:

# Enable all uimatch debug logs
export DEBUG=uimatch:*

# Enable only selector-anchors logs
export DEBUG=uimatch:selector-anchors

Integration Notes

Text Matching (uiMatch Plugin): Text matching verification is provided by @uimatch/cli in /uiMatch compare (via the textCheck option). mode: 'self' | 'descendants' / normalize: 'none' | 'nfkc' | 'nfkc_ws' / match: 'exact' | 'contains' | 'ratio' / minRatio: 0.98.

Role Selector Resolution: role:button[name="Submit"] uses getByRole(). Boolean attributes like checked/selected/... fall back to CSS.

Anchors JSON Format

JSON Schema Support

Enable IDE autocompletion and validation by adding the $schema property to your anchors.json:

{
  "$schema": "https://unpkg.com/@uimatch/selector-anchors@latest/dist/schema/anchors.schema.json",
  "version": "1.0.0",
  "anchors": []
}

VS Code/IntelliJ IDEA/WebStorm will automatically provide:

  • ✅ Autocomplete for all anchor properties
  • ✅ Inline documentation for each field
  • ✅ Real-time validation errors
  • ✅ Schema-driven snippets

Local Schema (Alternative):

{
  "$schema": "./node_modules/@uimatch/selector-anchors/dist/schema/anchors.schema.json",
  "version": "1.0.0",
  "anchors": []
}

Minimal Example

The simplest anchors.json with required fields only:

{
  "version": "1.0.0",
  "anchors": [
    {
      "id": "button-primary",
      "source": {
        "file": "src/components/Button.tsx",
        "line": 42,
        "col": 10
      }
    }
  ]
}

Standard Example (Recommended)

Full-featured anchors.json with hints, snippet hash, and metadata:

{
  "version": "1.0.0",
  "anchors": [
    {
      "id": "button-primary",
      "source": {
        "file": "src/components/Button.tsx",
        "line": 42,
        "col": 10
      },
      "hint": {
        "prefer": ["testid", "role", "text"],
        "testid": "button-primary",
        "role": "button",
        "expectedText": "Submit"
      },
      "snippetHash": "a3f2c9d8e1",
      "snippet": "export function Button({ variant = 'primary' }) {\n  return (\n    <button data-testid=\"button-primary\" role=\"button\">\n      Submit\n    </button>\n  );\n}",
      "snippetContext": {
        "contextBefore": 3,
        "contextAfter": 3,
        "algorithm": "sha1",
        "hashDigits": 10
      },
      "subselector": "button[data-testid='button-primary']",
      "resolvedCss": "button[data-testid='button-primary']",
      "lastSeen": "2024-01-15T10:30:00Z",
      "meta": {
        "component": "Button",
        "description": "Primary action button in header",
        "tags": ["interactive", "form"]
      }
    },
    {
      "id": "card-title",
      "source": {
        "file": "src/components/Card.tsx",
        "line": 18,
        "col": 6
      },
      "hint": {
        "prefer": ["role", "text"],
        "role": "heading",
        "expectedText": "Product Title"
      },
      "snippetHash": "b7e4d2c1f9",
      "snippetContext": {
        "contextBefore": 2,
        "contextAfter": 2,
        "algorithm": "sha1",
        "hashDigits": 10
      },
      "meta": {
        "component": "Card",
        "description": "Card heading element",
        "tags": ["typography", "heading"]
      }
    }
  ]
}

Field Notes:

  • snippetHash: Auto-generated hash for code movement detection (fuzzy matching when line numbers change)
  • snippetContext: Controls snippet extraction window (default: ±3 lines, sha1, 10 digits)
  • subselector: Optional child element selector for Figma auto-ROI targeting
  • resolvedCss: Last resolved CSS selector (write-back cache for fast lookup)
  • lastSeen: Timestamp when the selector was last successfully resolved
  • meta: Human-readable metadata for organization and debugging

For complete schema details, see schema.ts.

Architecture

Anchor Matching System

  • Multi-criteria scoring (exact match, testid, role, component metadata, snippet hash, stability)
  • Best match selection from multiple anchors

AST Resolution

  • TypeScript/JSX parsing with fallback strategies
  • Selector extraction with stability scoring
  • Snippet hash generation for code movement detection

Integration

  • SPI-compliant plugin architecture
  • CLI integration via --selectors-plugin flag
  • Probe interface for liveness checking
  • Write-back support for anchor updates

Path Aliases: #anchors/* resolved via esbuild (build) and imports field (runtime)

License

MIT