ultimate-jekyll-manager 1.8.1 → 1.9.1

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,37 @@ 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.1] - 2026-06-17
19
+
20
+ ### Changed
21
+
22
+ - **FormManager: snapshot-and-restore disabled-state model (replaces `data-fm-keep-disabled`).** `_init()` now snapshots every element that has `disabled` in HTML markup (excluding submit buttons — those are loading guards FM takes over). Snapshotted elements stay disabled through every state transition automatically — no data attributes needed. Recommended form pattern: `<form data-form-state="initializing" onsubmit="return false">` with CSS `form[data-form-state]:not([data-form-state="ready"]) { pointer-events: none; }`. Documented in `docs/javascript-libraries.md`.
23
+
24
+ ### Added
25
+
26
+ - **Comprehensive FormManager test suite (30 page-layer tests).** Disabled-state snapshot (5 tests), getData/setData/input groups (12 tests), validation/honeypot/file-accept (13 tests). Previously FormManager had zero automated tests.
27
+ - **Visual Test 7 on the FM test page** (`/test/libraries/form-manager`) — permanently disabled elements, submit cycle demo, rapid-cycle button for manual verification.
28
+
29
+ ---
30
+ ## [1.9.0] - 2026-06-17
31
+
32
+ ### Added
33
+
34
+ - **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.
35
+
36
+ ### Changed
37
+
38
+ - **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.
39
+
40
+ ---
41
+ ## [1.8.2] - 2026-06-14
42
+
43
+ ### Added
44
+
45
+ - **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).
46
+ - **`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.
47
+
17
48
  ---
18
49
  ## [1.8.1] - 2026-06-11
19
50
 
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
package/PROGRESS.md ADDED
@@ -0,0 +1,24 @@
1
+ # Project Progress Tracker
2
+ > Agents and maintainers should update this file regularly to reflect the current state of the project.
3
+
4
+ ## Current Focus
5
+ * **Goal:** FormManager disabled-state refactor (snapshot-and-restore)
6
+ * **Current Phase:** Phase 1 — implementation + tests complete, docs pending
7
+ * **Priority:** Medium
8
+ * **Last Updated:** 2026-06-17 6:05 PM PDT
9
+ * **Notes:** FM disabled-state refactor done + comprehensive FM test suite added (110 tests total, up from 80). Covers getData/setData/input groups/validation/honeypot/file-accept/disabled snapshot. Docs (javascript-libraries.md, CHANGELOG) still need updating before shipping.
10
+
11
+ ## Active Task List
12
+ * [ ] Phase 1: FormManager disabled-state snapshot-and-restore
13
+ * [x] Task 1.1: Refactor `_setDisabled` to use snapshot instead of `data-fm-keep-disabled`
14
+ * [x] Task 1.2: Add `_permanentlyDisabled` Set, populated in `_init()` before first disable
15
+ * [x] Task 1.3: Write page-layer tests (5 tests: snapshot capture, full disable, selective re-enable, cycle durability, onsubmit HTML guard)
16
+ * [x] Task 1.4: All 85 tests passing
17
+ * [x] Task 1.5: Add visual Test 7 to FM test page (form-manager.html + JS) with permanently disabled fields + rapid-cycle demo
18
+ * [x] Task 1.6: Write comprehensive FM page-layer tests — getData/setData (12 tests), validation/honeypot/file-accept (13 tests). 110 total.
19
+ * [ ] Task 1.7: Update `docs/javascript-libraries.md` — replace `data-fm-keep-disabled` docs with new snapshot pattern + `onsubmit="return false"` + `data-form-state="initializing"` CSS guard
20
+ * [ ] Task 1.8: Update CHANGELOG with the change
21
+ * [ ] Task 1.9: Ship (commit, push, publish)
22
+
23
+ ## Completed Task List
24
+ * [x] Phase 0: v1.9.0 release — MCP OAuth flow + CDP debugging docs + dev-URL updates
@@ -59,6 +59,7 @@ export class FormManager {
59
59
  // State
60
60
  this.state = 'initializing';
61
61
  this._isDirty = false;
62
+ this._permanentlyDisabled = new Set();
62
63
 
63
64
  // Event listeners
64
65
  this._listeners = {
@@ -92,6 +93,17 @@ export class FormManager {
92
93
  * Initialize the form manager
93
94
  */
94
95
  _init() {
96
+ // Snapshot elements that are disabled in HTML markup BEFORE the first
97
+ // blanket disable. These are business-logic disabled (e.g. "coming soon"
98
+ // options) and must stay disabled through every state transition.
99
+ // Submit buttons are excluded — disabled submit buttons in HTML are
100
+ // loading guards that FM takes over.
101
+ this.$form.querySelectorAll('button, input, select, textarea').forEach(($el) => {
102
+ if ($el.disabled && $el.type !== 'submit') {
103
+ this._permanentlyDisabled.add($el);
104
+ }
105
+ });
106
+
95
107
  // Disable form during initialization
96
108
  this._setDisabled(true);
97
109
 
@@ -782,7 +794,8 @@ export class FormManager {
782
794
  }
783
795
 
784
796
  /**
785
- * Enable/disable form controls
797
+ * Enable/disable form controls. Elements snapshotted as permanently
798
+ * disabled during _init() are never re-enabled.
786
799
  */
787
800
  _setDisabled(disabled) {
788
801
  /* @dev-only:start */
@@ -792,6 +805,10 @@ export class FormManager {
792
805
  /* @dev-only:end */
793
806
 
794
807
  this.$form.querySelectorAll('button, input, select, textarea').forEach(($el) => {
808
+ if (this._permanentlyDisabled.has($el)) {
809
+ $el.disabled = true;
810
+ return;
811
+ }
795
812
  $el.disabled = disabled;
796
813
  });
797
814
  }
@@ -19,6 +19,7 @@ export default () => {
19
19
  initTestFormManual();
20
20
  initTestFormGroups();
21
21
  initTestFormFileDrop();
22
+ initTestFormSnapshot();
22
23
 
23
24
  // Resolve after initialization
24
25
  return resolve();
@@ -245,6 +246,58 @@ function initTestFormGroups() {
245
246
  });
246
247
  }
247
248
 
249
+ // Test 7: Disabled-State Snapshot
250
+ function initTestFormSnapshot() {
251
+ const formManager = new FormManager('#test-form-snapshot');
252
+ const $status = document.getElementById('snapshot-status');
253
+ const $cycleCount = document.getElementById('snapshot-cycle-count');
254
+ const $output = document.getElementById('snapshot-output');
255
+ const $cycleBtn = document.getElementById('snapshot-cycle');
256
+
257
+ let cycles = 0;
258
+
259
+ function logStates() {
260
+ const form = document.getElementById('test-form-snapshot');
261
+ const lines = [];
262
+ form.querySelectorAll('input, select, textarea, button').forEach(($el) => {
263
+ const label = $el.name || $el.type || $el.tagName.toLowerCase();
264
+ lines.push(`${label}: disabled=${$el.disabled}`);
265
+ });
266
+ $output.textContent = lines.join('\n');
267
+ }
268
+
269
+ formManager.on('statechange', ({ state }) => {
270
+ $status.textContent = `Status: ${state}`;
271
+ logStates();
272
+ });
273
+
274
+ formManager.on('submit', async ({ data }) => {
275
+ console.log('[Test 7] Submitting:', data);
276
+ logStates();
277
+ await simulateApi(2000);
278
+ cycles++;
279
+ $cycleCount.textContent = `Cycles: ${cycles}`;
280
+ formManager.showSuccess('Done! Permanently disabled fields should still be disabled.');
281
+ });
282
+
283
+ // Rapid cycle button — triggers 5 fast disable/enable cycles
284
+ $cycleBtn.addEventListener('click', async () => {
285
+ $cycleBtn.disabled = true;
286
+ for (let i = 0; i < 5; i++) {
287
+ formManager._setDisabled(true);
288
+ logStates();
289
+ await simulateApi(300);
290
+ formManager._setDisabled(false);
291
+ logStates();
292
+ await simulateApi(300);
293
+ cycles++;
294
+ }
295
+ $cycleCount.textContent = `Cycles: ${cycles}`;
296
+ $cycleBtn.disabled = false;
297
+ formManager.showSuccess('5 rapid cycles complete. Check that Enterprise/Region/Notes stayed disabled.');
298
+ });
299
+ }
300
+
248
301
  // Test 6: File Drop
249
302
  function initTestFormFileDrop() {
250
303
  const formManager = new FormManager('#test-form-file-drop');
@@ -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
 
@@ -301,6 +301,78 @@ meta:
301
301
  </div>
302
302
  </div>
303
303
 
304
+ <!-- Test 7: Disabled-State Snapshot -->
305
+ <div class="col-lg-8">
306
+ <div class="card">
307
+ <div class="card-header">
308
+ <h5 class="mb-0">Test 7: Disabled-State Snapshot</h5>
309
+ <small class="text-muted">Elements disabled in HTML stay disabled through every FM state transition. Submit buttons are loading guards — FM takes them over.</small>
310
+ </div>
311
+ <div class="card-body">
312
+ <form id="test-form-snapshot" data-form-state="initializing" onsubmit="return false">
313
+ <div class="row">
314
+ <div class="col-md-6">
315
+ <h6 class="text-muted mb-3">FM-managed <span class="badge bg-success">enabled on ready</span></h6>
316
+ <div class="mb-3">
317
+ <label for="snapshot-name" class="form-label">Name</label>
318
+ <input type="text" class="form-control" id="snapshot-name" name="name" value="Ian">
319
+ </div>
320
+ <div class="mb-3">
321
+ <label for="snapshot-email" class="form-label">Email</label>
322
+ <input type="email" class="form-control" id="snapshot-email" name="email" value="ian@example.com">
323
+ </div>
324
+ <div class="mb-3">
325
+ <label class="form-label d-block">Plan</label>
326
+ <div class="form-check">
327
+ <input type="radio" class="form-check-input" id="snapshot-plan-free" name="plan" value="free" checked>
328
+ <label class="form-check-label" for="snapshot-plan-free">Free</label>
329
+ </div>
330
+ <div class="form-check">
331
+ <input type="radio" class="form-check-input" id="snapshot-plan-pro" name="plan" value="pro">
332
+ <label class="form-check-label" for="snapshot-plan-pro">Pro</label>
333
+ </div>
334
+ <div class="form-check">
335
+ <input type="radio" class="form-check-input" id="snapshot-plan-enterprise" name="plan" value="enterprise" disabled>
336
+ <label class="form-check-label" for="snapshot-plan-enterprise">Enterprise <span class="badge bg-secondary">Coming soon</span></label>
337
+ </div>
338
+ </div>
339
+ </div>
340
+ <div class="col-md-6">
341
+ <h6 class="text-muted mb-3">Permanently disabled <span class="badge bg-danger">stays disabled</span></h6>
342
+ <div class="mb-3">
343
+ <label for="snapshot-region" class="form-label">Region <span class="badge bg-secondary">Coming soon</span></label>
344
+ <select class="form-select" id="snapshot-region" name="region" disabled>
345
+ <option>US (coming soon)</option>
346
+ <option>EU (coming soon)</option>
347
+ </select>
348
+ </div>
349
+ <div class="mb-3">
350
+ <label for="snapshot-notes" class="form-label">Internal notes <span class="badge bg-secondary">Admin only</span></label>
351
+ <textarea class="form-control" id="snapshot-notes" name="notes" rows="2" disabled>Restricted field</textarea>
352
+ </div>
353
+ <div class="alert alert-info small mb-0">
354
+ Enterprise radio, Region select, and Notes textarea have <code>disabled</code> in the HTML. FM snapshots them at init and never re-enables them. The other fields start without <code>disabled</code> — the form-level <code>data-form-state</code> + CSS blocks interaction until FM is ready.
355
+ </div>
356
+ </div>
357
+ </div>
358
+ <div class="d-flex gap-2">
359
+ <button type="submit" class="btn btn-primary" disabled>
360
+ <span class="button-text">Submit (watch disabled states)</span>
361
+ </button>
362
+ <button type="button" id="snapshot-cycle" class="btn btn-outline-warning">Rapid cycle (5x)</button>
363
+ </div>
364
+ </form>
365
+ <div class="mt-3 d-flex gap-3">
366
+ <small class="text-muted" id="snapshot-status">Status: initializing</small>
367
+ <small class="text-muted" id="snapshot-cycle-count">Cycles: 0</small>
368
+ </div>
369
+ <div class="mt-2">
370
+ <pre id="snapshot-output" class="bg-body-secondary p-2 rounded small" style="max-height: 160px; overflow: auto;">Submit to see disabled states toggle. Enterprise radio + Region select should NEVER re-enable.</pre>
371
+ </div>
372
+ </div>
373
+ </div>
374
+ </div>
375
+
304
376
  </div>
305
377
  </div>
306
378
  </section>