@getdom/studio 0.1.0

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 (213) hide show
  1. package/LICENSE.md +109 -0
  2. package/README.md +44 -0
  3. package/SKILL.md +83 -0
  4. package/package.json +73 -0
  5. package/public/llms.txt +57 -0
  6. package/public/skills.sh +11 -0
  7. package/src/pages/_layout/componentCatalog.generated.js +4514 -0
  8. package/src/pages/_layout/componentInspector.js +60 -0
  9. package/src/pages/_layout/componentManager.js +192 -0
  10. package/src/pages/_layout/componentRuntimeRecords.js +49 -0
  11. package/src/pages/_layout/inspector/componentRegistry.js +639 -0
  12. package/src/pages/_layout/inspector/serialize.js +134 -0
  13. package/src/pages/_layout/inspector/useInspector.js +293 -0
  14. package/src/pages/components/accordion/DomAccordion.vue +39 -0
  15. package/src/pages/components/breadcrumbs/DomBreadcrumbs.vue +347 -0
  16. package/src/pages/components/button/DomButton.vue +132 -0
  17. package/src/pages/components/command-palette/DomCommandPalette.vue +482 -0
  18. package/src/pages/components/context-menu/DomContextMenu.vue +178 -0
  19. package/src/pages/components/dialog/DomDialog.vue +271 -0
  20. package/src/pages/components/dialog/DomDialogSchemaForm.vue +72 -0
  21. package/src/pages/components/dialog/DomDialogStack.vue +289 -0
  22. package/src/pages/components/dialog/useDialogs.js +253 -0
  23. package/src/pages/components/drawer/DomDrawer.vue +159 -0
  24. package/src/pages/components/dropdown/DomDropdown.vue +261 -0
  25. package/src/pages/components/icon-button/DomIconButton.vue +155 -0
  26. package/src/pages/components/lightbox/DomLightbox.vue +1561 -0
  27. package/src/pages/components/menu/DomMenu.vue +359 -0
  28. package/src/pages/components/menu/DomMenuItem.vue +233 -0
  29. package/src/pages/components/menu/DomMenuLabel.vue +19 -0
  30. package/src/pages/components/menu/DomMenuSeparator.vue +14 -0
  31. package/src/pages/components/menu/menuContext.js +11 -0
  32. package/src/pages/components/menu/menuPlacement.js +26 -0
  33. package/src/pages/components/menu/submenu.js +58 -0
  34. package/src/pages/components/pagination/DomPagination.vue +265 -0
  35. package/src/pages/components/playground/DomRenderer.vue +48 -0
  36. package/src/pages/components/popover/DomPopover.vue +244 -0
  37. package/src/pages/components/profile-menu/DomProfileMenu.vue +408 -0
  38. package/src/pages/components/profile-menu/DomProfileMenuItemContent.vue +45 -0
  39. package/src/pages/components/scroll-menu/DomScrollMenu.vue +430 -0
  40. package/src/pages/components/scroll-menu/DomScrollMenuItem.vue +212 -0
  41. package/src/pages/components/scroll-menu/scrollMenuContext.js +22 -0
  42. package/src/pages/components/splitter-panel/DomSplitterPanel.vue +283 -0
  43. package/src/pages/components/stepper/DomStepper.vue +386 -0
  44. package/src/pages/components/tabs/DomTabs.vue +149 -0
  45. package/src/pages/components/theme-switcher/DomThemeSwitcher.vue +226 -0
  46. package/src/pages/components/toast/DomToastItem.vue +52 -0
  47. package/src/pages/components/toast/DomToastStack.vue +768 -0
  48. package/src/pages/components/toast/useToasts.js +237 -0
  49. package/src/pages/components/toolbar/DomToolbar.vue +158 -0
  50. package/src/pages/components/tooltip/DomTooltip.vue +69 -0
  51. package/src/pages/components/tooltip/vDomTooltip.js +106 -0
  52. package/src/pages/components/tree-view/DomTreeView.vue +961 -0
  53. package/src/pages/components/tree-view/treeSchemas.js +129 -0
  54. package/src/pages/components/tree-view/treeUtils.js +228 -0
  55. package/src/pages/data/_shared/calculateVirtualWindow.js +101 -0
  56. package/src/pages/data/_shared/resourceRecord.js +29 -0
  57. package/src/pages/data/activity-timeline/DomActivityTimeline.vue +417 -0
  58. package/src/pages/data/agent-chat/DomAgentChatShell.vue +565 -0
  59. package/src/pages/data/agent-chat/DomAgentConversation.vue +737 -0
  60. package/src/pages/data/agent-chat/DomAgentConversationList.vue +319 -0
  61. package/src/pages/data/agent-chat/examples/agentChatMockApi.js +620 -0
  62. package/src/pages/data/agent-chat/examples/useAgentChatDemoStore.js +730 -0
  63. package/src/pages/data/data-grid/DataGridVirtualRows.vue +220 -0
  64. package/src/pages/data/data-grid/DomDataGrid.vue +1643 -0
  65. package/src/pages/data/document-editor/DomDocumentEditor.vue +3030 -0
  66. package/src/pages/data/document-editor/documentEditorCodeBlock.js +282 -0
  67. package/src/pages/data/document-editor/examples/documentEditorMockApi.js +272 -0
  68. package/src/pages/data/document-editor/examples/useDocumentEditorDemoStore.js +284 -0
  69. package/src/pages/data/filter-bar/DomFilterBar.vue +533 -0
  70. package/src/pages/data/kanban/DomKanban.vue +1703 -0
  71. package/src/pages/data/kanban/kanbanApi.js +80 -0
  72. package/src/pages/data/media-browser/DomMediaBrowser.vue +2344 -0
  73. package/src/pages/data/media-browser/examples/mediaBrowserMockApi.js +653 -0
  74. package/src/pages/data/media-browser/examples/useMediaBrowserDemoStore.js +1157 -0
  75. package/src/pages/data/month-calendar/DomMonthCalendar.vue +1718 -0
  76. package/src/pages/data/week-planner/DomWeekPlanner.vue +2308 -0
  77. package/src/pages/dev/code-block/DomCodeBlock.vue +263 -0
  78. package/src/pages/dev/json-viewer/DomJsonViewer.vue +962 -0
  79. package/src/pages/forms/_shared/DomFieldLoadingSpinner.vue +12 -0
  80. package/src/pages/forms/_shared/options.js +61 -0
  81. package/src/pages/forms/_shared/useOptionMenu.js +59 -0
  82. package/src/pages/forms/autocomplete/DomAutocomplete.vue +180 -0
  83. package/src/pages/forms/calendar/DomCalendar.vue +442 -0
  84. package/src/pages/forms/checkbox/DomCheckbox.vue +69 -0
  85. package/src/pages/forms/class-toggle-input/DomClassToggleInput.vue +316 -0
  86. package/src/pages/forms/code-input/DomCodeInput.vue +202 -0
  87. package/src/pages/forms/color-input/DomColorInput.vue +118 -0
  88. package/src/pages/forms/combobox/DomCombobox.vue +237 -0
  89. package/src/pages/forms/confirmation-code-input/DomConfirmationCodeInput.vue +345 -0
  90. package/src/pages/forms/date-picker/DomDatePicker.vue +299 -0
  91. package/src/pages/forms/date-time-picker/DomDateTimePicker.vue +630 -0
  92. package/src/pages/forms/email-input/DomEmailInput.vue +55 -0
  93. package/src/pages/forms/field/DomField.vue +267 -0
  94. package/src/pages/forms/field/fieldLayout.js +1 -0
  95. package/src/pages/forms/field/fieldProps.js +114 -0
  96. package/src/pages/forms/field/layouts/DomGridFieldLayout.vue +38 -0
  97. package/src/pages/forms/field/useField.js +343 -0
  98. package/src/pages/forms/field/validators.js +364 -0
  99. package/src/pages/forms/file-upload/DomFileUpload.vue +361 -0
  100. package/src/pages/forms/form/DomForm.vue +985 -0
  101. package/src/pages/forms/form/adapters/fieldBuilder.js +45 -0
  102. package/src/pages/forms/form/adapters/jsonSchema.js +264 -0
  103. package/src/pages/forms/form/adapters/shared.js +53 -0
  104. package/src/pages/forms/form/adapters/zod.js +235 -0
  105. package/src/pages/forms/form/formApi.js +38 -0
  106. package/src/pages/forms/form/formDefinition.js +392 -0
  107. package/src/pages/forms/form/schemaAdapter.js +54 -0
  108. package/src/pages/forms/icon-selector/DomIconSelector.vue +460 -0
  109. package/src/pages/forms/icon-selector/iconSelectorItems.js +490 -0
  110. package/src/pages/forms/input-group/DomInputGroup.vue +232 -0
  111. package/src/pages/forms/json-input/DomJsonInput.vue +227 -0
  112. package/src/pages/forms/json-list-input/DomJsonListInput.vue +206 -0
  113. package/src/pages/forms/listbox/DomListbox.vue +105 -0
  114. package/src/pages/forms/money-input/DomMoneyInput.vue +147 -0
  115. package/src/pages/forms/native-select/DomNativeSelect.vue +67 -0
  116. package/src/pages/forms/number-input/DomNumberInput.vue +47 -0
  117. package/src/pages/forms/password-input/DomPasswordInput.vue +218 -0
  118. package/src/pages/forms/password-input/pwnedPasswords.js +47 -0
  119. package/src/pages/forms/password-strength/DomPasswordStrength.vue +128 -0
  120. package/src/pages/forms/password-strength/passwordStrength.js +92 -0
  121. package/src/pages/forms/position-input/DomPositionInput.vue +83 -0
  122. package/src/pages/forms/radio-group/DomRadioGroup.vue +102 -0
  123. package/src/pages/forms/range-input/DomRangeInput.vue +296 -0
  124. package/src/pages/forms/select/DomSelect.vue +479 -0
  125. package/src/pages/forms/select-input/DomSelectInput.vue +155 -0
  126. package/src/pages/forms/tag-combobox/DomTagCombobox.vue +358 -0
  127. package/src/pages/forms/text-input/DomTextInput.vue +48 -0
  128. package/src/pages/forms/textarea-composer/DomTextareaComposer.vue +234 -0
  129. package/src/pages/forms/textarea-input/DomTextareaInput.vue +86 -0
  130. package/src/pages/forms/textarea-input/useTextareaAutosize.js +147 -0
  131. package/src/pages/forms/time-selector/DomTimeSelector.vue +389 -0
  132. package/src/pages/forms/time-selector/timeUtils.js +194 -0
  133. package/src/pages/forms/toggle/DomToggle.vue +158 -0
  134. package/src/pages/forms/toggle-button/DomToggleButton.vue +88 -0
  135. package/src/pages/forms/toggle-button-group/DomToggleButtonGroup.vue +340 -0
  136. package/src/pages/forms/url-input/DomUrlInput.vue +55 -0
  137. package/src/pages/forms/wysiwyg/DomWysiwyg.vue +204 -0
  138. package/src/pages/lib/css/color.js +245 -0
  139. package/src/pages/lib/headless/accordion.d.ts +7 -0
  140. package/src/pages/lib/headless/accordion.js +246 -0
  141. package/src/pages/lib/headless/autocomplete.d.ts +4 -0
  142. package/src/pages/lib/headless/autocomplete.js +33 -0
  143. package/src/pages/lib/headless/base.d.ts +25 -0
  144. package/src/pages/lib/headless/base.js +127 -0
  145. package/src/pages/lib/headless/checkbox.d.ts +19 -0
  146. package/src/pages/lib/headless/checkbox.js +99 -0
  147. package/src/pages/lib/headless/combobox.d.ts +7 -0
  148. package/src/pages/lib/headless/combobox.js +488 -0
  149. package/src/pages/lib/headless/dialog.d.ts +23 -0
  150. package/src/pages/lib/headless/dialog.js +384 -0
  151. package/src/pages/lib/headless/drawer.d.ts +21 -0
  152. package/src/pages/lib/headless/drawer.js +682 -0
  153. package/src/pages/lib/headless/dropdown.d.ts +19 -0
  154. package/src/pages/lib/headless/dropdown.js +412 -0
  155. package/src/pages/lib/headless/floating-effect.js +118 -0
  156. package/src/pages/lib/headless/floating.d.ts +34 -0
  157. package/src/pages/lib/headless/floating.js +382 -0
  158. package/src/pages/lib/headless/index.d.ts +19 -0
  159. package/src/pages/lib/headless/index.js +19 -0
  160. package/src/pages/lib/headless/listbox.d.ts +19 -0
  161. package/src/pages/lib/headless/listbox.js +126 -0
  162. package/src/pages/lib/headless/menu.d.ts +18 -0
  163. package/src/pages/lib/headless/menu.js +117 -0
  164. package/src/pages/lib/headless/popover-panel.d.ts +18 -0
  165. package/src/pages/lib/headless/popover-panel.js +309 -0
  166. package/src/pages/lib/headless/popover.d.ts +19 -0
  167. package/src/pages/lib/headless/popover.js +391 -0
  168. package/src/pages/lib/headless/radio-group.d.ts +19 -0
  169. package/src/pages/lib/headless/radio-group.js +100 -0
  170. package/src/pages/lib/headless/tabs.d.ts +18 -0
  171. package/src/pages/lib/headless/tabs.js +119 -0
  172. package/src/pages/lib/headless/toast.d.ts +13 -0
  173. package/src/pages/lib/headless/toast.js +65 -0
  174. package/src/pages/lib/headless/toggle.d.ts +19 -0
  175. package/src/pages/lib/headless/toggle.js +162 -0
  176. package/src/pages/lib/headless/tooltip-controller.d.ts +22 -0
  177. package/src/pages/lib/headless/tooltip-controller.js +549 -0
  178. package/src/pages/lib/headless/tooltip.d.ts +19 -0
  179. package/src/pages/lib/headless/tooltip.js +153 -0
  180. package/src/pages/lib/headless/transitions.d.ts +5 -0
  181. package/src/pages/lib/headless/transitions.js +89 -0
  182. package/src/pages/lib/vue/_internal/DomLiquidGlassSurface.vue +310 -0
  183. package/src/pages/lib/vue/_internal/liquidGlass.js +112 -0
  184. package/src/pages/lib/vue/_internal/liquidGlassConfig.js +10 -0
  185. package/src/pages/lib/vue/codeHighlighter.js +132 -0
  186. package/src/pages/lib/vue/floatingTeleportTarget.js +23 -0
  187. package/src/pages/lib/vue/index.d.ts +6806 -0
  188. package/src/pages/lib/vue/index.js +218 -0
  189. package/src/pages/lib/vue/useTheme.js +116 -0
  190. package/src/pages/mobile/action-sheet/DomActionSheet.vue +186 -0
  191. package/src/pages/mobile/bottom-nav/DomAppBottomNav.vue +94 -0
  192. package/src/pages/mobile/list-item/DomAppListItem.vue +90 -0
  193. package/src/pages/mobile/safe-area/DomSafeArea.vue +39 -0
  194. package/src/pages/mobile/shell/DomAppShell.vue +51 -0
  195. package/src/pages/mobile/stack/AppStackScreen.vue +63 -0
  196. package/src/pages/mobile/stack/DomAppStack.vue +711 -0
  197. package/src/pages/mobile/top-bar/DomAppTopBar.vue +67 -0
  198. package/src/pages/visual/alert/DomAlert.vue +363 -0
  199. package/src/pages/visual/avatar/DomAvatar.vue +110 -0
  200. package/src/pages/visual/avatar-stack/DomAvatarStack.vue +89 -0
  201. package/src/pages/visual/badge/DomBadge.vue +105 -0
  202. package/src/pages/visual/card/DomCard.vue +65 -0
  203. package/src/pages/visual/card-rail/DomCardRail.vue +99 -0
  204. package/src/pages/visual/empty-state/DomEmptyState.vue +88 -0
  205. package/src/pages/visual/progress/DomProgress.vue +212 -0
  206. package/src/pages/visual/progress/DomRadialProgress.vue +186 -0
  207. package/src/pages/visual/progress/DomSegmentedProgress.vue +350 -0
  208. package/src/pages/visual/skeleton/DomSkeleton.vue +174 -0
  209. package/src/pages/visual/spinner/DomSpinner.vue +122 -0
  210. package/src/pages/visual/status-pill/DomStatusPill.vue +75 -0
  211. package/src/pages/visual/text-diff/DomTextDiff.vue +577 -0
  212. package/src/pages/visual/text-diff/textDiff.js +300 -0
  213. package/src/style.css +446 -0
@@ -0,0 +1,284 @@
1
+ import { computed, reactive } from 'vue';
2
+ import { defineStore } from 'pinia';
3
+ import { createDocumentEditorMockApi } from './documentEditorMockApi.js';
4
+
5
+ const DEFAULT_BASE_URL = '/api/documents';
6
+ let logCounter = 0;
7
+
8
+ export const useDocumentEditorDemoStore = defineDocumentEditorStore('document-editor-demo', {
9
+ baseUrl: DEFAULT_BASE_URL,
10
+ api: createDocumentEditorMockApi(),
11
+ });
12
+
13
+ /**
14
+ * Creates a configured Pinia store for document editor workflows.
15
+ *
16
+ * @param {string} storeId Pinia store id.
17
+ * @param {Record<string, unknown>} options Store configuration.
18
+ * @returns {Function} Pinia use-store function.
19
+ */
20
+ export function defineDocumentEditorStore(storeId = 'document-editor', options = {}) {
21
+ return defineStore(storeId, () => createDocumentEditorStore(options));
22
+ }
23
+
24
+ /**
25
+ * Creates a fetch-backed API adapter matching the demo API contract.
26
+ *
27
+ * @param {Record<string, unknown>} config API configuration.
28
+ * @returns {Record<string, Function>} API adapter.
29
+ */
30
+ export function createDocumentEditorRestApi(config = {}) {
31
+ const baseUrl = String(config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, '');
32
+
33
+ /**
34
+ * Requests JSON and throws for non-2xx responses.
35
+ *
36
+ * @param {string} path Request path.
37
+ * @param {RequestInit} init Fetch options.
38
+ * @returns {Promise<Record<string, unknown>>} Parsed JSON response.
39
+ */
40
+ async function requestJson(path, init = {}) {
41
+ const response = await fetch(`${baseUrl}${path}`, {
42
+ headers: {
43
+ 'Content-Type': 'application/json',
44
+ ...(init.headers || {}),
45
+ },
46
+ ...init,
47
+ });
48
+ if (!response.ok) throw new Error(`Document editor request failed with ${response.status}.`);
49
+ return response.json();
50
+ }
51
+
52
+ return {
53
+ loadDocument: (request) => requestJson(`/${encodeURIComponent(request.documentId || 'default')}`),
54
+ saveDocument: (request) => requestJson(`/${encodeURIComponent(request.documentId || 'default')}`, {
55
+ method: 'PUT',
56
+ body: JSON.stringify(request || {}),
57
+ }),
58
+ rewriteSelection: (request) => requestJson(`/${encodeURIComponent(request.documentId || 'default')}/rewrite`, {
59
+ method: 'POST',
60
+ body: JSON.stringify(request || {}),
61
+ }),
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Creates the Pinia setup store used by the document editor docs and integrations.
67
+ *
68
+ * @param {Record<string, unknown>} options Store configuration.
69
+ * @returns {Record<string, unknown>} Store state, getters, and actions.
70
+ */
71
+ function createDocumentEditorStore(options = {}) {
72
+ const config = normalizeStoreOptions(options);
73
+ const api = config.api || createDocumentEditorRestApi(config);
74
+ const state = reactive(createInitialState(config));
75
+
76
+ const document = computed(() => state.document);
77
+ const content = computed(() => state.document?.content || null);
78
+ const title = computed(() => state.document?.title || 'Untitled document');
79
+ const revision = computed(() => state.document?.revision || 0);
80
+ const loading = computed(() => state.loading);
81
+ const saving = computed(() => state.saving);
82
+ const rewriting = computed(() => state.rewriting);
83
+ const savedAt = computed(() => state.savedAt);
84
+ const requestLog = computed(() => state.requestLog);
85
+ const lastRequest = computed(() => state.lastRequest);
86
+ const lastResponse = computed(() => state.lastResponse);
87
+
88
+ /**
89
+ * Loads the demo document from the configured API.
90
+ *
91
+ * @returns {Promise<void>} Resolves after the document is applied.
92
+ */
93
+ async function load() {
94
+ const log = logRequest(state, 'GET', `${config.baseUrl}/${state.documentId}`, { documentId: state.documentId });
95
+ state.loading = true;
96
+ try {
97
+ const response = await api.loadDocument({ documentId: state.documentId });
98
+ state.document = response.document || null;
99
+ state.savedAt = state.document?.updatedAt || '';
100
+ logResponse(state, log.id, response);
101
+ } catch (error) {
102
+ logError(state, log.id, error);
103
+ } finally {
104
+ state.loading = false;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Saves the current editor payload through the configured API.
110
+ *
111
+ * @param {{ content: Record<string, unknown>|string, html?: string, text?: string }} payload Save payload.
112
+ * @returns {Promise<void>} Resolves when the save request settles.
113
+ */
114
+ async function save(payload = {}) {
115
+ const request = {
116
+ documentId: state.documentId,
117
+ content: payload.content,
118
+ html: payload.html,
119
+ text: payload.text,
120
+ };
121
+ const log = logRequest(state, 'PUT', `${config.baseUrl}/${state.documentId}`, request);
122
+ state.saving = true;
123
+ try {
124
+ const response = await api.saveDocument(request);
125
+ state.document = response.document || state.document;
126
+ state.savedAt = state.document?.updatedAt || new Date().toISOString();
127
+ logResponse(state, log.id, response);
128
+ } catch (error) {
129
+ logError(state, log.id, error);
130
+ } finally {
131
+ state.saving = false;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Requests an AI rewrite for the selected editor text.
137
+ *
138
+ * @param {{ selectedText?: string, instruction?: string }} payload Rewrite payload.
139
+ * @returns {Promise<Record<string, unknown>|null>} Suggestion response, or null on failure.
140
+ */
141
+ async function rewrite(payload = {}) {
142
+ const request = {
143
+ documentId: state.documentId,
144
+ selectedText: payload.selectedText || '',
145
+ instruction: payload.instruction || '',
146
+ };
147
+ const log = logRequest(state, 'POST', `${config.baseUrl}/${state.documentId}/rewrite`, request);
148
+ state.rewriting = true;
149
+ try {
150
+ const response = await api.rewriteSelection(request);
151
+ logResponse(state, log.id, response);
152
+ return response.suggestion || null;
153
+ } catch (error) {
154
+ logError(state, log.id, error);
155
+ return null;
156
+ } finally {
157
+ state.rewriting = false;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Applies a local editor content update without persisting it.
163
+ *
164
+ * @param {Record<string, unknown>|string} value Editor content value.
165
+ * @returns {void}
166
+ */
167
+ function setContent(value) {
168
+ if (!state.document) return;
169
+ state.document = {
170
+ ...state.document,
171
+ content: value,
172
+ };
173
+ }
174
+
175
+ return {
176
+ document,
177
+ content,
178
+ title,
179
+ revision,
180
+ loading,
181
+ saving,
182
+ rewriting,
183
+ savedAt,
184
+ requestLog,
185
+ lastRequest,
186
+ lastResponse,
187
+ load,
188
+ save,
189
+ rewrite,
190
+ setContent,
191
+ };
192
+ }
193
+
194
+ /**
195
+ * Normalizes store options.
196
+ *
197
+ * @param {Record<string, unknown>} options Store options.
198
+ * @returns {Record<string, unknown>} Normalized options.
199
+ */
200
+ function normalizeStoreOptions(options = {}) {
201
+ return {
202
+ baseUrl: options.baseUrl || DEFAULT_BASE_URL,
203
+ api: options.api || null,
204
+ documentId: options.documentId || 'doc_launch_brief',
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Creates the initial reactive store state.
210
+ *
211
+ * @param {Record<string, unknown>} config Normalized config.
212
+ * @returns {Record<string, unknown>} Store state.
213
+ */
214
+ function createInitialState(config) {
215
+ return {
216
+ documentId: config.documentId,
217
+ document: null,
218
+ loading: false,
219
+ saving: false,
220
+ rewriting: false,
221
+ savedAt: '',
222
+ requestLog: [],
223
+ lastRequest: null,
224
+ lastResponse: null,
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Records a request in the demo API log.
230
+ *
231
+ * @param {Record<string, unknown>} state Store state.
232
+ * @param {string} method HTTP-like method.
233
+ * @param {string} url Request URL.
234
+ * @param {Record<string, unknown>} payload Request payload.
235
+ * @returns {Record<string, unknown>} Request log entry.
236
+ */
237
+ function logRequest(state, method, url, payload = {}) {
238
+ logCounter += 1;
239
+ const entry = {
240
+ id: `document-editor-log-${logCounter}`,
241
+ method,
242
+ url,
243
+ payload,
244
+ status: 'pending',
245
+ createdAt: new Date().toISOString(),
246
+ };
247
+ state.lastRequest = entry;
248
+ state.requestLog = [entry, ...state.requestLog].slice(0, 8);
249
+ return entry;
250
+ }
251
+
252
+ /**
253
+ * Records a successful response in the demo API log.
254
+ *
255
+ * @param {Record<string, unknown>} state Store state.
256
+ * @param {string} id Request log id.
257
+ * @param {Record<string, unknown>} response Response payload.
258
+ * @returns {void}
259
+ */
260
+ function logResponse(state, id, response) {
261
+ const entry = state.requestLog.find((item) => item.id === id);
262
+ if (!entry) return;
263
+ entry.status = 'complete';
264
+ entry.response = response;
265
+ entry.completedAt = new Date().toISOString();
266
+ state.lastResponse = response;
267
+ }
268
+
269
+ /**
270
+ * Records a failed response in the demo API log.
271
+ *
272
+ * @param {Record<string, unknown>} state Store state.
273
+ * @param {string} id Request log id.
274
+ * @param {unknown} error Error value.
275
+ * @returns {void}
276
+ */
277
+ function logError(state, id, error) {
278
+ const entry = state.requestLog.find((item) => item.id === id);
279
+ if (!entry) return;
280
+ entry.status = 'error';
281
+ entry.error = error?.message || String(error || 'Request failed.');
282
+ entry.completedAt = new Date().toISOString();
283
+ state.lastResponse = { error: entry.error };
284
+ }