From 03a22bb5092163d467dbcda286e3f73ade57323e Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Mon, 8 Jun 2026 08:19:51 +0000 Subject: [PATCH 1/3] chore: remove integration-specific references from framework The WordPress/Bricks/Gutenberg plugin now lives in its own repo. Strip leftover plugin coupling from the framework so it stays plugin-agnostic: - drop stale plugins/SLASHED-for-WP build-artifact paths from .gitignore - genericize WP/page-builder comments in reset.css and base.css - remove the 'WP plugin CSS generator' note from tokens.css - replace Bricks/WooCommerce/Gravity Forms examples in overrides-example with generic third-party wording Co-authored-by: Jack Granatowski --- .gitignore | 5 ----- core/base.css | 4 ++-- core/reset.css | 8 ++++---- core/tokens.css | 3 +-- optional/overrides-example.css | 12 ++++++------ 5 files changed, 13 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index 1757f19e..56c1e434 100644 --- a/.gitignore +++ b/.gitignore @@ -6,11 +6,6 @@ playwright-report/ .agents/ .claude/ -# Built source maps are large and not needed in shipped releases. -# Re-build locally via `npm run build` in admin-app/ to regenerate. -plugins/SLASHED-for-WP/integrations/bricks/assets/admin-app/*.map -plugins/SLASHED-for-WP/integrations/bricks/assets/editor-app/*.map - # Semantic review artifacts (sub-agent output, not source) semantic-review/ diff --git a/core/base.css b/core/base.css index 3709d0ec..dc991d06 100644 --- a/core/base.css +++ b/core/base.css @@ -143,8 +143,8 @@ /* Media: block display prevents the UA inline baseline gap and caps width to the container. Intentionally NOT in reset.css — - third-party UI (WP admin bar, builder chrome) uses inline SVG - icons that must remain inline. */ + third-party UI (admin toolbars, embedded widget chrome) uses + inline SVG icons that must remain inline. */ img, picture, video, canvas { display: block; max-inline-size: 100%; diff --git a/core/reset.css b/core/reset.css index ae503234..f69e3765 100644 --- a/core/reset.css +++ b/core/reset.css @@ -5,9 +5,9 @@ sets sensible modern baselines. Deliberately does NOT zero global margins/padding, strip list markers, or change form-element font inheritance — those are component-level concerns. This keeps the - reset composable with any third-party UI on the same page (WP admin - bar, page-builder chrome, cookie banners, etc.) without element- - specific exclusions. + reset composable with any third-party UI on the same page (admin + toolbars, embedded widget chrome, cookie banners, etc.) without + element-specific exclusions. A heavier "full reset" (zeroing all margins/padding, stripping list markers, etc.) is intentionally NOT shipped — it would conflict with @@ -21,7 +21,7 @@ /* Box-sizing — near-universal baseline, safe to set globally. Does not affect layout of third-party UI that explicitly - overrides it; UA agents and most WP/builder stylesheets do not + overrides it; UA agents and most third-party stylesheets do not set box-sizing, so this wins by default but harmlessly. */ *, *::before, *::after { box-sizing: border-box; diff --git a/core/tokens.css b/core/tokens.css index 4bc20a94..ab71cc72 100644 --- a/core/tokens.css +++ b/core/tokens.css @@ -1036,8 +1036,7 @@ same range used by the fluid type/space scales. Override either endpoint to retune; set both equal for a fixed height. The slope (0.0222…) matches the default 3.5rem→5rem delta; clamp() keeps the - result bounded to the endpoints even when they are overridden. - This mirrors the clamp the WP plugin's CSS generator emits. */ + result bounded to the endpoints even when they are overridden. */ --sf-header-height-mobile: 3.5rem; --sf-header-height-desktop: 5rem; --sf-header-height: clamp( diff --git a/optional/overrides-example.css b/optional/overrides-example.css index 01e7bed2..f389ca09 100644 --- a/optional/overrides-example.css +++ b/optional/overrides-example.css @@ -115,16 +115,16 @@ } */ /* =========================================================== - 5. THIRD-PARTY INTEGRATION OVERRIDES + 5. THIRD-PARTY CSS OVERRIDES =========================================================== - Third-party plugins (Bricks, WooCommerce, Gravity Forms, etc.) - often add CSS outside any @layer, which puts them in the - implicit layer — above all SLASHED layers. If a plugin's styles + Third-party code (page builders, e-commerce or form tools, etc.) + often adds CSS outside any @layer, which puts it in the + implicit layer — above all SLASHED layers. If those styles conflict with SLASHED, override them here rather than increasing specificity or using !important. */ - /* Example: reset a WooCommerce button that picks up wrong colours */ - /* .woocommerce button.button { + /* Example: reset a third-party button that picks up wrong colours */ + /* .third-party-widget button { background-color: var(--sf-color-action); color: var(--sf-color-text--on-action); } */ From f6b1eb343bb3480fd7ab1c882ad114389e67aa13 Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Mon, 8 Jun 2026 08:19:57 +0000 Subject: [PATCH 2/3] fix(release): correct version drift and make pipeline self-syncing package.json on main was never bumped past 0.5.21, so build-time bundle headers were stamped with the wrong version. version-sync.yml could never run: it triggered on the release event, but releases created with the built-in GITHUB_TOKEN do not emit events. package-lock had also drifted to 0.6.0. - set package.json, package-lock.json and roadmap to current 0.5.23 - fold the main-sync step into release.yml on the tag-push trigger so it always runs, and drop [skip ci] so publish-dist rebuilds the dist branch - delete the dead version-sync.yml workflow Co-authored-by: Jack Granatowski --- .github/workflows/publish-dist.yml | 6 ++- .github/workflows/release.yml | 64 +++++++++++++++++++++++++ .github/workflows/version-sync.yml | 76 ------------------------------ docs/roadmap.md | 2 +- package-lock.json | 4 +- package.json | 2 +- 6 files changed, 72 insertions(+), 82 deletions(-) delete mode 100644 .github/workflows/version-sync.yml diff --git a/.github/workflows/publish-dist.yml b/.github/workflows/publish-dist.yml index f8e86638..9ee19cdf 100644 --- a/.github/workflows/publish-dist.yml +++ b/.github/workflows/publish-dist.yml @@ -16,8 +16,10 @@ on: branches: [main] workflow_dispatch: -# Skip bot version-sync commits to avoid rebuilding identical dist artifacts. -# The [skip ci] tag in the commit message signals this intent. +# Skip only commits that explicitly opt out via [skip ci] in the message. +# The version-sync commit pushed by release.yml deliberately does NOT carry +# that tag, so a version bump always rebuilds the dist branch with the correct +# header. # If several merges land in quick succession, cancel any in-flight publish # and rerun against the newest commit only. Prevents racing force-pushes. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fdd77d0a..5453898d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,9 @@ on: required: true type: string +permissions: + contents: write + jobs: release: name: Publish GitHub Release @@ -104,3 +107,64 @@ jobs: dist/slashed.full.flat.min.css dist/slashed.full.flat.min.css.map fail_on_unmatched_files: true + + # Aligns main's committed version artifacts (package.json, package-lock.json, + # docs/roadmap.md) to the released tag, then pushes back to main. + # + # Why this lives here and not in a separate `on: release` workflow: + # a GitHub Release created by release.yml uses the built-in GITHUB_TOKEN, and + # GitHub does NOT emit `release` (or any) events for actions taken by that + # token — so an `on: release` workflow would never fire. The tag push that + # triggers THIS workflow is a real event, so syncing here always runs. + # + # The sync commit intentionally OMITS [skip ci]: changing the version must + # re-trigger publish-dist.yml so the `dist` branch bundles are rebuilt with + # the correct version header. The push targets main (a branch), which does + # not re-trigger this tag-scoped workflow, so there is no loop. + sync-main: + name: Sync version artifacts to main + needs: release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout main + uses: actions/checkout@v6 + with: + ref: main + fetch-depth: 1 + persist-credentials: false + - uses: actions/setup-node@v6 + with: + node-version: 22 + - name: Determine version + id: ver + env: + DISPATCH_VERSION: ${{ inputs.version }} + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + TAG="${DISPATCH_VERSION}" + else + TAG="${GITHUB_REF_NAME}" + fi + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" + - name: Align version artifacts on main + run: | + npm version "${{ steps.ver.outputs.version }}" --no-git-tag-version --allow-same-version + node scripts/version-sync.js + - name: Commit and push if anything changed + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add package.json package-lock.json docs/roadmap.md + if git diff --cached --quiet; then + echo "Nothing to commit — version artifacts on main already match ${{ steps.ver.outputs.tag }}." + else + git commit -m "chore: sync version artifacts to ${{ steps.ver.outputs.tag }}" + git push \ + "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \ + HEAD:main + fi diff --git a/.github/workflows/version-sync.yml b/.github/workflows/version-sync.yml deleted file mode 100644 index 417bfa18..00000000 --- a/.github/workflows/version-sync.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Sync version artifacts - -# Triggered whenever a GitHub release or pre-release is published. -# Checks out main, aligns non-JS version references (docs/roadmap.md) and -# package.json to the release tag, then commits any changes back to main. -# -# CSS bundles are NOT committed to the main branch — they live only on the -# dedicated `dist` branch (published by publish-dist.yml on every push to -# main) and in GitHub Release assets. -# -# Full release pipeline: -# 1. Developer runs `npm run release` locally → bumps package.json, runs -# version-sync + build, commits chore(release): vX.Y.Z, pushes tag. -# 2. release.yml fires on the tag → uploads dist assets to the GitHub release. -# 3. This workflow fires on the release.published event → re-aligns version -# references on main in case the release was cut outside `npm run release`. - -on: - release: - types: [released, prereleased] - -permissions: - contents: write - -jobs: - sync: - name: Sync version artifacts to ${{ github.event.release.tag_name }} - runs-on: ubuntu-latest - steps: - - name: Checkout main - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: main - fetch-depth: 1 - persist-credentials: false - - - name: Setup Node - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: 22 - cache: npm - - - name: Install dependencies - run: npm ci - - - name: Extract version from release tag - id: ver - run: | - TAG="${{ github.event.release.tag_name }}" - VERSION="${TAG#v}" - echo "tag=$TAG" >> "$GITHUB_OUTPUT" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - - - name: Align package.json to release tag - # npm version --no-git-tag-version only modifies package.json / package-lock.json; - # it does not create a git tag or commit. - run: npm version ${{ steps.ver.outputs.version }} --no-git-tag-version --allow-same-version - - - name: Sync non-JS version references - run: node scripts/version-sync.js - - - name: Commit and push if anything changed - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add package.json package-lock.json docs/roadmap.md - if git diff --cached --quiet; then - echo "Nothing to commit — all version artifacts already up to date." - else - git commit -m "chore: sync version artifacts to ${{ steps.ver.outputs.tag }} [skip ci]" - git push \ - "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \ - main - fi diff --git a/docs/roadmap.md b/docs/roadmap.md index 4be83aab..ab80a985 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # SLASHED — Roadmap -Current version: **0.5.21** +Current version: **0.5.23** --- diff --git a/package-lock.json b/package-lock.json index 43c03de6..ed861d2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "slashed", - "version": "0.6.0", + "version": "0.5.23", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "slashed", - "version": "0.6.0", + "version": "0.5.23", "devDependencies": { "@axe-core/playwright": "^4.11.3", "@commitlint/cli": "^21.0.1", diff --git a/package.json b/package.json index 264fe6c9..b179481b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "slashed", "type": "module", - "version": "0.5.21", + "version": "0.5.23", "description": "CSS framework", "style": "dist/slashed.full.css", "main": "dist/slashed.full.css", From ca7ae3ef7ee3d6bb37d732c4a1faff19b2e59bb6 Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Mon, 8 Jun 2026 08:33:51 +0000 Subject: [PATCH 3/3] feat(release): tag-authoritative stamping + drift guards Harden the version pipeline against mis-stamped artifacts: - bundle.js derives the header version from the git tag when building at a tag (GITHUB_REF / git describe --exact-match), falling back to package.json. A tagged-source build is now correct even if that commit's package.json lags. - release.yml fails the build if any unminified dist header != the tag being released, preventing silent mis-stamping. - add scripts/check-version-sync.js (npm run check:version) asserting package-lock.json and docs/roadmap.md match package.json; wired into CI. --- .github/workflows/ci.yml | 1 + .github/workflows/release.yml | 22 ++++++++++++++ package.json | 1 + scripts/bundle.js | 38 ++++++++++++++++++++++-- scripts/check-version-sync.js | 56 +++++++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 scripts/check-version-sync.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11c1180d..a949f581 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,7 @@ jobs: - run: npm ci - run: npm run audit:check - run: node scripts/check-artifacts.js --check + - run: node scripts/check-version-sync.js test: name: Regression tests diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5453898d..2ea1ea22 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,6 +50,28 @@ jobs: else echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" fi + - name: Verify dist headers match the release tag + # Guard against silent mis-stamping: every built bundle header must read + # the version being released. Fails the release if any header drifts. + run: | + EXPECTED="${{ steps.version.outputs.tag }}" + EXPECTED="${EXPECTED#v}" + fail=0 + for f in dist/*.css; do + # Minified bundles drop the non-license comment, so only the + # unminified bundles carry the stamped header — check those. + case "$f" in *.min.css) continue;; esac + header="$(head -1 "$f")" + if ! printf '%s' "$header" | grep -q "SLASHED v${EXPECTED} "; then + echo "::error file=${f}::expected 'SLASHED v${EXPECTED}', got: ${header}" + fail=1 + fi + done + if [ "$fail" -ne 0 ]; then + echo "Built dist headers do not match release tag v${EXPECTED}." >&2 + exit 1 + fi + echo "All dist headers correctly stamped v${EXPECTED}." - name: Extract changelog for this version id: changelog run: | diff --git a/package.json b/package.json index b179481b..c2e6bde0 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "docs:tokens": "node scripts/gen-token-reference.js", "docs:classes": "node scripts/gen-class-reference.js", "version-sync": "node scripts/version-sync.js", + "check:version": "node scripts/check-version-sync.js", "lint:css": "stylelint \"**/*.css\"", "lint:css:fix": "stylelint \"**/*.css\" --fix", "pretest": "npm run build && node --test tests/tier1-p1-srgb-validity.test.js tests/tier1-p2-coverage.test.js tests/tier1-p3-ordering.test.js tests/tier1-p7-oldengine.test.js tests/tier1-p10-contrast.test.js tests/tier1-p11-overrides.test.js", diff --git a/scripts/bundle.js b/scripts/bundle.js index bb4e54c6..7c00a131 100644 --- a/scripts/bundle.js +++ b/scripts/bundle.js @@ -3,12 +3,47 @@ import fs from 'node:fs'; import path from 'node:path'; import zlib from 'node:zlib'; +import { execSync } from 'node:child_process'; import { createRequire } from 'node:module'; const _require = createRequire(import.meta.url); const ROOT = path.resolve(import.meta.dirname, '..'); const CONFIG_PATH = path.join(ROOT, 'bundle.config.json'); +// Version stamped into every bundle header. Tag-authoritative: when building +// at a release tag — a CI tag build (GITHUB_REF=refs/tags/vX.Y.Z) or a local +// checkout sitting exactly on a tag — the tag wins. This guarantees Release +// assets and tagged-source builds are stamped correctly even if package.json +// on that commit lags behind (the historical cause of mis-stamped artifacts). +// Ordinary branch builds fall back to package.json. +function resolveVersion() { + const pkgVersion = JSON.parse( + fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'), + ).version; + + const ref = process.env.GITHUB_REF || ''; + const refName = process.env.GITHUB_REF_NAME || ''; + if (ref.startsWith('refs/tags/') && /^v?\d+\.\d+\.\d+/.test(refName)) { + return refName.replace(/^v/, ''); + } + + try { + const tag = execSync('git describe --tags --exact-match', { + cwd: ROOT, + stdio: ['ignore', 'pipe', 'ignore'], + }) + .toString() + .trim(); + if (/^v?\d+\.\d+\.\d+/.test(tag)) return tag.replace(/^v/, ''); + } catch { + /* not building at a tag — fall back to package.json */ + } + + return pkgVersion; +} + +const VERSION = resolveVersion(); + // lightningcss is a maintainer-only dev dependency. If it's missing (e.g. a // consumer cloned without dev deps), skip minification rather than fail. let lightningcss = null; @@ -122,9 +157,8 @@ function stripLayerWrappers(content, fileLabel) { function buildOne({ files, output, flat = false }) { const outputPath = resolveInsideRoot(output); - const { version } = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')); const flatTag = flat ? ' (flat)' : ''; - const header = `/* SLASHED v${version} — ${path.basename(output)}${flatTag} */\n`; + const header = `/* SLASHED v${VERSION} — ${path.basename(output)}${flatTag} */\n`; const parts = files.map((file) => { const filePath = resolveInsideRoot(file); diff --git a/scripts/check-version-sync.js b/scripts/check-version-sync.js new file mode 100644 index 00000000..457d93e6 --- /dev/null +++ b/scripts/check-version-sync.js @@ -0,0 +1,56 @@ +#!/usr/bin/env node +// Fails if version references have drifted apart. Guards the class of bug where +// package.json, package-lock.json and docs/roadmap.md fall out of sync (e.g. a +// tag cut without `npm run release`, leaving build-time headers mis-stamped). +// +// Checks: +// 1. package-lock.json version (root and packages[""]) === package.json +// 2. docs/roadmap.md "Current version" === package.json +// +// Run locally with: node scripts/check-version-sync.js +// Wired into CI (.github/workflows/ci.yml). + +import fs from 'node:fs'; +import path from 'node:path'; + +const ROOT = path.resolve(import.meta.dirname, '..'); +const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8'); + +const errors = []; + +const pkg = JSON.parse(read('package.json')); +const version = pkg.version; + +// 1. package-lock.json must match package.json. +const lock = JSON.parse(read('package-lock.json')); +if (lock.version !== version) { + errors.push( + `package-lock.json version "${lock.version}" != package.json "${version}"`, + ); +} +const lockSelf = lock.packages?.['']?.version; +if (lockSelf !== undefined && lockSelf !== version) { + errors.push( + `package-lock.json packages[""].version "${lockSelf}" != package.json "${version}"`, + ); +} + +// 2. docs/roadmap.md "Current version" must match package.json. +const roadmap = read('docs/roadmap.md'); +const m = roadmap.match(/Current version:\s*\*\*([^*]+)\*\*/); +if (!m) { + errors.push('docs/roadmap.md: "Current version" line not found'); +} else if (m[1].trim() !== version) { + errors.push( + `docs/roadmap.md version "${m[1].trim()}" != package.json "${version}"`, + ); +} + +if (errors.length) { + console.error('version-sync check FAILED:'); + for (const e of errors) console.error(` - ${e}`); + console.error('\nRun `npm version --no-git-tag-version` + `npm run version-sync` to realign.'); + process.exit(1); +} + +console.log(`version-sync check OK — all references at ${version}.`);