=== Theme SCSS Compiler ===
Contributors:      simonmista
Tags:              scss, sass, css, compiler, theme
Requires at least: 6.3
Tested up to:      6.9
Stable tag:        1.0.2
Requires PHP:      8.1
License:           GPLv2 or later
License URI:       https://www.gnu.org/licenses/gpl-2.0.html

Compile SCSS / Sass to CSS in the WP admin. Multiple file pairs, per-file versioning, @import-aware change detection, optional auto-compile.

== Description ==

Compile SCSS (Sass) to CSS straight from the WordPress admin. Theme SCSS Compiler manages as many SCSS → CSS file pairs as your theme needs, under Tools → Theme SCSS Compiler. It uses the bundled scssphp library, so there is no Node.js, no build step and no command line. SCSS is the modern Sass syntax: plain CSS plus nesting, variables and `@import`.

= Features =

* **Multiple file pairs** – as many SCSS → CSS pairs as your theme needs, paths relative to the active theme.
* **Frontend or Admin per pair** – each pair enqueues on `wp_enqueue_scripts` or `admin_enqueue_scripts`.
* **Per-file versioning** – every pair has its own version, applied through the `style_loader_src` filter. Your theme files stay untouched.
* **Smart change detection** – a version only bumps when that pair's compiled CSS really changed.
* **`@import`-aware recompile** – every partial (`@import`, `@use`, `@forward`) is tracked. Edit a partial and the next admin page load recompiles.
* **Compressed or expanded output** – minified for production, readable for development.
* **Auto-compile** – missing or stale CSS is rebuilt on the next admin page load, so a deploy never leaves you with unstyled pages.
* **Auto-enqueue** – the plugin calls `wp_enqueue_style()` for each pair and skips files already registered, so output is never duplicated.
* **Manual compile button** – one click, live feedback, persistent error display.
* **Concurrent-compile lock** – two admins compiling at once can't corrupt the CSS.
* **Code-first config** – set every option as a PHP constant in `wp-config.php`, your theme, or `.env` / Bedrock.
* **Admin-only** – every endpoint needs `manage_options`. Change that with the `tscsscompiler_capability` filter.
* **Modern and accessible** – PHP 8.1+, WCAG 2.1 AA admin UI, German translation included.

= Privacy / GDPR =

This plugin makes **no external HTTP requests**, sets **no cookies**, runs **no telemetry** and does **not** track users. All processing happens locally on your server. The bundled libraries (scssphp, league/uri, symfony/filesystem, PSR HTTP interfaces) are MIT licensed and GPL-compatible; their source is included in the plugin's `vendor/` directory.

== Installation ==

1. Upload the `theme-scss-compiler` folder to `/wp-content/plugins/`.
2. Activate the plugin in **Plugins**.
3. Go to **Tools → Theme SCSS Compiler** and set your SCSS / CSS paths and the per-pair context.
4. Click **Compile now**, or just load any admin page. Auto-compile is on by default and builds the CSS the first time.

Prefer code-based configuration? See the FAQ on constants for `wp-config.php`, your theme, or `.env` / Bedrock.

== Frequently Asked Questions ==

= Which SCSS version does this support? =

The plugin bundles scssphp 2.1. It supports the SCSS you write day to day: nesting, variables, mixins, functions and `@import` / `@use` / `@forward`. scssphp is a PHP port of Sass, not a byte-for-byte copy of Dart Sass, so a few Dart-only built-in functions are not available. For normal theme stylesheets that almost never matters. Check the compiled CSS once after you move a theme over.

= I'm new to SCSS. What does it actually look like? =

SCSS is CSS with a few extras. Every valid CSS file is already valid SCSS, so you can start small.

**Nesting.** Write child selectors inside their parent instead of repeating it:

    // SCSS
    .card {
        padding: 1rem;
        a { color: rebeccapurple; }
    }

    /* compiled CSS */
    .card { padding: 1rem; }
    .card a { color: rebeccapurple; }

