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
7 changes: 4 additions & 3 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ These items are planned as part of the path to a stable v1.0 release.

- **Class documentation tooltips** — extract `docs/classes.md` data into a
`data/classes.json` (generated at build time). When a new "Show class hints"
toggle in admin settings is enabled, inject short descriptions into Bricks'
class manager on hover. Implemented via a `show_class_hints` setting in
`class-token-store.php` and a flag passed to the editor JS bundle.
toggle in admin settings is enabled, inject a small "?" info icon beside each
SLASHED class row's action icons in Bricks' class manager; hovering or focusing
that icon reveals a short description. Implemented via a `show_class_hints`
setting in `class-token-store.php` and a flag passed to the editor JS bundle.

- **Inventory stale-detection** — a weekly WP cron job checks the npm registry
for a newer framework version and surfaces a subtle dashboard widget (not a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@
Set mobile and desktop header heights in <code>rem</code>. When both are provided,
<code>--sf-header-height</code> becomes a fluid <code>clamp()</code> between them using
the viewport range configured in the Spacing tab. Set both to the same value for a
fixed height. Leave blank to use the framework default (<code>5rem</code>).
fixed height. Leave blank to use the framework defaults (<code>3.5rem</code> mobile,
<code>5rem</code> desktop) — shown as the input placeholders.
</p>
<div class="rows">
<div class="pair-row">
Expand All @@ -84,7 +85,7 @@
label="Mobile (rem)"
min={0}
step={0.25}
default={defaults.header_height_mobile ?? ''}
default={defaults.header_height_mobile || '3.5'}

@coderabbitai coderabbitai Bot Jun 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use nullish coalescing (??) instead of logical OR (||) for fallback defaults.

The || operator treats all falsy values (0, '', false, null, undefined) as triggers for the fallback, which could mask legitimate PHP defaults. If defaults.header_height_mobile or similar fields are explicitly set to 0 or an empty string in PHP, the operator will incorrectly fall back to the hardcoded '3.5' or '5' instead of using the actual PHP default.

Use the nullish coalescing operator (??) to only fall back when the value is strictly null or undefined.

🔧 Proposed fix
-          default={defaults.header_height_mobile || '3.5'}
+          default={defaults.header_height_mobile ?? '3.5'}

-          default={defaults.header_height_desktop || '5'}
+          default={defaults.header_height_desktop ?? '5'}

-          default={defaults.sticky_offset_mobile || '3.5'}
+          default={defaults.sticky_offset_mobile ?? '3.5'}

-          default={defaults.sticky_offset_desktop || '5'}
+          default={defaults.sticky_offset_desktop ?? '5'}

Also applies to: 98-98, 113-113, 123-123

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugins/SLASHED-for-WP/integrations/bricks/admin-app/src/components/LayoutsTab.svelte`
at line 88, Replace logical-OR fallbacks with nullish coalescing for the default
bindings so only null/undefined trigger the hardcoded fallback: change
expressions like defaults.header_height_mobile || '3.5' to use ??, and do the
same for the other affected bindings (the occurrences referencing
defaults.header_height_mobile, defaults.header_height_desktop and the other
header height/default fields at the other noted locations). Ensure each Svelte
input/default uses the ?? operator so legitimate falsy values (0 or empty
string) from defaults are preserved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping || here intentionally. The default prop is used only as the input placeholder, and the PHP defaults for these four fields are empty strings ('header_height_mobile' => '', etc. in class-token-defaults.php) — that empty value means "no override, fall back to the framework value". Switching to ?? would yield '' ?? '3.5''', which re-introduces the very missing-placeholder bug this change fixes. With ||, an empty PHP default correctly surfaces the framework value (3.5rem / 5rem) as the placeholder. meta.defaults is the read-only framework default map, so a legitimate 0 is never expected for these height fields.


Generated by Claude Code

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jackgranatowski, thanks for the clear explanation — that makes complete sense. Since class-token-defaults.php uses '' as the "no override" sentinel for these height fields, || is exactly right: it correctly promotes an empty-string PHP default to the visible framework placeholder (3.5 / 5), whereas ?? would pass the empty string through and silently swallow the placeholder. I'll withdraw the suggestion.


✏️ Learnings added
Learnt from: jackgranatowski
Repo: codeslash-dev/SLASHED PR: 229
File: plugins/SLASHED-for-WP/integrations/bricks/admin-app/src/components/LayoutsTab.svelte:88-88
Timestamp: 2026-06-05T00:31:10.366Z
Learning: In `plugins/SLASHED-for-WP/integrations/bricks/admin-app/src/components/LayoutsTab.svelte`, the `default` prop on `NumberField` for `header_height_mobile`, `header_height_desktop`, `sticky_offset_mobile`, and `sticky_offset_desktop` intentionally uses logical-OR (`||`) rather than nullish coalescing (`??`). The PHP defaults in `class-token-defaults.php` for these four fields are empty strings (`''`), which means "no override — show framework default as placeholder". `||` correctly promotes `''` to the hardcoded framework placeholder (`'3.5'` / `'5'`), whereas `??` would pass the empty string through unchanged. A legitimate value of `0` is never expected for these rem-based height fields.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: codeslash-dev/SLASHED PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-05-12T22:15:57.949Z
Learning: Applies to **/*.css : Reach for fluid tokens (space, text, leading with `clamp()`) first for responsive design, then container-aware primitives (`.grid`, `.stack`, `.cluster`), then breakpoint utilities as a last resort.

cssVar="--sf-header-height-mobile"
width="100px"
/>
Expand All @@ -94,7 +95,7 @@
label="Desktop (rem)"
min={0}
step={0.25}
default={defaults.header_height_desktop ?? ''}
default={defaults.header_height_desktop || '5'}
cssVar="--sf-header-height-desktop"
width="100px"
/>
Expand All @@ -109,7 +110,7 @@
label="Mobile (rem)"
min={0}
step={0.25}
default={defaults.sticky_offset_mobile ?? ''}
default={defaults.sticky_offset_mobile || '3.5'}
cssVar="--sf-sticky-offset-mobile"
width="100px"
/>
Expand All @@ -119,7 +120,7 @@
label="Desktop (rem)"
min={0}
step={0.25}
default={defaults.sticky_offset_desktop ?? ''}
default={defaults.sticky_offset_desktop || '5'}
cssVar="--sf-sticky-offset-desktop"
width="100px"
/>
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,26 @@
* Formula (matches the framework's generated clamp()):
* value = (min + (max - min) * clamp((vw - VW_MIN) / (VW_MAX - VW_MIN), 0, 1)) * spaceScale
*
* VW bounds match the framework CSS (22.5rem = 360px, 90rem = 1440px).
* Per-step token overrides (space_*_min/max) are preferred over the
* hardcoded defaults when set.
* VW bounds are NOT hardcoded — they come from the Spacing tab's "Fluid
* Scale Viewport Range" (viewportRangePx, shared with the Typography
* preview) so scrubbing the slider matches the range the generated
* clamp() actually interpolates over. Per-step token overrides
* (space_*_min/max) are preferred over the hardcoded defaults when set.
*/
import { tokens, meta } from '../lib/stores.svelte.js';
import { tokens, meta, viewportRangePx } from '../lib/stores.svelte.js';

const VW_MIN = 360;
const VW_MAX = 1440;
/** Live viewport range (px) sourced from the Spacing viewport fields. */
const range = $derived(viewportRangePx());

/** Viewport slider state — starts at desktop width. */
let vw = $state(VW_MAX);
let vw = $state(viewportRangePx().maxPx);

// Keep the slider value inside the (possibly edited) range so it never
// drifts out of bounds when the user changes Min/Max viewport.
$effect(() => {
if (vw < range.minPx) vw = range.minPx;
else if (vw > range.maxPx) vw = range.maxPx;
});

const defaults = meta.defaults?.spacing ?? {};
const defaultSizes = defaults.space_sizes ?? {};
Expand All @@ -45,7 +54,7 @@
const spaceScale = parseFloat(
tokens.spacing?.space_scale ?? defaults.space_scale ?? 1
) || 1;
const t = Math.max(0, Math.min(1, (vw - VW_MIN) / (VW_MAX - VW_MIN)));
const t = Math.max(0, Math.min(1, (vw - range.minPx) / (range.maxPx - range.minPx)));

const stepNames = ['2xs', 'xs', 's', 'm', 'l', 'xl', '2xl', '3xl', '4xl'];
const maxRef = resolveStep('4xl').max * spaceScale;
Expand All @@ -61,7 +70,7 @@
/** Spacing values at current vw for the container card preview. */
const cardSpacing = $derived.by(() => {
const spaceScale = parseFloat(tokens.spacing?.space_scale ?? defaults.space_scale ?? 1) || 1;
const t = Math.max(0, Math.min(1, (vw - VW_MIN) / (VW_MAX - VW_MIN)));
const t = Math.max(0, Math.min(1, (vw - range.minPx) / (range.maxPx - range.minPx)));
const resolve = (name) => {
const { min, max } = resolveStep(name);
return ((min + (max - min) * t) * spaceScale).toFixed(3);
Expand All @@ -74,17 +83,17 @@
<div class="spacing-preview__header">
<p class="spacing-preview__title">Live Scale Preview</p>
<div class="spacing-preview__slider-wrap">
<span>{VW_MIN}px</span>
<span>{range.minPx}px</span>
<input
class="spacing-preview__slider"
type="range"
min={VW_MIN}
max={VW_MAX}
min={range.minPx}
max={range.maxPx}
step="1"
aria-label="Preview viewport width"
bind:value={vw}
/>
<span>{VW_MAX}px</span>
<span>{range.maxPx}px</span>
<span class="spacing-preview__vw-label">{vw}px</span>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,37 @@
* Live fluid-type scale preview.
*
* Renders each size step at the interpolated font size for a given
* viewport width. The slider lets the user scrub between 320 px and
* 1440 px to watch the clamp() scale play out in real time.
* viewport width. The slider scrubs across the fluid viewport range.
*
* Formula mirrors class-css-generator.php build_clamp():
* clamp(min, calc(slope * (100vw - 22.5rem) + min), max)
* where slope = (max - min) / (95 - 22.5) and VW_MIN/MAX = 360/1520px.
* clamp(min, calc(slope * (100vw - VW_MIN) + min), max)
*
* The VW_MIN/VW_MAX bounds are NOT owned here: they come from the
* Spacing tab's "Fluid Scale Viewport Range" via viewportRangePx(), the
* single source of truth the generated clamp() also uses. This keeps
* the Typography preview and the Spacing preview scrubbing the exact
* same range.
*
* Values are read from tokens.typography (user overrides) with
* meta.defaults.typography.font_sizes as the fallback, so the preview
* is always populated even before the user sets anything.
*/
import { tokens, meta } from '../lib/stores.svelte.js';
import { tokens, meta, viewportRangePx } from '../lib/stores.svelte.js';

const BASE_PX = 16;
const VW_MIN = 360; // 22.5rem × 16 — matches VIEWPORT_MIN in class-css-generator.php
const VW_MAX = 1520; // 95rem × 16 — matches VIEWPORT_MAX in class-css-generator.php

/** Live viewport range (px) sourced from the Spacing viewport fields. */
const range = $derived(viewportRangePx());

/** Viewport slider state — starts at desktop width. */
let vw = $state(VW_MAX);
let vw = $state(viewportRangePx().maxPx);

// Keep the slider value inside the (possibly edited) range so it never
// drifts out of bounds when the user changes Min/Max viewport.
$effect(() => {
if (vw < range.minPx) vw = range.minPx;
else if (vw > range.maxPx) vw = range.maxPx;
});

const defaults = meta.defaults?.typography ?? {};
const defaultSizes = defaults.font_sizes ?? {};
Expand All @@ -47,7 +59,7 @@
}

const stepAt = $derived.by(() => {
const t = Math.max(0, Math.min(1, (vw - VW_MIN) / (VW_MAX - VW_MIN)));
const t = Math.max(0, Math.min(1, (vw - range.minPx) / (range.maxPx - range.minPx)));
const steps = {};

for (const name of Object.keys(defaultSizes)) {
Expand Down Expand Up @@ -185,17 +197,17 @@
<div class="typo-preview__header">
<p class="typo-preview__title">Live Scale Preview</p>
<div class="typo-preview__slider-wrap">
<span>{VW_MIN}px</span>
<span>{range.minPx}px</span>
<input
class="typo-preview__slider"
type="range"
min={VW_MIN}
max={VW_MAX}
min={range.minPx}
max={range.maxPx}
step="1"
aria-label="Preview viewport width"
bind:value={vw}
/>
<span>{VW_MAX}px</span>
<span>{range.maxPx}px</span>
<span class="typo-preview__vw-label">{vw}px</span>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@
<h2 class="group-heading">Font Size Scale</h2>
<p class="hint">
Min and max values in <code>rem</code> for fluid type scaling via <code>clamp()</code>.
Leave both fields blank to use the framework defaults.
Leave both fields blank to use the framework defaults. The viewport range these
sizes interpolate across (and the range the Live Scale Preview above scrubs) is the
<strong>Fluid Scale Viewport Range</strong> set in the <strong>Spacing</strong> tab —
it's the shared source for both spacing and typography.
</p>
<ScaleGenerator />
<div class="rows">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,46 @@ export function clearSection(section) {
}
}

/**
* Resolve the fluid viewport range, in pixels, used by every fluid
* `clamp()` formula the framework emits.
*
* The range is owned by the Spacing tab's "Fluid Scale Viewport Range"
* (`tokens.spacing.viewport_min` / `viewport_max`, stored in `rem`) and
* is the single source of truth shared by the Spacing and Typography
* live-scale previews — so the previewed scale always matches what the
* generated CSS would actually clamp between. Falls back to the PHP
* defaults (22.5rem → 95rem) when the user hasn't overridden them.
*
* Reading `tokens` here keeps the result reactive: callers that wrap a
* `$derived` around this recompute when the viewport fields change.
*
* @returns {{ minRem: number, maxRem: number, minPx: number, maxPx: number }}
*/
export function viewportRangePx() {
const sp = tokens.spacing ?? {};
const def = meta.defaults?.spacing ?? {};

const read = (key, fallback) => {
const v = sp[key];
const n = v !== undefined && v !== '' ? parseFloat(v) : parseFloat(def[key] ?? fallback);
return Number.isFinite(n) ? n : fallback;
};

const minRem = read('viewport_min', 22.5);
const maxRem = read('viewport_max', 95);
// Guard against an inverted/zero range so the previews never divide by
// zero or run the slider backwards while the user is mid-edit.
const safeMax = maxRem > minRem ? maxRem : minRem + 0.5;

return {
minRem,
maxRem: safeMax,
minPx: Math.round(minRem * 16),
maxPx: Math.round(safeMax * 16),
};
}

/**
* Read a single field from a (possibly absent) section.
*
Expand Down

Large diffs are not rendered by default.

39 changes: 21 additions & 18 deletions plugins/SLASHED-for-WP/integrations/bricks/assets/admin-app/app.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Loading