Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/actions/setup-monorepo/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ runs:
with:
node-version: "24.13.0"

- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
key: size-baseline-${{ runner.os }}-${{ github.sha }}

- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
Expand Down
42 changes: 42 additions & 0 deletions .github/workflows/deploy-wrenfield.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Manual redeploy of wrenfield.excom.dev from `main`. Pushes to `main` deploy
# through the Release workflow (publish.yml), after the version bump, so the
# page always pins a Kit version that is on npm. Run this from `main` only:
# another ref can carry a Kit version that was never published.
#
# CLOUDFLARE_API_TOKEN needs Workers Scripts:Edit (account), plus Workers
# Routes:Edit and DNS:Edit on the excom.dev zone for the custom domain
# declared in packages/wrenfield/wrangler.jsonc.
name: Deploy Wrenfield

on:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
- uses: actions/checkout@v6

# No dependencies to install: staging copies files and reads the Kit
# version from packages/nucleus-kit/package.json.
- uses: actions/setup-node@v6
with:
node-version: "24.13.0"
- run: node packages/wrenfield/scripts/stage.mjs

# From the repo root like the docs deploy: the action installs wrangler
# with npm where it runs, which cannot read the package's `workspace:`
# dependencies. Config paths resolve relative to the config file.
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --config packages/wrenfield/wrangler.jsonc
8 changes: 4 additions & 4 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: "24.13.0"
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10

Expand All @@ -82,22 +82,22 @@ jobs:
run: pnpm dlx @cyclonedx/cdxgen -t pnpm --no-install-deps --no-babel --spec-version 1.6 -o bom.json

- name: Upload SBOM as Artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: sbom-cyclonedx.json
path: bom.json

- name: Grype Dependency Scanner (Report)
id: scan-package
uses: anchore/scan-action@40a61b52209e9d50e87917c5b901783d546b12d0
uses: anchore/scan-action@27805bf3b4e84b4a5c980df22ed233c00390a439 # v7.4.2
with:
sbom: bom.json
fail-build: false
severity-cutoff: critical
timeout-minutes: 10

- name: Upload Grype SARIF Results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: grype-scan-results.sarif
path: ${{ steps.scan-package.outputs.sarif }}
Expand Down
21 changes: 17 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
# 2. Build everything from the bumped tree, so package metas, CDN
# snippets, npm READMEs and the docs site carry the new versions.
# 3. Publish to npm (and the VS Code extension).
# 4. Deploy the docs site, on every push: a docs-only change has nothing
# to publish but still needs a deploy.
# 4. Deploy the docs site and Wrenfield, on every push: a docs-only change
# has nothing to publish but still needs a deploy. Wrenfield pins the Kit
# version just bumped, so it runs after the publish.
# 5. Push the bump commit last, so `main` only records versions that
# reached npm.
# Why one pipeline: a site deployed in parallel is built from the pre-bump
# commit and shows the old versions, and the bump commit never starts a run
# to correct it (`[skip ci]`; GITHUB_TOKEN pushes start no workflows).
# deploy-docs.yml is for manual redeploys only.
# deploy-docs.yml and deploy-wrenfield.yml are for manual redeploys only.
name: Release

on:
Expand Down Expand Up @@ -136,11 +137,23 @@ jobs:
path: size-baseline
key: size-baseline-${{ runner.os }}-${{ github.sha }}
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy packages/docs-site/dist --project-name=excom-docs
# Wrenfield (wrenfield.excom.dev), a Worker with static assets. The token
# also needs Workers Scripts:Edit (account), plus Workers Routes:Edit and
# DNS:Edit on the excom.dev zone for the custom domain. The stage step
# reads the bumped Kit version, which the publish above put on npm.
- name: Stage Wrenfield
run: node packages/wrenfield/scripts/stage.mjs
- name: Deploy Wrenfield to Cloudflare Workers
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --config packages/wrenfield/wrangler.jsonc

# 5. Push the bump, if one was committed (HEAD moved off the checked-out
# commit). Runs whenever npm publish succeeded, even if the extension or
Expand Down
48 changes: 27 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,22 @@ Your HTML __*is*__ the app! Drop-in custom elements that each have a single resp

## How it's different from existing UI solutions