**The `&` parent reference.** Handy for states like `:hover` or an `.active` class:

    // SCSS
    .button {
        background: #0073aa;
        &:hover  { background: #005177; }
        &.active { background: #003f66; }
    }

    /* compiled CSS */
    .button { background: #0073aa; }
    .button:hover  { background: #005177; }
    .button.active { background: #003f66; }

**Variables.** Set a value once, reuse it everywhere:

    $brand: #0073aa;

    a       { color: $brand; }
    .button { background: $brand; }

Save the file, open any wp-admin page, and the plugin compiles it to the CSS your theme loads. Full guide: https://sass-lang.com/guide/

= Does the plugin edit my theme files? =

No. It never edits your SCSS, your `functions.php` or your `style.css` header. The only file it writes is the compiled CSS target you set for each pair (for example `assets/css/style.css`). That file is overwritten on every compile, so treat it as build output, not something you hand-edit. Cache-busting versions are added at runtime through the `style_loader_src` filter, not by rewriting files.

= When does a version actually get bumped? =

Only when that pair's compiled CSS differs from the file already on disk. Comment-only edits, or changes that produce identical CSS, do not bump the version. Other pairs keep theirs.

= How does the per-pair Context (Frontend / Admin) setting work? =

Each pair has a *Context*. **Frontend** pairs are enqueued on `wp_enqueue_scripts` (public site only). **Admin** pairs are enqueued on `admin_enqueue_scripts` (wp-admin only). The version filter applies in both. New pairs default to Frontend.

= Who can access the admin page? =

By default only administrators (the `manage_options` capability). The menu, the save handler, the AJAX compile endpoint and the auto-compile hook all check it, so Editors and Authors can't see or use the plugin. To allow another role, use the `tscsscompiler_capability` filter:

    add_filter( 'tscsscompiler_capability', static function () {
        return 'edit_theme_options';
    } );

= Where do the compiled CSS files go, and what if they are missing? =

Each CSS target is written inside the active theme at the path you set. Commit those files or add them to `.gitignore`, your call. With **Auto-compile** on (the default), every admin page load checks whether each CSS file exists and whether its SCSS source or any `@import`-ed partial is newer, and rebuilds anything missing or stale. So a fresh deploy or `git pull` without the compiled CSS won't leave you with an unstyled site.

= Does it detect changes in `@import`-ed partials? =

Yes. After each successful compile the plugin records every file scssphp pulled in, including nested `@import` / `@use` / `@forward` chains. Auto-compile compares each tracked partial's modified time against the CSS output. So if `style.scss` does `@import "menu-styles";` and you only edit `menu-styles.scss`, the next admin page load recompiles. You don't have to touch `style.scss`.

= Does compiling ever run on the front end, or for visitors? =

No. Compiling only happens in wp-admin, for logged-in users who pass the capability check. AJAX and WP-Cron requests are excluded. Visitors are only ever served the already-compiled CSS, so there is no SCSS work on the front end.

= Where do I see compilation errors? =

On the Tools → Theme SCSS Compiler page. A failed compile stays in a "Last compile error" panel (server paths stripped from the message) until the next successful compile clears it. The **Compile now** button also reports success or failure live.

= Can the CSS target be outside the theme? =

No. Both the SCSS source and the CSS target are resolved relative to the active theme, and any path with `..` is rejected. The plugin only reads and writes inside the active theme.

= Does it work with child themes? =

Yes. Paths resolve against the active stylesheet directory, which is the child theme when one is active. Point the pairs at whichever theme ships the SCSS.

= Should I let the plugin enqueue my CSS, or do it in `functions.php`? =

Either works. **Auto-enqueue** is on by default: the plugin calls `wp_enqueue_style()` for each pair on its context, at `PHP_INT_MAX` priority, and skips any URL already registered, so it never duplicates output. Want to manage assets yourself? Turn the option off and call `wp_enqueue_style()` in your theme.

= Can I configure the plugin from code instead of the admin form? =

Yes. Define `TSCSSCOMPILER_PAIRS` and the plugin reads pairs from there instead of the database. The admin form becomes read-only and saving is disabled.

    define( 'TSCSSCOMPILER_PAIRS', [
        [ 'scss_path' => 'assets/scss/style.scss',       'css_path' => 'assets/css/style.css',       'version' => '1.0.0', 'context' => 'frontend' ],
        [ 'scss_path' => 'assets/scss/style-admin.scss', 'css_path' => 'assets/css/style-admin.css', 'version' => '1.0.0', 'context' => 'admin'    ],
    ] );

Toggle the rest individually:

* `TSCSSCOMPILER_AUTO_COMPILE` – `true` / `false`
* `TSCSSCOMPILER_BUMP_VERSION` – `true` / `false` (note: bumping is a no-op when defined via constant — versions live in your code)
* `TSCSSCOMPILER_AUTO_ENQUEUE` – `true` / `false`
* `TSCSSCOMPILER_OUTPUT_STYLE` – `'compressed'` / `'expanded'`

= Where can I put the `define()` calls? =

Three common places:

**1. `wp-config.php`** (loaded first, recommended):

    define( 'TSCSSCOMPILER_OUTPUT_STYLE', 'compressed' );

**2. Theme `functions.php`** (hook into `after_setup_theme` so the constant exists when the plugin reads it):

    add_action( 'after_setup_theme', static function () {
        if ( ! defined( 'TSCSSCOMPILER_PAIRS' ) ) {
            define( 'TSCSSCOMPILER_PAIRS', [
                [ 'scss_path' => 'assets/scss/style.scss', 'css_path' => 'assets/css/style.css', 'version' => '1.0.0', 'context' => 'frontend' ],
            ] );
        }
    } );

**3. `.env` + `wp-config.php` (Bedrock)**:

    # .env
    TSCSSCOMPILER_AUTO_ENQUEUE=true
    TSCSSCOMPILER_OUTPUT_STYLE=compressed

    # wp-config.php (or config/application.php in Bedrock)
    if ( getenv( 'TSCSSCOMPILER_AUTO_ENQUEUE' ) !== false ) {
        define( 'TSCSSCOMPILER_AUTO_ENQUEUE', filter_var( getenv( 'TSCSSCOMPILER_AUTO_ENQUEUE' ), FILTER_VALIDATE_BOOLEAN ) );
    }
    if ( $style = getenv( 'TSCSSCOMPILER_OUTPUT_STYLE' ) ) {
        define( 'TSCSSCOMPILER_OUTPUT_STYLE', $style );
    }

Pairs are a nested array and don't fit in `.env`. Define them in `wp-config.php` (or `config/application.php` in Bedrock) instead.

= Why do I see a notice "Configured via wp-config.php"? =

`TSCSSCOMPILER_PAIRS` is defined somewhere (wp-config, theme, or an `.env` bridge). The form is read-only in that state. Edit the source where the constant is defined to make changes. Boolean constants like `…_AUTO_COMPILE` show as locked switches too.

= Is the plugin accessible? =

Yes, the admin UI targets WCAG 2.1 AA: semantic headings, ARIA roles for status and alert regions, keyboard-focusable controls with a visible focus ring, and sufficient colour contrast. Decorative icons are marked `aria-hidden`.

= Is there a German translation? =

Yes, included out of the box. Every admin string, hint and error message is translated (`de_DE`).

== Screenshots ==

1. Settings page under Tools → Theme SCSS Compiler in production-default mode — multiple file pairs with per-pair version and Frontend/Admin context, auto-compile and auto-enqueue enabled.
2. Same page in development workflow — Expanded output for readable CSS, manual-compile mode with success feedback after a Compile-now click.

== Changelog ==

= 1.0.2 =
* Fixed: "Compile now" now updates each changed pair's Version field instantly, without a page reload.
* Documentation: expanded FAQ (beginner SCSS primer, front-end behaviour, error display, git/deploy, theme-relative paths, child themes); tempered the scssphp / Dart Sass wording.
* Synced bundled libraries to the locked versions (no behaviour change).
* No configuration, options or public API changes.

= 1.0.1 =
* Fixed: editing an `@import`ed partial did not always trigger auto-recompile. When the SCSS compiler reported an included file via a non-canonical path (containing `..`, `.` or doubled slashes), the recorded dependency was silently discarded and changes to that partial went undetected. Included paths are now collapsed before being stored.
* Fixed: on installs where the theme directory is a symlink (e.g. Bedrock-style layouts), dependency tracking failed entirely – the resolved (realpath) source paths never matched the unresolved theme directory, so every dependency was dropped. The theme directory is now resolved consistently for all comparisons.
* Note: both issues affected automatic compilation only. The manual "Compile now" button was never affected, as it compiles unconditionally without consulting the dependency cache.
* No changes to configuration, options or public API.

= 1.0.0 =
* Initial release.
* Multiple SCSS → CSS pairs with per-pair version and Frontend/Admin context.
* `@import`-aware dependency tracking – every imported partial is recorded; editing a partial alone triggers auto-recompile.
* Smart change detection via content comparison – versions only bump on real CSS changes.
* Auto-compile on missing or stale output.
* Auto-enqueue with duplicate detection (runs at `PHP_INT_MAX` priority).
* Manual compile button with AJAX feedback.
* Concurrent-compile lock to prevent race conditions on busy multi-admin sites.
* Code-first configuration via `TSCSSCOMPILER_*` constants.
* `tscsscompiler_capability` filter for granting access to custom roles.
* Filesystem writes via WordPress `WP_Filesystem` API.
* WCAG 2.1 AA compliant admin UI.
* German translation included.
* PHP 8.1+ required.
* Bundles scssphp 2.1 (MIT) and dependencies.

== Upgrade Notice ==

= 1.0.2 =
"Compile now" refreshes changed Version fields without a reload, plus expanded docs.

= 1.0.1 =
Bug-fix release. Recommended for everyone using auto-compile, and required if your theme directory is a symlink (e.g. Bedrock) – without this fix auto-recompile of changed `@import` partials does not work in that setup.

= 1.0.0 =
Initial release.
