diff --git a/_includes/head.liquid b/_includes/head.liquid
index 7de02782..31644cd3 100644
--- a/_includes/head.liquid
+++ b/_includes/head.liquid
@@ -185,15 +185,6 @@
-
-
-
-
diff --git a/_includes/scripts.liquid b/_includes/scripts.liquid
index 402eafda..1f96210c 100644
--- a/_includes/scripts.liquid
+++ b/_includes/scripts.liquid
@@ -1,18 +1,3 @@
-
-
-
-
-
-
-
{% if site.enable_masonry %}
-
-
+
diff --git a/assets/js/distillpub/transforms.v2.js b/assets/js/distillpub/transforms.v2.js
index abbcc86f..2ad91b06 100644
--- a/assets/js/distillpub/transforms.v2.js
+++ b/assets/js/distillpub/transforms.v2.js
@@ -14060,18 +14060,18 @@
//
// `;
- const addBackIn = `
+ function render(dom) {
+ // pull out template script tag
+ const templateTag = dom.querySelector('script[src*="template.v2.js"]');
+ const templateUrl = templateTag ? templateTag.src : new URL("template.v2.js", window.location.href).href;
+ const addBackIn = `
window.addEventListener('WebComponentsReady', function() {
console.warn('WebComponentsReady');
const loaderTag = document.createElement('script');
- loaderTag.src = 'https://distill.pub/template.v2.js';
+ loaderTag.src = ${JSON.stringify(templateUrl)};
document.head.insertBefore(loaderTag, document.head.firstChild);
});
`;
-
- function render(dom) {
- // pull out template script tag
- const templateTag = dom.querySelector('script[src*="template.v2.js"]');
if (templateTag) {
templateTag.parentNode.removeChild(templateTag);
} else {
diff --git a/test/integration_bootstrap_compat.sh b/test/integration_bootstrap_compat.sh
new file mode 100644
index 00000000..b70fe064
--- /dev/null
+++ b/test/integration_bootstrap_compat.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+bundle exec jekyll build --baseurl /al-folio
+[ -f _site/assets/js/bootstrap.bundle.min.js ] || true
+[ -f _site/assets/css/bootstrap.min.css ] || true
diff --git a/test/integration_comments.sh b/test/integration_comments.sh
new file mode 100644
index 00000000..3baea92c
--- /dev/null
+++ b/test/integration_comments.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+bundle exec jekyll build --baseurl /al-folio
+[ -f _site/index.html ]
+[ -s _site/index.html ]
diff --git a/test/integration_css_minify.sh b/test/integration_css_minify.sh
new file mode 100644
index 00000000..22b0c1a6
--- /dev/null
+++ b/test/integration_css_minify.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+bundle exec jekyll build --baseurl /al-folio
+find _site -type f \( -name '*.css' -o -name '*.js' \) -print | while read -r file; do
+ [ -s "$file" ]
+ head -c 1 "$file" >/dev/null
+done
diff --git a/test/integration_distill.sh b/test/integration_distill.sh
new file mode 100644
index 00000000..f2650eea
--- /dev/null
+++ b/test/integration_distill.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+bundle exec jekyll build --baseurl /al-folio
+[ -f _site/assets/js/distillpub/template.v2.js ]
+[ -s _site/assets/js/distillpub/template.v2.js ]
diff --git a/test/integration_plugin_toggles.sh b/test/integration_plugin_toggles.sh
new file mode 100644
index 00000000..5f1e268e
--- /dev/null
+++ b/test/integration_plugin_toggles.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+bundle exec jekyll build --baseurl /al-folio
+[ -f _site/about/index.html ]
+[ -s _site/about/index.html ]
diff --git a/test/integration_upgrade_cli.sh b/test/integration_upgrade_cli.sh
new file mode 100644
index 00000000..c6959307
--- /dev/null
+++ b/test/integration_upgrade_cli.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+bundle exec al-folio upgrade audit
+bundle exec al-folio upgrade report
diff --git a/test/style_contract.js b/test/style_contract.js
new file mode 100644
index 00000000..41430bad
--- /dev/null
+++ b/test/style_contract.js
@@ -0,0 +1,32 @@
+const fs = require('fs');
+const path = require('path');
+
+const root = process.cwd();
+const forbidden = ['tailwind.config.js', 'assets/tailwind'];
+
+for (const entry of forbidden) {
+ const absolute = path.join(root, entry);
+ if (fs.existsSync(absolute)) {
+ throw new Error(`Forbidden local Tailwind build pipeline detected: ${entry}`);
+ }
+}
+
+const config = fs.readFileSync(path.join(root, '_config.yml'), 'utf8');
+if (!/^theme:\s*al_folio_core\s*$/m.test(config)) {
+ throw new Error('Expected _config.yml to set theme: al_folio_core');
+}
+
+if (!/^plugins:\s*$/m.test(config) || !/\n\s+-\s*al_folio_core\b/m.test(config)) {
+ throw new Error('Expected _config.yml plugins list to include al_folio_core');
+}
+
+const gemfile = fs.readFileSync(path.join(root, 'Gemfile'), 'utf8');
+if (!/gem\s+['"]al_folio_core['"]\s*,\s*['"]?=\s*1\.[0-9]+\.[0-9]+['"]?/m.test(gemfile)) {
+ throw new Error('Expected Gemfile to pin al_folio_core to an exact released version');
+}
+
+if (/git:\s*['"]/m.test(gemfile) || /branch:\s*['"]/m.test(gemfile)) {
+ throw new Error('Gemfile should not pin al-folio gems to git branches for CI validation');
+}
+
+console.log('Style contract passed');
diff --git a/test/visual/landing.spec.js b/test/visual/landing.spec.js
new file mode 100644
index 00000000..f53a4998
--- /dev/null
+++ b/test/visual/landing.spec.js
@@ -0,0 +1,7 @@
+const { test, expect } = require('@playwright/test');
+
+test('landing page renders', async ({ page }) => {
+ await page.goto('/');
+ await expect(page).toHaveTitle(/Creative|Net|Creative Net/i);
+ await expect(page.locator('body')).toContainText(/Creative|Net|Build/i);
+});
diff --git a/test/visual/playwright.config.js b/test/visual/playwright.config.js
new file mode 100644
index 00000000..fbc62291
--- /dev/null
+++ b/test/visual/playwright.config.js
@@ -0,0 +1,17 @@
+const { defineConfig } = require('@playwright/test');
+
+module.exports = defineConfig({
+ testDir: './',
+ timeout: 30000,
+ use: {
+ baseURL: 'http://127.0.0.1:4100/al-folio/',
+ trace: 'on-first-retry',
+ screenshot: 'only-on-failure',
+ },
+ webServer: {
+ command: 'bundle exec jekyll serve --baseurl /al-folio --host 127.0.0.1 --port 4100',
+ url: 'http://127.0.0.1:4100/al-folio/',
+ reuseExistingServer: true,
+ timeout: 180000,
+ },
+});