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

@muten/core

v0.0.54

Published

AI-first frontend framework - compiles .muten files to vanilla JS + signals.

Readme

ALPHA - STILL ON DEVELOPMENT

Muten is still under active development. We are currently in the alpha stage and are working on training models with Muten. Please keep in mind that improvements are being made gradually, and version 1.0 has not been released yet.

An AI-first frontend framework. You write .muten files; muten compiles them to vanilla JS with fine-grained signals - no virtual DOM, no framework runtime to ship. The language is small, semantic and analyzable on purpose: an AI (or a person) can locate and mutate an app cheaply.

npm create muten@latest my-app   # scaffold a new app (cross-platform: Windows + macOS)
cd my-app && npm install && npm run dev

What a page looks like

One file, declarative, no imports or boilerplate. This is the whole thing:

screen home

entity Product {
  name  text
  price number
}

state {
  products = [] : list<Product>
  draft    = {} : Product
}

action add mutates products, draft <- p {
  products.push({ name: p.name, price: p.price })   # build a record inline
  draft.reset()                                     # then clear the form
}

Page class("flex flex-col gap-4") {
  Form bind(draft) submit(add) "Add product"

  each products.sortDesc by price as p {             # render the list, sorted
    Text "{p.name} - ${p.price}"
  }

  Text "Total: ${products.sum by price}"             # a live aggregate, no JS
}

No useState, no component tree, no build wiring, and muten check validates every reference and type before it ever runs in a browser.

Why muten

For an AI the cost of working on a codebase is context + mistakes + edit-radius. muten is built to cut all three by construction - these are properties of how it compiles, not marketing:

  • Almost nothing to ship: no virtual DOM, no framework runtime. The same todo app ships a small fraction of the JS the big frameworks do, and a static page ships zero. (See the size table in muten vs React / Vue / Svelte below.)
  • A deterministic oracle: muten check --json validates every page at compile time (unknown state/action/part, bad style token, illegal mutation, a non-list fed to each/DataTable, a self-referential get, a constraint on the wrong field kind, a match arm outside its enum, when <list> that's always truthy, every Ns polling that never runs) in milliseconds, no browser - a feedback loop the others don't have. A bounded language is what makes that possible.
  • The whole app as data: app.map.json is a compact index of routes + structure an agent reads first, instead of grepping a component tree.
  • Small edit radius: the UI is declarative, so a change is usually a few lines in one file.

The trade is deliberate: a small, analyzable language an AI can hold in its head, not a general-purpose one it can't.

muten vs React / Vue / Svelte - the honest version

They are general-purpose: they build anything, with mature ecosystems and a deep talent pool. For a human team building a big, bespoke product, that is usually the right call. muten makes the opposite trade on purpose, and it only wins on its own terms:

| | muten | React / Vue / Svelte | |---|---|---| | Best for | an AI builds & maintains it; the declarative 80% (CRUD, dashboards, content, internal tools) | human teams, large bespoke UIs | | Language surface | small - the whole thing fits in an AI's context | large (hooks, lifecycle, reactivity rules) | | Catches mistakes | muten check - at compile time, in milliseconds, no browser | at runtime / in tests | | A typical change | a few lines in one file | ripples across components | | Ships to the browser | ~3.7 KB gzip for a todo app (a ~2 KB signals runtime + your page) - a static page ships zero | 14-59 KB of runtime + your app | | Ecosystem / maturity | young · one maintainer · pre-1.0 | mature · huge |

The single biggest reason AI-written muten works on the first try more often is that middle row: a compiler that answers before the browser does.

The honest cost: muten is small by design, so it can't express everything. It shines when an AI builds and maintains the app and the app is the declarative 80%. It is not the tool for a hand-crafted, highly-custom UI that needs the full React ecosystem, and it doesn't pretend to be.

Rule of thumb: let muten do the structure and the data; couple in other tech for the rest (next section).

Capabilities

| Area | What you get | |---|---| | UI | declarative primitives (layout, text, forms, tables, links) · when/each control flow · class("…") is the single styling path - layout AND look (reactive: class(active when isOpen)) · on(event: action) on any element · on(enter: action) synthetic event on inputs (fires only on Enter key - no Custom needed for Enter-to-submit): SearchField bind(draft) on(enter: send) | | State | local state · app-global store · derived get · actions with if/else · fine-grained signals. A page action can call a store action (cart.add(d) draft.reset()): store + local work in one handler | | Lists | bounded ops, no raw map/reduce: inline objects (push({…})) · in-place patch · filtered each…where · aggregates sum/count/avg/min/max · sort/sortDesc (by a literal field or a text state = user-chosen column) · take(n) pagination/top-N · toggle(x) add⇄remove (favorites/subscriptions) | | Forms | a Form auto-built from an entity, one input per field: text · number (coerced) · email · bool (checkbox) · enum (select) · date · password · textarea, with built-in validation (an unknown type is flagged, not silently text) | | Data | query states over sources (full HTTP: method, headers, body, nested at) · one api block (named multi-backend clients) · optimistic CRUD (create/update/delete + .pending/.error) · refetch(q: …, page: …) · query x live (WebSocket real-time: the server pushes, only changed rows re-render) · a post/put/delete escape for non-REST | | Routing | real-path URLs · params (/product/:id -> param id) · route guards · /404 catch-all · route paths are quoted strings: routes { "/" -> home "/404" -> notfound } · Link "label" -> "/path" · guard redirects else "/login" (bare paths no longer parse) | | SEO / SSR | muten build pre-renders every route to real HTML (static pages ship zero JS; data pages fetched at build) · per-page meta { title … description … } with og:* auto-derived | | Media & widgets (all native, all oracle-checked) | Chart @data kind(bar) x(…) y(…) (reactive SVG - no chart library) · Icon "lucide:check" (Iconify, inlined at build, tree-shaken) · Image / Video · standalone inputs: Select bind(x) options(…) · Checkbox · Password · Number min(0) max(9) · Range (slider) · Date (native calendar) · drag-and-drop via draggable/droptarget with a typed payload | | Interop (lowest tier first) | class() for native HTML + CSS libs · Custom for vanilla-JS libs muten has no primitive for (maps, rich-text, canvas/WebGL) · use fmt from "./lib.ts" for JS logic · use functions callable as statements inside action or effect (side effects: persist, scroll, analytics): action send { messages.push(m) persist(messages) scrollBottom() } | | Targets | one muten bundle runs everywhere: the web · desktop (Tauri) · an installable app (public/manifest.webmanifest, Chrome ▸ Install) · an .apk (muten android --build, or CI with nothing installed). No mobile build, no second renderer - a phone runs the same Chromium your desktop does | | AI-native | lint == build · one source of truth per concept · the full language reference ships in every scaffolded app under .claude/ (an AGENTS guide + a Claude skill) |

Reactivity & reconciliation

muten is Solid's fine-grained signals + Svelte's compile-to-direct-DOM, with no virtual DOM:

  • Reads subscribe, writes notify. Each {count}, class(active when x), when, each compiles to its own tiny effect that reads exactly the signals it needs - when one changes, only that spot updates, never a re-render of the tree.
  • Lists reconcile by id (never by index): each/DataTable keep a per-row signal, so on new data only the rows whose fields changed touch the DOM (the rest are reused or moved in place), and removed rows dispose their effects - no leaks, no zombies. Focus, scroll and input survive live updates.
  • Derived values are lazy and glitch-free. A get is computed when read, once per change, never from a half-updated input; an effect whose derived inputs come out unchanged does not run. Static subtrees are cloned from a template instead of built node by node, and a reactive text or attribute touches the DOM only when its value really changed.
  • Updates batch into one microtask, the way Solid does: a burst of writes in a tick re-renders each spot once, not per write - so a real-time feed (a live socket) costs one render per frame, not one per message.

No diffing, no virtual-DOM memory, no framework interpreter to ship. That is what keeps muten fast as apps grow; for huge lists you still virtualize (render only the visible rows) and send server-side deltas, exactly as you would in any framework.

How muten couples with the rest of the web - two tiers

muten the language stays tiny on purpose; a muten app reaches the whole web platform through bounded, analyzable escapes. The point: you never fight the language to do something it doesn't have, you drop to the right tier, and the compiler still checks the seam. Reach for the lowest tier that works:

| Tier | What it reaches | Typical examples | The trade | |---|---|---|---| | 1 · Pure muten | the declarative 80% - pages, routing (params, guards, /404), state/store/get, the list toolkit, Form (+validation), query over REST, SSG + SEO | a whole CRUD / SaaS / catalog / dashboard / content app | zero extra deps | | 2 · muten + npm | CSS libs via class(), components via use element \| widget \| react (used like parts, no .js in the app), JS logic via use fn from "pkg", your own canvas/map via Custom | Web Components, Leaflet, a React date picker, zod, date-fns | a JS dep; React only on the pages with a React island |

Check tier 1 first. Charts, icons, video, sliders, date pickers, dropdowns and drag-and-drop look like tier-2 widgets and are native primitives (Chart, Icon, Video, Range, Date, Select, draggable). Escaping for one of them ships JS you didn't need, loses the oracle at that seam, and makes the app the opposite of what muten is for. Tier 2 is for what muten genuinely has no primitive for: maps, rich-text, canvas/WebGL.

Why the escapes stay safe. The compiler validates the seam - the state props and action callbacks crossing into a Custom, and the call site of a use function (an undeclared one is a check error). So coupling in Leaflet or zod never costs you the oracle on the muten side.

Deploy - the honest caveat. Two production paths, both muten's own runner. muten build (the CLI SSG) inlines the theme + project styles.css and pre-renders (SSR) your stores/query data, so each route ships fully styled with real content as zero-JS HTML. use functions, npm components and route guards work there too; what a static export can't do is persist store state across full-page navigations - for that, muten bundle produces a CSR build (per-route chunks, a ship-size report, source maps with --sourcemap). npm run dev runs every tier regardless.

| Your app uses... | Deploy with | |---|---| | Pure muten, static/content pages (styled, with data) | muten build (zero-JS HTML) or muten bundle | | a shared store across pages (state that must survive navigation) | muten bundle (keeps state across navigations) |

Most real apps use muten bundle.

Desktop & mobile - the same bundle

There is no per-target build and no second renderer. A phone runs the same Chromium your desktop does, so every target below ships the exact muten bundle output. Nothing about your .muten changes.

| Target | You install | How | |---|---|---| | Web | — | any static host (+ the SPA fallback above) | | Installable app (icon, fullscreen, offline) | — | public/manifest.webmanifest ships by default: open the site on a phone → Chrome ▸ Install | | .apk | — | scaffold --android, then push: the generated CI workflow builds it on a runner that already has the Android SDK | | .apk, locally (+ emulator) | a toolchain, into ~/.muten | muten android --build — one command, from anywhere | | Desktop | the Rust toolchain | scaffold --tauri → npm run tauri:build (ships the OS webview, not a browser) |

muten android --build ~/my-app   # → my-app/android/app/build/outputs/apk/debug/app-debug.apk

It checks the toolchain, generates the (gitignored, disposable) android/, bundles, syncs and runs Gradle - there is no order to learn and nothing to cd into. If the toolchain is missing it says so and stops: muten android --install then fetches a JDK 21 + the Android SDK into ~/.muten - no PATH, no registry, no admin, and rm -rf ~/.muten is the uninstall - and points the project at them so Gradle needs no env vars.

To iterate on a real phone you don't need any of it: muten dev prints a /_qr page. Scan it and the app runs on your phone with live reload, over your wifi, no cable and no SDK. (HMR follows because the dev client opens a same-origin EventSource - whatever host serves the page serves the reload channel.)

The app, by convention

my-app/
├─ src/
│  ├─ app.muten            # the ROOT: routes (+ optional persistent shell, + layouts for route groups)
│  ├─ pages/
│  │  └─ home/home.muten   # a page; the folder name is its route target
│  ├─ parts/               # reusable .muten components (object + action params)
│  └─ components/          # host-written Custom JS (the escape hatch)
├─ theme.muten             # the project's token scale (md=16px, breakpoints, …)
└─ src/styles.css          # the look (muten ships structure + layout; the skin is yours)

src/app.muten is the single source of truth the AI reads first:

routes {
  "/" -> home
  "/404" -> notfound
}

CLI

muten dev    [dir] [--host]  # dev server - esbuild, in-memory, SURGICAL HMR + the oracle on every save
                             #   this machine only; --host opens it to the wifi (a phone, Capacitor live reload)
muten bundle [dir] [--sourcemap]  # production build → ./dist (per-route chunks + ship-size report; maps only if asked)
muten build  [dir]           # SSG: pre-render every route to zero-JS HTML (+ .muten/app.map.json, never deployed)
muten check  [dir] [--json]  # parse + validate every page, no compile - the deterministic ORACLE
muten test   [dir] [--only=x]# the .test files next to each page, in headless Chrome, on mock data, network closed
                             #   --json → structured diagnostics (code + loc + "did you mean…?") in ms, no browser
muten map    [dir] [--json]  # emit app.map.json COLD (no build) - the app graph an AI reads FIRST
muten add    <name...>       # a PLUGIN (lowercase, e.g. `devtools`) → install @muten/<name> + enable it in muten.config;
                             #   a COMPONENT (PascalCase, e.g. `Card`) → copy its source from a registry into src/parts/
muten new    <page|store|app> <name...>
                             # scaffold STRUCTURE that already compiles (a page + its route, a store, the routes entry)
muten android --build [dir]  # THE .apk, in one command, from anywhere: checks the toolchain, generates android/,
                             #   bundles, syncs, runs Gradle, prints the file. No cd, no Gradle by hand, no order
                             #   to learn. (`npm run android` in a scaffolded app.)
muten android         [dir]  # just diagnose: is there a JDK + Android SDK Gradle can actually use? (exit 1 if not)
muten android --install      #   → get them: a JDK 21 + the SDK into ~/.muten (no PATH, no registry, no admin;
                             #     `rm -rf ~/.muten` uninstalls), and point android/ at them so nothing needs
                             #     environment variables. ~800MB, once per machine.

check and map are the AI-first feedback loop: an agent asks the compiler "is this valid, and what did I mean?" (check --json) and "what's the whole app?" (map) without running a browser. lint is an alias of check.

All commands default to the current directory; pass a path to target another. The muten bin ships with the app (it's a dependency). To scaffold a new app, use npm create muten@latest (the separate create-muten scaffolder).

Dev server & bundler - the muten runner

muten ships its own runner (esbuild, embedded - no Vite to configure). npm create muten wires the scripts; npm run dev / npm run build run them:

  • muten dev - an in-memory dev server (no .muten-dev/ folder) with surgical HMR: edit a node's text or class and only that node re-renders - your counters, inputs and list selection survive, no full reload, no flash. Compile/runtime errors surface with the file:line:col + a code frame + "did you mean…?", in the terminal and as a browser overlay. The oracle runs on every save.
  • muten bundle - the production CSR build: per-route chunks, content-hashed CSS and a per-route ship-size report so "minimum by construction" is visible. Nothing about the source ships: source maps (which point at your .muten lines) only with --sourcemap, and the app graph stays in .muten/app.map.json.

There is no Vite, no bundler config - muten owns the entry and the bundler (embedded esbuild). The build is configured entirely in muten.config - written in muten syntax, not JS (it carries the dev port and, for Tailwind/DaisyUI, the theme adapter + component classes):

# muten.config
dev { port 5173 }

# import a component-library plugin's parts as-is (no copy):
plugins { shadcn {} }

Plugins & component libraries

The core ships no component library and no devtools — both come from plugins (npm packages enabled in muten.config's plugins {} block). There are two kinds, and muten add picks the right flow by the name's case:

  • Component registries (a registry.json of muten parts) — e.g. @muten/shadcn. Either import parts as-is (plugins { shadcn {} }) or eject them with muten add Card Dialog … (PascalCase), which copies the source into src/parts/ (the "own the source" model). A part whose behaviour needs a Custom is muten add-only, since its host .js has to land in your src/components/.
  • Dev-boot plugins (a muten.devBoot export in package.json) — e.g. @muten/devtools. muten dev auto-mounts them (imports the export into the boot); muten bundle/build never do, so there's zero production cost. Add one with muten add devtools (lowercase) — it installs the package and enables it in muten.config for you.

Both seams are part of @muten/core; a plugin is any npm package whose package.json declares a registry and/or a muten.devBoot hook.

Programmatic API

import { buildApp, compile, parse, validate, toDoc } from '@muten/core';

await buildApp('./my-app');               // same as `muten build ./my-app`
const html = compile(toDoc(parse(src)));  // drive the compiler directly (embedding)

Architecture

The compiler is a straight pipeline of small, single-purpose stages:

.muten ─[lang]→ IR ─[ir: compose]→ tree ─[ir: flatten]→ Doc ─[ir: validate]→ ✓ ─[compile]→ JS

The source is TypeScript under src/, organized by domain: each has its own README:

| Domain | Role | |---|---| | src/engine/shared | contracts: types, the vocabulary (no magic strings), diagnostics | | src/engine/lang | front-end: .muten text → IR (lexer · grammar · parser · manifest) | | src/engine/ir | IR transforms + validation (compose · flatten · validate) | | src/engine/compile | back-end: Doc → runnable JS (DOM + logic + emit + helpers) | | src/engine/style | the styling token vocabulary (the engine ships no values) | | src/engine/project | filesystem + whole-app awareness (load · analyze · routes · styles) |

The runtime (the only thing shipped to the browser), the runner (esbuild-muten.ts - dev + bundle), the CLI and the build/lint orchestration also live in src/. See src/engine/README.md for the file-level conventions (≤500 lines, honest types, data-table dispatch, no magic strings).

Build

npm run build = tsc (strict type-check) + esbuild → dist/**/*.js, minified, per-file (modules preserved, so nothing bundles into a heavy monolith). dist/ is generated - edit src/.

Styling & escape hatch

muten imposes no theme. There is ONE way to style: class("…") (your CSS / Tailwind / anything) carries both layout and look - and it composes reactively: toggle a token (class(active when x)) or build one from a value (class("status-{m.status}") → status-online/status-idle, the reference oracle-checked). theme.muten holds the design values and muten emits them as :root CSS custom properties (--space-md, --color-primary, ...) that your CSS consumes. For a CSS value that changes at runtime (a progress width, a dynamic transform), style(w: "{pct}%") binds it to a CSS variable --w (the only thing style() can set). Common formatting is built in, so a use function is almost never the answer for it:

  • text - upper · lower · initial · truncate
  • numbers - money · round · floor · ceil · abs · min · max · pow · sqrt · sin · cos · pi
  • dates - now · ago · date · time · datetime · weekday · calendar · before · after · isToday · isPast · isFuture · daysUntil · addDays · dayKey
  • other - map (value → value lookup) · isEmail

For behavior the primitives can't express, drop to a Custom component (src/components/<Name>.js).

Status & roadmap (honest)

Pre-1.0 - the core is solid, the edges are young. Build real apps with it; don't bet a critical production system on it yet (small ecosystem, one maintainer, not yet battle-tested).

Solid today: the language + compiler, the dev / bundle / build / check / map CLI + oracle, the native runner (embedded esbuild) with surgical HMR + typed file:line:col errors + source maps, the VS Code extension (live-lint + autocomplete). The bounded list toolkit - inline objects, patch, each...where, aggregates (sum/count/avg/min/max), sort/sortDesc, and page->store action composition, so a real CRUD/dashboard app is pure muten, no JS escape. Form fields cover text · number (coerced) · email · bool (checkbox) · enum (select) · date · password · textarea, with validation. Reactivity is keyed and batched: each/DataTable reconcile rows by id with minimal DOM moves, and query x live streams real-time updates over a WebSocket (only changed rows re-render).

Next, toward 1.0:

  • built-in virtualization for huge lists (today you render only visible rows yourself).
  • richer SSG for stateful multi-page apps (today a shared .store across pages deploys via muten bundle, not the static muten build).

By design (the moat, not a bug): muten is declarative + bounded. The list toolkit (patch · sort · the aggregates · each...where) gives the common list jobs without exposing raw map/reduce - anything past that (an arbitrary transform) is a use JS function, and a real framework widget is a tier 2/3 escape. The ceiling is what keeps it small and analyzable; closing it would just make another general-purpose framework.

Limitations / not yet

These are honest gaps found during stress-testing. They are known and tracked; none are design mistakes, just things not built yet.

Build tooling

  • muten build (CLI SSG) inlines the theme + project styles.css, SSRs your store/query data and bundles use functions and npm components per page, but a static export can't persist store state across full-page navigations. Use muten bundle for a stateful production app.

Language features not yet available

  • query x live (WebSocket) requires the server to send an id per row for keyed diffing; without it, reconciliation falls back to full re-render.
  • An Icon name from data must be one the page spells out (a choice of names, or a field of a list written in the page): its SVGs are inlined at build. A name from the server is an error; an icon whose URL is in your data is an Image.

DataTable

  • Renders raw cell values only; no per-column formatting yet. For formatted cells, use each with a Part.

Form

  • Auto-generates one input per entity field; no conditional fields.
  • Enum fields cannot be marked required.
  • Field types: text, number, email, bool, enum, date, password, textarea. Anything else (url/tel/file) is flagged unknown-field-type - drop that field to a Custom.
  • Form renders all entity fields; there is no way to exclude a subset without a Custom.

Custom inputs

  • A Custom receives a snapshot of state at mount by default; for reactivity, its mount returns an updater function that muten re-runs whenever the bound @ state changes.

Composition

  • slot composes muten primitives inside reusable parts, the shell and layouts (a Container/Presentational split). A layout name { … slot … } in app.muten is chrome for a group of routes ("/x" -> page in name): mounted once, kept across them, and it may call parts. There is no slot inside a Custom (it's vanilla JS, outside muten's type system) - compose with a part, or do DOM composition in the Custom.