@alpaca-editor/core 1.0.4069 → 1.0.4073

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 (106) hide show
  1. package/dist/components/index.d.ts +2 -0
  2. package/dist/components/index.js +1 -0
  3. package/dist/components/index.js.map +1 -1
  4. package/dist/components/ui/select.d.ts +17 -0
  5. package/dist/components/ui/select.js +32 -0
  6. package/dist/components/ui/select.js.map +1 -0
  7. package/dist/components/ui/textarea.js +1 -1
  8. package/dist/components/ui/textarea.js.map +1 -1
  9. package/dist/editor/FieldListField.js.map +1 -1
  10. package/dist/editor/FieldListFieldWithFallbacks.js +8 -7
  11. package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
  12. package/dist/editor/ai/AgentTerminal.js +43 -6
  13. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  14. package/dist/editor/ai/AiResponseMessage.js +93 -45
  15. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  16. package/dist/editor/client/EditorClient.js +44 -26
  17. package/dist/editor/client/EditorClient.js.map +1 -1
  18. package/dist/editor/client/editContext.d.ts +10 -6
  19. package/dist/editor/client/editContext.js +12 -3
  20. package/dist/editor/client/editContext.js.map +1 -1
  21. package/dist/editor/client/itemsRepository.js +1 -2
  22. package/dist/editor/client/itemsRepository.js.map +1 -1
  23. package/dist/editor/commands/componentCommands.js +2 -8
  24. package/dist/editor/commands/componentCommands.js.map +1 -1
  25. package/dist/editor/editor-warnings/ValidationErrors.js +5 -2
  26. package/dist/editor/editor-warnings/ValidationErrors.js.map +1 -1
  27. package/dist/editor/field-types/DropLinkEditor.js +12 -24
  28. package/dist/editor/field-types/DropLinkEditor.js.map +1 -1
  29. package/dist/editor/field-types/DropListEditor.js +14 -25
  30. package/dist/editor/field-types/DropListEditor.js.map +1 -1
  31. package/dist/editor/field-types/MultiLineText.js +9 -9
  32. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  33. package/dist/editor/field-types/RichTextEditorComponent.js +1 -1
  34. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  35. package/dist/editor/field-types/SingleLineText.js +18 -17
  36. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  37. package/dist/editor/page-editor-chrome/CommentHighlighting.js +4 -3
  38. package/dist/editor/page-editor-chrome/CommentHighlighting.js.map +1 -1
  39. package/dist/editor/page-editor-chrome/FieldEditedIndicator.js +3 -2
  40. package/dist/editor/page-editor-chrome/FieldEditedIndicator.js.map +1 -1
  41. package/dist/editor/page-editor-chrome/FieldEditedIndicators.js +2 -2
  42. package/dist/editor/page-editor-chrome/FieldEditedIndicators.js.map +1 -1
  43. package/dist/editor/page-editor-chrome/InlineEditor.js +15 -14
  44. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  45. package/dist/editor/page-editor-chrome/SuggestionHighlighting.js +4 -3
  46. package/dist/editor/page-editor-chrome/SuggestionHighlighting.js.map +1 -1
  47. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +17 -13
  48. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
  49. package/dist/editor/page-viewer/PageViewer.js +3 -3
  50. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  51. package/dist/editor/page-viewer/PageViewerFrame.js +14 -12
  52. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  53. package/dist/editor/reviews/Comment.js +3 -2
  54. package/dist/editor/reviews/Comment.js.map +1 -1
  55. package/dist/editor/reviews/CommentPopover.js +6 -5
  56. package/dist/editor/reviews/CommentPopover.js.map +1 -1
  57. package/dist/editor/reviews/SuggestedEdit.js +6 -5
  58. package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
  59. package/dist/editor/reviews/SuggestionDisplayPopover.js +3 -2
  60. package/dist/editor/reviews/SuggestionDisplayPopover.js.map +1 -1
  61. package/dist/editor/sidebar/DictionaryEditor.js +5 -5
  62. package/dist/editor/sidebar/DictionaryEditor.js.map +1 -1
  63. package/dist/editor/sidebar/EditHistory.js +9 -4
  64. package/dist/editor/sidebar/EditHistory.js.map +1 -1
  65. package/dist/editor/sidebar/Validation.js +3 -2
  66. package/dist/editor/sidebar/Validation.js.map +1 -1
  67. package/dist/page-wizard/steps/ContentStep.js +4 -3
  68. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  69. package/dist/revision.d.ts +2 -2
  70. package/dist/revision.js +2 -2
  71. package/dist/styles.css +3 -0
  72. package/package.json +1 -1
  73. package/src/components/index.ts +2 -0
  74. package/src/components/ui/select.tsx +134 -0
  75. package/src/components/ui/textarea.tsx +1 -1
  76. package/src/editor/FieldListField.tsx +1 -1
  77. package/src/editor/FieldListFieldWithFallbacks.tsx +15 -7
  78. package/src/editor/ai/AgentTerminal.tsx +44 -6
  79. package/src/editor/ai/AiResponseMessage.tsx +125 -67
  80. package/src/editor/client/EditorClient.tsx +52 -30
  81. package/src/editor/client/editContext.ts +29 -10
  82. package/src/editor/client/itemsRepository.ts +1 -2
  83. package/src/editor/commands/componentCommands.tsx +3 -10
  84. package/src/editor/editor-warnings/ValidationErrors.tsx +11 -7
  85. package/src/editor/field-types/DropLinkEditor.tsx +21 -31
  86. package/src/editor/field-types/DropListEditor.tsx +28 -35
  87. package/src/editor/field-types/MultiLineText.tsx +11 -12
  88. package/src/editor/field-types/RichTextEditorComponent.tsx +4 -8
  89. package/src/editor/field-types/SingleLineText.tsx +21 -19
  90. package/src/editor/page-editor-chrome/CommentHighlighting.tsx +4 -6
  91. package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +7 -2
  92. package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +2 -2
  93. package/src/editor/page-editor-chrome/InlineEditor.tsx +20 -14
  94. package/src/editor/page-editor-chrome/SuggestionHighlighting.tsx +4 -6
  95. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +17 -13
  96. package/src/editor/page-viewer/PageViewer.tsx +3 -6
  97. package/src/editor/page-viewer/PageViewerFrame.tsx +21 -12
  98. package/src/editor/reviews/Comment.tsx +3 -2
  99. package/src/editor/reviews/CommentPopover.tsx +10 -5
  100. package/src/editor/reviews/SuggestedEdit.tsx +6 -5
  101. package/src/editor/reviews/SuggestionDisplayPopover.tsx +3 -2
  102. package/src/editor/sidebar/DictionaryEditor.tsx +10 -13
  103. package/src/editor/sidebar/EditHistory.tsx +11 -4
  104. package/src/editor/sidebar/Validation.tsx +3 -2
  105. package/src/page-wizard/steps/ContentStep.tsx +4 -3
  106. package/src/revision.ts +2 -2
@@ -1,13 +1,12 @@
1
1
  "use client";
2
2
 
3
- import { Dropdown, DropdownChangeEvent } from "primereact/dropdown";
4
-
5
3
  import { useEditContext } from "../client/editContext";
6
- import { useEffect, useState } from "react";
4
+ import { useEffect, useState, useCallback, useMemo } from "react";
7
5
 
8
6
  import { getLookupSources } from "../services/editService";
9
7
  import { TextField } from "../fieldTypes";
10
8
  import { ItemIdAndName } from "../pageModel";
9
+ import { Select, SelectOption } from "../../components";
11
10
 
12
11
  export function DropListEditor({
13
12
  field,
@@ -29,7 +28,7 @@ export function DropListEditor({
29
28
  onLazyLoad();
30
29
  }, [field.descriptor]);
31
30
 
32
- const onLazyLoad = async () => {
31
+ const onLazyLoad = useCallback(async () => {
33
32
  setLazyLoading(true);
34
33
  const options = await getLookupSources(field, editContext.sessionId);
35
34
  if (field.value && !options.find((o) => o.name === field.value)) {
@@ -41,44 +40,38 @@ export function DropListEditor({
41
40
  }
42
41
  setLazyItems(options);
43
42
  setLazyLoading(false);
44
- };
43
+ }, [field, editContext.sessionId]);
44
+
45
+ // Convert ItemIdAndName[] to SelectOption[] using name for both value and label - memoized
46
+ const selectOptions: SelectOption[] = useMemo(() =>
47
+ lazyItems.map((item) => ({
48
+ value: item.name,
49
+ label: item.name,
50
+ })), [lazyItems]
51
+ );
52
+
53
+ // Find the current value or show unknown value - memoized
54
+ const currentValue = useMemo(() =>
55
+ lazyItems.find((o) => o.name === field.value)?.name || field.value,
56
+ [lazyItems, field.value]
57
+ );
45
58
 
46
59
  return (
47
- <Dropdown
48
- value={
49
- lazyItems.find((o) => o.name === field.value)?.name ||
50
- "[Unknown: " + field.value + "]"
51
- }
52
- disabled={readOnly}
53
- onChange={(e: DropdownChangeEvent) => {
60
+ <Select
61
+ value={currentValue}
62
+ onValueChange={(value) => {
54
63
  editContext?.operations.editField({
55
64
  field: field.descriptor,
56
- rawValue: e.value,
65
+ rawValue: value,
57
66
  });
58
67
  }}
59
- options={lazyItems}
60
- optionLabel="name"
61
- optionValue="name"
68
+ options={selectOptions}
62
69
  placeholder="Select"
63
- className="md:w-14rem bg-gray-5 w-full"
64
- virtualScrollerOptions={{
65
- lazy: true,
66
- onLazyLoad: onLazyLoad,
67
- itemSize: 38,
68
- // showLoader: true,
69
- loading: lazyLoading,
70
- //delay: 250,
71
- // loadingTemplate: (options) => {
72
- // return (
73
- // <div
74
- // className="flex align-items-center p-2"
75
- // style={{ height: "38px" }}
76
- // >
77
- // <Skeleton width={options.even ? "60%" : "50%"} height="1rem" />
78
- // </div>
79
- // );
80
- // },
81
- }}
70
+ disabled={readOnly}
71
+ className="md:w-14rem w-full"
72
+ loading={lazyLoading}
73
+ onLazyLoad={onLazyLoad}
74
+ emptyMessage="No options available"
82
75
  />
83
76
  );
84
77
  }
@@ -2,9 +2,8 @@
2
2
 
3
3
  import { InputTextarea } from "primereact/inputtextarea";
4
4
  import {
5
- useEditContext,
6
5
  useEditContextRef,
7
- useModifiedFieldsContext,
6
+ useFieldsEditContextRef,
8
7
  } from "../client/editContext";
9
8
  import { useFieldModification } from "../client/fieldModificationStore";
10
9
 
@@ -22,7 +21,7 @@ export function MultiLineText({
22
21
  updateFieldValue: (value: string) => void;
23
22
  }) {
24
23
  const editContextRef = useEditContextRef();
25
- const editContext = useEditContext();
24
+ const fieldsContextRef = useFieldsEditContextRef();
26
25
  const inputRef = useRef<HTMLTextAreaElement>(null);
27
26
 
28
27
  const fieldItem = field.descriptor.item;
@@ -31,9 +30,9 @@ export function MultiLineText({
31
30
  // Field-specific subscription - only rerenders when THIS field changes
32
31
  const { modifiedField } = useFieldModification(
33
32
  field.id,
34
- fieldItem.id,
33
+ fieldItem.id,
35
34
  fieldItem.language,
36
- fieldItem.version
35
+ fieldItem.version,
37
36
  );
38
37
 
39
38
  useEffect(() => {
@@ -49,13 +48,13 @@ export function MultiLineText({
49
48
 
50
49
  useEffect(() => {
51
50
  setTimeout(() => {
51
+ const focusedField = fieldsContextRef.current?.focusedField;
52
52
  if (
53
- !editContextRef.current?.inlineEditingFieldElement &&
54
- editContextRef.current?.focusedField?.fieldId === field.id &&
55
- editContextRef.current?.focusedField?.item.id === fieldItem.id &&
56
- editContextRef.current?.focusedField?.item.language ===
57
- fieldItem.language &&
58
- editContextRef.current?.focusedField?.item.version === fieldItem.version
53
+ !fieldsContextRef.current?.inlineEditingFieldElement &&
54
+ focusedField?.fieldId === field.id &&
55
+ focusedField?.item.id === fieldItem.id &&
56
+ focusedField?.item.language === fieldItem.language &&
57
+ focusedField?.item.version === fieldItem.version
59
58
  ) {
60
59
  // Only focus if no other element currently has focus (e.g., popover buttons)
61
60
  if (
@@ -66,7 +65,7 @@ export function MultiLineText({
66
65
  }
67
66
  }
68
67
  }, 500);
69
- }, [editContext?.focusedField]);
68
+ }, [fieldsContextRef.current?.focusedField]);
70
69
 
71
70
  return (
72
71
  <InputTextarea
@@ -1,9 +1,6 @@
1
1
  "use client";
2
2
 
3
- import {
4
- useEditContextRef,
5
- useModifiedFieldsContext,
6
- } from "../client/editContext";
3
+ import { useEditContextRef, useFieldsEditContext } from "../client/editContext";
7
4
  import { useFieldModification } from "../client/fieldModificationStore";
8
5
 
9
6
  import { useThrottledCallback } from "use-debounce";
@@ -22,8 +19,7 @@ const FALLBACK_PROFILE: RichTextEditorProfile = {
22
19
  label: "Basic Formatting",
23
20
  display: "buttons" as const,
24
21
  showIconsOnly: true,
25
- options: [
26
- ],
22
+ options: [],
27
23
  },
28
24
  ],
29
25
  },
@@ -53,9 +49,9 @@ export function RichTextEditorComponent({
53
49
  // Field-specific subscription - only rerenders when THIS field changes
54
50
  const { modifiedField } = useFieldModification(
55
51
  field.id,
56
- fieldItem.id,
52
+ fieldItem.id,
57
53
  fieldItem.language,
58
- fieldItem.version
54
+ fieldItem.version,
59
55
  );
60
56
 
61
57
  useEffect(() => {
@@ -4,7 +4,8 @@ import { InputText } from "primereact/inputtext";
4
4
  import {
5
5
  useEditContext,
6
6
  useEditContextRef,
7
- useModifiedFieldsContext,
7
+ useFieldsEditContext,
8
+ useFieldsEditContextRef,
8
9
  SelectionRange,
9
10
  } from "../client/editContext";
10
11
  import { useFieldModification } from "../client/fieldModificationStore";
@@ -25,6 +26,7 @@ export function SingleLineText({
25
26
  }) {
26
27
  const editContextRef = useEditContextRef();
27
28
  const editContext = useEditContext();
29
+ const fieldsContextRef = useFieldsEditContextRef();
28
30
  const inputRef = useRef<HTMLInputElement>(null);
29
31
  // const [selectionStart, setSelectionStart] = useState(0);
30
32
  // const [selectionEnd, setSelectionEnd] = useState(0);
@@ -36,9 +38,9 @@ export function SingleLineText({
36
38
  // Field-specific subscription - only rerenders when THIS field changes
37
39
  const { modifiedField } = useFieldModification(
38
40
  field.id,
39
- fieldItem.id,
41
+ fieldItem.id,
40
42
  fieldItem.language,
41
- fieldItem.version
43
+ fieldItem.version,
42
44
  );
43
45
 
44
46
  useEffect(() => {
@@ -79,22 +81,22 @@ export function SingleLineText({
79
81
 
80
82
  if (
81
83
  range.text !== editContext?.selectedRange?.text ||
82
- (range.text && range.fieldId !== editContext?.selectedRange.fieldId) ||
83
- range.itemId !== editContext.selectedRange.itemId
84
+ (range.text && range.fieldId !== editContext?.selectedRange?.fieldId) ||
85
+ range.itemId !== editContext?.selectedRange?.itemId
84
86
  ) {
85
87
  editContextRef.current?.setSelectedRange(range);
86
88
  }
87
89
  };
88
90
 
89
91
  useEffect(() => {
92
+ const focusedField = fieldsContextRef.current?.focusedField;
90
93
  if (
91
- editContextRef.current?.focusedField?.fieldId === field.id &&
92
- editContextRef.current?.focusedField?.item.id === fieldItem.id &&
93
- editContextRef.current?.focusedField?.item.language ===
94
- fieldItem.language &&
95
- editContextRef.current?.focusedField?.item.version === fieldItem.version
94
+ focusedField?.fieldId === field.id &&
95
+ focusedField?.item.id === fieldItem.id &&
96
+ focusedField?.item.language === fieldItem.language &&
97
+ focusedField?.item.version === fieldItem.version
96
98
  ) {
97
- const range = editContextRef.current.selectedRange;
99
+ const range = editContextRef.current?.selectedRange;
98
100
  if (range && inputRef.current) {
99
101
  inputRef.current.setSelectionRange(range.startOffset, range.endOffset);
100
102
  // setSelectionStart(range.startOffset);
@@ -134,13 +136,13 @@ export function SingleLineText({
134
136
 
135
137
  useEffect(() => {
136
138
  setTimeout(() => {
139
+ const focusedField = fieldsContextRef.current?.focusedField;
137
140
  if (
138
- !editContextRef.current?.inlineEditingFieldElement &&
139
- editContextRef.current?.focusedField?.fieldId === field.id &&
140
- editContextRef.current?.focusedField?.item.id === fieldItem.id &&
141
- editContextRef.current?.focusedField?.item.language ===
142
- fieldItem.language &&
143
- editContextRef.current?.focusedField?.item.version === fieldItem.version
141
+ !fieldsContextRef.current?.inlineEditingFieldElement &&
142
+ focusedField?.fieldId === field.id &&
143
+ focusedField?.item.id === fieldItem.id &&
144
+ focusedField?.item.language === fieldItem.language &&
145
+ focusedField?.item.version === fieldItem.version
144
146
  ) {
145
147
  // Only focus if no other element currently has focus (e.g., popover buttons)
146
148
  if (
@@ -151,7 +153,7 @@ export function SingleLineText({
151
153
  }
152
154
  }
153
155
  }, 500);
154
- }, [editContext?.focusedField]);
156
+ }, [fieldsContextRef.current?.focusedField]);
155
157
 
156
158
  const customSource = (field as any)?.customProperties?.source as
157
159
  | string
@@ -185,7 +187,7 @@ export function SingleLineText({
185
187
  <button
186
188
  type="button"
187
189
  title="Re-render"
188
- className="absolute right-1 top-1/2 -translate-y-1/2 rounded p-1 text-gray-600 hover:text-gray-900"
190
+ className="absolute top-1/2 right-1 -translate-y-1/2 rounded p-1 text-gray-600 hover:text-gray-900"
189
191
  onClick={() => editContext?.requestRefresh("immediate")}
190
192
  >
191
193
  <RefreshCw className="h-4 w-4" strokeWidth={1} />
@@ -1,7 +1,4 @@
1
- import {
2
- useEditContext,
3
- useModifiedFieldsContext,
4
- } from "../client/editContext";
1
+ import { useEditContext, useFieldsEditContext } from "../client/editContext";
5
2
  import { findComponentRect, findFieldElement } from "../utils";
6
3
  import { Comment } from "../../types";
7
4
  import { useState } from "react";
@@ -21,7 +18,7 @@ export function CommentHighlighting({
21
18
  }) {
22
19
  const editContext = useEditContext();
23
20
  const isSelected = comment.id === editContext?.selectedComment?.id;
24
- const modifiedFields = useModifiedFieldsContext();
21
+ const modifiedFields = useFieldsEditContext();
25
22
 
26
23
  const [range, setRange] = useState<number[]>([
27
24
  comment.rangeStart || 0,
@@ -198,7 +195,8 @@ export function CommentHighlighting({
198
195
  editContext?.setScrollIntoView(comment.itemId);
199
196
  editContext?.select([comment.itemId]);
200
197
  if (comment.fieldId) {
201
- editContext?.setFocusedField(
198
+ const fieldsContext = modifiedFields; // alias for readability
199
+ fieldsContext?.setFocusedField(
202
200
  {
203
201
  fieldId: comment.fieldId,
204
202
  item: {
@@ -1,4 +1,8 @@
1
- import { EditedField, useEditContext } from "../client/editContext";
1
+ import {
2
+ EditedField,
3
+ useEditContext,
4
+ useFieldsEditContext,
5
+ } from "../client/editContext";
2
6
  import { PageViewContext } from "../page-viewer/pageViewContext";
3
7
 
4
8
  export function FieldEditedIndicator({
@@ -13,6 +17,7 @@ export function FieldEditedIndicator({
13
17
  scroll?: number;
14
18
  }) {
15
19
  const editContext = useEditContext();
20
+ const fieldsContext = useFieldsEditContext();
16
21
  if (!editContext) return null;
17
22
 
18
23
  const fieldElements =
@@ -24,7 +29,7 @@ export function FieldEditedIndicator({
24
29
  return (
25
30
  <>
26
31
  {[...fieldElements]
27
- .filter((x) => x != editContext.inlineEditingFieldElement)
32
+ .filter((x) => x != fieldsContext?.inlineEditingFieldElement)
28
33
  .map((element) => (
29
34
  <SingleFieldEditedIndicator
30
35
  element={element}
@@ -1,4 +1,4 @@
1
- import { useEditContext, useModifiedFieldsContext } from "../client/editContext";
1
+ import { useEditContext, useFieldsEditContext } from "../client/editContext";
2
2
  import { PageViewContext } from "../page-viewer/pageViewContext";
3
3
  import { FieldEditedIndicator } from "./FieldEditedIndicator";
4
4
 
@@ -12,7 +12,7 @@ export function FieldEditedIndicators({
12
12
  scroll?: number;
13
13
  }) {
14
14
  const editContext = useEditContext();
15
- const modifiedFieldsContext = useModifiedFieldsContext();
15
+ const modifiedFieldsContext = useFieldsEditContext();
16
16
 
17
17
  if (!editContext || !modifiedFieldsContext) return null;
18
18
 
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
  import {
3
3
  useEditContext,
4
- useModifiedFieldsContext,
4
+ useFieldsEditContext,
5
5
  useEditContextRef,
6
6
  } from "../client/editContext";
7
7
  import { useThrottledCallback } from "use-debounce";
@@ -22,7 +22,7 @@ export function InlineEditor({
22
22
  }) {
23
23
  const context = useEditContext();
24
24
  const contextRef = useEditContextRef();
25
- const modifiedFieldsContext = useModifiedFieldsContext();
25
+ const modifiedFieldsContext = useFieldsEditContext();
26
26
  const [cursorSpanId] = useState(
27
27
  () => `cursor-indicator-${Math.random().toString(36).substring(2, 9)}`,
28
28
  );
@@ -52,7 +52,7 @@ export function InlineEditor({
52
52
  // Don't include our cursor indicator in the saved value
53
53
  const iframeDocument =
54
54
  pageViewContext.editorIframe?.contentWindow?.document;
55
- const element = context.inlineEditingFieldElement;
55
+ const element = modifiedFieldsContext?.inlineEditingFieldElement;
56
56
  const isRichText = element?.getAttribute("data-is-richtext") === "true";
57
57
 
58
58
  // Clone element to avoid modifying the original
@@ -102,7 +102,7 @@ export function InlineEditor({
102
102
 
103
103
  useEffect(() => {
104
104
  if (!context || compareView || context.mode === "preview") return;
105
- const element = context.inlineEditingFieldElement;
105
+ const element = modifiedFieldsContext?.inlineEditingFieldElement;
106
106
 
107
107
  const editableElements =
108
108
  pageViewContext.editorIframe?.contentWindow?.document.querySelectorAll(
@@ -110,7 +110,7 @@ export function InlineEditor({
110
110
  );
111
111
 
112
112
  editableElements?.forEach((element) => {
113
- if (element !== context.inlineEditingFieldElement)
113
+ if (element !== modifiedFieldsContext?.inlineEditingFieldElement)
114
114
  element.setAttribute("contenteditable", "false");
115
115
  });
116
116
 
@@ -267,7 +267,7 @@ export function InlineEditor({
267
267
  }
268
268
 
269
269
  async function updateFocusedFieldContent() {
270
- const element = context?.inlineEditingFieldElement;
270
+ const element = modifiedFieldsContext?.inlineEditingFieldElement;
271
271
  if (!element) return;
272
272
 
273
273
  const savedPosition = saveCaretPosition(element);
@@ -285,7 +285,8 @@ export function InlineEditor({
285
285
  const descriptor = { id: itemId, language, version };
286
286
 
287
287
  // Retrieve the current field value from the repository.
288
- const loadedItem = await context.itemsRepository.getItem(descriptor);
288
+ const loadedItem =
289
+ await contextRef.current?.itemsRepository.getItem(descriptor);
289
290
  if (!loadedItem) return;
290
291
  // Get the baseline value from the repository.
291
292
  const repositoryField = loadedItem.fields.find(
@@ -307,8 +308,10 @@ export function InlineEditor({
307
308
 
308
309
  // If suggestions mode is active, merge all suggestions for this field.
309
310
 
310
- if (context.mode === "suggestions") {
311
- const fieldSuggestions = context.suggestedEdits.filter(
311
+ if (contextRef.current?.mode === "suggestions") {
312
+ const fieldSuggestions = (
313
+ contextRef.current?.suggestedEdits ?? []
314
+ ).filter(
312
315
  (s: any) =>
313
316
  s.fieldId === fieldId &&
314
317
  s.itemId === itemId &&
@@ -380,7 +383,7 @@ export function InlineEditor({
380
383
  cursorElement.parentNode?.removeChild(cursorElement);
381
384
  }
382
385
  };
383
- }, [context?.inlineEditingFieldElement]);
386
+ }, [modifiedFieldsContext?.inlineEditingFieldElement]);
384
387
 
385
388
  function saveCaretPosition(
386
389
  editableElement: HTMLElement,
@@ -508,7 +511,10 @@ export function InlineEditor({
508
511
  );
509
512
 
510
513
  elements?.forEach(async (element) => {
511
- if (element && element !== context?.inlineEditingFieldElement) {
514
+ if (
515
+ element &&
516
+ element !== modifiedFieldsContext?.inlineEditingFieldElement
517
+ ) {
512
518
  const realField = await context.itemsRepository.getField(field);
513
519
  const fieldType = realField?.type;
514
520
 
@@ -564,7 +570,7 @@ export function InlineEditor({
564
570
  if (!fieldElement) return;
565
571
 
566
572
  // Do not update if this field is currently focused.
567
- if (fieldElement === context.inlineEditingFieldElement) {
573
+ if (fieldElement === modifiedFieldsContext?.inlineEditingFieldElement) {
568
574
  return;
569
575
  }
570
576
 
@@ -689,7 +695,7 @@ export function InlineEditor({
689
695
  context.mode,
690
696
  modifiedFieldsContext?.modifiedFields,
691
697
  context?.itemsRepository.revision,
692
- context?.inlineEditingFieldElement,
698
+ modifiedFieldsContext?.inlineEditingFieldElement,
693
699
  context?.showSuggestedEdits,
694
700
  context?.suggestedEdits,
695
701
  pageViewContext.pageItemDescriptor,
@@ -715,7 +721,7 @@ export function InlineEditor({
715
721
  // don't stomp on the one that's live‑editing (only in non-preview modes)
716
722
  if (
717
723
  context.mode !== "preview" &&
718
- el === context.inlineEditingFieldElement
724
+ el === modifiedFieldsContext?.inlineEditingFieldElement
719
725
  )
720
726
  return;
721
727
 
@@ -1,7 +1,4 @@
1
- import {
2
- useEditContext,
3
- useModifiedFieldsContext,
4
- } from "../client/editContext";
1
+ import { useEditContext, useFieldsEditContext } from "../client/editContext";
5
2
  import { findFieldElement } from "../utils";
6
3
  import { SuggestedEdit } from "../../types";
7
4
  import { useState } from "react";
@@ -21,7 +18,7 @@ export function SuggestionHighlighting({
21
18
  iframe: HTMLIFrameElement;
22
19
  }) {
23
20
  const editContext = useEditContext();
24
- const modifiedFields = useModifiedFieldsContext();
21
+ const modifiedFields = useFieldsEditContext();
25
22
  const [ranges, setRanges] = useState<Array<[number, number]>>([]);
26
23
  const [textContent, setTextContent] = useState<string>("");
27
24
 
@@ -227,7 +224,8 @@ export function SuggestionHighlighting({
227
224
  )}
228
225
  onClick={() => {
229
226
  editContext.select([suggestion.itemId]);
230
- editContext.setFocusedField(
227
+ const fieldsContext = modifiedFields; // alias to use setter
228
+ fieldsContext?.setFocusedField(
231
229
  {
232
230
  item: {
233
231
  id: suggestion.itemId,
@@ -8,7 +8,7 @@ import {
8
8
  } from "react";
9
9
  import { useDebouncedCallback } from "use-debounce";
10
10
  import { PageViewContext } from "../page-viewer/pageViewContext";
11
- import { useEditContext } from "../client/editContext";
11
+ import { useEditContext, useFieldsEditContext } from "../client/editContext";
12
12
  import { executePrompt } from "../services/aiService";
13
13
  import {
14
14
  generatePageContext,
@@ -26,6 +26,7 @@ export function useInlineAiCompletion({
26
26
  isUpdatingRef: MutableRefObject<boolean>;
27
27
  }) {
28
28
  const editContext = useEditContext();
29
+ const fieldsContext = useFieldsEditContext();
29
30
  const [currentCompletion, setCurrentCompletion] = useState<string | null>(
30
31
  null,
31
32
  );
@@ -61,7 +62,7 @@ export function useInlineAiCompletion({
61
62
  try {
62
63
  const iframeWindow = pageViewContext.editorIframe?.contentWindow;
63
64
  const iframeDocument = iframeWindow?.document;
64
- const editableElement = editContext?.inlineEditingFieldElement;
65
+ const editableElement = fieldsContext?.inlineEditingFieldElement;
65
66
 
66
67
  if (!iframeWindow || !iframeDocument || !editableElement) return;
67
68
 
@@ -396,7 +397,7 @@ ONLY provide the completion text (what comes after the user's text), never repea
396
397
  // Debounced AI call: recompute the sentence, call getCompletion, and extract only the "tail" for the ghost text
397
398
  const getCompletionDebounced = useDebouncedCallback(
398
399
  async (isManualTrigger = false) => {
399
- const el = editContext?.inlineEditingFieldElement;
400
+ const el = fieldsContext?.inlineEditingFieldElement;
400
401
  if (!el) return;
401
402
 
402
403
  // 1) Recompute the exact sentence at this moment
@@ -426,7 +427,7 @@ ONLY provide the completion text (what comes after the user's text), never repea
426
427
 
427
428
  // Manual completion trigger (non-debounced for immediate response)
428
429
  const getCompletionManual = async () => {
429
- const el = editContext?.inlineEditingFieldElement;
430
+ const el = fieldsContext?.inlineEditingFieldElement;
430
431
  if (!el) return;
431
432
 
432
433
  // 1) Recompute the exact sentence at this moment
@@ -556,7 +557,7 @@ ONLY provide the completion text (what comes after the user's text), never repea
556
557
  // On every input: either reuse the existing suggestion or fire a new one
557
558
  const handleInput = useCallback(
558
559
  (e: KeyboardEvent) => {
559
- const el = editContext?.inlineEditingFieldElement;
560
+ const el = fieldsContext?.inlineEditingFieldElement;
560
561
  if (!el) return;
561
562
 
562
563
  // Clear completion when Escape is pressed
@@ -624,7 +625,7 @@ ONLY provide the completion text (what comes after the user's text), never repea
624
625
 
625
626
  // Check if cursor is at the end of the text
626
627
  const isAtEnd = () => {
627
- const el = editContext?.inlineEditingFieldElement;
628
+ const el = fieldsContext?.inlineEditingFieldElement;
628
629
  if (!el) return false;
629
630
 
630
631
  const iframeWindow = pageViewContext.editorIframe?.contentWindow;
@@ -663,7 +664,7 @@ ONLY provide the completion text (what comes after the user's text), never repea
663
664
  }
664
665
  },
665
666
  [
666
- editContext?.inlineEditingFieldElement,
667
+ fieldsContext?.inlineEditingFieldElement,
667
668
  currentCompletion,
668
669
  getCompletionDebounced,
669
670
  getCompletionManual,
@@ -673,12 +674,12 @@ ONLY provide the completion text (what comes after the user's text), never repea
673
674
  // Wire up the input listener
674
675
  useEffect(() => {
675
676
  if (!editContext?.enableCompletions) return;
676
- const el = editContext?.inlineEditingFieldElement;
677
+ const el = fieldsContext?.inlineEditingFieldElement;
677
678
  if (!el) return;
678
679
  el.addEventListener("keydown", handleInput);
679
680
  return () => el.removeEventListener("keydown", handleInput);
680
681
  }, [
681
- editContext?.inlineEditingFieldElement,
682
+ fieldsContext?.inlineEditingFieldElement,
682
683
  handleInput,
683
684
  editContext?.enableCompletions,
684
685
  ]);
@@ -686,7 +687,7 @@ ONLY provide the completion text (what comes after the user's text), never repea
686
687
  // Add mouse click handler to update cursor span position
687
688
  useEffect(() => {
688
689
  if (!editContext?.enableCompletions) return;
689
- const el = editContext?.inlineEditingFieldElement;
690
+ const el = fieldsContext?.inlineEditingFieldElement;
690
691
  if (!el) return;
691
692
 
692
693
  const handleMouseUp = () => {
@@ -703,7 +704,10 @@ ONLY provide the completion text (what comes after the user's text), never repea
703
704
 
704
705
  el.addEventListener("mouseup", handleMouseUp);
705
706
  return () => el.removeEventListener("mouseup", handleMouseUp);
706
- }, [editContext?.inlineEditingFieldElement, editContext?.enableCompletions]);
707
+ }, [
708
+ fieldsContext?.inlineEditingFieldElement,
709
+ editContext?.enableCompletions,
710
+ ]);
707
711
 
708
712
  // Clean up abort controller on unmount
709
713
  useEffect(() => {
@@ -728,7 +732,7 @@ ONLY provide the completion text (what comes after the user's text), never repea
728
732
  if (
729
733
  !iframeWindow ||
730
734
  !iframeDocument ||
731
- !editContext?.inlineEditingFieldElement
735
+ !fieldsContext?.inlineEditingFieldElement
732
736
  )
733
737
  return;
734
738
 
@@ -739,7 +743,7 @@ ONLY provide the completion text (what comes after the user's text), never repea
739
743
  const completionToApply = cursorSpan.textContent || "";
740
744
  if (!completionToApply) return;
741
745
 
742
- const element = editContext?.inlineEditingFieldElement;
746
+ const element = fieldsContext?.inlineEditingFieldElement;
743
747
 
744
748
  // Get the current selection position
745
749
  const selection = iframeWindow.getSelection();
@@ -3,10 +3,7 @@ import { EditorForm } from "./EditorForm";
3
3
  import { PageViewerFrame } from "./PageViewerFrame";
4
4
  import { useEffect, useState } from "react";
5
5
  import { SimpleIconButton } from "../ui/SimpleIconButton";
6
- import {
7
- useEditContext,
8
- useModifiedFieldsContext,
9
- } from "../client/editContext";
6
+ import { useEditContext, useFieldsEditContext } from "../client/editContext";
10
7
  import { PanelLeftClose, PanelLeftOpen } from "lucide-react";
11
8
  import { cn } from "../../lib/utils";
12
9
  import { Splitter, SplitterPanel } from "../ui/Splitter";
@@ -29,7 +26,7 @@ export function PageViewer({
29
26
  noMargins?: boolean;
30
27
  }) {
31
28
  const editContext = useEditContext();
32
- const modifiedFieldsContext = useModifiedFieldsContext();
29
+ const modifiedFieldsContext = useFieldsEditContext();
33
30
 
34
31
  const [followEdits, setFollowEdits] = useState(followEditsDefault);
35
32
  const [formEditorCollapsed, setFormEditorCollapsed] = useState(false);
@@ -50,7 +47,7 @@ export function PageViewer({
50
47
  modifiedFieldsContext?.recentEdits &&
51
48
  modifiedFieldsContext.recentEdits.length > 0
52
49
  ) {
53
- if (editContext?.inlineEditingFieldElement) return;
50
+ if (modifiedFieldsContext?.inlineEditingFieldElement) return;
54
51
 
55
52
  const lastEdit =
56
53
  modifiedFieldsContext.recentEdits[