ultimate-jekyll-manager 1.9.4 → 1.9.6

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.
Files changed (62) hide show
  1. package/CLAUDE.md +6 -0
  2. package/dist/assets/css/core/_utilities.scss +5 -2
  3. package/dist/assets/js/libs/form-manager.js +7 -8
  4. package/dist/commands/install.js +2 -2
  5. package/dist/commands/setup.js +2 -1
  6. package/dist/defaults/.github/workflows/build.yml +3 -1
  7. package/dist/defaults/dist/_includes/core/body.html +1 -7
  8. package/dist/defaults/dist/_includes/themes/classy/frontend/sections/footer.html +9 -1
  9. package/dist/defaults/dist/_layouts/blueprint/admin/calendar/index.html +1 -1
  10. package/dist/defaults/dist/_layouts/blueprint/admin/firebase/index.html +2 -2
  11. package/dist/defaults/dist/_layouts/blueprint/admin/studio/index.html +1 -1
  12. package/dist/defaults/dist/_layouts/blueprint/admin/users/index.html +2 -2
  13. package/dist/defaults/dist/_layouts/blueprint/admin/users/new.html +1 -1
  14. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/account/index.html +13 -13
  15. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/reset.html +2 -2
  16. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/signin.html +3 -3
  17. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/signup.html +3 -3
  18. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/blog/index.html +1 -1
  19. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/contact.html +1 -1
  20. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/download.html +1 -1
  21. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/feedback.html +1 -1
  22. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/index.html +2 -6
  23. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/payment/checkout.html +1 -1
  24. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/portal/email-preferences.html +2 -3
  25. package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/status.html +1 -1
  26. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/blog/index.html +1 -1
  27. package/dist/defaults/dist/_layouts/themes/newsflash/frontend/pages/contact.html +1 -1
  28. package/dist/defaults/dist/pages/test/libraries/form-manager.html +31 -31
  29. package/dist/lib/safe-install.js +13 -0
  30. package/package.json +8 -1
  31. package/.nvmrc +0 -1
  32. package/CHANGELOG.md +0 -792
  33. package/CLAUDE-ATTRIBUTION.md +0 -215
  34. package/PROGRESS.md +0 -27
  35. package/TODO-ATTRIBUTION2.md +0 -753
  36. package/TODO-AUTH-TESTING.md +0 -165
  37. package/TODO-BACKEND.md +0 -13
  38. package/TODO-CHAT1.md +0 -41
  39. package/TODO-CONSENT-LIVETEST.md +0 -201
  40. package/TODO-NEW.md +0 -21
  41. package/TODO-themes.md +0 -34
  42. package/TODO-tracking.md +0 -69
  43. package/TODO-tracking2.md +0 -103
  44. package/TODO.md +0 -415
  45. package/_notes/CLAUDE-pages.md +0 -19
  46. package/_notes/CLAUDE-pages2.md +0 -18
  47. package/_notes/LOGOS.md +0 -1
  48. package/_notes/NOTES-app.md +0 -92
  49. package/_notes/NOTES-sub-2.md +0 -42
  50. package/_notes/NOTES-sub-ai.md +0 -165
  51. package/_notes/NOTES-sub-endpoints.md +0 -44
  52. package/_notes/NOTES-sub-example.md +0 -92
  53. package/_notes/NOTES-sub-plan.md +0 -36
  54. package/_notes/NOTES-sub-status.md +0 -123
  55. package/_notes/TODO-Claude.rtf +0 -105
  56. package/_notes/TODO-FileStructure.md +0 -249
  57. package/_notes/TODO-checkout.md +0 -33
  58. package/_notes/TODO-frontend.md +0 -61
  59. package/_notes/TODO-legacy.md +0 -18
  60. package/_notes/TODO-template-system.md +0 -73
  61. package/_notes/TODO-theme.md +0 -17
  62. package/logs/test.log +0 -144
