ultimate-jekyll-manager 1.8.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -14,6 +14,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
14
14
  - `Fixed` for any bug fixes.
15
15
  - `Security` in case of vulnerabilities.
16
16
 
17
+ ---
18
+ ## [1.9.0] - 2026-06-17
19
+
20
+ ### Added
21
+
22
+ - **Token page: MCP OAuth flow.** When the `/token` page receives `?mcp=true&redirect_uri=...&state=...`, it completes an OAuth-style handshake — the user signs in via Firebase Auth, the page obtains a fresh ID token, and redirects back to the `redirect_uri` with `code=<idToken>&state=<state>`. This lets Claude (or any MCP client) authenticate users through the existing sign-in UI without a custom OAuth server.
23
+
24
+ ### Changed
25
+
26
+ - **Dev-URL guidance updated** — docs (local-development.md, themes.md, cdp-debugging.md, CLAUDE.md) now say "prefer `https://localhost:4000`; fall back to the network IP if localhost doesn't connect" instead of hardcoding a specific IP.
27
+
28
+ ---
29
+ ## [1.8.2] - 2026-06-14
30
+
31
+ ### Added
32
+
33
+ - **FormManager: `data-fm-keep-disabled` opt-out for permanently-disabled fields.** `_setDisabled` blanket-toggles `disabled` on every control in the form (loading/submitting ↔ ready), which silently re-enabled fields meant to STAY disabled — e.g. "coming soon" radio options rendered inside a managed form. Controls carrying `data-fm-keep-disabled` are now always forced to `disabled = true` and never re-enabled. Documented in `docs/javascript-libraries.md` (FormManager section).
34
+ - **`docs/cdp-debugging.md` — launching a controllable browser (mirrored across UJM/BEM/BXM/EM).** The canonical Chrome launch for agents and humans: CDP port + REQUIRED dedicated `--user-data-dir` (Chrome 136+ silently ignores the debug port on the default profile — verified on 149), the persistent agent profile (`~/Library/Application Support/chrome-profiles/agent` — log in once, state survives relaunches, verified), the shared-instance model (CDP is multi-client — agents share the one logged-in Chrome on one port, one tab each; a second profile/port only for a second identity), safe quit by profile match, and driving via the `chrome-devtools` MCP (`CHROME_CDP_PORT` set before the session) or any CDP client. UJM flavor: point it at the dev server for the edit → live-reload → screenshot/console/network loop. Indexed in CLAUDE.md.
35
+
17
36
  ---
18
37
  ## [1.8.1] - 2026-06-11
19
38
 
package/CLAUDE.md CHANGED
@@ -174,7 +174,7 @@ Note: `-t` short alias belongs to `translation`. The `test` command uses `--test
174
174
  ## Development Workflow
175
175
 
176
176
  - **🚫 NEVER run `npm start` in a consumer project** — the user runs the dev server; running it again kills theirs. Assume it's already running; if it isn't, **instruct the user to run it** rather than running it yourself. Instead, **check `logs/dev.log`** after editing files to confirm the watcher recompiled successfully (`Reloading Browsers...` = success; `errored` = fix the error) — never tail/attach to the process. If editing multiple files, check the log once after the last edit. A change that breaks the build is not a completed change. Running `npx mgr test` is fine.
177
- - **Live-test UI changes via CDP.** After code changes compile, use the `chrome-devtools` MCP tools (screenshots, click, evaluate JS, console logs) to verify the change works in the running browser. This is the primary way to confirm UI changes — type-checking and test suites verify code correctness, not feature correctness. See `~/.claude/mcp-server/servers/chrome-devtools/CLAUDE.md`.
177
+ - **Live-test UI changes via CDP.** After code changes compile, use the `chrome-devtools` MCP tools (screenshots, click, evaluate JS, console logs) to verify the change works in the running browser. This is the primary way to confirm UI changes — type-checking and test suites verify code correctness, not feature correctness. Read the URL from the consumer's `.temp/_config_browsersync.yml`. Prefer `https://localhost:4000`; fall back to the local network IP (e.g. `https://192.168.x.x:4000`) if localhost doesn't connect. See [docs/cdp-debugging.md](docs/cdp-debugging.md) + `~/.claude/mcp-server/servers/chrome-devtools/CLAUDE.md`.
178
178
 
