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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/publish-dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
86 changes: 86 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ on:
required: true
type: string

permissions:
contents: write

jobs:
release:
name: Publish GitHub Release
Expand Down Expand Up @@ -47,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: |
Expand Down Expand Up @@ -104,3 +129,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
76 changes: 0 additions & 76 deletions .github/workflows/version-sync.yml

This file was deleted.

5 changes: 0 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/

Expand Down
4 changes: 2 additions & 2 deletions core/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
Expand Down
8 changes: 4 additions & 4 deletions core/reset.css
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down
3 changes: 1 addition & 2 deletions core/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SLASHED β€” Roadmap

Current version: **0.5.21**
Current version: **0.5.23**

---

Expand Down
12 changes: 6 additions & 6 deletions optional/overrides-example.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
} */
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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",
Expand Down
38 changes: 36 additions & 2 deletions scripts/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Loading