Skip to content

chore: technical debt audit remediation — full batch - #146

Merged
jackgranatowski merged 33 commits into
mainfrom
claude/pr-469-audit-rebase-ggp0e4
Jul 2, 2026
Merged

jackgranatowski merged 33 commits into
mainfrom
claude/pr-469-audit-rebase-ggp0e4

Conversation

@jackgranatowski

@jackgranatowski jackgranatowski commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Merges the technical-debt audit's staging branch into main. This is the missing counterpart to codeslash-dev/SLASHED#474 — the equivalent step never happened for this repo, so none of the audit work below has been in any plugin release up to and including v0.4.20.

main's current tip is the exact common ancestor with this branch (verified via git merge-base), so this merges cleanly with zero conflicts, and both branches already agree on the 0.4.20 version number — no version-file drift to resolve.

What's included

Verification

  • All ten PRs above passed CI individually (build-admin-app, build-editor-app, quality, dependency-audit, CodeQL) before merging into this branch.
  • git merge-base origin/main origin/claude/pr-469-audit-rebase-ggp0e4 confirms main's tip is the common ancestor — this is a clean merge, not a rebase/conflict-resolution situation.

Test plan

  • CI passes on this PR (re-runs everything against the merged state)
  • After merging, cut a new plugin release so the fixes above — particularly the two real bugs — actually reach users

Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added improved save-state feedback, including a visible error state when saving fails.
    • Added new consistency checks for generated assets and updated automated test coverage.
  • Bug Fixes

    • Improved preview and export behavior for more reliable updates.
    • Tightened handling of color grouping and sharing links for more consistent results.

claude and others added 30 commits July 2, 2026 13:39
…ks/Gutenberg

PL-013/PL-014: classifyVar()/classify_color() exist in three near-identical
implementations (Bricks JS, Gutenberg JS, Gutenberg PHP) that had silently
drifted — only Gutenberg's JS correctly filtered both the `-light` and
`-dark` source-duplicate suffixes; Bricks' JS and Gutenberg's PHP only
filtered `-light`, so Bricks' color picker rendered a spurious duplicate
swatch for every `-dark` token that Gutenberg correctly hid. Fixed both to
match Gutenberg JS's already-correct behavior. Also ported Gutenberg
filterModel's missing Array.isArray(group.sections)/Array.isArray(section
.swatches) defensive guards into Bricks, which never had them.

PL-015: added tests/color-model-cross-impl.test.js, a parametrized
regression test running a shared fixture list against all three
implementations to guarantee they can't silently re-diverge. The PHP side
is exercised via tests/php-harness/classify-color.php, a small reflection-
based harness (classify_color() is private and reads no instance state,
so newInstanceWithoutConstructor() avoids needing a WordPress bootstrap)
shelled out to via execFileSync, matching the plugin's existing
lint-php.js convention. Left a TODO to migrate this into the PHPUnit
suite once PR-A3 (Brain Monkey scaffold) lands, per the remediation plan.

Rebuilt SLASHED-for-WP/integrations/bricks/assets/editor-app/app.js to
include the source fix, matching this repo's existing built-asset
commit convention.