package/CHANGELOG.md DELETED
@@ -1,792 +0,0 @@
1
- # CHANGELOG
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
-
7
- ## Changelog Categories
8
-
9
- - `BREAKING` for breaking changes.
10
- - `Added` for new features.
11
- - `Changed` for changes in existing functionality.
12
- - `Deprecated` for soon-to-be removed features.
13
- - `Removed` for now removed features.
14
- - `Fixed` for any bug fixes.
15
- - `Security` in case of vulnerabilities.
16
-
17
- ---
18
- ## [1.9.4] - 2026-06-20
19
-
20
- ### Fixed
21
- - **Blog post ads no longer render inside blockquotes.** Ad insertion now filters out `<p>` elements inside `<blockquote>`, `<details>`, and `<figure>` so ads only appear between top-level content blocks.
22
-
23
- ---
24
- ## [1.9.3] - 2026-06-18
25
-
26
- ### Changed
27
- - **Notifly build-error notifications use absolute path.** Switched from bare `notifly` keyword to full application path (`/Applications/Notifly.app/Contents/MacOS/Notifly`) so the notification binary resolves reliably regardless of PATH.
28
-
29
- ---
30
- ## [1.9.2] - 2026-06-17
31
-
32
- ### Added
33
- - **Account page: API & MCP section.** Renamed "API keys" to "API & MCP". New MCP integration card with server URL (copy-to-clipboard) and tabbed setup instructions for 9 providers: Claude, Cursor, VS Code, Codex, Gemini CLI, OpenCode, Windsurf, Zed, and Other. MCP URL built dynamically from `webManager.getApiUrl()`.
34
-
35
- ---
36
- ## [1.9.1] - 2026-06-17
37
-
38
- ### Changed
39
-
40
- - **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`.
41
-
42
- ### Added
43
-
44
- - **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.
45
- - **Visual Test 7 on the FM test page** (`/test/libraries/form-manager`) — permanently disabled elements, submit cycle demo, rapid-cycle button for manual verification.
46
-
47
- ---
48
- ## [1.9.0] - 2026-06-17
49
-
50
- ### Added
51
-
52
- - **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.
53
-
54
- ### Changed
55
-
56
- - **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.
57
-
58
- ---
59
- ## [1.8.2] - 2026-06-14
60
-
61
- ### Added
62
-
63
- - **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).
64
- - **`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.
65
-
66
- ---
67
- ## [1.8.1] - 2026-06-11
68
-
69
- ### Changed
70
-
71
- - **`docs/audit.md` rewritten as the full-audit check catalog (`/omega:ujm audit`).** The two-stage site audit became an ID'd catalog: mirrored universal checks (U-01..U-14 — tests at every layer, XSS, secrets, config canon, doc parity, dead code, dep health, …), UJM-specific checks (UJM-01..UJM-10 — inline-script ban, theme-prefixed classes, content writing rules, SEO meta, purge safelist, reads-vs-writes, page-module pattern, images, a11y), and framework-repo checks (F-01..F-04 — sister parity, defaults sync, docs completeness, green framework suite), with scope auto-detect (consumer vs framework via package.json), a persisted findings report (`.temp/audit/claude-audit.md`), a severity-ordered TodoWrite fix loop, and the `npx mgr audit` automated stage absorbed from the old doc. Wired to the `omega:ujm` router's Audit process; `docs/audit.md` is mirrored across BEM/BXM/EM. CLAUDE.md's docs index updated to match.
72
- - **package.json `keywords` corrected** — replaced the stale template list (`Autoprefixer`, `imagemin` — not used; `Browsersync` — true but noise) with accurate, discovery-oriented ones (`jekyll`, `static-site`, `static-site-generator`, `website`, `seo`, `gulp`, `sass`, `webpack`, `postcss`). npm-listing metadata only; no behavior change. Mirrored across BEM/BXM/EM.
73
-
74
- ---
75
- ## [1.8.0] - 2026-06-11
76
-
77
- ### Added
78
-
79
- - **`docs/migration.md` + expanded `docs/audit.md` + dashboard-pages pattern (action-skill consolidation).** The standalone `UJM:migrate`/`UJM:audit`/`UJM:new-page`/`UJM:new-site` skills were deleted and folded into `omega:ujm` as process checklists; their framework facts landed in the repo: new `docs/migration.md` (full old-UJ→UJM migration incl. `_legacy/` flow + config re-mapping, `_config.yml` quick-fix key schema, revert-posts procedure), `docs/audit.md` expanded to the full two-stage workflow (AI content pass → `npx mgr audit` fix loop), and `docs/layouts-and-pages.md` gained the dashboard list/detail/edit page pattern (separate pages, `?id=` redirects, breadcrumbs).
80
- - **Dev-process guidance relaxed: only `npm start` is off-limits.** The "NEVER run" rule in CLAUDE.md + `src/defaults/CLAUDE.md` now prohibits only the long-running dev server (instruct the user to start it if it isn't running; read `logs/*.log`, never tail the process) — `npx mgr test` and `npm run build` are fine to run.
81
- - **Skills-as-routers migration — `docs/no-inline-scripts.md` + `docs/purgecss.md` (new), plus `seo.md` / `layouts-and-pages.md` / `javascript-libraries.md` / `assets.md` extensions.** Framework facts migrated from the `omega:ujm` skill into the repo so they version-match the installed package: `no-inline-scripts.md` carries the full hard-rule playbook (where scripts move, Liquid `data-*`/`<template>`/conditional bridges, window-global callbacks, step-by-step migration, verification grep); `purgecss.md` carries the safelist playbook (two locations, RegExp anchoring, `variables: false` gotcha, per-file processing, current UJM safelist, category guide); `seo.md` retitled "SEO & Content" and gains the content writing rules (action-verb H1s, sentence case, headline/accent structure, frontmatter SEO, superheadline rules) + Services and Solutions page strategies + alternatives content guidelines; `layouts-and-pages.md` gains default-page customization rules (.md vs .html, page exclusions), the per-page customization-levels table (homepage/pricing/about/contact/download incl. hero display modes, pricing feature comments, testimonial/FAQ guidelines), and the custom-page creation checklist; `javascript-libraries.md` gains the reads-vs-writes rule (Firestore SDK for dashboard reads, Cloud Functions for writes) + Firestore read examples; `assets.md` gains the account-dropdown item field reference. The `data-wm-bind` deep reference moved to `web-manager/docs/bindings.md` (the module that owns the feature). All indexed in CLAUDE.md; the skill is now a thin router (pointers + hard rules + process checklists).
82
-
83
- ### Changed
84
-
85
- - **Router skill renamed `UJM:patterns` → `omega:ujm`** — all framework skills now live under the `omega:` namespace (`omega:em`/`omega:bxm`/`omega:ujm`/`omega:bem` + the `omega:main` hub). CLAUDE.md's Recommended skills section updated.
86
- - **newsflash homepage post slots are strictly deduplicated.** Each section consumes its own slice of `site.posts` so no story repeats down the page: hero = post 1, top stories = 2–4, "the latest" feed = 5–9, most-read rail = 10–14, "more to chew on" = 15–17 (only the ticker repeats the latest — it's chrome, not content). Sections disappear gracefully when the site doesn't have enough posts to fill their slice.
87
- - **Dev builds raise `--limit_posts` from 15 to 18** so a fully-populated newsflash front page (17 distinct posts) is visible during development (`--all-posts` still disables the cap entirely).
88
- - **Footer appearance picker is now icon-only.** The toggle button in classy's and newsflash's footers shows just the mode icon (`data-appearance-icon` spans) — the `data-appearance-current` text label was removed from the button (the mode words remain in the dropdown items). Pure HTML change; the framework `data-appearance-*` logic is untouched. Docs updated in [docs/themes.md](docs/themes.md#the-appearance-picker-is-required-in-every-footer) + [docs/appearance.md](docs/appearance.md).
89
- - **newsflash homepage desk cards link to their category pages.** Each desk card is now a story-card link (`href` per item, falling back to the slugified title → `/blog/categories/<slug>`) with a "Read the desk" affordance.
90
- - **newsflash testimonials moved from the homepage to the about page.** The "From our readers" quote-card section (universal `testimonials` key) now lives on about between the principles and the team CTA; the `.quote-card` styles moved from the homepage page CSS to the about page CSS.
91
- - **newsflash nav + footer include forks deleted — chrome now inherits from classy.** The nav fork was a verbatim copy of classy's (the fallback regenerates an identical file); the footer's editorial look (oversized serif wordmark, hidden avatar, 24em description measure, panel-color repaints of the `.link-muted`/`.text-body` utilities, weight-800 volt column heads) was ported into the theme's `_general.scss` footer rules, so classy's inherited markup renders the same ink-slab design in both modes.
92
- - **Classy footer language icon is now theme-adaptive.** The language dropdown button's hardcoded multicolor inline SVG (fixed fills that ignored `currentColor` and clashed on dark surfaces like newsflash's ink-slab footer) is replaced with `{% uj_icon "language" %}`, which inherits the button's color in every theme and mode.
93
- - **`docs/project-structure.md` renamed `docs/directory-structure.md`** (H1 `# Project Structure` → `# Directory Structure`) for cross-framework doc-file parity — BEM names the same concept `docs/directory-structure.md`, and mirrored docs must match down to the file name. All references updated (`CLAUDE.md`, historical CHANGELOG links). Also normalized `docs/test-boot-layer.md`'s H1 to `# Test Framework — Boot Layer` (matches BXM; EM normalized likewise).
94
-
95
- ### Added
96
-
97
- - **Docs parity — new `docs/build-system.md`, `docs/templating.md`, `docs/logging.md`, `docs/common-mistakes.md`.** `build-system.md` makes CLAUDE.md's existing pipeline-reference link real (was a dead link) — gulp task list, config flow, build modes, pure helpers; `templating.md` graduates from "(planned)" — node-powertools bracket conventions + Liquid coexistence; `logging.md` is now the SSOT for the log-file tee (extracted from local-development.md, which keeps a pointer — mirrors EM/BXM); `common-mistakes.md` extracts the canonical anti-pattern list into the repo (BEM already had one). All indexed in CLAUDE.md → Documentation.
98
- - **Test coverage convention (docs).** New mirrored "Test coverage" sections in `CLAUDE.md`, `docs/test-framework.md`, `src/defaults/CLAUDE.md`, and `src/defaults/test/README.md` — every feature ships with tests at every layer it has a surface in (logic `build`/`page`, UI `page`, end-to-end `boot`); a layer is skipped only when the feature genuinely has no surface there. Mirrored across EM/BXM/BEM.
99
- - **New `newsflash` theme — editorial news site.** Paper + ink + vermilion design language with volt highlights: Fraunces serif headlines over Schibsted Grotesk, a live news-ticker marquee above the sticky blurred masthead, framed editorial images, hard-offset "lift" pill buttons, film-grain overlay, and first-class dark mode (deep warm near-black paper, cream ink). Ships custom layouts for base (fonts + ticker), homepage (cover-story hero + top-story tiles + "the latest" editorial post feed with sticky most-read/newsletter rails + numbered rundown playbook + "more to chew on" story tiles + dark big-read CTA band), blog index (lead-story splash + editorial tiles), blog post (reading-progress bar, drop cap, serif crossheads, pullquotes, author card), blog category archives (desk directory with stroked story-count numerals + per-desk fronts), blog tag archives (topics wall + per-topic fronts), pricing (membership tiers with "Editor's pick" + flash-sale promo banner), about (newsroom timeline), contact ("contact the desk" + tips line), team ("the masthead" byline cards + ink-slab charter + newsroom principles), team member (reporter profile with beat facts strip), and a 404 "correction notice". Functional pages (download, feedback, updates, auth, account) intentionally inherit classy markup restyled. Select with `theme.id: "newsflash"`. See [docs/themes.md](docs/themes.md).
100
- - **Theme-authoring convention: genre-native frontmatter defaults.** A theme's layout frontmatter defaults are part of its identity and must be written for the theme's genre (newsflash ships news copy + news-purposed `rundown`/`desks` homepage sections), never copied from classy's SaaS demo data. Universal section keys (`hero`, `cta`, `stats`, `faqs`, `pricing.plans`, …) stay shared so consumer overrides survive theme swaps. Documented in [docs/themes.md](docs/themes.md#frontmatter-defaults-are-part-of-the-themes-identity) + the Path A authoring checklist.
101
- - **Appearance picker in every footer.** Classy's footer now ships the appearance (light/dark/system) dropdown next to the language dropdown — a pure drop-in block driven by the framework's `data-appearance-*` attributes. Every theme inherits it via the footer fallback; themes with custom footers (newsflash) include it themselves. Documented as required footer furniture in [docs/themes.md](docs/themes.md) + [docs/appearance.md](docs/appearance.md).
102
- - **Theme-authoring convention: inherit classy's nav + footer chrome, restyle via CSS.** Themes do NOT fork the chrome includes — `copyFallbackThemeFiles()` supplies classy's nav/footer (with paths rewritten into the theme's namespace) and the chrome's identity comes from theme CSS (newsflash's serif masthead + editorial ink-slab footer are pure `css/layout/` restyles of classy's markup). Fork an include only when the structure genuinely diverges — a re-skin fork is a copy that silently drifts (newsflash's nav fork had diverged from classy by nothing but a comment, and the icon-only picker fix had to be applied twice). Documented in [docs/themes.md](docs/themes.md) + the Path A authoring checklist.
103
- - **Theme-contract build test.** New `mgr:build/theme-contract` suite turns the docs/themes.md conventions into executable assertions, globbing every shipped theme (`_template` included) so a new theme is covered the moment it lands: entry files + `$avatar-sizes` + shared bootstrap-overrides import, `[ site.theme.id ]` bracket parents (swappability), no theme-prefixed classes in markup, no inline `<script>` bodies, the pricing JS-contract markers, `blog-post-content` in post layouts, and page-asset files matching a layout-declared `asset_path` shape (wrong shape = silent bundle skip). It caught neobrutalism's missing promo banner + `.card-title` on its first run.
104
- - **Shared plan-pricing include — `core/pricing/resolve-plan.html`.** The plan price-resolution Liquid (product lookup from `site.web_manager.payment.products`, frontmatter-over-config monthly/annual precedence, per-unit math) was copy-pasted across all three themes' pricing layouts; it's now a single shared "logic include" (Jekyll includes share caller scope) that every theme calls per plan and renders the assigned variables from. Also guards the per-unit `divided_by` against nil/zero unit values, which previously crashed the build when the per-unit feature value couldn't resolve.
105
- - **Theme-authoring convention: develop in ONE appearance mode, ship with BOTH.** Build against a primary mode (the consumer's `theme.appearance` default), then validate light AND dark before declaring the theme done — including a live click-through of the footer appearance picker. Documented in [docs/themes.md](docs/themes.md) authoring conventions + validation checklist.
106
- - **Theme-authoring gotchas documented:** theme page bundles re-emit Bootstrap after the main bundle (use doubled selectors — `:root:root`, `[data-bs-theme="dark"][data-bs-theme="dark"]`, `.btn.btn`), and `.bg-body-*`/`.text-body-*` utilities paint from the `--bs-*-rgb` companion variables, which must be remapped alongside the hex vars. See [docs/themes.md](docs/themes.md).
107
- - **`npx mgr blogify --count=<n>`.** The blogify command now accepts a post count (default 12, matching the old hardcoded behavior) — e.g. `--count=18` fills every deduplicated post slot on the newsflash homepage. Documented in README + the CLAUDE.md CLI table.
108
- - **`npx mgr test` tees output to `logs/test.log`.** All test-runner output is now duplicated (ANSI-stripped) to `<projectRoot>/logs/test.log`, truncated fresh on each run, via the existing `attach-log-file` util (skipped on CI through `isServer()`) — mirrors EM's and BEM's `test.log` pattern. Grep the file after a run instead of scrolling terminal scrollback.
109
- - **`TEST_EXTENDED_MODE` — extended test mode (`--extended`).** `npx mgr test --extended` (or `TEST_EXTENDED_MODE=true npx mgr test`) opts in tests that hit REAL external services (network fetches, Firebase via web-manager, live APIs); off by default so `npx mgr test` stays fast and offline-safe. The signal is the unprefixed `TEST_EXTENDED_MODE` env var — the SAME name across BEM/BXM/UJM/EM (cross-framework parity) — and once set it propagates to every spawned child (the Jekyll build, the boot HTTP server / Puppeteer browsers) via inherited `process.env`. A warning prints (and is teed to `logs/test.log`) when on. Gate live tests on `process.env.TEST_EXTENDED_MODE`. New `src/test/utils/extended-mode-warning.js` is the SSOT for the warning copy. Documented in [docs/test-framework.md](docs/test-framework.md#extended-mode-test_extended_mode) + the env-vars table.
110
-
111
- ### Changed
112
-
113
- - **Test command standardizes on `TEST_EXTENDED_MODE`, replacing `--integration` / `UJ_TEST_INTEGRATION`** (no backwards compat — the old flag/env are gone). Mirrors the canonical unprefixed `TEST_EXTENDED_MODE` shared across BEM/BXM/EM.
114
-
115
- ### Fixed
116
-
117
- - **`attach-log-file` no longer truncates `logs/test.log` mid-run.** The tee state is now wrapped in a `createTee()` factory (independent, stackable instances) with the process-wide singleton built on top. A later `attach()` captures the CURRENT `process.stdout.write` (which may already be an outer tee) and restores that exact reference on `detach()`, so stacked tees fan out and unwind in LIFO order. The new build-layer `attach-log-file` test uses its OWN `createTee()` instance, so exercising attach/detach no longer detaches the live singleton tee that's capturing the actual run (which previously cut `logs/test.log` to ~9 lines). UJM's name-based signature (`attachLogFile('test')`) and synchronous `detach()` are preserved. Mirrors EM's `createTee()` refactor.
118
-
119
- - **neobrutalism pricing page now satisfies the full framework JS contract.** Same two gaps newsflash had: the flash-sale promo banner block (`#pricing-promo-banner` + badge/text/countdown/code ids, shipped `hidden`) was missing entirely, and plan names lacked the `.card-title` class the pricing JS reads for analytics. Both caught by the new theme-contract test on its first run.
120
- - **Classy admin minimal layouts use bracket parents.** `admin/core/minimal.html` + `minimal-viewport-locked.html` hardcoded `layout: themes/classy/backend/...` instead of the `themes/[ site.theme.id ]/...` convention (the fallback's path rewrite masked it for non-classy themes). Normalized to brackets; now enforced by the theme-contract test.
121
- - **newsflash nav + dropdown hover text is readable again.** The hover fill (ink pill) left the label ink-on-ink: the shared nav/account includes put Bootstrap's `.text-body` utility (`!important`) on every link and dropdown item, which beat the theme's hover `color`. The hover/active colors now carry `!important`, and dropdown-item child spans with text utilities flip to `inherit` on hover (fixes the blacked-out account-dropdown rows too).
122
- - **newsflash dropdown item pills no longer press edge-to-edge against the panel.** Bootstrap's re-emitted `.dropdown-menu` rule (`--bs-dropdown-padding-x: 0`) was stripping the theme's panel inset on pages with page CSS — the highlighted pill touched the panel edges. The theme's dropdown rule now uses the doubled selector (`.dropdown-menu.dropdown-menu`), the established page-bundle-gotcha pattern.
123
- - **newsflash headings no longer go thin on pages with page CSS.** Bootstrap's re-emitted `.display-*` / `.lead` rules (stock weights 300) were clobbering the theme's main-bundle typography on every page that ships a page bundle (post + about read spindly; contact — no page bundle — looked right). Type metrics now ship as Bootstrap variables in the config `@forward with (...)` block (`$display-font-weight: 550`, `$display-line-height: 1.04`, `$lead-font-weight: 400`, `$headings-line-height: 1.1`, `$line-height-base: 1.55`) so every Bootstrap copy compiles them natively. Documented as an extension of the page-bundle gotcha in [docs/themes.md](docs/themes.md).
124
- - **newsflash post hero image fills its frame.** Two stacked causes: `uj_post` image-tags render as `<picture><img>`, so the img's percentage height resolved against the auto-height picture wrapper (the base `.art-frame` now makes `picture` fill the frame), and the framework's generic `.blog-post-image { max-height: 480px }` capped the image inside the 21/10 frame (the article-hero rule now lifts it with `max-height: none`).
125
- - **Footer brand description no longer disappears when its data value liquifies to empty.** Both classy's and newsflash's footers resolve the brand blurb via `data.logo.description | default: site.brand.description`, but `default:` only sees the RAW string — a footer.json value like `'{{ site.meta.description }}'` with no `meta.description` set passed the default check and then liquified to nothing, hiding the description even when `brand.description` was set. The footers now re-check after liquification and fall back to `site.brand.description`.
126
- - **newsflash dark mode no longer stomps consumer brand colors.** The dark-mode `--bs-primary`/`--bs-link-color` remaps were hardcoded to the brightened vermilion, so a consumer's `$primary` override (via `main.scss` `with (...)`) held in light mode but snapped back to vermilion in dark. The remap now derives from the compile-time `$primary` (stock vermilion keeps its hand-tuned brightening; other brand colors get a generic white-mix lift). Documented as a theme-authoring gotcha in [docs/themes.md](docs/themes.md#derive-dark-mode-brand-remaps-from-primary-gotcha).
127
- - **newsflash pricing page now satisfies the full framework JS contract.** Added the flash-sale promo banner (`#pricing-promo-banner` + badge/text/countdown/code ids, shipped `hidden` — the framework pricing JS reveals it, fills the rotating sale name + countdown, and offsets the masthead) and the `.card-title` class on plan names (read for add-to-cart analytics). The pricing JS contract — billing radios, `.amount`/`.billing-info`/`.price-per-unit` data attributes, `button[data-plan-id]`, plan-name selector, promo banner ids — is now documented as a required cross-theme contract in [docs/themes.md](docs/themes.md#the-pricing-page-has-a-js-contract-too).
128
- - **`npx mgr test <target>` now correctly scopes by source.** The positional test target was previously ignored — every run executed all suites regardless of the argument. It now selects which test FILES run: `project:` runs project tests only, `mgr:` / `ujm:` / `framework:` run framework tests only (`mgr:` is the universal cross-framework alias, equivalent to the UJM-specific `ujm:` / `framework:`), and a bare path (no prefix) matches both sources. The `--filter=<substring>` flag stays orthogonal — it matches test NAMES within the already-selected files and composes with the target. See [docs/test-framework.md](docs/test-framework.md#filtering-tests).
129
-
130
- ---
131
- ## [1.7.2] - 2026-06-09
132
-
133
- ### Added
134
-
135
- - **Push notification subscribe on payment confirmation CTA click** — hooks `subscribe()` onto CTA button clicks on the payment confirmation page. Requires a user gesture for the browser permission prompt, so uses `{ once: true }` click listeners.
136
-
137
- ### Fixed
138
-
139
- - **FormManager spinner layout in icon-only buttons** — `_showSpinner()` no longer adds `me-2` margin when `submittingText` is empty. Previously, the unnecessary right margin squished the spinner off-center in small round buttons (e.g. chat send).
140
-
141
- ---
142
- ## [1.7.1] - 2026-06-09
143
-
144
- ### Added
145
-
146
- - **Auto-subscribe push notifications on authenticated page load** — calls `webManager.notifications().subscribe()` in the core auth listener after the consent guard passes. Fires for both fresh signups and returning sign-ins. Failure logs a warning and never blocks navigation.
147
-
148
- ---
149
- ## [1.7.0] - 2026-06-09
150
-
151
- ### Added
152
-
153
- - **Animation Studio admin page** (`/admin/studio`). New default admin page for creating screen-recording-ready product demo animation clips. Framework provides all boilerplate: sidebar, FormManager controls, resolution picker (540p–4K), aspect ratio toggle (16:9/9:16), playback loop with speed/pause controls, and 60fps tab recording via `getDisplayMedia` + `CropTarget`.
154
- - **Clip builder helpers** — `flowClip`, `cardClip`, `chatClip` passed alongside `animate`/`el` to clip `build()` functions. Consumers declare data, not structure.
155
- - **Studio CSS partial** (`_studio.scss`) — importable via `@use 'studio'` in consumer page CSS. Includes boilerplate layout + generic animation primitives (`.s-hidden`, `.s-step-bg`, `.s-bar-fill`).
156
- - **`docs/animation-studio.md`** — full reference for the clip contract, helpers, recording, resolution, aspect ratios.
157
- - **Admin sidebar entry** for Animation Studio under new "Content" section.
158
-
159
- ---
160
- ## [1.6.9] - 2026-06-08
161
-
162
- ### Added
163
-
164
- - **Bootstrap-first theming convention.** Added comprehensive guidance to `docs/themes.md` and the default consumer `CLAUDE.md` — themes must restyle Bootstrap classes, not create parallel design systems.
165
- - **Dev workflow documentation.** Added explicit instructions to `CLAUDE.md` and consumer defaults to check `logs/dev.log` instead of running `npm start`/`npm test` in consumer projects.
166
-
167
- ### Fixed
168
-
169
- - **Signup metadata failure notification timeout.** Changed dev-only notification from permanent (`timeout: 0`) to 1 second (`timeout: 1000`) so it doesn't block the screen during development.
170
-
171
- ---
172
- ## [1.6.8] - 2026-06-07
173
-
174
- ### Fixed
175
-
176
- - **Lazy loading animation flash.** Elements with `data-lazy="@class animation-*"` were briefly visible before the IntersectionObserver fired, causing a jarring flash (visible → disappear → fade-in). Added CSS rule to hide these elements from initial paint so they smoothly animate in.
177
-
178
- ---
179
- ## [1.6.5] - 2026-06-04
180
-
181
- ### Added
182
-
183
- - **Dynamic event fitting in month view.** Calendar cells now show as many events as physically fit instead of a hardcoded max of 3; "+N more" only appears when events genuinely overflow.
184
- - **Local time display on calendar events.** Event pills show local time (data layer remains UTC). Editor modal shows a local date+time badge below the UTC inputs.
185
- - **Hover states on event pills.** Brightness + box-shadow effect on hover for month, week, and day views.
186
- - **Now line on month view.** Red progress line shows current time of day in today's cell, with dot rendered above cell borders.
187
- - **Left tab accent on all event pills.** Consistent dark left border matches week/day view style.
188
- - **Monthly-weekday (Nth weekday) recurrence pattern.** Calendar supports "2nd Wednesday of every month" style recurring campaigns with calendar-relative date stepping.
189
-
190
- ### Changed
191
-
192
- - **Per-string translation caching replaces all-or-nothing page caching.** Each page's cache is now a `hash→translation` map (`es/pages/index.html.json`). Only strings whose content hash changed are sent to the API — unchanged strings are served from cache. Dramatically reduces API calls and cost on incremental builds.
193
- - **Prompt hash mismatch now wipes page cache files** (not just meta entries) for a clean slate.
194
- - **Translation stats now track cached vs new strings** instead of whole-page hit/miss.
195
- - **Outside-month calendar cells** now fade only content (date number + events), keeping borders at full opacity for consistent grid lines.
196
- - **Imagemin constants renamed** (`MAX_SOURCE_DIMENSION` → `IMAGE_MAX_DIMENSION`) and default max dimension lowered from 4096 to 2048.
197
-
198
- ### Removed
199
-
200
- - **`RECHECK_DAYS`** — per-string content hashes make age-based invalidation unnecessary.
201
- - **All-day row** removed from week view (no all-day event concept in marketing calendar).
202
-
203
- ---
204
- ## [1.6.4] - 2026-06-03
205
-
206
- ### Fixed
207
-
208
- - **Workflow template `{ github.secrets }` clobbered `{ github.workflows.build.schedule }`.** Renamed to flat `{ githubSecrets }` key to avoid namespace collision in the template spread.
209
-
210
- ---
211
- ## [1.6.3] - 2026-06-03
212
-
213
- ### Changed
214
-
215
- - **Workflow template dynamically generates secrets from `.env`.** `defaults.js` reads the default `_.env`, extracts all key names, and produces a `{ github.secrets }` template variable — no more hardcoding individual secrets in `build.yml`.
216
- - **`publishSecrets()` replaces `publishGitHubToken()` in setup.** Now reads the consumer's `.env` and publishes ALL non-empty keys as GitHub Actions repo secrets (not just `GH_TOKEN`).
217
-
218
- ### Added
219
-
220
- - **Country flag SVGs:** id, in, ph, pk, ru, vn (modern-square style).
221
- - **Auto-create `pages/` dir for custom themes** in webpack.js — prevents `Module not found: __theme__/pages` error when a consumer theme lacks a `pages/` directory.
222
-
223
- ### Removed
224
-
225
- - **`BACKEND_MANAGER_KEY`** removed from workflow template (replaced by dynamic `.env` secrets).
226
-
227
- ---
228
- ## [1.6.2] - 2026-06-02
229
-
230
- ### Fixed
231
-
232
- - **`npx mgr setup` now merges new `.env` keys into existing consumer projects.** Previously, `ensureCoreFiles()` returned early when `src/_config.yml` existed, skipping the `gulp defaults` task that handles `.env` merging. New framework keys (like `BACKEND_MANAGER_OPENAI_API_KEY`) were never added to consumers that had already run setup once.
233
-
234
- ---
235
- ## [1.6.1] - 2026-06-02
236
-
237
- ### Changed
238
-
239
- - **Translation system overhauled: JSON array format, gpt-5.4-nano model, .env API key.** Replaced tagged text `[0]...[/0]` format with JSON arrays (eliminates tag corruption). Upgraded from `gpt-4.1-mini` to `gpt-5.4-nano` (3.5x cheaper). API key now read from `BACKEND_MANAGER_OPENAI_API_KEY` in `.env` instead of remote `fetchOpenAIKey()`. Uses GPT-5+ Responses API (`developer` role, `reasoning: { effort: 'low' }`).
240
- - **Translation concurrency: node-powertools `queue()` replaces batched delays.** Removed 25-page batches with 10s sleeps; now uses `queue({ concurrency: 5 })` for steady throughput.
241
- - **Translation retries simplified.** Removed recursive subdivision system. On JSON array length mismatch, retries up to `MAX_RETRIES` (2) with file/language context in logs.
242
-
243
- ### Added
244
-
245
- - **`data-uj-no-translate` HTML attribute** — marks DOM elements (and children) to skip during translation. Applied to country and phone code `<select>` dropdowns on the account page, reducing account.html from ~1241 to ~454 translatable strings.
246
- - **`BACKEND_MANAGER_OPENAI_API_KEY`** added to default `.env` template.
247
- - **Prompt cache HIT/MISS logging** with entry counts per language.
248
- - **`IGNORE_EXCEPTIONS`** — allows specific pages through folder-level exclusions (e.g. `test/translation.html` survives the `test/` folder exclusion).
249
-
250
- ### Removed
251
-
252
- - **`fetchOpenAIKey()`** — remote API key fetch from `api.itwcreativeworks.com` removed.
253
- - **Subdivision retry system** (`subdivideAndTranslate`) — replaced by simple length-mismatch retry.
254
- - **`test/`, `team/`, `updates/` folders** excluded from translation by default.
255
-
256
- ---
257
- ## [1.6.0] - 2026-06-02
258
-
259
- ### Changed
260
-
261
- - **`getEnvironment()` is now the single source of truth for environment detection** (and `testing` is a first-class environment). [src/utils/mode-helpers.js](src/utils/mode-helpers.js) — `getEnvironment()` is the ONLY function that reads the raw signals (`UJ_TEST_MODE` / `window.Configuration.uj.environment` / `UJ_BUILD_MODE` / `UJ_IS_SERVER` / `NODE_ENV`) and resolves them to exactly one of `'development' | 'testing' | 'production'` (mutually exclusive; **testing wins**). `isDevelopment()` / `isProduction()` / `isTesting()` now **derive** from it, so they can never disagree and exactly one is always true. `isProduction()` is a real positive check, not `!isDevelopment()`. [src/build.js](src/build.js) drops its own `getEnvironment()` (now mixed in via `attachTo`). Doc renamed `cross-context-helpers.md` → [docs/environment-detection.md](docs/environment-detection.md).
262
- - **Redirect delay now keys off non-production (dev OR testing), not dev-only.** [src/assets/js/modules/redirect.js](src/assets/js/modules/redirect.js) delays the redirect whenever `environment !== 'production'` so it's observable in tests too.
263
- - **Footer language dropdown gates the extra-languages list on `translation.enabled`** (+ vertical-alignment fix). [footer.html](src/defaults/dist/_includes/themes/classy/frontend/sections/footer.html)
264
-
265
- ### Added
266
-
267
- - **`test/_init.js` pre-test lifecycle hook (setup-only).** [src/test/runner.js](src/test/runner.js) loads an optional `test/_init.js` from BOTH the framework and consumer test roots and runs its `setup()` once before any suite (the `_`-prefix keeps it out of discovery). Mirrors BEM/EM/BXM. Default scaffold at [src/defaults/test/_init.js](src/defaults/test/_init.js). [docs/test-framework.md](docs/test-framework.md) adds a "NEVER mock — test against the real harness" section + `_init.js` reference, and tests now assert the env invariant across every signal scenario.
268
- - **`translation.exclude` config** — folder or page paths excluded from AI translation (added to both the files and folders match sets). Default [_config.yml](src/defaults/src/_config.yml) excludes `blog`. [src/gulp/tasks/translation.js](src/gulp/tasks/translation.js)
269
- - **`setup` prunes legacy first-name-only default team members** (e.g. `team/alex`) and their image dirs via `removeLegacyTeamMembers()`. [src/commands/setup.js](src/commands/setup.js)
270
- - **`npx mgr install live`** accepted as an alias for `prod`/`production`. [src/commands/install.js](src/commands/install.js); docs use `install dev` / `install live` consistently.
271
-
272
- ### Fixed
273
-
274
- - **Download buttons use a `[data-download]` hook** instead of the brittle `.btn-primary:not([type="submit"])` selector. [download.html](src/defaults/dist/_layouts/themes/classy/frontend/pages/download.html) + [download/index.js](src/assets/js/pages/download/index.js)
275
- - **Feedback review modal:** $100 gift-card incentive + an explicit "you must actually post your review" warning, and copy normalized "Write a review" → "Post your review". [feedback.html](src/defaults/dist/_layouts/themes/classy/frontend/pages/feedback.html) + [feedback/index.js](src/assets/js/pages/feedback/index.js)
276
- - Removed a leftover `package copy.json` from the repo root.
277
-
278
- ---
279
- ## [1.5.0] - 2026-06-02
280
-
281
- ### Added
282
-
283
- - **Neobrutalism theme** — a complete second shipped theme (alongside `classy`), not a recolor: hard ink borders, offset "press" shadows, chunky display type, flat color-blocks, square controls. Custom homepage + pricing layouts. It restyles **standard Bootstrap classes** (`.btn`, `.card`, `.text-bg-*`) and a **universal semantic layout vocabulary** (`.section-hero`, `.showcase-row`, `.stat-block`, `.pricing-plan`, `.cta-panel`, …) — **no theme-prefixed classes** — so the same markup is swappable across themes (change `theme.id`, done). [src/assets/themes/neobrutalism/](src/assets/themes/neobrutalism/)
284
- - **Theme system: three-layer page-asset cascade (Global → Theme → Consumer) for BOTH CSS and JS.**
285
- - Theme page CSS: `themes/<id>/pages/<path>/index.scss` compiles to a theme-suffixed bundle (`index.<themeId>.bundle.css`) via [src/gulp/tasks/sass.js](src/gulp/tasks/sass.js), linked by [head.html](src/defaults/dist/_includes/core/head.html) with `{% iffile %}`. Missing = nothing loads (component styles handle it), no fallback needed.
286
- - Theme page JS: `__theme__/pages/<path>/index.js` loaded as the `#theme` layer in [src/index.js](src/index.js), executed `main → theme → project` (a `webpackInclude: /\.js$/` guard stops `.scss` from being pulled into the JS import context).
287
- - Per-page HTML layout overrides under `_layouts/themes/<id>/` reuse classy's `page.resolved.*` frontmatter data contract and fall back to classy when a layout is absent.
288
- - **Theme-authoring template** at [src/assets/themes/_template/](src/assets/themes/_template/) + a full **[docs/themes.md](docs/themes.md)** reference for building a theme inside UJM or in a consumer project (selection/resolution, classy fallback, page CSS/JS layers, the no-prefix vocabulary, adaptive-button + interactive-accent gotchas).
289
- - **Single interactive-accent token** (`--nb-accent-interactive` = `var(--bs-primary)`) drives all hover/active states (blue), distinct from the yellow signature accent reserved for static blocks. Adaptive buttons (`btn-adaptive`/`-inverse`) get explicit theme overrides so they render solid and press like every other button.
290
- - **Asset-layer test harness** — `UJ_TEST_LAYERS` flag + [manage-test-layers.js](src/gulp/tasks/utils/manage-test-layers.js) generate real consumer-side test files; the `/test/libraries/layers` page renders the Global/Theme/Consumer cascade as red/green dots to prove all three layers load in order. `npm run start:test-layers` enables it.
291
-
292
- ---
293
- ## [1.4.3] - 2026-05-28
294
-
295
- ### Fixed
296
-
297
- - **Imagemin: uppercase-extension images (e.g. `IMG_3119.JPG`) now build end to end.** v1.4.2 made the glob case-insensitive so the file was discovered, but `gulp-responsive-modern`'s `lib/format.js` does a case-sensitive `switch` on `path.extname()` and returns the string `'unsupported'` for `.JPG`, which then crashes `sharp.toFormat()`. [src/gulp/tasks/imagemin.js](src/gulp/tasks/imagemin.js) now pipes each file through an in-stream `Transform` that lowercases the extension on the Vinyl path before the responsive plugin sees it (the on-disk source is left untouched).
298
- - **Log files no longer truncate before the crash that caused them.** [src/utils/attach-log-file.js](src/utils/attach-log-file.js) switched from `fs.createWriteStream` (async-buffered) to synchronous `fs.writeSync` against an open fd. The buffered stream dropped its tail when a gulp task threw and the process exited — so the lines describing the failure never reached `logs/build.log`. Synchronous writes guarantee the full error + stack survive an immediate exit.
299
-
300
- ### Changed
301
-
302
- - **Auth: signup-consent gating now keys off the user doc's `flags.signupProcessed` instead of a time window.** [src/assets/js/core/auth.js](src/assets/js/core/auth.js) drops the `SIGNUP_MAX_AGE` (5-minute) heuristic and the client-only `localStorage` flag. `sendUserSignupMetadata` fires whenever the doc shows signup unprocessed (the server is idempotent), and the consent guard only signs a user out once signup has actually been processed — removing the risk of locking users out on a transient metadata-send failure.
303
- - **Footer language dropdown always renders.** No longer gated on `site.translation.enabled`; falls back to `site.translation.default` (or `"en"`) when no extra languages are configured. [src/defaults/dist/_includes/themes/classy/frontend/sections/footer.html](src/defaults/dist/_includes/themes/classy/frontend/sections/footer.html)
304
- - **Sentence-case copy normalization** across default pages (pricing, alternatives, admin/test pages, sitemap section labels): "API access", "Flash sale", "Root pages", "…and more:" etc.
305
- - **Updates feed:** the `v0.0.1` sample entry is marked `draft: true` so it's hidden from the listing and sitemap (dev-only).
306
-
307
- ---
308
- ## [1.4.2] - 2026-05-27
309
-
310
- ### Fixed
311
-
312
- - **Imagemin: uppercase image extensions (e.g. `IMG_3119.JPG`) no longer break the responsive build.** `gulp-responsive-modern` uses micromatch internally, which is strictly case-sensitive regardless of filesystem. On macOS APFS, gulp's `src()` would discover the file and count it toward expected outputs, but the lowercase-only `**/*.{jpg,jpeg,png}` pattern wouldn't match — producing zero outputs and erroring with "Available images do not match the following config". [src/gulp/tasks/imagemin.js](src/gulp/tasks/imagemin.js) now expands `ALL_IMAGE_GLOB` and `RESPONSIVE_GLOB` to include uppercase variants so consumers don't need to rename camera/phone files.
313
-
314
- ---
315
- ## [1.4.1] - 2026-05-27
316
-
317
- ### Changed
318
-
319
- - **Bumped `puppeteer` `^24.43.1` → `^25.1.0`.** Puppeteer 25 is ESM-only and requires Node 22+; UJM's existing `require('puppeteer')` calls in [src/test/runners/boot.js](src/test/runners/boot.js) and [src/test/runners/chromium.js](src/test/runners/chromium.js) keep working via Node 22's native ESM-require interop.
320
- - **Bumped `html-validate` `^10.17.0` → `^11.4.0`.** Used by [src/gulp/tasks/audit.js](src/gulp/tasks/audit.js); audit suite still passes.
321
-
322
- ---
323
- ## [1.4.0] - 2026-05-27
324
-
325
- ### Added
326
-
327
- - **`UJ_IMAGEMIN_REWRITE_SOURCES=true` flag** (opt-in, off by default) — when set, the `imagemin` gulp task scans every image scheduled for processing and rewrites in place any whose longest dimension exceeds 4096px. Uses `sharp` with `fit: 'inside'` (aspect-preserving), JPEG quality 80 / mozjpeg / progressive, PNG quality 80. Cache hashes for affected files are updated so the new content becomes the new cache key. Intended as a one-off cleanup for repos with pre-existing oversized source images that silently stall `gulp-responsive-modern`/`sharp`. See [docs/images.md](docs/images.md#cleanup-for-existing-oversized-sources-uj_imagemin_rewrite_sources).
328
- - **Log-file tee** (`src/utils/attach-log-file.js`): every line of stdout/stderr produced by the gulp pipeline is duplicated to `logs/dev.log` (during `npm start`) or `logs/build.log` (during `npm run build`) in the consumer project root. ANSI color codes are stripped from the file output; terminal output is unchanged. Files truncate fresh on each run. Skipped when `UJ_IS_SERVER=true` (CI/cloud) — no `logs/` directory is created in workspace contexts. Attached at the top of `src/gulp/main.js`. See [docs/local-development.md](docs/local-development.md#log-files).
329
- - **Pricing page: 7-day money-back guarantee badge** under the billing toggle and per-card on each paid plan; free plan shows "Upgrade any time" with a rocket icon.
330
- - **Pricing page: trial-aware CTA copy** — buttons now read "Get free trial" only when the plan's `trial.days > 0`, otherwise "Get started". Free plan stays "Get started". Avoids misleading users into thinking they need a trial for a free plan or that every paid plan offers one.
331
- - **Auth: field-level error rendering.** New `isPasswordError()` and `passwordErrorMessage()` helpers in `src/assets/js/libs/auth.js` route Firebase password errors (`auth/weak-password`, `auth/missing-password`, `auth/wrong-password`, `auth/password-does-not-meet-requirements`) onto the password input via `FormManager.throwFieldErrors()` instead of the form-level banner. Signin: `auth/invalid-credential` | `auth/wrong-password` | `auth/user-not-found` highlight both email + password with "Incorrect email or password" (Firebase intentionally collapses these to prevent email enumeration). `auth/invalid-email` highlights the email field. Signup: when the email already exists and auto-signin fails, the message lands inline on the email field rather than throwing a generic banner error.
332
- - **Dev-only consent-guard warning** in `src/assets/js/core/auth.js` `sendUserSignupMetadata` catch block: shows a `webManager.utilities().showNotification()` with the exact wall-clock time the consent guard will sign the user out (and remaining mm:ss) when the metadata POST fails. Wrapped in `/* @dev-only:start */` blocks so production builds strip it.
333
-
334
- ### Changed
335
-
336
- - **Sentence-case copy normalization** across ~60 layouts, pages, and section configs. Examples: "Sign Out" → "Sign out", "API Keys" → "API keys", "Save Changes" → "Save changes", "Contact Us" → "Contact us", "Main Menu" → "Main menu", "All Items" → "All items", "Admin Panel" → "Admin panel", "Sign Up" → "Sign up". Touches frontend pages (account, auth, contact, download, extension, index, payment, pricing, team, etc.), blueprint admin/legal/portal pages, test pages, and nav/footer/sidebar/account JSON section configs.
337
- - **FormManager: `.has-validation` on input-group when a field is marked invalid.** Bootstrap requires this class on the wrapping `.input-group` so the trailing element (e.g. a password-visibility toggle) keeps its border-radius once a sibling `.invalid-feedback` is appended.
338
- - **Classy theme forms SCSS:** restored rounded right corners on `.input-group.has-validation > *:nth-last-child(2)` so the visually-last interactive element keeps its `$classy-radius-lg`. Also added `.form-control.is-invalid:focus` override to keep the danger (red) focus ring instead of the brand-blue ring the classy theme was applying.
339
-
340
- ### Fixed
341
-
342
- - **`imagemin` gulp task race condition (build mode):** the task was declared `async function imagemin(complete)` and returned a stream directly. Async functions wrap their return value in a Promise, so gulp resolved the task on the (already-resolved) Promise instead of waiting for the stream's `'finish'` event. Downstream tasks (jekyll, audit, minifyHtml) then started while imagemin was still writing to `dist/`, and jekyll would snapshot `_site/` before late images landed. Builds reported success while silently shipping a partial site. Fixed by explicitly `await`ing stream completion via `new Promise((resolve, reject) => { ... .on('finish', resolve).on('error', reject) })` before moving to the cache-push step, then `return complete()`. The wait only ever runs in build mode (dev mode short-circuits earlier in the task), so `npm start` startup is unaffected.
343
- - **Oversized source images silently failing to land in `_site/`.** Source images with very large dimensions (10000px+ longest side) decode into hundreds of MB per worker in `sharp`, which can stall the `gulp-responsive-modern` stream so quietly that gulp reports the task complete. The build appears successful but some images never reach `_site/`. Documented the constraint and the new `UJ_IMAGEMIN_REWRITE_SOURCES` cleanup flag in [docs/images.md](docs/images.md). Recommended fix is to cap images at the upload step; the rewrite flag is a fallback for cleaning up existing repos.
344
-
345
- ---
346
- ## [1.3.12] - 2026-05-25
347
-
348
- ### Fixed
349
-
350
- - **Fresh-consumer `npx mgr setup` now works on the first run.** Two ordering bugs prevented setup from succeeding on a brand-new consumer project: (1) `ensureBundle()` ran `bundle install` before `ensureCoreFiles()` scaffolded the `Gemfile`, failing with "Could not locate Gemfile" — reordered so core files are scaffolded first. (2) The gulpfile eagerly `require()`s every task module at load time, and `sass.js`/`distribute.js`/`imagemin.js` all call `Manager.getUJMConfig()` at module top-level, so a fresh consumer (with no `config/ultimate-jekyll-manager.json`) couldn't even invoke `gulp defaults` — extended `ensureCoreFiles()` to seed both `src/_config.yml` and `config/ultimate-jekyll-manager.json` from `dist/defaults/` before invoking gulp. Both steps remain idempotent (existing-file guard + `{ overwrite: false }` copy).
351
-
352
- ---
353
- ## [1.3.11] - 2026-05-24
354
-
355
- ### Changed
356
-
357
- - **Account page UI polish.** Three small consistency fixes on `/account`: (1) Notifications "Save preferences" button now has the `floppy-disk` icon to match the profile section's "Save Changes" button. (2) Generate signin link modal no longer shows a redundant Cancel button in its footer — the X in the modal header already dismisses it. (3) Connections "Manage connections" card no longer renders a divider between the section title and the first item (dropped `list-group list-group-flush` from `#connections-list`); the per-item `border-top` already handles inter-item separation, matching the Sign-in methods card's pattern.
358
-
359
- ---
360
- ## [1.3.10] - 2026-05-24
361
-
362
- ### Added
363
-
364
- - **Generate signin link** — advanced-user feature on `/account#security` that creates a temporary `/signin?authCustomToken=<token>` URL via `POST /backend-manager/user/token`. Centered text-link trigger sits under the Active sessions card (same Bootstrap utility classes as Cancel subscription: `btn btn-link btn-sm text-muted text-decoration-underline opacity-50`). Opens a danger-bordered modal with a typed-phrase gate (`I will not share this link`) that enables the red Generate button. On success, the warning view swaps in-place for the link inside a readonly monospaced input + Copy button, with a prominent 1-hour expiry warning. Token never persists outside the input — `show.bs.modal` resets state every open. Backend route already existed (`functions/routes/user/token/post.js`) and the existing `handleCustomTokenSignin` flow in `src/assets/js/libs/auth.js` consumes the resulting URL.
365
-
366
- ### Changed
367
-
368
- - **Account-page marketing toggle now requires an explicit "Save preferences" button.** v1.3.9 made the toggle auto-submit on change. Cleaner UX: user flips the toggle, sees the new state visually, then clicks Save when they actually want to commit. Markup adds a `<button type="submit" class="btn btn-primary">Save preferences</button>` to the `#marketing-emails-form` (classy account template). JS drops the `addEventListener('change', () => formManager.submit())` line. On failure, the toggle stays where the user left it (no auto-revert) — they see the error message via FormManager and can hit Save again.
369
-
370
- ### Removed
371
-
372
- - **`.cancel-trigger-link` SCSS class.** Replaced with Bootstrap utilities on both the existing Cancel subscription button and the new Generate signin link button (`text-muted opacity-50` instead of the custom `opacity: 0.7` + hover transition + `0.8125rem` font size). Avoids inventing project-specific classes when Bootstrap utilities cover the same styling.
373
-
374
- ---
375
- ## [1.3.9] - 2026-05-24
376
-
377
- ### Fixed
378
-
379
- - **Account-page marketing toggle showed "Failed to update email preferences" even on successful unsubscribe.** Root cause: `pages/account/sections/notifications.js` was checking `response.data?.success !== true`, but `authorizedFetch` returns the JSON body directly (no `data` wrapper), and BEM's `assistant.respond({ success: true })` writes the object at the response root via `res.json(response)`. So `response.success === true` and `response.data?.success === undefined` — the check fired even though the backend had successfully written `consent.marketing.status = 'revoked'` AND removed the contact from SendGrid + Beehiiv. Frontend UX showed a danger toast and reverted the toggle, but server state was already correct, putting the UI out of sync with reality. Fix: check `response.success` at the root.
380
-
381
- ### Changed
382
-
383
- - **`pages/account/sections/notifications.js` refactored to use FormManager**, matching the project rule that all user-driven API forms use form-manager for in-flight/success/error UX. The toggle is now wrapped in `<form id="marketing-emails-form">` (classy account template, `src/defaults/dist/_layouts/themes/classy/frontend/pages/account/index.html`), and the change event triggers `formManager.submit()`. Success notification uses `formManager.showSuccess()`; failure throws so FormManager surfaces the error toast and the JS reverts the toggle to its last-known-good state. Replaces the ad-hoc `addEventListener('change')` + raw `authorizedFetch` + manual `webManager.utilities().showNotification()` pattern.
384
-
385
- ---
386
- ## [1.3.8] - 2026-05-24
387
-
388
- ### Fixed
389
-
390
- - **Reverse-signup now keeps the user on `/signin` so they actually see the inline error.** v1.3.7 fixed `isNewUser` detection, but a follow-on race appeared: when Firebase's `getRedirectResult()` returns a fresh-signup user, the auth-state-change listener in `core/auth.js` fires `state.user = <about-to-be-deleted>` BEFORE `reverseAccidentalSignup`'s `await newUser.delete() → signOut()` chain completes. The listener's `policy === 'unauthenticated'` branch then redirects to `/account` (or `authReturnUrl`), and by the time the inline `showError()` call fires, the user is already off the page. Fixed with a `window.__UJM_REVERSING_SIGNUP` flag set synchronously before the delete + cleared after signOut's followup state-change. The listener checks the flag at the top and short-circuits the entire callback — no redirect, no metadata POST, no consent guard, nothing — until the reversal completes and the user lands on `user = null` with the inline error visible on `/signin`.
391
-
392
- ---
393
- ## [1.3.7] - 2026-05-24
394
-
395
- ### Fixed
396
-
397
- - **Reverse-signup detection on `/signin` was completely broken.** The reverse-signup-on-/signin flow in `src/assets/js/libs/auth.js` (lines 289 and 664) was reading `result.additionalUserInfo?.isNewUser` directly off the `UserCredential` returned by `getRedirectResult()` and `signInWithPopup()`. That property does NOT exist on the v9+ modular SDK — verified against `@firebase/auth`'s `auth-public.d.ts`, which declares `UserCredential` as exactly `{ user, providerId, operationType }`. The legacy compat SDK exposed `additionalUserInfo` as a direct property, hence the v9 migration footgun. On the modular SDK you must call the standalone helper `getAdditionalUserInfo(userCredential): AdditionalUserInfo | null` to access `isNewUser`. Result: `isNewUser` was always `undefined` → always falsy → the `if (isNewUser && !isSignupPage)` reverse gate at line 296 never fired in production. New Google accounts on `/signin` got signed in straight to `/account` with no consent on record, despite the reverse-signup code existing in the bundle since multiple versions ago. Confirmed live on Somiibo (Test 4 of `TODO-CONSENT-LIVETEST.md` failed 3× in a row with `operationType: 'signIn'` and no `[Auth] Reversing accidental signup` log line). Now both sites import `getAdditionalUserInfo` and call it on the result. Added two `console.warn` diagnostic logs (one per site) so future regressions surface immediately — can be removed once we're confident the fix sticks.
398
-
399
- ---
400
- ## [1.3.6] - 2026-05-24
401
-
402
- ### Fixed
403
-
404
- - **`auth/error-code:-47` now shows a friendly message instead of the raw FirebaseError.** v1.3.5's diagnostic confirmed: on the OAuth redirect path (`signInWithIdp` → 503), Firebase strips the BEM-side `HttpsError` message and delivers `code: 'auth/error-code:-47'` with `customData: {}` — empty. There's nothing to extract because Firebase ate the message client-side. This contradicts Firebase's own [Identity Platform docs](https://cloud.google.com/identity-platform/docs/blocking-functions) which describe a `BLOCKING_FUNCTION_ERROR_RESPONSE` wrapper that SHOULD carry the original message. The wrapper works on the 400 path (email signup, OAuth popup) — our v1.3.4 extractor handles that fine. The 503 path is broken: tracked at [firebase-js-sdk#8054](https://github.com/firebase/firebase-js-sdk/issues/8054), where a Firebase engineer said "503 seems to be the working as design error codes" then the issue was auto-closed as stale 5 weeks later without a fix or workaround. The `-47` code is 1:1 with "blocking-function rejected this signup," so `extractBlockingFunctionMessage()` now returns a generic-but-helpful message covering all three BEM `beforeCreate` reasons (rate limit, disposable email, custom hook reject): "Account creation is temporarily restricted. This can happen if you've recently created too many accounts, or your email is on our blocked list. Please try again later or contact support." The original `customData.serverResponse` path stays as the primary handler — the `-47` catchall is an additive fallback for when Firebase eats the message.
405
-
406
- ---
407
- ## [1.3.5] - 2026-05-24
408
-
409
- ### Added
410
-
411
- - **Diagnostic logging in `extractBlockingFunctionMessage()` (`src/assets/js/libs/auth.js`).** When BEM's `beforeCreate` rate limit (2 signups/day/IP) fires via Google OAuth redirect, the user just saw "Firebase: Error (auth/error-code:-47)" instead of the helpful "Unable to create account at this time. Please try again later." message. The 1.3.4 extraction handles the standard 400-with-`BLOCKING_FUNCTION_ERROR_RESPONSE` path, but the 503 path (Google's Identity Toolkit returns 503 directly with code -47, no `customData.serverResponse`) flows through to the generic `auth.code` branch. Added a `console.warn` that dumps the full error shape (code, message, customData, serverResponse) so the next failed signup attempt reveals exactly what Firebase delivers — then we can write a matching handler. Diagnostic ships first; fix follows in a subsequent version.
412
-
413
- ---
414
- ## [1.3.4] - 2026-05-22
415
-
416
- ### Added
417
-
418
- - **Surface BEM blocking-function error messages to users.** Firebase Auth blocking functions (`before-create`, `before-signin`) that throw `HttpsError('resource-exhausted', 'Too many signups...')` get wrapped by Firebase as the opaque `auth/internal-error` (sometimes `auth/error-code:-47`). The actual BEM-side message is buried in `error.customData.serverResponse` inside a `BLOCKING_FUNCTION_ERROR_RESPONSE : ((error : (message : "...")))` wrapper. New `extractBlockingFunctionMessage(error)` helper in `src/assets/js/libs/auth.js` unwraps it. Wired into all 4 auth error sites (OAuth popup, OAuth redirect, email signup, email signin) so users now see "Too many signups from your IP, please try again later" instead of "Firebase: Error (auth/error-code:-47)."
419
-
420
- ---
421
- ## [1.3.3] - 2026-05-21
422
-
423
- ### Changed
424
-
425
- - **Reordered account page sections** in `src/defaults/dist/_layouts/themes/classy/frontend/pages/account/index.html`. Sidebar nav and section blocks now run Profile → Security → Subscription → Billing → Referrals → Team → Notifications → API Keys, pushing the less-frequently-used Team and Notifications sections below Referrals. Section content unchanged.
426
-
427
- ---
428
- ## [1.3.2] - 2026-05-22
429
-
430
- ### Fixed
431
-
432
- - **Consent guard was running before `sendUserSignupMetadata`, killing every fresh signup.** `src/assets/js/core/auth.js`: on a brand-new signup the user doc exists with `consent.legal.status: 'revoked'` (the schema default written by BEM's `on-create` event); `sendUserSignupMetadata` is what flips it to `'granted'`. Previously the guard ran first and signed the user out before the metadata POST could fire — orphaned every new account. Reordered to run `sendUserSignupMetadata` first, and the guard now skips accounts younger than `SIGNUP_MAX_AGE` (5min) so a transient network error during the POST doesn't lock a user out forever — they can retry. After the grace window, the guard fires normally.
433
-
434
- ---
435
- ## [1.3.1] - 2026-05-21
436
-
437
- ### Changed
438
-
439
- - **`ENFORCE_CONSENT_GUARD` flipped to `true`** in `src/assets/js/core/auth.js`. The page-load consent guard now silently signs out any authenticated user whose doc has `consent.legal.status !== 'granted'`. Caveat: any pre-consent-system user doc (missing the field, or defaulted to `'revoked'`) will be signed out on page load — run the legacy-user migration first, or live-test against fresh signups.
440
-
441
- ---
442
- ## [1.3.0] - 2026-05-21
443
-
444
- ### Added
445
-
446
- - **Marketing consent capture on the signup form.** Frontend half of `backend-manager` v5.2.0's consent system. `src/defaults/dist/_layouts/themes/classy/frontend/pages/auth/signup.html` replaces the legal-copy line with two real checkboxes (`consent-legal`, `consent-marketing`) wrapped in a `#consent-group` so validation can highlight the pair as a unit. `consent-legal` is required to submit.
447
- - **`captureSignupConsent()` + `validateConsent()` in `src/assets/js/libs/auth.js`.** Pulls checkbox state + label text from the FormManager-collected data and writes it to `webManager.storage()` under key `consent` BEFORE Firebase auth fires — survives the post-signup redirect the same way `attribution` does. `validateConsent()` blocks submit via a phantom `__consent` field name and surfaces feedback via the wrapper outline + an inline error message instead of red-X-ing the single legal checkbox.
448
- - **`reverseAccidentalSignup()` for the Google quirk.** Landing on `/signin` with an unknown Google account auto-creates the Firebase auth user; this reverses that path — deletes the user, signs out, strips `authReturnUrl`, and surfaces an inline form error. Best-effort delete; the page-load consent guard (below, currently OFF) is the backstop if delete fails.
449
- - **`ENFORCE_CONSENT_GUARD` in `src/assets/js/core/auth.js`.** Page-load guard that silently signs out any authenticated user whose doc has `consent.legal.status !== 'granted'`. Default **FALSE** until the legacy-user migration runs (which sets all existing docs to `granted` + `source: 'imported'`); flipping it on before then would lock every existing user out.
450
- - **`consent` field on the `sendUserSignupMetadata` payload.** Forwards the storage-survived consent blob to BEM's `/user/signup` route so it can write the canonical `consent.{legal,marketing}` sub-tree on the new user doc.
451
- - **Marketing-emails toggle on the account page.** `src/defaults/dist/_layouts/themes/classy/frontend/pages/account/index.html` + `src/assets/js/pages/account/sections/notifications.js` reworked to read `account.consent.marketing.status` (not the old `preferences.notifications.marketing`) and POST to `/backend-manager/marketing/email-preferences` on change. Shows the original grant date below the toggle.
452
-
453
- ### Changed
454
-
455
- - **`web-manager` bumped to `^4.2.0`** (was `file:../web-manager` from local dev). Locks in `DEFAULT_ACCOUNT.consent.{legal,marketing}` so `resolveAccount()` always returns a defined consent shape for legacy users.
456
- - Minor template touchups on `oauth2.html`, `reset.html`, `signin.html`, `token.html`, `signup.html` — heading casing, `filter-adaptive` class on the brandmark logo so it inverts in dark mode.
457
-
458
- ### Fixed
459
-
460
- - **`_team` seed authors** — corrected LinkedIn/Twitter handles in `christina-hill.md`, `james-oconnor.md`, `marcus-johnson.md`, `priya-sharma.md`, `sarah-rodriguez.md` so the default scaffolded `/team/` page links don't 404.
461
-
462
- ---
463
- ## [1.2.3] - 2026-05-19
464
-
465
- ### Added
466
-
467
- - **Markdown table styles in blog posts** — `article .blog-post-content table` in `src/assets/css/pages/blog/post.scss` now renders any plain `| col | col |` markdown table with a rounded outer border, uppercase letter-spaced thead on a tinted background, zebra-striped tbody rows, hover highlight, and tighter padding/font-size on mobile (≤575.98px).
468
-
469
- ### Changed
470
-
471
- - **Tidied heading-margin rule** in `src/assets/css/pages/blog/post.scss` — collapsed `h1..h6` onto one selector and removed the commented-out `:first-child` reset.
472
-
473
- ---
474
- ## [1.2.2] - 2026-05-18
475
-
476
- ### Added
477
-
478
- - **`docs/<topic>.md` deep references** — 17 new files referenced by the v1.2.0 CLAUDE.md reorg that hadn't been committed yet: `ads.md`, `analytics.md`, `appearance.md`, `assets.md`, `audit.md`, `css.md`, `icons.md`, `images.md`, `javascript-libraries.md`, `jekyll-plugin.md`, `layouts-and-pages.md`, `lazy-loading.md`, `local-development.md`, `page-loading.md`, `directory-structure.md`, `seo.md`, `xss-prevention.md`. Restores parity with the cross-links already shipped in [CLAUDE.md](CLAUDE.md).
479
- - **`src/defaults/docs/README.md`, `src/defaults/test/README.md`, `src/defaults/CHANGELOG.md`** — consumer-project scaffolding files distributed via the `defaults` gulp task.
480
-
481
- ---
482
- ## [1.2.1] - 2026-05-18
483
-
484
- ### Changed
485
-
486
- - **Default `/extension` page** — removed the redundant downloads-section heading block ("Install" badge + "Available on every browser" headline + "Choose your browser below to get started" subheadline) that duplicated the role of the page hero. Browser-selector pills now sit directly under the hero. Affects `src/defaults/dist/_layouts/themes/classy/frontend/pages/extension/index.html` and drops the now-unused `downloads.superheadline` / `downloads.headline` / `downloads.headline_accent` / `downloads.subheadline` frontmatter keys.
487
-
488
- ---
489
- ## [1.2.0] - 2026-05-12
490
-
491
- ### Added
492
-
493
- - **Three-layer test framework** (`build` / `page` / `boot`, 60 framework tests passing in ~3s). New under `src/test/`: `assert.js` (Jest-compatible matcher set), `runner.js` (discovery + dispatch + reporter), `index.js` (public API), `runners/{chromium,boot}.js` (Puppeteer launchers with a zero-dep embedded HTTP server in `server.js` for serving `_site/` to Chromium — required because service workers can't register from `file://`), `harness/page/index.html` (stub harness page), `fixtures/consumer-site/` (minimal hand-built `_site/` for framework boot tests). Consumer-test discovery uses the `isFrameworkSelfTest` package-name check to scope framework `boot/` suites to UJM's own runs.
494
- - **`test` CLI command + `--test` alias** (`src/commands/test.js`) — avoids `-t` collision with the existing `translation` command. Sets `UJ_TEST_MODE=true` + auto-routes `UJ_TEST_BOOT_PROJECT` to the fixture when UJM tests itself. `"test": "node ./bin/ultimate-jekyll test"` added to scripts, `"test": "npx mgr test"` added to projectScripts.
495
- - **`src/utils/mode-helpers.js`** — `attachTo(Manager)` mixin exposing `isTesting`/`isDevelopment`/`isProduction`/`getVersion`. Wired into `src/build.js` (CJS, build-time Manager) and `src/index.js` (ESM, frontend Manager). Driven by `UJ_TEST_MODE` env in Node + `globalThis.UJ_TEST_MODE` in browser contexts.
496
- - **`puppeteer` devDep** — peer-optional for consumers (only needed if they write `page`/`boot` tests; `build` layer needs nothing extra).
497
- - **New `docs/<topic>.md` deep references** — `docs/test-framework.md`, `docs/test-boot-layer.md`, `docs/cross-context-helpers.md`. Plus `docs/_legacy-claude-md.md` as a holding pen for the previous 1832-line CLAUDE.md content awaiting future per-subsystem split.
498
- - **Consumer-shipped `src/defaults/CLAUDE.md`** with `# ========== Default Values ==========` / `# ========== Custom Values ==========` markers. Framework section stays live-synced across `npx mgr setup` while the Custom section is preserved verbatim — same merge protocol as `.env`/`.gitignore`.
499
- - **`'CLAUDE.md'` FILE_MAP rule** (`src/gulp/tasks/defaults.js`) with `mergeLines: true` — positioned after the `'**/*.md'` catch-all so the last-match-wins logic in `getFileOptions` activates the merge path.
500
-
501
- ### Changed
502
-
503
- - **CLAUDE.md reorganized from 1832 to 195 lines** as a TOC hub with one-paragraph-per-subsystem + cross-links. Legacy content stashed in `docs/_legacy-claude-md.md` as a migration source.
504
- - **README.md updated** with a Testing section (build/page/boot layer overview + example test files) and a Sister Projects callout.
505
-
506
- ### Fixed
507
-
508
- - **`mergeLineBasedFiles` idempotency bug** — the inline merge function unconditionally inserted a blank line before `CUSTOM_SECTION_MARKER`, causing first-merge after a fresh `jetpack.copy` to grow the file by one newline. Now skips the insert if `mergedDefaultSection` already ends blank. Affects `.env`/`.gitignore`/`CLAUDE.md` equally — first-merge is now a true no-op.
509
-
510
- ---
511
- ## [1.1.10] - 2026-05-10
512
- ### Removed
513
- - `through2` dependency. Replaced with native `node:stream` `Transform` across 6 gulp task files (`defaults.js`, `distribute.js`, `jsonToHtml.js`, `minifyHtml.js`, `sass.js`, `utils/template-transform.js`). through2@5 became ESM-only with no `require` condition in its exports, breaking CJS require; the built-in `Transform` is a drop-in replacement
514
-
515
- ### Changed
516
- - Bumped `@babel/preset-env` from ^7.29.2 to ^7.29.5
517
- - Bumped `dompurify` from ^3.3.3 to ^3.4.2
518
- - Bumped `dotenv` from ^17.4.1 to ^17.4.2
519
- - Bumped `fast-xml-parser` from ^5.5.11 to ^5.7.3
520
- - Bumped `gulp-filter` from ^9.0.1 to ^10.0.0 (Node 22 ESM-CJS interop keeps `require('gulp-filter').default` working)
521
- - Bumped `html-validate` from ^10.11.3 to ^10.16.0
522
- - Bumped `libsodium-wrappers` from ^0.8.3 to ^0.8.4
523
- - Bumped `postcss` from ^8.5.9 to ^8.5.14
524
- - Bumped `prettier` from ^3.8.2 to ^3.8.3
525
- - Bumped `web-manager` from ^4.1.40 to ^4.1.41
526
- - Bumped `webpack` from ^5.106.1 to ^5.106.2
527
-
528
- ---
529
- ## [1.1.9] - 2026-04-23
530
- ### Added
531
- - Admin users page: "Sign in as user" dropdown option that calls BEM `POST /backend-manager/user/token` to generate a custom auth token, then shows a modal with the sign-in URL (copy button + open-in-new-tab button)
532
- - Modal opens immediately in a loading state while the token is generated, then swaps to ready/error state
533
- - Auth signin page: handle `authCustomToken` URL param via Firebase `signInWithCustomToken`, redirecting to `authReturnUrl` (validated) or `/dashboard`
534
-
535
- ### Fixed
536
- - Billing section: cancel subscription button now appears for suspended paid subscriptions (previously hidden). Logic updated to `isPaid && rawStatus !== 'cancelled' && !resolved.cancelling` so it correctly shows for active, trialing, and suspended paid subs, while hiding for free users, already-cancelled subs, and subs with pending cancellation
537
-
538
- ### Changed
539
- - Admin users table: dropdown trigger button restyled using `btn-outline-adaptive rounded-circle` for a cleaner look
540
-
541
- ---
542
- ## [1.1.8] - 2026-04-22
543
- ### Changed
544
- - Widen backend sidebar from 282px to 283px so inner content (after `p-3` horizontal padding) clears the 250px minimum required by Google AdSense units
545
- - Apply same 283px width to mobile offcanvas sidebar (`#mobileSidebar`) via `--bs-offcanvas-width` to override Bootstrap's default 400px
546
- - Simplify admin firebase page cell rendering: drop redundant `String()` wrapping around values passed to `escapeHTML()` (already coerces to string internally)
547
-
548
- ---
549
- ## [1.1.7] - 2026-04-10
550
- ### Changed
551
- - Update dependencies: web-manager to 4.1.39, webpack to 5.106.1, prettier to 3.8.2, libsodium-wrappers to 0.8.3, prepare-package to 2.1.0
552
- - Add empty `hooks` object to `preparePackage` config in package.json for prepare-package 2.1.0's new hooks feature
553
-
554
- ---
555
- ## [1.1.6] - 2026-04-09
556
- ### Changed
557
- - Add `hover-flex` prebuilt animation class to pricing page billing cycle toggle (Monthly/Annually) for subtle scale-up on hover
558
- - Update README and TODO docs to use `npx mgr` instead of `npx uj`
559
- - Fix `[Billing] Cancel complete` log to read product ID from current account instead of undefined variable
560
-
561
- ---
562
- ## [1.1.5] - 2026-04-09
563
- ### Changed
564
- - Move pricing and feature limit values from layout frontmatter to default `_config.yml` under `web_manager.payment.products`, making the pricing page fully config-driven
565
- - Add default `payment.products` array with 4 example plans (basic, plus, pro, max) including limits, prices, and trial config
566
- - Handle boolean `true` config limits in feature value display (renders feature name only, check icon in comparison table)
567
-
568
- ---
569
- ## [1.1.4] - 2026-04-09
570
- ### Changed
571
- - Update web-manager from v4.1.37 to v4.1.38
572
-
573
- ---
574
- ## [1.1.3] - 2026-04-08
575
- ### Security
576
- - Escape all remaining unescaped innerHTML values (formatDate, formatDateTime, formatIncidentStatus, formatTimeAgo, statusLabels, dataStatusMap, numeric values) for defense-in-depth
577
- - Add `https://` scheme validation to `window.open()` and `href` attributes for push notification URLs in calendar-events
578
- - Remove `style` from DOMPurify `ALLOWED_ATTR` in campaign email preview to prevent CSS-based data exfiltration
579
-
580
- ---
581
- ## [1.1.2] - 2026-04-08
582
- ### Fixed
583
- - Fix AdSense minimum width error in dashboard sidebar by increasing sidebar width from 280px to 282px (content area now meets 250px minimum)
584
-
585
- ### Changed
586
- - Update dependencies: fast-xml-parser, postcss, webpack, wonderful-fetch, prepare-package
587
-
588
- ---
589
- ## [1.1.1] - 2026-04-06
590
- ### Security
591
- - Fix open redirect via `authReturnUrl` URL parameter in core/auth.js — now validated with `isValidRedirectUrl()`
592
- - Fix cross-origin redirect via unvalidated postMessage in vert.js — added origin allowlist
593
- - Replace `new Function()` code execution in redirect.js with safe named modifier lookup
594
- - Sanitize markdown-it output with DOMPurify in campaign-preview.js (newsletter-safe tag allowlist)
595
- - Validate OAuth redirect URL scheme in connections.js
596
- - Escape `classes` parameter in prerendered-icons.js to prevent attribute breakout
597
- - Defense-in-depth: escape `formatDate()` outputs in security.js, team.js, referrals.js
598
- - Defense-in-depth: escape cancel/refund reason strings in billing.js, refund.js
599
- - Defense-in-depth: escape `submittingText` in form-manager.js spinner
600
- - Document redirect validation, postMessage origin checks, eval prohibition, and DOMPurify rules in CLAUDE.md
601
-
602
- ### Added
603
- - `dompurify` dependency for HTML sanitization
604
-
605
- ## [1.1.0] - 2026-04-06
606
- ### Added
607
- - `payment-config.js` shared library for reading payment data from build-time config
608
- - Pricing layout resolves prices and feature limits from `_config.yml` when not set in frontmatter
609
- - `oauth2` config injected into client-side Configuration object via `foot.html`
610
- - Pricing page shows "Switch to This Plan" on other paid plans when user has active subscription
611
-
612
- ### Changed
613
- - Move `payment` under `web_manager` in default `_config.yml` so it serializes into client-side config
614
- - Checkout page uses `payment-config.js` instead of fetching `/backend-manager/brand`
615
- - Account billing section uses config for products/limits/currency instead of brand API
616
- - Account connections section reads `oauth2` from config instead of brand API
617
- - Admin dashboard uses config for product list in MRR calculations
618
- - Remove `/backend-manager/brand` fetch from account page entirely
619
- - "Everything in [plan]" now uses dynamic previous plan name instead of hardcoded index
620
-
621
- ### Fixed
622
- - Liquid 4.x compatibility: use loop-based hash lookup instead of bracket notation for config limits
623
-
624
- ## [1.0.22] - 2026-04-05
625
- ### Changed
626
- - Bump web-manager from ^4.1.36 to ^4.1.37
627
- - Bump dotenv from ^17.4.0 to ^17.4.1
628
- - Bump html-validate from ^10.11.2 to ^10.11.3
629
-
630
- ## [1.0.21] - 2026-04-03
631
- ### Fixed
632
- - Disable cache breaker on Slapform contact form fetch to prevent appending cache-busting query params to POST request
633
-
634
- ## [1.0.20] - 2026-04-03
635
- ### Fixed
636
- - Fix contact form sending `user: map[]` to Slapform by replacing nested `user` object with flat `uid` string field
637
- - Autofill visible email input from auth state via `data-wm-bind="@value auth.user.email"` for logged-in users
638
- - Remove redundant hidden `auth.user.email` field
639
-
640
- ## [1.0.19] - 2026-04-02
641
- ### Security
642
- - Comprehensive XSS hardening: escape all dynamic data in innerHTML with `webManager.utilities().escapeHTML()`
643
- - Remove all local `escapeHtml` implementations — single source of truth via web-manager
644
- - Rebuild `showToast()` and `showNotification()` to use `textContent` instead of `innerHTML`
645
- - Add `javascript:` protocol blocking in web-manager `@attr` binding directive
646
- - Add URL scheme validation for vert.js postMessage handler
647
- - Fix double-escaping in `showSuccess()`/`showError()`/`showNotification()` callers
648
- - Document zero-trust XSS policy in CLAUDE.md and skills
649
-
650
- ### Changed
651
- - Refactor webManager from passed parameter to direct singleton import across all modules
652
- - Remove `init(wm)` pattern and Manager parameter passing throughout page modules
653
- - Calendar core/events/renderer use direct imports instead of constructor injection
654
- - Fix file structure and spacing across all JS files (consistent Libraries/Module pattern)
655
- - Fix alternatives layout markdown code block rendering issue
656
-
657
- ---
658
- ## [1.0.18] - 2026-03-30
659
- ### Changed
660
- - Removed redundant "Additional gems" comment from Gemfile template output in defaults.js
661
-
662
- ---
663
- ## [1.0.17] - 2026-03-30
664
- ### Added
665
- - Configurable gems support via `gems` array in `config/ultimate-jekyll-manager.json`
666
- - Function-based template data in defaults.js for runtime-computed values
667
-
668
- ---
669
- ## [1.0.16] - 2026-03-30
670
- ### Changed
671
- - Removed @dev-only wrappers from page module loading console.log statements in src/index.js
672
-
673
- ---
674
- ## [1.0.15] - 2026-03-30
675
- ### Changed
676
- - Bump web-manager from 4.1.32 to 4.1.33 (includes @sentry/* 10.46.0, chatsy 2.0.13)
677
-
678
- ---
679
- ## [1.0.13] - 2026-03-27
680
- ### Added
681
- - MRR stat card on admin dashboard calculated from brand config prices × subscriber counts
682
- - `setStatSubValue` helper in admin-helpers.js for displaying sub-metrics on stat cards
683
- - Green "+N in 30d" sub-values under Total Users and Push Subscribers stat cards
684
- - New "Active users (30d)" stat card on admin users page
685
-
686
- ### Changed
687
- - Dashboard charts now use `getCountFromServer` queries per product × frequency instead of fetching all user docs
688
- - Product list and billing frequencies derived dynamically from `/backend-manager/brand` API
689
- - Consolidated "New users (30d)" from standalone card into sub-value under Total Users
690
-
691
- ### Fixed
692
- - Pacman-shaped spinners in stat cards caused by `spinner-border-sm` inheriting `<h3>` font size (added `fs-6`)
693
-
694
- ### Removed
695
- - `showUnauthenticated()` flows from all admin pages — pages now return early if no user
696
-
697
- ## [1.0.11] - 2026-03-24
698
- ### Added
699
- - Firestore version + transport test page at `/test/libraries/firestore` for diagnosing SDK connectivity across browsers
700
-
701
- ## [1.0.10] - 2026-03-24
702
- ### Fixed
703
- - `getUJMConfig()` now throws descriptive errors when config file is missing, empty, or malformed instead of crashing silently
704
- - Admin dashboard subscription queries now filter by `subscription.status == 'active'` instead of expiry timestamp
705
-
706
- ### Changed
707
- - Webpack watch path for web-manager changed from `src/` to `dist/`
708
-
709
- ## [1.0.9] - 2026-03-20
710
- ### Changed
711
- - `authorizedFetch` no longer throws when no user is logged in; logs a warning and proceeds without the Authorization header
712
-
713
- ## [1.0.7] - 2026-03-20
714
- ### Changed
715
- - Upgrade `web-manager` from ^4.1.29 to ^4.1.30
716
-
717
- ## [1.0.3] - 2026-03-16
718
- ### Added
719
- - Ensure consuming projects have `"private": true` in package.json during setup to prevent accidental npm publishes
720
-
721
- ## [1.0.1] - 2026-03-15
722
- ### Changed
723
- - Upgrade `node-powertools` from ^2.3.2 to ^3.0.0
724
- - Upgrade `web-manager` from ^4.1.26 to ^4.1.28
725
- - Upgrade `wonderful-fetch` from ^1.3.4 to ^2.0.4
726
- - Upgrade `prepare-package` from ^1.2.6 to ^2.0.7
727
- - Add `preparePackage.type: "copy"` configuration
728
-
729
- ## [Unreleased]
730
- ### Changed
731
- - Migrate "app" terminology to "brand" across frontend and service worker: renamed `appData`/`fetchAppData` to `brandData`/`fetchBrandData`, `appConfig`/`fetchAppConfig` to `brandConfig`/`fetchBrandConfig`, API endpoint from `/backend-manager/app` to `/backend-manager/brand`, and `this.app` to `this.brand` in service worker
732
-
733
- ### Added
734
- - Abandoned cart tracking on checkout page: creates a Firestore document in `payments-carts/{uid}` when authenticated users begin checkout, with a 15-minute first reminder delay
735
- - Backend sidebar auto-expands collapsible dropdown sections containing the currently active page link (desktop and mobile)
736
- - Email preferences page (`/portal/account/email-preferences`) for unsubscribe/resubscribe from marketing emails
737
- - Email masking on preferences page to prevent forwarded-email abuse (e.g., `ia***b@gm***.com`)
738
- - HMAC signature verification for unsubscribe links to prevent forged requests
739
- - Checkout page supports daily, weekly, monthly, and annually billing frequencies with selective UI visibility via wm-bindings
740
- - Default billing frequency auto-selects the longest available term (annually > monthly > weekly > daily), with URL param override
741
- - Auth state settles before any authorized fetches fire on checkout, preventing race conditions
742
- - Quick boot mode (`UJ_QUICK=true`) for faster dev server startup (~5s vs ~20s) by skipping clean, slow setup operations, and deferring webpack/sass compilation until after Jekyll's first build
743
- - Dev-only warning in FormManager for form fields missing `name` attributes (skipped by validation and `getData()`)
744
- - FAQPage JSON-LD schema with 3-level fallback chain (`schema.faq_page.items` → `faqs.items` → `alternative.faqs.items`)
745
- - FAQPage schema enabled on blueprint pages with FAQ sections (pricing, contact, download, extension, alternatives)
746
- - OG image dimension meta tags (`og:image:width`, `og:image:height`) with 1200×630 defaults
747
- - Article published/modified time meta tags for blog posts
748
- - Admin marketing calendar page (`/admin/calendar`) with custom-built interactive calendar for scheduling newsletters and notifications
749
- - Calendar supports 4 view modes (month, week, day, year) with event CRUD, drag-and-drop, overlapping event layout, and `window.calendarAPI`
750
- - Real-time red "now" line indicator in day/week views, updates every 60 seconds
751
- - Viewport-locked admin layout variant (`themes/classy/admin/core/minimal-viewport-locked`) for full-height admin pages
752
- - Feedback page (`/feedback`) with emoji rating selection, written feedback fields, review prompt modal, and analytics tracking
753
- - FormManager auto-populates form fields from URL query parameters (skips utm_*, itm_*, cb, fbclid, gclid)
754
- - Review prompt modal after positive feedback submission with copy-paste textarea and external review site link
755
-
756
- ### Changed
757
- - Twitter card default from `summary` to configurable `summary_large_image`
758
- - Rename `site.tracking` config to `site.analytics` with simplified keys (`google-analytics` → `google`, `meta-pixel` → `meta`, `tiktok-pixel` → `tiktok`)
759
- - Update `webManager.config.tracking['meta-pixel']` to `webManager.config.analytics?.meta` in auth.js
760
- - Replace hardcoded discount codes with server-side validation via `payments/discount` API endpoint
761
- - Simplify payment intent payload: remove `auth`, `cancelUrl`, and `verification.status` fields; send `discountCode` from validated state
762
- - Form submit falls back to first visible payment button when Enter is pressed instead of throwing
763
- - Clear FormManager dirty state before redirect to avoid "leave site" prompt
764
- - Use proper adjective forms in subscription terms text (e.g., "annual" instead of "annually")
765
- - Add discount disclaimer to subscription terms when a discount code is applied
766
- - Align billing section to backend SSOT: consume unified subscription structure directly (3 statuses, `product.id` as object, `payment.price` in dollars, `cancellation.pending`, `trial.claimed` + `trial.expires`)
767
- - Use WM bindings (`data-wm-bind`) for billing plan heading, action button visibility, and cancel trigger instead of manual JS DOM manipulation
768
- - Standardize cancel, delete, and data-request forms to use FormManager built-in `required` validation instead of manual disabled toggle and checkbox throws
769
- - Test subscriptions now deep-merge into real user data instead of full replacement, preserving actual product/payment info
770
- - Add `onsubmit="return false"` to all JS-managed forms as a safety net against native submission before FormManager loads
771
- - Checkout payment method buttons start hidden and are revealed via `data-wm-bind` when payment methods load
772
- - Remove development-only guard from click prevention logging in body.html
773
-
774
- ### Removed
775
- - Remove hardcoded `DISCOUNT_CODES` map and `autoApplyWelcomeCoupon` (replaced by server-side validation)
776
- - Remove `generateCheckoutId` and `state.checkoutId` from checkout session
777
- - Unexport `resolvePrice` helper (internal-only usage)
778
-
779
- ### Fixed
780
- - Fix broken `</>` tag in checkout HTML causing page rendering to break
781
- - Fix checkout price display for APIs returning plain numbers instead of `{amount: N}` objects
782
- - Fix quantity badge styling (proper circle instead of pill shape)
783
- - Fix form checkboxes missing `name` attributes causing FormManager to silently skip validation (cancel, delete forms)
784
- - Fix admin forms (notifications, users) and blog/status forms missing `novalidate`, `onsubmit`, `name` attributes, and `.button-text` spans
785
- - Fix profile premium badge using removed `trialing` status and `access` field
786
- - Add dev-only artificial pre-delay support to checkout page for testing form protection timing
787
- - Fix `btn-check:checked` outline button styling in classy theme — transparent `!important` rule was overriding Bootstrap's checked background due to higher CSS specificity
788
-
789
- ---
790
- ## [1.0.0] - 2024-06-19
791
- ### Added
792
- - Initial release of the project 🚀