179
179
  ## File Conventions
180
180
 
@@ -218,6 +218,7 @@ Deep references live in `docs/`. Treat docs as a first-class deliverable. **When
218
218
  - [docs/build-system.md](docs/build-system.md) — gulp pipeline (15 tasks), config flow, build modes, pure helpers
219
219
  - [docs/templating.md](docs/templating.md) — node-powertools bracket conventions, Liquid coexistence
220
220
  - [docs/local-development.md](docs/local-development.md) — browsersync URL, Firebase emulator connect, PurgeCSS safelist
221
+ - [docs/cdp-debugging.md](docs/cdp-debugging.md) — launching a controllable Chrome (CDP) at the dev server, persistent agent profile (one-time logins), driving via MCP/CDP (screenshots, clicks, network)
221
222
  - [docs/logging.md](docs/logging.md) — `dev.log` / `build.log` / `test.log` tee, CI skip
222
223
  - [docs/common-mistakes.md](docs/common-mistakes.md) — the canonical "don't do this" list
223
224
  - [docs/assets.md](docs/assets.md) — UJM vs consumer file layout, section config (nav/footer/account), frontmatter-driven page customization, webpack aliases, page module pattern
@@ -782,7 +782,9 @@ export class FormManager {
782
782
  }
783
783
 
784
784
  /**
785
- * Enable/disable form controls
785
+ * Enable/disable form controls. Fields marked data-fm-keep-disabled stay
786
+ * disabled permanently (e.g. "coming soon" options rendered inside a
787
+ * managed form) — the toggle never re-enables them.
786
788
  */
