@syntrologie/adapt-faq 0.0.0-semantically-released → 2.0.1

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.
@@ -7,7 +7,28 @@
7
7
  * Demonstrates the compositional action pattern where child actions
8
8
  * (faq:question) serve as configuration data for the parent widget.
9
9
  */
10
- import type { FAQWidgetProps, FAQConfig, FAQWidgetRuntime } from './types';
10
+ import type { FAQConfig, DecisionStrategy } from './types';
11
+ export interface FAQWidgetRuntime {
12
+ evaluateSync: <T>(strategy: DecisionStrategy<T>) => {
13
+ value: T;
14
+ isFallback: boolean;
15
+ };
16
+ context: {
17
+ subscribe: (callback: () => void) => () => void;
18
+ };
19
+ events: {
20
+ publish: (name: string, props?: Record<string, unknown>) => void;
21
+ };
22
+ state?: {
23
+ get: (key: string) => unknown;
24
+ set: (key: string, value: unknown) => void;
25
+ };
26
+ }
27
+ interface FAQWidgetProps {
28
+ config: FAQConfig;
29
+ runtime: FAQWidgetRuntime;
30
+ instanceId: string;
31
+ }
11
32
  /**
12
33
  * FAQWidget - Renders a collapsible Q&A accordion with per-item activation.
13
34
  *
@@ -1 +1 @@
1
- {"version":3,"file":"FAQWidget.d.ts","sourceRoot":"","sources":["../src/FAQWidget.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAqB,SAAS,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AA6N9F;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,cAAc,2CAuJxE;AAMD;;GAEG;AACH,eAAO,MAAM,kBAAkB;qBAEhB,WAAW,WACb,SAAS,GAAG;QAAE,OAAO,CAAC,EAAE,gBAAgB,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;CAuC3E,CAAC;AAEF,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"FAQWidget.d.ts","sourceRoot":"","sources":["../src/FAQWidget.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAEV,SAAS,EAIT,gBAAgB,EACjB,MAAM,SAAS,CAAC;AA0CjB,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK;QAAE,KAAK,EAAE,CAAC,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,CAAC;IACtF,OAAO,EAAE;QAAE,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;KAAE,CAAC;IAC7D,MAAM,EAAE;QAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;KAAE,CAAC;IAC7E,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;QAAC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;KAAE,CAAC;CACvF;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AA4SD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,cAAc,2CAqOxE;AAMD;;GAEG;AACH,eAAO,MAAM,kBAAkB;qBAEhB,WAAW,WACb,SAAS,GAAG;QAAE,OAAO,CAAC,EAAE,gBAAgB,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;CA+C3E,CAAC;AAEF,eAAe,SAAS,CAAC"}
package/dist/FAQWidget.js CHANGED
@@ -8,7 +8,43 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
8
  * Demonstrates the compositional action pattern where child actions
9
9
  * (faq:question) serve as configuration data for the parent widget.
10
10
  */
11
- import { useEffect, useReducer, useMemo, useCallback, useState } from 'react';
11
+ import React, { useEffect, useReducer, useMemo, useCallback, useState } from 'react';
12
+ import { createRoot } from 'react-dom/client';
13
+ // ============================================================================
14
+ // Helpers
15
+ // ============================================================================
16
+ /** Extract plain text from an FAQAnswer for search matching */
17
+ function getAnswerText(answer) {
18
+ if (typeof answer === 'string')
19
+ return answer;
20
+ if (answer.type === 'rich')
21
+ return answer.html;
22
+ return answer.content;
23
+ }
24
+ /** Render an FAQAnswer based on its type */
25
+ function renderAnswer(answer) {
26
+ if (typeof answer === 'string') {
27
+ return _jsx("p", { style: { margin: 0 }, children: answer });
28
+ }
29
+ if (answer.type === 'rich') {
30
+ return _jsx("div", { style: { margin: 0 }, dangerouslySetInnerHTML: { __html: answer.html } });
31
+ }
32
+ // markdown — render content as text (full markdown rendering is a future enhancement)
33
+ return _jsx("p", { style: { margin: 0 }, children: answer.content });
34
+ }
35
+ /** Resolve feedback config into a normalized FeedbackConfig or null */
36
+ function resolveFeedbackConfig(feedback) {
37
+ if (!feedback)
38
+ return null;
39
+ if (feedback === true) {
40
+ return { style: 'thumbs' };
41
+ }
42
+ return feedback;
43
+ }
44
+ /** Get the feedback prompt text */
45
+ function getFeedbackPrompt(feedbackConfig) {
46
+ return feedbackConfig.prompt || 'Was this helpful?';
47
+ }
12
48
  // ============================================================================
