@prmichaelsen/acp-visualizer 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 (180) hide show
  1. package/README.md +68 -0
  2. package/agent/commands/acp.clarification-address.md +417 -0
  3. package/agent/commands/acp.clarification-capture.md +386 -0
  4. package/agent/commands/acp.clarification-create.md +437 -0
  5. package/agent/commands/acp.clarifications-research.md +326 -0
  6. package/agent/commands/acp.command-create.md +432 -0
  7. package/agent/commands/acp.design-create.md +286 -0
  8. package/agent/commands/acp.design-reference.md +355 -0
  9. package/agent/commands/acp.handoff.md +270 -0
  10. package/agent/commands/acp.index.md +423 -0
  11. package/agent/commands/acp.init.md +546 -0
  12. package/agent/commands/acp.package-create.md +895 -0
  13. package/agent/commands/acp.package-info.md +212 -0
  14. package/agent/commands/acp.package-install.md +539 -0
  15. package/agent/commands/acp.package-list.md +280 -0
  16. package/agent/commands/acp.package-publish.md +541 -0
  17. package/agent/commands/acp.package-remove.md +293 -0
  18. package/agent/commands/acp.package-search.md +307 -0
  19. package/agent/commands/acp.package-update.md +361 -0
  20. package/agent/commands/acp.package-validate.md +540 -0
  21. package/agent/commands/acp.pattern-create.md +386 -0
  22. package/agent/commands/acp.plan.md +587 -0
  23. package/agent/commands/acp.proceed.md +882 -0
  24. package/agent/commands/acp.project-create.md +675 -0
  25. package/agent/commands/acp.project-info.md +312 -0
  26. package/agent/commands/acp.project-list.md +226 -0
  27. package/agent/commands/acp.project-remove.md +379 -0
  28. package/agent/commands/acp.project-set.md +227 -0
  29. package/agent/commands/acp.project-update.md +307 -0
  30. package/agent/commands/acp.projects-restore.md +228 -0
  31. package/agent/commands/acp.projects-sync.md +347 -0
  32. package/agent/commands/acp.report.md +407 -0
  33. package/agent/commands/acp.resume.md +239 -0
  34. package/agent/commands/acp.sessions.md +301 -0
  35. package/agent/commands/acp.status.md +293 -0
  36. package/agent/commands/acp.sync.md +364 -0
  37. package/agent/commands/acp.task-create.md +500 -0
  38. package/agent/commands/acp.update.md +302 -0
  39. package/agent/commands/acp.validate.md +466 -0
  40. package/agent/commands/acp.version-check-for-updates.md +276 -0
  41. package/agent/commands/acp.version-check.md +191 -0
  42. package/agent/commands/acp.version-update.md +289 -0
  43. package/agent/commands/command.template.md +339 -0
  44. package/agent/commands/git.commit.md +526 -0
  45. package/agent/commands/git.init.md +514 -0
  46. package/agent/commands/tanstack-cloudflare.deploy.md +272 -0
  47. package/agent/commands/tanstack-cloudflare.tail.md +275 -0
  48. package/agent/design/.gitkeep +0 -0
  49. package/agent/design/design.template.md +154 -0
  50. package/agent/design/local.dashboard-layout-routing.md +288 -0
  51. package/agent/design/local.data-model-yaml-parsing.md +310 -0
  52. package/agent/design/local.search-filtering.md +331 -0
  53. package/agent/design/local.server-api-auto-refresh.md +235 -0
  54. package/agent/design/local.table-tree-views.md +299 -0
  55. package/agent/design/local.visualizer-requirements.md +349 -0
  56. package/agent/design/requirements.template.md +387 -0
  57. package/agent/index/.gitkeep +0 -0
  58. package/agent/index/acp.core.yaml +137 -0
  59. package/agent/index/local.main.template.yaml +37 -0
  60. package/agent/manifest.template.yaml +13 -0
  61. package/agent/manifest.yaml +302 -0
  62. package/agent/milestones/.gitkeep +0 -0
  63. package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +67 -0
  64. package/agent/milestones/milestone-1-{title}.template.md +206 -0
  65. package/agent/milestones/milestone-2-dashboard-views-interaction.md +79 -0
  66. package/agent/package.template.yaml +86 -0
  67. package/agent/patterns/.gitkeep +0 -0
  68. package/agent/patterns/bootstrap.template.md +1237 -0
  69. package/agent/patterns/pattern.template.md +382 -0
  70. package/agent/patterns/tanstack-cloudflare.acl-permissions.md +332 -0
  71. package/agent/patterns/tanstack-cloudflare.action-bar-item.md +416 -0
  72. package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +401 -0
  73. package/agent/patterns/tanstack-cloudflare.auth-session-management.md +387 -0
  74. package/agent/patterns/tanstack-cloudflare.card-and-list.md +271 -0
  75. package/agent/patterns/tanstack-cloudflare.chat-engine.md +353 -0
  76. package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +346 -0
  77. package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +516 -0
  78. package/agent/patterns/tanstack-cloudflare.email-service.md +431 -0
  79. package/agent/patterns/tanstack-cloudflare.expander.md +98 -0
  80. package/agent/patterns/tanstack-cloudflare.fcm-push.md +115 -0
  81. package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +441 -0
  82. package/agent/patterns/tanstack-cloudflare.firebase-auth.md +348 -0
  83. package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +550 -0
  84. package/agent/patterns/tanstack-cloudflare.firebase-storage.md +369 -0
  85. package/agent/patterns/tanstack-cloudflare.form-controls.md +145 -0
  86. package/agent/patterns/tanstack-cloudflare.global-search-context.md +93 -0
  87. package/agent/patterns/tanstack-cloudflare.image-carousel.md +126 -0
  88. package/agent/patterns/tanstack-cloudflare.library-services.md +553 -0
  89. package/agent/patterns/tanstack-cloudflare.lightbox.md +169 -0
  90. package/agent/patterns/tanstack-cloudflare.markdown-content.md +115 -0
  91. package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +98 -0
  92. package/agent/patterns/tanstack-cloudflare.modal.md +156 -0
  93. package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +461 -0
  94. package/agent/patterns/tanstack-cloudflare.notifications-engine.md +151 -0
  95. package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +90 -0
  96. package/agent/patterns/tanstack-cloudflare.og-metadata.md +296 -0
  97. package/agent/patterns/tanstack-cloudflare.pagination.md +442 -0
  98. package/agent/patterns/tanstack-cloudflare.pill-input.md +220 -0
  99. package/agent/patterns/tanstack-cloudflare.provider-adapter.md +401 -0
  100. package/agent/patterns/tanstack-cloudflare.rate-limiting.md +323 -0
  101. package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +338 -0
  102. package/agent/patterns/tanstack-cloudflare.searchable-settings.md +375 -0
  103. package/agent/patterns/tanstack-cloudflare.slide-over.md +129 -0
  104. package/agent/patterns/tanstack-cloudflare.ssr-preload.md +571 -0
  105. package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +508 -0
  106. package/agent/patterns/tanstack-cloudflare.toast-system.md +142 -0
  107. package/agent/patterns/tanstack-cloudflare.unified-header.md +280 -0
  108. package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +628 -0
  109. package/agent/patterns/tanstack-cloudflare.websocket-manager.md +237 -0
  110. package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +358 -0
  111. package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +336 -0
  112. package/agent/progress.template.yaml +161 -0
  113. package/agent/progress.yaml +145 -0
  114. package/agent/schemas/package.schema.yaml +276 -0
  115. package/agent/scripts/acp.common.sh +1781 -0
  116. package/agent/scripts/acp.install.sh +333 -0
  117. package/agent/scripts/acp.package-create.sh +924 -0
  118. package/agent/scripts/acp.package-info.sh +288 -0
  119. package/agent/scripts/acp.package-install.sh +893 -0
  120. package/agent/scripts/acp.package-list.sh +311 -0
  121. package/agent/scripts/acp.package-publish.sh +420 -0
  122. package/agent/scripts/acp.package-remove.sh +348 -0
  123. package/agent/scripts/acp.package-search.sh +156 -0
  124. package/agent/scripts/acp.package-update.sh +517 -0
  125. package/agent/scripts/acp.package-validate.sh +1018 -0
  126. package/agent/scripts/acp.uninstall.sh +85 -0
  127. package/agent/scripts/acp.version-check-for-updates.sh +98 -0
  128. package/agent/scripts/acp.version-check.sh +47 -0
  129. package/agent/scripts/acp.version-update.sh +176 -0
  130. package/agent/scripts/acp.yaml-parser.sh +985 -0
  131. package/agent/scripts/acp.yaml-validate.sh +205 -0
  132. package/agent/tasks/.gitkeep +0 -0
  133. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +210 -0
  134. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +294 -0
  135. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +193 -0
  136. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +262 -0
  137. package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +156 -0
  138. package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +178 -0
  139. package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +141 -0
  140. package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +153 -0
  141. package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +174 -0
  142. package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +233 -0
  143. package/agent/tasks/task-1-{title}.template.md +244 -0
  144. package/bin/visualize.mjs +84 -0
  145. package/package.json +48 -0
  146. package/src/components/ExtraFieldsBadge.tsx +15 -0
  147. package/src/components/FilterBar.tsx +33 -0
  148. package/src/components/Header.tsx +23 -0
  149. package/src/components/MilestoneTable.tsx +167 -0
  150. package/src/components/MilestoneTree.tsx +84 -0
  151. package/src/components/ProgressBar.tsx +20 -0
  152. package/src/components/SearchInput.tsx +22 -0
  153. package/src/components/Sidebar.tsx +54 -0
  154. package/src/components/StatusBadge.tsx +23 -0
  155. package/src/components/StatusDot.tsx +12 -0
  156. package/src/components/TaskList.tsx +36 -0
  157. package/src/components/ViewToggle.tsx +31 -0
  158. package/src/lib/config.ts +8 -0
  159. package/src/lib/file-watcher.ts +43 -0
  160. package/src/lib/search.ts +48 -0
  161. package/src/lib/types.ts +73 -0
  162. package/src/lib/useAutoRefresh.ts +31 -0
  163. package/src/lib/useCollapse.ts +31 -0
  164. package/src/lib/useFilteredData.ts +55 -0
  165. package/src/lib/yaml-loader-real.spec.ts +47 -0
  166. package/src/lib/yaml-loader.spec.ts +201 -0
  167. package/src/lib/yaml-loader.ts +265 -0
  168. package/src/routeTree.gen.ts +140 -0
  169. package/src/router.tsx +10 -0
  170. package/src/routes/__root.tsx +75 -0
  171. package/src/routes/api/watch.ts +29 -0
  172. package/src/routes/index.tsx +115 -0
  173. package/src/routes/milestones.tsx +50 -0
  174. package/src/routes/search.tsx +84 -0
  175. package/src/routes/tasks.tsx +63 -0
  176. package/src/services/progress-database.service.ts +46 -0
  177. package/src/styles.css +25 -0
  178. package/tsconfig.json +24 -0
  179. package/vite.config.ts +16 -0
  180. package/vitest.config.ts +27 -0
@@ -0,0 +1,169 @@
1
+ # LightboxContainer & ImageLightbox
2
+
3
+ **Category**: Design
4
+ **Applicable To**: Full-screen image galleries, memory detail viewers, crop editors, and any swipeable full-screen overlay
5
+ **Status**: Stable
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ LightboxContainer is a reusable full-screen shell (portal, z-55) with slide animation, keyboard/swipe navigation, and a counter badge. ImageLightbox extends it for image galleries with crop data, lazy preloading, and scaled previews. Other lightboxes (MemoryLightbox, ImageCropLightbox) also build on LightboxContainer.
12
+
13
+ ---
14
+
15
+ ## Implementation
16
+
17
+ ### LightboxContainer (Shell)
18
+
19
+ **File**: `src/components/LightboxContainer.tsx`
20
+
21
+ ```typescript
22
+ interface LightboxContainerProps {
23
+ totalCount: number
24
+ index: number
25
+ onPrev: () => void
26
+ onNext: () => void
27
+ onClose: () => void
28
+ navDisabled?: boolean // Disable keyboard/swipe nav (e.g., during crop edit)
29
+ onEscapeWhileNavDisabled?: () => void // Escape exits sub-mode, not lightbox
30
+ onBackdropClickWhileNavDisabled?: () => void
31
+ children: ReactNode // The slide content
32
+ overlay?: ReactNode // Non-animated toolbar/controls
33
+ animatedClassName?: string // Extra classes on the animated content div
34
+ }
35
+ ```
36
+
37
+ **Features**:
38
+ - Portal to `document.body`, z-55, `bg-black/90 backdrop-blur-sm`
39
+ - Body scroll lock + safe-area-inset-top
40
+ - **Slide animation**: 200ms ease-out, scale(0.92) + translateX(±60px) on exit/enter
41
+ - **Keyboard**: ArrowLeft/Right for prev/next, Escape to close
42
+ - **Touch/swipe**: Horizontal >100px = nav, vertical up >80px from backdrop = dismiss
43
+ - **Navigation UI**: Chevron buttons (hidden on mobile), counter badge at bottom ("3 / 10")
44
+ - Close button (X) top-right with safe-area offset
45
+
46
+ **Gesture Detection**:
47
+
48
+ ```typescript
49
+ // Horizontal swipe: navigate
50
+ if (deltaX > 100 && absX > absY) onPrev()
51
+ if (deltaX < -100 && absX > absY) onNext()
52
+ // Vertical swipe up from backdrop: dismiss
53
+ if (onBackdrop && deltaY < -80 && absY > absX) onClose()
54
+ ```
55
+
56
+ **Usage** (building a custom lightbox):
57
+
58
+ ```typescript
59
+ function MyLightbox({ items, startIndex, onClose }) {
60
+ const [index, setIndex] = useState(startIndex)
61
+ return (
62
+ <LightboxContainer
63
+ totalCount={items.length}
64
+ index={index}
65
+ onPrev={() => setIndex(i => Math.max(0, i - 1))}
66
+ onNext={() => setIndex(i => Math.min(items.length - 1, i + 1))}
67
+ onClose={onClose}
68
+ >
69
+ <MySlideContent item={items[index]} />
70
+ </LightboxContainer>
71
+ )
72
+ }
73
+ ```
74
+
75
+ ---
76
+
77
+ ### ImageLightbox (Gallery Viewer)
78
+
79
+ **File**: `src/components/ImageLightbox.tsx`
80
+
81
+ ```typescript
82
+ interface ImageLightboxProps {
83
+ images: Array<{ src: string; alt?: string; crop?: CropData | null }>
84
+ startIndex: number
85
+ onClose: () => void
86
+ }
87
+ ```
88
+
89
+ **Features**:
90
+ - Built on LightboxContainer
91
+ - Lazy-loads crop data via HEAD request to image proxy (module-level cache)
92
+ - Preloads adjacent images (current ± 1) to eliminate flash
93
+ - ScaledCropPreview: uses CSS `background-position` + `background-size` for efficient cropped display
94
+ - Max constraints: 95vw width, 85vh height
95
+ - Click-stop propagation on images (prevents backdrop close)
96
+
97
+ ---
98
+
99
+ ### ImageCropLightbox (Crop Editor)
100
+
101
+ **File**: `src/components/ImageCropLightbox.tsx`
102
+
103
+ ```typescript
104
+ interface ImageCropLightboxProps {
105
+ images: CropImage[]
106
+ startIndex: number
107
+ onClose: () => void
108
+ onCropChange?: (index: number, crop: CropData) => void
109
+ }
110
+ ```
111
+
112
+ **Features**:
113
+ - Built on LightboxContainer with `navDisabled` during active crop
114
+ - Toggle between view mode and crop mode (Escape exits crop mode, not lightbox)
115
+ - Overlay buttons: crop toggle, reset crop
116
+ - Lazy-loads natural image dimensions for proper crop calculations
117
+
118
+ ---
119
+
120
+ ## Anti-Patterns
121
+
122
+ ### Building Full-Screen Overlays Without LightboxContainer
123
+
124
+ ```typescript
125
+ // Bad: Reimplementing swipe, keyboard, animation, scroll lock
126
+ <div className="fixed inset-0 z-50" onKeyDown={handleKey}>
127
+ {/* Custom implementation */}
128
+ </div>
129
+
130
+ // Good: Use LightboxContainer for the shell
131
+ <LightboxContainer totalCount={n} index={i} onPrev={prev} onNext={next} onClose={close}>
132
+ {/* Just the slide content */}
133
+ </LightboxContainer>
134
+ ```
135
+
136
+ ### Forgetting navDisabled for Sub-Modes
137
+
138
+ ```typescript
139
+ // Bad: User swipes during crop edit, navigates away and loses changes
140
+ <LightboxContainer onPrev={prev} onNext={next}>
141
+ <CropEditor />
142
+ </LightboxContainer>
143
+
144
+ // Good: Disable nav during sub-mode
145
+ <LightboxContainer
146
+ navDisabled={cropActive}
147
+ onEscapeWhileNavDisabled={() => setCropActive(false)}
148
+ onBackdropClickWhileNavDisabled={() => setCropActive(false)}
149
+ >
150
+ <CropEditor />
151
+ </LightboxContainer>
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Checklist
157
+
158
+ - [ ] Use LightboxContainer for any full-screen gallery or detail viewer
159
+ - [ ] Set `navDisabled` when entering a sub-mode (crop, edit, etc.)
160
+ - [ ] Provide `onEscapeWhileNavDisabled` to exit sub-mode on Escape
161
+ - [ ] Preload adjacent slides to eliminate flash on navigation
162
+ - [ ] Apply `e.stopPropagation()` on interactive content to prevent backdrop close
163
+ - [ ] Safe-area-inset-top applied via inline style
164
+
165
+ ---
166
+
167
+ **Status**: Stable
168
+ **Last Updated**: 2026-03-14
169
+ **Contributors**: Community
@@ -0,0 +1,115 @@
1
+ # Markdown Content Rendering
2
+
3
+ **Category**: Design
4
+ **Applicable To**: Rendering AI-generated or user-submitted markdown with XSS protection, @mention badges, code blocks, and font size preferences
5
+ **Status**: Stable
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ `MarkdownContent` wraps ReactMarkdown with rehype-sanitize for XSS protection, custom mention preprocessing (`@agent`, `@uid:userId`), link validation (internal/external routing), syntax-highlighted code blocks, and user font size preferences. Safe for rendering AI-generated content that may contain arbitrary markdown.
12
+
13
+ ---
14
+
15
+ ## Implementation
16
+
17
+ **File**: `src/components/chat/MarkdownContent.tsx`
18
+
19
+ ```typescript
20
+ interface MarkdownContentProps {
21
+ content: string
22
+ className?: string
23
+ currentUserId?: string // For mention badge interactivity
24
+ }
25
+ ```
26
+
27
+ ### Processing Pipeline
28
+
29
+ ```
30
+ Raw content
31
+ → stripTimestampPrefix() // Remove <msg ts="..."/> prefixes
32
+ → linkifyText() // Convert bare URLs to [url](url) markdown
33
+ → preprocessMentions() // @agent → **@agent**, @uid:X → **@uid:X**
34
+ → ReactMarkdown
35
+ + rehype-sanitize // Strip XSS vectors
36
+ + custom components // Links, code, strong (mentions)
37
+ ```
38
+
39
+ ### XSS Sanitization
40
+
41
+ ```typescript
42
+ const sanitizeSchema = {
43
+ ...defaultSchema,
44
+ attributes: {
45
+ code: [...(defaultSchema.attributes?.code || []), 'className'],
46
+ a: ['href', 'title'],
47
+ },
48
+ protocols: { href: ['http', 'https', 'mailto'] },
49
+ tagNames: (defaultSchema.tagNames || []).filter(tag =>
50
+ !['script', 'iframe', 'object', 'embed', 'style'].includes(tag)
51
+ ),
52
+ }
53
+ ```
54
+
55
+ ### Link Routing
56
+
57
+ ```typescript
58
+ // Internal links (/memory/abc): navigate within app
59
+ // External links (https://...): target="_blank" rel="noopener noreferrer"
60
+ // Invalid URLs: render as red [Invalid Link] span
61
+
62
+ function isInternalUrl(url: string): boolean { return url.startsWith('/') }
63
+ function isValidUrl(url: string): boolean {
64
+ if (url.startsWith('/')) return true
65
+ try { return ['http:', 'https:', 'mailto:'].includes(new URL(url).protocol) }
66
+ catch { return false }
67
+ }
68
+ ```
69
+
70
+ ### Code Blocks
71
+
72
+ ```typescript
73
+ // Multiline → CodeBlock component with syntax highlighting + copy button
74
+ // Inline → gray background with font size preference
75
+ const isCodeBlock = code.includes('\n') || startLine !== endLine
76
+ if (isCodeBlock) return <CodeBlock code={code} language={language} />
77
+ return <code className="bg-gray-700 px-1.5 py-0.5 rounded">{children}</code>
78
+ ```
79
+
80
+ ### Mention Badges
81
+
82
+ `@agent` and `@uid:userId` are preprocessed to bold markdown, then the `strong` renderer detects them:
83
+
84
+ ```typescript
85
+ strong({ children }) {
86
+ const text = extractText(children)
87
+ if (text === '@agent') return <span className="bg-blue-500/20 text-blue-400 ...">@agent</span>
88
+ if (text.startsWith('@uid:')) return <MentionBadge userId={text.slice(5)} />
89
+ return <strong>{children}</strong>
90
+ }
91
+ ```
92
+
93
+ ### Font Size Integration
94
+
95
+ Uses `useUIPreferencesLocal()` context — heading sizes and prose classes scale with preference.
96
+
97
+ ---
98
+
99
+ ## Anti-Patterns
100
+
101
+ ### Rendering Unsanitized HTML
102
+
103
+ ```typescript
104
+ // Bad: XSS vulnerability
105
+ <div dangerouslySetInnerHTML={{ __html: aiResponse }} />
106
+
107
+ // Good: ReactMarkdown + rehype-sanitize
108
+ <MarkdownContent content={aiResponse} />
109
+ ```
110
+
111
+ ---
112
+
113
+ **Status**: Stable
114
+ **Last Updated**: 2026-03-14
115
+ **Contributors**: Community
@@ -0,0 +1,98 @@
1
+ # Mention Suggestion System
2
+
3
+ **Category**: Design
4
+ **Applicable To**: @mention autocomplete in chat inputs with instant context + async search tiers
5
+ **Status**: Stable
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ A two-tier mention autocomplete: Tier 1 (instant) uses in-memory conversation participant profiles for sub-millisecond results. Tier 2 (async) searches the global people index via API when the context tier has no matches. Stale call detection prevents race conditions. Suggestions sorted by: agent first → prefix matches → substring matches.
12
+
13
+ ---
14
+
15
+ ## Implementation
16
+
17
+ ### useMentionSuggestions Hook
18
+
19
+ **File**: `src/hooks/useMentionSuggestions.ts`
20
+
21
+ ```typescript
22
+ interface MentionSuggestion {
23
+ id: string // userId or 'agent'
24
+ username: string
25
+ type: 'agent' | 'user'
26
+ avatarUrl?: string
27
+ displayName?: string
28
+ section?: 'context' | 'search'
29
+ }
30
+
31
+ function useMentionSuggestions(participantIds?: string[]) {
32
+ const contextProfilesRef = useRef<Map<string, UserProfile>>(new Map())
33
+ const callIdRef = useRef(0) // Stale call detection
34
+
35
+ // Tier 1: Load participant profiles on mount
36
+ useEffect(() => {
37
+ if (!participantIds?.length) return
38
+ ProfileService.getProfiles(participantIds).then(profiles => {
39
+ contextProfilesRef.current = new Map(Object.entries(profiles))
40
+ })
41
+ }, [participantIds?.join(',')])
42
+
43
+ const getSuggestions = useCallback(async (query: string) => {
44
+ const thisCallId = ++callIdRef.current
45
+
46
+ // Tier 1: Instant context matches
47
+ const contextResults = matchFromContext(query, contextProfilesRef.current)
48
+
49
+ // Tier 2: Async global search (if context insufficient)
50
+ if (contextResults.length < 3 && query.length >= 1) {
51
+ const searchResults = await PeopleService.search(query, 5)
52
+ if (callIdRef.current !== thisCallId) return [] // Stale
53
+ return [...contextResults, ...searchResults]
54
+ }
55
+
56
+ return contextResults
57
+ }, [])
58
+
59
+ return { getSuggestions }
60
+ }
61
+ ```
62
+
63
+ ### MentionAutocomplete Component
64
+
65
+ **File**: `src/components/chat/MentionAutocomplete.tsx`
66
+
67
+ ```typescript
68
+ interface MentionAutocompleteProps {
69
+ inputValue: string
70
+ textareaRef: RefObject<HTMLTextAreaElement | null>
71
+ onSelect: (suggestion, startIndex, endIndex) => void
72
+ getSuggestions?: (query: string) => MentionSuggestion[] | Promise<MentionSuggestion[]>
73
+ maxResults?: number
74
+ reverse?: boolean // Bottom-to-top rendering (closest to input = most relevant)
75
+ }
76
+ ```
77
+
78
+ **Mention Detection**: Scans backwards from cursor for `@` preceded by whitespace or start-of-input.
79
+
80
+ **Keyboard Navigation**: ArrowUp/Down (flipped in reverse mode), Enter/Tab to select, Escape to close.
81
+
82
+ **Text Insertion**: `onSelect(suggestion, @position, endPosition)` — caller replaces the `@query` range with `@username `.
83
+
84
+ ---
85
+
86
+ ## Checklist
87
+
88
+ - [ ] Tier 1 profiles loaded from conversation participants on mount
89
+ - [ ] Tier 2 search triggered when context has < 3 matches
90
+ - [ ] Stale call detection via incrementing callIdRef
91
+ - [ ] Agent suggestion always appears first if query matches
92
+ - [ ] `reverse` mode used when autocomplete renders above input
93
+
94
+ ---
95
+
96
+ **Status**: Stable
97
+ **Last Updated**: 2026-03-14
98
+ **Contributors**: Community
@@ -0,0 +1,156 @@
1
+ # Modal & Confirmation Modal
2
+
3
+ **Category**: Design
4
+ **Applicable To**: All dialog overlays, confirmations, form modals, and persistent consent dialogs
5
+ **Status**: Stable
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ The Modal system provides a portal-rendered overlay (z-55) with backdrop blur, escape/click-outside dismissal, body scroll lock, and safe-area-inset-top support. ConfirmationModal extends it with variant-colored icons and a two-button confirm/cancel footer. Use `persistent: true` to disable all dismissal paths for consent flows.
12
+
13
+ ---
14
+
15
+ ## Implementation
16
+
17
+ ### Modal (Base)
18
+
19
+ **File**: `src/components/modals/Modal.tsx`
20
+
21
+ ```typescript
22
+ interface ModalProps {
23
+ isOpen: boolean
24
+ onClose: () => void
25
+ children: React.ReactNode
26
+ title?: string
27
+ style?: React.CSSProperties
28
+ maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl'
29
+ persistent?: boolean // Disables Escape, backdrop click, and close button
30
+ }
31
+ ```
32
+
33
+ **Behavior**:
34
+ - `createPortal` to `document.body` at z-index 55
35
+ - Backdrop: `bg-black/50 backdrop-blur-sm`, click-outside closes (unless persistent)
36
+ - Escape key closes (unless persistent)
37
+ - Body scroll lock: `document.body.style.overflow = 'hidden'` on mount, restored on unmount
38
+ - Close button (X) top-right, hidden when persistent
39
+ - Title rendered above children if provided
40
+ - `paddingTop: env(safe-area-inset-top)` on the fixed container
41
+ - Backdrop click uses `e.target === e.currentTarget` to avoid closing on content clicks
42
+
43
+ **Usage**:
44
+
45
+ ```typescript
46
+ <Modal isOpen={showModal} onClose={() => setShowModal(false)} title="Edit Item" maxWidth="md">
47
+ <form>{/* form content */}</form>
48
+ </Modal>
49
+ ```
50
+
51
+ ---
52
+
53
+ ### ConfirmationModal
54
+
55
+ **File**: `src/components/modals/ConfirmationModal.tsx`
56
+
57
+ ```typescript
58
+ interface ConfirmationModalProps {
59
+ isOpen: boolean
60
+ onClose: () => void
61
+ onConfirm: () => void
62
+ title: string
63
+ message: string | React.ReactNode
64
+ confirmText?: string // default: "Confirm"
65
+ cancelText?: string // default: "Cancel"
66
+ variant?: 'danger' | 'warning' | 'info'
67
+ isLoading?: boolean
68
+ }
69
+ ```
70
+
71
+ **Behavior**:
72
+ - Built on Modal (sm width)
73
+ - Variant-colored gradient icon circle at top:
74
+ - `danger`: purple-pink gradient
75
+ - `warning`: yellow-orange gradient
76
+ - `info`: blue-cyan gradient
77
+ - Two-button footer: Cancel (gray) | Confirm (variant gradient)
78
+ - Both buttons disabled during `isLoading`
79
+ - Prevents modal dismiss during loading: passes `() => {}` as `onClose` to Modal
80
+
81
+ **Usage**:
82
+
83
+ ```typescript
84
+ <ConfirmationModal
85
+ isOpen={showDelete}
86
+ onClose={() => setShowDelete(false)}
87
+ onConfirm={handleDelete}
88
+ title="Delete Memory"
89
+ message="This action cannot be undone."
90
+ confirmText="Delete"
91
+ variant="danger"
92
+ isLoading={deleting}
93
+ />
94
+ ```
95
+
96
+ ---
97
+
98
+ ### SuccessModal
99
+
100
+ **File**: `src/components/modals/SuccessModal.tsx`
101
+
102
+ ```typescript
103
+ interface SuccessModalProps {
104
+ isOpen: boolean
105
+ onClose: () => void
106
+ title: string
107
+ message: React.ReactNode
108
+ }
109
+ ```
110
+
111
+ Single "Close" button with blue-cyan gradient. Check icon in gradient circle.
112
+
113
+ ---
114
+
115
+ ## Anti-Patterns
116
+
117
+ ### Rendering Modal Content Without Portal
118
+
119
+ ```typescript
120
+ // Bad: Modal renders in component tree, z-index conflicts with parents
121
+ <div className="relative z-10">
122
+ <div className="fixed inset-0 bg-black/50">{content}</div>
123
+ </div>
124
+
125
+ // Good: Use Modal component (portals to document.body)
126
+ <Modal isOpen={open} onClose={close}>{content}</Modal>
127
+ ```
128
+
129
+ ### Forgetting isLoading Guard on Dismiss
130
+
131
+ ```typescript
132
+ // Bad: User can close modal while async confirm is running
133
+ <Modal isOpen={open} onClose={() => setOpen(false)}>
134
+ <button onClick={asyncConfirm}>Confirm</button>
135
+ </Modal>
136
+
137
+ // Good: Disable dismiss during loading
138
+ <ConfirmationModal isLoading={loading} onClose={() => setOpen(false)} ... />
139
+ // ConfirmationModal internally passes () => {} as onClose when loading
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Checklist
145
+
146
+ - [ ] Use `Modal` base for custom dialogs, `ConfirmationModal` for confirm/cancel flows
147
+ - [ ] Set `persistent: true` for consent/TOS dialogs that must not be dismissed
148
+ - [ ] Set `maxWidth` appropriately (sm for confirms, md-lg for forms, xl-2xl for complex content)
149
+ - [ ] Guard dismiss during async operations with `isLoading`
150
+ - [ ] Content uses max-h-[90vh] with overflow-auto for long content
151
+
152
+ ---
153
+
154
+ **Status**: Stable
155
+ **Last Updated**: 2026-03-14
156
+ **Contributors**: Community