liteagents 2.5.2 → 2.6.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 (80) hide show
  1. package/CHANGELOG.md +76 -15
  2. package/README.md +58 -30
  3. package/docs/INSTALLATION_DEMO.md +1 -1
  4. package/docs/INSTALLATION_LOCATIONS.md +5 -5
  5. package/docs/INSTALLER_GUIDE.md +5 -5
  6. package/docs/KNOWLEDGE_BASE.md +5 -5
  7. package/docs/MIGRATION.md +1 -1
  8. package/docs/PASS_QUICK_START.md +1 -1
  9. package/docs/PRIVACY.md +1 -1
  10. package/docs/PUBLISHING.md +7 -7
  11. package/docs/RELEASE_NOTES_1.2.0.md +2 -2
  12. package/docs/SILENT_MODE_GUIDE.md +1 -1
  13. package/installer/cli.js +1 -1
  14. package/installer/installation-engine.js +82 -21
  15. package/installer/package-manager.js +34 -3
  16. package/package.json +7 -6
  17. package/packages/ampcode/AGENT.md +1 -0
  18. package/packages/ampcode/commands/friction/friction.js +19 -4
  19. package/packages/ampcode/commands/live-canvas/DESIGN_PRINCIPLES.md +2140 -0
  20. package/packages/ampcode/commands/live-canvas/README.md +273 -0
  21. package/packages/ampcode/commands/live-canvas/templates/.claude/settings.local.json +8 -0
  22. package/packages/ampcode/commands/live-canvas/templates/demo/post-variants.html +205 -0
  23. package/packages/ampcode/commands/live-canvas/templates/feedback-react/FeedbackOverlay.tsx +1299 -0
  24. package/packages/ampcode/commands/live-canvas/templates/feedback-react/format-utils.ts +283 -0
  25. package/packages/ampcode/commands/live-canvas/templates/feedback-react/index.ts +62 -0
  26. package/packages/ampcode/commands/live-canvas/templates/feedback-react/selector-utils.ts +423 -0
  27. package/packages/ampcode/commands/live-canvas/templates/feedback-react/types.ts +118 -0
  28. package/packages/ampcode/commands/live-canvas/templates/overlay-vanilla.js +477 -0
  29. package/packages/ampcode/commands/live-canvas.md +1104 -0
  30. package/packages/claude/CLAUDE.md +1 -0
  31. package/packages/claude/commands/friction/friction.js +19 -4
  32. package/packages/claude/plugins/live-canvas-marketplace/.claude-plugin/marketplace.json +14 -0
  33. package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/.claude-plugin/plugin.json +18 -0
  34. package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/README.md +89 -0
  35. package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/package-lock.json +1142 -0
  36. package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/package.json +17 -0
  37. package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/schema.json +37 -0
  38. package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/server.js +179 -0
  39. package/packages/claude/plugins/live-canvas-marketplace/setup.sh +61 -0
  40. package/packages/claude/skills/live-canvas/DESIGN_PRINCIPLES.md +2140 -0
  41. package/packages/claude/skills/live-canvas/INTEGRATION_NOTES.md +210 -0
  42. package/packages/claude/skills/live-canvas/README.md +273 -0
  43. package/packages/claude/skills/live-canvas/SKILL.md +1119 -0
  44. package/packages/claude/skills/live-canvas/templates/.claude/settings.local.json +8 -0
  45. package/packages/claude/skills/live-canvas/templates/demo/post-variants.html +205 -0
  46. package/packages/claude/skills/live-canvas/templates/feedback-react/FeedbackOverlay.tsx +1299 -0
  47. package/packages/claude/skills/live-canvas/templates/feedback-react/format-utils.ts +283 -0
  48. package/packages/claude/skills/live-canvas/templates/feedback-react/index.ts +62 -0
  49. package/packages/claude/skills/live-canvas/templates/feedback-react/selector-utils.ts +423 -0
  50. package/packages/claude/skills/live-canvas/templates/feedback-react/types.ts +118 -0
  51. package/packages/claude/skills/live-canvas/templates/overlay-vanilla.js +477 -0
  52. package/packages/claude/variants.json +2 -1
  53. package/packages/droid/AGENTS.md +1 -0
  54. package/packages/droid/commands/friction/friction.js +19 -4
  55. package/packages/droid/commands/live-canvas/DESIGN_PRINCIPLES.md +2140 -0
  56. package/packages/droid/commands/live-canvas/README.md +273 -0
  57. package/packages/droid/commands/live-canvas/templates/.claude/settings.local.json +8 -0
  58. package/packages/droid/commands/live-canvas/templates/demo/post-variants.html +205 -0
  59. package/packages/droid/commands/live-canvas/templates/feedback-react/FeedbackOverlay.tsx +1299 -0
  60. package/packages/droid/commands/live-canvas/templates/feedback-react/format-utils.ts +283 -0
  61. package/packages/droid/commands/live-canvas/templates/feedback-react/index.ts +62 -0
  62. package/packages/droid/commands/live-canvas/templates/feedback-react/selector-utils.ts +423 -0
  63. package/packages/droid/commands/live-canvas/templates/feedback-react/types.ts +118 -0
  64. package/packages/droid/commands/live-canvas/templates/overlay-vanilla.js +477 -0
  65. package/packages/droid/commands/live-canvas.md +1104 -0
  66. package/packages/opencode/AGENTS.md +1 -0
  67. package/packages/opencode/command/friction/friction.js +19 -4
  68. package/packages/opencode/command/live-canvas/DESIGN_PRINCIPLES.md +2140 -0
  69. package/packages/opencode/command/live-canvas/README.md +273 -0
  70. package/packages/opencode/command/live-canvas/templates/.claude/settings.local.json +8 -0
  71. package/packages/opencode/command/live-canvas/templates/demo/post-variants.html +205 -0
  72. package/packages/opencode/command/live-canvas/templates/feedback-react/FeedbackOverlay.tsx +1299 -0
  73. package/packages/opencode/command/live-canvas/templates/feedback-react/format-utils.ts +283 -0
  74. package/packages/opencode/command/live-canvas/templates/feedback-react/index.ts +62 -0
  75. package/packages/opencode/command/live-canvas/templates/feedback-react/selector-utils.ts +423 -0
  76. package/packages/opencode/command/live-canvas/templates/feedback-react/types.ts +118 -0
  77. package/packages/opencode/command/live-canvas/templates/overlay-vanilla.js +477 -0
  78. package/packages/opencode/command/live-canvas.md +1104 -0
  79. package/packages/opencode/opencode.jsonc +4 -0
  80. package/packages/subagentic-manual.md +18 -15
@@ -0,0 +1,423 @@
1
+ /**
2
+ * Design Lab Interactive Feedback System - Element Selector Utilities
3
+ *
4
+ * Generates reliable CSS selectors and human-readable paths for clicked elements.
5
+ * Uses multiple fallback strategies to ensure Claude can locate elements in code.
6
+ */
7
+
8
+ import type { ElementIdentifier, VariantId, Coordinates } from './types';
9
+ import { OVERLAY_CLASS_PREFIX } from './types';
10
+
11
+ /**
12
+ * Regex pattern to detect CSS-in-JS generated class names.
13
+ * Matches patterns like: css-1a2b3c, sc-abc123, emotion-xyz, etc.
14
+ */
15
+ const CSS_IN_JS_PATTERN = /^(css|sc|emotion|styled|chakra|mui|ant)-[a-z0-9]+$/i;
16
+
17
+ /**
18
+ * Regex pattern to detect hash-like suffixes in class names.
19
+ * Matches: component_abc123, button__hash, etc.
20
+ */
21
+ const HASH_SUFFIX_PATTERN = /_[a-z0-9]{5,}$/i;
22
+
23
+ /**
24
+ * Check if a class name appears to be generated by CSS-in-JS.
25
+ */
26
+ function isCssInJsClass(className: string): boolean {
27
+ return (
28
+ CSS_IN_JS_PATTERN.test(className) ||
29
+ HASH_SUFFIX_PATTERN.test(className) ||
30
+ // Tailwind-like utility classes are fine, but random hashes aren't
31
+ /^[a-z]{1,3}[0-9]{4,}$/i.test(className)
32
+ );
33
+ }
34
+
35
+ /**
36
+ * Filter class names to remove CSS-in-JS generated ones.
37
+ * Returns only meaningful, human-authored class names.
38
+ */
39
+ function filterClasses(classNames: string): string {
40
+ if (!classNames) return '';
41
+
42
+ return classNames
43
+ .split(/\s+/)
44
+ .filter((cls) => cls && !isCssInJsClass(cls))
45
+ .join(' ');
46
+ }
47
+
48
+ /**
49
+ * Get relevant attributes from an element.
50
+ * Focuses on data-*, aria-*, and id attributes.
51
+ */
52
+ function getRelevantAttributes(element: HTMLElement): Record<string, string> {
53
+ const attrs: Record<string, string> = {};
54
+ const relevantPrefixes = ['data-', 'aria-'];
55
+
56
+ for (const attr of Array.from(element.attributes)) {
57
+ if (
58
+ relevantPrefixes.some((prefix) => attr.name.startsWith(prefix)) ||
59
+ attr.name === 'id' ||
60
+ attr.name === 'role' ||
61
+ attr.name === 'type' ||
62
+ attr.name === 'name'
63
+ ) {
64
+ attrs[attr.name] = attr.value;
65
+ }
66
+ }
67
+
68
+ return attrs;
69
+ }
70
+
71
+ /**
72
+ * Generate a CSS selector for an element, prioritizing reliable identifiers.
73
+ *
74
+ * Priority order:
75
+ * 1. data-testid or data-cy (most reliable for code lookup)
76
+ * 2. Unique ID
77
+ * 3. Semantic + accessible name (aria-label, role)
78
+ * 4. Meaningful class path
79
+ * 5. Tag + text content hint
80
+ * 6. Structural path (fallback)
81
+ */
82
+ export function generateSelector(
83
+ element: HTMLElement,
84
+ variantRoot: HTMLElement
85
+ ): string {
86
+ // Priority 1: data-testid or data-cy
87
+ const testId =
88
+ element.getAttribute('data-testid') || element.getAttribute('data-cy');
89
+ if (testId) {
90
+ return `[data-testid="${testId}"]`;
91
+ }
92
+
93
+ // Priority 2: Unique ID (not auto-generated)
94
+ const id = element.id;
95
+ if (id && !isCssInJsClass(id) && !id.startsWith(':')) {
96
+ return `#${id}`;
97
+ }
98
+
99
+ // Priority 3: Semantic + aria-label
100
+ const ariaLabel = element.getAttribute('aria-label');
101
+ const role = element.getAttribute('role');
102
+ if (ariaLabel) {
103
+ const tag = element.tagName.toLowerCase();
104
+ return `${tag}[aria-label="${ariaLabel}"]`;
105
+ }
106
+ if (role && !['generic', 'presentation', 'none'].includes(role)) {
107
+ const tag = element.tagName.toLowerCase();
108
+ // Add context to make more unique
109
+ const name = element.getAttribute('name');
110
+ if (name) {
111
+ return `${tag}[role="${role}"][name="${name}"]`;
112
+ }
113
+ return `${tag}[role="${role}"]`;
114
+ }
115
+
116
+ // Priority 4: Meaningful class path
117
+ const meaningfulClasses = filterClasses(element.className);
118
+ if (meaningfulClasses) {
119
+ const classSelector = meaningfulClasses
120
+ .split(/\s+/)
121
+ .slice(0, 2) // Use first 2 meaningful classes
122
+ .map((c) => `.${c}`)
123
+ .join('');
124
+
125
+ // Check if this is unique enough within the variant
126
+ const matches = variantRoot.querySelectorAll(classSelector);
127
+ if (matches.length === 1) {
128
+ return classSelector;
129
+ }
130
+
131
+ // Add parent context
132
+ const parent = element.parentElement;
133
+ if (parent && parent !== variantRoot) {
134
+ const parentClasses = filterClasses(parent.className);
135
+ if (parentClasses) {
136
+ const parentSelector = parentClasses
137
+ .split(/\s+/)
138
+ .slice(0, 1)
139
+ .map((c) => `.${c}`)
140
+ .join('');
141
+ return `${parentSelector} > ${classSelector}`;
142
+ }
143
+ }
144
+
145
+ return classSelector;
146
+ }
147
+
148
+ // Priority 5: Tag + type for inputs/buttons
149
+ const tag = element.tagName.toLowerCase();
150
+ const type = element.getAttribute('type');
151
+ const name = element.getAttribute('name');
152
+
153
+ if (tag === 'input' || tag === 'button' || tag === 'select') {
154
+ if (name) {
155
+ return `${tag}[name="${name}"]`;
156
+ }
157
+ if (type) {
158
+ return `${tag}[type="${type}"]`;
159
+ }
160
+ }
161
+
162
+ // Priority 6: Structural path (fallback)
163
+ return generateStructuralPath(element, variantRoot);
164
+ }
165
+
166
+ /**
167
+ * Generate a structural path selector (nth-child based).
168
+ * Used as a fallback when no better identifiers exist.
169
+ */
170
+ function generateStructuralPath(
171
+ element: HTMLElement,
172
+ variantRoot: HTMLElement
173
+ ): string {
174
+ const path: string[] = [];
175
+ let current: HTMLElement | null = element;
176
+
177
+ while (current && current !== variantRoot && current.parentElement) {
178
+ const parent = current.parentElement;
179
+ const siblings = Array.from(parent.children).filter(
180
+ (child) => child.tagName === current!.tagName
181
+ );
182
+
183
+ const tag = current.tagName.toLowerCase();
184
+
185
+ if (siblings.length === 1) {
186
+ path.unshift(tag);
187
+ } else {
188
+ const index = siblings.indexOf(current) + 1;
189
+ path.unshift(`${tag}:nth-child(${index})`);
190
+ }
191
+
192
+ current = parent;
193
+
194
+ // Limit depth to avoid overly long selectors
195
+ if (path.length >= 4) break;
196
+ }
197
+
198
+ return path.join(' > ');
199
+ }
200
+
201
+ /**
202
+ * Generate a human-readable path for an element.
203
+ * Example: "Variant B > Form > Submit Button"
204
+ */
205
+ export function generateReadablePath(
206
+ element: HTMLElement,
207
+ variantRoot: HTMLElement,
208
+ variantId: VariantId
209
+ ): string {
210
+ const parts: string[] = [`Variant ${variantId}`];
211
+ const path: HTMLElement[] = [];
212
+
213
+ let current: HTMLElement | null = element;
214
+ while (current && current !== variantRoot) {
215
+ path.unshift(current);
216
+ current = current.parentElement;
217
+ }
218
+
219
+ // Take up to 3 ancestors for context
220
+ const relevantPath = path.slice(-3);
221
+
222
+ for (const el of relevantPath) {
223
+ const label = getElementLabel(el);
224
+ if (label) {
225
+ parts.push(label);
226
+ }
227
+ }
228
+
229
+ return parts.join(' > ');
230
+ }
231
+
232
+ /**
233
+ * Get a human-readable label for an element.
234
+ */
235
+ function getElementLabel(element: HTMLElement): string {
236
+ const tag = element.tagName.toLowerCase();
237
+
238
+ // Use aria-label if available
239
+ const ariaLabel = element.getAttribute('aria-label');
240
+ if (ariaLabel) {
241
+ return ariaLabel;
242
+ }
243
+
244
+ // Use text content for buttons and links
245
+ if (tag === 'button' || tag === 'a') {
246
+ const text = element.textContent?.trim().slice(0, 30);
247
+ if (text) {
248
+ return `${capitalizeTag(tag)}: "${text}"`;
249
+ }
250
+ }
251
+
252
+ // Use name or placeholder for inputs
253
+ if (tag === 'input' || tag === 'select' || tag === 'textarea') {
254
+ const name =
255
+ element.getAttribute('name') ||
256
+ element.getAttribute('placeholder') ||
257
+ element.getAttribute('aria-label');
258
+ if (name) {
259
+ return `${capitalizeTag(tag)}: ${name}`;
260
+ }
261
+ }
262
+
263
+ // Use heading level
264
+ if (/^h[1-6]$/.test(tag)) {
265
+ const text = element.textContent?.trim().slice(0, 30);
266
+ if (text) {
267
+ return `Heading: "${text}"`;
268
+ }
269
+ }
270
+
271
+ // Use meaningful classes
272
+ const classes = filterClasses(element.className);
273
+ if (classes) {
274
+ const primary = classes.split(/\s+/)[0];
275
+ return formatClassName(primary);
276
+ }
277
+
278
+ // Fallback to tag name
279
+ return capitalizeTag(tag);
280
+ }
281
+
282
+ /**
283
+ * Capitalize a tag name for display.
284
+ */
285
+ function capitalizeTag(tag: string): string {
286
+ const tagLabels: Record<string, string> = {
287
+ div: 'Container',
288
+ section: 'Section',
289
+ article: 'Article',
290
+ nav: 'Navigation',
291
+ header: 'Header',
292
+ footer: 'Footer',
293
+ main: 'Main',
294
+ aside: 'Sidebar',
295
+ form: 'Form',
296
+ ul: 'List',
297
+ ol: 'Numbered List',
298
+ li: 'List Item',
299
+ button: 'Button',
300
+ a: 'Link',
301
+ input: 'Input',
302
+ select: 'Dropdown',
303
+ textarea: 'Text Area',
304
+ img: 'Image',
305
+ span: 'Text',
306
+ p: 'Paragraph',
307
+ table: 'Table',
308
+ tr: 'Row',
309
+ td: 'Cell',
310
+ th: 'Header Cell',
311
+ };
312
+
313
+ return tagLabels[tag] || tag.charAt(0).toUpperCase() + tag.slice(1);
314
+ }
315
+
316
+ /**
317
+ * Format a class name for display.
318
+ * Converts kebab-case or camelCase to readable text.
319
+ */
320
+ function formatClassName(className: string): string {
321
+ return className
322
+ .replace(/[-_]/g, ' ')
323
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
324
+ .replace(/\b\w/g, (c) => c.toUpperCase());
325
+ }
326
+
327
+ /**
328
+ * Get truncated text content from an element.
329
+ */
330
+ export function getTextContent(element: HTMLElement, maxLength = 50): string {
331
+ const text = element.textContent?.trim() || '';
332
+ if (text.length <= maxLength) {
333
+ return text;
334
+ }
335
+ return text.slice(0, maxLength - 3) + '...';
336
+ }
337
+
338
+ /**
339
+ * Calculate coordinates as percentages within the variant container.
340
+ */
341
+ export function calculateCoordinates(
342
+ element: HTMLElement,
343
+ variantRoot: HTMLElement,
344
+ clickX: number,
345
+ clickY: number
346
+ ): Coordinates {
347
+ const rootRect = variantRoot.getBoundingClientRect();
348
+
349
+ const x = ((clickX - rootRect.left) / rootRect.width) * 100;
350
+ const y = ((clickY - rootRect.top) / rootRect.height) * 100;
351
+
352
+ return {
353
+ x: Math.round(x * 10) / 10,
354
+ y: Math.round(y * 10) / 10,
355
+ };
356
+ }
357
+
358
+ /**
359
+ * Identify an element completely, returning all identification data.
360
+ */
361
+ export function identifyElement(
362
+ element: HTMLElement,
363
+ variantRoot: HTMLElement,
364
+ variantId: VariantId,
365
+ clickX: number,
366
+ clickY: number
367
+ ): { element: ElementIdentifier; coordinates: Coordinates } {
368
+ return {
369
+ element: {
370
+ selector: generateSelector(element, variantRoot),
371
+ readablePath: generateReadablePath(element, variantRoot, variantId),
372
+ tagName: element.tagName.toLowerCase(),
373
+ textContent: getTextContent(element),
374
+ className: filterClasses(element.className),
375
+ attributes: getRelevantAttributes(element),
376
+ },
377
+ coordinates: calculateCoordinates(element, variantRoot, clickX, clickY),
378
+ };
379
+ }
380
+
381
+ /**
382
+ * Check if an element is part of the feedback overlay itself.
383
+ * Used to prevent selecting overlay elements.
384
+ */
385
+ export function isOverlayElement(element: HTMLElement): boolean {
386
+ let current: HTMLElement | null = element;
387
+
388
+ while (current) {
389
+ if (
390
+ current.className &&
391
+ typeof current.className === 'string' &&
392
+ current.className.includes(OVERLAY_CLASS_PREFIX)
393
+ ) {
394
+ return true;
395
+ }
396
+ current = current.parentElement;
397
+ }
398
+
399
+ return false;
400
+ }
401
+
402
+ /**
403
+ * Find the variant container that an element belongs to.
404
+ * Looks for elements with data-variant attribute.
405
+ */
406
+ export function findVariantContainer(
407
+ element: HTMLElement
408
+ ): { root: HTMLElement; variantId: VariantId } | null {
409
+ let current: HTMLElement | null = element;
410
+
411
+ while (current) {
412
+ const variantId = current.getAttribute('data-variant');
413
+ if (variantId && /^[A-F]$/.test(variantId)) {
414
+ return {
415
+ root: current,
416
+ variantId: variantId as VariantId,
417
+ };
418
+ }
419
+ current = current.parentElement;
420
+ }
421
+
422
+ return null;
423
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Design Lab Interactive Feedback System - Type Definitions
3
+ *
4
+ * These types define the structure for capturing, storing, and
5
+ * formatting user feedback on design variants.
6
+ */
7
+
8
+ /** Supported variant identifiers */
9
+ export type VariantId = 'A' | 'B' | 'C' | 'D' | 'E' | 'F';
10
+
11
+ /**
12
+ * Element identification with multiple fallback strategies.
13
+ * Claude uses these identifiers to locate elements in code.
14
+ */
15
+ export interface ElementIdentifier {
16
+ /** Primary: CSS selector (data-testid preferred) */
17
+ selector: string;
18
+ /** Secondary: Human-readable path for context */
19
+ readablePath: string;
20
+ /** HTML tag name */
21
+ tagName: string;
22
+ /** First 50 chars of text content (for disambiguation) */
23
+ textContent: string;
24
+ /** Original class names (CSS-in-JS hashes filtered out) */
25
+ className: string;
26
+ /** Relevant attributes (data-*, aria-*, id) */
27
+ attributes: Record<string, string>;
28
+ }
29
+
30
+ /**
31
+ * Position coordinates as percentages within the variant container.
32
+ * Used as visual backup when selectors fail.
33
+ */
34
+ export interface Coordinates {
35
+ /** Percentage from left edge of variant container */
36
+ x: number;
37
+ /** Percentage from top edge of variant container */
38
+ y: number;
39
+ }
40
+
41
+ /**
42
+ * A single feedback comment attached to an element.
43
+ */
44
+ export interface Comment {
45
+ /** Unique identifier for this comment */
46
+ id: string;
47
+ /** Which variant this comment belongs to */
48
+ variant: VariantId;
49
+ /** Element identification data */
50
+ element: ElementIdentifier;
51
+ /** Click position within the variant */
52
+ coordinates: Coordinates;
53
+ /** User's feedback text */
54
+ text: string;
55
+ /** Unix timestamp when created */
56
+ timestamp: number;
57
+ }
58
+
59
+ /**
60
+ * Complete feedback payload ready for clipboard/download.
61
+ */
62
+ export interface FeedbackPayload {
63
+ /** Schema version for future compatibility */
64
+ version: '1.0';
65
+ /** Target component or page name */
66
+ target: string;
67
+ /** ISO timestamp of submission */
68
+ timestamp: string;
69
+ /** All comments grouped by creation order */
70
+ comments: Comment[];
71
+ /** User's overall direction/synthesis guidance */
72
+ overall: string;
73
+ }
74
+
75
+ /**
76
+ * FeedbackOverlay component props.
77
+ */
78
+ export interface FeedbackOverlayProps {
79
+ /** Name of the component/page being designed */
80
+ targetName: string;
81
+ /** Optional: variant IDs to display (defaults to A-E) */
82
+ variants?: VariantId[];
83
+ /** Optional: callback when feedback is submitted */
84
+ onSubmit?: (payload: FeedbackPayload) => void;
85
+ }
86
+
87
+ /**
88
+ * Internal state for the feedback overlay.
89
+ */
90
+ export interface FeedbackState {
91
+ /** Whether feedback mode is active */
92
+ isActive: boolean;
93
+ /** Currently selected element (if any) */
94
+ selectedElement: HTMLElement | null;
95
+ /** Position for the comment input panel */
96
+ panelPosition: { x: number; y: number } | null;
97
+ /** All comments created in this session */
98
+ comments: Comment[];
99
+ /** Text in the current comment input */
100
+ currentCommentText: string;
101
+ /** Overall direction text */
102
+ overallDirection: string;
103
+ /** Whether the submit modal is showing */
104
+ showSubmitModal: boolean;
105
+ /** The formatted output (for modal preview) */
106
+ formattedOutput: string;
107
+ }
108
+
109
+ /**
110
+ * Storage key for persisting comments in localStorage.
111
+ */
112
+ export const STORAGE_KEY = 'design-lab-feedback';
113
+
114
+ /**
115
+ * CSS class prefix for feedback overlay elements.
116
+ * Used to identify and exclude overlay elements from selection.
117
+ */
118
+ export const OVERLAY_CLASS_PREFIX = 'dl-feedback';