@alpaca-editor/core 1.0.4094 → 1.0.4096

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 (43) hide show
  1. package/dist/editor/ai/AgentTerminal.js +151 -58
  2. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  3. package/dist/editor/ai/ContextInfoBar.d.ts +11 -0
  4. package/dist/editor/ai/ContextInfoBar.js +347 -0
  5. package/dist/editor/ai/ContextInfoBar.js.map +1 -0
  6. package/dist/editor/ai/DancingDots.js +1 -1
  7. package/dist/editor/ai/DancingDots.js.map +1 -1
  8. package/dist/editor/client/itemsRepository.js +5 -2
  9. package/dist/editor/client/itemsRepository.js.map +1 -1
  10. package/dist/editor/control-center/setup-steps/{AiSetupStep.d.ts → AiSetupStep/index.d.ts} +1 -0
  11. package/dist/editor/control-center/setup-steps/{AiSetupStep.js → AiSetupStep/index.js} +11 -17
  12. package/dist/editor/control-center/setup-steps/AiSetupStep/index.js.map +1 -0
  13. package/dist/editor/control-center/setup-steps/AiSetupStep/provider/ProviderSection.d.ts +15 -0
  14. package/dist/editor/control-center/setup-steps/AiSetupStep/provider/ProviderSection.js +10 -0
  15. package/dist/editor/control-center/setup-steps/AiSetupStep/provider/ProviderSection.js.map +1 -0
  16. package/dist/editor/control-center/setup-steps/AiSetupStep/required-containers/RequiredContainersList.d.ts +15 -0
  17. package/dist/editor/control-center/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js +14 -0
  18. package/dist/editor/control-center/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js.map +1 -0
  19. package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.d.ts +7 -0
  20. package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.js +8 -0
  21. package/dist/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.js.map +1 -0
  22. package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -1
  23. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  24. package/dist/editor/page-editor-chrome/InlineEditor.js +102 -7
  25. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  26. package/dist/page-wizard/steps/ContentStep.js +5 -2
  27. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  28. package/dist/revision.d.ts +2 -2
  29. package/dist/revision.js +2 -2
  30. package/package.json +1 -1
  31. package/src/editor/ai/AgentTerminal.tsx +181 -157
  32. package/src/editor/ai/ContextInfoBar.tsx +551 -0
  33. package/src/editor/ai/DancingDots.tsx +1 -1
  34. package/src/editor/client/itemsRepository.ts +5 -5
  35. package/src/editor/control-center/setup-steps/{AiSetupStep.tsx → AiSetupStep/index.tsx} +38 -131
  36. package/src/editor/control-center/setup-steps/AiSetupStep/provider/ProviderSection.tsx +74 -0
  37. package/src/editor/control-center/setup-steps/AiSetupStep/required-containers/RequiredContainersList.tsx +92 -0
  38. package/src/editor/control-center/setup-steps/AiSetupStep/tools/GenerateToolsSection.tsx +32 -0
  39. package/src/editor/menubar/toolbar-sections/EditControls.tsx +1 -0
  40. package/src/editor/page-editor-chrome/InlineEditor.tsx +129 -7
  41. package/src/page-wizard/steps/ContentStep.tsx +46 -0
  42. package/src/revision.ts +2 -2
  43. package/dist/editor/control-center/setup-steps/AiSetupStep.js.map +0 -1
@@ -1,21 +1,16 @@
1
1
  import React, { useCallback, useMemo, useState } from "react";
2
- import { Card } from "../../../components/ui/card";
3
- import { Button } from "../../../components/ui/button";
4
- import { Select } from "../../../components/ui/select";
5
- import { Input } from "../../../components/ui/input";
6
- import { useEditContext } from "../../client/editContext";
7
- import { ItemDescriptor } from "../../pageModel";
8
- import { getChildren } from "../../services/contentService";
9
- import type { ItemTreeNodeData } from "../../services/contentService";
10
- import {
11
- CheckCircle,
12
- RefreshCw,
13
- SparklesIcon,
14
- AlertCircle,
15
- } from "lucide-react";
16
- import { contentItemId } from "../../../config/config";
17
-
18
- type StepState = "idle" | "checking" | "success" | "error";
2
+ import { Card } from "../../../../components/ui/card";
3
+ import { useEditContext } from "../../../client/editContext";
4
+ import { ItemDescriptor } from "../../../pageModel";
5
+ import { getChildren } from "../../../services/contentService";
6
+ import type { ItemTreeNodeData } from "../../../services/contentService";
7
+ import { SparklesIcon } from "lucide-react";
8
+ import { contentItemId } from "../../../../config/config";
9
+ import { RequiredContainersList } from "./required-containers/RequiredContainersList";
10
+ import { ProviderSection } from "./provider/ProviderSection";
11
+ import { GenerateToolsSection } from "./tools/GenerateToolsSection";
12
+
13
+ export type StepState = "idle" | "checking" | "success" | "error";
19
14
 
20
15
  function findByName(
21
16
  items: ItemTreeNodeData[],
@@ -577,7 +572,7 @@ export function AiSetupStep() {
577
572
  } finally {
578
573
  setAiBusy(false);
579
574
  }
580
- }, [aiProvider, editContext, userLang, resolvePathUnderContent]);
575
+ }, [aiProvider, editContext, userLang, resolvePathUnderContent, apiKey]);
581
576
 
582
577
  const checkRequiredContainers = useCallback(async () => {
583
578
  try {
@@ -659,119 +654,31 @@ export function AiSetupStep() {
659
654
  title="AI setup"
660
655
  description="Add an AI endpoint (OpenAI, Azure OpenAI, or OpenRouter)."
661
656
  >
662
- <div className="flex flex-col gap-2">
663
- {requiredContainers
664
- .filter((rc) => containerStates[rc.name] === "error")
665
- .map((rc) => (
666
- <div
667
- key={rc.name}
668
- className="flex items-center justify-between gap-2"
669
- >
670
- <div className="flex items-center gap-2">
671
- {containerStates[rc.name] === "checking" ? (
672
- <RefreshCw
673
- strokeWidth={1}
674
- className="h-4 w-4 animate-spin text-amber-600"
675
- />
676
- ) : containerStates[rc.name] === "success" ? (
677
- <CheckCircle
678
- strokeWidth={1}
679
- className="h-4 w-4 text-green-600"
680
- />
681
- ) : (
682
- <AlertCircle
683
- strokeWidth={1}
684
- className="h-4 w-4 text-red-600"
685
- />
686
- )}
687
- <span className="text-sm text-gray-700">
688
- {containerStates[rc.name] === "checking"
689
- ? `Checking ${rc.name}...`
690
- : containerStates[rc.name] === "success"
691
- ? `${rc.name} present`
692
- : `${rc.name} missing`}
693
- </span>
694
- </div>
695
- {containerStates[rc.name] !== "success" && (
696
- <Button
697
- size="sm"
698
- onClick={() => createContainer(rc.name, rc.templateId)}
699
- disabled={!editorSettingsItem || !!busyMap[rc.name]}
700
- >
701
- {busyMap[rc.name] ? (
702
- <RefreshCw
703
- strokeWidth={1}
704
- className="h-4 w-4 animate-spin"
705
- />
706
- ) : (
707
- <CheckCircle strokeWidth={1} className="h-4 w-4" />
708
- )}
709
- Fix
710
- </Button>
711
- )}
712
- </div>
713
- ))}
714
- </div>
715
- {containersError && (
716
- <div className="mt-2 rounded border border-yellow-200 bg-yellow-50 p-2 text-xs whitespace-pre-wrap text-yellow-800">
717
- {containersError}
718
- </div>
719
- )}
720
- <div className="flex items-center justify-between gap-2">
721
- <div className="flex items-center gap-2">
722
- <CheckCircle strokeWidth={1} className="h-4 w-4 text-green-600" />
723
- <span className="text-sm text-gray-700">
724
- Select provider and add endpoint
725
- </span>
726
- </div>
727
- </div>
728
- <div className="mt-3 flex items-center gap-2">
729
- <Select
730
- value={aiProvider}
731
- onValueChange={setAiProvider}
732
- options={aiOptions}
733
- placeholder="Choose provider"
734
- />
735
- {aiProvider && (
736
- <Input
737
- type="password"
738
- placeholder="API key"
739
- value={apiKey}
740
- onChange={(e) => setApiKey(e.target.value)}
741
- className="w-56"
742
- />
743
- )}
744
- <Button
745
- size="sm"
746
- disabled={!aiProvider || !apiKey || aiBusy}
747
- onClick={createAiEndpoint}
748
- >
749
- {aiBusy ? (
750
- <RefreshCw strokeWidth={1} className="h-4 w-4 animate-spin" />
751
- ) : (
752
- <CheckCircle strokeWidth={1} className="h-4 w-4" />
753
- )}
754
- Add endpoint
755
- </Button>
756
- </div>
757
- <div className="mt-4 flex items-center gap-2">
758
- <Button
759
- size="sm"
760
- variant="outline"
761
- disabled={toolsBusy}
762
- onClick={ensureAiToolsAndGenerate}
763
- >
764
- {toolsBusy ? (
765
- <RefreshCw strokeWidth={1} className="h-4 w-4 animate-spin" />
766
- ) : (
767
- <CheckCircle strokeWidth={1} className="h-4 w-4" />
768
- )}
769
- Generate AI tool items
770
- </Button>
771
- {toolsError && (
772
- <span className="text-xs text-red-600">{toolsError}</span>
773
- )}
774
- </div>
657
+ <RequiredContainersList
658
+ requiredContainers={requiredContainers}
659
+ containerStates={containerStates}
660
+ busyMap={busyMap}
661
+ canFix={!!editorSettingsItem}
662
+ containersError={containersError}
663
+ onFixContainer={createContainer}
664
+ />
665
+
666
+ <ProviderSection
667
+ aiProvider={aiProvider}
668
+ aiOptions={aiOptions}
669
+ onProviderChange={setAiProvider}
670
+ apiKey={apiKey}
671
+ onApiKeyChange={setApiKey}
672
+ aiBusy={aiBusy}
673
+ onAddEndpoint={createAiEndpoint}
674
+ />
675
+
676
+ <GenerateToolsSection
677
+ toolsBusy={toolsBusy}
678
+ toolsError={toolsError}
679
+ onGenerateTools={ensureAiToolsAndGenerate}
680
+ />
681
+
775
682
  {aiError && (
776
683
  <div className="mt-2 rounded border border-red-200 bg-red-50 p-2 text-xs whitespace-pre-wrap text-red-700">
777
684
  {aiError}
@@ -0,0 +1,74 @@
1
+ import React from "react";
2
+ import { Select } from "../../../../../components/ui/select";
3
+ import { Input } from "../../../../../components/ui/input";
4
+ import { Button } from "../../../../../components/ui/button";
5
+ import { CheckCircle, RefreshCw } from "lucide-react";
6
+
7
+ interface ProviderOption {
8
+ value: string;
9
+ label: string;
10
+ }
11
+
12
+ interface ProviderSectionProps {
13
+ aiProvider: string;
14
+ aiOptions: ProviderOption[];
15
+ onProviderChange: (value: string) => void;
16
+ apiKey: string;
17
+ onApiKeyChange: (value: string) => void;
18
+ aiBusy: boolean;
19
+ onAddEndpoint: () => void;
20
+ }
21
+
22
+ export function ProviderSection(props: ProviderSectionProps) {
23
+ const {
24
+ aiProvider,
25
+ aiOptions,
26
+ onProviderChange,
27
+ apiKey,
28
+ onApiKeyChange,
29
+ aiBusy,
30
+ onAddEndpoint,
31
+ } = props;
32
+
33
+ return (
34
+ <>
35
+ <div className="flex items-center justify-between gap-2">
36
+ <div className="flex items-center gap-2">
37
+ <CheckCircle strokeWidth={1} className="h-4 w-4 text-green-600" />
38
+ <span className="text-sm text-gray-700">
39
+ Select provider and add endpoint
40
+ </span>
41
+ </div>
42
+ </div>
43
+ <div className="mt-3 flex items-center gap-2">
44
+ <Select
45
+ value={aiProvider}
46
+ onValueChange={onProviderChange}
47
+ options={aiOptions}
48
+ placeholder="Choose provider"
49
+ />
50
+ {aiProvider && (
51
+ <Input
52
+ type="password"
53
+ placeholder="API key"
54
+ value={apiKey}
55
+ onChange={(e) => onApiKeyChange(e.target.value)}
56
+ className="w-56"
57
+ />
58
+ )}
59
+ <Button
60
+ size="sm"
61
+ disabled={!aiProvider || !apiKey || aiBusy}
62
+ onClick={onAddEndpoint}
63
+ >
64
+ {aiBusy ? (
65
+ <RefreshCw strokeWidth={1} className="h-4 w-4 animate-spin" />
66
+ ) : (
67
+ <CheckCircle strokeWidth={1} className="h-4 w-4" />
68
+ )}
69
+ Add endpoint
70
+ </Button>
71
+ </div>
72
+ </>
73
+ );
74
+ }
@@ -0,0 +1,92 @@
1
+ import React from "react";
2
+ import { Button } from "../../../../../components/ui/button";
3
+ import { AlertCircle, CheckCircle, RefreshCw } from "lucide-react";
4
+ import type { StepState } from "../index";
5
+
6
+ interface RequiredContainerDef {
7
+ name: string;
8
+ templateId: string;
9
+ }
10
+
11
+ interface RequiredContainersListProps {
12
+ requiredContainers: RequiredContainerDef[];
13
+ containerStates: Record<string, StepState>;
14
+ busyMap: Record<string, boolean>;
15
+ canFix: boolean;
16
+ containersError: string | null;
17
+ onFixContainer: (name: string, templateId: string) => void;
18
+ }
19
+
20
+ export function RequiredContainersList(props: RequiredContainersListProps) {
21
+ const {
22
+ requiredContainers,
23
+ containerStates,
24
+ busyMap,
25
+ canFix,
26
+ containersError,
27
+ onFixContainer,
28
+ } = props;
29
+
30
+ return (
31
+ <>
32
+ <div className="flex flex-col gap-2">
33
+ {requiredContainers
34
+ .filter((rc) => containerStates[rc.name] === "error")
35
+ .map((rc) => (
36
+ <div
37
+ key={rc.name}
38
+ className="flex items-center justify-between gap-2"
39
+ >
40
+ <div className="flex items-center gap-2">
41
+ {containerStates[rc.name] === "checking" ? (
42
+ <RefreshCw
43
+ strokeWidth={1}
44
+ className="h-4 w-4 animate-spin text-amber-600"
45
+ />
46
+ ) : containerStates[rc.name] === "success" ? (
47
+ <CheckCircle
48
+ strokeWidth={1}
49
+ className="h-4 w-4 text-green-600"
50
+ />
51
+ ) : (
52
+ <AlertCircle
53
+ strokeWidth={1}
54
+ className="h-4 w-4 text-red-600"
55
+ />
56
+ )}
57
+ <span className="text-sm text-gray-700">
58
+ {containerStates[rc.name] === "checking"
59
+ ? `Checking ${rc.name}...`
60
+ : containerStates[rc.name] === "success"
61
+ ? `${rc.name} present`
62
+ : `${rc.name} missing`}
63
+ </span>
64
+ </div>
65
+ {containerStates[rc.name] !== "success" && (
66
+ <Button
67
+ size="sm"
68
+ onClick={() => onFixContainer(rc.name, rc.templateId)}
69
+ disabled={!canFix || !!busyMap[rc.name]}
70
+ >
71
+ {busyMap[rc.name] ? (
72
+ <RefreshCw
73
+ strokeWidth={1}
74
+ className="h-4 w-4 animate-spin"
75
+ />
76
+ ) : (
77
+ <CheckCircle strokeWidth={1} className="h-4 w-4" />
78
+ )}
79
+ Fix
80
+ </Button>
81
+ )}
82
+ </div>
83
+ ))}
84
+ </div>
85
+ {containersError && (
86
+ <div className="mt-2 rounded border border-yellow-200 bg-yellow-50 p-2 text-xs whitespace-pre-wrap text-yellow-800">
87
+ {containersError}
88
+ </div>
89
+ )}
90
+ </>
91
+ );
92
+ }
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import { Button } from "../../../../../components/ui/button";
3
+ import { CheckCircle, RefreshCw } from "lucide-react";
4
+
5
+ interface GenerateToolsSectionProps {
6
+ toolsBusy: boolean;
7
+ toolsError: string | null;
8
+ onGenerateTools: () => void;
9
+ }
10
+
11
+ export function GenerateToolsSection(props: GenerateToolsSectionProps) {
12
+ const { toolsBusy, toolsError, onGenerateTools } = props;
13
+
14
+ return (
15
+ <div className="mt-4 flex items-center gap-2">
16
+ <Button
17
+ size="sm"
18
+ variant="outline"
19
+ disabled={toolsBusy}
20
+ onClick={onGenerateTools}
21
+ >
22
+ {toolsBusy ? (
23
+ <RefreshCw strokeWidth={1} className="h-4 w-4 animate-spin" />
24
+ ) : (
25
+ <CheckCircle strokeWidth={1} className="h-4 w-4" />
26
+ )}
27
+ Generate AI tool items
28
+ </Button>
29
+ {toolsError && <span className="text-xs text-red-600">{toolsError}</span>}
30
+ </div>
31
+ );
32
+ }
@@ -56,6 +56,7 @@ export function EditControls({
56
56
  icon={<MessagesSquare strokeWidth={1} className="h-6 w-6 p-1" />}
57
57
  label="Write suggestions"
58
58
  size="large"
59
+ data-testid="suggestions-mode-button"
59
60
  onClick={() => editContext?.setMode("suggestions")}
60
61
  >
61
62
  {totalCount > 0 && (
@@ -48,6 +48,73 @@ export function InlineEditor({
48
48
  );
49
49
  };
50
50
 
51
+ // Helper: set innerHTML and log with stack trace when content changes
52
+ function setInnerHTMLWithStackTrace(
53
+ element: HTMLElement,
54
+ nextHTML: string,
55
+ meta: {
56
+ source: string;
57
+ fieldId?: string | null;
58
+ itemId?: string | null;
59
+ language?: string | null;
60
+ version?: number | null;
61
+ },
62
+ ): boolean {
63
+ const prevHTML = element.innerHTML ?? "";
64
+ if (prevHTML === nextHTML) return false;
65
+
66
+ element.innerHTML = nextHTML;
67
+
68
+ // try {
69
+ // const resolvedFieldId =
70
+ // meta.fieldId ?? element.getAttribute("data-fieldid");
71
+ // const resolvedItemId = meta.itemId ?? element.getAttribute("data-itemid");
72
+ // const resolvedLanguage =
73
+ // meta.language ?? element.getAttribute("data-language");
74
+ // const resolvedVersion =
75
+ // meta.version ??
76
+ // (element.getAttribute("data-version")
77
+ // ? parseInt(element.getAttribute("data-version") as string, 10)
78
+ // : null);
79
+
80
+ // // Log concise info and stack trace
81
+ // // Limit previews to keep console tidy
82
+ // const preview = (s: string) =>
83
+ // s.length > 200 ? s.slice(0, 200) + "…" : s;
84
+ // console.groupCollapsed(
85
+ // `[InlineEditor] DOM field update (${meta.source}) ${nextHTML} `,
86
+ // );
87
+
88
+ // const modifiedFieldValue = modifiedFieldsContext?.modifiedFields.find(
89
+ // (x) =>
90
+ // x.fieldId === resolvedFieldId &&
91
+ // x.item.id === resolvedItemId &&
92
+ // x.item.language === resolvedLanguage &&
93
+ // x.item.version === resolvedVersion,
94
+ // )?.value;
95
+
96
+ // console.log({
97
+ // fieldId: resolvedFieldId,
98
+ // itemId: resolvedItemId,
99
+ // language: resolvedLanguage,
100
+ // version: resolvedVersion,
101
+ // prevLength: prevHTML.length,
102
+ // nextLength: nextHTML.length,
103
+ // prevPreview: preview(prevHTML),
104
+ // nextPreview: preview(nextHTML),
105
+ // modifiedFieldsContext: modifiedFieldsContext?.modifiedFields,
106
+ // repository: contextRef.current?.itemsRepository,
107
+ // modifiedFieldValue,
108
+ // });
109
+ // console.trace();
110
+ // console.groupEnd();
111
+ // } catch {
112
+ // // no-op logging failure
113
+ // }
114
+
115
+ return true;
116
+ }
117
+
51
118
  const debouncedSetFieldvalue = useThrottledCallback(
52
119
  (capturedValue, fieldId, fieldName, itemId, language, version) => {
53
120
  // Use the value captured at mutation time to avoid cross-field leakage
@@ -362,12 +429,24 @@ export function InlineEditor({
362
429
  }
363
430
  // Update focused field element with the merged plain text value.
364
431
 
365
- element.innerHTML = mergedValue;
432
+ setInnerHTMLWithStackTrace(element as HTMLElement, mergedValue, {
433
+ source: "updateFocusedFieldContent:mergedValue",
434
+ fieldId,
435
+ itemId,
436
+ language,
437
+ version,
438
+ });
366
439
  contentWasUpdated = true;
367
440
  } else {
368
441
  if (element.innerHTML !== baseValue) {
369
442
  // If suggestions mode is off, update with the base value.
370
- element.innerHTML = baseValue;
443
+ setInnerHTMLWithStackTrace(element as HTMLElement, baseValue, {
444
+ source: "updateFocusedFieldContent:baseValue",
445
+ fieldId,
446
+ itemId,
447
+ language,
448
+ version,
449
+ });
371
450
  contentWasUpdated = true;
372
451
  }
373
452
  }
@@ -540,7 +619,18 @@ export function InlineEditor({
540
619
  if (fieldType && isTextFieldType(fieldType)) {
541
620
  const next = field?.value ? (field.value as string) : "";
542
621
  if (element.innerHTML !== next) {
543
- element.innerHTML = next;
622
+ setInnerHTMLWithStackTrace(element as HTMLElement, next, {
623
+ source: "updateFieldsWithSuggestions:modifiedField",
624
+ fieldId: element.getAttribute("data-fieldid"),
625
+ itemId: element.getAttribute("data-itemid"),
626
+ language: element.getAttribute("data-language"),
627
+ version: element.getAttribute("data-version")
628
+ ? parseInt(
629
+ element.getAttribute("data-version") as string,
630
+ 10,
631
+ )
632
+ : null,
633
+ });
544
634
  }
545
635
  }
546
636
  }
@@ -645,7 +735,17 @@ export function InlineEditor({
645
735
  // If showSuggestedEdits is false, update with the base value.
646
736
  if (!context.showSuggestedEdits && context.mode !== "suggestions") {
647
737
  if (fieldElement.innerHTML !== originalValue) {
648
- fieldElement.innerHTML = originalValue;
738
+ setInnerHTMLWithStackTrace(
739
+ fieldElement as HTMLElement,
740
+ originalValue,
741
+ {
742
+ source: "updateFieldsWithSuggestions:baselineOnly",
743
+ fieldId,
744
+ itemId,
745
+ language,
746
+ version,
747
+ },
748
+ );
649
749
  }
650
750
 
651
751
  return;
@@ -700,7 +800,17 @@ export function InlineEditor({
700
800
  // If showSuggestedEditsDiff is false, show only the merged text
701
801
  if (!context.showSuggestedEditsDiff) {
702
802
  if (fieldElement.innerHTML !== mergedValue) {
703
- fieldElement.innerHTML = mergedValue;
803
+ setInnerHTMLWithStackTrace(
804
+ fieldElement as HTMLElement,
805
+ mergedValue,
806
+ {
807
+ source: "updateFieldsWithSuggestions:mergedNoDiff",
808
+ fieldId,
809
+ itemId,
810
+ language,
811
+ version,
812
+ },
813
+ );
704
814
  }
705
815
  return;
706
816
  }
@@ -725,7 +835,13 @@ export function InlineEditor({
725
835
 
726
836
  // Update the element's innerHTML with the diff markup.
727
837
  if (fieldElement.innerHTML !== diffHTML) {
728
- fieldElement.innerHTML = diffHTML;
838
+ setInnerHTMLWithStackTrace(fieldElement as HTMLElement, diffHTML, {
839
+ source: "updateFieldsWithSuggestions:diffMarkup",
840
+ fieldId,
841
+ itemId,
842
+ language,
843
+ version,
844
+ });
729
845
  }
730
846
  });
731
847
  };
@@ -823,7 +939,13 @@ export function InlineEditor({
823
939
 
824
940
  // write it in
825
941
  if (el.innerHTML !== value) {
826
- el.innerHTML = value;
942
+ setInnerHTMLWithStackTrace(el as HTMLElement, value, {
943
+ source: "resetAllFieldsToBaselineOrLocal",
944
+ fieldId,
945
+ itemId,
946
+ language,
947
+ version,
948
+ });
827
949
  }
828
950
  });
829
951
  }, [context.mode, context.showSuggestedEdits, context.suggestedEdits]);
@@ -84,6 +84,7 @@ export function ContentStep({
84
84
  const [message, setMessage] = useState<string>();
85
85
  const [isCreatingComponents, setIsCreatingComponents] = useState(false);
86
86
  const [pageItem, setPageItem] = useState<ItemDescriptor>();
87
+ const [showDataObject, setShowDataObject] = useState(false);
87
88
 
88
89
  // New state for name and language functionality
89
90
  const [isGenerating, setIsGenerating] = useState(false);
@@ -1192,6 +1193,51 @@ export function ContentStep({
1192
1193
  }
1193
1194
  >
1194
1195
  <div>{customInstructionsPanel()}</div>
1196
+ <div className="mt-2">
1197
+ <button
1198
+ className="flex items-center text-xs text-blue-600 hover:underline"
1199
+ onClick={() => setShowDataObject(!showDataObject)}
1200
+ >
1201
+ <ChevronDown
1202
+ strokeWidth={1}
1203
+ className={`mr-1 h-4 w-4 transition-transform duration-200 ${showDataObject ? "rotate-180" : "rotate-0"}`}
1204
+ />
1205
+ {showDataObject
1206
+ ? "Hide data available to instructions"
1207
+ : "Show data available to instructions"}
1208
+ </button>
1209
+ <div className="mt-2 text-xs text-gray-600">
1210
+ <div className="font-medium">
1211
+ Referencing data in instructions
1212
+ </div>
1213
+ <div>Use curly braces to inject values from the data object:</div>
1214
+ <div className="mt-1 grid gap-1">
1215
+ <div>
1216
+ <code>{"{title}"}</code> inserts <code>data.title</code>
1217
+ </div>
1218
+ <div>
1219
+ <code>{"{data.user.name}"}</code> inserts a nested property
1220
+ </div>
1221
+ <div>
1222
+ <code>{"{items.filter(x => x.active).length}"}</code> supports
1223
+ JavaScript expressions
1224
+ </div>
1225
+ <div>
1226
+ <code>{"{data}"}</code> inserts the entire data object as JSON
1227
+ </div>
1228
+ </div>
1229
+ <div className="mt-1">
1230
+ Arrays/objects are inserted as JSON text.
1231
+ </div>
1232
+ </div>
1233
+ {showDataObject && (
1234
+ <div className="mt-2 rounded border border-gray-200 bg-gray-50 p-2">
1235
+ <pre className="max-h-64 overflow-auto text-xs break-words whitespace-pre-wrap">
1236
+ {JSON.stringify(data, null, 2)}
1237
+ </pre>
1238
+ </div>
1239
+ )}
1240
+ </div>
1195
1241
  </WizardBox>
1196
1242
  </div>
1197
1243
  {pageItem && (
package/src/revision.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const version = "1.0.4094";
2
- export const buildDate = "2025-09-18 16:11:16";
1
+ export const version = "1.0.4096";
2
+ export const buildDate = "2025-09-19 08:03:30";