@allegrocdp/preview 0.1.0-dev.6 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -6,116 +6,11 @@ import { Command } from "commander";
6
6
  // src/server.ts
7
7
  import chokidar from "chokidar";
8
8
  import express from "express";
9
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
9
+ import { existsSync, readdirSync, readFileSync } from "fs";
10
10
  import open from "open";
11
11
  import { basename, isAbsolute, join, relative, resolve } from "path";
12
12
  import { fileURLToPath } from "url";
13
13
  var __dirname = fileURLToPath(new URL(".", import.meta.url));
14
- var BOILERPLATE_HTML = `<div class="signup">
15
- <div class="signup__eyebrow">Newsletter</div>
16
- <h2 class="signup__headline">{% headline %}</h2>
17
- <p class="signup__sub">{% subheadline %}</p>
18
- <form class="signup__form" onsubmit="return false">
19
- <div class="signup__row">
20
- <input class="signup__input" type="email" placeholder="{% input_placeholder %}" />
21
- <button class="signup__btn" type="submit">{% button_text %}</button>
22
- </div>
23
- <p class="signup__note">{% privacy_note %}</p>
24
- </form>
25
- </div>
26
- `;
27
- var BOILERPLATE_CSS = `*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
28
-
29
- .signup {
30
- font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', system-ui, sans-serif;
31
- -webkit-font-smoothing: antialiased;
32
- padding: 16px;
33
- }
34
-
35
- .signup__eyebrow {
36
- font-size: 11px;
37
- font-weight: 700;
38
- letter-spacing: 0.1em;
39
- text-transform: uppercase;
40
- color: #6366f1;
41
- margin-bottom: 8px;
42
- }
43
-
44
- .signup__headline {
45
- font-size: clamp(20px, 4vw, 26px);
46
- font-weight: 800;
47
- color: #0f172a;
48
- line-height: 1.2;
49
- letter-spacing: -0.02em;
50
- margin-bottom: 8px;
51
- }
52
-
53
- .signup__sub {
54
- font-size: 14px;
55
- color: #64748b;
56
- line-height: 1.6;
57
- margin-bottom: 20px;
58
- }
59
-
60
- .signup__row {
61
- display: flex;
62
- gap: 8px;
63
- flex-wrap: wrap;
64
- }
65
-
66
- .signup__input {
67
- flex: 1;
68
- min-width: 180px;
69
- height: 42px;
70
- padding: 0 12px;
71
- border: 1.5px solid #e2e8f0;
72
- border-radius: 8px;
73
- font-size: 14px;
74
- color: #0f172a;
75
- background: #f8fafc;
76
- outline: none;
77
- transition: border-color 0.15s, background 0.15s;
78
- }
79
-
80
- .signup__input:focus { border-color: #6366f1; background: #fff; }
81
- .signup__input::placeholder { color: #94a3b8; }
82
-
83
- .signup__btn {
84
- height: 42px;
85
- padding: 0 20px;
86
- background: #6366f1;
87
- color: #fff;
88
- border: none;
89
- border-radius: 8px;
90
- font-size: 14px;
91
- font-weight: 600;
92
- font-family: inherit;
93
- cursor: pointer;
94
- white-space: nowrap;
95
- transition: background 0.15s;
96
- }
97
-
98
- .signup__btn:hover { background: #4f46e5; }
99
- .signup__btn:active { opacity: 0.9; }
100
-
101
- .signup__note {
102
- margin-top: 10px;
103
- font-size: 12px;
104
- color: #94a3b8;
105
- }
106
- `;
107
- var BOILERPLATE_FIELDS = [
108
- { slug: "headline", label: "Headline", type: "text", default_value: "Stay in the know" },
109
- {
110
- slug: "subheadline",
111
- label: "Subheadline",
112
- type: "textarea",
113
- default_value: "Get the latest stories delivered to your inbox."
114
- },
115
- { slug: "input_placeholder", label: "Input placeholder", type: "text", default_value: "Enter your email" },
116
- { slug: "button_text", label: "Button text", type: "text", default_value: "Subscribe" },
117
- { slug: "privacy_note", label: "Privacy note", type: "text", default_value: "No spam. Unsubscribe anytime." }
118
- ];
119
14
  var sseClients = /* @__PURE__ */ new Set();
120
15
  function broadcast(event) {
121
16
  for (const client of sseClients) {
@@ -228,36 +123,6 @@ async function startServer(templatePath, port = 0, tenant = "") {
228
123
  }
229
124
  res.json(readTemplateFiles(resolvedDir));
230
125
  });
231
- app.post("/api/template/create", express.json(), (req, res) => {
232
- const body = req.body;
233
- const slug = body.slug;
234
- if (typeof slug !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) {
235
- res.status(400).json({
236
- error: "Slug must be lowercase letters, numbers, and hyphens only (e.g. my-template)."
237
- });
238
- return;
239
- }
240
- const targetDir = join(baseDir, slug);
241
- if (existsSync(targetDir)) {
242
- res.status(400).json({ error: `A template named "${slug}" already exists.` });
243
- return;
244
- }
245
- try {
246
- mkdirSync(targetDir);
247
- writeFileSync(join(targetDir, "index.html"), BOILERPLATE_HTML);
248
- writeFileSync(join(targetDir, "index.css"), BOILERPLATE_CSS);
249
- writeFileSync(
250
- join(targetDir, "template.json"),
251
- JSON.stringify({ name: slug, fields: BOILERPLATE_FIELDS }, null, 2) + "\n"
252
- );
253
- } catch (err) {
254
- console.error("[allegro-preview] Failed to create template:", err);
255
- res.status(500).json({ error: "Failed to create template files." });
256
- return;
257
- }
258
- rescan();
259
- res.status(201).json({ slug });
260
- });
261
126
  app.get("/api/events", (req, res) => {
262
127
  res.setHeader("Content-Type", "text/event-stream");
263
128
  res.setHeader("Cache-Control", "no-cache");
@@ -273,9 +138,7 @@ async function startServer(templatePath, port = 0, tenant = "") {
273
138
  if (existsSync(indexHtmlPath)) {
274
139
  res.type("html").send(readFileSync(indexHtmlPath));
275
140
  } else {
276
- res.status(503).send(
277
- 'Preview client build artifacts not found. Ensure the client is built (for example, by running "npm run build" in this package during development).'
278
- );
141
+ res.status(503).send('Preview client build artifacts not found. Ensure the client is built (for example, by running "npm run build" in this package during development).');
279
142
  }
280
143
  });
281
144
  const TEMPLATE_FILES = ["index.html", "index.css", "index.js", "template.json"];
@@ -290,14 +153,7 @@ async function startServer(templatePath, port = 0, tenant = "") {
290
153
  console.log(`Changed: ${changedPath}`);
291
154
  broadcastReload();
292
155
  });
293
- fileWatcher.on("add", (addedPath) => {
294
- console.log(`Added: ${addedPath}`);
295
- if (basename(addedPath) === "index.html") {
296
- rescan();
297
- }
298
- broadcastReload();
299
- });
300
- const dirWatcher = chokidar.watch(baseDir, { depth: 1, ignoreInitial: true });
156
+ const dirWatcher = chokidar.watch(baseDir, { depth: 0, ignoreInitial: true });
301
157
  function rescan() {
302
158
  templates = scanTemplates(baseDir);
303
159
  console.log(`Templates updated: ${templates.map((t) => t.slug).join(", ") || "(none)"}`);
@@ -324,37 +180,23 @@ async function startServer(templatePath, port = 0, tenant = "") {
324
180
  fileWatcher.add(templateFilePaths(join(filePath, "..")));
325
181
  }
326
182
  });
327
- const bindServer = (p) => new Promise((resolve2, reject) => {
328
- const server = app.listen(p);
329
- server.once("listening", () => {
183
+ await new Promise((resolvePromise) => {
184
+ const server = app.listen(port, () => {
330
185
  const addr = server.address();
331
- const assignedPort2 = typeof addr === "object" && addr ? addr.port : p;
332
- resolve2({ server, assignedPort: assignedPort2 });
333
- });
334
- server.once("error", reject);
335
- });
336
- let assignedPort;
337
- try {
338
- ({ assignedPort } = await bindServer(port));
339
- } catch (err) {
340
- const nodeErr = err;
341
- if (nodeErr.code === "EADDRINUSE" && port !== 0) {
342
- console.log(`Port ${port} is in use, using a random available port instead.`);
343
- ({ assignedPort } = await bindServer(0));
344
- } else {
345
- throw err;
346
- }
347
- }
348
- const url = `http://localhost:${assignedPort}`;
349
- console.log(`
186
+ const assignedPort = typeof addr === "object" && addr ? addr.port : port;
187
+ const url = `http://localhost:${assignedPort}`;
188
+ console.log(`
350
189
  allegro-preview \u2192 ${url}
351
190
  `);
352
- void open(url);
191
+ void open(url);
192
+ resolvePromise();
193
+ });
194
+ });
353
195
  }