787
789
  _setDisabled(disabled) {
788
790
  /* @dev-only:start */
@@ -792,6 +794,10 @@ export class FormManager {
792
794
  /* @dev-only:end */
793
795
 
794
796
  this.$form.querySelectorAll('button, input, select, textarea').forEach(($el) => {
797
+ if ($el.dataset.fmKeepDisabled !== undefined) {
798
+ $el.disabled = true;
799
+ return;
800
+ }
795
801
  $el.disabled = disabled;
796
802
  });
797
803
  }
@@ -1,4 +1,5 @@
1
1
  // This file is required by /token page to generate custom auth tokens for extensions/apps
2
+ // Also handles MCP OAuth flow: user signs in → Firebase ID token sent back to Claude as auth code
2
3
  import authorizedFetch from '__main_assets__/js/libs/authorized-fetch.js';
3
4
  import webManager from 'web-manager';
4
5
 
@@ -11,19 +12,27 @@ export default function () {
11
12
  // Get URL params
12
13
  const url = new URL(window.location.href);
13
14
  const authReturnUrl = url.searchParams.get('authReturnUrl');
15
+ const mcpRedirectUri = url.searchParams.get('redirect_uri');
16
+ const mcpState = url.searchParams.get('state');
17
+ const isMcp = url.searchParams.get('mcp') === 'true';
14
18
 
15
19
  // Handle DOM ready
16
20
  webManager.dom().ready()
17
21
  .then(async () => {
18
22
  // Log
19
- console.log('[Token] Initialized. authReturnUrl:', authReturnUrl);
23
+ console.log('[Token] Initialized.', isMcp ? 'MCP OAuth flow' : 'Standard flow', 'authReturnUrl:', authReturnUrl);
20
24
 
21
- // Validate authReturnUrl if present
25
+ // Validate redirect URLs
22
26
  if (authReturnUrl && !webManager.isValidRedirectUrl(authReturnUrl)) {
23
27
  showError('Invalid redirect URL');
24
28
  return;
25
29
  }
26
30
 
31
+ if (isMcp && !mcpRedirectUri) {
32
+ showError('Missing redirect_uri for MCP OAuth flow');
33
+ return;
34
+ }
35
+
27
36
  // Wait for auth to be ready and get user
28
37
  webManager.auth().listen({ once: true }, async (state) => {
29
38
  const user = state.user;
@@ -35,7 +44,32 @@ export default function () {
35
44
  }
36
45
 
37
46
  try {
38
- // Generate custom token
47
+ // MCP OAuth flow: return Firebase ID token as the authorization code
48
+ if (isMcp && mcpRedirectUri) {
49
+ updateStatus('Completing MCP authorization...');
50
+
51
+ const idToken = await webManager.auth().getIdToken(true);
52
+ const returnUrl = new URL(mcpRedirectUri);
53
+ returnUrl.searchParams.set('code', idToken);
54
+
55
+ if (mcpState) {
56
+ returnUrl.searchParams.set('state', mcpState);
57
+ }
58
+
59
+ const redirectUrl = returnUrl.toString();
60
+ console.log('[Token] MCP redirect to:', redirectUrl);
61
+
62
+ updateStatus('Redirecting to Claude...');
63
+
64
+ setTimeout(() => {
65
+ updateStatus('If you were not redirected, <a href="' + webManager.utilities().escapeHTML(redirectUrl) + '">click here to try again</a>.', true);
66
+ }, 3000);
67
+
68
+ window.location.href = redirectUrl;
69
+ return;
70
+ }
71
+
72
+ // Standard flow: generate custom token via BEM API
39
73
  updateStatus('Generating secure token...');
40
74
  const token = await generateCustomToken();
41
75
 
@@ -0,0 +1,48 @@
1
+ # CDP Debugging (driving a live browser)
2
+
3
+ How to launch a browser you can CONTROL — see the site live, screenshot it, click, type, read console logs, inspect network requests — for agents (Claude via MCP/CDP) and humans.
4
+
5
+ > Mirrored across the four sister frameworks (UJM / BEM / BXM / EM) — same core section, framework-flavored. Edit all four together.
6
+
7
+ ## Launching a controllable Chrome (the canonical command)
8
+
9
+ ```bash
10
+ open -gna "Google Chrome" --args \
11
+ --remote-debugging-port=9223 \
12
+ --user-data-dir="$HOME/Library/Application Support/chrome-profiles/agent" \
13
+ --no-first-run --no-default-browser-check \
14
+ --disable-background-timer-throttling \
15
+ --disable-backgrounding-occluded-windows \
16
+ --disable-renderer-backgrounding \
17
+ https://localhost:4000 # ← the UJM dev server (or the IP from .temp/_config_browsersync.yml if localhost doesn't connect)
18
+ ```
19
+
20
+ Verify it's up: `curl -s http://127.0.0.1:9223/json/version`
21
+
22
+ The rules that make this work (each one learned the hard way):
23
+
24
+ - **`open -gna` launches WITHOUT stealing focus.** `-g` = don't bring to foreground, `-n` = new instance (required — without it `open` just activates the already-running daily Chrome and the `--args` are ignored). Launching the Chrome binary directly ALWAYS activates the app and steals focus. Do NOT use `-j`/`--hide` — animations need a visible window; instead the three `--disable-*` flags keep timers/rAF/rendering at FULL speed while the window sits behind your work (verified: rAF at the display's native 120fps while backgrounded, focus never moved).
25
+ - **`--user-data-dir` is REQUIRED, not optional.** Chrome 136+ **silently ignores** `--remote-debugging-port` on the default profile — no error, no port, nothing (verified on Chrome 149). This is the #1 "why isn't CDP up" trap.
26
+ - **The profile dir IS the persistent login state.** Cookies + localStorage survive relaunches (verified by round-trip). **Log into sites once in the agent profile and every agent reuses the authenticated state.** Ecosystem convention: ONE shared profile at `~/Library/Application Support/chrome-profiles/agent` across all four frameworks, so logins are a one-time setup.
27
+ - **One Chrome instance per profile dir — but MANY agents per instance.** CDP is multi-client (verified: two concurrent clients driving different tabs of one instance): agents and sessions attach to the SAME port, each drives its own tab, and all share the profile's logins. One agent per tab is the only rule. A second launch with the same dir just opens a window in the existing instance and **ignores the new debug port** — attach to the running one instead. Reach for a second profile + port (`…/b` on 9224) only for a different IDENTITY (a different account = a different cookie jar) or hard isolation.
28
+ - It runs **side-by-side with the daily Chrome** — a different `--user-data-dir` is a fully separate instance.
29
+ - **Quit by profile match, never by app name**: `pkill -f "chrome-profiles/agent"`. (`osascript 'tell app "Google Chrome" to quit'` hits the daily browser too — same app name.)
30
+
31
+ ## Driving it
32
+
33
+ | Client | Good for | Port handoff |
34
+ |---|---|---|
35
+ | `chrome-devtools` MCP | rich interaction — click, fill, type, screenshots, network requests, console messages, performance traces | `CHROME_CDP_PORT` env var, **expanded ONCE when the Claude session spawns its MCP — set it BEFORE launching `claude`** (mid-session changes do nothing) |
36
+ | Any CDP client — including EM's `npx mgr cdp` run from any EM project | quick JS eval, per-renderer screenshots | per invocation: `EM_CDP_PORT=9223 npx mgr cdp eval ":4000" 'document.title'` |
37
+
38
+ Port conventions: **9222** = Electron apps (EM), **9223+** = Chrome instances.
39
+
40
+ ## UJM specifics
41
+
42
+ <<<<<<< HEAD
43
+ - **The dev server URL is NEVER `localhost`.** BrowserSync serves on the machine's local network IP over HTTPS (e.g. `https://192.168.86.69:4000`) — `localhost:4000` is refused. The port also varies (4001, …) when multiple sites run simultaneously. The exact served URL is written to `.temp/_config_browsersync.yml` at the root of the WEBSITE project being served (the UJM consumer — e.g. `<brand>-website/.temp/_config_browsersync.yml`) — read that file FIRST, every time, before navigating.
44
+ =======
45
+ - **The dev server is HTTPS.** BrowserSync serves over HTTPS (self-signed cert). Prefer `https://localhost:4000`; fall back to the machine's local network IP (e.g. `https://192.168.x.x:4000`) if localhost doesn't connect. Port 4000 by default, increments to 4001+ when multiple sites run. The exact URL is in `.temp/_config_browsersync.yml` at the root of the WEBSITE project (the UJM consumer — e.g. `<brand>-website/.temp/_config_browsersync.yml`) — read that file FIRST, every time, before navigating.
46
+ >>>>>>> df950b1 (v1.9.0: MCP OAuth flow on /token page, CDP debugging docs, dev-URL updates)
47
+ - Launch it pointed at the dev server — UJM's live reload keeps the tab current as you edit, so the agent sees every change without relaunching anything.
48
+ - Typical loop: edit → reload happens → MCP `take_screenshot` / `list_console_messages` / `list_network_requests` to verify the page, requests to the backend, and console cleanliness.
@@ -387,6 +387,14 @@ Errors display with Bootstrap's `is-invalid` class and `.invalid-feedback` eleme
387
387
 
388
388
  When the form transitions to `ready` state, FormManager automatically focuses the field with the `autofocus` attribute (if present and not disabled).
389
389
 
390
+ **Permanently-disabled fields (`data-fm-keep-disabled`):**
391
+
392
+ FormManager blanket-toggles `disabled` on every control in the form while loading/submitting and re-enables them on `ready`/error. Fields that must STAY disabled (e.g. "coming soon" options rendered inside a managed form) opt out with the `data-fm-keep-disabled` attribute — the toggle always forces them to `disabled = true` and never re-enables them:
393
+
394
+ ```html
395
+ <input type="radio" name="plan" value="enterprise" disabled data-fm-keep-disabled>
396
+ ```
397
+
390
398
  **Methods:**
391
399
 
392
400
  | Method | Description |
@@ -1,6 +1,6 @@
1
1
  # Local Development
2
2
 
3
- The local development server URL is stored in `.temp/_config_browsersync.yml` in the consuming project's root directory. Read this file to determine the correct URL for browsing and testing. By default, use `https://192.168.86.69:4000`.
3
+ The exact local dev server URL is written to `.temp/_config_browsersync.yml` in the consuming project's root directory always read it, never guess. BrowserSync serves over HTTPS (self-signed cert). Prefer `https://localhost:4000`; fall back to the machine's local network IP (e.g. `https://192.168.x.x:4000`) if localhost doesn't connect. Port 4000 by default, increments to 4001+ when multiple sites run simultaneously.
4
4
 
5
5
  ## Log Files
6
6
 
package/docs/themes.md CHANGED
@@ -711,8 +711,9 @@ UJM cannot run a dev server itself (it runs inside a consumer). To verify a them
711
711
 
712
712
  1. In UJM: `npm run prepare` (or `npm start` for watch) to publish `src/`→`dist/`.
713
713
  2. In a consumer wired to local UJM (`"ultimate-jekyll-manager": "file:../ultimate-jekyll-manager"`),
714
- set `theme.id` and run `npm start`. Capture the BrowserSync URL (e.g.
715
- `https://localhost:4000`).
714
+ set `theme.id` and run `npm start`. Read the BrowserSync URL from the
715
+ consumer's `.temp/_config_browsersync.yml`. Prefer `https://localhost:4000`;
716
+ fall back to the local network IP (e.g. `https://192.168.x.x:4000`) if localhost doesn't connect.
716
717
  3. Screenshot the key pages (home, pricing, signin, signup) in **both** light and
717
718
  dark (`document.documentElement.setAttribute('data-bs-theme','dark')`) — e.g.
718
719
  via the chrome-devtools MCP. Check the console for errors and that your theme's
package/logs/test.log CHANGED
@@ -1,39 +1,39 @@
1
- # ujm log — 2026-06-11T10:04:30.890Z — pid=33077
2
- [03:04:30] 'test': Running tests (layer=all)
3
- [03:04:30] 'test': Test mode: normal (external APIs skipped)
1
+ # ujm log — 2026-06-17T22:01:06.463Z — pid=85523
2
+ [15:01:06] 'test': Running tests (layer=all)
3
+ [15:01:06] 'test': Test mode: normal (external APIs skipped)
4
4
 
5
5
  Ultimate Jekyll Manager Tests
6
6
 
7
7
  Framework Tests
8
8
  ⤷ attach-log-file — tee stdout/stderr to a file
9
9
  ✓ exports the expected surface (0ms)
10
- ✓ stripAnsi removes color escape codes (0ms)
10
+ ✓ stripAnsi removes color escape codes (1ms)
11
11
  hello world
12
12
  colored line
13
13
  ✓ attach + stdout.write + detach: file contains the writes (0ms)
14
14
  ✓ idempotent: attaching twice with same name returns same fd (0ms)
15
- ✓ attach with falsy name returns null and does nothing (1ms)
15
+ ✓ attach with falsy name returns null and does nothing (0ms)
16
16
  ⤷ CLI alias resolution
17
- ✓ cli.js exports a Main class (0ms)
17
+ ✓ cli.js exports a Main class (1ms)
18
18
  ✓ all expected commands exist on disk (0ms)
19
- ✓ each command module exports an async function (7ms)
19
+ ✓ each command module exports an async function (8ms)
20
20
  ⤷ collectTextNodes (utils/collectTextNodes.js)
21
- ✓ extracts page title (177ms)
21
+ ✓ extracts page title (134ms)
22
22
  ✓ skips <script> and <style> (1ms)
23
23
  ✓ spellcheck dictionary (utils/dictionary.js) (0ms)
24
24
  ⤷ expect() matcher set
25
25
  ✓ toBe + toEqual basics (0ms)
26
26
  ✓ .not negates (0ms)
27
27
  ✓ toContain works on arrays and strings (0ms)
28
- ✓ toThrow catches sync + async throws (0ms)
29
- ✓ toBeGreaterThan / toBeLessThan (1ms)
28
+ ✓ toThrow catches sync + async throws (1ms)
29
+ ✓ toBeGreaterThan / toBeLessThan (0ms)
30
30
  ✓ failing assertions throw AssertionError (0ms)
31
31
  ✓ package.json exports resolve to real files in dist/ (0ms)
32
32
  ⤷ Logger (src/lib/logger.js)
33
33
  ✓ Logger constructor stores name (0ms)
34
34
  ✓ Logger exposes log/error/warn/info methods (0ms)
35
35
  ✓ Logger.format is chalk (0ms)
36
- ✓ Logger output goes through console with prefix (1ms)
36
+ ✓ Logger output goes through console with prefix (0ms)
37
37
  ⤷ Manager (build.js) public surface
38
38
  ✓ Manager constructor is a function (0ms)
39
39
  ✓ static methods match prototype methods (0ms)
@@ -44,23 +44,23 @@ colored line
44
44
  ✓ actLikeProduction is true when isBuildMode OR UJ_AUDIT_FORCE (0ms)
45
45
  ✓ getRootPath("package") points at UJM root (0ms)
46
46
  ✓ getMemoryUsage returns shape with MB-sized numbers (0ms)
47
- ✓ getArguments returns object with _ array + boolean defaults (0ms)
47
+ ✓ getArguments returns object with _ array + boolean defaults (1ms)
48
48
  ✓ logger returns object with log/error/warn/info methods (0ms)
49
49
  ✓ processBatches processes items in chunks and returns flat results (0ms)
50
50
  ⤷ mergeJekyllConfigs (utils/merge-jekyll-configs.js)
51
- ✓ merges collections from both configs (project additions win) (5ms)
52
- ✓ dedups defaults by scope key (project wins) (2ms)
53
- ✓ returns null when there is nothing to merge (1ms)
51
+ ✓ merges collections from both configs (project additions win) (3ms)
52
+ ✓ dedups defaults by scope key (project wins) (1ms)
53
+ ✓ returns null when there is nothing to merge (0ms)
54
54
  ⤷ mode-helpers (isTesting / isDevelopment / isProduction / getVersion)
55
55
  ✓ helpers attach to Manager statically AND on prototype (0ms)
56
- ✓ isTesting reflects UJ_TEST_MODE env (0ms)
56
+ ✓ isTesting reflects UJ_TEST_MODE env (1ms)
57
57
  ✓ isDevelopment false / isProduction true when UJ_BUILD_MODE=true (and not testing) (0ms)
58
58
  ✓ environments are mutually exclusive — testing wins under UJ_TEST_MODE (0ms)
59
- ✓ invariant: is*() exactly matches getEnvironment() + mutually exclusive (every scenario) (1ms)
59
+ ✓ invariant: is*() exactly matches getEnvironment() + mutually exclusive (every scenario) (0ms)
60
60
  ✓ getVersion returns a non-empty string when run from a package (0ms)
61
61
  ⤷ createTemplateTransform (utils/template-transform.js)
62
62
  ✓ replaces [site.theme.id] with config value in .html files (1ms)
63
- ✓ leaves non-matching extensions untouched (e.g. .css) (1ms)
63
+ ✓ leaves non-matching extensions untouched (e.g. .css) (0ms)
64
64
  ✓ passes directories through untouched (0ms)
65
65
  ⤷ node-powertools templating brackets ({} and [])
66
66
  ✓ default { } brackets resolve nested keys (0ms)
@@ -69,43 +69,43 @@ colored line
69
69
  ⤷ theme contract (structure, swappability, cross-theme JS contracts)
70
70
  ✓ _template: entry files + config contract (0ms)
71
71
  ✓ classy: entry files + config contract (0ms)
72
- ✓ neobrutalism: entry files + config contract (0ms)
72
+ ✓ neobrutalism: entry files + config contract (1ms)
73
73
  ✓ newsflash: entry files + config contract (0ms)
74
- ✓ _template: layouts swappable, markup clean (2ms)
74
+ ✓ _template: layouts swappable, markup clean (1ms)
75
75
  ✓ classy: layouts swappable, markup clean (14ms)
76
76
  ✓ neobrutalism: layouts swappable, markup clean (1ms)
77
77
  ✓ newsflash: layouts swappable, markup clean (3ms)
78
78
  ✓ _template: cross-theme JS contracts (0ms)
79
- ✓ classy: cross-theme JS contracts (2ms)
80
- ✓ neobrutalism: cross-theme JS contracts (0ms)
81
- ✓ newsflash: cross-theme JS contracts (1ms)
82
- ✓ page asset files match a declared asset_path shape (8ms)
79
+ ✓ classy: cross-theme JS contracts (0ms)
80
+ ✓ neobrutalism: cross-theme JS contracts (1ms)
81
+ ✓ newsflash: cross-theme JS contracts (0ms)
82
+ ✓ page asset files match a declared asset_path shape (4ms)
83
83
  ⤷ validateYAMLFrontMatter (utils/_validate-yaml.js)
84
84
  ✓ returns { valid: true } for a file with valid frontmatter (1ms)
85
- ✓ returns { valid: true } when no frontmatter present (1ms)
86
- ✓ flags malformed YAML frontmatter as invalid with error message (1ms)
85
+ ✓ returns { valid: true } when no frontmatter present (0ms)
86
+ ✓ flags malformed YAML frontmatter as invalid with error message (0ms)
87
87
  ⤷ page-layer baseline (DOM + fetch + storage)
88
- ✓ document is interactive or complete (3ms)
89
- ✓ fetch() works against the local harness server (9ms)
90
- ✓ localStorage is available (1ms)
88
+ ✓ document is interactive or complete (0ms)
89
+ ✓ fetch() works against the local harness server (2ms)
90
+ ✓ localStorage is available (0ms)
91
91
  ⤷ harness globals (window.Configuration + dataset)
92
92
  ✓ window.Configuration has brand + theme + web_manager (0ms)
93
93
  ✓ document.documentElement.dataset.pagePath is set (0ms)
94
94
  ✓ UJ_TEST_MODE is signalled on globalThis (0ms)
95
95
  ⤷ prerendered icons template lookup
96
- ✓ template#prerendered-icons exists and has the test icon (1ms)
96
+ ✓ template#prerendered-icons exists and has the test icon (0ms)
97
97
  ✓ looking up a missing icon returns null (0ms)
98
98
  ⤷ boot tests (consumer _site/)
99
- ✓ /service-worker.js served with javascript content type (113ms)
100
- ✓ index.html registers SW and reaches activated state (226ms)
101
- ✓ SW responds to get-cache-name message with brand-id pattern (104ms)
102
- ✓ home page renders with title + body content (218ms)
103
- ✓ /about resolves via Jekyll-style .html fallback (103ms)
104
- ✓ build.json is served with brand metadata (98ms)
105
- ✓ CSS bundle served with text/css content type (88ms)
99
+ ✓ /service-worker.js served with javascript content type (94ms)
100
+ ✓ index.html registers SW and reaches activated state (146ms)
101
+ ✓ SW responds to get-cache-name message with brand-id pattern (76ms)
102
+ ✓ home page renders with title + body content (171ms)
103
+ ✓ /about resolves via Jekyll-style .html fallback (75ms)
104
+ ✓ build.json is served with brand metadata (67ms)
105
+ ✓ CSS bundle served with text/css content type (70ms)
106
106
 
107
107
  Results
108
108
  80 passing
109
109
 
110
- Total: 80 tests in 4505ms
110
+ Total: 80 tests in 6044ms
111
111
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultimate-jekyll-manager",
3
- "version": "1.8.1",
3
+ "version": "1.9.0",
4
4
  "description": "Ultimate Jekyll dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {