syntaxmatrix 2.6.4.4__py3-none-any.whl → 3.0.0__py3-none-any.whl

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 (41) hide show
  1. syntaxmatrix/__init__.py +6 -4
  2. syntaxmatrix/agentic/agents.py +195 -15
  3. syntaxmatrix/agentic/agents_orchestrer.py +16 -10
  4. syntaxmatrix/client_docs.py +237 -0
  5. syntaxmatrix/commentary.py +96 -25
  6. syntaxmatrix/core.py +142 -56
  7. syntaxmatrix/dataset_preprocessing.py +2 -2
  8. syntaxmatrix/db.py +0 -17
  9. syntaxmatrix/kernel_manager.py +174 -150
  10. syntaxmatrix/page_builder_generation.py +654 -50
  11. syntaxmatrix/page_layout_contract.py +25 -3
  12. syntaxmatrix/page_patch_publish.py +368 -15
  13. syntaxmatrix/plugins/__init__.py +0 -0
  14. syntaxmatrix/premium/__init__.py +10 -2
  15. syntaxmatrix/premium/catalogue/__init__.py +121 -0
  16. syntaxmatrix/premium/gate.py +15 -3
  17. syntaxmatrix/premium/state.py +507 -0
  18. syntaxmatrix/premium/verify.py +222 -0
  19. syntaxmatrix/profiles.py +1 -1
  20. syntaxmatrix/routes.py +9782 -8004
  21. syntaxmatrix/settings/model_map.py +50 -65
  22. syntaxmatrix/settings/prompts.py +1435 -380
  23. syntaxmatrix/settings/string_navbar.py +4 -4
  24. syntaxmatrix/static/icons/bot_icon.png +0 -0
  25. syntaxmatrix/static/icons/bot_icon2.png +0 -0
  26. syntaxmatrix/templates/admin_billing.html +408 -0
  27. syntaxmatrix/templates/admin_branding.html +65 -2
  28. syntaxmatrix/templates/admin_features.html +54 -0
  29. syntaxmatrix/templates/dashboard.html +285 -8
  30. syntaxmatrix/templates/edit_page.html +199 -18
  31. syntaxmatrix/themes.py +17 -17
  32. syntaxmatrix/workspace_db.py +0 -23
  33. syntaxmatrix-3.0.0.dist-info/METADATA +219 -0
  34. {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.0.dist-info}/RECORD +38 -33
  35. {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.0.dist-info}/WHEEL +1 -1
  36. syntaxmatrix/settings/default.yaml +0 -13
  37. syntaxmatrix-2.6.4.4.dist-info/METADATA +0 -539
  38. syntaxmatrix-2.6.4.4.dist-info/licenses/LICENSE.txt +0 -21
  39. /syntaxmatrix/{plugin_manager.py → plugins/plugin_manager.py} +0 -0
  40. /syntaxmatrix/static/icons/{logo3.png → logo2.png} +0 -0
  41. {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.0.dist-info}/top_level.txt +0 -0
@@ -703,6 +703,116 @@
703
703
  font-size: 0.8em;
704
704
  opacity: 0.7;
705
705
  }
706
+
707
+ /* ----------------------------
708
+ Thought Process renderer
709
+ ---------------------------- */
710
+ .tp-render{margin-top:10px}
711
+ .tp-grid{
712
+ display:grid;
713
+ grid-template-columns: 1fr 1fr;
714
+ gap:10px;
715
+ margin-bottom:10px;
716
+ }
717
+ @media (max-width: 900px){
718
+ .tp-grid{grid-template-columns: 1fr;}
719
+ }
720
+ .tp-card{
721
+ border:1px solid var(--border);
722
+ border-radius: 12px;
723
+ background: #fff;
724
+ padding:10px 12px;
725
+ overflow:hidden;
726
+ }
727
+ .tp-card h4{
728
+ margin:0 0 6px;
729
+ font-size:.95rem;
730
+ letter-spacing:.01em;
731
+ }
732
+ .tp-kv{
733
+ display:grid;
734
+ grid-template-columns: 180px 1fr;
735
+ gap:6px 10px;
736
+ font-size:.92rem;
737
+ line-height:1.35;
738
+ }
739
+ .tp-kv .k{color: var(--muted); font-weight:700;}
740
+ .tp-kv .v{color: var(--text);}
741
+ .tp-steps ol{
742
+ margin:0;
743
+ padding-left: 1.1rem;
744
+ }
745
+ .tp-steps li{ margin: 0 0 6px; }
746
+
747
+ .tp-code{ position:relative; }
748
+ .tp-code pre{
749
+ margin:0;
750
+ padding:10px 12px;
751
+ border-radius:12px;
752
+ background: #0b1220;
753
+ color:#e5e7eb;
754
+ overflow:auto;
755
+ border:1px solid rgba(15,23,42,.2);
756
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
757
+ font-size: .86rem;
758
+ line-height: 1.45;
759
+ white-space: pre;
760
+ }
761
+ .tp-copy{
762
+ position:absolute;
763
+ top:10px;
764
+ right:10px;
765
+ border:1px solid var(--border);
766
+ background:#fff;
767
+ color:var(--text);
768
+ border-radius:999px;
769
+ padding:5px 10px;
770
+ font-weight:700;
771
+ cursor:pointer;
772
+ font-size:.78rem;
773
+ }
774
+ .tp-copy:hover{filter:brightness(.98)}
775
+
776
+ .tp-badges{
777
+ display:flex;
778
+ flex-wrap:wrap;
779
+ gap:8px;
780
+ margin-bottom:10px;
781
+ }
782
+ .tp-badge{
783
+ border:1px solid var(--border);
784
+ border-radius:999px;
785
+ padding:4px 10px;
786
+ background:#fff;
787
+ font-size:.8rem;
788
+ font-weight:700;
789
+ color: var(--text);
790
+ }
791
+
792
+ .tp-raw{
793
+ margin-top:10px;
794
+ border:1px dashed var(--border);
795
+ border-radius:12px;
796
+ padding:8px 10px;
797
+ background:#fff;
798
+ }
799
+ .tp-raw summary{
800
+ cursor:pointer;
801
+ font-weight:800;
802
+ color: var(--muted);
803
+ }
804
+ .tp-raw-pre{
805
+ margin:10px 0 0;
806
+ padding:10px 12px;
807
+ border-radius:10px;
808
+ background:#f8fafc;
809
+ border:1px solid var(--border);
810
+ overflow:auto;
811
+ max-height: 320px;
812
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
813
+ font-size: .85rem;
814
+ white-space: pre-wrap;
815
+ }
706
816
  </style>
707
817
 
708
818
  <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
@@ -781,7 +891,6 @@
781
891
  <textarea id="askai" name="askai_question" type="text" rows="5"
782
892
  style="position:relative; width:90%; padding:16px; font-size:0.8em; border-radius:8px;"
783
893
  placeholder="Ask me about {{ (selected_dataset or 'your dataset\n. But upload it first.').replace('_', ' ').replace('.csv', '') }}" required></textarea>
784
- <!-- <button type="submit" style="font-size:1.2rem; width:8rem; padding:4px;">Submit</button> -->
785
894
  <button
786
895
  type="submit"
787
896
  class="btn btn-primary eda-submit-btn"
@@ -798,21 +907,25 @@
798
907
  </form>
799
908
  <div style="margin-bottom: 36px;">
800
909
  {% if askai_question %}
801
- <!--
802
910
  <div class="askai-qblock" style="margin:20px 5px;">
803
- <span class="askai-q-label"><b>Task:</b></span>
911
+ <span class="askai-q-label"><b>Question:</b></span><br>
804
912
  <span class="askai-q">{{ askai_question }}</span>
805
913
  </div>
806
- -->
807
- <br><br>
914
+ <hr><br>
808
915
  <div class="refined-qblock">
809
916
  <details>
810
917
  <summary class="refined-q-label" style="cursor: pointer; list-style: none;">
811
918
  <span class="toggle-arrow">▶</span>
812
- <b>Thought Process</b>
919
+ <b>Reasoning Process</b>
813
920
  </summary>
814
921
  <div class="li" style="margin-top: 10px; padding-left: 14px; border-left: 3px solid #e0e5ee;">
815
- <span class="refined-q">{{ refined_question|safe }}</span>
922
+ <div class="tp-render" id="tp-render"></div>
923
+ <script type="application/json" id="tp-data">{{ (thought_obj if thought_obj else refined_question)|tojson }}</script>
924
+
925
+ <details class="tp-raw">
926
+ <pre class="tp-raw-pre">{{ refined_question|e }}</pre>
927
+ </details>
928
+ <hl>
816
929
  <br><br>
817
930
  {% if tasks %}
818
931
  <b>Tasks Performed: </b>
@@ -822,7 +935,7 @@
822
935
  {% endif %}
823
936
  <br><br>
824
937
  {% if TOKENS %}
825
- <b>Refiner Agent: </b><span>{{ TOKENS['Refiner'][0] }} | {{ TOKENS['Refiner'][1] }}</span><br>
938
+ <b>Assistant Agent: </b><span>{{ TOKENS['Refiner'][0] }} | {{ TOKENS['Refiner'][1] }}</span><br>
826
939
  <b>Token Usage: </b>
827
940
  <li>Input Tokens: {{ TOKENS['Refiner'][2] }}</li>
828
941
  <li>Output Tokens: {{ TOKENS['Refiner'][3] }}</li>
@@ -839,6 +952,7 @@
839
952
  </details>
840
953
  </div>
841
954
  {% endif %}
955
+ <hr>
842
956
  {% if ai_outputs %}
843
957
  <div class="d-flex align-items-center justify-content-between" style="margin: 12px;">
844
958
  <br>
@@ -1012,5 +1126,168 @@
1012
1126
  }
1013
1127
  });
1014
1128
  </script>
1129
+ <script>
1130
+ (function(){
1131
+ const host = document.getElementById("tp-render");
1132
+ const src = document.getElementById("tp-data");
1133
+ if (!host || !src) return;
1134
+
1135
+ function safeString(x){
1136
+ if (x === null || x === undefined) return "";
1137
+ if (typeof x === "string") return x;
1138
+ try { return JSON.stringify(x, null, 2); } catch(e){ return String(x); }
1139
+ }
1140
+
1141
+ function el(tag, cls){
1142
+ const e = document.createElement(tag);
1143
+ if (cls) e.className = cls;
1144
+ return e;
1145
+ }
1146
+
1147
+ let data = null;
1148
+ try{
1149
+ data = JSON.parse(src.textContent || "null");
1150
+ }catch(e){
1151
+ data = (src.textContent || "").trim();
1152
+ }
1153
+
1154
+ // If the payload is itself a JSON string, try parse again.
1155
+ if (typeof data === "string"){
1156
+ const s = data.trim();
1157
+ if ((s.startsWith("{") && s.endsWith("}")) || (s.startsWith("[") && s.endsWith("]"))){
1158
+ try { data = JSON.parse(s); } catch(e) {}
1159
+ }
1160
+ }
1161
+
1162
+ // Fallback: render as plain pre.
1163
+ if (!data || (typeof data !== "object")){
1164
+ const pre = el("pre", "tp-raw-pre");
1165
+ pre.textContent = safeString(data);
1166
+ host.appendChild(pre);
1167
+ return;
1168
+ }
1169
+
1170
+ const taskCat = data["ML_Task_Category"] || data["Task_Category"] || data["Task category"] || "";
1171
+ const statTest = (data["Logic Mapping"] && (data["Logic Mapping"]["Statistical Test"] || data["Logic Mapping"]["statistical_test"])) ||
1172
+ data["Statistical Test"] || data["Statistical_Test"] || "";
1173
+ const viz = data["Visualization Template"] || data["Visualisation Template"] || data["Visualization"] || data["Visualisation"] || "";
1174
+
1175
+ const badges = el("div", "tp-badges");
1176
+ function addBadge(label, value){
1177
+ if (!value) return;
1178
+ const b = el("div", "tp-badge");
1179
+ b.textContent = label + ": " + value;
1180
+ badges.appendChild(b);
1181
+ }
1182
+ addBadge("Category", safeString(taskCat));
1183
+ addBadge("Test", safeString(statTest));
1184
+ addBadge("Visual", safeString(viz));
1185
+ if (badges.childElementCount) host.appendChild(badges);
1186
+
1187
+ const grid = el("div", "tp-grid");
1188
+
1189
+ // Logic mapping
1190
+ const mapping = data["Logic Mapping"] || data["Logic_Mapping"] || null;
1191
+ const mapCard = el("div", "tp-card");
1192
+ const mapTitle = el("h4"); mapTitle.textContent = "Logic mapping";
1193
+ mapCard.appendChild(mapTitle);
1194
+
1195
+ const kv = el("div", "tp-kv");
1196
+ if (mapping && typeof mapping === "object"){
1197
+ Object.keys(mapping).forEach(k=>{
1198
+ const v = mapping[k];
1199
+ const kEl = el("div","k"); kEl.textContent = k;
1200
+ const vEl = el("div","v"); vEl.textContent = safeString(v);
1201
+ kv.appendChild(kEl); kv.appendChild(vEl);
1202
+ });
1203
+ } else {
1204
+ const kEl = el("div","k"); kEl.textContent = "Mapping";
1205
+ const vEl = el("div","v"); vEl.textContent = "—";
1206
+ kv.appendChild(kEl); kv.appendChild(vEl);
1207
+ }
1208
+ mapCard.appendChild(kv);
1209
+ grid.appendChild(mapCard);
1210
+
1211
+ // Steps
1212
+ const stepsRaw = data["Step-by-Step Chain-of-Thought"] || data["Step-by-Step"] || data["Steps"] || data["Plan"] || null;
1213
+ const stepsCard = el("div", "tp-card tp-steps");
1214
+ const stepsTitle = el("h4"); stepsTitle.textContent = "Steps";
1215
+ stepsCard.appendChild(stepsTitle);
1216
+
1217
+ const ol = el("ol");
1218
+ let steps = [];
1219
+ if (Array.isArray(stepsRaw)) {
1220
+ steps = stepsRaw.map(x=>safeString(x)).filter(Boolean);
1221
+ } else if (typeof stepsRaw === "string") {
1222
+ steps = stepsRaw.split(/\n+/).map(s=>s.trim()).filter(Boolean);
1223
+ }
1224
+ if (!steps.length) {
1225
+ const li = el("li"); li.textContent = "—";
1226
+ ol.appendChild(li);
1227
+ } else {
1228
+ steps.forEach(s=>{
1229
+ const li = el("li"); li.textContent = s.replace(/^\d+\.\s*/, "");
1230
+ ol.appendChild(li);
1231
+ });
1232
+ }
1233
+ stepsCard.appendChild(ol);
1234
+ grid.appendChild(stepsCard);
1235
+
1236
+ host.appendChild(grid);
1237
+
1238
+ // Code snippet
1239
+ const code = data["Code Snippet"] || data["Code"] || data["code"] || "";
1240
+ if (code){
1241
+ const codeCard = el("div", "tp-card tp-code");
1242
+ const h = el("h4"); h.textContent = "Generated code";
1243
+ codeCard.appendChild(h);
1244
+
1245
+ const btn = el("button", "tp-copy");
1246
+ btn.type = "button";
1247
+ btn.textContent = "Copy";
1248
+ btn.addEventListener("click", async ()=>{
1249
+ try{
1250
+ await navigator.clipboard.writeText(String(code));
1251
+ btn.textContent = "Copied";
1252
+ setTimeout(()=> btn.textContent = "Copy", 900);
1253
+ }catch(e){
1254
+ btn.textContent = "Copy failed";
1255
+ setTimeout(()=> btn.textContent = "Copy", 1100);
1256
+ }
1257
+ });
1258
+ codeCard.appendChild(btn);
1259
+
1260
+ const pre = el("pre");
1261
+ pre.textContent = String(code);
1262
+ codeCard.appendChild(pre);
1263
+
1264
+ host.appendChild(codeCard);
1265
+ }
1266
+
1267
+ // Any other keys
1268
+ const known = new Set([
1269
+ "ML_Task_Category","Task_Category","Task category",
1270
+ "Logic Mapping","Logic_Mapping",
1271
+ "Visualization Template","Visualisation Template","Visualization","Visualisation",
1272
+ "Step-by-Step Chain-of-Thought","Step-by-Step","Steps","Plan",
1273
+ "Code Snippet","Code","code"
1274
+ ]);
1275
+
1276
+ const extraKeys = Object.keys(data).filter(k => !known.has(k));
1277
+ if (extraKeys.length){
1278
+ const extra = el("div","tp-card");
1279
+ const h = el("h4"); h.textContent = "Extra details";
1280
+ extra.appendChild(h);
1281
+ const kv2 = el("div","tp-kv");
1282
+ extraKeys.forEach(k=>{
1283
+ const kEl = el("div","k"); kEl.textContent = k;
1284
+ const vEl = el("div","v"); vEl.textContent = safeString(data[k]);
1285
+ kv2.appendChild(kEl); kv2.appendChild(vEl);
1286
+ });
1287
+ extra.appendChild(kv2);
1288
+ host.appendChild(extra);
1289
+ }
1290
+ })();
1291
+ </script>
1015
1292
  </body>
1016
1293
  </html>
@@ -7,6 +7,7 @@
7
7
 
8
8
  <!-- SortableJS (drag & drop) -->
9
9
  <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.6/Sortable.min.js"></script>
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/6.8.3/tinymce.min.js" referrerpolicy="no-referrer"></script>
10
11
 
11
12
  <!-- CodeMirror (real code editor: syntax colours + matching brackets) -->
12
13
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css"/>
@@ -279,9 +280,27 @@
279
280
  background: rgba(2,6,23,.45);
280
281
  transition: opacity .12s ease;
281
282
  }
282
- .item:hover .thumb .drop{opacity:1;}
283
- .item .title{font-weight:750; font-size:.86rem;}
284
- .item .text{color:var(--muted); font-size:.8rem; line-height:1.25;}
283
+ .item .title{
284
+ font-weight:750;
285
+ font-size:.86rem;
286
+ line-height:1.25;
287
+
288
+ /* 2-line clamp so long titles look good */
289
+ display:-webkit-box;
290
+ -webkit-line-clamp:2;
291
+ -webkit-box-orient:vertical;
292
+ overflow:hidden;
293
+ min-height: calc(1.25em * 2);
294
+ }
295
+
296
+ .item .text{
297
+ opacity:.9;
298
+ font-size:.82rem;
299
+ color: var(--mut);
300
+
301
+ /* show new lines in the builder preview */
302
+ white-space: pre-wrap;
303
+ }
285
304
 
286
305
  .item .cta-drop{
287
306
  margin-top:6px;
@@ -555,7 +574,6 @@
555
574
  <option value="4">Default: 4 cols</option>
556
575
  <option value="5">Default: 5 cols</option>
557
576
  </select>
558
- <button class="btn danger" id="btn-clear" type="button">Clear</button>
559
577
  </div>
560
578
  </div>
561
579
  <div class="sections" id="sections"></div>
@@ -586,9 +604,44 @@
586
604
  <input id="insp-title" placeholder="Title"/>
587
605
  </div>
588
606
 
589
- <div class="field">
607
+ <!-- Alignment controls (per item) -->
608
+ <div class="row" id="row-text-style" style="display:none;">
609
+ <div class="field" style="flex:1;">
610
+ <label>Title alignment</label>
611
+ <select id="insp-title-align">
612
+ <option value="">Default</option>
613
+ <option value="left">Left</option>
614
+ <option value="center">Centre</option>
615
+ <option value="right">Right</option>
616
+ <option value="justify">Justify</option>
617
+ </select>
618
+ </div>
619
+
620
+ <div class="field" style="flex:1;">
621
+ <label>Body alignment</label>
622
+ <select id="insp-text-align">
623
+ <option value="">Default</option>
624
+ <option value="left">Left</option>
625
+ <option value="center">Centre</option>
626
+ <option value="right">Right</option>
627
+ <option value="justify">Justify</option>
628
+ </select>
629
+ </div>
630
+ </div>
631
+
632
+ <!-- Plain text (used for sections + fallback) -->
633
+ <div class="field" id="field-text-plain">
590
634
  <label>Text</label>
591
- <textarea id="insp-text" placeholder="Text / description"></textarea>
635
+ <textarea id="insp-text-plain" rows="6" placeholder="Short description (new lines supported)"></textarea>
636
+ </div>
637
+
638
+ <!-- Rich text (WordPress-like) -->
639
+ <div class="field" id="field-text-rich" style="display:none;">
640
+ <label>Body</label>
641
+ <textarea id="insp-text-rich"></textarea>
642
+ <div class="muted" style="margin-top:6px;">
643
+ Tip: use the toolbar for bold, lists, colours, font sizes, alignment, and divider lines.
644
+ </div>
592
645
  </div>
593
646
 
594
647
  <div class="row">
@@ -1569,8 +1622,18 @@
1569
1622
  const inspBox = document.getElementById("insp");
1570
1623
  const inspEmpty = document.getElementById("insp-empty");
1571
1624
  const inspKind = document.getElementById("insp-kind");
1625
+
1572
1626
  const inspTitle = document.getElementById("insp-title");
1573
- const inspText = document.getElementById("insp-text");
1627
+ // plain + rich
1628
+ const inspTextPlain = document.getElementById("insp-text-plain");
1629
+ const inspTextRich = document.getElementById("insp-text-rich");
1630
+ const fieldTextPlain = document.getElementById("field-text-plain");
1631
+ const fieldTextRich = document.getElementById("field-text-rich");
1632
+ // alignment
1633
+ const rowTextStyle = document.getElementById("row-text-style");
1634
+ const inspTitleAlign = document.getElementById("insp-title-align");
1635
+ const inspTextAlign = document.getElementById("insp-text-align");
1636
+
1574
1637
  const inspCols = document.getElementById("insp-cols");
1575
1638
  const inspImg = document.getElementById("insp-img");
1576
1639
  const inspHref = document.getElementById("insp-href");
@@ -1597,6 +1660,83 @@
1597
1660
  const secPad = document.getElementById("sec-pad");
1598
1661
  const secAlign = document.getElementById("sec-align");
1599
1662
 
1663
+ function escapeHtml(s){
1664
+ s = String(s || "");
1665
+ return s
1666
+ .replaceAll("&", "&amp;")
1667
+ .replaceAll("<", "&lt;")
1668
+ .replaceAll(">", "&gt;")
1669
+ .replaceAll('"', "&quot;")
1670
+ .replaceAll("'", "&#39;");
1671
+ }
1672
+
1673
+ function plainToHtml(s){
1674
+ // turn new lines into <br> (good enough as a fallback)
1675
+ const esc = escapeHtml(s || "");
1676
+ return esc.replaceAll("\n", "<br>");
1677
+ }
1678
+
1679
+ function htmlToText(html){
1680
+ if (!html) return "";
1681
+ const tmp = document.createElement("div");
1682
+ tmp.innerHTML = html;
1683
+ const txt = (tmp.textContent || tmp.innerText || "");
1684
+ return txt.replace(/\n{3,}/g, "\n\n").trim();
1685
+ }
1686
+
1687
+ let _tinymceInitDone = false;
1688
+
1689
+ function ensureTinyMCE(){
1690
+ if (_tinymceInitDone) return true;
1691
+ if (!window.tinymce) return false;
1692
+
1693
+ try{
1694
+ window.tinymce.init({
1695
+ selector: "#insp-text-rich",
1696
+ height: 280,
1697
+ menubar: false,
1698
+ branding: false,
1699
+ statusbar: false,
1700
+ plugins: "lists link hr autoresize",
1701
+ toolbar: "undo redo | bold italic underline | fontsize forecolor | alignleft aligncenter alignright alignjustify | bullist numlist | hr | link | removeformat",
1702
+ setup: (ed) => {
1703
+ ed.on("init", () => {
1704
+ _tinymceInitDone = true;
1705
+ if (ed._smxPendingHtml != null){
1706
+ ed.setContent(ed._smxPendingHtml);
1707
+ ed._smxPendingHtml = null;
1708
+ }
1709
+ });
1710
+ }
1711
+ });
1712
+
1713
+ return true;
1714
+ }catch(e){
1715
+ console.warn("TinyMCE init failed:", e);
1716
+ return false;
1717
+ }
1718
+ }
1719
+
1720
+ function setRichHtml(html){
1721
+ const ed = window.tinymce && window.tinymce.get("insp-text-rich");
1722
+ if (ed && _tinymceInitDone){
1723
+ ed.setContent(html || "");
1724
+ return;
1725
+ }
1726
+ if (ed){
1727
+ ed._smxPendingHtml = html || "";
1728
+ return;
1729
+ }
1730
+ inspTextRich.value = html || "";
1731
+ }
1732
+
1733
+ function getRichHtml(){
1734
+ const ed = window.tinymce && window.tinymce.get("insp-text-rich");
1735
+ if (ed && _tinymceInitDone){
1736
+ return ed.getContent() || "";
1737
+ }
1738
+ return inspTextRich.value || "";
1739
+ }
1600
1740
 
1601
1741
  function openInspectorForSelection(){
1602
1742
  if (heroCtasBox) heroCtasBox.style.display = "none";
@@ -1614,7 +1754,12 @@
1614
1754
 
1615
1755
  inspKind.value = "Section · " + sectionLabel(s.type);
1616
1756
  inspTitle.value = safeText(s.title || "");
1617
- inspText.value = safeText(s.text || "");
1757
+ if (rowTextStyle) rowTextStyle.style.display = "none";
1758
+ if (fieldTextPlain) fieldTextPlain.style.display = "block";
1759
+ if (fieldTextRich) fieldTextRich.style.display = "none";
1760
+
1761
+ inspTextPlain.value = safeText(s.text || "");
1762
+
1618
1763
  inspCols.value = String(s.cols || 1);
1619
1764
  inspImg.value = safeText(s.imageUrl || "");
1620
1765
 
@@ -1684,7 +1829,32 @@
1684
1829
 
1685
1830
  inspKind.value = "Item · " + safeText(it.type);
1686
1831
  inspTitle.value = safeText(it.title || "");
1687
- inspText.value = safeText(it.text || "");
1832
+ const useRich = (t !== "button"); // buttons don’t need rich body
1833
+
1834
+ if (rowTextStyle) rowTextStyle.style.display = "flex";
1835
+
1836
+ // show align values (default = empty)
1837
+ if (inspTitleAlign) inspTitleAlign.value = String(it.titleAlign || "");
1838
+ if (inspTextAlign) inspTextAlign.value = String(it.textAlign || "");
1839
+
1840
+ if (useRich){
1841
+ if (fieldTextPlain) fieldTextPlain.style.display = "none";
1842
+ if (fieldTextRich) fieldTextRich.style.display = "block";
1843
+
1844
+ // init editor once (falls back silently if CDN blocked)
1845
+ ensureTinyMCE();
1846
+
1847
+ const html = String(it.textHtml || "").trim()
1848
+ ? String(it.textHtml)
1849
+ : plainToHtml(it.text || "");
1850
+
1851
+ setRichHtml(html);
1852
+ } else {
1853
+ if (fieldTextPlain) fieldTextPlain.style.display = "block";
1854
+ if (fieldTextRich) fieldTextRich.style.display = "none";
1855
+
1856
+ inspTextPlain.value = safeText(it.text || "");
1857
+ }
1688
1858
  inspCols.value = "1";
1689
1859
 
1690
1860
  // image
@@ -1722,7 +1892,7 @@
1722
1892
  if (!s) return;
1723
1893
 
1724
1894
  s.title = inspTitle.value;
1725
- s.text = inspText.value;
1895
+ s.text = inspTextPlain.value;
1726
1896
  s.cols = parseInt(inspCols.value || "1", 10);
1727
1897
  s.imageUrl = inspImg.value;
1728
1898
 
@@ -1745,7 +1915,25 @@
1745
1915
  const t = String(it.type || "").toLowerCase();
1746
1916
 
1747
1917
  it.title = inspTitle.value;
1748
- it.text = inspText.value;
1918
+ const useRich = (t !== "button");
1919
+
1920
+ it.title = inspTitle.value;
1921
+
1922
+ // save align (empty/default removes the property)
1923
+ const ta = (inspTitleAlign ? String(inspTitleAlign.value || "").trim() : "");
1924
+ const ba = (inspTextAlign ? String(inspTextAlign.value || "").trim() : "");
1925
+
1926
+ if (!ta) delete it.titleAlign; else it.titleAlign = ta;
1927
+ if (!ba) delete it.textAlign; else it.textAlign = ba;
1928
+
1929
+ if (useRich){
1930
+ const html = getRichHtml();
1931
+ it.textHtml = html;
1932
+ it.text = htmlToText(html); // keep a readable plain version for preview/search
1933
+ } else {
1934
+ it.text = inspTextPlain.value;
1935
+ delete it.textHtml;
1936
+ }
1749
1937
 
1750
1938
  if (t === "button"){
1751
1939
  it.href = (inspHref.value || "").trim();
@@ -2345,13 +2533,6 @@
2345
2533
  }
2346
2534
  });
2347
2535
 
2348
- // Clear canvas
2349
- document.getElementById("btn-clear").addEventListener("click", ()=>{
2350
- state.sections = [];
2351
- clearSelection();
2352
- render();
2353
- });
2354
-
2355
2536
  // ----------------------------
2356
2537
  // CodeMirror init
2357
2538
  // ----------------------------