- **Back to the future** Welcome back to building static HTML5 apps. A break from complex JavaScript apps that compile to HTML.
- **Little to no JavaScript** You no longer need to write JS for the vast majority of UI cases. You may still call-out to your own pure functions for complex cases.
- **Native++** It's real HTML. With a separate, synergistic guest: Quark.
- **No magic** No special frameworks, build processes, rendering wizardry, or "HTML-in-my-JS" / "JS-in-my-HTML" DSLs.
- **Progressively enhanced** Drop into existing static/server-side-rendered sites. Neutron and Quark can also be used independently.
- **Fully composable** Templates, templating, behavior, and custom logic are all decoupled & robust.
- **Reactive State Machine** A simple, reliable, declarative syntax for your business logic - Quark.
- **No reconciliation tax** No large memory copies of state/DOM to be rebuilt, diffed against the DOM, recompiled with every state change.
- **Lightweight** All Nucleus Kit elements + Quark + Neutron have a smaller footprint (just over ~50kb compressed) than some UI framework cores alone.
- **Back to the future** — Welcome back to building static HTML5 apps. A break from complex JavaScript apps that compile to HTML.
- **Little to no JavaScript** — You no longer need to write JS for the vast majority of UI cases. You may still call-out to your own pure functions for complex cases.
- **Native++** — It's real HTML. With a separate, synergistic guest: Quark.
- **No magic** — No special frameworks, build processes, rendering wizardry, or "HTML-in-my-JS" / "JS-in-my-HTML" DSLs.
- **Progressively enhanced** — Drop into existing static/server-side-rendered sites. Nucleus elements and Quark can also be used independently.
- **Fully composable** — Templates, templating, behavior, and custom logic are all decoupled & robust.
- **Reactive State Machine** — A simple, reliable, declarative syntax for your business logic - Quark.
- **No reconciliation tax** — No large memory copies of state/DOM to be rebuilt, diffed against the DOM, recompiled with every state change.
- **Lightweight** — All Nucleus elements + Quark + Neutron have a smaller footprint (just over ~50kb compressed) than some UI framework cores alone.

## What's in the stack

- **Nucleus Kit elements** A growing catalog of drop-in custom elements, including: drawers, tabs, tables, lazy views, forms, routing, passkeys, data fetching, and more.
- **Quark** Like CSS, for document mutation. Select elements, bind data, stamp lists, wire events, and drive state transitions with simple rules instead of imperative code.
- **Valence.css** Semantic, classless CSS that caters to both native and custom elements, with themes, light/dark schemes, and design tokens. Designed for easy drop-in. Optional.
- **Neutron** The small JS factory used to author the elements above. Reach for it only when the Nucleus Kit catalog lacks what you need. Optional.
- **Nucleus Kit elements** — A growing catalog of drop-in custom elements, including: drawers, tabs, tables, lazy views, forms, routing, passkeys, data fetching, and more.
- **Quark** — Like CSS, for document mutation. Select elements, bind data, stamp lists, wire events, and drive state transitions with simple rules instead of imperative code.
- **Valence.css** — Semantic, classless CSS that caters to both native and custom elements, with themes, light/dark schemes, and design tokens. Designed for easy drop-in. Optional.
- **Neutron** — The small JS factory used to author the elements above. Reach for it only when the Nucleus Kit catalog lacks what you need. Optional.

Every piece stands alone. Use one element on an existing site, or compose the whole stack into a full single-page app. `nucleus-kit` bundles it all behind one import; if you find you only use a handful of elements, install those packages à la carte instead (`@excom/content-drawer`, `@excom/quark-sheet`, …) and skip the rest.

Expand All @@ -49,19 +49,25 @@ Then `import "@excom/nucleus-kit"` and `@import "@excom/nucleus-kit/basic.css"`.

## Why teams pick it

- **Significantly less app code** This is possible for two primary reasons. First, because Nucleus Kit elements are fully composable, configurable, and controllable, they will likely be compatible with the desired experience of most applications that use them; there is a low likelihood you will need to build your own. Secondly, Quark enables the majority of customization without needing to invite JavaScript.
- **No components** There is no "component" concept in this architecture. This allows application pieces to be maximally reusable and composable, as there is no home to entrap logic with a tightly coupled view.
- **One source of truth** Live markup _is_ the primary application state, so an entire family of bugs ("the UI disagrees with the model") cannot exist.
- **Fully inspectable** Open devtools and the entire application is in front of you: every value, every binding, and every transition. The state is the document/DOM, so all is plainly transparent to see, alter, and debug in your inspector.
- **Accessible by default** Declarative & ARIA state is the state... not a mirror someone forgot to update.
- **Human and machine friendly** Inspect [the docs site](https://excom.dev/nucleus) to see its declarativeness. No more `<div>` soups bound to untraceable JavaScript. Custom elements make for a beautifully declarative document. A page that is legible, addressable, and serializable is an ideal target for code generation, AI-assisted editing, and confident human auditing. Tools reason about the screen's exact state instead of inferring a component tree. Likewise, writing and debugging UI code by hand has never felt simpler.
- **Declarative business behavior** The vast majority of your proprietary behaviors exist as simple configurations, rather than buried inside imperative spaghetti code.
- **Significantly less app code** — This is possible for two primary reasons. First, because Nucleus Kit elements are fully composable, configurable, and controllable, they will likely be compatible with the desired experience of most applications that use them; there is a low likelihood you will need to build your own. Secondly, Quark enables the majority of customization without needing to invite JavaScript.
- **No components** — There is no "component" concept in this architecture. This allows application pieces to be maximally reusable and composable, as there is no home to entrap logic with a tightly coupled view.
- **One source of truth** — Live markup _is_ the primary application state, so an entire family of bugs ("the UI disagrees with the model") cannot exist.
- **Fully inspectable** — Open devtools and the entire application is in front of you: every value, every binding, and every transition. The state is the document/DOM, so all is plainly transparent to see, alter, and debug in your inspector.
- **Accessible by default** — Declarative & ARIA state is the state... not a mirror someone forgot to update.
- **Human and machine friendly** — Inspect [the docs site](https://excom.dev/nucleus) to see its declarativeness. No more `<div>` soups bound to untraceable JavaScript. Custom elements make for a beautifully declarative document. A page that is legible, addressable, and serializable is an ideal target for code generation, AI-assisted editing, and confident human auditing. Tools reason about the screen's exact state instead of inferring a component tree. Likewise, writing and debugging UI code by hand has never felt simpler.
- **Declarative business behavior** — The vast majority of your proprietary behaviors exist as simple configurations, rather than buried inside imperative spaghetti code.

## Where it shines

Content-rich sites, complex data-driven business rules, progressive enhancement of static/server-rendered pages, embedded user experiences. See [Limitations](https://excom.dev/nucleus/docs/limitations) for the edges.

The Nucleus Stack also opens up new possibilities that were not easily served by any UI technology before: zero-build-tool UIs (e.g. on-the-fly generation), declarative & auditable target for LLM UI building, plain text assembly to rich UX (like a CMS), incremental upgrading of static/legacy SSR sites, resource-constrained web UIs (especially where scripting needs to be validated or limited, like an ATM), embedded UX (such as upgrading markdown with embedded functionality).
The Nucleus Stack also opens up new possibilities that were not easily served by any UI technology before:
- zero-build-tool UIs (e.g. on-the-fly generation)
- declarative & auditable target for LLM UI building
- programmatic assembly of rich UX (like a CMS)
- incremental upgrading of static/legacy SSR sites
- resource-constrained, secure, or sandboxed web UIs (especially where scripting needs to be validated or limited, like an ATM)
- embedded UX (such as upgrading markdown with embedded functionality)

## Dogfood is nutritious

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/abortable-element",
"comment": "Add `@excom/nucleus-test` and update test imports",
"type": "none"
}
],
"packageName": "@excom/abortable-element"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/content-carousel",
"comment": "Track mode hides every slide beyond the active slide's neighbours and no longer animates the first slide into place",
"type": "patch"
}
],
"packageName": "@excom/content-carousel"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/content-drawer",
"comment": "Add `@excom/nucleus-test` and update test imports",
"type": "none"
}
],
"packageName": "@excom/content-drawer"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/content-tabs",
"comment": "Add `@excom/nucleus-test` and update test imports",
"type": "none"
}
],
"packageName": "@excom/content-tabs"
}
10 changes: 10 additions & 0 deletions common/changes/@excom/data-table/test-rig_2026-10-01-tests.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/data-table",
"comment": "Add `@excom/nucleus-test` and update test imports",
"type": "none"
}
],
"packageName": "@excom/data-table"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/detect-browser",
"comment": "Add `@excom/nucleus-test` and update test imports",
"type": "none"
}
],
"packageName": "@excom/detect-browser"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/detect-features",
"comment": "Add `@excom/nucleus-test` and update test imports",
"type": "none"
}
],
"packageName": "@excom/detect-features"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/detect-media",
"comment": "Add `@excom/nucleus-test` and update test imports",
"type": "none"
}
],
"packageName": "@excom/detect-media"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/dialog-anchor",
"comment": "Add `@excom/nucleus-test` and update test imports",
"type": "none"
}
],
"packageName": "@excom/dialog-anchor"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/dismiss-watcher",
"comment": "Add `@excom/nucleus-test` and update test imports",
"type": "none"
}
],
"packageName": "@excom/dismiss-watcher"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/docs-site",
"comment": "Fix happy-dom iframe throw in live-app test and correct nav order assertion in site-nav test",
"type": "none"
}
],
"packageName": "@excom/docs-site"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/dom-observer",
"comment": "Add `@excom/nucleus-test` and update test imports",
"type": "none"
}
],
"packageName": "@excom/dom-observer"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/event-handler",
"comment": "Add `@excom/nucleus-test` and update test imports",
"type": "none"
}
],
"packageName": "@excom/event-handler"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/fetchable-element",
"comment": "Add `@excom/nucleus-test` and update test imports",
"type": "none"
}
],
"packageName": "@excom/fetchable-element"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/gesture-handler",
"comment": "Pan handlers cancel the page's cross-axis scroll on the first touch move",
"type": "patch"
}
],
"packageName": "@excom/gesture-handler"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/hash-object",
"comment": "Add `@excom/nucleus-test` and update test imports",
"type": "none"
}
],
"packageName": "@excom/hash-object"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@excom/heft-rig",
"comment": "Add the optional `excom.navGroup` package.json key (only `\"libraries\"` is accepted by `format-package-json`), carried into the docs-site `index.json` catalog so a package can move from its type's sidebar list into a named group",
"type": "minor"
}
],
"packageName": "@excom/heft-rig"
}
Loading
Loading