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

+