From fc38dfef1073efb1fa139d545718390083f276dd Mon Sep 17 00:00:00 2001 From: deepench Date: Fri, 25 Sep 2026 05:15:48 +0100 Subject: [PATCH 01/19] Fix - CI dependency install fails with pnpm 11 on Node 20 runners --- package.json | 2 +- pnpm-lock.yaml | 334 +++++++++++++++++++++----------------------- pnpm-workspace.yaml | 3 + 3 files changed, 165 insertions(+), 174 deletions(-) diff --git a/package.json b/package.json index 82f2300d..4a34ac9d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "packageManager": "pnpm@11.24.0", + "packageManager": "pnpm@9.15.9", "name": "spacious", "title": "Spacious", "version": "1.9.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a1cfff9c..b33d704f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,10 +13,10 @@ importers: version: 8.6.5 browser-sync: specifier: ~2.26.3 - version: 2.26.14(debug@4.3.2(supports-color@7.2.0))(supports-color@7.2.0) + version: 2.26.14 gulp: specifier: ~4.0.0 - version: 4.0.2(supports-color@7.2.0) + version: 4.0.2 devDependencies: '@playwright/test': specifier: ^1.48.0 @@ -62,10 +62,10 @@ importers: version: 5.1.0 gulp-watch: specifier: ^4.3.11 - version: 4.3.11(supports-color@7.2.0) + version: 4.3.11 node-sass: specifier: ^9.0.0 - version: 9.0.0(bluebird@3.7.2)(supports-color@7.2.0) + version: 9.0.0 typescript: specifier: ^5.4.5 version: 5.9.3 @@ -507,8 +507,8 @@ packages: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} - caniuse-lite@1.0.30001810: - resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + caniuse-lite@1.0.30001812: + resolution: {integrity: sha512-qN+QNNBr93TCmFrmte0bBCjSDMuRvt78VlHT99qIGPszm4QsqCX8lnyWUFkHi8B7ZBAPq8SH+UqyXNy/odMdng==} chalk@0.2.1: resolution: {integrity: sha512-nmVapomwGksziCuynboy7I+dtW4ytIdqXPlrfY/ySx8l8EqFRGHyA04q6NMNpOri8XliGUGwXyfScVl48zFHbw==} @@ -871,8 +871,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.436: - resolution: {integrity: sha512-iG79/xCF3iThADicbgbES2+liFYmTvelo7jWcEfg6lQ+Adrh7zShrXSg2ZdEDLbKjJ298kGAKMnnzVWobp0L+g==} + electron-to-chromium@1.5.439: + resolution: {integrity: sha512-qu6QIPXhsb+CRcAiTMNjR4A1y/7tCYKkKjr5CZXRVih6qkDf79peZ2BpEU3qoDBNCRrcy3Mra3X9nG5oruuA7Q==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3349,9 +3349,9 @@ snapshots: after@0.8.2: {} - agent-base@6.0.2(supports-color@7.2.0): + agent-base@6.0.2: dependencies: - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -3403,9 +3403,9 @@ snapshots: micromatch: 2.3.11 normalize-path: 2.1.1 - anymatch@2.0.0(supports-color@7.2.0): + anymatch@2.0.0: dependencies: - micromatch: 3.1.10(supports-color@7.2.0) + micromatch: 3.1.10 normalize-path: 2.1.1 transitivePeerDependencies: - supports-color @@ -3544,15 +3544,15 @@ snapshots: autoprefixer@8.6.5: dependencies: browserslist: 3.2.8 - caniuse-lite: 1.0.30001810 + caniuse-lite: 1.0.30001812 normalize-range: 0.1.2 num2fraction: 1.2.2 postcss: 6.0.23 postcss-value-parser: 3.3.1 - axios@0.21.4(debug@4.3.2(supports-color@7.2.0)): + axios@0.21.4(debug@4.3.2): dependencies: - follow-redirects: 1.16.0(debug@4.3.2(supports-color@7.2.0)) + follow-redirects: 1.16.0(debug@4.3.2) transitivePeerDependencies: - debug @@ -3629,7 +3629,7 @@ snapshots: preserve: 0.2.0 repeat-element: 1.1.4 - braces@2.3.2(supports-color@7.2.0): + braces@2.3.2: dependencies: arr-flatten: 1.1.0 array-unique: 0.3.2 @@ -3637,7 +3637,7 @@ snapshots: fill-range: 4.0.0 isobject: 3.0.1 repeat-element: 1.1.4 - snapdragon: 0.8.2(supports-color@7.2.0) + snapdragon: 0.8.2 snapdragon-node: 2.1.1 split-string: 3.1.0 to-regex: 3.0.2 @@ -3654,28 +3654,28 @@ snapshots: fresh: 0.5.2 mitt: 1.2.0 - browser-sync-ui@2.29.3(supports-color@7.2.0): + browser-sync-ui@2.29.3: dependencies: async-each-series: 0.1.1 chalk: 4.1.2 connect-history-api-fallback: 1.6.0 immutable: 3.8.4 server-destroy: 1.0.1 - socket.io-client: 4.8.3(supports-color@7.2.0) + socket.io-client: 4.8.3 stream-throttle: 0.1.3 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - browser-sync@2.26.14(debug@4.3.2(supports-color@7.2.0))(supports-color@7.2.0): + browser-sync@2.26.14: dependencies: browser-sync-client: 2.29.3 - browser-sync-ui: 2.29.3(supports-color@7.2.0) + browser-sync-ui: 2.29.3 bs-recipes: 1.3.4 bs-snippet-injector: 2.0.1 chokidar: 3.6.0 - connect: 3.6.6(supports-color@7.2.0) + connect: 3.6.6 connect-history-api-fallback: 1.6.0 dev-ip: 1.0.1 easy-extender: 2.3.4 @@ -3683,21 +3683,21 @@ snapshots: etag: 1.8.1 fresh: 0.5.2 fs-extra: 3.0.1 - http-proxy: 1.18.1(debug@4.3.2(supports-color@7.2.0)) + http-proxy: 1.18.1 immutable: 3.8.4 - localtunnel: 2.0.2(supports-color@7.2.0) + localtunnel: 2.0.2 micromatch: 4.0.8 opn: 5.3.0 portscanner: 2.1.1 qs: 6.2.3 raw-body: 2.5.3 - resp-modifier: 6.0.2(supports-color@7.2.0) + resp-modifier: 6.0.2 rx: 4.1.0 - send: 0.16.2(supports-color@7.2.0) - serve-index: 1.9.1(supports-color@7.2.0) - serve-static: 1.13.2(supports-color@7.2.0) + send: 0.16.2 + serve-index: 1.9.1 + serve-static: 1.13.2 server-destroy: 1.0.1 - socket.io: 2.4.0(supports-color@7.2.0) + socket.io: 2.4.0 ua-parser-js: 0.7.41 yargs: 15.4.1 transitivePeerDependencies: @@ -3708,8 +3708,8 @@ snapshots: browserslist@3.2.8: dependencies: - caniuse-lite: 1.0.30001810 - electron-to-chromium: 1.5.436 + caniuse-lite: 1.0.30001812 + electron-to-chromium: 1.5.439 bs-recipes@1.3.4: {} @@ -3723,7 +3723,7 @@ snapshots: bytes@3.1.2: {} - cacache@15.3.0(bluebird@3.7.2): + cacache@15.3.0: dependencies: '@npmcli/fs': 1.1.1 '@npmcli/move-file': 1.1.2 @@ -3738,7 +3738,7 @@ snapshots: minipass-pipeline: 1.2.4 mkdirp: 1.0.4 p-map: 4.0.0 - promise-inflight: 1.0.1(bluebird@3.7.2) + promise-inflight: 1.0.1 rimraf: 3.0.2 ssri: 8.0.1 tar: 6.2.1 @@ -3746,7 +3746,7 @@ snapshots: transitivePeerDependencies: - bluebird - cacache@16.1.3(bluebird@3.7.2): + cacache@16.1.3: dependencies: '@npmcli/fs': 2.1.2 '@npmcli/move-file': 2.0.1 @@ -3761,7 +3761,7 @@ snapshots: minipass-pipeline: 1.2.4 mkdirp: 1.0.4 p-map: 4.0.0 - promise-inflight: 1.0.1(bluebird@3.7.2) + promise-inflight: 1.0.1 rimraf: 3.0.2 ssri: 9.0.1 tar: 6.2.1 @@ -3808,7 +3808,7 @@ snapshots: camelcase@5.3.1: {} - caniuse-lite@1.0.30001810: {} + caniuse-lite@1.0.30001812: {} chalk@0.2.1: dependencies: @@ -3834,7 +3834,7 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chokidar@1.7.0(supports-color@7.2.0): + chokidar@1.7.0: dependencies: anymatch: 1.3.2 async-each: 1.0.6 @@ -3843,24 +3843,24 @@ snapshots: is-binary-path: 1.0.1 is-glob: 2.0.1 path-is-absolute: 1.0.1 - readdirp: 2.2.1(supports-color@7.2.0) + readdirp: 2.2.1 optionalDependencies: fsevents: 1.2.13 transitivePeerDependencies: - supports-color - chokidar@2.1.8(supports-color@7.2.0): + chokidar@2.1.8: dependencies: - anymatch: 2.0.0(supports-color@7.2.0) + anymatch: 2.0.0 async-each: 1.0.6 - braces: 2.3.2(supports-color@7.2.0) + braces: 2.3.2 glob-parent: 3.1.0 inherits: 2.0.4 is-binary-path: 1.0.1 is-glob: 4.0.3 normalize-path: 3.0.0 path-is-absolute: 1.0.1 - readdirp: 2.2.1(supports-color@7.2.0) + readdirp: 2.2.1 upath: 1.2.0 optionalDependencies: fsevents: 1.2.13 @@ -3999,10 +3999,10 @@ snapshots: connect-history-api-fallback@1.6.0: {} - connect@3.6.6(supports-color@7.2.0): + connect@3.6.6: dependencies: - debug: 2.6.9(supports-color@7.2.0) - finalhandler: 1.1.0(supports-color@7.2.0) + debug: 2.6.9 + finalhandler: 1.1.0 parseurl: 1.3.3 utils-merge: 1.0.1 transitivePeerDependencies: @@ -4061,35 +4061,25 @@ snapshots: dateformat@4.6.3: {} - debug@2.6.9(supports-color@7.2.0): + debug@2.6.9: dependencies: ms: 2.0.0 - optionalDependencies: - supports-color: 7.2.0 - debug@3.1.0(supports-color@7.2.0): + debug@3.1.0: dependencies: ms: 2.0.0 - optionalDependencies: - supports-color: 7.2.0 - debug@4.1.1(supports-color@7.2.0): + debug@4.1.1: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 7.2.0 - debug@4.3.2(supports-color@7.2.0): + debug@4.3.2: dependencies: ms: 2.1.2 - optionalDependencies: - supports-color: 7.2.0 - debug@4.4.3(supports-color@7.2.0): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 7.2.0 decamelize-keys@1.1.1: dependencies: @@ -4197,7 +4187,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.436: {} + electron-to-chromium@1.5.439: {} emoji-regex@8.0.0: {} @@ -4211,11 +4201,11 @@ snapshots: dependencies: once: 1.4.0 - engine.io-client@3.5.6(supports-color@7.2.0): + engine.io-client@3.5.6: dependencies: component-emitter: 1.3.1 component-inherit: 0.0.3 - debug: 3.1.0(supports-color@7.2.0) + debug: 3.1.0 engine.io-parser: 2.2.1 has-cors: 1.1.0 indexof: 0.0.1 @@ -4229,10 +4219,10 @@ snapshots: - supports-color - utf-8-validate - engine.io-client@6.6.6(supports-color@7.2.0): + engine.io-client@6.6.6: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 engine.io-parser: 5.2.3 ws: 8.21.3 xmlhttprequest-ssl: 2.1.2 @@ -4251,12 +4241,12 @@ snapshots: engine.io-parser@5.2.3: {} - engine.io@3.5.0(supports-color@7.2.0): + engine.io@3.5.0: dependencies: accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 - debug: 4.1.1(supports-color@7.2.0) + debug: 4.1.1 engine.io-parser: 2.2.1 ws: 7.4.6 transitivePeerDependencies: @@ -4345,14 +4335,14 @@ snapshots: dependencies: is-posix-bracket: 0.1.1 - expand-brackets@2.1.4(supports-color@7.2.0): + expand-brackets@2.1.4: dependencies: - debug: 2.6.9(supports-color@7.2.0) + debug: 2.6.9 define-property: 0.2.5 extend-shallow: 2.0.1 posix-character-classes: 0.1.1 regex-not: 1.0.2 - snapdragon: 0.8.2(supports-color@7.2.0) + snapdragon: 0.8.2 to-regex: 3.0.2 transitivePeerDependencies: - supports-color @@ -4388,15 +4378,15 @@ snapshots: dependencies: is-extglob: 1.0.0 - extglob@2.0.4(supports-color@7.2.0): + extglob@2.0.4: dependencies: array-unique: 0.3.2 define-property: 1.0.0 - expand-brackets: 2.1.4(supports-color@7.2.0) + expand-brackets: 2.1.4 extend-shallow: 2.0.1 fragment-cache: 0.2.1 regex-not: 1.0.2 - snapdragon: 0.8.2(supports-color@7.2.0) + snapdragon: 0.8.2 to-regex: 3.0.2 transitivePeerDependencies: - supports-color @@ -4434,9 +4424,9 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@1.1.0(supports-color@7.2.0): + finalhandler@1.1.0: dependencies: - debug: 2.6.9(supports-color@7.2.0) + debug: 2.6.9 encodeurl: 1.0.2 escape-html: 1.0.3 on-finished: 2.3.0 @@ -4461,20 +4451,20 @@ snapshots: glob: 3.2.11 lodash: 2.4.2 - findup-sync@2.0.0(supports-color@7.2.0): + findup-sync@2.0.0: dependencies: detect-file: 1.0.0 is-glob: 3.1.0 - micromatch: 3.1.10(supports-color@7.2.0) + micromatch: 3.1.10 resolve-dir: 1.0.1 transitivePeerDependencies: - supports-color - findup-sync@3.0.0(supports-color@7.2.0): + findup-sync@3.0.0: dependencies: detect-file: 1.0.0 is-glob: 4.0.3 - micromatch: 3.1.10(supports-color@7.2.0) + micromatch: 3.1.10 resolve-dir: 1.0.1 transitivePeerDependencies: - supports-color @@ -4512,9 +4502,9 @@ snapshots: inherits: 2.0.4 readable-stream: 2.3.8 - follow-redirects@1.16.0(debug@4.3.2(supports-color@7.2.0)): + follow-redirects@1.16.0(debug@4.3.2): optionalDependencies: - debug: 4.3.2(supports-color@7.2.0) + debug: 4.3.2 for-in@1.0.2: {} @@ -4647,11 +4637,11 @@ snapshots: to-absolute-glob: 2.0.2 unique-stream: 2.4.0 - glob-watcher@5.0.5(supports-color@7.2.0): + glob-watcher@5.0.5: dependencies: - anymatch: 2.0.0(supports-color@7.2.0) + anymatch: 2.0.0 async-done: 1.3.2 - chokidar: 2.1.8(supports-color@7.2.0) + chokidar: 2.1.8 is-negated-glob: 1.0.0 just-debounce: 1.1.0 normalize-path: 3.0.0 @@ -4863,7 +4853,7 @@ snapshots: minimatch: 3.1.5 nopt: 5.0.0 - gulp-cli@2.3.0(supports-color@7.2.0): + gulp-cli@2.3.0: dependencies: ansi-colors: 1.1.0 archy: 1.0.0 @@ -4875,8 +4865,8 @@ snapshots: gulplog: 1.0.0 interpret: 1.4.0 isobject: 3.0.1 - liftoff: 3.1.0(supports-color@7.2.0) - matchdep: 2.0.0(supports-color@7.2.0) + liftoff: 3.1.0 + matchdep: 2.0.0 mute-stdout: 1.0.1 pretty-hrtime: 1.0.3 replace-homedir: 1.0.0 @@ -4940,10 +4930,10 @@ snapshots: through2: 2.0.5 vinyl: 0.5.3 - gulp-watch@4.3.11(supports-color@7.2.0): + gulp-watch@4.3.11: dependencies: anymatch: 1.3.2 - chokidar: 1.7.0(supports-color@7.2.0) + chokidar: 1.7.0 glob-parent: 3.1.0 gulp-util: 3.0.8 object-assign: 4.1.1 @@ -4955,10 +4945,10 @@ snapshots: transitivePeerDependencies: - supports-color - gulp@4.0.2(supports-color@7.2.0): + gulp@4.0.2: dependencies: - glob-watcher: 5.0.5(supports-color@7.2.0) - gulp-cli: 2.3.0(supports-color@7.2.0) + glob-watcher: 5.0.5 + gulp-cli: 2.3.0 undertaker: 1.3.0 vinyl-fs: 3.0.3 transitivePeerDependencies: @@ -5058,34 +5048,34 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - http-proxy-agent@4.0.1(supports-color@7.2.0): + http-proxy-agent@4.0.1: dependencies: '@tootallnate/once': 1.1.2 - agent-base: 6.0.2(supports-color@7.2.0) - debug: 4.4.3(supports-color@7.2.0) + agent-base: 6.0.2 + debug: 4.4.3 transitivePeerDependencies: - supports-color - http-proxy-agent@5.0.0(supports-color@7.2.0): + http-proxy-agent@5.0.0: dependencies: '@tootallnate/once': 2.0.1 - agent-base: 6.0.2(supports-color@7.2.0) - debug: 4.4.3(supports-color@7.2.0) + agent-base: 6.0.2 + debug: 4.4.3 transitivePeerDependencies: - supports-color - http-proxy@1.18.1(debug@4.3.2(supports-color@7.2.0)): + http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.16.0(debug@4.3.2(supports-color@7.2.0)) + follow-redirects: 1.16.0(debug@4.3.2) requires-port: 1.0.0 transitivePeerDependencies: - debug - https-proxy-agent@5.0.1(supports-color@7.2.0): + https-proxy-agent@5.0.1: dependencies: - agent-base: 6.0.2(supports-color@7.2.0) - debug: 4.4.3(supports-color@7.2.0) + agent-base: 6.0.2 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -5345,10 +5335,10 @@ snapshots: dependencies: flush-write-stream: 1.1.1 - liftoff@3.1.0(supports-color@7.2.0): + liftoff@3.1.0: dependencies: extend: 3.0.2 - findup-sync: 3.0.0(supports-color@7.2.0) + findup-sync: 3.0.0 fined: 1.2.0 flagged-respawn: 1.0.1 is-plain-object: 2.0.4 @@ -5385,10 +5375,10 @@ snapshots: pinkie-promise: 2.0.1 strip-bom: 2.0.0 - localtunnel@2.0.2(supports-color@7.2.0): + localtunnel@2.0.2: dependencies: - axios: 0.21.4(debug@4.3.2(supports-color@7.2.0)) - debug: 4.3.2(supports-color@7.2.0) + axios: 0.21.4(debug@4.3.2) + debug: 4.3.2 openurl: 1.1.1 yargs: 17.1.1 transitivePeerDependencies: @@ -5473,13 +5463,13 @@ snapshots: lru-cache@7.18.3: {} - make-fetch-happen@10.2.1(bluebird@3.7.2)(supports-color@7.2.0): + make-fetch-happen@10.2.1: dependencies: agentkeepalive: 4.6.0 - cacache: 16.1.3(bluebird@3.7.2) + cacache: 16.1.3 http-cache-semantics: 4.2.0 - http-proxy-agent: 5.0.0(supports-color@7.2.0) - https-proxy-agent: 5.0.1(supports-color@7.2.0) + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 is-lambda: 1.0.1 lru-cache: 7.18.3 minipass: 3.3.6 @@ -5489,19 +5479,19 @@ snapshots: minipass-pipeline: 1.2.4 negotiator: 0.6.4 promise-retry: 2.0.1 - socks-proxy-agent: 7.0.0(supports-color@7.2.0) + socks-proxy-agent: 7.0.0 ssri: 9.0.1 transitivePeerDependencies: - bluebird - supports-color - make-fetch-happen@9.1.0(bluebird@3.7.2)(supports-color@7.2.0): + make-fetch-happen@9.1.0: dependencies: agentkeepalive: 4.6.0 - cacache: 15.3.0(bluebird@3.7.2) + cacache: 15.3.0 http-cache-semantics: 4.2.0 - http-proxy-agent: 4.0.1(supports-color@7.2.0) - https-proxy-agent: 5.0.1(supports-color@7.2.0) + http-proxy-agent: 4.0.1 + https-proxy-agent: 5.0.1 is-lambda: 1.0.1 lru-cache: 6.0.0 minipass: 3.3.6 @@ -5511,7 +5501,7 @@ snapshots: minipass-pipeline: 1.2.4 negotiator: 0.6.4 promise-retry: 2.0.1 - socks-proxy-agent: 6.2.1(supports-color@7.2.0) + socks-proxy-agent: 6.2.1 ssri: 8.0.1 transitivePeerDependencies: - bluebird @@ -5531,10 +5521,10 @@ snapshots: dependencies: object-visit: 1.0.1 - matchdep@2.0.0(supports-color@7.2.0): + matchdep@2.0.0: dependencies: - findup-sync: 2.0.0(supports-color@7.2.0) - micromatch: 3.1.10(supports-color@7.2.0) + findup-sync: 2.0.0 + micromatch: 3.1.10 resolve: 1.22.12 stack-trace: 0.0.10 transitivePeerDependencies: @@ -5575,20 +5565,20 @@ snapshots: parse-glob: 3.0.4 regex-cache: 0.4.4 - micromatch@3.1.10(supports-color@7.2.0): + micromatch@3.1.10: dependencies: arr-diff: 4.0.0 array-unique: 0.3.2 - braces: 2.3.2(supports-color@7.2.0) + braces: 2.3.2 define-property: 2.0.2 extend-shallow: 3.0.2 - extglob: 2.0.4(supports-color@7.2.0) + extglob: 2.0.4 fragment-cache: 0.2.1 kind-of: 6.0.3 - nanomatch: 1.2.13(supports-color@7.2.0) + nanomatch: 1.2.13 object.pick: 1.3.0 regex-not: 1.0.2 - snapdragon: 0.8.2(supports-color@7.2.0) + snapdragon: 0.8.2 to-regex: 3.0.2 transitivePeerDependencies: - supports-color @@ -5716,7 +5706,7 @@ snapshots: nan@2.29.0: {} - nanomatch@1.2.13(supports-color@7.2.0): + nanomatch@1.2.13: dependencies: arr-diff: 4.0.0 array-unique: 0.3.2 @@ -5727,7 +5717,7 @@ snapshots: kind-of: 6.0.3 object.pick: 1.3.0 regex-not: 1.0.2 - snapdragon: 0.8.2(supports-color@7.2.0) + snapdragon: 0.8.2 to-regex: 3.0.2 transitivePeerDependencies: - supports-color @@ -5738,12 +5728,12 @@ snapshots: next-tick@1.1.0: {} - node-gyp@8.4.1(bluebird@3.7.2)(supports-color@7.2.0): + node-gyp@8.4.1: dependencies: env-paths: 2.2.1 glob: 7.2.3 graceful-fs: 4.2.11 - make-fetch-happen: 9.1.0(bluebird@3.7.2)(supports-color@7.2.0) + make-fetch-happen: 9.1.0 nopt: 5.0.0 npmlog: 6.0.2 rimraf: 3.0.2 @@ -5756,7 +5746,7 @@ snapshots: node-int64@0.4.0: {} - node-sass@9.0.0(bluebird@3.7.2)(supports-color@7.2.0): + node-sass@9.0.0: dependencies: async-foreach: 0.1.3 chalk: 4.1.2 @@ -5765,10 +5755,10 @@ snapshots: get-stdin: 4.0.1 glob: 7.2.3 lodash: 4.18.1 - make-fetch-happen: 10.2.1(bluebird@3.7.2)(supports-color@7.2.0) + make-fetch-happen: 10.2.1 meow: 9.0.0 nan: 2.29.0 - node-gyp: 8.4.1(bluebird@3.7.2)(supports-color@7.2.0) + node-gyp: 8.4.1 sass-graph: 4.0.1 stdout-stream: 1.4.1 true-case-path: 2.2.1 @@ -6068,9 +6058,7 @@ snapshots: process-nextick-args@2.0.1: {} - promise-inflight@1.0.1(bluebird@3.7.2): - optionalDependencies: - bluebird: 3.7.2 + promise-inflight@1.0.1: {} promise-retry@2.0.1: dependencies: @@ -6170,10 +6158,10 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readdirp@2.2.1(supports-color@7.2.0): + readdirp@2.2.1: dependencies: graceful-fs: 4.2.11 - micromatch: 3.1.10(supports-color@7.2.0) + micromatch: 3.1.10 readable-stream: 2.3.8 transitivePeerDependencies: - supports-color @@ -6261,9 +6249,9 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - resp-modifier@6.0.2(supports-color@7.2.0): + resp-modifier@6.0.2: dependencies: - debug: 2.6.9(supports-color@7.2.0) + debug: 2.6.9 minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -6318,9 +6306,9 @@ snapshots: semver@7.8.5: {} - send@0.16.2(supports-color@7.2.0): + send@0.16.2: dependencies: - debug: 2.6.9(supports-color@7.2.0) + debug: 2.6.9 depd: 1.1.2 destroy: 1.0.4 encodeurl: 1.0.2 @@ -6336,11 +6324,11 @@ snapshots: transitivePeerDependencies: - supports-color - serve-index@1.9.1(supports-color@7.2.0): + serve-index@1.9.1: dependencies: accepts: 1.3.8 batch: 0.6.1 - debug: 2.6.9(supports-color@7.2.0) + debug: 2.6.9 escape-html: 1.0.3 http-errors: 1.6.3 mime-types: 2.1.35 @@ -6348,12 +6336,12 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@1.13.2(supports-color@7.2.0): + serve-static@1.13.2: dependencies: encodeurl: 1.0.2 escape-html: 1.0.3 parseurl: 1.3.3 - send: 0.16.2(supports-color@7.2.0) + send: 0.16.2 transitivePeerDependencies: - supports-color @@ -6409,10 +6397,10 @@ snapshots: dependencies: kind-of: 3.2.2 - snapdragon@0.8.2(supports-color@7.2.0): + snapdragon@0.8.2: dependencies: base: 0.11.2 - debug: 2.6.9(supports-color@7.2.0) + debug: 2.6.9 define-property: 0.2.5 extend-shallow: 2.0.1 map-cache: 0.2.2 @@ -6424,83 +6412,83 @@ snapshots: socket.io-adapter@1.1.2: {} - socket.io-client@2.4.0(supports-color@7.2.0): + socket.io-client@2.4.0: dependencies: backo2: 1.0.2 component-bind: 1.0.0 component-emitter: 1.3.1 - debug: 3.1.0(supports-color@7.2.0) - engine.io-client: 3.5.6(supports-color@7.2.0) + debug: 3.1.0 + engine.io-client: 3.5.6 has-binary2: 1.0.3 indexof: 0.0.1 parseqs: 0.0.6 parseuri: 0.0.6 - socket.io-parser: 3.3.6(supports-color@7.2.0) + socket.io-parser: 3.3.6 to-array: 0.1.4 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - socket.io-client@4.8.3(supports-color@7.2.0): + socket.io-client@4.8.3: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.4.3(supports-color@7.2.0) - engine.io-client: 6.6.6(supports-color@7.2.0) - socket.io-parser: 4.2.7(supports-color@7.2.0) + debug: 4.4.3 + engine.io-client: 6.6.6 + socket.io-parser: 4.2.7 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - socket.io-parser@3.3.6(supports-color@7.2.0): + socket.io-parser@3.3.6: dependencies: component-emitter: 1.3.1 - debug: 3.1.0(supports-color@7.2.0) + debug: 3.1.0 isarray: 2.0.1 transitivePeerDependencies: - supports-color - socket.io-parser@3.4.5(supports-color@7.2.0): + socket.io-parser@3.4.5: dependencies: component-emitter: 1.2.1 - debug: 4.1.1(supports-color@7.2.0) + debug: 4.1.1 isarray: 2.0.1 transitivePeerDependencies: - supports-color - socket.io-parser@4.2.7(supports-color@7.2.0): + socket.io-parser@4.2.7: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 transitivePeerDependencies: - supports-color - socket.io@2.4.0(supports-color@7.2.0): + socket.io@2.4.0: dependencies: - debug: 4.1.1(supports-color@7.2.0) - engine.io: 3.5.0(supports-color@7.2.0) + debug: 4.1.1 + engine.io: 3.5.0 has-binary2: 1.0.3 socket.io-adapter: 1.1.2 - socket.io-client: 2.4.0(supports-color@7.2.0) - socket.io-parser: 3.4.5(supports-color@7.2.0) + socket.io-client: 2.4.0 + socket.io-parser: 3.4.5 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - socks-proxy-agent@6.2.1(supports-color@7.2.0): + socks-proxy-agent@6.2.1: dependencies: - agent-base: 6.0.2(supports-color@7.2.0) - debug: 4.4.3(supports-color@7.2.0) + agent-base: 6.0.2 + debug: 4.4.3 socks: 2.8.10 transitivePeerDependencies: - supports-color - socks-proxy-agent@7.0.0(supports-color@7.2.0): + socks-proxy-agent@7.0.0: dependencies: - agent-base: 6.0.2(supports-color@7.2.0) - debug: 4.4.3(supports-color@7.2.0) + agent-base: 6.0.2 + debug: 4.4.3 socks: 2.8.10 transitivePeerDependencies: - supports-color diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a32a7d55..0761c585 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,6 @@ +packages: + - '.' + allowBuilds: es5-ext: true node-sass: true From edb501e1672685f1576c0cc9ccd1b4c97d302167 Mon Sep 17 00:00:00 2001 From: deepench Date: Fri, 25 Sep 2026 08:16:24 +0100 Subject: [PATCH 02/19] Fix - Copilot review: use pnpm 10 instead of 9, and the real build-script allowlist key pnpm 9 doesn't gate install/postinstall scripts at all, so downgrading to it silently dropped the build-script allowlist protection pnpm 11 had (verified directly: pnpm 11 blocks an unlisted package's postinstall by default, pnpm 9 runs every package's scripts unconditionally). pnpm 10.34.5 declares the same Node >=18.12 compatibility as 9, so it still fixes the original CI failure, but keeps the same default-deny behavior as 11. Also switched pnpm-workspace.yaml from the pnpm 11-only "allowBuilds" map (confirmed not recognized by pnpm 10) to the documented, version-portable "onlyBuiltDependencies" array, which works correctly on both. --- .github/workflows/pr-build-zip.yml | 4 ++-- package.json | 2 +- pnpm-workspace.yaml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr-build-zip.yml b/.github/workflows/pr-build-zip.yml index 758cb8ec..ef5284dc 100644 --- a/.github/workflows/pr-build-zip.yml +++ b/.github/workflows/pr-build-zip.yml @@ -11,9 +11,9 @@ jobs: uses: themegrill/.github/.github/workflows/pr-build-zip.yml@master with: node-version: '20.x' - package-manager: npm + package-manager: pnpm composer-install: false - build-command: npm run build + build-command: pnpm run build zip-glob: 'dist/spacious.zip' artifacts-bucket: themegrill-pr-artifacts public-base-url: https://themegrill-pr-artifacts.s3.amazonaws.com diff --git a/package.json b/package.json index 4a34ac9d..f454b88f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "packageManager": "pnpm@9.15.9", + "packageManager": "pnpm@10.34.5", "name": "spacious", "title": "Spacious", "version": "1.9.12", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0761c585..5c45e2ec 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,6 @@ packages: - '.' -allowBuilds: - es5-ext: true - node-sass: true +onlyBuiltDependencies: + - es5-ext + - node-sass From a910e3719ebd5ea6cdee6c9bd13412303131881f Mon Sep 17 00:00:00 2001 From: Deependra Chaudhary Date: Mon, 28 Sep 2026 05:05:17 +0100 Subject: [PATCH 03/19] Fix - WooCommerce product page layout shows Right Sidebar but renders full width (#139) --- inc/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/functions.php b/inc/functions.php index 66741197..62fbf4ed 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -152,7 +152,7 @@ function spacious_body_class( $classes ) { $spacious_default_page_layout = get_theme_mod( 'spacious_pages_default_layout', 'right_sidebar' ); $spacious_default_post_layout = get_theme_mod( 'spacious_single_posts_default_layout', 'right_sidebar' ); $spacious_woo_archive_layout = get_theme_mod( 'spacious_woo_archive_layout', 'no_sidebar_full_width' ); - $spacious_woo_product_layout = get_theme_mod( 'spacious_woo_product_layout', 'no_sidebar_full_width' ); + $spacious_woo_product_layout = get_theme_mod( 'spacious_woo_product_layout', 'right_sidebar' ); if ( $layout_meta == 'default_layout' ) { if ( is_page() ) { From 3e424461b534ae719d082bb80225aa0f3530d934 Mon Sep 17 00:00:00 2001 From: Deependra Chaudhary Date: Mon, 28 Sep 2026 05:06:40 +0100 Subject: [PATCH 04/19] Fix - Responsive menu shows enabled but never applies until saved once (#138) * Fix - Responsive menu shows enabled but never applies until saved once * Fix - Copilot review findings on the responsive-menu e2e suite Scopes the changeset cleanup to the suite's own admin user, represents an absent theme_mods row instead of throwing on a fresh install, restricts menu reuse to the primary location, maps inc/functions.php into the header area, fixes the mobile-menu-toggle spec to open the submenu's own caret toggle before asserting its content is visible, and publishes reverted Customizer values in cleanup (not just setting them) so Playground runs actually revert. Also fixes: a missing ignoreHTTPSErrors option that blocked the suite from ever running against a local DDEV site, and several specs calling setControl() on a page that had already navigated away from the Customizer. * Fix - auth.setup guards against a cold-boot fill race on Playground Waits for both login fields to be visible and confirms the password value actually landed before submitting, instead of filling immediately after goto(). A cold Playground boot can serve wp-login.php before it has fully hydrated, silently losing a fill() with no error. --- .themegrill-qa/suite.json | 1 + inc/functions.php | 2 +- tests/e2e/auth.setup.ts | 15 +++++++- tests/e2e/fixtures/content.ts | 8 ++-- tests/e2e/fixtures/theme-mods-snapshot.ts | 38 +++++++++++++++---- tests/e2e/global-setup.ts | 12 +++++- tests/e2e/playwright.config.ts | 4 ++ .../accessibility/mobile-menu-toggle.spec.ts | 18 ++++++++- ...display-type-body-class-round-trip.spec.ts | 4 ++ ...splay-type-full-content-vs-excerpt.spec.ts | 10 +++++ .../global-primary-color-round-trip.spec.ts | 5 ++- .../site-layout-body-class-round-trip.spec.ts | 4 ++ .../slider-activation-toggle.spec.ts | 10 ++++- .../single-post/author-bio-round-trip.spec.ts | 10 +++++ .../single-post/related-posts-toggle.spec.ts | 10 +++++ 15 files changed, 132 insertions(+), 19 deletions(-) diff --git a/.themegrill-qa/suite.json b/.themegrill-qa/suite.json index 3c964e68..504c06f4 100644 --- a/.themegrill-qa/suite.json +++ b/.themegrill-qa/suite.json @@ -41,6 +41,7 @@ "header": [ "inc/customizer/options/header/**", "inc/header-functions.php", + "inc/functions.php", "header.php", "navigation.php", "assets/scss/_header.scss" diff --git a/inc/functions.php b/inc/functions.php index 62fbf4ed..65535452 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -222,7 +222,7 @@ function spacious_body_class( $classes ) { $classes[] = 'no-sidebar-content-stretched'; } - if ( get_theme_mod( 'spacious_new_menu', 0 ) == '1' ) { + if ( get_theme_mod( 'spacious_new_menu', '1' ) == '1' ) { $classes[] = 'better-responsive-menu'; } diff --git a/tests/e2e/auth.setup.ts b/tests/e2e/auth.setup.ts index 47f3984d..b4068038 100644 --- a/tests/e2e/auth.setup.ts +++ b/tests/e2e/auth.setup.ts @@ -32,8 +32,19 @@ setup('authenticate', async ({ browser }) => { try { await page.goto('/wp-login.php'); - await page.locator('#user_login').fill(user); - await page.locator('#user_pass').fill(password); + // A cold Playground boot can serve wp-login.php before it has fully + // hydrated — filling a field that's about to be replaced silently loses + // the value with no error, which is exactly what an empty-password + // "Please fill out this field" native-validation failure looks like. + // Waiting for both fields to be attached AND stable first is cheap + // insurance against that race. + const loginField = page.locator('#user_login'); + const passField = page.locator('#user_pass'); + await loginField.waitFor({ state: 'visible' }); + await passField.waitFor({ state: 'visible' }); + await loginField.fill(user); + await passField.fill(password); + await expect(passField).toHaveValue(password); await page.locator('#wp-submit').click(); // The admin bar only renders once the login round-trip actually completed, diff --git a/tests/e2e/fixtures/content.ts b/tests/e2e/fixtures/content.ts index ea887404..8ec8ecea 100644 --- a/tests/e2e/fixtures/content.ts +++ b/tests/e2e/fixtures/content.ts @@ -257,9 +257,11 @@ export const test = base.extend<{ content: ContentHelper }>({ ); for (const menu of menus) { - // An unassigned menu is never rendered, so reusing it would hand the - // mobile specs labels that do not appear in the header. - if (!menu.locations || 0 === menu.locations.length) continue; + // #site-navigation only ever renders the 'primary'-location menu + // (inc/header-functions.php's spacious_main_nav()) — a menu assigned + // only to some other location is never rendered there, so reusing it + // would hand the mobile specs labels that do not appear in the header. + if (!menu.locations || !menu.locations.includes('primary')) continue; const items = await get< Array<{ id: number; parent: number; title: { rendered: string }; url: string }> diff --git a/tests/e2e/fixtures/theme-mods-snapshot.ts b/tests/e2e/fixtures/theme-mods-snapshot.ts index b1016457..6151689c 100644 --- a/tests/e2e/fixtures/theme-mods-snapshot.ts +++ b/tests/e2e/fixtures/theme-mods-snapshot.ts @@ -5,6 +5,11 @@ import { hasMysql, targetEnv } from '../env'; const SNAPSHOT_PATH = path.join(__dirname, '..', '.auth', 'theme-mods-baseline.json'); +// Sentinel written instead of a base64 payload when the option row doesn't +// exist at all — standard base64 (MySQL's TO_BASE64 alphabet) never contains +// an underscore, so this can't collide with a real snapshot. +const ABSENT = '__ABSENT__'; + export type DbConfig = { host?: string; port: string; @@ -132,6 +137,13 @@ async function themeModsOption(config: DbConfig): Promise { * A no-op off `local`. On Playground the site is disposable and rebuilt from a * blueprint per run, so there is nothing to protect and — more to the point — * no MySQL to protect it with. + * + * A genuinely fresh install has no `theme_mods_` row at all — the + * option doesn't exist until the first `set_theme_mod()` call. That is a + * normal, expected baseline (the whole point of the `@fresh` tier is to run + * against exactly this state), not a failure: recorded as the `ABSENT` + * sentinel rather than thrown, since throwing here runs inside unguarded + * global-setup and would abort the entire suite before any test executes. */ export async function snapshotThemeMods(): Promise { if (!hasMysql()) { @@ -147,14 +159,12 @@ export async function snapshotThemeMods(): Promise { config, `SELECT TO_BASE64(option_value) FROM ${config.tablePrefix}options WHERE option_name='${option}';`, ); + fs.mkdirSync(path.dirname(SNAPSHOT_PATH), { recursive: true }); if (!base64) { - throw new Error( - `theme-mods-snapshot: '${option}' not found in ${config.tablePrefix}options — nothing to snapshot. ` + - 'That means the active theme has never had a customizer setting saved; open the Customizer ' + - 'and publish once, then re-run.', - ); + fs.writeFileSync(SNAPSHOT_PATH, ABSENT, 'utf8'); + console.warn(`theme-mods-snapshot: '${option}' does not exist yet — baseline recorded as absent.`); + return; } - fs.mkdirSync(path.dirname(SNAPSHOT_PATH), { recursive: true }); fs.writeFileSync(SNAPSHOT_PATH, base64, 'utf8'); console.warn(`theme-mods-snapshot: baseline captured for '${option}'.`); } @@ -192,10 +202,22 @@ export async function restoreThemeMods(): Promise { try { const config = readDbConfig(); const option = await themeModsOption(config); - const base64 = fs.readFileSync(SNAPSHOT_PATH, 'utf8').trim(); + const snapshot = fs.readFileSync(SNAPSHOT_PATH, 'utf8').trim(); + + if (snapshot === ABSENT) { + // The baseline was "no row at all" — an UPDATE can't represent that, + // it can only modify a row that already exists. Delete whatever a test + // created instead, so the site genuinely returns to its fresh state. + await runMysql( + config, + `DELETE FROM ${config.tablePrefix}options WHERE option_name='${option}';`, + ); + return; + } + await runMysql( config, - `UPDATE ${config.tablePrefix}options SET option_value = FROM_BASE64('${base64}') ` + + `UPDATE ${config.tablePrefix}options SET option_value = FROM_BASE64('${snapshot}') ` + `WHERE option_name='${option}';`, ); } catch (err) { diff --git a/tests/e2e/global-setup.ts b/tests/e2e/global-setup.ts index 85de5d15..3b81e8aa 100644 --- a/tests/e2e/global-setup.ts +++ b/tests/e2e/global-setup.ts @@ -1,6 +1,6 @@ import { execFile } from 'child_process'; import { promisify } from 'util'; -import { baseUrl, hasMysql, targetEnv } from './env'; +import { adminCredentials, baseUrl, hasMysql, targetEnv } from './env'; import { connectionArgs, readDbConfig, @@ -60,6 +60,11 @@ export default async function globalSetup() { * fixture, and Spacious shares the same Customizer changeset mechanics * (core WordPress, not theme-specific), so the same precaution applies here. * + * Scoped to the suite's own admin user (via `post_author`) — this can run + * against a shared `local` site, and an unscoped trash would discard any + * *other* administrator's unpublished Customizer work along with this + * suite's own leftovers. + * * `customize_changeset` is not a REST-exposed post type, so this shells out to * a `mysql` client directly, configured via WP_DB_* env vars. Trashes rather * than deletes, matching how WP's own UI handles removal. @@ -69,10 +74,13 @@ export default async function globalSetup() { */ async function clearStaleChangesets(): Promise { const config = readDbConfig(); + const { user: adminUser } = adminCredentials(); + const escapedUser = adminUser.replace(/'/g, "''"); const sql = `UPDATE ${config.tablePrefix}posts SET post_status='trash' ` + - `WHERE post_type='customize_changeset' AND post_status='auto-draft';`; + `WHERE post_type='customize_changeset' AND post_status='auto-draft' ` + + `AND post_author = (SELECT ID FROM ${config.tablePrefix}users WHERE user_login='${escapedUser}');`; const args = [...connectionArgs(config), '-u', config.user]; if (config.password) args.push(`-p${config.password}`); diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index e185e131..e70476b1 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -55,6 +55,10 @@ export default defineConfig({ trace: 'retain-on-failure', screenshot: 'only-on-failure', video: 'retain-on-failure', + // A local DDEV site serves over HTTPS with a self-signed cert — without + // this, every navigation on `local` fails at the certificate check + // before a single spec's own logic runs. + ignoreHTTPSErrors: true, }, projects: [ diff --git a/tests/e2e/specs/accessibility/mobile-menu-toggle.spec.ts b/tests/e2e/specs/accessibility/mobile-menu-toggle.spec.ts index c3113a37..1996df4f 100644 --- a/tests/e2e/specs/accessibility/mobile-menu-toggle.spec.ts +++ b/tests/e2e/specs/accessibility/mobile-menu-toggle.spec.ts @@ -50,6 +50,15 @@ const NARROW_VIEWPORT = { width: 390, height: 844 }; * NOT verified live — no live session has exercised a mobile viewport against * a real Spacious site yet. Written from js/navigation.js and style.css read * in full, not from an observed click. + * + * With `spacious_new_menu` on by default (this repo's own #135 fix), `` + * carries `better-responsive-menu`, under which `js/spacious-custom.js` + * appends a `.sub-toggle` caret to every `.menu-item-has-children` and keeps + * `.sub-menu` collapsed (`display:none`, same breakpoint as the main toggle) + * until that caret — not the main nav toggle — is clicked + * (`.sub-toggle` click handler calls `.sub-menu.slideToggle()`). So opening + * the main toggle alone is not enough to reveal the child link; the submenu's + * own toggle must be opened first. */ test('mobile menu toggle opens and closes the primary nav @fresh @accessibility', async ({ page, content }) => { const menu = await content.aMenuWithDropdown(); @@ -62,6 +71,8 @@ test('mobile menu toggle opens and closes the primary nav @fresh @accessibility' // The child link's own text is unambiguous evidence of the seeded menu, // rather than asserting against the whole
    's bounding box. const childLink = nav.getByRole('link', { name: menu.childLabel, exact: true }); + const parentItem = nav.locator('.menu-item-has-children', { hasText: menu.parentLabel }).first(); + const subToggle = parentItem.locator('.sub-toggle').first(); // Closed by default: main-navigation, menu hidden under the 768px breakpoint. await expect(nav).toHaveClass(/(^|\s)main-navigation(\s|$)/); @@ -70,9 +81,14 @@ test('mobile menu toggle opens and closes the primary nav @fresh @accessibility' await toggle.click(); - // Open: navigation.js's onclick replaced the class, exposing the menu. + // Open: navigation.js's onclick replaced the class, exposing the top-level menu. await expect(nav).toHaveClass(/main-small-navigation/); await expect(nav).not.toHaveClass(/(^|\s)main-navigation(\s|$)/); + + // The submenu itself stays collapsed behind its own caret toggle — opening + // the main nav is not sufficient on its own. + await expect(subToggle).toBeVisible(); + await subToggle.click(); await expect(childLink).toBeVisible(); await toggle.click(); diff --git a/tests/e2e/specs/blog-layout/archive-display-type-body-class-round-trip.spec.ts b/tests/e2e/specs/blog-layout/archive-display-type-body-class-round-trip.spec.ts index 362f9c1e..aa84e401 100644 --- a/tests/e2e/specs/blog-layout/archive-display-type-body-class-round-trip.spec.ts +++ b/tests/e2e/specs/blog-layout/archive-display-type-body-class-round-trip.spec.ts @@ -72,8 +72,12 @@ test('Content > Blog Posts display type persists a body class through publish an const reopened = await page.evaluate((id) => (window as any).wp.customize(id).get(), CONTROL_ID); expect(reopened).toBe(testValue); } finally { + // Published, not just set: on Playground the fixture's own MySQL-based + // restore (customizer.ts / theme-mods-snapshot.ts) is a no-op, so this + // publish is what actually reverts the live, persisted value there. try { await customizer.setControl(CONTROL_ID, original); + await customizer.publish(); } catch (revertError) { console.warn(`Revert of ${CONTROL_ID} did not complete cleanly:`, revertError); } diff --git a/tests/e2e/specs/blog-layout/archive-display-type-full-content-vs-excerpt.spec.ts b/tests/e2e/specs/blog-layout/archive-display-type-full-content-vs-excerpt.spec.ts index ffa49e69..d53d295e 100644 --- a/tests/e2e/specs/blog-layout/archive-display-type-full-content-vs-excerpt.spec.ts +++ b/tests/e2e/specs/blog-layout/archive-display-type-full-content-vs-excerpt.spec.ts @@ -119,6 +119,11 @@ test('Content > Blog Posts display type swaps excerpt for full content on the fr 'Expected the 40-word excerpt to truncate before the tail marker with spacious_archive_display_type=blog_large', ).not.toContain(TAIL_MARKER); + // setControl() drives window.wp.customize() in the CURRENT page — the + // goto() above navigated away from the Customizer entirely, so it must + // be reopened before the next setControl() call has anything to act on. + await customizer.open({ control: CONTROL_ID }); + // ---- blog_full_content: the_content(), tail marker present ---- await customizer.setControl(CONTROL_ID, 'blog_full_content'); await customizer.publish(); @@ -129,8 +134,13 @@ test('Content > Blog Posts display type swaps excerpt for full content on the fr 'Expected the full post content (including the tail marker) with spacious_archive_display_type=blog_full_content', ).toContain(TAIL_MARKER); } finally { + // Published, not just set: on Playground the fixture's own MySQL-based + // restore (customizer.ts / theme-mods-snapshot.ts) is a no-op, so this + // publish is what actually reverts the live, persisted value there. try { + await customizer.open({ control: CONTROL_ID }); await customizer.setControl(CONTROL_ID, originalValue); + await customizer.publish(); } catch (revertError) { console.warn(`Revert of ${CONTROL_ID} did not complete cleanly:`, revertError); } diff --git a/tests/e2e/specs/customizer/global-primary-color-round-trip.spec.ts b/tests/e2e/specs/customizer/global-primary-color-round-trip.spec.ts index 02251c38..f5a4a436 100644 --- a/tests/e2e/specs/customizer/global-primary-color-round-trip.spec.ts +++ b/tests/e2e/specs/customizer/global-primary-color-round-trip.spec.ts @@ -72,9 +72,12 @@ test('Global > Colors > Primary Color persists through publish and reopen @fresh // Courtesy-only revert for a reused browser context — the fixture's own // teardown (see customizer.ts / theme-mods-snapshot.ts) is the real // safety net and restores the true DB value regardless of how this test - // exits. + // exits. Published, not just set: on Playground that teardown is a + // no-op (no MySQL there), so this publish is what actually reverts the + // live, persisted value on that tier. try { await customizer.setControl(CONTROL_ID, original); + await customizer.publish(); } catch (revertError) { console.warn(`Revert of ${CONTROL_ID} did not complete cleanly:`, revertError); } diff --git a/tests/e2e/specs/customizer/site-layout-body-class-round-trip.spec.ts b/tests/e2e/specs/customizer/site-layout-body-class-round-trip.spec.ts index 36b4e113..9366ec92 100644 --- a/tests/e2e/specs/customizer/site-layout-body-class-round-trip.spec.ts +++ b/tests/e2e/specs/customizer/site-layout-body-class-round-trip.spec.ts @@ -62,8 +62,12 @@ test('Global > Layout > Site Layout persists through publish and reopen @fresh @ const reopened = await page.evaluate((id) => (window as any).wp.customize(id).get(), CONTROL_ID); expect(reopened).toBe(testLayout); } finally { + // Published, not just set: on Playground the fixture's own MySQL-based + // restore (customizer.ts / theme-mods-snapshot.ts) is a no-op, so this + // publish is what actually reverts the live, persisted value there. try { await customizer.setControl(CONTROL_ID, original); + await customizer.publish(); } catch (revertError) { console.warn(`Revert of ${CONTROL_ID} did not complete cleanly:`, revertError); } diff --git a/tests/e2e/specs/front-page/slider-activation-toggle.spec.ts b/tests/e2e/specs/front-page/slider-activation-toggle.spec.ts index 67c8bf66..7b0d0532 100644 --- a/tests/e2e/specs/front-page/slider-activation-toggle.spec.ts +++ b/tests/e2e/specs/front-page/slider-activation-toggle.spec.ts @@ -66,6 +66,10 @@ test('Slider > Activate slider shows and hides #featured-slider on the front pag 'Expected #featured-slider to render on the front page once spacious_activate_slider is on', ).toBeVisible(); + // setControl() drives window.wp.customize() in the CURRENT page — the + // goto() above navigated away from the Customizer entirely, so it must + // be reopened before the next setControl() call has anything to act on. + await customizer.open({ control: CONTROL_ID }); await customizer.setControl(CONTROL_ID, false); await customizer.publish(); await page.goto('/?e2e-cache-bust=' + Date.now()); @@ -77,9 +81,13 @@ test('Slider > Activate slider shows and hides #featured-slider on the front pag // Courtesy-only revert for a reused browser context — the fixture's own // teardown (see customizer.ts / theme-mods-snapshot.ts) is the real // safety net and restores the true DB value regardless of how this test - // exits. + // exits. Published, not just set: on Playground the fixture's own + // teardown is a no-op (no MySQL there), so this publish is the only + // thing that actually reverts the live, persisted value on that tier. try { + await customizer.open({ control: CONTROL_ID }); await customizer.setControl(CONTROL_ID, original); + await customizer.publish(); } catch (revertError) { console.warn(`Revert of ${CONTROL_ID} did not complete cleanly:`, revertError); } diff --git a/tests/e2e/specs/single-post/author-bio-round-trip.spec.ts b/tests/e2e/specs/single-post/author-bio-round-trip.spec.ts index 3aa1fb2a..9f57d1a4 100644 --- a/tests/e2e/specs/single-post/author-bio-round-trip.spec.ts +++ b/tests/e2e/specs/single-post/author-bio-round-trip.spec.ts @@ -107,6 +107,11 @@ test('Content > Single Post > Author Bio shows/hides .author-box based on the co await expect(authorBox, '.author-box should render once spacious_author_bio is on and the author has a bio').toBeVisible(); await expect(authorBox.locator('.author-description')).toContainText(TEST_BIO); + // setControl() drives window.wp.customize() in the CURRENT page — the + // goto() above navigated away from the Customizer entirely, so it must + // be reopened before the next setControl() call has anything to act on. + await customizer.open({ control: CONTROL_ID }); + // ---- control OFF: .author-box gone even though the bio is still set ---- await customizer.setControl(CONTROL_ID, false); await customizer.publish(); @@ -116,8 +121,13 @@ test('Content > Single Post > Author Bio shows/hides .author-box based on the co '.author-box should not render once spacious_author_bio is off, regardless of the author bio', ).toHaveCount(0); } finally { + // Published, not just set: on Playground the fixture's own MySQL-based + // restore (customizer.ts / theme-mods-snapshot.ts) is a no-op, so this + // publish is what actually reverts the live, persisted value there. try { + await customizer.open({ control: CONTROL_ID }); await customizer.setControl(CONTROL_ID, originalControlValue); + await customizer.publish(); } catch (revertError) { console.warn(`Revert of ${CONTROL_ID} did not complete cleanly:`, revertError); } diff --git a/tests/e2e/specs/single-post/related-posts-toggle.spec.ts b/tests/e2e/specs/single-post/related-posts-toggle.spec.ts index ffa24ff9..27de041e 100644 --- a/tests/e2e/specs/single-post/related-posts-toggle.spec.ts +++ b/tests/e2e/specs/single-post/related-posts-toggle.spec.ts @@ -120,6 +120,11 @@ test('Content > Single Post > Related Posts shows/hides "You May Also Like" base ).toBeVisible(); await expect(page.locator('.related-posts')).toContainText(other.title.rendered); + // setControl() drives window.wp.customize() in the CURRENT page — the + // goto() above navigated away from the Customizer entirely, so it must + // be reopened before the next setControl() call has anything to act on. + await customizer.open({ control: CONTROL_ID }); + // ---- control OFF: neither renders ---- await customizer.setControl(CONTROL_ID, false); await customizer.publish(); @@ -129,8 +134,13 @@ test('Content > Single Post > Related Posts shows/hides "You May Also Like" base 'Related Posts heading should not render once spacious_related_posts_activate is off', ).toHaveCount(0); } finally { + // Published, not just set: on Playground the fixture's own MySQL-based + // restore (customizer.ts / theme-mods-snapshot.ts) is a no-op, so this + // publish is what actually reverts the live, persisted value there. try { + await customizer.open({ control: CONTROL_ID }); await customizer.setControl(CONTROL_ID, originalControlValue); + await customizer.publish(); } catch (revertError) { console.warn(`Revert of ${CONTROL_ID} did not complete cleanly:`, revertError); } From cf74ae669da10f1ba8d09eada885cf0be21903b4 Mon Sep 17 00:00:00 2001 From: Rajat Gautam <83450756+rajatgautam755421@users.noreply.github.com> Date: Mon, 28 Sep 2026 09:53:27 +0545 Subject: [PATCH 05/19] Fix - Version theme scripts and styles with the theme version (#142) * Fix - Version theme scripts and styles with the theme version The main stylesheet and most theme scripts were enqueued without a version, so WordPress appended its own core version and browsers or CDNs kept serving old copies after a theme update until WordPress itself was updated. Pass SPACIOUS_THEME_VERSION, as the Customizer and admin assets already do. Handles, dependencies and load order are unchanged. Co-Authored-By: Claude Opus 5.5 * Fix - Leave a child theme's stylesheet on its default version get_stylesheet_uri() is the child theme's own style.css when a child theme is active, so stamping it with the parent's version was wrong. Keep WordPress' default version there, exactly as before this change, and use the theme version only for Spacious' own stylesheet. Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 --- inc/enqueue-scripts.php | 13 +++++++------ inc/functions.php | 2 +- inc/spacious-toolkit.php | 6 +++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/inc/enqueue-scripts.php b/inc/enqueue-scripts.php index 22274b3c..15e813bf 100644 --- a/inc/enqueue-scripts.php +++ b/inc/enqueue-scripts.php @@ -63,11 +63,12 @@ function spacious_scripts_styles_method() { /** * Loads our main stylesheet. */ - wp_enqueue_style( 'spacious_style', get_stylesheet_uri() ); + // A child theme's own style.css keeps WordPress' default version; only Spacious' stylesheet uses the theme version. + wp_enqueue_style( 'spacious_style', get_stylesheet_uri(), array(), is_child_theme() ? false : SPACIOUS_THEME_VERSION ); wp_style_add_data( 'spacious_style', 'rtl', 'replace' ); if ( get_theme_mod( 'spacious_color_skin', 'light' ) == 'dark' ) { - wp_enqueue_style( 'spacious_dark_style', SPACIOUS_CSS_URL . '/dark.css' ); + wp_enqueue_style( 'spacious_dark_style', SPACIOUS_CSS_URL . '/dark.css', array(), SPACIOUS_THEME_VERSION ); } // Add Genericons, used in the main stylesheet. @@ -104,7 +105,7 @@ function spacious_scripts_styles_method() { * Register JQuery cycle2 js file for slider. */ wp_register_script( 'jquery_cycle', SPACIOUS_JS_URL . '/jquery.cycle2.min.js', array( 'jquery' ), '2.1.6', true ); - wp_register_script( 'jquery-swipe', SPACIOUS_JS_URL . '/jquery.cycle2.swipe.min.js', array( 'jquery' ), false, true ); + wp_register_script( 'jquery-swipe', SPACIOUS_JS_URL . '/jquery.cycle2.swipe.min.js', array( 'jquery' ), SPACIOUS_THEME_VERSION, true ); /** * Enqueue Slider setup js file. @@ -114,12 +115,12 @@ function spacious_scripts_styles_method() { wp_enqueue_script( 'jquery_cycle' ); } - wp_enqueue_script( 'spacious-navigation', SPACIOUS_JS_URL . '/navigation.js', array( 'jquery' ), false, true ); + wp_enqueue_script( 'spacious-navigation', SPACIOUS_JS_URL . '/navigation.js', array( 'jquery' ), SPACIOUS_THEME_VERSION, true ); // Skip link focus fix JS enqueue. - wp_enqueue_script( 'spacious-skip-link-focus-fix', SPACIOUS_JS_URL . '/skip-link-focus-fix.js', array(), false, true ); + wp_enqueue_script( 'spacious-skip-link-focus-fix', SPACIOUS_JS_URL . '/skip-link-focus-fix.js', array(), SPACIOUS_THEME_VERSION, true ); - wp_enqueue_script( 'spacious-custom', SPACIOUS_JS_URL . '/spacious-custom.js', array( 'jquery' ) ); + wp_enqueue_script( 'spacious-custom', SPACIOUS_JS_URL . '/spacious-custom.js', array( 'jquery' ), SPACIOUS_THEME_VERSION ); wp_enqueue_script( 'html5', SPACIOUS_JS_URL . '/html5shiv.min.js', true ); wp_script_add_data( 'html5', 'conditional', 'lte IE 8' ); diff --git a/inc/functions.php b/inc/functions.php index 65535452..39011824 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -16,7 +16,7 @@ * Enqueue Google fonts and editor styles. */ function spacious_block_editor_styles() { - wp_enqueue_style( 'spacious-block-editor-styles', get_template_directory_uri() . '/style-editor-block.css' ); + wp_enqueue_style( 'spacious-block-editor-styles', get_template_directory_uri() . '/style-editor-block.css', array(), SPACIOUS_THEME_VERSION ); } add_action( 'enqueue_block_editor_assets', 'spacious_block_editor_styles', 1, 1 ); diff --git a/inc/spacious-toolkit.php b/inc/spacious-toolkit.php index 765d11ca..a68b886c 100644 --- a/inc/spacious-toolkit.php +++ b/inc/spacious-toolkit.php @@ -65,14 +65,14 @@ public function __construct() { */ public function spacious_elementor_styles() { // Enqueue the main Elementor CSS file for use with Elementor. - wp_enqueue_style( 'spacious-elementor', get_template_directory_uri() . '/inc/elementor/assets/css/elementor.css' ); + wp_enqueue_style( 'spacious-elementor', get_template_directory_uri() . '/inc/elementor/assets/css/elementor.css', array(), SPACIOUS_THEME_VERSION ); } /** * Enqueue scripts for Elementor frontends */ public function spacious_elementor_enqueue_scripts() { - wp_enqueue_script( 'elementor-custom', SPACIOUS_JS_URL . '/elementor-custom.js', array( 'jquery' ), false, true ); + wp_enqueue_script( 'elementor-custom', SPACIOUS_JS_URL . '/elementor-custom.js', array( 'jquery' ), SPACIOUS_THEME_VERSION, true ); } /** @@ -80,7 +80,7 @@ public function spacious_elementor_enqueue_scripts() { */ public function spacious_elementor_register_scripts() { wp_register_script( 'jquery-waypoints', SPACIOUS_JS_URL . '/waypoints' . $this->suffix . '.js', array( 'jquery' ), '2.0.3', true ); - wp_register_script( 'jquery-countTo', SPACIOUS_JS_URL . '/jquery.countTo' . $this->suffix . '.js', array( 'jquery' ), false, true ); + wp_register_script( 'jquery-countTo', SPACIOUS_JS_URL . '/jquery.countTo' . $this->suffix . '.js', array( 'jquery' ), SPACIOUS_THEME_VERSION, true ); } } From c248fe6c14d3e5630bacdad54ace0224619a52ac Mon Sep 17 00:00:00 2001 From: Deependra Chaudhary Date: Mon, 28 Sep 2026 06:08:40 +0100 Subject: [PATCH 06/19] Fix - Slider assets load unconditionally on the blog index due to an operator-precedence bug (#145) --- inc/enqueue-scripts.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/enqueue-scripts.php b/inc/enqueue-scripts.php index 15e813bf..7039905c 100644 --- a/inc/enqueue-scripts.php +++ b/inc/enqueue-scripts.php @@ -110,7 +110,7 @@ function spacious_scripts_styles_method() { /** * Enqueue Slider setup js file. */ - if ( is_home() || is_front_page() && get_theme_mod( 'spacious_activate_slider', '0' ) == '1' ) { + if ( ( is_home() || is_front_page() ) && get_theme_mod( 'spacious_activate_slider', '0' ) == '1' ) { wp_enqueue_script( 'jquery-swipe' ); wp_enqueue_script( 'jquery_cycle' ); } From e6af37c7778aaa1ec158fa38117abe8b1875b902 Mon Sep 17 00:00:00 2001 From: Deependra Chaudhary Date: Mon, 28 Sep 2026 06:09:27 +0100 Subject: [PATCH 07/19] Fix - Submenu caret icon never flips direction when toggled (#149) The sub-toggle click handler looked for a child matching ".sub-toggle .fa", but inside the handler `this` already is the .sub-toggle element itself, not a container of one - so the selector never matched anything and toggleClass() silently ran on an empty set. The submenu itself opened and closed correctly (a separate call), only the caret's own direction (caret-right/caret-down) never updated. Fixed by matching the icon's actual direct child selector, ".fa". Verified live in both themes: before the fix, clicking the caret expanded the submenu but the icon class never changed; after, it flips fa-caret-right <-> fa-caret-down correctly in both directions. Also checked and ruled out a previously-suspected duplicate id="site-navigation" theory: header.php's two spacious_main_nav() call sites are wrapped in genuinely mutually exclusive conditions (spacious_header_display_type === 'four' vs !== 'four'), so only one nav ever renders - confirmed with 0 duplicates under both settings, live. Nothing to fix there. --- js/spacious-custom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/spacious-custom.js b/js/spacious-custom.js index 1b66091d..230bccd6 100644 --- a/js/spacious-custom.js +++ b/js/spacious-custom.js @@ -24,7 +24,7 @@ jQuery( document ).ready( function () { jQuery('.better-responsive-menu .menu-primary-container .sub-toggle').click(function() { jQuery(this).parent('.menu-item-has-children').children('ul.sub-menu').first().slideToggle('1000'); - jQuery(this).children('.sub-toggle .fa').first().toggleClass('fa-caret-down fa-caret-right'); + jQuery(this).children('.fa').first().toggleClass('fa-caret-down fa-caret-right'); }); /** From ba2540a2e4ae1a25f4a328d8355aa40a1e54930e Mon Sep 17 00:00:00 2001 From: Rajat Gautam <83450756+rajatgautam755421@users.noreply.github.com> Date: Mon, 28 Sep 2026 10:56:29 +0545 Subject: [PATCH 08/19] Fix - Deprecated IE-conditional html5shiv enqueue firing a notice on every page load (WP 6.9+) (#151) Co-authored-by: Claude Opus 5.5 --- inc/enqueue-scripts.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/enqueue-scripts.php b/inc/enqueue-scripts.php index 7039905c..96067f92 100644 --- a/inc/enqueue-scripts.php +++ b/inc/enqueue-scripts.php @@ -122,8 +122,8 @@ function spacious_scripts_styles_method() { wp_enqueue_script( 'spacious-custom', SPACIOUS_JS_URL . '/spacious-custom.js', array( 'jquery' ), SPACIOUS_THEME_VERSION ); - wp_enqueue_script( 'html5', SPACIOUS_JS_URL . '/html5shiv.min.js', true ); - wp_script_add_data( 'html5', 'conditional', 'lte IE 8' ); + // Empty handle kept so scripts that list 'html5' as a dependency still load. + wp_register_script( 'html5', false, array(), SPACIOUS_THEME_VERSION, false ); } add_action( 'wp_enqueue_scripts', 'spacious_scripts_styles_method' ); From 64548aa297f68af484082b9e85e2b75006bc3a60 Mon Sep 17 00:00:00 2001 From: Rajat Gautam <83450756+rajatgautam755421@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:04:35 +0545 Subject: [PATCH 09/19] Fix - navigation.js null errors on pages without the theme header (Widgets screen previews) (#147) Co-authored-by: Claude Opus 5.5 --- js/navigation.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js/navigation.js b/js/navigation.js index 254736d0..293716d6 100644 --- a/js/navigation.js +++ b/js/navigation.js @@ -71,6 +71,9 @@ ( function () { var container; container = document.getElementById( 'site-navigation' ); + if ( ! container ) { + return; + } /** * Toggles `focus` class to allow submenu access on tablets. @@ -180,7 +183,7 @@ } // Return if no excess menu items. - if ( ! navigation.classList.contains( 'tg-extra-menus' ) ) { + if ( ! navigation || ! navigation.classList.contains( 'tg-extra-menus' ) ) { return; } From df759807187d748848692b7410688e9c44f4978c Mon Sep 17 00:00:00 2001 From: Rajat Gautam <83450756+rajatgautam755421@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:05:46 +0545 Subject: [PATCH 10/19] Fix - Search block input renders taller than its Search button (#148) Co-authored-by: Claude Opus 5.5 --- assets/scss/_theme-style.scss | 4 ++++ style-rtl.css | 4 ++++ style.css | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/assets/scss/_theme-style.scss b/assets/scss/_theme-style.scss index 6e64ad48..ba10d4e8 100644 --- a/assets/scss/_theme-style.scss +++ b/assets/scss/_theme-style.scss @@ -564,6 +564,10 @@ button { font-size: 16px; } +.wp-block-search__button { + margin-bottom: 0; +} + .spacious-button:hover { -webkit-box-shadow: 0 0 2px rgba(0, 0, 0, 0.6); -moz-box-shadow: 0 0 2px rgba(0, 0, 0, 0.6); diff --git a/style-rtl.css b/style-rtl.css index 6046fa4f..b7bc99cd 100644 --- a/style-rtl.css +++ b/style-rtl.css @@ -607,6 +607,10 @@ button { font-size: 16px; } +.wp-block-search__button { + margin-bottom: 0; +} + .spacious-button:hover { -webkit-box-shadow: 0 0 2px rgba(0, 0, 0, 0.6); box-shadow: 0 0 2px rgba(0, 0, 0, 0.6); diff --git a/style.css b/style.css index f8062eee..1d83f83a 100644 --- a/style.css +++ b/style.css @@ -517,6 +517,10 @@ button { font-size: 16px; } +.wp-block-search__button { + margin-bottom: 0; +} + .spacious-button:hover { -webkit-box-shadow: 0 0 2px rgba(0, 0, 0, 0.6); box-shadow: 0 0 2px rgba(0, 0, 0, 0.6); From aa165c6eebf69bc7fc57038a7657f9061deb9a4c Mon Sep 17 00:00:00 2001 From: Deependra Chaudhary Date: Mon, 28 Sep 2026 07:20:28 +0100 Subject: [PATCH 11/19] Fix - Meta box save routine reads $_POST without isset() checks or a value whitelist (#140) --- inc/admin/meta-boxes.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/inc/admin/meta-boxes.php b/inc/admin/meta-boxes.php index 0fedffcd..4305c8d3 100644 --- a/inc/admin/meta-boxes.php +++ b/inc/admin/meta-boxes.php @@ -98,7 +98,7 @@ function spacious_save_custom_meta( $post_id ) { return; } - if ( 'page' == $_POST['post_type'] ) { + if ( isset( $_POST['post_type'] ) && 'page' == $_POST['post_type'] ) { if ( ! current_user_can( 'edit_page', $post_id ) ) { return $post_id; } @@ -106,10 +106,22 @@ function spacious_save_custom_meta( $post_id ) { return $post_id; } + $allowed_values = wp_list_pluck( $page_layout, 'value' ); + foreach ( $page_layout as $field ) { + if ( ! isset( $_POST[ $field['id'] ] ) ) { + continue; + } + + $new = $_POST[ $field['id'] ]; + + // Allow '' through (it clears the meta below); reject anything else not in the whitelist. + if ( '' !== $new && ! in_array( $new, $allowed_values, true ) ) { + continue; + } + //Execute this saving function $old = get_post_meta( $post_id, $field['id'], true ); - $new = $_POST[ $field['id'] ]; if ( $new && $new != $old ) { update_post_meta( $post_id, $field['id'], $new ); } elseif ( '' == $new && $old ) { From c1b35895548cb5fa760b50a0b4286f8920bc3d9e Mon Sep 17 00:00:00 2001 From: Deependra Chaudhary Date: Mon, 28 Sep 2026 07:22:27 +0100 Subject: [PATCH 12/19] Fix - Unbounded get_users() full-table scan in theme-switch cleanup (#144) --- inc/admin/class-spacious-tdi-notice.php | 12 ++++++++---- inc/admin/class-spacious-theme-review-notice.php | 16 ++++++++++------ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/inc/admin/class-spacious-tdi-notice.php b/inc/admin/class-spacious-tdi-notice.php index 20333c5c..8ce84eb4 100644 --- a/inc/admin/class-spacious-tdi-notice.php +++ b/inc/admin/class-spacious-tdi-notice.php @@ -71,14 +71,18 @@ public function ignore_tdi_notice() { } public function remove_tdi_notice() { - $get_all_users = get_users(); + $user_ids = get_users( array( 'fields' => 'ID' ) ); - foreach ( $get_all_users as $user ) { - $ignored_notice = get_user_meta( $user->ID, 'ignore_spacious_tdi_notice', true ); + // Fetching IDs only skips core's own cache_users() priming, so prime + // it ourselves - one query for every user's meta instead of one per user. + update_meta_cache( 'user', $user_ids ); + + foreach ( $user_ids as $user_id ) { + $ignored_notice = get_user_meta( $user_id, 'ignore_spacious_tdi_notice', true ); // Delete permanent notice remove data. if ( $ignored_notice ) { - delete_user_meta( $user->ID, 'ignore_spacious_tdi_notice' ); + delete_user_meta( $user_id, 'ignore_spacious_tdi_notice' ); } } } diff --git a/inc/admin/class-spacious-theme-review-notice.php b/inc/admin/class-spacious-theme-review-notice.php index 3fa3be38..41b3e87f 100644 --- a/inc/admin/class-spacious-theme-review-notice.php +++ b/inc/admin/class-spacious-theme-review-notice.php @@ -193,7 +193,7 @@ public function spacious_ignore_theme_review_notice_partially() { */ public function review_notice_data_remove() { - $get_all_users = get_users(); + $user_ids = get_users( array( 'fields' => 'ID' ) ); $theme_installed_time = get_option( 'spacious_theme_installed_time' ); // Delete options data. @@ -201,19 +201,23 @@ public function review_notice_data_remove() { delete_option( 'spacious_theme_installed_time' ); } + // Fetching IDs only skips core's own cache_users() priming, so prime + // it ourselves - one query for every user's meta instead of two per user. + update_meta_cache( 'user', $user_ids ); + // Delete user meta data for theme review notice. - foreach ( $get_all_users as $user ) { - $ignored_notice = get_user_meta( $user->ID, 'spacious_ignore_theme_review_notice', true ); - $ignored_notice_partially = get_user_meta( $user->ID, 'nag_spacious_ignore_theme_review_notice_partially', true ); + foreach ( $user_ids as $user_id ) { + $ignored_notice = get_user_meta( $user_id, 'spacious_ignore_theme_review_notice', true ); + $ignored_notice_partially = get_user_meta( $user_id, 'nag_spacious_ignore_theme_review_notice_partially', true ); // Delete permanent notice remove data. if ( $ignored_notice ) { - delete_user_meta( $user->ID, 'spacious_ignore_theme_review_notice' ); + delete_user_meta( $user_id, 'spacious_ignore_theme_review_notice' ); } // Delete partial notice remove data. if ( $ignored_notice_partially ) { - delete_user_meta( $user->ID, 'nag_spacious_ignore_theme_review_notice_partially' ); + delete_user_meta( $user_id, 'nag_spacious_ignore_theme_review_notice_partially' ); } } } From cfd8f7f0446eb907c21425e83030e53e0c935ede Mon Sep 17 00:00:00 2001 From: Deependra Chaudhary Date: Mon, 28 Sep 2026 07:36:23 +0100 Subject: [PATCH 13/19] Fix - Missing capability check on free->pro migration trigger (#146) Adds current_user_can( 'edit_theme_options' ) alongside the existing nonce verification in the demo-import migration trigger, its notice, and its dismiss handler. The nonce alone only proved the request came from the current user's own session, not that they were allowed to run the action - any logged-in user, regardless of role, could force a site-wide theme_mods and options rewrite using stale legacy data, since the migration functions are hooked to after_setup_theme, which fires on the front end too. Verified live: a subscriber-role account, using a nonce genuinely valid for their own session, is now correctly rejected with "You do not have permission to perform this action." --- inc/demo-import-migration.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/inc/demo-import-migration.php b/inc/demo-import-migration.php index bbfb515b..d0709489 100644 --- a/inc/demo-import-migration.php +++ b/inc/demo-import-migration.php @@ -16,6 +16,10 @@ * Display the admin notice for demo import customize migration. */ function spacious_demo_import_migration_notice() { + if ( ! current_user_can( 'edit_theme_options' ) ) { + return; + } + $demo_imported = get_option( 'themegrill_demo_importer_activated_id' ); $notice_dismiss = get_option( 'spacious_demo_import_migration_notice_dismiss' ); @@ -60,6 +64,10 @@ class="btn button-primary" */ function spacious_demo_import_migration_notice_dismiss() { if ( isset( $_GET['demo-import-migration-notice-dismiss'] ) && isset( $_GET['_demo_import_migration_notice_dismiss_nonce'] ) ) { + if ( ! current_user_can( 'edit_theme_options' ) ) { + wp_die( __( 'You do not have permission to perform this action.', 'spacious' ) ); + } + if ( ! wp_verify_nonce( $_GET['_demo_import_migration_notice_dismiss_nonce'], 'demo_import_migration_notice_dismiss' ) ) { wp_die( __( 'Action failed. Please refresh the page and retry.', 'spacious' ) ); } @@ -78,6 +86,10 @@ function spacious_demo_import_migration_notice_dismiss() { */ function spacious_demo_import_migration() { if ( isset( $_GET['demo-import-migration'] ) && isset( $_GET['_demo_import_migration_nonce'] ) ) { + if ( ! current_user_can( 'edit_theme_options' ) ) { + wp_die( __( 'You do not have permission to perform this action.', 'spacious' ) ); + } + if ( ! wp_verify_nonce( $_GET['_demo_import_migration_nonce'], 'demo_import_migration' ) ) { wp_die( __( 'Action failed. Please refresh the page and retry.', 'spacious' ) ); } From a576d69487620d4f3c654c5a28bcc28df14b5bb5 Mon Sep 17 00:00:00 2001 From: Rajat Gautam <83450756+rajatgautam755421@users.noreply.github.com> Date: Mon, 28 Sep 2026 12:39:58 +0545 Subject: [PATCH 14/19] Fix - Block borders set in the editor not showing on the front end (#150) Co-authored-by: Claude Opus 5.5 --- assets/scss/_theme-style.scss | 49 +++++++++++++++++++++++++++++++++++ style-rtl.css | 45 ++++++++++++++++++++++++++++++++ style.css | 49 +++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) diff --git a/assets/scss/_theme-style.scss b/assets/scss/_theme-style.scss index ba10d4e8..368f309a 100644 --- a/assets/scss/_theme-style.scss +++ b/assets/scss/_theme-style.scss @@ -89,6 +89,55 @@ td { vertical-align: baseline; } +/*rtl:begin:ignore*/ +html :where(.has-border-color), +html :where([style^=border-color], [style*=";border-color"], [style*="; border-color"]), +html :where([style^=border-width], [style*=";border-width"], [style*="; border-width"]) { + border-style: solid; +} + +html :where([style^=border-top-color], [style*=";border-top-color"], [style*="; border-top-color"]), +html :where([style^=border-top-width], [style*=";border-top-width"], [style*="; border-top-width"]) { + border-top-style: solid; +} + +html :where([style^=border-right-color], [style*=";border-right-color"], [style*="; border-right-color"]), +html :where([style^=border-right-width], [style*=";border-right-width"], [style*="; border-right-width"]) { + border-right-style: solid; +} + +html :where([style^=border-bottom-color], [style*=";border-bottom-color"], [style*="; border-bottom-color"]), +html :where([style^=border-bottom-width], [style*=";border-bottom-width"], [style*="; border-bottom-width"]) { + border-bottom-style: solid; +} + +html :where([style^=border-left-color], [style*=";border-left-color"], [style*="; border-left-color"]), +html :where([style^=border-left-width], [style*=";border-left-width"], [style*="; border-left-width"]) { + border-left-style: solid; +} + +html :where(.has-border-color), +html :where([style^=border-color], [style*=";border-color"], [style*="; border-color"]) { + border-width: medium; +} + +html :where([style^=border-top-color], [style*=";border-top-color"], [style*="; border-top-color"]) { + border-top-width: medium; +} + +html :where([style^=border-right-color], [style*=";border-right-color"], [style*="; border-right-color"]) { + border-right-width: medium; +} + +html :where([style^=border-bottom-color], [style*=";border-bottom-color"], [style*="; border-bottom-color"]) { + border-bottom-width: medium; +} + +html :where([style^=border-left-color], [style*=";border-left-color"], [style*="; border-left-color"]) { + border-left-width: medium; +} +/*rtl:end:ignore*/ + html { overflow-y: scroll; /* Keeps page centred in all browsers regardless of content height */ diff --git a/style-rtl.css b/style-rtl.css index b7bc99cd..f2cd8a68 100644 --- a/style-rtl.css +++ b/style-rtl.css @@ -177,7 +177,52 @@ td { padding: 0; vertical-align: baseline; } +html :where(.has-border-color), +html :where([style^=border-color], [style*=";border-color"], [style*="; border-color"]), +html :where([style^=border-width], [style*=";border-width"], [style*="; border-width"]) { + border-style: solid; +} + +html :where([style^=border-top-color], [style*=";border-top-color"], [style*="; border-top-color"]), +html :where([style^=border-top-width], [style*=";border-top-width"], [style*="; border-top-width"]) { + border-top-style: solid; +} + +html :where([style^=border-right-color], [style*=";border-right-color"], [style*="; border-right-color"]), +html :where([style^=border-right-width], [style*=";border-right-width"], [style*="; border-right-width"]) { + border-right-style: solid; +} + +html :where([style^=border-bottom-color], [style*=";border-bottom-color"], [style*="; border-bottom-color"]), +html :where([style^=border-bottom-width], [style*=";border-bottom-width"], [style*="; border-bottom-width"]) { + border-bottom-style: solid; +} + +html :where([style^=border-left-color], [style*=";border-left-color"], [style*="; border-left-color"]), +html :where([style^=border-left-width], [style*=";border-left-width"], [style*="; border-left-width"]) { + border-left-style: solid; +} +html :where(.has-border-color), +html :where([style^=border-color], [style*=";border-color"], [style*="; border-color"]) { + border-width: medium; +} + +html :where([style^=border-top-color], [style*=";border-top-color"], [style*="; border-top-color"]) { + border-top-width: medium; +} + +html :where([style^=border-right-color], [style*=";border-right-color"], [style*="; border-right-color"]) { + border-right-width: medium; +} + +html :where([style^=border-bottom-color], [style*=";border-bottom-color"], [style*="; border-bottom-color"]) { + border-bottom-width: medium; +} + +html :where([style^=border-left-color], [style*=";border-left-color"], [style*="; border-left-color"]) { + border-left-width: medium; +} html { overflow-y: scroll; /* Keeps page centred in all browsers regardless of content height */ diff --git a/style.css b/style.css index 1d83f83a..84cd6b65 100644 --- a/style.css +++ b/style.css @@ -88,6 +88,55 @@ td { vertical-align: baseline; } +/*rtl:begin:ignore*/ +html :where(.has-border-color), +html :where([style^=border-color], [style*=";border-color"], [style*="; border-color"]), +html :where([style^=border-width], [style*=";border-width"], [style*="; border-width"]) { + border-style: solid; +} + +html :where([style^=border-top-color], [style*=";border-top-color"], [style*="; border-top-color"]), +html :where([style^=border-top-width], [style*=";border-top-width"], [style*="; border-top-width"]) { + border-top-style: solid; +} + +html :where([style^=border-right-color], [style*=";border-right-color"], [style*="; border-right-color"]), +html :where([style^=border-right-width], [style*=";border-right-width"], [style*="; border-right-width"]) { + border-right-style: solid; +} + +html :where([style^=border-bottom-color], [style*=";border-bottom-color"], [style*="; border-bottom-color"]), +html :where([style^=border-bottom-width], [style*=";border-bottom-width"], [style*="; border-bottom-width"]) { + border-bottom-style: solid; +} + +html :where([style^=border-left-color], [style*=";border-left-color"], [style*="; border-left-color"]), +html :where([style^=border-left-width], [style*=";border-left-width"], [style*="; border-left-width"]) { + border-left-style: solid; +} + +html :where(.has-border-color), +html :where([style^=border-color], [style*=";border-color"], [style*="; border-color"]) { + border-width: medium; +} + +html :where([style^=border-top-color], [style*=";border-top-color"], [style*="; border-top-color"]) { + border-top-width: medium; +} + +html :where([style^=border-right-color], [style*=";border-right-color"], [style*="; border-right-color"]) { + border-right-width: medium; +} + +html :where([style^=border-bottom-color], [style*=";border-bottom-color"], [style*="; border-bottom-color"]) { + border-bottom-width: medium; +} + +html :where([style^=border-left-color], [style*=";border-left-color"], [style*="; border-left-color"]) { + border-left-width: medium; +} + +/*rtl:end:ignore*/ html { overflow-y: scroll; /* Keeps page centred in all browsers regardless of content height */ From d7387a10540c5631676eb0fbc5a6c021fae1c732 Mon Sep 17 00:00:00 2001 From: Rajat Gautam <83450756+rajatgautam755421@users.noreply.github.com> Date: Mon, 28 Sep 2026 13:12:47 +0545 Subject: [PATCH 15/19] Fix - Header cart count and total stale after AJAX add to cart (#143) * Fix - Header cart count and total stayed stale after AJAX add to cart The header cart block was rendered once at page load and never registered as a WooCommerce cart fragment, so it only refreshed on a full reload while the mini-cart updated. Move the block into a shared function used by both the initial render and a woocommerce_add_to_cart_fragments filter. The filter is registered with the default spacious_cart_icon(), so a child theme overriding it keeps its own markup, and it never overwrites a fragment already supplied for the same selector. Co-Authored-By: Claude Opus 5.5 * Update translation template after moving the header cart strings Regenerated with the makepot options from Gruntfile.js. Only the creation date and source line references change. Co-Authored-By: Claude Opus 5.5 * Fix - Update the header cart on the Cart and Checkout pages The WooCommerce cart widget, which normally loads cart fragments, hides itself on the Cart and Checkout pages, so the header cart never refreshed there. The Cart and Checkout blocks also change the cart through the Store API without firing the events cart fragments listen for. Load cart fragments whenever the header cart icon is on, and when the Cart or Checkout block's cart store changes its item count or subtotal, trigger a fragment refresh so the header is re-rendered by spacious_cart_icon_views(). Co-Authored-By: Claude Opus 5.5 (1M context) --------- Co-authored-by: Claude Opus 5.5 --- inc/header-functions.php | 125 ++++++++++++++++++++++++++++++++++----- languages/spacious.pot | 34 +++++------ 2 files changed, 126 insertions(+), 33 deletions(-) diff --git a/inc/header-functions.php b/inc/header-functions.php index 83f2945e..8cddb63f 100644 --- a/inc/header-functions.php +++ b/inc/header-functions.php @@ -81,22 +81,7 @@ function spacious_cart_icon() { if ( ( get_theme_mod( 'spacious_cart_icon', 0 ) == 1 ) && class_exists( 'woocommerce' ) ) : ?>
    -
    - - - cart->get_cart_url(); ?> - - - - cart->get_cart_contents_count() ); ?> - - - -
    -
    -
    cart->get_cart_subtotal() ); ?>
    -
    -
    + @@ -106,6 +91,114 @@ function spacious_cart_icon() { endif; } + // Registered with the default renderer, so a child theme overriding spacious_cart_icon() keeps its own markup. + add_filter( 'woocommerce_add_to_cart_fragments', 'spacious_cart_icon_fragment' ); + add_action( 'wp_enqueue_scripts', 'spacious_cart_icon_scripts' ); + +endif; + +if ( ! function_exists( 'spacious_cart_icon_views' ) ) : + + /** + * Output the header cart icon, item count and total. + * + * Shared by the initial render and the WooCommerce cart fragment so both stay identical. + */ + function spacious_cart_icon_views() { + $cart_url = function_exists( 'wc_get_cart_url' ) ? wc_get_cart_url() : WC()->cart->get_cart_url(); + ?> +
    + + + + + + cart->get_cart_contents_count() ); ?> + + + +
    +
    +
    cart->get_cart_subtotal() ); ?>
    +
    +
    + cart ) || isset( $fragments[ $selector ] ) ) { + return $fragments; + } + + ob_start(); + spacious_cart_icon_views(); + $fragments[ $selector ] = ob_get_clean(); + + return $fragments; + } + +endif; + +if ( ! function_exists( 'spacious_cart_icon_scripts' ) ) : + + /** + * Load what keeps the header cart icon in sync on the Cart and Checkout pages. + * + * The WooCommerce cart widget, which normally loads cart fragments, hides itself on those pages, + * and the Cart and Checkout blocks change the cart without firing the events cart fragments listen for. + */ + function spacious_cart_icon_scripts() { + if ( 1 !== (int) get_theme_mod( 'spacious_cart_icon', 0 ) || ! class_exists( 'woocommerce' ) ) { + return; + } + + wp_enqueue_script( 'wc-cart-fragments' ); + + // Printed only where the blocks load their cart store. + wp_add_inline_script( + 'wc-blocks-data-store', + "( function () { + var cart = window.wp && wp.data && wp.data.select( 'wc/store/cart' ); + + if ( ! cart ) { + return; + } + + function key() { + var data = cart.getCartData(); + + return data.itemsCount + '|' + data.totals.total_items; + } + + var last = key(); + + wp.data.subscribe( function () { + var now = key(); + + if ( now !== last && window.jQuery ) { + jQuery( document.body ).trigger( 'wc_fragment_refresh' ); + } + + last = now; + } ); + } )();" + ); + } + endif; /****************************************************************************************/ diff --git a/languages/spacious.pot b/languages/spacious.pot index c0b826f1..2b27b92e 100644 --- a/languages/spacious.pot +++ b/languages/spacious.pot @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: Spacious 1.9.12\n" "Report-Msgid-Bugs-To: themegrill@gmail.com\n" -"POT-Creation-Date: 2026-09-18 11:47:31+00:00\n" +"POT-Creation-Date: 2026-09-28 06:56:32+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -187,7 +187,7 @@ msgstr "" #: functions.php:324 functions.php:329 #: inc/customizer/class-spacious-customizer-partials.php:30 -#: inc/functions.php:662 inc/header-functions.php:166 +#: inc/functions.php:662 inc/header-functions.php:259 #: inc/widgets/class-spacious-featured-single-page-widget.php:104 #: inc/widgets/class-spacious-service-widget.php:48 #: inc/widgets/class-spacious-service-widget.php:74 @@ -421,7 +421,7 @@ msgstr "" #: inc/customizer/class-spacious-customizer-partials.php:49 #: inc/customizer/class-spacious-customizer-partials.php:56 -#: inc/header-functions.php:314 inc/header-functions.php:320 +#: inc/header-functions.php:407 inc/header-functions.php:413 msgid "You are here: " msgstr "" @@ -1447,59 +1447,59 @@ msgstr "" msgid "Comments Off" msgstr "" -#: inc/header-functions.php:96 +#: inc/header-functions.php:121 msgid "Total" msgstr "" -#: inc/header-functions.php:244 +#: inc/header-functions.php:337 msgid "Author: %s" msgstr "" -#: inc/header-functions.php:252 +#: inc/header-functions.php:345 msgid "Day: %s" msgstr "" -#: inc/header-functions.php:255 +#: inc/header-functions.php:348 msgid "Month: %s" msgstr "" -#: inc/header-functions.php:258 +#: inc/header-functions.php:351 msgid "Year: %s" msgstr "" -#: inc/header-functions.php:261 +#: inc/header-functions.php:354 msgid "Asides" msgstr "" -#: inc/header-functions.php:264 +#: inc/header-functions.php:357 msgid "Images" msgstr "" -#: inc/header-functions.php:267 +#: inc/header-functions.php:360 msgid "Videos" msgstr "" -#: inc/header-functions.php:270 +#: inc/header-functions.php:363 msgid "Quotes" msgstr "" -#: inc/header-functions.php:273 +#: inc/header-functions.php:366 msgid "Links" msgstr "" -#: inc/header-functions.php:279 sidebar-left.php:29 sidebar.php:32 +#: inc/header-functions.php:372 sidebar-left.php:29 sidebar.php:32 msgid "Archives" msgstr "" -#: inc/header-functions.php:283 +#: inc/header-functions.php:376 msgid "Page NOT Found" msgstr "" -#: inc/header-functions.php:285 +#: inc/header-functions.php:378 msgid "Search Results" msgstr "" -#: inc/header-functions.php:341 +#: inc/header-functions.php:434 msgid "Menu" msgstr "" From 35dfca8fe59e1e11be2dba45f51b28d6b1425676 Mon Sep 17 00:00:00 2001 From: Rajat Gautam <83450756+rajatgautam755421@users.noreply.github.com> Date: Mon, 28 Sep 2026 13:29:11 +0545 Subject: [PATCH 16/19] Fix - Block editor ignores Customizer typography and colors (#152) * Fix - Block editor ignores Customizer typography and colors Co-Authored-By: Claude Opus 5.5 * Fix - Editor post title used the H1 size instead of the front-end title size Co-Authored-By: Claude Opus 5.5 * Add - E2E guard: block editor matches the front-end Customizer typography and colors Co-Authored-By: Claude Opus 5.5 * Remove - manually added E2E spec; specs come from the verify-fix flow Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 --- inc/functions.php | 105 +++++++++++++++++++++++++++++++++++++++++ style-editor-block.css | 23 ++++----- 2 files changed, 115 insertions(+), 13 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 39011824..f6b7c083 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -17,10 +17,115 @@ */ function spacious_block_editor_styles() { wp_enqueue_style( 'spacious-block-editor-styles', get_template_directory_uri() . '/style-editor-block.css', array(), SPACIOUS_THEME_VERSION ); + + $editor_css = spacious_block_editor_dynamic_css(); + + if ( $editor_css ) { + wp_add_inline_style( 'spacious-block-editor-styles', $editor_css ); + } } add_action( 'enqueue_block_editor_assets', 'spacious_block_editor_styles', 1, 1 ); +/** + * Load the theme fonts inside the block editor canvas. + * + * Lato is bundled with the theme, like on the front end; other Customizer fonts + * come from the same loader the front end uses. + */ +function spacious_block_editor_fonts() { + if ( ! is_admin() ) { + return; + } + + $lato_faces = array( + 'Thin' => array( 100, 'normal' ), + 'ThinItalic' => array( 100, 'italic' ), + 'Light' => array( 300, 'normal' ), + 'LightItalic' => array( 300, 'italic' ), + 'Regular' => array( 400, 'normal' ), + 'Italic' => array( 400, 'italic' ), + 'Bold' => array( 700, 'normal' ), + 'BoldItalic' => array( 700, 'italic' ), + 'Black' => array( 900, 'normal' ), + 'BlackItalic' => array( 900, 'italic' ), + ); + $lato_css = ''; + + foreach ( $lato_faces as $file => $face ) { + $lato_css .= "@font-face{font-family:'Lato';font-style:" . $face[1] . ';font-weight:' . $face[0] . ';src:url(' . esc_url( get_template_directory_uri() . '/assets/fonts/Lato-' . $file . '.woff' ) . ") format('woff');}"; + } + + wp_register_style( 'spacious-editor-fonts', false, array(), SPACIOUS_THEME_VERSION ); + wp_enqueue_style( 'spacious-editor-fonts' ); + wp_add_inline_style( 'spacious-editor-fonts', $lato_css ); + + Spacious_Generate_Fonts::render_fonts(); +} + +add_action( 'enqueue_block_assets', 'spacious_block_editor_fonts' ); + +if ( ! function_exists( 'spacious_block_editor_dynamic_css' ) ) : + + /** + * Build block editor CSS from the Customizer typography and color settings. + * + * Mirrors the post content rules of Spacious_Dynamic_CSS::render_output(), scoped to + * the editor canvas. Like the front end, a setting outputs nothing while it is at its default. + * + * @return string Editor CSS. + */ + function spacious_block_editor_dynamic_css() { + $wrapper = '.editor-styles-wrapper'; + $css = ''; + + // The Dark skin stylesheet is front-end only, so its colors would be overridden here. + if ( 'dark' !== get_theme_mod( 'spacious_color_skin', 'light' ) ) { + $primary_color = get_theme_mod( 'spacious_primary_color', '#0FBE7C' ); + $primary_dark = spacious_darkcolor( $primary_color, -50 ); + + $css .= spacious_parse_css( + '#0FBE7C', + $primary_color, + array( + $wrapper . ' a, ' . $wrapper . ' .wp-block-file .wp-block-file__textlink' => array( + 'color' => esc_html( $primary_color ), + ), + $wrapper . ' input[type="reset"], ' . $wrapper . ' input[type="button"], ' . $wrapper . ' input[type="submit"]' => array( + 'background-color' => esc_html( $primary_color ), + ), + $wrapper . ' blockquote:not(.wp-block-quote)' => array( + 'border-left-color' => esc_html( $primary_color ), + ), + $wrapper . ' input[type="reset"]:hover, ' . $wrapper . ' input[type="button"]:hover, ' . $wrapper . ' input[type="submit"]:hover' => array( + 'background' => esc_html( $primary_dark ), + ), + ) + ); + } + + $font_default = array( + 'font-family' => 'Lato', + 'font-weight' => 'regular', + ); + + $css .= spacious_parse_typography_css( + $font_default, + get_theme_mod( 'spacious_content_font_typography', $font_default ), + $wrapper . ', ' . $wrapper . ' > *, ' . $wrapper . ' p' + ); + + $css .= spacious_parse_typography_css( + $font_default, + get_theme_mod( 'spacious_titles_font_typography', $font_default ), + $wrapper . ' h1, ' . $wrapper . ' h2, ' . $wrapper . ' h3, ' . $wrapper . ' h4, ' . $wrapper . ' h5, ' . $wrapper . ' h6' + ); + + return $css; + } + +endif; + /* * Display the related posts. */ diff --git a/style-editor-block.css b/style-editor-block.css index 58af11af..c4979436 100644 --- a/style-editor-block.css +++ b/style-editor-block.css @@ -20,10 +20,7 @@ max-width: 1268px; } -.editor-styles-wrapper .wp-block.editor-post-title__block .editor-post-title__input { - font-weight: normal; - color: #222222; - font-family: 'Lato', sans-serif; +.editor-styles-wrapper .editor-post-title__input { font-size: 22px; padding-bottom: 0; line-height: 36px; @@ -42,32 +39,32 @@ font-family: 'Lato', sans-serif; } -.editor-styles-wrapper .wp-block h1 { +.editor-styles-wrapper h1 { font-size: 30px; line-height: 36px; } -.editor-styles-wrapper .wp-block h2 { +.editor-styles-wrapper h2 { font-size: 28px; line-height: 34px; } -.editor-styles-wrapper .wp-block h3 { +.editor-styles-wrapper h3 { font-size: 26px; - line-height: 30px; + line-height: 32px; } -.editor-styles-wrapper .wp-block h4 { +.editor-styles-wrapper h4 { font-size: 24px; - line-height: 28px; + line-height: 30px; } -.editor-styles-wrapper .wp-block h5 { +.editor-styles-wrapper h5 { font-size: 22px; - line-height: 26px; + line-height: 28px; } -.editor-styles-wrapper .wp-block h6 { +.editor-styles-wrapper h6 { font-size: 19px; line-height: 25px; } From 7615141a4fbea4c3d74856f4726c3f90fee55e5f Mon Sep 17 00:00:00 2001 From: deepench Date: Mon, 28 Sep 2026 12:05:15 +0100 Subject: [PATCH 17/19] Match footer credit wording to Pro's corrected punctuation spacious_footer_copyright()'s default text was missing a colon after "Theme" and carried a trailing period Pro's own fix already dropped - "Theme Spacious by ThemeGrill. Powered by: WordPress." here vs Pro's "Theme: Spacious Pro by ThemeGrill. Powered by: WordPress". Added the colon and removed the trailing period so both editions read the same way. Verified live: front end now shows "Theme: Spacious by ThemeGrill. Powered by: WordPress" with no trailing period. PHPCS unchanged (166 errors / 66 warnings, identical before and after). --- inc/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/functions.php b/inc/functions.php index f6b7c083..515cff5b 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -659,7 +659,7 @@ function spacious_footer_copyright() { $tg_link = '' . __( 'Spacious', 'spacious' ) . ''; - $default_footer_value = sprintf( __( 'Copyright © %1$s %2$s.', 'spacious' ), date( 'Y' ), $site_link ) . ' ' .__('All rights reserved.', 'spacious' ). sprintf( __( ' Theme %1$s', 'spacious' ), $tg_link ) . ' ' . sprintf( __( 'by ThemeGrill. Powered by: %2$s.', 'spacious' ), 'Spacious', $wp_link ); + $default_footer_value = sprintf( __( 'Copyright © %1$s %2$s.', 'spacious' ), date( 'Y' ), $site_link ) . ' ' .__('All rights reserved.', 'spacious' ). sprintf( __( ' Theme: %1$s', 'spacious' ), $tg_link ) . ' ' . sprintf( __( 'by ThemeGrill. Powered by: %2$s', 'spacious' ), 'Spacious', $wp_link ); $spacious_footer_copyright = ''; echo $spacious_footer_copyright; From 2442642f3c000fcb518b4033b8e4eace07dc596e Mon Sep 17 00:00:00 2001 From: deepench Date: Mon, 28 Sep 2026 12:10:17 +0100 Subject: [PATCH 18/19] Version bump to release of v1.10.0 --- package.json | 2 +- readme.txt | 13 +++++ style-rtl.css | 2 +- style.css | 134 +++++++++++++++++++++++++------------------------- 4 files changed, 82 insertions(+), 69 deletions(-) diff --git a/package.json b/package.json index f454b88f..83355fd7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "packageManager": "pnpm@10.34.5", "name": "spacious", "title": "Spacious", - "version": "1.9.12", + "version": "1.10.0", "homepage": "https://themegrill.com/themes/spacious", "repository": { "type": "git", diff --git a/readme.txt b/readme.txt index c3e48771..3bdbec94 100644 --- a/readme.txt +++ b/readme.txt @@ -49,6 +49,19 @@ and we will include it within the theme from next version update. /**********************************************************/ == Changelog == +== Version 1.10.0 - 2026-09-28 == +* Dev - Missing capability check on demo-import migration trigger. +* Dev - Unbounded get_users() full-table scan in theme-switch cleanup. +* Dev - Meta box save routine reads $_POST without isset() checks or a value whitelist. +* Dev - Deprecation notice on every page load from the IE 8 html5shiv script under WordPress 6.9+. +* Dev - JavaScript errors from navigation.js on the Widgets screen and other pages without the theme header. +* Fix - Submenu caret icon never flips direction when toggled. +* Fix - Search block input renders taller than its Search button. +* Fix - Slider scripts loaded on the homepage even when the slider setting was off. +* Fix - Header cart count and total now update right after an AJAX add to cart, without a page reload. +* Fix - Block editor typography (fonts, sizes, and colors) didn't reflect the Customizer's configured settings. +* Fix - Block borders set in the editor now also show on the front end. Borders that were set but hidden will now appear. + == Version 1.9.12- 2025-08-08 == * Update - Settings for blocks. diff --git a/style-rtl.css b/style-rtl.css index f2cd8a68..627f5382 100644 --- a/style-rtl.css +++ b/style-rtl.css @@ -95,7 +95,7 @@ Theme URI: https://themegrill.com/themes/spacious Author: ThemeGrill Author URI: https://themegrill.com Description: Spacious is an incredible free business WordPress theme that fits small to medium businesses. This responsive theme is optimized for speed. So, you end up with a business website that loads within 1 second. Since Spacious is compatible with WooCommerce, you can easily set up an online store for your business. It's amazing collection of 16 free starter demos makes building a complete website very beginner-friendly. Spacious theme supports Gutenberg and page builder plugins like Elementor, Brizy, Beaver Builder, SiteOrigin, etc. So, import a demo with just one click and start customizing it using a page builder of your choice. Here are the key Spacious features you get with this theme: >> Well-coded >> Translation & RTL Ready >> Sticky Menu >> Complete Documentation >> Multiple Widget Areas >> Optimized for SEO >> Compatible with Major Browsers. Try this free theme yourself to establish and grow your business’ online presence without having to rely on developers or designers. You can always reach out at https://themegrill.com/contact/ if you encounter any issues. Also, explore all the available free starter sites at https://themegrilldemos.com/spacious-demos. -Version: 1.9.12 +Version: 1.10.0 Tested up to: 6.8 Requires PHP: 5.6 License: GNU General Public License v2 or laterals diff --git a/style.css b/style.css index 84cd6b65..4676caa2 100644 --- a/style.css +++ b/style.css @@ -1,24 +1,24 @@ @charset "UTF-8"; -/* -Theme Name: Spacious -Theme URI: https://themegrill.com/themes/spacious -Author: ThemeGrill -Author URI: https://themegrill.com -Description: Spacious is an incredible free business WordPress theme that fits small to medium businesses. This responsive theme is optimized for speed. So, you end up with a business website that loads within 1 second. Since Spacious is compatible with WooCommerce, you can easily set up an online store for your business. It's amazing collection of 16 free starter demos makes building a complete website very beginner-friendly. Spacious theme supports Gutenberg and page builder plugins like Elementor, Brizy, Beaver Builder, SiteOrigin, etc. So, import a demo with just one click and start customizing it using a page builder of your choice. Here are the key Spacious features you get with this theme: >> Well-coded >> Translation & RTL Ready >> Sticky Menu >> Complete Documentation >> Multiple Widget Areas >> Optimized for SEO >> Compatible with Major Browsers. Try this free theme yourself to establish and grow your business’ online presence without having to rely on developers or designers. You can always reach out at https://themegrill.com/contact/ if you encounter any issues. Also, explore all the available free starter sites at https://themegrilldemos.com/spacious-demos. -Version: 1.9.12 -Tested up to: 6.8 -Requires PHP: 7.4 -License: GNU General Public License v2 or laterals -License URI: http://www.gnu.org/licenses/gpl-2.0.html -Tags: one-column, two-columns, right-sidebar, left-sidebar, flexible-header, custom-header, custom-background, custom-menu, custom-colors, sticky-post, threaded-comments, translation-ready, featured-images, theme-options, footer-widgets, blog, e-commerce, rtl-language-support -Text Domain: spacious - -Resetting and rebuilding styles have been helped along thanks to the fine work of -Eric Meyer http://meyerweb.com/eric/tools/css/reset/index.html -along with Nicolas Gallagher and Jonathan Neal http://necolas.github.com/normalize.css/ -and Blueprint http://www.blueprintcss.org/ +/* +Theme Name: Spacious +Theme URI: https://themegrill.com/themes/spacious +Author: ThemeGrill +Author URI: https://themegrill.com +Description: Spacious is an incredible free business WordPress theme that fits small to medium businesses. This responsive theme is optimized for speed. So, you end up with a business website that loads within 1 second. Since Spacious is compatible with WooCommerce, you can easily set up an online store for your business. It's amazing collection of 16 free starter demos makes building a complete website very beginner-friendly. Spacious theme supports Gutenberg and page builder plugins like Elementor, Brizy, Beaver Builder, SiteOrigin, etc. So, import a demo with just one click and start customizing it using a page builder of your choice. Here are the key Spacious features you get with this theme: >> Well-coded >> Translation & RTL Ready >> Sticky Menu >> Complete Documentation >> Multiple Widget Areas >> Optimized for SEO >> Compatible with Major Browsers. Try this free theme yourself to establish and grow your business’ online presence without having to rely on developers or designers. You can always reach out at https://themegrill.com/contact/ if you encounter any issues. Also, explore all the available free starter sites at https://themegrilldemos.com/spacious-demos. +Version: 1.10.0 +Tested up to: 6.8 +Requires PHP: 7.4 +License: GNU General Public License v2 or laterals +License URI: http://www.gnu.org/licenses/gpl-2.0.html +Tags: one-column, two-columns, right-sidebar, left-sidebar, flexible-header, custom-header, custom-background, custom-menu, custom-colors, sticky-post, threaded-comments, translation-ready, featured-images, theme-options, footer-widgets, blog, e-commerce, rtl-language-support +Text Domain: spacious + +Resetting and rebuilding styles have been helped along thanks to the fine work of +Eric Meyer http://meyerweb.com/eric/tools/css/reset/index.html +along with Nicolas Gallagher and Jonathan Neal http://necolas.github.com/normalize.css/ +and Blueprint http://www.blueprintcss.org/ */ -/* =Reset +/* =Reset -------------------------------------------------------------- */ html, body, @@ -242,7 +242,7 @@ button::-moz-focus-inner { margin: 0; } -/* =GLOBAL +/* =GLOBAL ----------------------------------------------- */ body, button, @@ -310,7 +310,7 @@ hr, margin: 30px 0; } -/* =TEXT ELEMENTS +/* =TEXT ELEMENTS ----------------------------------------------- */ p { line-height: 24px; @@ -374,7 +374,7 @@ table { width: 100%; } -/* =SUPERCRIPT AND SUBSCRIPT +/* =SUPERCRIPT AND SUBSCRIPT ----------------------------------------------- */ sup, sub { @@ -421,7 +421,7 @@ li > ol { margin: 0 0 0 10px; } -/* =BLOCKQUOTE +/* =BLOCKQUOTE ----------------------------------------------- */ blockquote, .wp-block-quote, @@ -457,7 +457,7 @@ blockquote cite, text-transform: uppercase; } -/* =FORMS +/* =FORMS ----------------------------------------------- */ input, textarea { @@ -604,7 +604,7 @@ input[type="reset"]:active, input[type="button"]:active, input[type="submit"]:ac box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.3); } -/* =LINKS +/* =LINKS ----------------------------------------------- */ a { color: #0FBE7C; @@ -621,7 +621,7 @@ a:focus { outline: thin dotted; } -/* =IMAGES +/* =IMAGES ----------------------------------------------- */ figure { margin: 0; @@ -662,7 +662,7 @@ img#wpstats { display: none; } -/* =CLEARFIX +/* =CLEARFIX ----------------------------------------------- */ .clearfix { display: inline-block; @@ -689,7 +689,7 @@ img#wpstats { clear: both; } -/* =ALIGNMENT +/* =ALIGNMENT ----------------------------------------------- */ .alignleft { display: inline; @@ -767,7 +767,7 @@ img#wpstats { /*****************************************************************/ /*****************************************************************/ -/* =BODY +/* =BODY ----------------------------------------------- */ body { padding-top: 30px; @@ -820,7 +820,7 @@ body { max-width: 978px; } -/* =FEATURED SLIDER +/* =FEATURED SLIDER ----------------------------------------------- */ #featured-slider { border-bottom: 1px solid #EAEAEA; @@ -927,7 +927,7 @@ body { background-color: #666666; } -/* =SLIDER CONTROLLERS +/* =SLIDER CONTROLLERS ----------------------------------------------- */ #controllers { text-align: center; @@ -953,7 +953,7 @@ body { color: #0FBE7C; } -/* =PAGE HEADER TITLE +/* =PAGE HEADER TITLE ----------------------------------------------- */ .header-post-title-container { border-bottom: 1px solid #EAEAEA; @@ -974,7 +974,7 @@ body { line-height: 36px; } -/* =BREADCRUMB NAVXT PLUGIN SUPPORT +/* =BREADCRUMB NAVXT PLUGIN SUPPORT ----------------------------------------------- */ .breadcrumb { float: right; @@ -993,7 +993,7 @@ body { color: #0FBE7C; } -/* =CONTENT +/* =CONTENT ----------------------------------------------- */ .sticky { display: block; @@ -1183,7 +1183,7 @@ body { font-size: 11px; } -/* =META +/* =META ----------------------------------------------- */ .meta { border-bottom: 1px dashed #EAEAEA; @@ -1219,7 +1219,7 @@ body { text-align: center; } -/* =NAVIGATION +/* =NAVIGATION ----------------------------------------------- */ .default-wp-page .previous { float: left; @@ -1239,7 +1239,7 @@ body { padding-bottom: 3px; } -/* =PAGINATION +/* =PAGINATION ----------------------------------------------- */ .pagination { margin: 15px 0; @@ -1263,7 +1263,7 @@ body { text-decoration: none; } -/* =TESTIMONIAL WIDGET +/* =TESTIMONIAL WIDGET ----------------------------------------------- */ .widget_testimonial { margin-bottom: 30px; @@ -1316,7 +1316,7 @@ body { color: #666666; } -/* =CALL TO ACTION WIDGET +/* =CALL TO ACTION WIDGET ----------------------------------------------- */ .widget_call_to_action { margin-bottom: 60px; @@ -1387,7 +1387,7 @@ body { margin-top: 5px; } -/* =MEDIA +/* =MEDIA ----------------------------------------------- */ .wp-caption { border: 1px solid #ccc; @@ -1409,7 +1409,7 @@ body { margin: 0.8075rem 0; } -/* =WORDPRESS GALLERY SUPPORT +/* =WORDPRESS GALLERY SUPPORT ----------------------------------------------- */ .gallery { margin: 0 auto 15px; @@ -1494,7 +1494,7 @@ ul.wp-block-gallery img { margin-bottom: 0; } -/* Gutenberg Blocks Layout. +/* Gutenberg Blocks Layout. --------------------------------------------------------------*/ .wp-block-gallery.alignleft, .wp-block-gallery.alignright, .wp-block-gallery.aligncenter, .wp-block-social-links.alignleft, @@ -1505,7 +1505,7 @@ ul.wp-block-gallery img { display: flex; } -/* =COMMENTS +/* =COMMENTS ----------------------------------------------- */ #comments { background-color: #FFFFFF; @@ -1715,7 +1715,7 @@ ul.wp-block-gallery img { color: #0FBE7C; } -/* =COMMENT FORM +/* =COMMENT FORM ----------------------------------------------- */ #respond h3#reply-title { line-height: 1.5; @@ -1832,7 +1832,7 @@ ul.wp-block-gallery img { color: #0FBE7C; } -/* =WIDGETS +/* =WIDGETS ----------------------------------------------- */ #secondary .widget ul li { line-height: 1.5; @@ -1898,7 +1898,7 @@ ul.wp-block-gallery img { width: 50%; } -/* =CALENDAR WIDGET +/* =CALENDAR WIDGET ----------------------------------------------- */ #wp-calendar th, #wp-calendar td, @@ -1946,7 +1946,7 @@ ul.wp-block-gallery img { padding: 1px 1px 1px 2px; } -/* =SCROLL UP +/* =SCROLL UP ----------------------------------------------- */ a#scroll-up { bottom: 20px; @@ -1976,7 +1976,7 @@ a#scroll-up:hover { /* For IE8 and earlier */ } -/* =READ MORE LINK +/* =READ MORE LINK ----------------------------------------------- */ .read-more, .more-link { @@ -1985,7 +1985,7 @@ a#scroll-up:hover { font-family: 'Lato', sans-serif; } -/* =PRIMARY AND SECONDARY CONTENT AREA +/* =PRIMARY AND SECONDARY CONTENT AREA ----------------------------------------------- */ #primary { float: left; @@ -2007,7 +2007,7 @@ a#scroll-up:hover { width: 30.4827586%; } -/* =LEFT SIDEBAR CONTENT LAYOUT +/* =LEFT SIDEBAR CONTENT LAYOUT -------------------------------------------------------------- */ .left-sidebar #primary { float: right; @@ -2017,7 +2017,7 @@ a#scroll-up:hover { float: left; } -/* =NO SIDEBAR CONTENT LAYOUT +/* =NO SIDEBAR CONTENT LAYOUT -------------------------------------------------------------- */ .no-sidebar #primary { float: none; @@ -2033,7 +2033,7 @@ a#scroll-up:hover { display: none; } -/* =POST CONTENT +/* =POST CONTENT ----------------------------------------------- */ #content .post, #content .page { @@ -2227,7 +2227,7 @@ a#scroll-up:hover { text-decoration: none; } -/* header media css +/* header media css --------------------------------------------------------------*/ div.wp-custom-header { position: relative; @@ -2384,7 +2384,7 @@ body.no-sidebar-content-stretched #main .inner-wrap #primary { src: url(assets/fonts/Lato-BlackItalic.woff) format("woff"); } -/* =HEADER +/* =HEADER ----------------------------------------------- */ #header-meta { background-color: #F8F8F8; @@ -2470,7 +2470,7 @@ h3#site-title { width: 100%; } -/* =SEARCH WIDGET +/* =SEARCH WIDGET ----------------------------------------------- */ #masthead .search-wrap input.s { width: 130px; @@ -2494,7 +2494,7 @@ h3#site-title { padding: 3px 7px; } -/* =HEADER INFO TEXT +/* =HEADER INFO TEXT ----------------------------------------------- */ .small-info-text { float: left; @@ -2509,7 +2509,7 @@ h3#site-title { margin-bottom: 0; } -/* =SOCIAL LINKS +/* =SOCIAL LINKS ----------------------------------------------- */ .social-links { max-width: 100%; @@ -2568,7 +2568,7 @@ h3#site-title { /* For IE8 and earlier */ } -/* =NAVIGATION +/* =NAVIGATION ----------------------------------------------- */ nav li > ul, nav li > ol { @@ -2812,7 +2812,7 @@ li.default-menu { background-color: #0fbe7c; } -/* =CART ICON +/* =CART ICON ----------------------------------------------- */ .cart-wrapper { float: right; @@ -2958,9 +2958,9 @@ li.default-menu { color: #ffffff; } -/* =DROP DOWN +/* =DROP DOWN ----------------------------------------------- */ -/* =SMALL MENU +/* =SMALL MENU ----------------------------------------------- */ .menu-toggle { display: none; @@ -3028,7 +3028,7 @@ li.default-menu { left: -100%; } -/* =SECONDARY MENUS +/* =SECONDARY MENUS ----------------------------------------------- */ .small-menu { float: right; @@ -3069,7 +3069,7 @@ li.default-menu { color: #0FBE7C; } -/* =Header Display Menu Bottom +/* =Header Display Menu Bottom ----------------------------------------------- */ .spacious-header-display-four .bottom-menu .main-navigation { float: left; @@ -3151,7 +3151,7 @@ li.default-menu { outline: none; } -/* =FOOTER WIDGET AREA +/* =FOOTER WIDGET AREA ----------------------------------------------- */ .footer-widgets-wrapper { background-color: #333333; @@ -3207,7 +3207,7 @@ li.default-menu { line-height: 18px; } -/* =FOOTER SOCKET AREA +/* =FOOTER SOCKET AREA ----------------------------------------------- */ .footer-socket-wrapper { padding: 10px 0; @@ -3246,7 +3246,7 @@ li.default-menu { top: 21px; } -/* =RESPONSIVE STRUCTURE +/* =RESPONSIVE STRUCTURE ----------------------------------------------- */ @media screen and (min-width: 1500px) { .wide-978 #featured-slider .entry-container { @@ -3488,7 +3488,7 @@ li.default-menu { line-height: 1.5em; padding: 10px; } - /* =RESPONSIVE SUB TOGGLE MENU + /* =RESPONSIVE SUB TOGGLE MENU ----------------------------------------------- */ /* sub-toggle */ .sub-toggle { From dd84c154ba21ef83687d8e10318a981adc97b241 Mon Sep 17 00:00:00 2001 From: Prajjwal Poudel Date: Tue, 29 Sep 2026 14:56:55 +0545 Subject: [PATCH 19/19] Default theme options to an empty array in the major controls migration get_option( $themename ) returns false whenever the option doesn't exist (fresh installs), and the subsequent unset() calls on that false value log PHP 8.1+ "Automatic conversion of false to array" deprecation notices on every after_setup_theme bootstrap until the migration flag is set. Ported from spacious-pro release/v2.8.0 (commit 7601809f), which fixed the same pattern against its own hardcoded 'spacious' option lookup. Verified live: reproduced 2 deprecation notices at migration.php:63 with the old spacious option absent, confirmed 0 after, via a real front-end request with Playwright against the Spacious theme. --- inc/migration.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/inc/migration.php b/inc/migration.php index 7cecd306..076b8d15 100644 --- a/inc/migration.php +++ b/inc/migration.php @@ -32,7 +32,9 @@ function spacious_major_controls_migrate() { // Get theme options. $themename = get_option( 'stylesheet' ); $themename = preg_replace( "/\W/", "_", strtolower( $themename ) ); - $spacious_theme_options = get_option( $themename ); + // get_option() returns false when the option doesn't exist (fresh installs), and the + // array writes/unset() below on a false value log PHP 8.1+ deprecation notices. + $spacious_theme_options = (array) get_option( $themename, array() ); // Base heading. $spacious_content_font = isset( $spacious_theme_options['spacious_content_font'] ) ? $spacious_theme_options['spacious_content_font'] : 'Lato';