Verified: node --test tests/*.test.js (95/95, including the new 28-case
cross-impl suite — confirmed it catches the original bug by temporarily
reverting the Bricks fix and observing exactly the 4 expected subtest
failures), php -l via npm run lint:php, npm run verify, and a full
build:editor-app rebuild. composer phpstan/phpcs could not be run in
this sandbox — composer install fails on all dev dependencies with
"Could not authenticate against github.com" for both dist and source
fallback downloads, a pre-existing environment network limitation
unrelated to this change.
Matches scripts/lint-php.js's existing convention so a missing php
binary fails with a clear message instead of an opaque ENOENT from
execFileSync. Also caps the harness call with a timeout so a stuck
php process can't hang the test runner.
…names

SLASHED's SL-016 (codec.ts's `fa` → `generateCSS`) and SL-022
(lucide-svelte → @lucide/svelte) landed in main via #474. AppOverlay.svelte
is plugin-specific and not vendored, so it kept referencing the old names
and broke `vite build` as soon as CI's prebuild sync pulled the renamed
exports from slashed@main — failing on every open plugin PR, not just the
one that triggered it.

Swaps the import/call site to generateCSS and updates package.json/
package-lock.json to depend on @lucide/svelte instead of the deprecated
lucide-svelte package.
…ator

PL-025: sync-core.mjs now supports --check/--dry-run, reporting missing/
stale/orphaned vendored files against the framework source without ever
writing to disk. All writes are routed through a single writeFile() helper
so the never-write invariant only needs to hold in one place.

Adds scripts/check.js (npm run check) to run the class-hints, variables-
hints, and admin-app sync checks together and aggregate their exit status.

Also: PL-036 fixes CLAUDE.md's npm run sync location (it's defined in
admin-app/package.json, not the root), PL-033 trims .syncignore's stale
historical PR reference, and PL-037 adds the missing assets/, data/,
tests/, and docs/ directories to CLAUDE.md's structure diagram.
PR-C1's branch forked before #135 landed, so it inherited the same
stale fa/lucide-svelte references breaking `vite build`.
npm run check (svelte-check) had no equivalent of dev/build's
predev/prebuild sync hook, so it type-checked whatever was already on
disk. On a fresh checkout with the committed vendored src/ still
predating the lucide-svelte rename, that meant a stale module-resolution
error instead of an accurate check against the current framework source.
- copyLocalDir: open the fd once and reuse it for both the isDirectory
  check and the file read (CodeQL: potential file system race
  condition) instead of a separate statSync + readFileSync on the path.
- GitHub-mode syncignore preservation: use O_NOFOLLOW like the
  local-mode block already does, instead of a plain 'r' open that
  would follow symlinks (Qodo).
- reportOrphans(): also scan framework-css/core/ for files outside the
  fixed CHROME_LAYERS set, not just src/ (Qodo).
- Document the accepted trust boundary for the GitHub-fetch-to-disk
  write path (CodeQL: network data written to file) — hardcoded repo/
  ref, HTTPS transport, and destination paths already confined to SRC
  regardless of fetched content.
This branch predates the SL-016/SL-022 rename fix, so it inherited the
same stale fa/lucide-svelte references breaking `vite build` in CI.
…review effect

PL-029/030: window.slashedApp is a plain global any script on the page
can clobber before plugin-main.ts runs. Add a same-origin check before
using cssUrl as a stylesheet href, and replace the (window as any) cast
with a typed local accessor (mirroring persistence.ts's own
window.slashedApp read) instead of widening to any.

PL-031: AppOverlay.svelte's live-preview effect (injectLivePreview +
registerPreviewDoc) re-ran on every override change with no coalescing,
unlike PreviewPanel.svelte's SL-020 fix. A fast-changing control
(dragging a slider) could re-run it many times per frame even though
the underlying <style> rewrite only needs to happen once per paint.
Wrapped it in the same rAF-coalescing pattern.
PL-034: the script hardcoded an absolute path to one specific sandbox's
global playwright install (/opt/node22/...), assumed a test-admin.html
fixture that isn't committed anywhere in the repo, assumed a dev server
already running on port 9999, and had no pass/fail assertions (a run
with console errors still exited 0). None of that is fixable without
committing a fixture, starting a server in CI, and rewriting it as real
assertions — out of scope here.

Instead: replace the hardcoded path with a normal `import('playwright')`
that fails with clear install instructions instead of a cryptic
ERR_MODULE_NOT_FOUND, and document in the file header and CLAUDE.md that
this is a manual local tool, not part of `npm test`/CI.
…iew effect

Svelte 5's \$effect only tracks reactive reads that happen during its own
synchronous execution. The rAF-coalescing added for PL-031 read overrides
only inside the (later-firing) requestAnimationFrame callback, so the
effect would never re-run after the first paint -- the live preview
would silently stop reacting to override changes.

PreviewPanel.svelte's SL-020 fix (the pattern this was meant to mirror)
already captures its reactive dependencies synchronously before entering
the rAF callback for exactly this reason -- this file just missed it.
…-model-fix

fix(color-model): filter -dark source tokens consistently across Bricks/Gutenberg (PL-013/014/015)
…n-check-script

feat: add sync drift checking (PL-025/033/036/037)
…end-hardening

fix: harden frontend overlay's cssUrl injection and preview effect (PL-029/030/031)
Brings in the already-merged #134/#135/#136/#138 content; the only
real conflict was two independent additions to CLAUDE.md's Key
scripts section (npm run check row from #136, the playwright-admin.js
manual-only note from #139) -- kept both.
…right-docs

docs: document playwright-admin.js as manual-only (PL-034)
Wave 1 (PR-SYNC): catches admin-app/src/ and framework-css/core/ up to
the SLASHED framework audit fixes merged to main via #474 (SL-001..034)
-- the committed vendored copy had predated all of them since before
this remediation effort started, which is exactly the drift PR-C1's
--check mode has been reporting all session.

Ran `npm run sync` against the current local SLASHED checkout (verified
byte-identical to origin/main for configurator/src and badges/), then
rebuilt assets/admin-app/ per this repo's committed-build-output
convention.

Verified: `sync-core.mjs --check` now reports zero drift, `npm run
check` has no new errors (the one remaining error, plugin-main.ts's
.ts import extension, is pre-existing and unrelated), `npm run build`
succeeds, `npm test` passes (95/95), `npm run lint` is clean.
…chup

chore: re-sync vendored configurator core from SLASHED main (PR-SYNC)
…n classes

PR-A2 (architecture cleanup):

- Slashed_Color_Resolver mixed generic OKLab/sRGB color-space math
  (oklch_to_hex, hex_to_oklch, mix_rgb, etc.) with SLASHED-specific
  semantic-token resolution logic. Extracted the math into a new
  dependency-free Slashed_Color_Math class; the resolver now delegates
  to it instead of duplicating both concerns in one 876-line file.

- Slashed_Inventory bundled CSS-bundle resolution/caching with a
  ~110-entry hardcoded category-display-name map -- presentation data
  living inside a data-resolution class. Extracted it into a new
  Slashed_Category_Map class; categorize_variable()/category_order()
  now delegate to it.

Both extractions verified behavior-preserving: ran the pre-refactor and
post-refactor versions of each class standalone (via a throwaway
harness, not committed) against representative inputs covering every
branch -- resolve()/resolve_dark() across a full color set, and
categorize_variable() across every category-map segment plus unknown
segments (Misc fallback) -- output was byte-identical in both cases.

Also updated the two require_once chains that load class-inventory.php
directly (bypassing the plugin's central load order) -- the Bricks
Slashed_Bricks_Color_Resolver compat shim and both integrations'
Inventory subclasses -- to pull in the new classes too.
…otstrap

slashed_bricks_require_data_classes() required the new shared class from
SLASHED_BRICKS_PATH . 'includes/class-color-math.php', which resolves
inside the Bricks integration's own includes/ (no such file there) instead
of the shared top-level includes/ where Slashed_Color_Math actually lives.
Match Gutenberg's already-correct '../../includes/class-color-math.php'.
Adds phpunit/phpunit (^9.6, PHP 7.4-compatible) as a composer dev
dependency, a phpunit.xml pointed at a new tests-php/ suite, and a
bootstrap that defines ABSPATH and stubs sanitize_key() — the only
WordPress function any of the covered classes touch — so the suite
never needs a WP bootstrap or a mocking framework.

Covers three pure/near-pure targets identified in the audit:
Slashed_CSS_Parser::parse(), Slashed_CSS_Generator::validate_override_value()
(plus its is_css_safe()/balanced_parens() defence-in-depth guards), and
Slashed_REST_Controller::sanitize_rebemer_element_map(). Every assertion
was cross-checked against the real implementation via a standalone php -r
harness before being committed, since composer install can't reach
github.com in this sandbox (pre-existing network limitation) to actually
run the suite here — CI's quality job, which already runs a real
composer install, is the first place this executes for real.

Wires composer phpunit into CI's quality job and documents the new
tests-php/ convention in CLAUDE.md.
composer.lock was generated on this sandbox's local PHP 8.4, so
`composer require phpunit/phpunit` resolved doctrine/instantiator
2.1.0 (requires PHP ^8.4) as a transitive dependency. CI runs PHP 8.2
and failed composer install with a platform-requirement conflict.

Pinning config.platform.php to 7.4 (composer.json's own declared
floor) makes dependency resolution deterministic regardless of which
PHP version composer happens to run on, and re-locks phpunit/phpunit's
tree onto doctrine/instantiator 1.5.0, which supports the full 7.4-8.4
range.
Only define ABSPATH when not already set, matching the defined()
guard every plugin file uses, so running this suite in a context
where ABSPATH is already defined doesn't emit a redefinition warning.
apply.js (buildPlan, applyAutoNumbering, applyToSubtree) had zero test
coverage despite being the core mutation path for the reBEMer panel —
680 lines handling five apply modes, sibling auto-numbering collision
resolution, migrate-mode key lifting/conflict detection, and an
error-triggered rollback across a whole subtree apply.

buildPlan()/computeBlockAssignment() are pure and tested directly.
applyToSubtree() only reaches live Bricks state through bricks-api.js,
which itself needs just one DOM call
(document.querySelector('[data-v-app]') → a Vue-app-shaped object
exposing $_state) to resolve — so these tests stub that single call
with an in-memory fake state tree and let the real bricks-api.js run
against it, rather than mocking the api module. This exercises the
actual integration (findElement, upsertGlobalClass, setElementClasses,
batchMutations) including a real induced-failure rollback test, not a
synthetic mock of the API surface.

Covers: mode validation, auto-numbering (collision resolution,
authoritative-provenance protection, post-numbering integrity check),
modifier slug resolution order, sub-block scoping, all five apply
modes' happy paths, migrate-mode's conflict-refusal and additive-merge
paths, and full rollback on a mid-apply error.
Three gaps in the GitHub-API fallback path of the vendoring sync
(used when no local sibling framework checkout is found — e.g. a
fresh CI checkout):

- ghFetch() made a single fetch attempt with no retry, so a
  transient network blip or a momentary GitHub rate-limit hit failed
  the whole sync outright. Added fetchWithRetry(): exponential
  backoff on network errors, 5xx responses, and a rate-limit-
  exhausted 403 (x-ratelimit-remaining: 0) specifically — a plain
  403/404 still fails immediately so the existing "keep the vendored
  copy" fallback in syncGhFile()/vendorChromeRemote() isn't delayed
  by pointless retries on genuine not-found/permission errors.
- syncGhDir()'s recursive Promise.all() fan-out was unbounded: a
  wide/deep configurator/src tree could issue hundreds of concurrent
  GitHub API requests, which is exactly what trips secondary rate
  limits. Added createLimiter(), a small bounded-concurrency job
  queue shared across the whole recursive walk (not just per
  directory), capping total in-flight requests to 6.
- Added tests/sync-core.test.js covering fetchWithRetry and
  createLimiter with injected fetch/sleep — the first test coverage
  sync-core.mjs itself has had (existing tests/sync.test.js only
  covers the separate verify-sync.js). main() still does real fs
  writes and real network calls, so it isn't imported/exercised
  directly; it's now gated behind an isMainModule check so importing
  the module for these unit tests doesn't trigger a real sync as an
  import side effect.

Verified with `node scripts/sync-core.mjs --check` (local-source
path, unaffected by this PR) and the full node --test suite — no
regressions.
…imiter

Qodo caught a real, likely-live bug: syncGhDir() wrapped its whole
recursive syncGhDir()/syncGhFile() call in ghLimiter, so a directory
job held a limiter slot for its entire subtree's duration while its
own children queued behind the same limiter for a slot — a
reentrant lock-holding pattern that starves or deadlocks once enough
directory jobs are simultaneously in flight. This is a very plausible
explanation for a CI "Build admin-app" failure on this same PR: the
GitHub-path sync fetched only 7 of ~50 files and then silently
stopped with no error, consistent with the walk getting stuck once
enough concurrent directory jobs occupied the limiter's slots.

Fix: move the limiter to wrap only the atomic HTTP request itself
(inside ghFetch(), via fetchWithRetry), not the recursive directory
traversal. syncGhDir()'s fan-out is unbounded again structurally, but
every actual GitHub API call anywhere in the whole recursive tree
funnels through the same ghFetch() -> ghLimiter, so total concurrent
network requests are still capped at 6 tree-wide — with no
self-referential wait possible, since a slot is now held only for the
duration of one HTTP request rather than a whole subtree.

Also fixes two related createLimiter() robustness bugs Qodo flagged
in the same review:
- A job that throws synchronously (rather than returning a rejected
  promise) used to skip the .finally() cleanup entirely, permanently
  leaking its slot and stalling the queue forever. Now invoked via
  Promise.resolve().then(fn) so a sync throw is caught the same way
  as an async rejection.
- createLimiter(limit) silently built a permanently-stalled limiter
  for limit <= 0 (active >= limit is true from the start, so next()
  never runs anything). Now throws a TypeError immediately for any
  non-positive or non-integer limit.

Added tests for both: a synchronously-throwing job still frees its
slot for subsequent jobs, and invalid limit values throw at
construction. Re-verified the whole GitHub-path walk against the
real codeslash-dev/slashed repo with a throwaway script exercising
just the exported helpers (49 files, 7 dirs, completed cleanly) before
this fix; local --check mode and the full test suite (110 tests)
still pass after it.
SLASHED-for-WP/assets/admin-app/, integrations/bricks/assets/editor-app/,
and dist/ are Vite/framework build output (verified against the actual
outDir config in both admin-app/vite.config.js and
integrations/bricks/editor-app/vite.config.js, and dist/'s own CLAUDE.md
description) — never hand-edited, and previously showed up as noisy
full-file diffs and skewed the repo's language stats on every rebuild.

Deliberately excludes SLASHED-for-WP/assets/admin/ (settings.css,
bricks-settings.css): those are hand-written admin-page styles, not
Vite output, confirmed by checking there's no build script targeting
that directory.

Also suppresses line diffs on *.min.css/*.min.js/*.js.map/*.css.map
specifically, since a "changed" diff on minified/source-map content is
never something a reviewer can meaningfully read.
…rchitecture

refactor: extract color-math and category-map from oversized PHP classes
…it-scaffold

test: add PHPUnit scaffold for pure/near-pure PHP logic
…-tests

test: add coverage for the Bricks reBEMer apply.js engine
…hardening

fix: harden sync-core.mjs's GitHub API sync path
…tributes

chore: mark built SPA/CSS output as linguist-generated
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jackgranatowski, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 74bd3708-19e6-4518-83eb-32144ecd0ce9

📥 Commits

Reviewing files that changed from the base of the PR and between d8deaea and de2773a.

📒 Files selected for processing (2)
  • SLASHED-for-WP/admin-app/package.json
  • SLASHED-for-WP/admin-app/src/AppOverlay.svelte
📝 Walkthrough

Walkthrough

This PR refactors codec/color-math logic, adds check-mode drift detection to the framework sync script, extracts shared PHP color math and category-mapping classes used across Bricks/Gutenberg integrations, updates Svelte icon imports and save-state handling, adds PHPUnit test infrastructure, and updates CI, config, and docs.

Changes

Frontend codec, persistence, and UI wiring

Layer / File(s) Summary
Shared type contracts
SLASHED-for-WP/admin-app/src/types.ts
Adds ApiIndex, SlashedClass, TokenRegistry, DecodeOptions, ShareOptions interfaces/types.
codec.ts rewrite
SLASHED-for-WP/admin-app/src/lib/codec.ts
Replaces minified helpers with typed encode/decode/generateCSS/parseCSS/encodeOverrides/buildShareUrl exports.
Persistence integration
SLASHED-for-WP/admin-app/src/lib/persistence.ts, savedThemes.ts, previewResolver.svelte.ts
Switches to new codec exports, validates localStorage JSON via exported isStringRecord.
Component icon/save-state updates
SLASHED-for-WP/admin-app/src/App.svelte, AppOverlay.svelte, components/**/*
Migrates lucide-svelte to @lucide/svelte, adds saveState: 'error' handling, coalesces preview DOM writes via rAF, switches CSS/link generation to new codec functions.
Overlay CSS same-origin guard
SLASHED-for-WP/admin-app/src/plugin-main.ts
Adds getCssUrl()/isSameOrigin() gating before mounting overlay stylesheet.

