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

@x12i/ui-data-mapper

v0.8.0

Published

Map an existing HTML UI to object/property and semantic UI metadata, with interactive SVG connectors.

Readme

UI ↔ Metadata Mapper

Portable Vite + React + TypeScript project for mapping an existing HTML UI to two kinds of metadata and drawing interactive links between them. The included example is deliberately domain-neutral and uses a cross-functional work portfolio.

Install

npm install @x12i/ui-data-mapper

The published package includes the production build under dist/ (serve that folder with any static file server) and the scripts/build-ui-from-source.mjs helper. Source checkout is still the usual way to develop:

Start in VS Code

Requirements: Node.js 20 or newer.

npm install
npm run dev

Open the local URL printed by Vite, normally:

http://localhost:5173

Core mapping works fully offline (file import/export, bundled example). Optional Memorix persistence is documented in MEMORIX.md and enabled when VITE_MEMORIX_BASE_URL and VITE_MEMORIX_ORG_ID are set (see .env.example).

Docs (Docify)

Narrative guides live under docs-library/. Humans browse the Docify site; agents use the knowledge SDK.

npm run docs              # build if needed, serve dist/web on :4173
npm run docs:rebuild      # force rebuild + serve
npm run docs:bundle       # → dist/bundle + dist/web
npm run docs:knowledge    # embed packs into @x12i/ui-data-mapper-docs
npm run docs:publish      # Cloudflare Pages → docs.ui-data-mapper.x12i.com
npm run docs:publish:preview

Production: https://docs.ui-data-mapper.x12i.com

npm i -D @x12i/ui-data-mapper-docs
import { uiDataMapperDocs } from "@x12i/ui-data-mapper-docs";

uiDataMapperDocs.getUseCaseMarkdown("map-a-rendered-ui");
uiDataMapperDocs.getBookMarkdown("01-the-mapper", "developers");

The three-input contract

The bundled domain-neutral work portfolio example is under public/examples/operations/:

ui.html       Existing UI, rendered without recreating it
data.json     Runtime sample used only for validation and recurring-item resolution
mapping.json  Object/property metadata, UI metadata, and selector rules

You can also click Load 3-file package and select one .html file and two .json files.

When Memorix is configured, Load from Memorix / Save to Memorix store the same three documents as sibling content types on one recordId (ui-packages / uiHtml+data+mapping). File Import/Export stay available as the offline fallback.

Abstract mapping

mapping.json can carry the same abstract-name vocabulary as @x12i/io-matrix-schema. An abstract is an agreed canonical name (for example project_code) claimed by a model or UI-metadata node. The tree has a Raw / Abstract naming lens; coverage is mapped / total properties that have at least one abstract.

{
  "abstractCatalog": ["project_code", "project_title"],
  "model": {
    "nodes": [
      { "path": "$.projects[0].code", "kind": "property", "abstracts": ["project_code"] }
    ]
  }
}

Helpers (catalogFor, coverageFor, mappingToObjectTypes, validateAbstracts) are exported from @x12i/ui-data-mapper/mapper. Abstracts are the join key used by @x12i/ui-data-mapper/binder to bind UI selectors to OpenAPI fields.

What works

  • Independent Import and Export for ui.html, data.json, and mapping.json.
  • Object and property definitions are shown as a collapsible metadata tree with branch guides, child counts, types, descriptions, and recurring-template badges.
  • Selecting a container expands it to reveal its direct properties; selecting a mapped UI value automatically opens every ancestor needed to reveal the linked property.
  • Runtime values from data.json are never rendered or edited in the tree. They are used only to validate required/missing state and identify recurring UI instances.
  • Arrays expose one representative item definition. Every repeated rendered row links to the same property definitions instead of producing one model per runtime item.
  • Configured UI atoms are clickable inside the embedded UI.
  • Clicking either side highlights the corresponding item on the other side.
  • Connections are bidirectional: selecting object metadata reveals every linked UI occurrence, while selecting any UI occurrence expands and selects its metadata definition.
  • SVG curves connect the selected JSON leaf and rendered UI atom.
  • Connection modes: Selected, Visible, and Off.
  • Metadata search, layer filters, and mapped-only filtering.
  • Explicit selector and recurring-template mappings are used; runtime value matching is disabled in metadata mode.
  • Mutation observation re-instruments dynamic UI content, tabs, tables, and drawers.
  • The left-side Object metadata | UI metadata toggle switches between two metadata scopes—never between metadata and runtime data.
  • Object metadata edits labels, types, descriptions, layers, status, required state, and schema definitions under model in mapping.json; data.json is unchanged.
  • UI metadata describes interface concepts such as titles, tabs, labels, headings, column names, and actions under metadata in mapping.json.
  • Both metadata scopes use the same tree editor, status, required, missing, click-to-map, and connector behavior.
  • Scan UI metadata discovers actions, tabs, headings, labels, navigation, and table headings, grouping repeated controls by meaning.
  • Click-to-map creates either a precise selector mapping or a relative repeater field rule when the selected value belongs to a recurring UI item.
  • Repeater mappings connect one UI item template to one JSON collection, then resolve every rendered row by stable key, visible identity field, or index.
  • Detect recurring UI matches repeated sibling structures to array data and proposes relative field rules automatically.
  • Required nodes remain visible when absent or null and can be repaired from the editor.
  • Status metadata works on objects, arrays, properties, and explicit mappings.
  • Generate starter metadata can bootstrap a mapping package from the rendered UI.

