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

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