Estimated code review effort: 4 (Complex) | ~60 minutes

PHP color math and category map extraction

Layer / File(s) Summary
Slashed_Color_Math class
SLASHED-for-WP/includes/class-color-math.php
New OKLCH/hex/RGB conversion and mixing static methods.
Slashed_Category_Map class
SLASHED-for-WP/includes/class-category-map.php
New canonical category order and segment-to-label mapping.
Resolver refactor
SLASHED-for-WP/includes/class-color-resolver.php
Replaces internal color math with Slashed_Color_Math calls throughout.
Inventory refactor
SLASHED-for-WP/includes/class-inventory.php
Replaces internal category logic with Slashed_Category_Map calls.
Integration wiring
SLASHED-for-WP/integrations/bricks/*, SLASHED-for-WP/integrations/gutenberg/*
Adds require_once for new shared classes and filters -dark duplicate tokens in color classifiers.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sync-core drift detection, check pipeline, and PHPUnit/CI infra

Layer / File(s) Summary
sync-core.mjs check mode/retry/limiter
SLASHED-for-WP/admin-app/scripts/sync-core.mjs, .vendored-manifest.json, .syncignore, package.json
Adds --check drift detection, writeFile() wrapper, orphan reporting, fetchWithRetry, createLimiter.
check.js orchestration
scripts/check.js, package.json
New CLI runs drift checks sequentially and exits non-zero on failure.
PHPUnit infra
phpunit.xml, tests-php/bootstrap.php, composer.json, .github/workflows/ci.yml, .gitignore
Adds PHPUnit configuration, bootstrap, dependency, script, and CI step.
PHP tests
tests-php/*.php
Adds tests for CSS generator validation, CSS parser, and REST controller BEM sanitization.
JS tests
tests/apply.test.js, tests/color-model-cross-impl.test.js, tests/php-harness/classify-color.php, tests/sync-core.test.js, tests/playwright-admin.js
Adds tests for reBEMer apply engine, cross-implementation color classification, sync-core retry/limiter, and updates Playwright import.
Docs and CSS comments
CLAUDE.md, .gitattributes, SLASHED-for-WP/admin-app/framework-css/core/*.css
Updates developer docs and clarifies CSS documentation comments only.

Estimated code review effort: 4 (Complex) | ~50 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as sync-core.mjs main()
  participant WriteFile as writeFile()
  participant GH as GitHub API (ghFetch)
  participant Limiter as createLimiter

  CLI->>GH: syncGhFile()/vendorChromeRemote()
  GH->>Limiter: acquire slot
  Limiter->>GH: fetchWithRetry(url)
  GH-->>CLI: fetched content
  CLI->>WriteFile: writeFile(path, content)
  WriteFile-->>CLI: compare vs disk (check mode) or write (sync mode)
  CLI->>CLI: reportOrphans() / finishCheck()
Loading
sequenceDiagram
  participant Effect as Svelte $effect
  participant RAF as requestAnimationFrame
  participant Iframe as Preview iframe

  Effect->>Effect: capture overrides synchronously
  Effect->>RAF: schedule callback
  RAF->>Iframe: write generateCSS(overrides) to style tag
  RAF->>Iframe: set data-theme/data-lumlocker
  RAF->>Iframe: registerPreviewDoc(doc)
  Effect->>RAF: cancel on cleanup
Loading

Possibly related PRs

Suggested labels: codex

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the PR’s main purpose: a broad technical-debt remediation batch.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/pr-469-audit-rebase-ggp0e4

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added the codex label Jul 2, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

chore: technical debt audit remediation — full batch

🐞 Bug fix ✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Align color token classification across Bricks JS, Gutenberg JS, and Gutenberg PHP.
• Add drift-check tooling (npm run check, sync-core.mjs --check) and harden GitHub sync.
• Introduce PHPUnit + Node test coverage for core mutation, parsing, and sanitization paths.
Diagram

graph TD
    subgraph PHP_Backend["PHP Backend"]
        CM["Slashed_Color_Math"] --> CR["Slashed_Color_Resolver"] --> BricksBootstrap["slashed-bricks.php"]
        CatMap["Slashed_Category_Map"] --> Inv["Slashed_Inventory"]
    end

    subgraph Color_Drift["Color Drift Lock-in"]
        BricksColorModel["Bricks color-model.js"] --> CrossTest["cross-impl test"]
        GutenbergColorModel["Gutenberg color-model.js"] --> CrossTest
        GutenbergPresets["class-presets.php"] --> CrossTest
    end

    subgraph Sync_Checks["Sync + Drift Checks"]
        SyncCore["sync-core.mjs"] --> CheckJS["scripts/check.js"] --> CI["CI quality"]
    end

    subgraph Test_Suites["Test Suites"]
        PHPUnit["tests-php/"] --> CI
        ApplyTest["apply.test.js"] --> CI
        SyncTest["sync-core.test.js"] --> CI
    end

    subgraph Legend
      direction LR
      _mod["Module"] ~~~ _svc(["CI Job"]) ~~~ _test{{"Test"}}
    end
Loading
High-Level Assessment

Given the stated merge-base guarantee and that the constituent PRs were already CI-validated, the batch-merge strategy is reasonable. The fixes address root causes (cross-impl drift, sync drift detection, concurrency deadlock risk, Bricks bootstrap fatal) and back them with regression tests, which is preferable to partial cherry-picks that would increase divergence and maintenance burden.

Files changed (53) +2666 / -811

Enhancement (3) +458 / -127
sync-core.mjsAdd drift check mode + GitHub sync hardening +330/-81

Add drift check mode + GitHub sync hardening

• Introduces '--check/--dry-run' drift reporting without writes, adds retry/backoff for transient GitHub API failures, and caps concurrent network requests with a bounded limiter to avoid rate limits and deadlock risk. Exports helper functions and gates main() execution so tests can import safely.

SLASHED-for-WP/admin-app/scripts/sync-core.mjs

PreviewPanel.svelteCoalesce preview DOM writes with requestAnimationFrame +84/-46

Coalesce preview DOM writes with requestAnimationFrame

• Moves expensive/duplicative iframe DOM updates into rAF callbacks to collapse bursty re-runs (e.g., slider drag) and avoids unnecessary scheduling when split mode is off. Updates CSS generation call to 'generateCSS' and Lucide import path.

SLASHED-for-WP/admin-app/src/components/shell/PreviewPanel.svelte

check.jsAdd drift-check orchestrator +44/-0

Add drift-check orchestrator

• Runs multiple generated-artifact drift checks (class hints, variable hints, admin-app sync) and aggregates exit status for CI gating.

scripts/check.js

Bug fix (9) +67 / -16
AppOverlay.svelteFix preview effect tracking + rename CSS generator import +22/-9

Fix preview effect tracking + rename CSS generator import

• Fixes a Svelte 5 effect dependency tracking pitfall by capturing overrides synchronously and coalescing DOM writes into rAF. Renames codec import to 'generateCSS' and updates Lucide import path.

SLASHED-for-WP/admin-app/src/AppOverlay.svelte

plugin-main.tsHarden frontend overlay cssUrl injection +31/-2

Harden frontend overlay cssUrl injection

• Adds typed accessors for 'window.slashedApp.cssUrl' and enforces same-origin validation before injecting a stylesheet link, mitigating global-clobbering risks.

SLASHED-for-WP/admin-app/src/plugin-main.ts

color-model.jsFix Bricks color classification drift (-dark filtering) +5/-3

Fix Bricks color classification drift (-dark filtering)

• Filters both '-light' and '-dark' source tokens to avoid duplicate UI swatches and adds defensive array guards in filtering logic.

SLASHED-for-WP/integrations/bricks/editor-app/src/lib/color-model.js

class-color-resolver.phpRequire extracted Slashed_Color_Math in Bricks integration +1/-0

Require extracted Slashed_Color_Math in Bricks integration

• Adds bootstrap includes so Bricks can load the extracted math class reliably.

SLASHED-for-WP/integrations/bricks/includes/class-color-resolver.php

class-inventory.phpRequire extracted Slashed_Category_Map in Bricks integration +1/-0

Require extracted Slashed_Category_Map in Bricks integration

• Adds bootstrap includes so Bricks can load the extracted category map class reliably.

SLASHED-for-WP/integrations/bricks/includes/class-inventory.php

slashed-bricks.phpFix Bricks bootstrap include path for extracted classes +1/-0

Fix Bricks bootstrap include path for extracted classes

• Adds missing 'require_once' for 'class-color-math.php', preventing a fatal error on first load in Bricks-only installs.

SLASHED-for-WP/integrations/bricks/slashed-bricks.php

class-inventory.phpRequire extracted Slashed_Category_Map in Gutenberg integration +1/-0

Require extracted Slashed_Category_Map in Gutenberg integration

• Adds bootstrap includes so Gutenberg can load the extracted category map class reliably.

SLASHED-for-WP/integrations/gutenberg/includes/class-inventory.php

class-presets.phpFix Gutenberg PHP color classification drift (-dark filtering) +4/-2

Fix Gutenberg PHP color classification drift (-dark filtering)

• Filters both '-light' and '-dark' source tokens to match JS implementations and avoid duplicate classification output.

SLASHED-for-WP/integrations/gutenberg/includes/class-presets.php

slashed-gutenberg.phpRequire extracted Slashed_Color_Math in Gutenberg bootstrap +1/-0

Require extracted Slashed_Color_Math in Gutenberg bootstrap

• Adds bootstrap include for the extracted color math class.

SLASHED-for-WP/integrations/gutenberg/slashed-gutenberg.php

Refactor (16) +749 / -616
App.svelteUpdate Lucide imports +8/-8

Update Lucide imports

• Migrates icon imports to '@lucide/svelte'.

SLASHED-for-WP/admin-app/src/App.svelte

DomainPanel.svelteUpdate Lucide imports +1/-1

Update Lucide imports

• Migrates icon imports to '@lucide/svelte'.

SLASHED-for-WP/admin-app/src/components/DomainPanel.svelte

CheatsheetPanel.svelteUpdate Lucide imports and codec call sites +5/-4

Update Lucide imports and codec call sites

• Migrates icon imports and updates codec usage to renamed functions.

SLASHED-for-WP/admin-app/src/components/panels/CheatsheetPanel.svelte

ExportPanel.svelteUpdate Lucide imports and codec call sites +4/-4

Update Lucide imports and codec call sites

• Migrates icon imports and updates codec usage to renamed functions.

SLASHED-for-WP/admin-app/src/components/panels/ExportPanel.svelte

HomePanel.svelteUpdate Lucide imports +1/-1

Update Lucide imports

• Migrates icon imports to '@lucide/svelte'.

SLASHED-for-WP/admin-app/src/components/panels/HomePanel.svelte

ThemesPanel.svelteUpdate Lucide imports +1/-1

Update Lucide imports

• Migrates icon imports to '@lucide/svelte'.

SLASHED-for-WP/admin-app/src/components/panels/ThemesPanel.svelte

SidebarNav.svelteUpdate Lucide imports +1/-1

Update Lucide imports

• Migrates icon imports to '@lucide/svelte'.

SLASHED-for-WP/admin-app/src/components/shell/SidebarNav.svelte

StudioHeader.svelteUpdate Lucide imports and codec call sites +9/-4

Update Lucide imports and codec call sites

• Migrates icon imports and updates codec usage to renamed functions.

SLASHED-for-WP/admin-app/src/components/shell/StudioHeader.svelte

codec.tsDe-minify codec API and harden CSS generation inputs +151/-153

De-minify codec API and harden CSS generation inputs

• Renames previously obfuscated exports to descriptive names (encode/decode/generateCSS/parseCSS/share helpers), introduces typed options and registry interfaces, and centralizes constants for codec versioning and limits.

SLASHED-for-WP/admin-app/src/lib/codec.ts

persistence.tsUpdate persistence to renamed codec API +9/-5

Update persistence to renamed codec API

• Adjusts imports/call sites to use new codec function names.

SLASHED-for-WP/admin-app/src/lib/persistence.ts

savedThemes.tsUpdate saved themes logic to renamed codec API +1/-1

Update saved themes logic to renamed codec API

• Adjusts imports/call sites to use new codec function names.

SLASHED-for-WP/admin-app/src/lib/savedThemes.ts

types.tsAdd typed shapes for generated JSON and codec options +62/-0

Add typed shapes for generated JSON and codec options

• Introduces interfaces for api-index/classes/token-registry JSON plus codec option types, replacing 'any' usage across call sites.

SLASHED-for-WP/admin-app/src/types.ts

class-category-map.phpAdd Slashed_Category_Map extracted from inventory +182/-0

Add Slashed_Category_Map extracted from inventory

• Introduces a dedicated class for category ordering and first-segment label mapping used to group variables in the UI.

SLASHED-for-WP/includes/class-category-map.php

class-color-math.phpAdd Slashed_Color_Math extracted from color resolver +242/-0

Add Slashed_Color_Math extracted from color resolver

• Introduces a dedicated class for color parsing/conversion/mixing primitives to keep resolver logic focused and testable.

SLASHED-for-WP/includes/class-color-math.php

class-color-resolver.phpRefactor resolver to use Slashed_Color_Math +68/-289

Refactor resolver to use Slashed_Color_Math

• Replaces internal self-contained math helpers with calls to the extracted 'Slashed_Color_Math' class.

SLASHED-for-WP/includes/class-color-resolver.php

class-inventory.phpDelegate variable categorization to Slashed_Category_Map +4/-144

Delegate variable categorization to Slashed_Category_Map

• Removes large inlined category order/map tables and delegates to the new map class.

SLASHED-for-WP/includes/class-inventory.php

Tests (8) +1221 / -0
CssGeneratorValidateOverrideValueTest.phpAdd PHPUnit coverage for override-value validation +67/-0

Add PHPUnit coverage for override-value validation

• Adds data-driven tests for accepted and rejected override values to prevent CSS injection and validate supported syntax.

tests-php/CssGeneratorValidateOverrideValueTest.php

CssParserTest.phpAdd PHPUnit coverage for CSS parser +69/-0

Add PHPUnit coverage for CSS parser

• Adds tests covering variable extraction, class extraction, natural sorting, and property initial-value precedence.

tests-php/CssParserTest.php

RestControllerSanitizeRebemerElementMapTest.phpAdd PHPUnit coverage for REST sanitization of reBEMer map +126/-0

Add PHPUnit coverage for REST sanitization of reBEMer map

• Adds tests for key/value normalization, reserved/invalid name rejection, and cap enforcement.

tests-php/RestControllerSanitizeRebemerElementMapTest.php

bootstrap.phpAdd PHPUnit bootstrap with minimal WP stubs +33/-0

Add PHPUnit bootstrap with minimal WP stubs

• Defines ABSPATH, stubs 'sanitize_key()', and requires the tested classes directly without booting WordPress.

tests-php/bootstrap.php

apply.test.jsAdd node:test suite for Bricks reBEMer apply engine +558/-0

Add node:test suite for Bricks reBEMer apply engine

• Adds extensive unit/integration tests for plan building, block assignment, and subtree application using an in-memory fake Bricks state.

tests/apply.test.js

color-model-cross-impl.test.jsAdd 3-way cross-implementation color classification regression test +102/-0

Add 3-way cross-implementation color classification regression test

• Ensures Bricks JS, Gutenberg JS, and Gutenberg PHP agree on filtering and classification fields for a shared fixture list; uses a PHP harness for the private PHP method.

tests/color-model-cross-impl.test.js

classify-color.phpAdd PHP reflection harness for cross-impl tests +42/-0

Add PHP reflection harness for cross-impl tests

• Invokes Gutenberg's private 'classify_color()' via reflection without a WordPress bootstrap, enabling Node-based cross-impl regression tests.

tests/php-harness/classify-color.php

sync-core.test.jsAdd unit tests for sync-core retry/backoff and limiter helpers +224/-0

Add unit tests for sync-core retry/backoff and limiter helpers

• Adds node:test coverage for exponential backoff, retry classification (5xx/rate-limit/network errors), and limiter correctness/concurrency bounds.

tests/sync-core.test.js

Documentation (2) +56 / -7
CLAUDE.mdUpdate repo docs for sync/check/test workflows +31/-4

Update repo docs for sync/check/test workflows

• Documents new directories (tests/docs), clarifies sync execution location, and adds guidance for 'npm run check' and 'composer phpunit' plus Playwright admin script expectations.

CLAUDE.md

playwright-admin.jsClarify Playwright admin script is manual-only and make import portable +25/-3

Clarify Playwright admin script is manual-only and make import portable

• Documents the script as a local QA helper (not CI) and switches from a hardcoded Playwright path to dynamic import with guidance when missing.

tests/playwright-admin.js

Other (15) +115 / -45
.gitattributesMark built SPA/CSS output as linguist-generated +17/-0

Mark built SPA/CSS output as linguist-generated

• Adds linguist-generated markers for built artifact directories and disables diffs for minified files and sourcemaps to reduce noisy PR reviews.

.gitattributes

ci.ymlRun PHPUnit in CI +3/-0

Run PHPUnit in CI

• Adds a 'composer phpunit' step to the quality workflow job to execute the new PHP test suite.

.github/workflows/ci.yml

.syncignoreAdjust admin-app sync ignore list +0/-5

Adjust admin-app sync ignore list

• Removes entries from the sync ignore list, aligning vendoring behavior with the re-synced upstream core.

SLASHED-for-WP/admin-app/.syncignore

.vendored-manifest.jsonRefresh vendored manifest +1/-1

Refresh vendored manifest

• Updates vendored manifest metadata to reflect the latest re-sync state.

SLASHED-for-WP/admin-app/.vendored-manifest.json

layout.cssRe-sync vendored framework layout layer +16/-3

Re-sync vendored framework layout layer

• Updates the vendored chrome layer CSS to match upstream SLASHED.

SLASHED-for-WP/admin-app/framework-css/core/layout.css

themes.cssRe-sync vendored framework themes layer +6/-1

Re-sync vendored framework themes layer

• Updates the vendored chrome layer CSS to match upstream SLASHED.

SLASHED-for-WP/admin-app/framework-css/core/themes.css

tokens.cssRe-sync vendored framework tokens layer +16/-6

Re-sync vendored framework tokens layer

• Updates the vendored chrome layer CSS to match upstream SLASHED.

SLASHED-for-WP/admin-app/framework-css/core/tokens.css

package.jsonSwitch Lucide package and add precheck +2/-1

Switch Lucide package and add precheck

• Replaces 'lucide-svelte' with '@lucide/svelte' and adds a 'precheck' hook that runs the vendored-core sync script before Svelte type checks.

SLASHED-for-WP/admin-app/package.json

previewResolver.svelte.tsAdd vendored preview resolver module +8/-0

Add vendored preview resolver module

• Adds a new module as part of the upstream configurator re-sync.

SLASHED-for-WP/admin-app/src/lib/previewResolver.svelte.ts

app.cssUpdate built admin SPA CSS artifact +1/-1

Update built admin SPA CSS artifact

• Updates committed build output to match source changes and vendored re-sync.

SLASHED-for-WP/assets/admin-app/app.css

app.jsUpdate built admin SPA JS artifact +23/-23

Update built admin SPA JS artifact

• Updates committed build output to match source changes and vendored re-sync.

SLASHED-for-WP/assets/admin-app/app.js

app.jsUpdate built Bricks editor app artifact +2/-2

Update built Bricks editor app artifact

• Updates committed build output to match source changes (notably the color-model filter fix).

SLASHED-for-WP/integrations/bricks/assets/editor-app/app.js

composer.jsonAdd PHPUnit dependency and phpunit script +7/-2

Add PHPUnit dependency and phpunit script

• Adds 'phpunit/phpunit' and a composer script for running the PHP unit suite; pins platform PHP to 7.4.

composer.json

package.jsonAdd root-level npm run check +1/-0

Add root-level npm run check

• Adds 'npm run check' to run the drift-check orchestrator script.

package.json

phpunit.xmlAdd PHPUnit configuration +12/-0

Add PHPUnit configuration

• Configures PHPUnit (bootstrap + testsuite directory) for the new 'tests-php/' suite.

phpunit.xml

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 1 rule

Grey Divider


Informational

1. Vendored App.svelte modified 📘 Rule violation § Compliance
Description
The PR modifies SLASHED-for-WP/admin-app/src/App.svelte, which is explicitly listed as a vendored
file in SLASHED-for-WP/admin-app/.vendored-manifest.json. This violates the requirement that files
listed in the vendored manifest must not be edited in this repository’s PR diff.
Code

SLASHED-for-WP/admin-app/src/App.svelte[R1-16]

<script lang="ts">
  import { onMount, untrack } from 'svelte';
-  import { SlidersHorizontal, Eye, RotateCcw } from 'lucide-svelte';
-  import type { PreviewTemplate, SlashedToken } from './types';
+  import { SlidersHorizontal, Eye, RotateCcw } from '@lucide/svelte';
+  import type { PreviewTemplate, SlashedToken, ApiIndex } from './types';
  import StudioHeader from './components/shell/StudioHeader.svelte';
  import SidebarNav from './components/shell/SidebarNav.svelte';
  import StatusBar from './components/shell/StatusBar.svelte';
  import PreviewPanel from './components/shell/PreviewPanel.svelte';
  import DomainPanel from './components/DomainPanel.svelte';
-  import { fa } from './lib/codec';
+  import { generateCSS } from './lib/codec';
  import { loadInitialOverrides, injectLivePreview, saveOverrides, hasWpBoot } from './lib/persistence';
  import { domainOf } from './lib/domains';
  import tokensRaw from './data/api-index.generated.json';
  import CommandPalette from './components/CommandPalette.svelte';

-  const ALL_TOKENS = ((tokensRaw as any).tokens ?? tokensRaw) as SlashedToken[];
+  const ALL_TOKENS = ((tokensRaw as ApiIndex).tokens ?? tokensRaw) as SlashedToken[];
Relevance

⭐ Low

Vendored App.svelte is routinely changed via re-sync PRs; rule is “don’t hand-edit,” not “never
modify.”

PR-#122
PR-#127
PR-#128

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
.vendored-manifest.json lists src/App.svelte as a vendored file, and the PR includes changes to
SLASHED-for-WP/admin-app/src/App.svelte (see diff pointer). Modifying a file that is declared
vendored violates PR Compliance ID 1514148.

Rule 1514148: Do not modify vendored files listed in .vendored-manifest.json
SLASHED-for-WP/admin-app/.vendored-manifest.json[9-17]
SLASHED-for-WP/admin-app/src/App.svelte[1-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`SLASHED-for-WP/admin-app/src/App.svelte` is a vendored file (per `.vendored-manifest.json`) but is modified in this PR. Vendored files must not be directly changed in this repository.

## Issue Context
The vendored manifest is the source of truth for which files are considered vendored; any PR that changes a listed file violates the vendoring compliance policy.

## Fix Focus Areas
- SLASHED-for-WP/admin-app/src/App.svelte[1-16]
- SLASHED-for-WP/admin-app/.vendored-manifest.json[9-17]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Check skips API failures 🐞 Bug ☼ Reliability
Description
In --check mode, syncGhFile() (and vendorChromeRemote()) returns early on GitHub API 403/404
when the local vendored file exists, producing no drift finding and allowing npm run check to pass
even though upstream content was not verified (or is missing upstream). This undermines the
drift-check contract because missing/unverifiable upstream files don’t fail the check.
Code

SLASHED-for-WP/admin-app/scripts/sync-core.mjs[R454-463]

+  visitedSrcRel.add(relPosix);
  let content;
  try {
    content = await ghFetchContent(ghPath);
  } catch (err) {
    if ((err.status === 403 || err.status === 404) && existsSync(destPath)) {
-      process.stdout.write(`  keep  src/${rel} (GitHub API ${err.status} — keeping vendored copy)\n`);
+      if (!CHECK_MODE) process.stdout.write(`  keep  src/${rel} (GitHub API ${err.status} — keeping vendored copy)\n`);
      return;
    }
    throw err;
Relevance

⭐ Low

PR #73 deliberately keeps local copy on GitHub 403/404; failing check-mode would contradict that
behavior.

PR-#73

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
syncGhFile() adds the file to visitedSrcRel, then on 403/404 with an existing local file returns
without calling writeFile() (the only place drift gets recorded in check mode). finishCheck()
prints OK when driftFindings is empty, and the root scripts/check.js runs this script with
--check in CI, so these early-returns can cause a passing check without verifying upstream
content.

SLASHED-for-WP/admin-app/scripts/sync-core.mjs[446-465]
SLASHED-for-WP/admin-app/scripts/sync-core.mjs[493-509]
SLASHED-for-WP/admin-app/scripts/sync-core.mjs[206-217]
scripts/check.js[18-36]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`--check` mode can produce false negatives when GitHub fetches fail (403/404) but the local vendored file exists: the code returns early without recording drift and the overall check can still print OK.

## Issue Context
- `npm run check` runs `admin-app/scripts/sync-core.mjs --check` and expects non-zero exit when drift exists.
- Today, 403/404 + existing local file causes an early return (no `reportDrift(...)`), and `finishCheck()` reports OK when `driftFindings.length === 0`.

## Fix Focus Areas
- SLASHED-for-WP/admin-app/scripts/sync-core.mjs[446-470]
- SLASHED-for-WP/admin-app/scripts/sync-core.mjs[493-510]
- SLASHED-for-WP/admin-app/scripts/sync-core.mjs[206-217]

## What to change
- In `--check` mode, do **not** silently treat upstream fetch failures as “keep vendored copy”. Instead:
 - For **404**: record a drift finding (e.g., `orphan`/`missing_upstream`) for that path because it indicates the local file no longer exists upstream.
 - For **403** (and other non-OK statuses): record an `unverifiable` drift finding and set `process.exitCode = 1` (or push a drift finding and let `finishCheck()` set exitCode) so CI fails when it can’t validate.
- Consider moving `visitedSrcRel.add(relPosix)` to only occur after a successful upstream resolution (or add a separate `visitedAttempted` set) so orphan detection isn’t suppressed when a fetch failed.
- Apply the same check-mode behavior to `vendorChromeRemote()`’s 403/404 fallback.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
SLASHED-for-WP/admin-app/src/AppOverlay.svelte (1)

73-73: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

AppOverlay.svelte is missing the saveState: 'error' handling added to App.svelte.

App.svelte's identical save-state logic was updated in this PR to add an 'error' variant (type union, reset-on-error, and catch-block assignment), but this duplicate implementation in AppOverlay.svelte still resets to 'idle' on save failure — so a failed save via the frontend overlay gives the user no visible feedback (only a console.warn).

🐛 Proposed fix to mirror App.svelte's error handling
-  let saveState = $state<'idle' | 'saving' | 'saved'>('idle');
+  let saveState = $state<'idle' | 'saving' | 'saved' | 'error'>('idle');
-      if (saveState === 'saved') saveState = 'idle';
+      if (saveState === 'saved' || saveState === 'error') saveState = 'idle';
     } catch (err) {
       console.warn('slashed: save failed', err);
-      saveState = 'idle';
+      saveState = 'error';
     }

Also applies to: 155-155, 177-180

🤖 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 `@SLASHED-for-WP/admin-app/src/AppOverlay.svelte` at line 73,
AppOverlay.svelte’s duplicated save flow is missing the new error state, so
mirror the App.svelte save-state updates in the AppOverlay logic. Update the
saveState union to include 'error', change the save-failure path in the save
handler to set saveState to 'error' instead of resetting to 'idle', and ensure
the reset-on-error behavior matches the corresponding App.svelte implementation
in the save button/update flow.
🧹 Nitpick comments (4)
tests-php/RestControllerSanitizeRebemerElementMapTest.php (1)

116-125: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Cap test only verifies count, not retained-entry identity.

Confirms REBEMER_MAP_CAP truncation count but not that the first N entries (per insertion order) are the ones kept.

♻️ Optional: assert first-N entries retained
 		$result = Slashed_REST_Controller::sanitize_rebemer_element_map( $raw );

 		$this->assertCount( Slashed_REST_Controller::REBEMER_MAP_CAP, $result );
+		$this->assertArrayHasKey( 'type-0', $result );
+		$this->assertArrayNotHasKey( 'type-' . Slashed_REST_Controller::REBEMER_MAP_CAP, $result );
🤖 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 `@tests-php/RestControllerSanitizeRebemerElementMapTest.php` around lines 116 -
125, The cap test only checks the final count and should also verify which
entries are preserved after truncation. Update
test_caps_the_entry_count_at_rebemer_map_cap in
RestControllerSanitizeRebemerElementMapTest to assert that
sanitize_rebemer_element_map keeps the first REBEMER_MAP_CAP insertion-order
entries from the raw array, using Slashed_REST_Controller::REBEMER_MAP_CAP and
the existing $raw/$result variables.
scripts/check.js (1)

30-37: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Swallowed spawn errors give no diagnostic when a check script itself fails to launch.

If execFileSync throws for a reason other than the subprocess's own reported failure (e.g., missing file, spawn error), the bare catch { failed = true; } drops the error entirely with no message printed, since stdio: 'inherit' only surfaces the subprocess's own output. Logging err.message on catch would aid debugging such spawn-level failures.

♻️ Proposed fix
   try {
     execFileSync(process.execPath, [file, '--check'], { cwd, stdio: 'inherit' });
-  } catch {
+  } catch (err) {
+    console.error(`[check] ${label} failed to run: ${err.message}`);
     failed = true;
   }
🤖 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 `@scripts/check.js` around lines 30 - 37, The CHECKS loop in check.js swallows
launch-time failures from execFileSync, so missing files or spawn errors produce
no diagnostics. Update the try/catch around execFileSync in the CHECKS runner to
accept the thrown error object and log its message before setting failed = true,
so the script reports which check failed to start while preserving the existing
failed flag behavior.
SLASHED-for-WP/admin-app/scripts/sync-core.mjs (1)

174-186: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

listRelFiles follows symlinks via statSync, unlike the rest of the file.

Every other traversal in this file (copyLocalDir, the preserve-across-wipe reads) deliberately avoids following symlinks via O_NOFOLLOW. listRelFiles uses statSync, which dereferences symlinks, so a symlink cycle under src/ (or a symlinked directory) would cause unbounded recursion / a stack overflow during --check. Low real-world likelihood since src/ is normally populated only by this script's own writes, but worth aligning with the rest of the file's symlink-safety posture (e.g., lstatSync + skip symlinks).

🤖 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 `@SLASHED-for-WP/admin-app/scripts/sync-core.mjs` around lines 174 - 186,
`listRelFiles` currently dereferences symlinks with `statSync`, which can
recurse indefinitely on symlinked directories or cycles during `--check`. Update
this helper to match the symlink-safety used elsewhere in `sync-core.mjs` (for
example, `copyLocalDir` and the preserve-across-wipe reads) by using `lstatSync`
and skipping symlink entries instead of descending into them. Keep the recursive
traversal in `listRelFiles` returning only real files relative to `base`, and
use the existing helpers like `readdirSync`, `relative`, and `toPosix` unchanged
where possible.
SLASHED-for-WP/admin-app/src/lib/savedThemes.ts (1)

13-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Possible duplicate isStringRecord implementation.

Cross-file context shows persistence.ts defines an identical isStringRecord function (same body, same line range) and calls it internally in loadInitialOverrides without importing it from this module. If both files independently define this guard, any future change to validation logic will need to be kept in sync manually. Consider having one module own the canonical implementation and have the other import it.

🤖 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 `@SLASHED-for-WP/admin-app/src/lib/savedThemes.ts` around lines 13 - 18, The
`isStringRecord` type guard in `savedThemes.ts` is duplicated elsewhere with the
same logic, so there should be a single canonical implementation. Move or keep
`isStringRecord` in one owning module and update the other module (including
`loadInitialOverrides` in `persistence.ts`) to import and reuse that shared
function instead of maintaining an identical copy.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@SLASHED-for-WP/admin-app/package.json`:
- Around line 13-14: The package.json lifecycle hook is causing `check` to run
the full `sync-core.mjs` sync implicitly, which can mutate files and fetch data
during a simple validation command. Update the `precheck`/`check` setup so `npm
run check` only verifies drift, using the non-mutating `--check` or `--dry-run`
path in `scripts/sync-core.mjs` (or remove the `precheck` hook entirely if that
better matches the intended workflow). Keep the `precheck` and `check` entries
aligned with the script’s `--check` support so type-checking stays side-effect
free.

In `@SLASHED-for-WP/admin-app/src/types.ts`:
- Around line 22-83: Move the type definitions change out of the generated app
copy and into the upstream framework source, since src/types.ts in this repo is
not protected by .syncignore and will be overwritten on the next resync. Update
the corresponding types file in the framework repo, keeping the new interfaces
DecodeOptions, ShareOptions, ApiIndex, ClassIndex, TokenRegistry, ApiIndexToken,
SlashedClass, and TokenRegistryEntry aligned with codec.ts and the generated
JSON shapes.

In `@SLASHED-for-WP/includes/class-color-math.php`:
- Around line 32-38: The parse_oklch() parser in class-color-math.php only
recognizes unitless L C H values with optional deg, so update it to also accept
CSS percentage lightness and an optional "/ alpha" component. Adjust the regex
and returned parsing in parse_oklch() so inputs like oklch(45% 0.2 264 / 0.5)
are accepted, while preserving the existing behavior for current OKLCH formats.

---

Outside diff comments:
In `@SLASHED-for-WP/admin-app/src/AppOverlay.svelte`:
- Line 73: AppOverlay.svelte’s duplicated save flow is missing the new error
state, so mirror the App.svelte save-state updates in the AppOverlay logic.
Update the saveState union to include 'error', change the save-failure path in
the save handler to set saveState to 'error' instead of resetting to 'idle', and
ensure the reset-on-error behavior matches the corresponding App.svelte
implementation in the save button/update flow.

---

Nitpick comments:
In `@scripts/check.js`:
- Around line 30-37: The CHECKS loop in check.js swallows launch-time failures
from execFileSync, so missing files or spawn errors produce no diagnostics.
Update the try/catch around execFileSync in the CHECKS runner to accept the
thrown error object and log its message before setting failed = true, so the
script reports which check failed to start while preserving the existing failed
flag behavior.

In `@SLASHED-for-WP/admin-app/scripts/sync-core.mjs`:
- Around line 174-186: `listRelFiles` currently dereferences symlinks with
`statSync`, which can recurse indefinitely on symlinked directories or cycles
during `--check`. Update this helper to match the symlink-safety used elsewhere
in `sync-core.mjs` (for example, `copyLocalDir` and the preserve-across-wipe
reads) by using `lstatSync` and skipping symlink entries instead of descending
into them. Keep the recursive traversal in `listRelFiles` returning only real
files relative to `base`, and use the existing helpers like `readdirSync`,
`relative`, and `toPosix` unchanged where possible.

In `@SLASHED-for-WP/admin-app/src/lib/savedThemes.ts`:
- Around line 13-18: The `isStringRecord` type guard in `savedThemes.ts` is
duplicated elsewhere with the same logic, so there should be a single canonical
implementation. Move or keep `isStringRecord` in one owning module and update
the other module (including `loadInitialOverrides` in `persistence.ts`) to
import and reuse that shared function instead of maintaining an identical copy.

In `@tests-php/RestControllerSanitizeRebemerElementMapTest.php`:
- Around line 116-125: The cap test only checks the final count and should also
verify which entries are preserved after truncation. Update
test_caps_the_entry_count_at_rebemer_map_cap in
RestControllerSanitizeRebemerElementMapTest to assert that
sanitize_rebemer_element_map keeps the first REBEMER_MAP_CAP insertion-order
entries from the raw array, using Slashed_REST_Controller::REBEMER_MAP_CAP and
the existing $raw/$result variables.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 18e19f91-9531-4e31-aaf6-af77f2fb7427

📥 Commits

Reviewing files that changed from the base of the PR and between d0f9cad and d8deaea.

⛔ Files ignored due to path filters (2)
  • SLASHED-for-WP/admin-app/package-lock.json is excluded by !**/package-lock.json
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (54)
  • .gitattributes
  • .github/workflows/ci.yml
  • .gitignore
  • CLAUDE.md
  • SLASHED-for-WP/admin-app/.syncignore
  • SLASHED-for-WP/admin-app/.vendored-manifest.json
  • SLASHED-for-WP/admin-app/framework-css/core/layout.css
  • SLASHED-for-WP/admin-app/framework-css/core/themes.css
  • SLASHED-for-WP/admin-app/framework-css/core/tokens.css
  • SLASHED-for-WP/admin-app/package.json
  • SLASHED-for-WP/admin-app/scripts/sync-core.mjs
  • SLASHED-for-WP/admin-app/src/App.svelte
  • SLASHED-for-WP/admin-app/src/AppOverlay.svelte
  • SLASHED-for-WP/admin-app/src/components/DomainPanel.svelte
  • SLASHED-for-WP/admin-app/src/components/panels/CheatsheetPanel.svelte
  • SLASHED-for-WP/admin-app/src/components/panels/ExportPanel.svelte
  • SLASHED-for-WP/admin-app/src/components/panels/HomePanel.svelte
  • SLASHED-for-WP/admin-app/src/components/panels/ThemesPanel.svelte
  • SLASHED-for-WP/admin-app/src/components/shell/PreviewPanel.svelte
  • SLASHED-for-WP/admin-app/src/components/shell/SidebarNav.svelte
  • SLASHED-for-WP/admin-app/src/components/shell/StudioHeader.svelte
  • SLASHED-for-WP/admin-app/src/lib/codec.ts
  • SLASHED-for-WP/admin-app/src/lib/persistence.ts
  • SLASHED-for-WP/admin-app/src/lib/previewResolver.svelte.ts
  • SLASHED-for-WP/admin-app/src/lib/savedThemes.ts
  • SLASHED-for-WP/admin-app/src/plugin-main.ts
  • SLASHED-for-WP/admin-app/src/types.ts
  • SLASHED-for-WP/assets/admin-app/app.css
  • SLASHED-for-WP/assets/admin-app/app.js
  • SLASHED-for-WP/includes/class-category-map.php
  • SLASHED-for-WP/includes/class-color-math.php
  • SLASHED-for-WP/includes/class-color-resolver.php
  • SLASHED-for-WP/includes/class-inventory.php
  • SLASHED-for-WP/integrations/bricks/assets/editor-app/app.js
  • SLASHED-for-WP/integrations/bricks/editor-app/src/lib/color-model.js
  • SLASHED-for-WP/integrations/bricks/includes/class-color-resolver.php
  • SLASHED-for-WP/integrations/bricks/includes/class-inventory.php
  • SLASHED-for-WP/integrations/bricks/slashed-bricks.php
  • SLASHED-for-WP/integrations/gutenberg/includes/class-inventory.php
  • SLASHED-for-WP/integrations/gutenberg/includes/class-presets.php
  • SLASHED-for-WP/integrations/gutenberg/slashed-gutenberg.php
  • composer.json
  • package.json
  • phpunit.xml
  • scripts/check.js
  • tests-php/CssGeneratorValidateOverrideValueTest.php
  • tests-php/CssParserTest.php
  • tests-php/RestControllerSanitizeRebemerElementMapTest.php
  • tests-php/bootstrap.php
  • tests/apply.test.js
  • tests/color-model-cross-impl.test.js
  • tests/php-harness/classify-color.php
  • tests/playwright-admin.js
  • tests/sync-core.test.js
💤 Files with no reviewable changes (1)
  • SLASHED-for-WP/admin-app/.syncignore

Comment thread SLASHED-for-WP/admin-app/package.json Outdated
Comment on lines +22 to +83
// SL-017/024: types for the two consumed-generated-JSON shapes and codec.ts's
// options objects, replacing `any` at their 7 call sites.

/** A token entry as it appears in data/api-index.generated.json's `tokens` array. */
export interface ApiIndexToken extends SlashedToken {
fallbackOnly?: boolean;
optional?: boolean;
layer?: string | null;
bundles?: string[];
}

/** Shape of data/api-index.generated.json (configurator/scripts/sync-api.mjs's output). */
export interface ApiIndex {
_sync?: Record<string, unknown>;
tokens: ApiIndexToken[];
}

/** A class entry as it appears in data/classes.generated.json's `classes` array. */
export interface SlashedClass {
name: string;
selector: string;
kind: string;
category: string;
group?: string;
description?: string;
optional?: boolean;
layer?: string | null;
}

/** Shape of data/classes.generated.json (configurator/scripts/sync-api.mjs's output). */
export interface ClassIndex {
_sync?: Record<string, unknown>;
classes: SlashedClass[];
}

/** A registry entry as it appears in data/token-registry.generated.json's `tokens` array. */
export interface TokenRegistryEntry {
id: number;
name: string;
removed?: boolean;
}