354
196
 
355
197
  // src/cli.ts
356
198
  var program = new Command();
357
- program.name("allegro-preview").description("Local dev server for previewing Allegro templates").argument("[path]", "Path to the template folder", process.cwd()).option("-p, --port <number>", "Port to listen on (default: 4747, falls back to random if taken)", "4747").option("--tenant <url>", "Tenant base URL to pre-fill (overrides ALLEGRO_TENANT env var)").action(async (templatePath, options) => {
199
+ program.name("allegro-preview").description("Local dev server for previewing Allegro templates").argument("[path]", "Path to the template folder", process.cwd()).option("-p, --port <number>", "Port to listen on (default: random available port)", "0").option("--tenant <url>", "Tenant base URL to pre-fill (overrides ALLEGRO_TENANT env var)").action(async (templatePath, options) => {
358
200
  const port = parseInt(options.port, 10);
359
201
  const tenant = options.tenant ?? process.env.ALLEGRO_TENANT ?? "";
360
202
  await startServer(templatePath, port, tenant);
@@ -0,0 +1,96 @@
1
+ (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))o(i);new MutationObserver(i=>{for(const r of i)if(r.type==="childList")for(const s of r.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&o(s)}).observe(document,{childList:!0,subtree:!0});function n(i){const r={};return i.integrity&&(r.integrity=i.integrity),i.referrerPolicy&&(r.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?r.credentials="include":i.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function o(i){if(i.ep)return;i.ep=!0;const r=n(i);fetch(i.href,r)}})();const q="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js";function V({html:e,css:t,js:n,externalCssUrls:o,interactive:i}){const r=o.map(l=>`<link rel="stylesheet" href="${l}">`).join(`
2
+ `),s="",a=n?n.replace(/<\/script>/gi,"<\\/script>"):"";return`<!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <script>window.__ALLEGRO_PREVIEW__ = true;<\/script>
8
+ <script defer src="${q}"><\/script>
9
+ ${r}
10
+ <style>${s}${t}</style>
11
+ </head>
12
+ <body>${e}${a?`<script>${a}<\/script>`:""}</body>
13
+ </html>`}function $(e,t){let n=e;for(const o of t){const i=new RegExp(`\\{%\\s*${o.slug}\\s*%\\}`,"g");n=n.replace(i,o.value)}return n}const U={full:0,desktop:1280,tablet:768,mobile:390},z=`
14
+ *, *::before, *::after { box-sizing: border-box; }
15
+ * { margin: 0; }
16
+ body { font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; line-height: 1.5; -webkit-font-smoothing: antialiased; }
17
+ img, picture, video, canvas, svg { display: block; max-width: 100%; }
18
+ input, button, textarea, select { font: inherit; }
19
+ p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }
20
+ `,N=["The city council voted unanimously Tuesday to approve a sweeping new infrastructure plan that would reshape public transit routes across the metropolitan area over the next decade.","Mayor Jordan Ellison called the decision a turning point for how the region thinks about getting around, noting that similar investments in peer cities have led to measurable reductions in traffic congestion and improved air quality.","The plan includes upgrades to 14 existing bus rapid transit lines, the introduction of three new light-rail corridors connecting the downtown core to outlying neighborhoods, and dedicated cycling infrastructure along six major arterials.","Critics, however, warn that the projected cost of $2.4 billion may be optimistic. Independent analysts suggest the final figure could climb to $3.8 billion once land acquisition, labor, and contingency costs are factored in over the full construction timeline.","Council member Dana Reyes, who voted in favor, acknowledged the fiscal concern but argued that inaction carries its own price, noting that congestion costs residents and businesses hundreds of millions in lost productivity each year.","Environmental groups praised the announcement, saying expanded transit capacity is essential to meet the stated goal of reducing transportation emissions by 45 percent before 2035.","Public comment sessions are scheduled for next month at community centers in all twelve districts. Construction is expected to begin no sooner than mid-next year, pending environmental review and final design approvals."];function j(e){return e?`<script src="${e}"><\/script>`:""}function H(e,t,n,o){const i=JSON.stringify(e),r=JSON.stringify(t),s=JSON.stringify(n);return`(function() {
21
+ var html = ${i};
22
+ var css = ${r};
23
+ var js = ${s};
24
+
25
+ var host = document.createElement('div');
26
+ host.setAttribute('data-allegro-preview', '');
27
+ var shadow = host.attachShadow({ mode: 'open' });
28
+
29
+ if (css) {
30
+ var style = document.createElement('style');
31
+ style.textContent = css;
32
+ shadow.appendChild(style);
33
+ }
34
+
35
+ var container = document.createElement('div');
36
+ container.innerHTML = html;
37
+ shadow.appendChild(container);
38
+
39
+ if (js) {
40
+ try { (new Function('shadowRoot', 'hostElement', js))(shadow, host); }
41
+ catch(e) { console.error('[allegro-preview] JS error:', e); }
42
+ }
43
+
44
+ var article = document.getElementById('article-body');
45
+ if (!article) return;
46
+
47
+ var blockTags = new Set(['P','H1','H2','H3','H4','H5','H6','LI','BLOCKQUOTE','DIV','SECTION','FIGURE','PRE']);
48
+ var blocks = Array.from(article.children).filter(function(c) { return blockTags.has(c.tagName); });
49
+
50
+ ${o==="end"?"article.appendChild(host);":`var cutIndex = Math.min(${o}, blocks.length);
51
+ var pivot = blocks[cutIndex - 1] || blocks[0];
52
+ pivot.after(host);
53
+ blocks.slice(cutIndex).forEach(function(el) { el.remove(); });`}
54
+ })();`}function M(e,t,n){const o=e.map(i=>`<p>${i}</p>`).join(`
55
+ `);return`<!DOCTYPE html>
56
+ <html>
57
+ <head>
58
+ <meta charset="UTF-8">
59
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
60
+ ${j(n)}
61
+ <style>
62
+ * { box-sizing: border-box; }
63
+ *, *::before, *::after { box-sizing: border-box; }
64
+ body { font-family: Georgia, 'Times New Roman', serif; background: #fff; color: #111;
65
+ max-width: 680px; margin: 0 auto; padding: 32px 24px 64px; line-height: 1.75;
66
+ -webkit-font-smoothing: antialiased; }
67
+ h1 { font-size: 1.6rem; line-height: 1.25; margin: 0 0 6px; font-weight: 700; }
68
+ .meta { color: #6b7280; font-size: 0.82rem; font-family: system-ui, sans-serif; margin-bottom: 24px; }
69
+ p { margin: 0 0 1.1em; font-size: 1rem; overflow-wrap: break-word; }
70
+ [data-allegro-preview] { display: block; margin: 24px 0; }
71
+ </style>
72
+ </head>
73
+ <body>
74
+ <header>
75
+ <h1>Council Approves Decade-Long Transit Overhaul</h1>
76
+ <p class="meta">By Staff Reporter &nbsp;&middot;&nbsp; March 14, 2026</p>
77
+ </header>
78
+ <article id="article-body">
79
+ ${o}
80
+ </article>
81
+ <script>${t}<\/script>
82
+ </body>
83
+ </html>`}function J(e,t,n){const o=j(n);return V({html:o+$(e.html,t),css:z+e.css,js:e.js,externalCssUrls:[],interactive:!0})}function G(e,t,n){const o=$(e.html,t),i=H(o,e.css,e.js,"end");return M(N,i,n)}function W(e,t,n){const o=$(e.html,t),i=H(o,e.css,e.js,3);return M(N,i,n)}function Y(e){return U[e]}let h=[],d=null,g="standalone",v="full",p=null,f={},b;const k=document.getElementById("template-nav"),u=document.getElementById("field-list"),c=document.getElementById("preview-area"),E=document.getElementById("tab-group"),x=document.getElementById("device-group"),C=document.getElementById("toolbar-badge"),K=document.getElementById("theme-toggle"),Q=document.getElementById("theme-icon-sun"),X=document.getElementById("theme-icon-moon"),w=document.getElementById("tenant-input"),y=document.getElementById("tenant-status");function A(e){document.documentElement.setAttribute("data-theme",e?"dark":"light"),Q.style.display=e?"none":"",X.style.display=e?"":"none"}function Z(){const e=localStorage.getItem("allegro-preview-theme"),t=window.matchMedia("(prefers-color-scheme: dark)").matches;A(e?e==="dark":t)}K.addEventListener("click",()=>{const e=document.documentElement.getAttribute("data-theme")!=="dark";A(e),localStorage.setItem("allegro-preview-theme",e?"dark":"light")});function ee(e){const t=e.trim();return t?(/^https?:\/\//.test(t)?t:`https://${t}`).replace(/\/+$/,"")+"/client.js":""}function P(e){const t=ee(e);b=t||void 0,t?(y.className="tenant-active-badge",y.textContent=t):(y.className="tenant-hint",y.textContent="Loads /client.js on the preview page"),p&&m()}w.addEventListener("input",()=>{localStorage.setItem("allegro-preview-tenant",w.value),P(w.value)});function te(e=""){const n=localStorage.getItem("allegro-preview-tenant")??e;n&&(w.value=n,P(n))}function ne(){const e=window.location.hash.slice(1);return e?decodeURIComponent(e):null}function oe(e){history.replaceState(null,"",`#${encodeURIComponent(e)}`)}function ie(){history.replaceState(null,"",window.location.pathname)}function B(e){E.querySelectorAll(".tab-btn").forEach(t=>{t.disabled=!e}),x.querySelectorAll(".device-btn").forEach(t=>{t.disabled=!e})}E.addEventListener("click",e=>{const t=e.target.closest(".tab-btn");!t||t.disabled||(g=t.dataset.tab,E.querySelectorAll(".tab-btn").forEach(n=>n.classList.remove("active")),t.classList.add("active"),localStorage.setItem("allegro-preview-tab",g),m())});x.addEventListener("click",e=>{const t=e.target.closest(".device-btn");!t||t.disabled||(v=t.dataset.device,x.querySelectorAll(".device-btn").forEach(n=>n.classList.remove("active")),t.classList.add("active"),localStorage.setItem("allegro-preview-device",v),m())});function S(){k.innerHTML="";for(const e of h){const t=document.createElement("button");t.className="template-nav-item"+(e.slug===d?" active":""),t.innerHTML=`<span class="template-nav-dot"></span><span class="template-nav-name">${L(e.name)}</span>`,t.addEventListener("click",()=>void F(e.slug)),k.appendChild(t)}}async function F(e){d=e,oe(e),S(),await T(e)}function O(){p=null,d=null,ie(),S(),B(!1),C.textContent="No template selected",C.style.opacity="0.4",u.innerHTML='<p class="no-fields">Select a template to edit fields.</p>';const e=document.createElement("div");if(e.className="selector-screen",h.length===0){e.innerHTML=`
84
+ <div class="selector-empty">
85
+ <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
86
+ <span>No templates found in this directory.</span>
87
+ </div>`,c.innerHTML="",c.appendChild(e);return}e.innerHTML=`
88
+ <div class="selector-eyebrow">Allegro Preview</div>
89
+ <h2 class="selector-heading">Choose a template</h2>
90
+ <p class="selector-sub">Select a template to start previewing.</p>
91
+ <div class="selector-grid" id="selector-grid"></div>`,c.innerHTML="",c.appendChild(e);const t=e.querySelector("#selector-grid");for(const n of h){const o=document.createElement("button");o.className="selector-card",o.innerHTML=`
92
+ <div class="selector-card-icon">
93
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
94
+ </div>
95
+ <div class="selector-card-name">${L(n.name)}</div>
96
+ <div class="selector-card-slug">${L(n.slug)}</div>`,o.addEventListener("click",()=>void F(n.slug)),t.appendChild(o)}}const I=["text","number","checkbox","textarea"];function R(e){if(typeof e!="object"||e===null)return!1;const t=e;return typeof t.slug=="string"&&t.slug.length>0&&I.includes(t.type)}function se(e){if(typeof e!="object"||e===null)return`Invalid field: expected an object, got ${JSON.stringify(e)}.`;const t=e,n=[];if((typeof t.slug!="string"||t.slug.length===0)&&n.push('missing required "slug" property'),!I.includes(t.type)){const i=t.type!==void 0?`"${String(t.type)}"`:"none";n.push(`invalid type ${i} — valid types: ${I.join(", ")}`)}return`${typeof t.slug=="string"&&t.slug.length>0?`Field "${t.slug}"`:"Field"}: ${n.join("; ")}.`}function re(e){f={};for(const t of e)R(t)&&(f[t.slug]=t.default_value??"")}function ae(e,t){if(u.innerHTML="",t){const n=document.createElement("div");n.className="field-error",n.textContent=`template.json parse error: ${t}`,u.appendChild(n);return}if(e.length===0){u.innerHTML='<p class="no-fields">No fields in template.json.</p>';return}for(const n of e){if(!R(n)){const s=document.createElement("div");s.className="field-error",s.textContent=se(n),u.appendChild(s);continue}const o=n;if(o.type==="checkbox"){const s=document.createElement("div");s.className="field-checkbox-row";const a=document.createElement("input");a.type="checkbox",a.id=`field-${o.slug}`,a.checked=o.default_value==="true"||o.default_value==="1",a.addEventListener("change",()=>{f[o.slug]=a.checked?"true":"false",m()});const l=document.createElement("label");l.className="field-label",l.htmlFor=`field-${o.slug}`,l.textContent=o.label||o.slug,s.appendChild(a),s.appendChild(l),u.appendChild(s);continue}const i=document.createElement("div");i.className="field-group";const r=document.createElement("label");if(r.className="field-label",r.htmlFor=`field-${o.slug}`,r.textContent=o.label||o.slug,i.appendChild(r),o.type==="textarea"){const s=document.createElement("textarea");s.className="field-input",s.id=`field-${o.slug}`,s.rows=3,s.value=o.default_value??"",s.addEventListener("input",()=>{f[o.slug]=s.value,m()}),i.appendChild(s)}else{const s=document.createElement("input");s.type=o.type==="number"?"number":"text",s.className="field-input",s.id=`field-${o.slug}`,s.value=o.default_value??"",s.addEventListener("input",()=>{f[o.slug]=s.value,m()}),i.appendChild(s)}u.appendChild(i)}}function m(){if(!p)return;const e=Object.entries(f).map(([l,D])=>({slug:l,value:D})),t=Y(v),n=t===0,o=g==="standalone"?J(p,e,b):g==="append"?G(p,e,b):W(p,e,b);c.innerHTML="";const i=document.createElement("div");i.className=n?"preview-stage":"preview-stage preview-stage--breakpoint";const r=document.createElement("div");r.className="preview-scaler";const s=document.createElement("div");s.className="preview-frame-wrapper";const a=document.createElement("iframe");a.style.height="calc(100vh - 48px)",v==="mobile"&&(a.style.maxHeight="812px"),a.setAttribute("sandbox","allow-scripts allow-same-origin"),a.srcdoc=o,n?(s.style.width="100%",a.style.width="100%",r.style.width="100%"):(s.style.width=`${t}px`,a.style.width=`${t}px`),s.appendChild(a),r.appendChild(s),i.appendChild(r),c.appendChild(i)}async function T(e){try{const t=await fetch(`/api/template?slug=${encodeURIComponent(e)}`);if(!t.ok)throw new Error(`HTTP ${t.status}`);const n=await t.json();p=n,C.textContent=n.name,C.style.opacity="1",B(!0),re(n.fields),ae(n.fields,n.fieldsParseError),m()}catch(t){console.error("[allegro-preview] Failed to load template:",t),c.innerHTML="";const n=document.createElement("div");n.style.padding="40px",n.style.color="var(--text-muted)",n.style.fontSize="13px",n.textContent=`Failed to load template "${e}".`,c.appendChild(n)}}async function le(){const t=await(await fetch("/api/config")).json();h=t.templates,te(t.tenant??"");const n=ne(),o=n&&h.find(i=>i.slug===n)?n:null;o?(d=o,S(),await T(o)):O()}function ce(){const e=localStorage.getItem("allegro-preview-tab"),t=localStorage.getItem("allegro-preview-device");e&&["standalone","append","cut"].includes(e)&&(g=e,E.querySelectorAll(".tab-btn").forEach(n=>{n.classList.toggle("active",n.dataset.tab===e)})),t&&["full","desktop","tablet","mobile"].includes(t)&&(v=t,x.querySelectorAll(".device-btn").forEach(n=>{n.classList.toggle("active",n.dataset.device===t)}))}function _(){const e=new EventSource("/api/events");e.addEventListener("reload",()=>{d&&T(d)}),e.addEventListener("config",()=>{de()}),e.onerror=()=>{e.close(),setTimeout(_,2e3)}}async function de(){h=(await(await fetch("/api/config")).json()).templates,S(),d&&!h.find(n=>n.slug===d)&&O()}function L(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}Z();ce();le();_();