@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,416 @@
1
+ # ActionBarItem System
2
+
3
+ **Category**: Design
4
+ **Applicable To**: All action bars, action menus, card action strips, and any surface rendering a set of user actions with popovers/modals
5
+ **Status**: Specification (not yet implemented)
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ The ActionBarItem system is a config-driven abstraction for composable, portable action items. Each action (rate, delete, publish, share, etc.) is a self-contained object returned by a hook, bundling its icon, state, handler, popover content, and modals. A generic `ActionBar` container iterates items and handles layout + popover orchestration. The same items render identically in horizontal bars, vertical menus, compact card strips, and slide-over panels — no rewiring needed.
12
+
13
+ ---
14
+
15
+ ## When to Use This Pattern
16
+
17
+ **Use this pattern when:**
18
+ - Building an action bar with multiple actions that need popovers or modals
19
+ - Adding the same action (delete, publish, rate) to multiple surfaces
20
+ - Rendering actions in different layouts (horizontal icons, vertical menu, compact strip)
21
+ - Composing action sets from per-concern hooks
22
+
23
+ **Don't use this pattern when:**
24
+ - A single standalone button with no popover (just use a button)
25
+ - Actions with no shared logic across surfaces (one-off inline handler)
26
+
27
+ ---
28
+
29
+ ## Core Principles
30
+
31
+ 1. **Hook Per Action**: Each action is a hook (`useDeleteActionBarItem`, `usePublishActionBarItem`) returning an `ActionBarItem`
32
+ 2. **Implicit Content Detection**: `renderContent !== undefined` means the item has a popover — no separate boolean flag
33
+ 3. **One Popover at a Time**: The `ActionBar` container manages `openKey: string | null`, closing any open popover when another opens
34
+ 4. **Hook Owns Ref**: Each hook creates its own `triggerRef` — the container applies it to the rendered trigger element
35
+ 5. **Reusable Renderers**: Shared renderer components (ConfirmRenderer, StarRatingRenderer, PublishMenuRenderer) serve as default `renderContent` implementations, overridable per consumer
36
+
37
+ ---
38
+
39
+ ## Implementation
40
+
41
+ ### ActionBarItem Interface
42
+
43
+ **File**: `src/types/action-bar.ts`
44
+
45
+ ```typescript
46
+ interface ActionBarItem {
47
+ key: string // Unique ID for popover orchestration
48
+ icon: LucideIcon
49
+ label: string
50
+ onTrigger?: () => void // Direct action (no popover)
51
+ renderContent?: (ctx: ActionBarContentContext) => ReactNode // Popover content
52
+ renderModals?: () => ReactNode // Always-mounted portaled modals
53
+ onContentClose?: () => void // Cleanup when popover closes
54
+ triggerRef?: RefObject<HTMLButtonElement | null> // Hook owns the ref
55
+ loading?: boolean
56
+ disabled?: boolean
57
+ hidden?: boolean
58
+ danger?: boolean
59
+ active?: boolean
60
+ iconClassName?: string // e.g. 'fill-indigo-400 text-indigo-400'
61
+ to?: string // Link variant (renders as <Link>)
62
+ linkParams?: Record<string, string>
63
+ suffix?: ReactNode // For MenuItem rendering (e.g. "Coming soon")
64
+ }
65
+
66
+ interface ActionBarContentContext {
67
+ close: () => void
68
+ anchorRef: RefObject<HTMLElement | null>
69
+ }
70
+ ```
71
+
72
+ **Key decisions**:
73
+ - Items with `renderContent` get a Popover; items without get a direct click handler (`onTrigger`)
74
+ - Items with `to` render as `<Link>` instead of `<button>`
75
+ - Extended return types expose state: hooks return `ActionBarItem & { isDeleted: boolean }` etc.
76
+ - `renderModals` is always-mounted (outside popover lifecycle) for modals that need portal rendering
77
+
78
+ ### ActionBar Container
79
+
80
+ **File**: `src/components/action-bar/ActionBar.tsx`
81
+
82
+ ```typescript
83
+ interface ActionBarProps {
84
+ items: ActionBarItem[]
85
+ layout?: 'horizontal' | 'vertical' | 'compact'
86
+ className?: string
87
+ }
88
+ ```
89
+
90
+ **Behavior**:
91
+ 1. Filters out `hidden` items
92
+ 2. Manages `openKey: string | null` — one popover at a time
93
+ 3. Applies `item.triggerRef` to each trigger element via ref callback
94
+ 4. Renders triggers as:
95
+ - **horizontal/compact**: Icon buttons (`p-2 text-gray-400 hover:text-white`)
96
+ - **vertical**: MenuItem components (icon + label + suffix)
97
+ 5. Renders Popover for the active `openKey` item, anchored to its trigger
98
+ 6. Always renders `item.renderModals?.()` for all items (modals live outside popover)
99
+ 7. Calls `item.onContentClose?.()` when popover closes
100
+ 8. Spacing: `gap-1` (horizontal/compact), `space-y-1` (vertical)
101
+
102
+ ### Reusable Renderers
103
+
104
+ ```
105
+ src/components/action-bar/renderers/
106
+ ConfirmRenderer.tsx — Confirm/cancel with variant color
107
+ StarRatingRenderer.tsx — Wraps StarRating component
108
+ PublishMenuRenderer.tsx — Multi-step menu (main → submenu → confirm)
109
+ ```
110
+
111
+ ```typescript
112
+ // ConfirmRenderer
113
+ interface ConfirmRendererProps {
114
+ text: string
115
+ confirmLabel: string
116
+ onConfirm: () => void | Promise<void>
117
+ loading?: boolean
118
+ variant?: 'danger' | 'restore' // Red vs purple button
119
+ close: () => void
120
+ }
121
+
122
+ // StarRatingRenderer
123
+ interface StarRatingRendererProps {
124
+ currentRating: number | null
125
+ onRate: (rating: number | null) => void
126
+ close: () => void
127
+ }
128
+ ```
129
+
130
+ Hooks provide a default `renderContent` using these renderers. Consumers override the whole `renderContent` via hook options if they need different presentation.
131
+
132
+ ### Per-Item Hooks
133
+
134
+ All in `src/hooks/action-bar/`:
135
+
136
+ | Hook | Key | Content | Extended State |
137
+ |---|---|---|---|
138
+ | `useRatingActionBarItem(rating, onRate)` | `rate` | StarRatingRenderer | — |
139
+ | `useDeleteActionBarItem(memoryId, isDeleted, opts?)` | `delete` | ConfirmRenderer | `{ isDeleted }` |
140
+ | `usePublishActionBarItem(memoryId, opts)` | `publish` | PublishMenuRenderer + modals | — |
141
+ | `useShareActionBarItem(entityId, type)` | `share` | None (direct action) | — |
142
+ | `useUnlinkActionBarItem(memoryId, relId, onUnlink?)` | `unlink` | ConfirmRenderer | `{ isUnlinked }` |
143
+ | `useViewActionBarItem(memories, index)` | `view` | None (direct — lightbox) | — |
144
+ | `useCommentActionBarItem(onToggle)` | `comment` | None (direct action) | — |
145
+ | `useChatActionBarItem(memoryId, opts)` | `chat` | None (useNavigate internal) | — |
146
+ | `useAssignActionBarItem(memoryId)` | `assign` | Modal via renderModals | — |
147
+ | `useReportActionBarItem(memoryId, opts)` | `report` | Modal via renderModals | — |
148
+
149
+ Each hook:
150
+ - Creates `const triggerRef = useRef<HTMLButtonElement>(null)`
151
+ - Wraps existing shared hooks (`useMemoryDelete`, `useMemoryPublish`) or services
152
+ - Provides default `renderContent` using reusable renderers
153
+ - Accepts optional `renderContent` override in options for customization
154
+
155
+ ---
156
+
157
+ ## Examples
158
+
159
+ ### Example 1: useDeleteActionBarItem
160
+
161
+ ```typescript
162
+ function useDeleteActionBarItem(
163
+ memoryId: string,
164
+ initialDeleted: boolean,
165
+ opts?: {
166
+ onDeleteChange?: (deleted: boolean) => void
167
+ renderContent?: (ctx: ActionBarContentContext) => ReactNode
168
+ },
169
+ ): ActionBarItem & { isDeleted: boolean } {
170
+ const del = useMemoryDelete(memoryId, initialDeleted, opts?.onDeleteChange)
171
+ const triggerRef = useRef<HTMLButtonElement>(null)
172
+
173
+ const defaultRenderContent = useCallback(({ close }: ActionBarContentContext) => (
174
+ <ConfirmRenderer
175
+ text={del.confirmText}
176
+ confirmLabel={del.actionLabel}
177
+ onConfirm={async () => { await del.handleDeleteRestore(); close() }}
178
+ loading={del.deleteLoading}
179
+ variant={del.isDeleted ? 'restore' : 'danger'}
180
+ close={close}
181
+ />
182
+ ), [del])
183
+
184
+ return {
185
+ key: 'delete',
186
+ icon: del.isDeleted ? RotateCcw : Trash2,
187
+ label: del.actionLabel,
188
+ renderContent: opts?.renderContent ?? defaultRenderContent,
189
+ triggerRef,
190
+ loading: del.deleteLoading,
191
+ danger: !del.isDeleted,
192
+ isDeleted: del.isDeleted,
193
+ }
194
+ }
195
+ ```
196
+
197
+ ### Example 2: Horizontal Action Bar (MemoryDetailActionBar)
198
+
199
+ ```typescript
200
+ function MemoryDetailActionBar(props) {
201
+ const rateItem = useRatingActionBarItem(props.userRating, props.onRate)
202
+ const commentItem = useCommentActionBarItem(props.onCommentToggle)
203
+ const assignItem = useAssignActionBarItem(props.memoryId)
204
+ const shareItem = useShareActionBarItem(props.memoryId, 'memory')
205
+ const publishItem = usePublishActionBarItem(props.memoryId, { ... })
206
+ const deleteItem = useDeleteActionBarItem(props.memoryId, props.isDeleted, { ... })
207
+ const chatItem = useChatActionBarItem(props.memoryId, { ... })
208
+ const reportItem = useReportActionBarItem(props.memoryId, { ... })
209
+ const editItem: ActionBarItem = {
210
+ key: 'edit', icon: Pencil, label: 'Edit', disabled: true,
211
+ suffix: <span className="text-xs text-gray-600">Coming soon</span>,
212
+ }
213
+
214
+ return (
215
+ <ActionBar items={[
216
+ rateItem, commentItem, assignItem, shareItem,
217
+ publishItem, editItem, deleteItem, chatItem, reportItem,
218
+ ]} />
219
+ )
220
+ }
221
+ ```
222
+
223
+ ### Example 3: Compact Card Strip (SortableMemoryCard)
224
+
225
+ ```typescript
226
+ const viewItem = useViewActionBarItem(allItems, index)
227
+ const rateItem = useRatingActionBarItem(userRating, (r) => onRate(memoryId, r))
228
+ const unlinkItem = useUnlinkActionBarItem(memoryId, relationshipId, onUnlink)
229
+ const deleteItem = useDeleteActionBarItem(memoryId, !!item.deleted_at, {
230
+ renderContent: ({ close }) => (
231
+ <ConfirmRenderer
232
+ text="Delete this memory everywhere?"
233
+ confirmLabel={deleteItem.isDeleted ? 'Restore' : 'Delete'}
234
+ onConfirm={async () => { await del.handleDeleteRestore(); close() }}
235
+ variant={deleteItem.isDeleted ? 'restore' : 'danger'}
236
+ close={close}
237
+ />
238
+ ),
239
+ })
240
+
241
+ const faded = deleteItem.isDeleted || unlinkItem.isUnlinked
242
+
243
+ return (
244
+ <div className={faded ? 'opacity-20' : ''}>
245
+ {/* card content */}
246
+ <ActionBar items={[viewItem, rateItem, unlinkItem, deleteItem]} layout="compact" />
247
+ </div>
248
+ )
249
+ ```
250
+
251
+ ### Example 4: Vertical Menu (MemoryActions)
252
+
253
+ ```typescript
254
+ return (
255
+ <div className="p-4">
256
+ <h3>Settings</h3>
257
+ <ActionBar
258
+ items={[publishItem, shareItem, editItem, deleteItem, chatItem, reportItem, rateItem]}
259
+ layout="vertical"
260
+ />
261
+ </div>
262
+ )
263
+ ```
264
+
265
+ ### Example 5: Link Items (RelationshipDetailActionBar)
266
+
267
+ ```typescript
268
+ const reorderItem: ActionBarItem = {
269
+ key: 'reorder',
270
+ icon: LayoutDashboard,
271
+ label: 'Reorder',
272
+ to: '/relationships/$relationshipId/reorder',
273
+ linkParams: { relationshipId },
274
+ }
275
+ const shareItem = useShareActionBarItem(relationshipId, 'relationship')
276
+
277
+ return <ActionBar items={[reorderItem, shareItem]} />
278
+ ```
279
+
280
+ ### Example 6: Inline Static Items
281
+
282
+ For one-off actions that don't need a hook:
283
+
284
+ ```typescript
285
+ const copyItem: ActionBarItem = {
286
+ key: 'copy',
287
+ icon: Copy,
288
+ label: 'Copy link',
289
+ onTrigger: () => {
290
+ navigator.clipboard.writeText(url)
291
+ toast.success({ title: 'Copied!' })
292
+ },
293
+ }
294
+ ```
295
+
296
+ ---
297
+
298
+ ## Anti-Patterns
299
+
300
+ ### Duplicating Action Logic Across Consumers
301
+
302
+ ```typescript
303
+ // Bad: Every consumer reimplements delete state + handler + confirmation
304
+ function MemoryDetailActionBar() {
305
+ const [isDeleted, setIsDeleted] = useState(false)
306
+ const [deleteLoading, setDeleteLoading] = useState(false)
307
+ const handleDelete = async () => { /* 20 lines */ }
308
+ // ... popover state, anchor ref, confirmation UI
309
+ }
310
+
311
+ function MemoryActions() {
312
+ const [isDeleted, setIsDeleted] = useState(false) // Same logic again
313
+ // ...
314
+ }
315
+
316
+ // Good: Hook encapsulates everything, container handles popover
317
+ const deleteItem = useDeleteActionBarItem(memoryId, isDeleted)
318
+ return <ActionBar items={[deleteItem]} />
319
+ ```
320
+
321
+ ### Managing Popover State Per Consumer
322
+
323
+ ```typescript
324
+ // Bad: Every consumer manages openKey + one-at-a-time logic
325
+ const [openPopover, setOpenPopover] = useState<string | null>(null)
326
+ const rateRef = useRef(null)
327
+ const deleteRef = useRef(null)
328
+ // ... 30 lines of popover orchestration per consumer
329
+
330
+ // Good: ActionBar container handles all orchestration
331
+ <ActionBar items={[rateItem, deleteItem, publishItem]} />
332
+ ```
333
+
334
+ ### Per-Prop Customization
335
+
336
+ ```typescript
337
+ // Bad: Adding confirmText, confirmLabel, variant props to the hook
338
+ useDeleteActionBarItem(id, false, { confirmText: 'Custom text', variant: 'warning' })
339
+
340
+ // Good: Override the entire renderContent with a reusable renderer
341
+ useDeleteActionBarItem(id, false, {
342
+ renderContent: ({ close }) => (
343
+ <ConfirmRenderer text="Custom text" confirmLabel="OK" variant="danger"
344
+ onConfirm={handleDelete} close={close} />
345
+ ),
346
+ })
347
+ ```
348
+
349
+ ---
350
+
351
+ ## Key Design Decisions
352
+
353
+ ### Interface
354
+
355
+ | Decision | Choice | Rationale |
356
+ |---|---|---|
357
+ | Content detection | Implicit (`renderContent !== undefined`) | Simpler, less API surface |
358
+ | Content context | `{ close, anchorRef }` | No containerType needed — always Popover |
359
+ | State exposure | Extended return types per hook | Type-safe, no casting |
360
+ | Trigger ref ownership | Hook creates ref, container applies it | Simpler lifecycle |
361
+ | Customization | `renderContent` override + reusable renderers | No per-prop customization; renderers compose |
362
+
363
+ ### Container
364
+
365
+ | Decision | Choice | Rationale |
366
+ |---|---|---|
367
+ | Content rendering | Always Popover, all layouts | One rendering strategy |
368
+ | Sub-menu navigation | Inside `renderContent` only | Nested item lists too complex for container |
369
+ | Popover position | Delegate to Popover auto-flip | Container doesn't need layout-aware positioning |
370
+
371
+ ### Hooks
372
+
373
+ | Decision | Choice | Rationale |
374
+ |---|---|---|
375
+ | Location | `src/hooks/action-bar/` | Grouped by concern |
376
+ | Naming | `use{Action}ActionBarItem` | Explicit, discoverable |
377
+ | Publish modals | Inside hook via `renderModals` | Encapsulates modal lifecycle |
378
+
379
+ ---
380
+
381
+ ## Dependencies
382
+
383
+ - `useMemoryDelete` hook (`src/hooks/useMemoryDelete.ts`)
384
+ - `useMemoryPublish` hook (`src/hooks/useMemoryPublish.ts`)
385
+ - `Popover` component (`src/components/Popover.tsx`)
386
+ - `MenuItem` component (`src/components/MenuItem.tsx`)
387
+ - `StarRating` component (`src/components/memories/StarRating.tsx`)
388
+ - `PublishModal`, `RetractModal`, `ReportModal`, `RelationshipAssignModal` (all exist)
389
+
390
+ ---
391
+
392
+ ## Checklist
393
+
394
+ - [ ] Each action is a hook returning `ActionBarItem` — no inline action logic in consumers
395
+ - [ ] Hook creates its own `triggerRef` — container applies it
396
+ - [ ] `renderContent` uses reusable renderers as building blocks
397
+ - [ ] `renderModals` is always-mounted for portal-based modals
398
+ - [ ] Extended state (isDeleted, isUnlinked) exposed via typed return
399
+ - [ ] ActionBar container manages `openKey` for one-popover-at-a-time
400
+ - [ ] Static/one-off items defined as plain objects (no hook needed)
401
+ - [ ] Items with `to` render as `<Link>`, not `<button>`
402
+
403
+ ---
404
+
405
+ ## Related Patterns
406
+
407
+ - **[Modal](./tanstack-cloudflare.modal.md)**: ConfirmationModal used by ConfirmRenderer
408
+ - **[Toast System](./tanstack-cloudflare.toast-system.md)**: useActionToast used inside action hooks
409
+ - **[Card & List](./tanstack-cloudflare.card-and-list.md)**: Cards consume ActionBar in compact layout
410
+
411
+ ---
412
+
413
+ **Status**: Specification (not yet implemented — planned for M84)
414
+ **Recommendation**: Implement per-item hooks first, then ActionBar container, then migrate consumers one at a time
415
+ **Last Updated**: 2026-03-14
416
+ **Contributors**: Community