13
49
  // Styles
14
50
  // ============================================================================
@@ -73,6 +109,36 @@ const baseStyles = {
73
109
  borderRadius: '4px',
74
110
  marginBottom: '8px',
75
111
  },
112
+ categoryHeader: {
113
+ fontSize: '13px',
114
+ fontWeight: 700,
115
+ textTransform: 'uppercase',
116
+ letterSpacing: '0.05em',
117
+ padding: '12px 4px 6px 4px',
118
+ marginTop: '8px',
119
+ },
120
+ feedback: {
121
+ display: 'flex',
122
+ alignItems: 'center',
123
+ gap: '8px',
124
+ marginTop: '12px',
125
+ paddingTop: '10px',
126
+ borderTop: '1px solid rgba(0, 0, 0, 0.08)',
127
+ fontSize: '13px',
128
+ },
129
+ feedbackButton: {
130
+ background: 'none',
131
+ border: '1px solid transparent',
132
+ cursor: 'pointer',
133
+ fontSize: '16px',
134
+ padding: '4px 8px',
135
+ borderRadius: '4px',
136
+ transition: 'background-color 0.15s ease, border-color 0.15s ease',
137
+ },
138
+ feedbackButtonSelected: {
139
+ borderColor: 'rgba(0, 0, 0, 0.2)',
140
+ backgroundColor: 'rgba(0, 0, 0, 0.04)',
141
+ },
76
142
  emptyState: {
77
143
  textAlign: 'center',
78
144
  padding: '48px 24px',
@@ -116,9 +182,15 @@ const themeStyles = {
116
182
  backgroundColor: '#e0e7ff',
117
183
  color: '#4338ca',
118
184
  },
185
+ categoryHeader: {
186
+ color: '#6b7280',
187
+ },
119
188
  emptyState: {
120
189
  color: '#9ca3af',
121
190
  },
191
+ feedbackPrompt: {
192
+ color: '#6b7280',
193
+ },
122
194
  },
123
195
  dark: {
124
196
  container: {
@@ -151,15 +223,21 @@ const themeStyles = {
151
223
  backgroundColor: '#312e81',
152
224
  color: '#a5b4fc',
153
225
  },
226
+ categoryHeader: {
227
+ color: '#9ca3af',
228
+ },
154
229
  emptyState: {
155
230
  color: '#6b7280',
156
231
  },
232
+ feedbackPrompt: {
233
+ color: '#9ca3af',
234
+ },
157
235
  },
158
236
  };