/** Shape of data/token-registry.generated.json, consumed by codec.ts's encode/decode. */
export interface TokenRegistry {
_meta?: Record<string, unknown>;
tokens: TokenRegistryEntry[];
}

/** Options accepted by codec.ts's decode(). */
export interface DecodeOptions {
sanitize?: (value: string) => string;
isKnown?: (tokenName: string) => boolean;
}

/**
* Options accepted by codec.ts's readShareFromHash() / readShareFromHashIfPresent().
* Deliberately omits `sanitize` (unlike DecodeOptions): these are the public
* share-link entry points and always force sanitizeValue as a CSS-injection
* safeguard, so a caller-supplied sanitize is never honoured.
*/
export type ShareOptions = Pick<DecodeOptions, "isKnown">;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate and print .syncignore, then check whether the edited src files are protected.
fd -H -t f '^\.syncignore$' SLASHED-for-WP/admin-app --exec sh -c 'echo "== {} =="; cat "{}"'
echo "---- membership check ----"
fd -H -t f '^\.syncignore$' SLASHED-for-WP/admin-app --exec grep -nE 'types\.ts|codec\.ts|lib/' {} \; || echo "no matching entries found"

Repository: codeslash-dev/SLASHED-Plugins

Length of output: 583


Move this change upstream