Status, required, and missing

Object/property metadata lives in mapping.json; data.json remains a separate runtime input:

{
  "model": {
    "statuses": [
      { "id": "verified", "label": "Verified", "color": "#22c55e" },
      { "id": "review", "label": "Review", "color": "#f59e0b" }
    ],
    "nodes": [
      {
        "path": "$.projects[0].code",
        "kind": "property",
        "dataType": "string",
        "description": "Stable project code",
        "status": "verified",
        "required": true,
        "layer": "data"
      }
    ]
  }
}

missing is computed from the runtime sample when a required path is absent, null, or an empty string. For recurring fields, the representative definition is marked missing if any runtime item lacks the required property. The runtime value itself is never displayed.

UI metadata

UI concepts that describe the interface rather than an object—actions such as Analysis and Raw facts, tabs, headings, and column labels—live in mapping.json:

{
  "metadata": {
    "values": {
      "interface": {
        "analysis": { "label": "Analysis", "role": "action" },
        "rawFacts": { "label": "Raw facts", "role": "action" }
      }
    },
    "mappings": [
      {
        "id": "metadata-analysis-action",
        "dataPath": "$.interface.analysis.label",
        "selector": "#list > .row .actions > .btn.primary"
      }
    ]
  }
}

One metadata selector may resolve to many repeated UI controls. This expresses that every row's Analysis button has the same semantic meaning without creating one metadata object per row.

Main source files

src/core/    The reusable mapper engine — <MetadataMapper>, types, matching/selector logic
src/App.tsx  Thin standalone-app wrapper: bundled-example fetch, "Load 3-file package"
src/main.tsx React entry point

Building ui.html from TSX/JSX source

The mapper only ever reads rendered DOM through CSS selectors (see PROJECT-NOTES.md); it does not execute or understand JSX/TSX, and it does not rebuild the supplied UI as React components. That means a .tsx/.jsx file can't be imported as-is — it has to be turned into rendered HTML first.

scripts/build-ui-from-source.mjs does that offline, once, as a normal Node build step. It bundles a React entry file — plus its local imports, npm dependencies, CSS, and image/font assets — into one self-contained ui.html, which you then import exactly like any hand-written UI package:

npm run build-ui-from-source -- path/to/entry.tsx path/to/output/ui.html

Try it against the bundled smoke test:

npm run build-ui-from-source -- scripts/tsx-source-example/entry.tsx scripts/tsx-source-example/ui.html

The entry file must mount itself into #root, the same way src/main.tsx does for this app:

import { createRoot } from "react-dom/client";
import App from "./App";

createRoot(document.getElementById("root")!).render(<App />);

The output has no CDN or network dependency — React itself is bundled in — so it stays consistent with this project's "no backend, no hosting dependency" design. This step only produces ui.html; data.json and mapping.json are still separate inputs, either authored by hand or generated from the rendered result with Generate starter metadata inside the running app.

Capturing ui.html from a live page (Chrome extension)

build-ui-from-source.mjs needs the actual TSX/JSX source. When you don't have it — a real running app, behind auth, wired to a live backend, or built with tooling too complex to bundle standalone — use the Chrome extension under extension/ instead. It captures whatever is actually rendered in a live tab, any framework, into the same kind of self-contained ui.html, ready for Load 3-file package:

npm run build-extension

Then load extension/ unpacked via chrome://extensions (Developer mode). See extension/README.md for details, capture modes, and permissions.

Embedding the mapper as a component

Everything under What works above runs the same way whether you use this repo's standalone page or drop the mapper into another React app. The reusable engine — src/core/ — is published separately from the standalone app as @x12i/ui-data-mapper/mapper:

npm install @x12i/ui-data-mapper
import { useRef } from "react";
import { MetadataMapper, MetadataMapperHandle } from "@x12i/ui-data-mapper/mapper";
import "@x12i/ui-data-mapper/mapper.css";

function Embedded() {
  const mapperRef = useRef<MetadataMapperHandle>(null);

  return (
    <div style={{ width: "100%", height: 600 }}>
      <MetadataMapper
        ref={mapperRef}
        initialUiHtml={uiHtml}
        initialData={data}
        initialMapping={mapping}
        onMappingChange={(next) => saveMappingSomewhere(next)}
        confirm={(message) => myHostApp.confirm(message)}
      />
    </div>
  );
}
  • The component sizes itself to its parent (width: 100%; height: 100%) and namespaces all of its CSS and custom properties under one root class, so it composes inside a host page's own layout instead of assuming it owns the whole viewport — mount it in a sized container, as above.
  • ref.current.loadPackage({ uiHtml, data, mapping, packageName? }) replaces the whole three-file package at once (what a file-picker, Memorix load, or a host's own "load" action would call); ref.current.getPackage() reads the current one back out, e.g. to implement your own export/save action instead of a file download.
  • Memorix helpers (createMemorixClient, readMemorixConfigFromEnv, envelope mappers) are exported from the same @x12i/ui-data-mapper/mapper entry — see MEMORIX.md.
  • onDataChange/onMappingChange fire whenever the runtime sample or mapping config changes for any reason (click-to-map, node editing, Scan UI metadata, Detect recurring UI, Generate starter metadata, or a loadPackage() call) — use them to persist changes your own way.
  • confirm overrides the handful of destructive confirmations (hiding metadata, replacing a generated scan); it defaults to window.confirm.
  • react/react-dom are peer dependencies of this entry point — the host app's own React instance is reused rather than bundling a second one.

react and react-dom need to be installed in the host app already (they're peer dependencies of the ./mapper entry point). See src/core/index.ts for the full exported surface.

Embedding the binder

@x12i/ui-data-mapper/binder binds the intended UI to OpenAPI operations through the same abstract names. It does not call the API. Output is an IoMatrixDocument (iomatrix.json) plus resolved bindings (UI selector ↔ abstract ↔ API field).

import { UiApiBinder } from "@x12i/ui-data-mapper/binder";
import "@x12i/ui-data-mapper/mapper.css";
import "@x12i/ui-data-mapper/binder.css";

<UiApiBinder
  uiHtml={uiHtml}
  data={data}
  mapping={mapping}
  onMappingChange={setMapping}
  openapi={[openApiDocument]}
/>
  • Bind — claim an API field and an abstract. The UI side is tagged in mapping.json.
  • Issues — unresolved abstracts and pipeline problems from @x12i/io-matrix-schema.
  • MatrixObjectPropertyIOMatrix from @x12i/io-matrix-view.
  • ref.getDocument() / ref.getBindings() return the current contract for a host to persist or codegen against.

The standalone app's Binder tab loads public/examples/operations/ (ui.html, mapping.json, openapi.json).

Production build

npm run build        # standalone app -> dist/
npm run build:lib    # embeddable mapper -> dist-lib/ (@x12i/ui-data-mapper/mapper)
npm run build:binder # embeddable binder -> dist-binder/ (@x12i/ui-data-mapper/binder)
npm run preview

The static standalone site is generated in dist/; the embeddable component build is generated in dist-lib/.

Memorix persistence (optional)

See MEMORIX.md. Copy .env.example to .env, point VITE_MEMORIX_BASE_URL at a running @x12i/memorix-service, and restart npm run dev. The topbar gains Load from Memorix / Save to Memorix; file Import/Export keep working as the offline path.

npm run validate-memorix   # envelope round-trip sanity (no network)

Recurring mapping shape

{
  "version": "1.0",
  "ui": {
    "atomSelectors": ["[data-field]", ".metric", "table td"]
  },
  "auto": { "enabled": false },
  "repeaters": [
    {
      "id": "project-row",
      "itemSelector": "#list > .row",
      "dataPath": "$.projects",
      "match": {
        "mode": "key",
        "dataPath": "id",
        "uiAttribute": "id",
        "uiPattern": "row-{value}"
      },
      "fields": [
        {
          "id": "project-code",
          "dataPath": "code",
          "selector": ".code",
          "layer": "data",
          "required": true,
          "status": "verified"
        }
      ]
    }
  ]
}

This means .code is defined once relative to a project row. At runtime, row-p1 maps to the object whose id is p1, regardless of row order or filtering. The same collection can have additional templates, such as a table row, without duplicating per-object map definitions.