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

@glossarist/concept-browser

v0.7.133

Published

Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support

Readme

Glossarist Concept Browser

A statically deployable terminology browser. Install as an npm package, configure via YAML, build, deploy.

Quick Start

# 1. Create your project
mkdir my-dictionary && cd my-dictionary
npm init -y

# 2. Install
npm install @glossarist/concept-browser

# 3. Create site-config.yml
cat > site-config.yml << 'EOF'
id: my-dictionary
title: My Dictionary
uri_base: https://example.com
datasets:
  - id: my-vocab
    title: My Vocabulary
    local_path: datasets/my-vocab    # or gcr_package: https://...
EOF

# 4. Build
npx concept-browser build

# 5. Deploy
# Upload dist/ to any static host (GitHub Pages, Netlify, S3, etc.)

How It Works

Your project (CWD)                    Package (read-only)
├── site-config.yml          ──┐
├── datasets/                 ─┤
│   └── my-vocab/             ─┤     ┌──────────────────────────┐
│       └── concepts/*.yaml   ─┤     │ @glossarist/concept-browser │
├── public/                   ─┤ ──> │                          │
│   └── (logos, favicons)     ─┤     │ generate-data.ts         │
├── .cb-content/   ← generated │     │ bridge-to-astro.ts       │
└── dist/          ← output    │     │ astro build              │
                             ──┘     └──────────────────────────┘

The package reads your config and data, generates .cb-content/ (content collections) and dist/ (static site) in your project. The package itself (node_modules/) is never modified.

Data / Deployment Boundary

There is a strict separation between dataset authors and deployers:

| Role | Owns | Does NOT touch | |------|------|----------------| | Dataset author | YAML concept files, bibliography, figures/tables | site-config.yml, deployment URLs, other datasets | | Deployer | site-config.yml, URI patterns, branding, fonts | dataset content, inline references, concept IDs | | concept-browser | Resolves every citation at runtime | — |

The same dataset YAML works in any deployment. A citation like {{cite:iso704}} resolves differently depending on which datasets are co-deployed — the deployer's site-config.yml:uriPatterns decides.

Inline Content Syntax

All inline references in concept text use the unified {{kind:target}} notation:

| Kind | Syntax | Example | Description | |------|--------|---------|-------------| | cite | {{cite:sourceId}} | {{cite:iso7301}} | Cite a ConceptSource from this concept's sources[] | | cite+label | {{cite:id, label}} | {{cite:iso7301, rice}} | Same, with explicit display label | | urn | {{urn:URN}} | {{urn:iso:std:iso:704}} | Reference via URN routing | | fig | {{fig:id}} | {{fig:diagram_3}} | Reference a figure entity | | table | {{table:id}} | {{table:unit_list}} | Reference a table entity | | formula | {{formula:id}} | {{formula:ohm_law}} | Reference a formula entity | | bib | {{bib:id}} | {{bib:ref_1}} | Bibliography entry (no underlying concept) | | link | {{link:URL}} | {{link:https://example.com}} | External URL | | link+label | {{link:URL, label}} | {{link:https://example.com, click here}} | External URL with label | | image | {{image:src}} | {{image:diagram.png}} | Inline image embed | | image+alt | {{image:src, alt}} | {{image:diagram.png, The diagram}} | Image with alt text | | designation | {{designation}} | {{measurement unit}} | Reference by designation text | | numeric | {{numeric_id}} | {{112-01-10}} | Reference by numeric ID |

The deprecated <<ref,title>> AsciiDoc xref syntax still works but emits a console warning. Migrate to the unified syntax.

Citation Resolution Cascade

When concept-browser encounters {{cite:sourceId}}, it walks this cascade:

  1. uriPatterns — Is there a co-deployed dataset matching this URI? → internal link
  2. routing[] — Is there a routing entry in site-config.yml? → external link
  3. citation.link — Does the source have a canonical link? → flat bibliography record
  4. unresolved — No match → plain text

Configuration

Everything is in site-config.yml. See site-config.example.yml for all options.

Dataset location

Each dataset specifies where its source data lives. Three options:

datasets:
  # Option A: Local directory with concepts/*.yaml
  - id: my-vocab
    local_path: datasets/my-vocab        # relative to project root

  # Option B: GCR package (auto-downloaded at build time)
  - id: iev
    gcr_package: https://github.com/org/repo/releases/download/v1.0/my-vocab.gcr

  # Option C: Git repository (cloned at build time)
  - id: iso-terms
    source_repo: https://github.com/org/iso-terms

Colors

Each dataset can have a color — single hex or light/dark pair:

datasets:
  - id: my-vocab
    color: "#2563eb"                      # single color (both modes)

  - id: legacy-vocab
    color:
      light: "#004996"                    # light mode
      dark: "#3B82F6"                     # dark mode

Colors appear in the sidebar (color dot per dataset), sphere cards (top bar + tint), badges, and relationship edges. If omitted, a palette color is assigned automatically.

Group colors work the same way:

dataset_groups:
  - id: my-vocab-series
    color: "#d97706"

Logo

Place logo SVGs/PNGs in public/ and reference them in branding:

branding:
  logo:
    alt: My Dictionary
    light: /images/logo-light.svg         # shown in light mode
    dark: /images/logo-dark.svg           # shown in dark mode

If no logo is configured, the Glossarist logo is shown by default. The footer always shows the Glossarist logo ("Powered by Glossarist").

Fonts

Brand typography is fully slot-based and category-agnostic. There are four slotstitle, heading, body, mono — and each slot accepts any category (serif, sans-serif, monospace). Nothing dictates that "headings must be serif" or "body must be sans-serif". Pick whatever combination matches your brand.

Slots

| Slot | Applies to | Default family | Default category | |---|---|---|---| | title | The single most-prominent text on each page (home hero, concept name on detail page, dataset/group title) | DM Serif Display | serif | | heading | h2–h6 section headings | DM Serif Display | serif | | body | Paragraph text, lists, table cells | DM Sans | sans-serif | | mono | Code blocks, inline code, kbd | JetBrains Mono | monospace |

The defaults preserve the Glossarist visual identity — override any of them to match your brand.

Per-slot category override

Set category on any slot to control the fallback chain (the browser uses it when the primary family fails to load or is still loading):

branding:
  fonts:
    title:
      family: Inter
      source: google
      category: sans-serif           # sans-serif title (was serif by default)
      weights: [400, 600, 700]
    heading:
      family: Inter
      source: google
      category: sans-serif
      weights: [600, 700]
    body:
      family: Merriweather
      source: google
      category: serif                # serif body (was sans-serif by default)
      weights: [400, 700]
    mono:
      family: Fira Code
      source: google
      category: monospace
      weights: [400, 500]

This produces:

| Slot | CSS variable | Stack | |---|---|---| | title | --font-title | 'Inter', system-ui, sans-serif | | heading | --font-heading (and --font-header for backward compat) | 'Inter', system-ui, sans-serif | | body | --font-body | 'Merriweather', Georgia, serif | | mono | --font-mono | 'Fira Code', ui-monospace, "JetBrains Mono", Menlo, Monaco, monospace |

Source options

| source | Behavior | |---|---| | google | Build emits a Google Fonts CSS @import for the declared family + weights. | | url | Build emits a @font-face block loading from url. | | local | Consumer ships the font files in public/; no build-time fetch. |

Backward compatibility

The legacy branding.fonts.header slot still works — it's a deprecated alias for branding.fonts.heading. Existing configs don't break. The CSS variable --font-header is still emitted (as an alias for --font-heading) so existing stylesheets that reference it continue to work.

Mixed-category example: all sans-serif

branding:
  fonts:
    title:   { family: Inter,        source: google, category: sans-serif }
    heading: { family: Inter,        source: google, category: sans-serif }
    body:    { family: Inter,        source: google, category: sans-serif }
    mono:    { family: JetBrains Mono, source: google, category: monospace }

Mixed-category example: traditional serif

branding:
  fonts:
    title:   { family: Playfair Display, source: google, category: serif }
    heading: { family: Lora,             source: google, category: serif }
    body:    { family: Source Sans Pro,  source: google, category: sans-serif }
    mono:    { family: Source Code Pro,  source: google, category: monospace }

Favicons

branding.favicon accepts two shapes — a legacy string form and an object form that lets consumers provide their own canonical favicon set without writing a post-build script.

String form (legacy, still supported)

Path to a single source SVG/PNG. The CLI generates the full variant set (favicon.ico, apple-touch-icon-*.png, etc.) from it using the favicons package:

branding:
  favicon: assets/my-brand.svg

Object form (recommended for branded deployments)

The object form lets you provide your own canonical favicon set (typically RealFaviconGenerator output) and have the CLI install it without a post-build script.

A canonical favicon set is multiple files — typically favicon.svg, favicon.ico, favicon-96x96.png, apple-touch-icon.png, web-app-manifest-192x192.png, web-app-manifest-512x512.png, and site.webmanifest. Put all of them in a directory (e.g. assets/favicons/) and reference it via source_dir:

my-deployment/
├── site-config.yml
└── assets/
    └── favicons/                  ← branding.favicon.source_dir points here
        ├── favicon.svg
        ├── favicon.ico
        ├── favicon-96x96.png
        ├── apple-touch-icon.png
        ├── web-app-manifest-192x192.png
        ├── web-app-manifest-512x512.png
        └── site.webmanifest       ← optional; CLI regenerates with BASE_PATH

Then in site-config.yml:

branding:
  favicon:
    base_path: /                              # URL prefix (BASE_PATH-aware; default '/')
    source_dir: assets/favicons               # canonical files (all of them, in one directory)
    icons:                                    # DATA — declare each icon, not HTML
      - rel: icon
        type: image/svg+xml
        href: favicon.svg
      - rel: icon
        type: image/png
        sizes: 96x96
        href: favicon-96x96.png
      - rel: shortcut icon
        href: favicon.ico
      - rel: apple-touch-icon
        sizes: 180x180
        href: apple-touch-icon.png
      - rel: manifest
        href: site.webmanifest

The CLI copies every file from source_dir/ into public/ (also removing the default cruft), then renders one <link> tag per icon entry. The href is a filename — the system applies the correct base_path prefix automatically. Absolute URLs (https://cdn.example.com/x.png) and root-relative paths (/x.png) are emitted unchanged.

| Field | Type | Effect | |---|---|---| | source_dir | string | Path (relative to cwd) to a directory containing all canonical favicon files. The CLI copies every file in this directory into public/, overriding any defaults. Also removes the default-generated cruft (apple-touch-icon-57x57.png, favicon-16x16.png, etc.) so it doesn't linger. | | icons | FaviconIcon[] | DATA — recommended. Array of { rel, href, type?, sizes? } entries. Each renders to one <link> tag with BASE_PATH-aware href. Replaces the default 16-link set. | | skip_default_links | boolean | When true, the CLI does NOT call the favicons package and does NOT emit the default <link> tags. Pair with icons and source_dir for fully custom branding. | | base_path | string | URL prefix prepended to every emitted link. Useful for BASE_PATH-scoped deployments (e.g. /vocab/). | | ~~links_html~~ | string | @deprecated — use icons instead. Raw HTML emitted verbatim. Impossible to validate or safely BASE_PATH-rewrite; kept for backward compat with a console warning. |

The object form exists so consumers with a RealFaviconGenerator favicon set (or any other canonical brand favicon bundle) can install it without a post-build script. Previously this required workarounds like glossarist/cie-eilv/scripts/install-favicons.mjs (149 lines) and glossarist/iala-vocab/scripts/install-favicons.mjs (175 lines) — both are now unnecessary.

If neither form is set, the Glossarist default favicon is used.

About pages

Place JSON files in public/pages/:

public/
└── pages/
    ├── about.json          → /about
    └── my-vocab-about.json → /my-vocab-about

Each file:

{
  "title": "About This Dictionary",
  "html": "<p>Content here. Supports full HTML.</p>"
}

The page appears in the sidebar navigation automatically.

Full example

See site-config.example.yml for all options including UI languages, fonts, features, and dataset groups.

Data/Deployment Boundary

Concept-browser enforces a strict separation between data (authored by dataset authors) and deployment (configured by deployers):

  • Dataset authors write concepts, sources, bibliography, and inline mentions. They don't know where their dataset will be deployed or what other datasets will be co-deployed.
  • Deployers write site-config.yml to register datasets, declare URI patterns, and optionally add routing entries for external datasets. They never edit dataset content.
  • Concept-browser resolves every cross-reference at runtime via a fixed cascade, making multiple datasets behave as one coherent whole.

The resolution cascade

Every {{cite:...}} and {{urn:...}} mention walks this cascade at render time:

  1. uriPatterns — is there a co-deployed dataset that matches? → internal link (case 1)
  2. routing[] — is there a routing entry for the URI? → external link (case 2)
  3. citation.link — does the source have a canonical link? → flat bib record (case 3)
  4. Unresolved — plain text

The same YAML renders differently in different deployments. The data never changes.

Inline Content Syntax

All inline references in concept text use the unified {{kind:target}} notation:

| Kind | Example | What it does | |---|---|---| | cite | {{cite:sourceId}} | Cite a ConceptSource from this concept's sources[] — walks the full resolution cascade | | urn | {{urn:iso:std:iso:704}} | Reference a concept via URN routing | | fig | {{fig:diagram_3}} | Reference a figure entity in the same dataset | | table | {{table:units}} | Reference a table entity | | formula | {{formula:ohm_law}} | Reference a formula entity | | bib | {{bib:ref_1}} | Reference a bibliography entry (case-3-only, no underlying concept) | | link | {{link:https://example.com}} | External URL (canonical, deployment-independent) | | image | {{image:src, alt}} | Inline image embed | | (none) | {{measurement unit}} | Designation match in same dataset | | (none) | {{112-01-10}} | Numeric ID match in same dataset |

Each kind accepts an optional label: {{kind:target, label}}.

Deprecated: <<ref,title>> (AsciiDoc xref syntax) emits a deprecation warning. Migrate to {{kind:target}}.

See /learn/inline-content on any deployed site for a full interactive reference.

CLI Commands

npx concept-browser fetch      # Download datasets from GCR/repos
npx concept-browser generate   # Convert YAML → JSON-LD + RDF artifacts
npx concept-browser build      # Full pipeline: fetch + generate + build site

.gitignore for Your Project

Add these to your project's .gitignore:

.cb-content/
dist/
.datasets/
.gcr/
public/data/

Data Format

Each concept is a YAML file in datasets/{id}/concepts/:

---
data:
  identifier: "1-1-1"
  localized_concepts:
    eng: <uuid>
  sources:
    - type: authoritative
      origin:
        ref:
          source: "ISO 9000"
  definition:
    - content: "The concept definition text."
  terms:
    - type: expression
      normative_status: preferred
      designation: "quality"
  language_code: eng
---

Or use a Glossarist GCR package — set gcr_package in your dataset config and the CLI downloads it automatically.

Deployment

The build outputs a static site to dist/. Deploy it anywhere:

  • GitHub Pages: Push dist/ to gh-pages branch
  • Netlify/Vercel: Set build command to npx concept-browser build, publish directory to dist/
  • S3/CloudFront: aws s3 sync dist/ s3://my-bucket/

No server required. All data is pre-built into static JSON files.