@orion-studios/payload-studio 0.6.0-beta.45 → 0.6.0-beta.47

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.
@@ -3,11 +3,16 @@ import type { FC } from 'react'
3
3
  export type {
4
4
  BuilderV2EditorInitialData,
5
5
  BuilderV2EditorProps,
6
+ BuilderV2PageTreeNode,
7
+ BuilderV2PermissionSet,
6
8
  BuilderV2ProjectAdapter,
7
9
  BuilderV2ProjectComponentDefinition,
8
10
  BuilderV2RuntimeComponent,
9
11
  BuilderV2RuntimeComponentProps,
10
12
  BuilderV2TraitDefinition,
13
+ BuilderV2ThemeTokens,
14
+ BuilderV2ValidationIssue,
15
+ BuilderV2VersionSnapshot,
11
16
  } from './types'
12
17
 
13
18
  export declare const GrapesPageEditor: FC<import('./types').BuilderV2EditorProps>
@@ -3,11 +3,16 @@ import type { FC } from 'react'
3
3
  export type {
4
4
  BuilderV2EditorInitialData,
5
5
  BuilderV2EditorProps,
6
+ BuilderV2PageTreeNode,
7
+ BuilderV2PermissionSet,
6
8
  BuilderV2ProjectAdapter,
7
9
  BuilderV2ProjectComponentDefinition,
8
10
  BuilderV2RuntimeComponent,
9
11
  BuilderV2RuntimeComponentProps,
10
12
  BuilderV2TraitDefinition,
13
+ BuilderV2ThemeTokens,
14
+ BuilderV2ValidationIssue,
15
+ BuilderV2VersionSnapshot,
11
16
  } from './types'
12
17
 
13
18
  export declare const GrapesPageEditor: FC<import('./types').BuilderV2EditorProps>
@@ -256,6 +256,7 @@ var allowedIframeHosts = [
256
256
  "calendar.google.com",
257
257
  "calendly.com",
258
258
  "player.vimeo.com",
259
+ "www.google.com",
259
260
  "www.youtube.com",
260
261
  "youtube.com"
261
262
  ];
@@ -281,10 +282,127 @@ var sanitizeBuilderCss = (value) => {
281
282
  }
282
283
  return value.replace(/@import\s+[^;]+;/gi, "").replace(/expression\s*\(/gi, "").replace(/javascript\s*:/gi, "");
283
284
  };
285
+ var scopeSelector = (selector, scope) => selector.split(",").map((part) => {
286
+ const trimmed = part.trim();
287
+ if (!trimmed || trimmed.startsWith(scope) || trimmed.startsWith("@")) {
288
+ return trimmed;
289
+ }
290
+ if (/^(html|body|:root)\b/i.test(trimmed)) {
291
+ return trimmed.replace(/^(html|body|:root)\b/i, scope);
292
+ }
293
+ return `${scope} ${trimmed}`;
294
+ }).filter(Boolean).join(", ");
295
+ var scopeBuilderCss = (value, scope = ".orion-builder-v2-runtime") => {
296
+ const css = sanitizeBuilderCss(value);
297
+ if (!css) {
298
+ return "";
299
+ }
300
+ let output = "";
301
+ let cursor = 0;
302
+ const rulePattern = /([^{}]+)\{/g;
303
+ let match;
304
+ while ((match = rulePattern.exec(css)) !== null) {
305
+ const selectorStart = match.index;
306
+ const selector = match[1];
307
+ output += css.slice(cursor, selectorStart);
308
+ const trimmedSelector = selector.trim();
309
+ if (trimmedSelector.startsWith("@keyframes") || trimmedSelector.startsWith("@font-face") || trimmedSelector.startsWith("@page")) {
310
+ output += `${selector}{`;
311
+ } else if (trimmedSelector.startsWith("@media") || trimmedSelector.startsWith("@supports")) {
312
+ output += `${selector}{`;
313
+ } else {
314
+ output += `${scopeSelector(selector, scope)} {`;
315
+ }
316
+ cursor = rulePattern.lastIndex;
317
+ }
318
+ output += css.slice(cursor);
319
+ return output;
320
+ };
321
+
322
+ // src/builder-v2/validation.ts
323
+ var hasMatch = (value, pattern) => pattern.test(value);
324
+ var validateBuilderV2Output = (input) => {
325
+ const html = typeof input.html === "string" ? input.html : "";
326
+ const css = typeof input.css === "string" ? input.css : "";
327
+ const issues = [];
328
+ if (!html.trim()) {
329
+ issues.push({
330
+ code: "empty-page",
331
+ message: "This page has no rendered content.",
332
+ path: "compiledHtml",
333
+ severity: "error"
334
+ });
335
+ }
336
+ if (!hasMatch(html, /<h1\b/i)) {
337
+ issues.push({
338
+ code: "missing-h1",
339
+ message: "Add one H1 so the published page has a clear primary heading.",
340
+ path: "compiledHtml",
341
+ severity: "warning"
342
+ });
343
+ }
344
+ if (hasMatch(html, /\son[a-z]+\s*=/i)) {
345
+ issues.push({
346
+ code: "inline-event-handler",
347
+ message: "Inline event handlers are not allowed in published builder HTML.",
348
+ path: "compiledHtml",
349
+ severity: "error"
350
+ });
351
+ }
352
+ if (hasMatch(html, /<(script|object|embed)\b/i)) {
353
+ issues.push({
354
+ code: "unsafe-element",
355
+ message: "Script, object, and embed tags are not allowed in builder content.",
356
+ path: "compiledHtml",
357
+ severity: "error"
358
+ });
359
+ }
360
+ if (hasMatch(html, /\b(?:href|src)=["']\s*javascript:/i)) {
361
+ issues.push({
362
+ code: "unsafe-url",
363
+ message: "Links and media cannot use javascript URLs.",
364
+ path: "compiledHtml",
365
+ severity: "error"
366
+ });
367
+ }
368
+ if (hasMatch(css, /@import\s/i)) {
369
+ issues.push({
370
+ code: "css-import",
371
+ message: "CSS imports are removed at publish time. Use the site font and theme managers instead.",
372
+ path: "compiledCss",
373
+ severity: "warning"
374
+ });
375
+ }
376
+ if (hasMatch(css, /position\s*:\s*fixed/i)) {
377
+ issues.push({
378
+ code: "fixed-position",
379
+ message: "Fixed positioning can cover site navigation or dialogs. Review this before publishing.",
380
+ path: "compiledCss",
381
+ severity: "warning"
382
+ });
383
+ }
384
+ return issues;
385
+ };
386
+ var hasBlockingBuilderV2Issues = (issues) => issues.some((issue) => issue.severity === "error");
284
387
 
285
388
  // src/builder-v2/editor/defaultBlocks.ts
286
389
  var registerOrionBuilderV2Blocks = (editor) => {
287
390
  const blocks = editor.Blocks;
391
+ blocks.add("orion-nav", {
392
+ category: "Chrome",
393
+ content: `
394
+ <header class="orion-builder-v2-nav">
395
+ <a class="orion-builder-v2-logo" href="/">Brand</a>
396
+ <nav aria-label="Primary">
397
+ <a href="/">Home</a>
398
+ <a href="/about">About</a>
399
+ <a href="/contact">Contact</a>
400
+ </nav>
401
+ <a class="orion-builder-v2-button" href="/contact">Start</a>
402
+ </header>
403
+ `,
404
+ label: "Nav"
405
+ });
288
406
  blocks.add("orion-section", {
289
407
  category: "Layout",
290
408
  content: `
@@ -324,6 +442,89 @@ var registerOrionBuilderV2Blocks = (editor) => {
324
442
  `,
325
443
  label: "Columns"
326
444
  });
445
+ blocks.add("orion-card-grid", {
446
+ category: "Sections",
447
+ content: `
448
+ <section class="orion-builder-v2-section">
449
+ <div class="orion-builder-v2-container">
450
+ <p class="orion-builder-v2-kicker">Featured</p>
451
+ <h2>Cards that explain the offer</h2>
452
+ <div class="orion-builder-v2-grid is-3">
453
+ <article class="orion-builder-v2-card"><h3>Service One</h3><p>Describe the outcome clients care about.</p><a href="/contact">Learn more</a></article>
454
+ <article class="orion-builder-v2-card"><h3>Service Two</h3><p>Keep the card concise and scannable.</p><a href="/contact">Learn more</a></article>
455
+ <article class="orion-builder-v2-card"><h3>Service Three</h3><p>Use the same structure for visual rhythm.</p><a href="/contact">Learn more</a></article>
456
+ </div>
457
+ </div>
458
+ </section>
459
+ `,
460
+ label: "Card grid"
461
+ });
462
+ blocks.add("orion-gallery", {
463
+ category: "Media",
464
+ content: `
465
+ <section class="orion-builder-v2-section">
466
+ <div class="orion-builder-v2-container">
467
+ <h2>Gallery</h2>
468
+ <div class="orion-builder-v2-gallery">
469
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
470
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
471
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
472
+ <img alt="Gallery item" src="https://placehold.co/900x700" />
473
+ </div>
474
+ </div>
475
+ </section>
476
+ `,
477
+ label: "Gallery"
478
+ });
479
+ blocks.add("orion-testimonials", {
480
+ category: "Sections",
481
+ content: `
482
+ <section class="orion-builder-v2-section is-muted">
483
+ <div class="orion-builder-v2-container">
484
+ <p class="orion-builder-v2-kicker">Testimonials</p>
485
+ <h2>What clients say</h2>
486
+ <div class="orion-builder-v2-grid is-3">
487
+ <blockquote class="orion-builder-v2-card"><p>"A clear, warm experience from start to finish."</p><cite>Client Name</cite></blockquote>
488
+ <blockquote class="orion-builder-v2-card"><p>"Exactly what we needed, without friction."</p><cite>Client Name</cite></blockquote>
489
+ <blockquote class="orion-builder-v2-card"><p>"The site made every next step obvious."</p><cite>Client Name</cite></blockquote>
490
+ </div>
491
+ </div>
492
+ </section>
493
+ `,
494
+ label: "Testimonials"
495
+ });
496
+ blocks.add("orion-faq", {
497
+ category: "Sections",
498
+ content: `
499
+ <section class="orion-builder-v2-section">
500
+ <div class="orion-builder-v2-container is-narrow">
501
+ <p class="orion-builder-v2-kicker">FAQ</p>
502
+ <h2>Questions answered</h2>
503
+ <details open><summary>What should visitors know first?</summary><p>Give a short, useful answer that removes hesitation.</p></details>
504
+ <details><summary>How does the process work?</summary><p>Explain the next step clearly.</p></details>
505
+ <details><summary>How do we get started?</summary><p>Point people to the strongest call to action.</p></details>
506
+ </div>
507
+ </section>
508
+ `,
509
+ label: "FAQ"
510
+ });
511
+ blocks.add("orion-pricing", {
512
+ category: "Commerce",
513
+ content: `
514
+ <section class="orion-builder-v2-section">
515
+ <div class="orion-builder-v2-container">
516
+ <p class="orion-builder-v2-kicker">Pricing</p>
517
+ <h2>Simple package options</h2>
518
+ <div class="orion-builder-v2-grid is-3">
519
+ <article class="orion-builder-v2-card"><h3>Starter</h3><p class="orion-builder-v2-price">$500</p><p>Best for focused launches.</p><a class="orion-builder-v2-button" href="/contact">Choose Starter</a></article>
520
+ <article class="orion-builder-v2-card is-featured"><h3>Growth</h3><p class="orion-builder-v2-price">$1,500</p><p>Best for expanding teams.</p><a class="orion-builder-v2-button" href="/contact">Choose Growth</a></article>
521
+ <article class="orion-builder-v2-card"><h3>Custom</h3><p class="orion-builder-v2-price">Quote</p><p>Best for complex builds.</p><a class="orion-builder-v2-button" href="/contact">Talk with us</a></article>
522
+ </div>
523
+ </div>
524
+ </section>
525
+ `,
526
+ label: "Pricing"
527
+ });
327
528
  blocks.add("orion-button", {
328
529
  category: "Basic",
329
530
  content: '<a class="orion-builder-v2-button" href="/contact">Button</a>',
@@ -348,6 +549,22 @@ var registerOrionBuilderV2Blocks = (editor) => {
348
549
  `,
349
550
  label: "Form"
350
551
  });
552
+ blocks.add("orion-footer", {
553
+ category: "Chrome",
554
+ content: `
555
+ <footer class="orion-builder-v2-footer">
556
+ <div>
557
+ <strong>Brand</strong>
558
+ <p>Short positioning line for the site footer.</p>
559
+ </div>
560
+ <nav aria-label="Footer">
561
+ <a href="/privacy">Privacy</a>
562
+ <a href="/contact">Contact</a>
563
+ </nav>
564
+ </footer>
565
+ `,
566
+ label: "Footer"
567
+ });
351
568
  };
352
569
 
353
570
  // src/builder-v2/editor/projectComponents.ts
@@ -417,7 +634,7 @@ var postToParent = (payload) => {
417
634
  };
418
635
  var buildSavePayload = (editor, status, projectData) => ({
419
636
  builderMode: "grapes-v2",
420
- compiledCss: sanitizeBuilderCss(editor.getCss()),
637
+ compiledCss: scopeBuilderCss(editor.getCss()),
421
638
  compiledHtml: sanitizeBuilderHtml(editor.getHtml()),
422
639
  projectData,
423
640
  status
@@ -516,12 +733,53 @@ var uploadPayloadMediaAssets = async (editor, files) => {
516
733
  editor.AssetManager.add(uploadedAssets);
517
734
  }
518
735
  };
519
- function GrapesPageEditor({ adapter, initialData, pageID }) {
736
+ function GrapesPageEditor({
737
+ adapter,
738
+ autosaveIntervalMs = 3e4,
739
+ initialData,
740
+ pageID
741
+ }) {
520
742
  const containerRef = (0, import_react.useRef)(null);
521
743
  const editorRef = (0, import_react.useRef)(null);
744
+ const autosaveTimerRef = (0, import_react.useRef)(null);
745
+ const saveRef = (0, import_react.useRef)(async () => void 0);
522
746
  const [error, setError] = (0, import_react.useState)("");
747
+ const [historyState, setHistoryState] = (0, import_react.useState)({ canRedo: false, canUndo: false });
748
+ const [lastSavedAt, setLastSavedAt] = (0, import_react.useState)("");
523
749
  const [loading, setLoading] = (0, import_react.useState)(true);
750
+ const [selectedDevice, setSelectedDevice] = (0, import_react.useState)("desktop");
524
751
  const [saving, setSaving] = (0, import_react.useState)(null);
752
+ const [saveMessage, setSaveMessage] = (0, import_react.useState)("");
753
+ const [validationIssues, setValidationIssues] = (0, import_react.useState)([]);
754
+ const pageTree = initialData?.meta?.pageTree || [];
755
+ const updateHistoryState = (editor) => {
756
+ const next = {
757
+ canRedo: editor.UndoManager.hasRedo(),
758
+ canUndo: editor.UndoManager.hasUndo()
759
+ };
760
+ setHistoryState(next);
761
+ postToParent({
762
+ ...next,
763
+ type: "history-state"
764
+ });
765
+ };
766
+ const runValidation = (editor) => {
767
+ const issues = validateBuilderV2Output({
768
+ css: editor.getCss(),
769
+ html: editor.getHtml()
770
+ });
771
+ setValidationIssues(issues);
772
+ postToParent({ issues, type: "validation-state" });
773
+ return issues;
774
+ };
775
+ const setDevice = (device) => {
776
+ const editor = editorRef.current;
777
+ if (!editor) {
778
+ return;
779
+ }
780
+ editor.setDevice(device);
781
+ setSelectedDevice(device);
782
+ };
525
783
  (0, import_react.useEffect)(() => {
526
784
  let active = true;
527
785
  const init = async () => {
@@ -605,12 +863,24 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
605
863
  void loadPayloadMediaAssets(editor);
606
864
  editor.on("update", () => {
607
865
  postToParent({ dirty: editor.getDirtyCount() > 0, type: "dirty-state" });
608
- postToParent({
609
- canRedo: editor.UndoManager.hasRedo(),
610
- canUndo: editor.UndoManager.hasUndo(),
611
- type: "history-state"
612
- });
866
+ updateHistoryState(editor);
867
+ runValidation(editor);
868
+ setSaveMessage("Unsaved changes");
869
+ if (autosaveTimerRef.current) {
870
+ window.clearTimeout(autosaveTimerRef.current);
871
+ }
872
+ autosaveTimerRef.current = window.setTimeout(() => {
873
+ if (editor.getDirtyCount() > 0) {
874
+ void saveRef.current("draft", { autosave: true });
875
+ }
876
+ }, autosaveIntervalMs);
613
877
  });
878
+ editor.on("component:selected", () => {
879
+ setSaveMessage("Selection ready");
880
+ });
881
+ setSelectedDevice(editor.getDevice() || "desktop");
882
+ runValidation(editor);
883
+ updateHistoryState(editor);
614
884
  setLoading(false);
615
885
  } catch (initError) {
616
886
  setError(initError instanceof Error ? initError.message : "Could not load the website builder.");
@@ -620,15 +890,30 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
620
890
  void init();
621
891
  return () => {
622
892
  active = false;
893
+ if (autosaveTimerRef.current) {
894
+ window.clearTimeout(autosaveTimerRef.current);
895
+ }
623
896
  editorRef.current?.destroy();
624
897
  editorRef.current = null;
625
898
  };
626
- }, [adapter, initialData?.projectData, initialData?.title]);
627
- const save = async (status) => {
899
+ }, [adapter, autosaveIntervalMs, initialData?.projectData, initialData?.title]);
900
+ const save = async (status, options = {}) => {
628
901
  const editor = editorRef.current;
629
902
  if (!editor || saving) {
630
903
  return;
631
904
  }
905
+ const issues = runValidation(editor);
906
+ if (status === "published" && hasBlockingBuilderV2Issues(issues)) {
907
+ const message = "Resolve blocking validation errors before publishing.";
908
+ setSaveMessage(message);
909
+ postToParent({
910
+ message,
911
+ ok: false,
912
+ status,
913
+ type: "save-result"
914
+ });
915
+ return;
916
+ }
632
917
  setSaving(status);
633
918
  try {
634
919
  const projectData = editor.getProjectData();
@@ -643,15 +928,19 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
643
928
  builderMode: "grapes-v2",
644
929
  builderProjectData: projectData,
645
930
  builderPublishedProjectData: projectData,
646
- builderValidationIssues: [],
931
+ builderLastPublishedAt: (/* @__PURE__ */ new Date()).toISOString(),
932
+ builderLastSavedAt: (/* @__PURE__ */ new Date()).toISOString(),
933
+ builderValidationIssues: issues,
647
934
  compiledCss: payload.compiledCss,
648
935
  compiledHtml: payload.compiledHtml
649
936
  } : {
650
937
  _status: "draft",
938
+ builderAutosaveProjectData: options.autosave ? projectData : null,
651
939
  builderDynamicComponents: dynamicComponents,
652
940
  builderMode: "grapes-v2",
653
941
  builderProjectData: projectData,
654
- builderValidationIssues: [],
942
+ builderLastSavedAt: (/* @__PURE__ */ new Date()).toISOString(),
943
+ builderValidationIssues: issues,
655
944
  compiledCss: payload.compiledCss,
656
945
  compiledHtml: payload.compiledHtml
657
946
  }
@@ -674,14 +963,18 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
674
963
  type: "dirty-state"
675
964
  });
676
965
  postToParent({
677
- message: status === "published" ? "Published." : "Draft saved.",
966
+ message: status === "published" ? "Published." : options.autosave ? "Autosaved." : "Draft saved.",
678
967
  ok: true,
679
968
  status,
680
969
  type: "save-result"
681
970
  });
971
+ setLastSavedAt((/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }));
972
+ setSaveMessage(status === "published" ? "Published" : options.autosave ? "Autosaved" : "Draft saved");
682
973
  } catch (saveError) {
974
+ const message = saveError instanceof Error ? saveError.message : "Could not save.";
975
+ setSaveMessage(message);
683
976
  postToParent({
684
- message: saveError instanceof Error ? saveError.message : "Could not save.",
977
+ message,
685
978
  ok: false,
686
979
  status,
687
980
  type: "save-result"
@@ -690,6 +983,9 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
690
983
  setSaving(null);
691
984
  }
692
985
  };
986
+ (0, import_react.useEffect)(() => {
987
+ saveRef.current = save;
988
+ }, [saving]);
693
989
  (0, import_react.useEffect)(() => {
694
990
  const onMessage = (event) => {
695
991
  const data = event.data;
@@ -699,19 +995,11 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
699
995
  }
700
996
  if (data.type === "dirty-check-request") {
701
997
  postToParent({ dirty: editor.getDirtyCount() > 0, type: "dirty-state" });
702
- postToParent({
703
- canRedo: editor.UndoManager.hasRedo(),
704
- canUndo: editor.UndoManager.hasUndo(),
705
- type: "history-state"
706
- });
998
+ updateHistoryState(editor);
707
999
  return;
708
1000
  }
709
1001
  if (data.type === "history-check-request") {
710
- postToParent({
711
- canRedo: editor.UndoManager.hasRedo(),
712
- canUndo: editor.UndoManager.hasUndo(),
713
- type: "history-state"
714
- });
1002
+ updateHistoryState(editor);
715
1003
  return;
716
1004
  }
717
1005
  if (data.type === "undo") {
@@ -730,19 +1018,89 @@ function GrapesPageEditor({ adapter, initialData, pageID }) {
730
1018
  return () => window.removeEventListener("message", onMessage);
731
1019
  }, [saving]);
732
1020
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-editor", children: [
1021
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "orion-builder-v2-topbar", children: [
1022
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1023
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "orion-builder-v2-eyebrow", children: "Website Builder V2" }),
1024
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: initialData?.title || "Untitled page" })
1025
+ ] }),
1026
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-toolbar", "aria-label": "Builder controls", children: [
1027
+ ["desktop", "tablet", "mobile"].map((device) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1028
+ "button",
1029
+ {
1030
+ "aria-pressed": selectedDevice === device,
1031
+ className: "orion-builder-v2-tool",
1032
+ onClick: () => setDevice(device),
1033
+ type: "button",
1034
+ children: device
1035
+ },
1036
+ device
1037
+ )),
1038
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1039
+ "button",
1040
+ {
1041
+ className: "orion-builder-v2-tool",
1042
+ disabled: !historyState.canUndo,
1043
+ onClick: () => {
1044
+ editorRef.current?.UndoManager.undo();
1045
+ editorRef.current && updateHistoryState(editorRef.current);
1046
+ },
1047
+ type: "button",
1048
+ children: "Undo"
1049
+ }
1050
+ ),
1051
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1052
+ "button",
1053
+ {
1054
+ className: "orion-builder-v2-tool",
1055
+ disabled: !historyState.canRedo,
1056
+ onClick: () => {
1057
+ editorRef.current?.UndoManager.redo();
1058
+ editorRef.current && updateHistoryState(editorRef.current);
1059
+ },
1060
+ type: "button",
1061
+ children: "Redo"
1062
+ }
1063
+ ),
1064
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "orion-builder-v2-tool is-primary", disabled: Boolean(saving), onClick: () => void save("draft"), type: "button", children: saving === "draft" ? "Saving..." : "Save draft" }),
1065
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "orion-builder-v2-tool is-publish", disabled: Boolean(saving), onClick: () => void save("published"), type: "button", children: saving === "published" ? "Publishing..." : "Publish" })
1066
+ ] })
1067
+ ] }),
733
1068
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", { className: "orion-builder-v2-sidebar", children: [
1069
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
1070
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Pages" }),
1071
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-page-tree", children: pageTree.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "No pages loaded." }) : pageTree.map((page) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", { className: "orion-builder-v2-page-link", href: `#page-${page.id}`, children: [
1072
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: page.title }),
1073
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: page.path })
1074
+ ] }, page.id)) })
1075
+ ] }),
734
1076
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
735
1077
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Blocks" }),
1078
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Drag sections into the page. Dynamic blocks render through the project adapter." }),
736
1079
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "orion-builder-v2-blocks" })
737
1080
  ] }),
738
1081
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
739
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Settings" }),
1082
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Inspector" }),
1083
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Selection settings, dynamic bindings, links, and labels." }),
740
1084
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "orion-builder-v2-traits" })
1085
+ ] }),
1086
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
1087
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Validation" }),
1088
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-validation-list", children: validationIssues.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "No issues found." }) : validationIssues.map((issue) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `orion-builder-v2-validation is-${issue.severity}`, children: [
1089
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: issue.message }),
1090
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: issue.severity })
1091
+ ] }, `${issue.code}-${issue.path}`)) })
741
1092
  ] })
742
1093
  ] }),
743
1094
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { className: "orion-builder-v2-main", children: [
744
1095
  loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-status", children: "Loading builder..." }) : null,
745
1096
  error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-error", children: error }) : null,
1097
+ saveMessage || lastSavedAt ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-save-status", children: [
1098
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: saveMessage || "Ready" }),
1099
+ lastSavedAt ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1100
+ "Last saved ",
1101
+ lastSavedAt
1102
+ ] }) : null
1103
+ ] }) : null,
746
1104
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-canvas", ref: containerRef })
747
1105
  ] })
748
1106
  ] });