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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
58 changes: 54 additions & 4 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 }}
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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`.
Expand All @@ -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
<link rel="stylesheet" href="https://unpkg.com/layout-style-css@3.0.0/dist/layout-style-css.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/layout-style-css@3.0.0/dist/layout-style-css.min.css">
<link rel="stylesheet" href="https://unpkg.com/layout-style-css@3.0.1/dist/layout-style-css.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/layout-style-css@3.0.1/dist/layout-style-css.min.css">
```

## Clean-Break Migration
Expand Down
118 changes: 100 additions & 18 deletions demo/demo.js
Original file line number Diff line number Diff line change
@@ -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([
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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");
Expand All @@ -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();
Expand Down Expand Up @@ -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];

Expand Down
Loading
Loading