159
- function FAQItem({ item, isExpanded, onToggle, theme }) {
237
+ function FAQItem({ item, isExpanded, onToggle, theme, feedbackConfig, feedbackValue, onFeedback, }) {
160
238
  const [isHovered, setIsHovered] = useState(false);
161
239
  const colors = themeStyles[theme];
162
- const { question, answer, category } = item.config;
240
+ const { question, answer } = item.config;
163
241
  const itemStyle = {
164
242
  ...baseStyles.item,
165
243
  ...colors.item,
@@ -180,11 +258,17 @@ function FAQItem({ item, isExpanded, onToggle, theme }) {
180
258
  maxHeight: isExpanded ? '500px' : '0',
181
259
  paddingBottom: isExpanded ? '16px' : '0',
182
260
  };
183
- const categoryStyle = {
184
- ...baseStyles.category,
185
- ...colors.category,
261
+ const feedbackStyle = {
262
+ ...baseStyles.feedback,
263
+ ...colors.feedbackPrompt,
186
264
  };
187
- return (_jsxs("div", { style: itemStyle, children: [_jsxs("button", { style: questionStyle, onClick: onToggle, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), "aria-expanded": isExpanded, children: [_jsx("span", { children: question }), _jsx("span", { style: chevronStyle, children: "\u25BC" })] }), _jsxs("div", { style: answerStyle, "aria-hidden": !isExpanded, children: [category && _jsx("span", { style: categoryStyle, children: category }), _jsx("p", { style: { margin: 0 }, children: answer })] })] }));
265
+ return (_jsxs("div", { style: itemStyle, "data-faq-item-id": item.config.id, children: [_jsxs("button", { style: questionStyle, onClick: onToggle, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), "aria-expanded": isExpanded, children: [_jsx("span", { children: question }), _jsx("span", { style: chevronStyle, children: '\u25BC' })] }), _jsxs("div", { style: answerStyle, "aria-hidden": !isExpanded, children: [renderAnswer(answer), isExpanded && feedbackConfig && (_jsxs("div", { style: feedbackStyle, children: [_jsx("span", { children: getFeedbackPrompt(feedbackConfig) }), _jsx("button", { style: {
266
+ ...baseStyles.feedbackButton,
267
+ ...(feedbackValue === 'up' ? baseStyles.feedbackButtonSelected : {}),
268
+ }, "aria-label": "Thumbs up", onClick: () => onFeedback(item.config.id, question, 'up'), children: '\uD83D\uDC4D' }), _jsx("button", { style: {
269
+ ...baseStyles.feedbackButton,
270
+ ...(feedbackValue === 'down' ? baseStyles.feedbackButtonSelected : {}),
271
+ }, "aria-label": "Thumbs down", onClick: () => onFeedback(item.config.id, question, 'down'), children: '\uD83D\uDC4E' })] }))] })] }));
188
272
  }
189
273
  // ============================================================================
190
274
  // FAQWidget Component
@@ -205,6 +289,10 @@ export function FAQWidget({ config, runtime, instanceId }) {
205
289
  const [expandedIds, setExpandedIds] = useState(new Set());
206
290
  // Search query state
207
291
  const [searchQuery, setSearchQuery] = useState('');
292
+ // Track feedback state per item
293
+ const [feedbackState, setFeedbackState] = useState(new Map());
294
+ // Resolve feedback config
295
+ const feedbackConfig = useMemo(() => resolveFeedbackConfig(config.feedback), [config.feedback]);
208
296
  // Subscribe to context changes for reactive updates
209
297
  useEffect(() => {
210
298
  const unsubscribe = runtime.context.subscribe(() => {
@@ -213,27 +301,47 @@ export function FAQWidget({ config, runtime, instanceId }) {
213
301
  return unsubscribe;
214
302
  }, [runtime.context]);
215
303
  // Filter visible questions based on per-item showWhen
216
- const visibleQuestions = useMemo(() => {
217
- return config.actions.filter((q) => {
218
- // No showWhen = always visible
219
- if (!q.showWhen)
220
- return true;
221
- // Evaluate the decision strategy
222
- const result = runtime.evaluateSync(q.showWhen);
223
- return result.value;
224
- });
225
- }, [config.actions, runtime]);
304
+ const visibleQuestions = useMemo(() => config.actions.filter((q) => {
305
+ // No showWhen = always visible
306
+ if (!q.showWhen)
307
+ return true;
308
+ // Evaluate the decision strategy
309
+ const result = runtime.evaluateSync(q.showWhen);
310
+ return result.value;
311
+ }), [config.actions, runtime]);
312
+ // Apply priority ordering
313
+ const orderedQuestions = useMemo(() => {
314
+ if (config.ordering === 'priority') {
315
+ return [...visibleQuestions].sort((a, b) => (b.config.priority ?? 0) - (a.config.priority ?? 0));
316
+ }
317
+ // 'static' or undefined — preserve config order
318
+ return visibleQuestions;
319
+ }, [visibleQuestions, config.ordering]);
226
320
  // Apply search filter
227
321
  const filteredQuestions = useMemo(() => {
228
322
  if (!config.searchable || !searchQuery.trim()) {
229
- return visibleQuestions;
323
+ return orderedQuestions;
230
324
  }
231
325
  const query = searchQuery.toLowerCase();
232
- return visibleQuestions.filter((q) => q.config.question.toLowerCase().includes(query) ||
233
- q.config.answer.toLowerCase().includes(query) ||
326
+ return orderedQuestions.filter((q) => q.config.question.toLowerCase().includes(query) ||
327
+ getAnswerText(q.config.answer).toLowerCase().includes(query) ||
234
328
  q.config.category?.toLowerCase().includes(query));
235
- }, [visibleQuestions, searchQuery, config.searchable]);
236
- // Resolve theme (auto → detect system preference)
329
+ }, [orderedQuestions, searchQuery, config.searchable]);
330
+ // Group by category
331
+ const categoryGroups = useMemo(() => {
332
+ const groups = new Map();
333
+ for (const q of filteredQuestions) {
334
+ const cat = q.config.category;
335
+ if (!groups.has(cat)) {
336
+ groups.set(cat, []);
337
+ }
338
+ groups.get(cat).push(q);
339
+ }
340
+ return groups;
341
+ }, [filteredQuestions]);
342
+ // Check if any items have categories
343
+ const hasCategories = useMemo(() => filteredQuestions.some((q) => q.config.category), [filteredQuestions]);
344
+ // Resolve theme (auto -> detect system preference)
237
345
  const resolvedTheme = useMemo(() => {
238
346
  if (config.theme !== 'auto')
239
347
  return config.theme;
@@ -254,16 +362,14 @@ export function FAQWidget({ config, runtime, instanceId }) {
254
362
  }
255
363
  return new Set([id]);
256
364
  }
365
+ // Multiple mode: toggle this one
366
+ if (prev.has(id)) {
367
+ next.delete(id);
368
+ }
257
369
  else {
258
- // Multiple mode: toggle this one
259
- if (prev.has(id)) {
260
- next.delete(id);
261
- }
262
- else {
263
- next.add(id);
264
- }
265
- return next;
370
+ next.add(id);
266
371
  }
372
+ return next;
267
373
  });
268
374
  // Publish toggle event for analytics
269
375
  runtime.events.publish('faq:toggled', {
@@ -273,6 +379,19 @@ export function FAQWidget({ config, runtime, instanceId }) {
273
379
  timestamp: Date.now(),
274
380
  });
275
381
  }, [config.expandBehavior, runtime.events, instanceId, expandedIds]);
382
+ // Handle feedback
383
+ const handleFeedback = useCallback((itemId, question, value) => {
384
+ setFeedbackState((prev) => {
385
+ const next = new Map(prev);
386
+ next.set(itemId, value);
387
+ return next;
388
+ });
389
+ runtime.events.publish('faq:feedback', {
390
+ itemId,
391
+ question,
392
+ value,
393
+ });
394
+ }, [runtime.events]);
276
395
  // Compute styles
277
396
  const containerStyle = {
278
397
  ...baseStyles.container,
@@ -286,11 +405,19 @@ export function FAQWidget({ config, runtime, instanceId }) {
286
405
  ...baseStyles.emptyState,
287
406
  ...themeStyles[resolvedTheme].emptyState,
288
407
  };
408
+ const categoryHeaderStyle = {
409
+ ...baseStyles.categoryHeader,
410
+ ...themeStyles[resolvedTheme].categoryHeader,
411
+ };
412
+ // Render a list of FAQ items
413
+ const renderItems = (items) => items.map((q) => (_jsx(FAQItem, { item: q, isExpanded: expandedIds.has(q.config.id), onToggle: () => handleToggle(q.config.id), theme: resolvedTheme, feedbackConfig: feedbackConfig, feedbackValue: feedbackState.get(q.config.id), onFeedback: handleFeedback }, q.config.id)));
289
414
  // Empty state (no visible questions at all)
290
415
  if (visibleQuestions.length === 0) {
291
416
  return (_jsx("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-faq", children: _jsx("div", { style: emptyStateStyle, children: "No FAQ questions available." }) }));
292
417
  }
293
- return (_jsxs("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-faq", children: [config.searchable && (_jsx("div", { style: baseStyles.searchWrapper, children: _jsx("input", { type: "text", placeholder: "Search questions...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), style: searchInputStyle }) })), _jsx("div", { style: baseStyles.accordion, children: filteredQuestions.map((q) => (_jsx(FAQItem, { item: q, isExpanded: expandedIds.has(q.config.id), onToggle: () => handleToggle(q.config.id), theme: resolvedTheme }, q.config.id))) }), config.searchable && filteredQuestions.length === 0 && searchQuery && (_jsxs("div", { style: { ...baseStyles.noResults, ...themeStyles[resolvedTheme].emptyState }, children: ["No questions found matching \"", searchQuery, "\""] }))] }));
418
+ return (_jsxs("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-faq", children: [config.searchable && (_jsx("div", { style: baseStyles.searchWrapper, children: _jsx("input", { type: "text", placeholder: "Search questions...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), style: searchInputStyle }) })), _jsx("div", { style: baseStyles.accordion, children: hasCategories
419
+ ? Array.from(categoryGroups.entries()).map(([category, items]) => (_jsxs(React.Fragment, { children: [category && (_jsx("div", { style: categoryHeaderStyle, "data-category-header": category, children: category })), renderItems(items)] }, category ?? '__ungrouped')))
420
+ : renderItems(filteredQuestions) }), config.searchable && filteredQuestions.length === 0 && searchQuery && (_jsxs("div", { style: { ...baseStyles.noResults, ...themeStyles[resolvedTheme].emptyState }, children: ["No questions found matching \"", searchQuery, "\""] }))] }));
294
421
  }
295
422
  // ============================================================================
296
423
  // Mountable Widget Interface
@@ -300,30 +427,36 @@ export function FAQWidget({ config, runtime, instanceId }) {
300
427
  */
301
428
  export const FAQMountableWidget = {
302
429
  mount(container, config) {
303
- // This is a simplified mount for non-React environments
304
- // In practice, the runtime handles React rendering
305
- const { runtime, instanceId: _instanceId = 'faq-widget', ...faqConfig } = config || {
430
+ const { runtime, instanceId = 'faq-widget', ...faqConfig } = config || {
306
431
  expandBehavior: 'single',
307
432
  searchable: false,
308
433
  theme: 'auto',
309
434
  actions: [],
310
435
  };
311
- // Create simple HTML fallback if no runtime
312
- if (!runtime) {
313
- const questions = faqConfig.actions || [];
314
- container.innerHTML = `
315
- <div style="font-family: system-ui; max-width: 800px;">
316
- ${questions
317
- .map((q) => `
318
- <div style="margin-bottom: 8px; padding: 16px; background: #f9fafb; border-radius: 8px;">
319
- <strong>${q.config.question}</strong>
320
- <p style="margin-top: 8px; color: #4b5563;">${q.config.answer}</p>
321
- </div>
322
- `)
323
- .join('')}
324
- </div>
325
- `;
436
+ // React rendering when runtime + ReactDOM are available
437
+ if (runtime && typeof createRoot === 'function') {
438
+ const root = createRoot(container);
439
+ root.render(React.createElement(FAQWidget, {
440
+ config: faqConfig,
441
+ runtime: runtime,
442
+ instanceId,
443
+ }));
444
+ return () => { root.unmount(); };
326
445
  }
446
+ // HTML fallback for non-React environments
447
+ const questions = faqConfig.actions || [];
448
+ container.innerHTML = `
449
+ <div style="font-family: system-ui; max-width: 800px;">
450
+ ${questions
451
+ .map((q) => `
452
+ <div style="margin-bottom: 8px; padding: 16px; background: #f9fafb; border-radius: 8px;">
453
+ <strong>${q.config.question}</strong>
454
+ <p style="margin-top: 8px; color: #4b5563;">${getAnswerText(q.config.answer)}</p>
455
+ </div>
456
+ `)
457
+ .join('')}
458
+ </div>
459
+ `;
327
460
  return () => {
328
461
  container.innerHTML = '';
329
462
  };
package/dist/cdn.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * This module is bundled for CDN delivery and self-registers with the global
5
5
  * SynOS app registry when loaded dynamically via the AppLoader.
6
6
  */
7
+ import FAQEditor from './editor';
7
8
  /**
8
9
  * App manifest for registry registration.
9
10
  * Follows the AppManifest interface expected by AppLoader/AppRegistry.
@@ -14,12 +15,21 @@ export declare const manifest: {
14
15
  name: string;
15
16
  description: string;
16
17
  runtime: {
17
- actions: never[];
18
+ actions: readonly [{
19
+ readonly kind: "faq:scroll_to";
20
+ readonly executor: typeof import("./executors").executeScrollToFaq;
21
+ }, {
22
+ readonly kind: "faq:toggle_item";
23
+ readonly executor: typeof import("./executors").executeToggleFaqItem;
24
+ }, {
25
+ readonly kind: "faq:update";
26
+ readonly executor: typeof import("./executors").executeUpdateFaq;
27
+ }];
18
28
  widgets: {
19
29
  id: string;
20
30
  component: {
21
31
  mount(container: HTMLElement, config?: import("./types").FAQConfig & {
22
- runtime?: import("./types").FAQWidgetRuntime;
32
+ runtime?: import("./FAQWidget").FAQWidgetRuntime;
23
33
  instanceId?: string;
24
34
  }): () => void;
25
35
  };
@@ -30,6 +40,14 @@ export declare const manifest: {
30
40
  };
31
41
  }[];
32
42
  };
43
+ editor: {
44
+ component: typeof FAQEditor;
45
+ panel: {
46
+ title: string;
47
+ icon: string;
48
+ description: string;
49
+ };
50
+ };
33
51
  metadata: {
34
52
  isBuiltIn: boolean;
35
53
  };
package/dist/cdn.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;2BA2BixT,CAAC;8BAA8B,CAAC;;;;;;;;;;;;;CAdr0T,CAAC;AAaF,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,SAA0B,MAAM,UAAU,CAAC;AAGlD;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;2BA2B8kgB,CAAC;8BAA8B,CAAC;;;;;;;;;;;;;;;;;;;;;CAdlogB,CAAC;AAaF,eAAe,QAAQ,CAAC"}
package/dist/cdn.js CHANGED
@@ -4,21 +4,22 @@
4
4
  * This module is bundled for CDN delivery and self-registers with the global
5
5
  * SynOS app registry when loaded dynamically via the AppLoader.
6
6
  */
7
+ import FAQEditor, { editorPanel } from './editor';
7
8
  import { runtime } from './runtime';
8
9
  /**
9
10
  * App manifest for registry registration.
10
11
  * Follows the AppManifest interface expected by AppLoader/AppRegistry.
11
12
  */
12
13
  export const manifest = {
13
- id: 'faq',
14
+ id: 'adaptive-faq',
14
15
  version: runtime.version,
15
16
  name: runtime.name,
16
17
  description: runtime.description,
17
18
  runtime: {
18
- // FAQ is widget-based, no action executors
19
- actions: [],
19
+ actions: runtime.executors,
20
20
  widgets: runtime.widgets,
21
21
  },
22
+ editor: { component: FAQEditor, panel: editorPanel },
22
23
  metadata: {
23
24
  isBuiltIn: false,
24
25
  },
@@ -28,9 +29,9 @@ export const manifest = {
28
29
  * This happens when loaded via script tag (UMD).
29
30
  */
30
31
  if (typeof window !== 'undefined') {
31
- const globalRegistry = window.__SYNOS_APP_REGISTRY__;
32
- if (globalRegistry && typeof globalRegistry.register === 'function') {
33
- globalRegistry.register(manifest);
32
+ const registry = window.SynOS?.appRegistry;
33
+ if (registry && typeof registry.register === 'function') {
34
+ registry.register(manifest);
34
35
  }
35
36
  }
36
37
  export default manifest;
package/dist/editor.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * Adaptive FAQ - Editor Component
3
3
  *
4
- * Visual editor panel for configuring FAQ questions.
4
+ * Review & tweak editor for AI-generated FAQ question decisions.
5
+ * Displays a scannable list of Q&A cards with trigger, rationale,
6
+ * and inline editing. Includes detection badges and hover-to-highlight.
5
7
  */
6
- import type { EditorPanelProps } from './types';
8
+ import { type EditorPanelProps } from './types';
7
9
  export declare function FAQEditor({ config, onChange, editor }: EditorPanelProps): import("react/jsx-runtime").JSX.Element;
8
10
  /**
9
11
  * Editor panel configuration for the app registry.
@@ -13,5 +15,13 @@ export declare const editorPanel: {
13
15
  icon: string;
14
16
  description: string;
15
17
  };
18
+ export declare const editor: {
19
+ panel: {
20
+ title: string;
21
+ icon: string;
22
+ description: string;
23
+ };
24
+ component: typeof FAQEditor;
25
+ };
16
26
  export default FAQEditor;
17
27
  //# sourceMappingURL=editor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAqMhD,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,gBAAgB,2CAgNvE;AAED;;GAEG;AACH,eAAO,MAAM,WAAW;;;;CAIvB,CAAC;AAEF,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAKL,KAAK,gBAAgB,EAEtB,MAAM,SAAS,CAAC;AAgbjB,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,gBAAgB,2CA0WvE;AAED;;GAEG;AACH,eAAO,MAAM,WAAW;;;;CAIvB,CAAC;AAEF,eAAO,MAAM,MAAM;;;;;;;CAGlB,CAAC;AAEF,eAAe,SAAS,CAAC"}