SLASHED-for-WP/admin-app/.syncignore only exempts src/plugin-main.ts and src/AppOverlay.svelte; src/types.ts is not protected here, so this edit will be overwritten on the next core resync unless it lives in the framework repo.

🤖 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 `@SLASHED-for-WP/admin-app/src/types.ts` around lines 22 - 83, Move the type
definitions change out of the generated app copy and into the upstream framework
source, since src/types.ts in this repo is not protected by .syncignore and will
be overwritten on the next resync. Update the corresponding types file in the
framework repo, keeping the new interfaces DecodeOptions, ShareOptions,
ApiIndex, ClassIndex, TokenRegistry, ApiIndexToken, SlashedClass, and
TokenRegistryEntry aligned with codec.ts and the generated JSON shapes.

Source: Path instructions

Comment on lines +32 to +38
public static function parse_oklch( $str ) {
$str = trim( $str );
if ( ! preg_match( '/oklch\(\s*([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)(?:deg)?\s*\)/i', $str, $m ) ) {
return null;
}
return array( (float) $m[1], (float) $m[2], (float) $m[3] );
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find where color-*-dark overrides / oklch strings originate to confirm accepted formats
rg -nP -C3 "color-[a-z]+-dark|oklch\(" --type=php --type=ts --type=js

Repository: codeslash-dev/SLASHED-Plugins

Length of output: 167


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant files and inspect the parser plus its callers/inputs.
git ls-files 'SLASHED-for-WP/includes/**' | rg 'class-color-math\.php|class-color-resolver\.php|color'
printf '\n--- outline: class-color-math.php ---\n'
ast-grep outline SLASHED-for-WP/includes/class-color-math.php --view expanded || true
printf '\n--- outline: class-color-resolver.php ---\n'
ast-grep outline SLASHED-for-WP/includes/class-color-resolver.php --view expanded || true
printf '\n--- parse_oklch + callers ---\n'
rg -n -C 4 'parse_oklch|hex_to_oklch|derive_dark_sources|oklch\(' SLASHED-for-WP/includes/class-color-math.php SLASHED-for-WP/includes/class-color-resolver.php

Repository: codeslash-dev/SLASHED-Plugins

Length of output: 14153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Trace where `color_values` comes from and which formats are documented/produced.
rg -n -C 4 'color_values|--sf-color-.*-dark|initial-value|oklch\(' SLASHED-for-WP/includes SLASHED-for-WP -g '!*.min.*'
printf '\n--- class-color-resolver.php (relevant ranges) ---\n'
sed -n '1,120p' SLASHED-for-WP/includes/class-color-resolver.php
printf '\n--- class-color-resolver.php (resolve_sources / derive_dark_sources) ---\n'
sed -n '280,360p' SLASHED-for-WP/includes/class-color-resolver.php
printf '\n--- class-color-math.php (parse_oklch docs + regex) ---\n'
sed -n '20,45p' SLASHED-for-WP/includes/class-color-math.php

Repository: codeslash-dev/SLASHED-Plugins

Length of output: 50387


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the exact inputs flowing into the resolver and the token formats that reach it.
rg -n -C 3 'class-color-resolver|color_values|--sf-color-.*-dark|--sf-color-.*-light|parse_oklch|hex_to_oklch' \
  SLASHED-for-WP/includes \
  SLASHED-for-WP/admin-app/src \
  --glob '!**/*.map' --glob '!**/node_modules/**' --glob '!**/*.min.*' \
  | sed -n '1,260p'

Repository: codeslash-dev/SLASHED-Plugins

Length of output: 22098


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '60,130p' SLASHED-for-WP/includes/class-css-parser.php
printf '\n---\n'
sed -n '1,120p' SLASHED-for-WP/includes/class-token-store.php

Repository: codeslash-dev/SLASHED-Plugins

Length of output: 6992


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect how override values are validated before they can reach the resolver.
rg -n -C 4 'sanitize.*color|oklch\(|source-dark|source-light|update_overrides|get_overrides|tokens/overrides|rest' \
  SLASHED-for-WP/includes \
  SLASHED-for-WP/admin-app/src \
  --glob '!**/*.map' --glob '!**/*.min.*' \
  | sed -n '1,240p'

Repository: codeslash-dev/SLASHED-Plugins

Length of output: 20418


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the override validation/emission rules to see what values can reach `parse_oklch`.
sed -n '1,220p' SLASHED-for-WP/includes/class-css-generator.php
printf '\n---\n'
rg -n -C 3 'validate_override_value|is_css_safe|emit|override' SLASHED-for-WP/includes/class-css-generator.php

Repository: codeslash-dev/SLASHED-Plugins

Length of output: 14590


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the color validation branch that feeds stored overrides.
sed -n '320,384p' SLASHED-for-WP/includes/class-css-generator.php

Repository: codeslash-dev/SLASHED-Plugins

Length of output: 2393


Expand parse_oklch() to handle CSS oklch() percentages and / alpha
The override save path already accepts oklch() syntax, but this parser only handles unitless L C H plus optional deg. Values like oklch(45% 0.2 264 / 0.5) fall back to the auto-derived dark source in the PHP preview.

🤖 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 `@SLASHED-for-WP/includes/class-color-math.php` around lines 32 - 38, The
parse_oklch() parser in class-color-math.php only recognizes unitless L C H
values with optional deg, so update it to also accept CSS percentage lightness
and an optional "/ alpha" component. Adjust the regex and returned parsing in
parse_oklch() so inputs like oklch(45% 0.2 264 / 0.5) are accepted, while
preserving the existing behavior for current OKLCH formats.

…tate

CodeRabbit review on #146:

- admin-app's "precheck" hook ran a real sync-core.mjs sync (writes,
  GitHub fetches) as an npm pre-script side effect of "npm run check"
  (svelte-check), the opposite of what --check/--dry-run mode exists
  for. Add --check so precheck verifies drift and fails loudly on
  staleness instead of silently mutating files before every type-check.

- AppOverlay.svelte's save flow is a plugin-owned (non-vendored)
  duplicate of App.svelte's, and never got the 'error' saveState App.svelte
  gained — a failed save from the frontend overlay silently reset to
  'idle' with only a console.warn, unlike the main admin panel which
  now shows a clear error state. Mirrors App.svelte/StudioHeader's
  saveState union, reset-on-error, and catch-block assignment, plus a
  matching AlertTriangle/red-tinted icon state in the overlay's compact
  save button.

Verified: svelte-check passes with no new errors (the one pre-existing
plugin-main.ts import-extension warning predates this PR), and the
full node --test suite (142 tests) passes.
@jackgranatowski
jackgranatowski merged commit 8a1cd70 into main Jul 2, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants