diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index aacc42f..8ff757a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -64,3 +64,61 @@ jobs:
# Keep engine failures isolated so a browser-specific regression is immediately visible.
- name: Run rendered browser matrix
run: npm run test:demo:${{ matrix.browser }}
+
+ release-preflight:
+ name: PR-safe ecosystem release preflight
+ if: github.event_name == 'pull_request'
+ needs: validate
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - name: Check out Layout candidate
+ uses: actions/checkout@v4
+
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ cache: npm
+
+ - name: Install candidate dependencies
+ run: npm ci
+
+ - name: Resolve immutable UI release fixture
+ id: ui_fixture
+ run: node scripts/release-fixture-contract.mjs --write-github-outputs
+
+ - name: Check out reviewed UI release fixture
+ uses: actions/checkout@v4
+ with:
+ repository: ${{ steps.ui_fixture.outputs.ui_repository }}
+ ref: ${{ steps.ui_fixture.outputs.ui_revision }}
+ path: _ecosystem/ui-style-kit-css
+
+ - name: Install UI fixture dependencies
+ working-directory: _ecosystem/ui-style-kit-css
+ run: npm ci
+
+ - name: Resolve immutable Interactive companion
+ id: ecosystem_sources
+ working-directory: _ecosystem/ui-style-kit-css
+ run: node scripts/write-ecosystem-workflow-outputs.mjs
+
+ - name: Check out reviewed Interactive companion
+ uses: actions/checkout@v4
+ with:
+ repository: ${{ steps.ecosystem_sources.outputs.interactive_repository }}
+ ref: ${{ steps.ecosystem_sources.outputs.interactive_revision }}
+ path: _ecosystem/Interactive-Surface-CSS
+
+ - name: Install Interactive companion dependencies
+ working-directory: _ecosystem/Interactive-Surface-CSS
+ run: npm ci
+
+ - name: Install clean-consumer Chromium
+ working-directory: _ecosystem/ui-style-kit-css
+ run: npx playwright install --with-deps chromium
+
+ # Candidate override is an npm-pack tarball; the other two sources stay at reviewed immutable commits.
+ - name: Run read-only release preflight
+ run: npm run release:preflight -- --fixture-root $GITHUB_WORKSPACE/_ecosystem/ui-style-kit-css --interactive-repo $GITHUB_WORKSPACE/_ecosystem/Interactive-Surface-CSS --interactive-docs-repo $GITHUB_WORKSPACE/_ecosystem/Interactive-Surface-CSS --layout-docs-repo $GITHUB_WORKSPACE
diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml
index e84a2b9..2d8d95d 100644
--- a/.github/workflows/npm-publish.yml
+++ b/.github/workflows/npm-publish.yml
@@ -6,7 +6,7 @@ on:
workflow_dispatch:
inputs:
release_tag:
- description: "Release tag to publish, for example v3.0.0"
+ description: "Release tag to publish, for example v3.0.1"
required: true
type: string
@@ -79,10 +79,60 @@ jobs:
- name: Install Playwright browsers
run: npx --no-install playwright install --with-deps chromium firefox webkit
- - name: Verify release package
- run: npm run release:verify
+ # These checks are deliberately local-only. release:verify includes the
+ # ecosystem preflight, which cannot run until immutable fixtures are staged.
+ - name: Run non-ecosystem release checks
+ run: npm run check:full
+
+ - name: Audit release dependencies
+ run: npm audit --audit-level=moderate
+
+ - name: Inspect package tarball
+ run: npm run pack:dry-run
+
+ - name: Exercise publish packaging without lifecycle scripts
+ run: npm run publish:dry-run
+
+ - name: Resolve immutable UI release fixture
+ id: ui_fixture
+ run: node scripts/release-fixture-contract.mjs --write-github-outputs
+
+ - name: Check out reviewed UI release fixture
+ uses: actions/checkout@v4
+ with:
+ repository: ${{ steps.ui_fixture.outputs.ui_repository }}
+ ref: ${{ steps.ui_fixture.outputs.ui_revision }}
+ path: _ecosystem/ui-style-kit-css
+
+ - name: Install UI fixture dependencies
+ working-directory: _ecosystem/ui-style-kit-css
+ run: npm ci
+
+ - name: Resolve immutable Interactive companion
+ id: ecosystem_sources
+ working-directory: _ecosystem/ui-style-kit-css
+ run: node scripts/write-ecosystem-workflow-outputs.mjs
+
+ - name: Check out reviewed Interactive companion
+ uses: actions/checkout@v4
+ with:
+ repository: ${{ steps.ecosystem_sources.outputs.interactive_repository }}
+ ref: ${{ steps.ecosystem_sources.outputs.interactive_revision }}
+ path: _ecosystem/Interactive-Surface-CSS
+
+ - name: Install Interactive companion dependencies
+ working-directory: _ecosystem/Interactive-Surface-CSS
+ run: npm ci
+
+ - name: Install clean-consumer Chromium
+ working-directory: _ecosystem/ui-style-kit-css
+ run: npx playwright install --with-deps chromium
+
+ # Release mutation remains downstream of the same read-only gate used by pull requests.
+ - name: Run ecosystem release preflight
+ run: npm run release:preflight -- --fixture-root $GITHUB_WORKSPACE/_ecosystem/ui-style-kit-css --interactive-repo $GITHUB_WORKSPACE/_ecosystem/Interactive-Surface-CSS --interactive-docs-repo $GITHUB_WORKSPACE/_ecosystem/Interactive-Surface-CSS --layout-docs-repo $GITHUB_WORKSPACE
- name: Publish package to npm
- run: npm publish --access public --provenance
+ run: npm publish --access public --provenance --ignore-scripts
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f50fadb..fb5bf96 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,21 @@
All notable changes to `layout-style-css` are documented here. This project follows semantic versioning.
+## [3.0.1] - 2026-08-09
+
+### Changed
+
+- Corrected the GitHub Pages homepage used by npm, kept readable package defaults alongside explicit minified CDN metadata, and included the maintained packaged documentation.
+- Synchronized the package, lockfile, public manifest, demo metadata, Pages sitemap, release guidance, and current ecosystem contract while preserving the complete 13-export API and existing CSS behavior.
+
+### Security
+
+- Retained the audited dependency overrides and secure release tooling used by the protected publication workflow without adding runtime or peer dependencies.
+
+### Tests
+
+- Added version-sensitive package, manifest, layout, documentation, demo, Pages, release, packed-export, clean-install, and ecosystem checks for the patch candidate.
+
## [3.0.0] - 2026-07-29
### Breaking
diff --git a/README.md b/README.md
index e533a72..3f53a47 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Zero-configuration, dependency-free structural CSS that responds to the width and height a layout actually receives.
-`layout-style-css@3.0.0` is a clean-break release. It provides intrinsic wrappers, composition primitives, seven semantic recipes, and sixteen spatial personalities. Layout owns structure; UI Style Kit owns paint; Interactive Surface owns interaction styling.
+`layout-style-css@3.0.1` is the current v3 patch release and preserves the clean-break structural contract introduced in 3.0.0. It provides intrinsic wrappers, composition primitives, seven semantic recipes, and sixteen spatial personalities. Layout owns structure; UI Style Kit owns paint; Interactive Surface owns interaction styling.
## Requirements
@@ -13,7 +13,7 @@ Zero-configuration, dependency-free structural CSS that responds to the width an
## Install
```bash
-npm install layout-style-css@3.0.0
+npm install layout-style-css@3.0.1
```
Most applications need one import:
@@ -24,6 +24,8 @@ import "layout-style-css";
The full bundle includes the core modules and all personality profiles. No breakpoint configuration, wrapper, JavaScript, or companion library is required.
+Package and bundler defaults (`main`, `style`, and the root export) resolve to the readable `dist/layout-style-css.css` bundle for straightforward inspection and debugging. The `unpkg` and `jsdelivr` convenience fields remain minified, and `layout-style-css/min.css` is the explicit minified package import.
+
## Zero-Configuration Start
Use `.ly-root` as the responsive boundary and keep the mobile DOM order authoritative:
@@ -151,6 +153,7 @@ Each personality is a token/topology profile consumed by the shared recipe engin
| `layout-style-css/utilities.css` | Small structural utility set |
| `layout-style-css/personalities.css` | All sixteen profiles |
| `layout-style-css/personalities/minimal-saas.css` | One profile; substitute any public personality name |
+| `layout-style-css/personalities.json` | Public layout-to-visual pairing recommendations |
| `layout-style-css/package.json` | Package metadata |
The cascade order is `ly.reset`, `ly.tokens`, `ly.wrappers`, `ly.primitives`, `ly.recipes`, `ly.utilities`, and `ly.personalities`.
@@ -168,11 +171,13 @@ import "layout-style-css";
This order lets UI Style Kit establish paint and theme roles, Interactive Surface add interaction states, and Layout apply structure.
+`data-ly-layout`, `data-ui`, `data-theme`, and `data-mode` are independently selectable. See [Layout Styles](docs/wiki/Layout-Styles.md#visual-pairing-guidance) for the full recommendation matrix; pairings are never dependencies.
+
## CDN
```html
-
-
+
+
```
## Clean-Break Migration
diff --git a/demo/demo.js b/demo/demo.js
index 7cf42be..4f3d3a4 100644
--- a/demo/demo.js
+++ b/demo/demo.js
@@ -1,5 +1,6 @@
const UI_STYLE_KIT_VERSION = "2.1.0";
const UI_STYLE_KIT_MANIFEST_URL = `https://unpkg.com/ui-style-kit-css@${UI_STYLE_KIT_VERSION}/manifest.json`;
+const PERSONALITY_METADATA_URL = document.querySelector("script[data-personalities-url]")?.dataset.personalitiesUrl;
const UI_STYLE_KIT_MANIFEST_FALLBACK = Object.freeze({
version: UI_STYLE_KIT_VERSION,
presets: Object.freeze([
@@ -77,6 +78,79 @@ async function loadUiStyleKitManifest() {
}
const UI_STYLE_KIT_MANIFEST = await loadUiStyleKitManifest();
+function normalizePersonalityMetadata(metadata) {
+ const personalities = Array.isArray(metadata?.personalities) ? metadata.personalities : [];
+ const validCompatibility = new Set(["native", "any", "recommended"]);
+ const normalized = personalities.map((personality) => ({
+ id: String(personality?.id ?? ""),
+ label: String(personality?.label ?? personality?.id ?? ""),
+ visualCompatibility: String(personality?.visualCompatibility ?? ""),
+ recommendedVisualPresets: Array.isArray(personality?.recommendedVisualPresets)
+ ? personality.recommendedVisualPresets.map(String)
+ : []
+ }));
+
+ if (
+ metadata?.schemaVersion !== 1 ||
+ normalized.length === 0 ||
+ normalized.some(({ id, label, visualCompatibility }) =>
+ !id || !label || !validCompatibility.has(visualCompatibility)
+ )
+ ) {
+ throw new Error("Layout personality metadata is missing a valid public pairing contract.");
+ }
+
+ return Object.freeze({
+ schemaVersion: metadata.schemaVersion,
+ personalities: Object.freeze(normalized.map((personality) => Object.freeze(personality)))
+ });
+}
+
+function minimalPersonalityFallback() {
+ return Object.freeze({
+ schemaVersion: 1,
+ personalities: Object.freeze([
+ Object.freeze({
+ id: "minimal-saas",
+ label: "Minimal SaaS",
+ visualCompatibility: "any",
+ recommendedVisualPresets: []
+ })
+ ])
+ });
+}
+
+async function loadPersonalityMetadata() {
+ try {
+ if (!PERSONALITY_METADATA_URL) {
+ throw new Error("The demo requires a local layout personality metadata URL.");
+ }
+
+ const response = await fetch(PERSONALITY_METADATA_URL, { cache: "force-cache" });
+
+ if (!response.ok) {
+ throw new Error(`Layout personality metadata request failed with HTTP ${response.status}.`);
+ }
+
+ return { metadata: normalizePersonalityMetadata(await response.json()), status: "" };
+ } catch (error) {
+ try {
+ return {
+ metadata: normalizePersonalityMetadata(window.LAYOUT_STYLE_PERSONALITY_METADATA),
+ status: "Layout pairing metadata is unavailable; using packaged fallback."
+ };
+ } catch {
+ console.error("Layout personality metadata and packaged fallback are unavailable.", error);
+ return {
+ metadata: minimalPersonalityFallback(),
+ status: "Layout pairing metadata is unavailable; Minimal SaaS remains available."
+ };
+ }
+ }
+}
+
+const PERSONALITY_METADATA_LOAD = await loadPersonalityMetadata();
+const PERSONALITY_METADATA = PERSONALITY_METADATA_LOAD.metadata;
const ALLOWLISTS = Object.freeze({
device: Object.freeze([
"custom",
@@ -97,24 +171,7 @@ const ALLOWLISTS = Object.freeze({
"gallery",
"card-grid"
]),
- personality: Object.freeze([
- "minimal-saas",
- "bauhaus",
- "tactile",
- "cyberpunk",
- "f-pattern",
- "brutalism",
- "neumorphism",
- "y2k",
- "retro-glass",
- "z-pattern",
- "retrofuturism",
- "mondrian",
- "synthwave",
- "bento",
- "maximalist",
- "split-screen"
- ]),
+ personality: Object.freeze(PERSONALITY_METADATA.personalities.map(({ id }) => id)),
container: Object.freeze([
"auto",
"20rem",
@@ -240,6 +297,7 @@ const recipePreview = document.querySelector("#recipePreview");
const importsSnippet = document.querySelector("#importsSnippet");
const markupSnippet = document.querySelector("#markupSnippet");
const copyStatus = document.querySelector("#copyStatus");
+const personalityMetadataStatus = document.querySelector("#personalityMetadataStatus");
const ecosystemStatus = document.querySelector("#ecosystemStatus");
const containerReadout = document.querySelector("#containerReadout");
const topologyReadout = document.querySelector("#topologyReadout");
@@ -255,6 +313,11 @@ const stateToggle = document.querySelector("#stateToggle");
const mobileControlsQuery = window.matchMedia("(max-width: 63.999rem)");
body.dataset.uiManifestVersion = UI_STYLE_KIT_MANIFEST.version;
+body.dataset.personalityMetadataVersion = String(PERSONALITY_METADATA.schemaVersion);
+syncPersonalityMetadataSelectOptions();
+if (personalityMetadataStatus) {
+ personalityMetadataStatus.textContent = PERSONALITY_METADATA_LOAD.status;
+}
syncUiManifestSelectOptions();
let state = readStateFromQuery();
@@ -439,6 +502,25 @@ function syncUiManifestSelectOptions() {
}
}
+function syncPersonalityMetadataSelectOptions() {
+ const select = controls.personality;
+
+ if (!select) {
+ return;
+ }
+
+ select.replaceChildren(
+ ...PERSONALITY_METADATA.personalities.map(({ id, label, visualCompatibility }) => {
+ const option = document.createElement("option");
+ option.value = id;
+ option.textContent = label;
+ option.dataset.visualCompatibility = visualCompatibility;
+ return option;
+ })
+ );
+ select.setAttribute("aria-busy", "false");
+}
+
function syncUiKitClasses() {
const prefix = UI_CLASS_PREFIXES[state.ui];
diff --git a/demo/index.html b/demo/index.html
index 3b0bf72..cad59f9 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -6,7 +6,7 @@
-
+
@@ -47,7 +47,7 @@
-
+
@@ -71,27 +71,28 @@
-
-
+
+
-
+
+
Skip to the layout lab
@@ -161,25 +162,11 @@ Layout controls
+