@burdenoff/microfe-vibecontrols 2026.601.19 → 2026.601.21

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.
@@ -1 +1 @@
1
- {"version":3,"file":"NotesTabPanel.js","names":[],"sources":["../../../src/components/notes/NotesTabPanel.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useState, useMemo } from 'react';\nimport { Plus, FileText, Grid, List, Pin, RefreshCw, Lock, Globe } from 'lucide-react';\nimport { AnimatePresence } from 'framer-motion';\nimport { useNavigate } from 'react-router-dom';\nimport { useApolloClient } from '@apollo/client/react';\nimport { MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { NoteCard } from './NoteCard';\nimport { NoteList } from './NoteList';\nimport { NoteEditor } from './NoteEditor';\nimport type { Note, NoteVisibility } from './types';\nimport type { NoteEditorSubmitData } from './NoteEditor';\nimport {\n SearchInput,\n FilterDropdown,\n EmptyState,\n LoadingState,\n ErrorState,\n QuotaExhaustedDialog,\n} from '@/components/shared';\nimport {\n useListNotesQuery,\n usePinNoteMutation,\n useUnpinNoteMutation,\n useDeleteNoteMutation,\n useCreateNoteMutation,\n useUpdateNoteMutation,\n useReplaceNoteTagsMutation,\n useAvailableNoteTagsQuery,\n useShareNoteMutation,\n ListNotesDocument,\n} from '@/generated/wspace-operations';\nimport { NoteVisibility as GqlNoteVisibility } from '@/generated/wspace-types';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { isQuotaExhaustedError, markQuotaHandledLocally } from '@/utils/quotaUtils';\n\ntype ViewMode = 'grid' | 'list';\n\ninterface NotesTabPanelProps {\n /** 'vibe' = single vibe context; 'agent' = multi-vibe agent context */\n context: 'vibe' | 'agent';\n /** For vibe context: the single vibeId to scope notes to */\n vibeId?: string;\n /** For agent context: the vibes belonging to the agent */\n agentVibes?: { id: string; name: string }[];\n}\n\n/**\n * Reusable notes panel for embedding as a tab in detail pages.\n * Supports vibe-scoped and agent-scoped contexts.\n */\nexport function NotesTabPanel({ context, vibeId, agentVibes = [] }: NotesTabPanelProps) {\n const { t } = useI18n();\n const navigate = useNavigate();\n const apolloClient = useApolloClient();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n\n const canListNotes = hasPermission(VIBE_PERMISSIONS.NOTE_LIST);\n const canCreateNote = hasPermission(VIBE_PERMISSIONS.NOTE_CREATE);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [vibeFilter, setVibeFilter] = useState('all');\n const [tagFilter, setTagFilter] = useState('all');\n const [visibilityFilter, setVisibilityFilter] = useState<'all' | 'PRIVATE' | 'WORKSPACE'>('all');\n const [showPinnedOnly, setShowPinnedOnly] = useState(false);\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showEditor, setShowEditor] = useState(false);\n const [selectedNote, setSelectedNote] = useState<Note | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n\n // Evict cached notes list and refetch — bypasses stale cache-and-network data\n const evictAndRefetch = (refetch: () => void) => {\n apolloClient.cache.evict({ id: 'ROOT_QUERY', fieldName: 'notes' });\n apolloClient.cache.gc();\n refetch();\n };\n\n const [pinNote] = usePinNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [unpinNote] = useUnpinNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [deleteNote] = useDeleteNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [createNote] = useCreateNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [updateNote] = useUpdateNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [replaceNoteTags] = useReplaceNoteTagsMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [shareNote] = useShareNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n\n const shouldSkipQueries = permissionsLoading || !canListNotes;\n\n const { data: availableTagsData } = useAvailableNoteTagsQuery({ skip: shouldSkipQueries });\n\n // Build query filter based on context\n const queryFilter = useMemo(() => {\n const visibilityVal =\n visibilityFilter !== 'all' ? (visibilityFilter as GqlNoteVisibility) : undefined;\n\n if (context === 'vibe' && vibeId) {\n return {\n vibeId,\n isPinned: showPinnedOnly ? true : undefined,\n visibility: visibilityVal,\n };\n }\n // Agent context: fetch all notes, filter client-side\n return {\n vibeId: vibeFilter === 'none' ? null : vibeFilter !== 'all' ? vibeFilter : undefined,\n isPinned: showPinnedOnly ? true : undefined,\n visibility: visibilityVal,\n };\n }, [context, vibeId, vibeFilter, showPinnedOnly, visibilityFilter]);\n\n const { data, loading, error, refetch } = useListNotesQuery({\n variables: { filter: queryFilter, pagination: { limit: 50 } },\n fetchPolicy: 'cache-and-network',\n skip: shouldSkipQueries,\n });\n\n // Vibe filter options (agent context only)\n const vibeOptions = useMemo(() => {\n if (context !== 'agent') return [];\n return [\n { value: 'all', label: 'All Vibes' },\n { value: 'none', label: 'No Vibe (General)' },\n ...agentVibes.map((v) => ({ value: v.id, label: v.name })),\n ];\n }, [context, agentVibes]);\n\n // Tag label lookup by ID — used when federation doesn't return nested tag object\n type AvailableTag = { id: string; key: string; label: string; color?: string | null };\n const tagLabelMap = useMemo((): Map<string, AvailableTag> => {\n const tags = (availableTagsData?.availableNoteTags ?? []) as AvailableTag[];\n return new Map(tags.map((tag) => [tag.id, tag]));\n }, [availableTagsData?.availableNoteTags]);\n\n // Map GraphQL response to Note type\n const notes: Note[] = useMemo(() => {\n const rawNotes = data?.notes?.items ?? [];\n const mapped = rawNotes.map((note: NonNullable<typeof rawNotes>[number]) => ({\n id: note.id,\n workspaceId: note.workspaceId,\n vibeId: note.vibeId || null,\n title: note.title,\n content: note.content,\n tags: note.tags || [],\n tagAssignments:\n note.tagAssignments?.map((a: NonNullable<typeof note.tagAssignments>[number]) => {\n // `a.tag` may be null when federation doesn't resolve the tag entity.\n // Fall back to the availableNoteTags lookup so the label always shows.\n const resolvedTag = a.tag\n ? { id: a.tag.id, label: a.tag.label, key: a.tag.key, color: a.tag.color }\n : tagLabelMap.has(a.tagId)\n ? {\n id: a.tagId,\n label: tagLabelMap.get(a.tagId)!.label,\n key: tagLabelMap.get(a.tagId)!.key,\n color: tagLabelMap.get(a.tagId)!.color ?? null,\n }\n : undefined;\n return {\n id: a.id,\n tagId: a.tagId,\n tag: resolvedTag,\n entityType: a.entityType,\n entityId: a.entityId,\n source: a.source,\n assignedBy: a.assignedBy,\n assignedAt: a.assignedAt,\n };\n }) || [],\n isPinned: note.isPinned,\n visibility: (note.visibility ?? 'WORKSPACE') as NoteVisibility,\n createdBy: note.createdBy ?? '',\n metadata: (note.metadata as Record<string, unknown>) || {},\n createdAt: note.createdAt,\n updatedAt: note.updatedAt,\n vibe: note.vibe ? { id: note.vibe.id, name: note.vibe.name } : undefined,\n }));\n\n // Agent context: client-side filter to only agent's vibes\n if (context === 'agent' && agentVibes.length > 0) {\n const vibeIdSet = new Set(agentVibes.map((v) => v.id));\n return mapped.filter((n: Note) => n.vibeId && vibeIdSet.has(n.vibeId));\n }\n\n return mapped;\n }, [data?.notes?.items, context, agentVibes, tagLabelMap]);\n\n // Tag filter options\n const tagOptions = useMemo(() => {\n const tags = availableTagsData?.availableNoteTags ?? [];\n return [\n { value: 'all', label: 'All Tags' },\n ...tags.map((tag: AvailableTag) => ({\n value: tag.id,\n label: tag.label,\n })),\n ];\n }, [availableTagsData?.availableNoteTags]);\n\n // Client-side search and tag filtering\n const filteredNotes = useMemo(() => {\n return notes.filter((note) => {\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n note.title.toLowerCase().includes(query) ||\n note.content.toLowerCase().includes(query) ||\n note.tagAssignments?.some((a) => a.tag?.label?.toLowerCase().includes(query));\n if (!matchesSearch) return false;\n }\n if (tagFilter !== 'all') {\n const hasTag = note.tagAssignments?.some((a) => a.tagId === tagFilter);\n if (!hasTag) return false;\n }\n return true;\n });\n }, [notes, searchQuery, tagFilter]);\n\n // Sort: pinned first, then by updatedAt\n const sortedNotes = useMemo(() => {\n return [...filteredNotes].sort((a, b) => {\n if (a.isPinned && !b.isPinned) return -1;\n if (!a.isPinned && b.isPinned) return 1;\n return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();\n });\n }, [filteredNotes]);\n\n const handleNoteSelect = (note: Note) => {\n setSelectedNote(note);\n setShowEditor(true);\n };\n\n const handleCreateNote = () => {\n setSelectedNote(null);\n setShowEditor(true);\n };\n\n const handlePinNote = async (note: Note) => {\n try {\n if (note.isPinned) {\n await unpinNote({ variables: { id: note.id } });\n } else {\n await pinNote({ variables: { id: note.id } });\n }\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to toggle pin for note:', err);\n }\n };\n\n const handleDeleteNote = async (note: Note) => {\n try {\n await deleteNote({ variables: { id: note.id } });\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to delete note:', err);\n }\n };\n\n const handleSaveNote = async (noteData: NoteEditorSubmitData) => {\n const closeEditorForQuota = () => {\n setShowEditor(false);\n setSelectedNote(null);\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n };\n\n try {\n let noteId: string | undefined;\n\n // In vibe context, always force the vibeId\n const effectiveVibeId = context === 'vibe' ? vibeId : noteData.vibeId;\n\n if (noteData.id) {\n let result;\n try {\n result = await updateNote({\n variables: {\n id: noteData.id,\n input: {\n title: noteData.title,\n content: noteData.content,\n visibility: noteData.visibility\n ? (noteData.visibility as GqlNoteVisibility)\n : undefined,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n closeEditorForQuota();\n return;\n }\n\n throw err;\n }\n noteId = result.data?.updateNote?.id;\n } else {\n let result;\n try {\n result = await createNote({\n variables: {\n input: {\n title: noteData.title || '',\n content: noteData.content || '',\n vibeId: effectiveVibeId,\n visibility: noteData.visibility\n ? (noteData.visibility as GqlNoteVisibility)\n : GqlNoteVisibility.Workspace,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n closeEditorForQuota();\n return;\n }\n\n throw err;\n }\n noteId = result.data?.createNote?.id;\n }\n\n // Assign tags\n if (noteId && noteData.selectedTagIds && noteData.selectedTagIds.length > 0) {\n await replaceNoteTags({\n variables: { input: { noteId, tagIds: noteData.selectedTagIds } },\n });\n } else if (noteId && noteData.selectedTagIds?.length === 0 && noteData.id) {\n await replaceNoteTags({\n variables: { input: { noteId, tagIds: [] } },\n });\n }\n\n setShowEditor(false);\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to save note:', err);\n }\n };\n\n const handleShareNote = async (note: Note) => {\n const newVisibility =\n note.visibility === 'PRIVATE' ? GqlNoteVisibility.Workspace : GqlNoteVisibility.Private;\n try {\n await shareNote({ variables: { id: note.id, visibility: newVisibility } });\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to toggle note visibility:', err);\n }\n };\n\n // Stats\n const stats = {\n total: notes.length,\n pinned: notes.filter((n) => n.isPinned).length,\n };\n\n // Vibe options for editor\n const vibeOptionsForEditor = useMemo(() => {\n if (context === 'vibe') return [];\n return agentVibes;\n }, [context, agentVibes]);\n\n // Permission guard\n if (!permissionsLoading && permissions && !canListNotes) {\n return (\n <AccessDenied\n title=\"Access Denied\"\n description=\"You don't have permission to view notes.\"\n details=\"Required permission: note:list\"\n />\n );\n }\n\n if (loading && notes.length === 0) {\n return <LoadingState message=\"Loading notes...\" />;\n }\n\n if (error && notes.length === 0) {\n return (\n <ErrorState title=\"Failed to load notes\" message={error.message} onRetry={() => refetch()} />\n );\n }\n\n const isFiltered =\n searchQuery ||\n (context === 'agent' && vibeFilter !== 'all') ||\n tagFilter !== 'all' ||\n showPinnedOnly;\n\n return (\n <div className=\"space-y-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold text-text-primary\">Notes</h3>\n <p className=\"text-sm text-text-secondary\">\n {stats.total} {stats.total === 1 ? 'note' : 'notes'}, {stats.pinned} pinned\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title=\"Refresh notes\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n <PermissionButton permission={VIBE_PERMISSIONS.NOTE_CREATE}>\n <button\n type=\"button\"\n onClick={handleCreateNote}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n New Note\n </button>\n </PermissionButton>\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder=\"Search notes...\"\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title=\"Grid view\"\n >\n <Grid className=\"size-4 text-text-secondary\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title=\"List view\"\n >\n <List className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n {context === 'agent' && (\n <FilterDropdown\n label=\"Vibe\"\n value={vibeFilter}\n options={vibeOptions}\n onChange={setVibeFilter}\n />\n )}\n <FilterDropdown\n label=\"Tag\"\n value={tagFilter}\n options={tagOptions}\n onChange={setTagFilter}\n />\n {/* Visibility filter */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-0.5\">\n <button\n type=\"button\"\n onClick={() => setVisibilityFilter('all')}\n className={`px-2.5 py-1 text-xs rounded transition-colors ${visibilityFilter === 'all' ? 'bg-bg-surface text-text-primary shadow-sm' : 'text-text-muted hover:text-text-secondary'}`}\n >\n All\n </button>\n <button\n type=\"button\"\n onClick={() => setVisibilityFilter('WORKSPACE')}\n className={`flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors ${visibilityFilter === 'WORKSPACE' ? 'bg-bg-surface text-text-primary shadow-sm' : 'text-text-muted hover:text-text-secondary'}`}\n >\n <Globe className=\"size-3\" />\n Shared\n </button>\n <button\n type=\"button\"\n onClick={() => setVisibilityFilter('PRIVATE')}\n className={`flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors ${visibilityFilter === 'PRIVATE' ? 'bg-bg-surface text-text-primary shadow-sm' : 'text-text-muted hover:text-text-secondary'}`}\n >\n <Lock className=\"size-3\" />\n Private\n </button>\n </div>\n </div>\n </div>\n\n {/* Pinned toggle */}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setShowPinnedOnly(!showPinnedOnly)}\n className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded-full transition-colors ${\n showPinnedOnly\n ? 'bg-status-warning-bg text-status-warning-text'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80'\n }`}\n >\n <Pin className=\"size-3\" />\n Pinned ({stats.pinned})\n </button>\n </div>\n\n {/* Results count */}\n {isFiltered ? (\n <p className=\"text-sm text-text-secondary\">\n Showing {sortedNotes.length} of {notes.length} notes\n {loading && <span className=\"ml-2 text-text-muted\">(refreshing…)</span>}\n </p>\n ) : null}\n\n {/* Notes display */}\n <AnimatePresence mode=\"wait\">\n {sortedNotes.length > 0 ? (\n viewMode === 'grid' ? (\n <MotionList\n key=\"grid\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {sortedNotes.map((note) => (\n <MotionItem key={note.id}>\n <NoteCard\n note={note}\n onOpen={handleNoteSelect}\n onEdit={handleNoteSelect}\n onPin={handlePinNote}\n onUnpin={handlePinNote}\n onDelete={handleDeleteNote}\n onShare={handleShareNote}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <NoteList\n key=\"list\"\n notes={sortedNotes}\n onOpen={handleNoteSelect}\n onEdit={handleNoteSelect}\n onPin={handlePinNote}\n onUnpin={handlePinNote}\n onDelete={handleDeleteNote}\n onShare={handleShareNote}\n />\n )\n ) : (\n <EmptyState\n icon={<FileText className=\"size-8 text-text-secondary\" />}\n title={isFiltered ? 'No notes found' : 'No notes yet'}\n description={\n isFiltered\n ? 'Try adjusting your search or filters'\n : 'Create your first note to start documenting your work'\n }\n action={\n !isFiltered && canCreateNote ? (\n <button\n type=\"button\"\n onClick={handleCreateNote}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n New Note\n </button>\n ) : undefined\n }\n />\n )}\n </AnimatePresence>\n\n {/* Note Editor Modal */}\n {showEditor && (\n <NoteEditor\n note={selectedNote || undefined}\n vibeOptions={vibeOptionsForEditor}\n defaultVibeId={context === 'vibe' ? vibeId : undefined}\n hideVibeSelect={context === 'vibe'}\n onSubmit={handleSaveNote}\n onCancel={() => setShowEditor(false)}\n />\n )}\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title=\"Note Quota Reached\"\n message=\"You've used all available note quota on your current plan. Upgrade your plan to create or update more notes.\"\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"NotesTabPanel.js","names":[],"sources":["../../../src/components/notes/NotesTabPanel.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useState, useMemo } from 'react';\nimport { Plus, FileText, Grid, List, Pin, RefreshCw, Lock, Globe } from 'lucide-react';\nimport { AnimatePresence } from 'framer-motion';\nimport { useNavigate } from 'react-router-dom';\nimport { useApolloClient } from '@apollo/client/react';\nimport { MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { NoteCard } from './NoteCard';\nimport { NoteList } from './NoteList';\nimport { NoteEditor } from './NoteEditor';\nimport type { Note, NoteVisibility } from './types';\nimport type { NoteEditorSubmitData } from './NoteEditor';\nimport {\n SearchInput,\n FilterDropdown,\n EmptyState,\n LoadingState,\n ErrorState,\n QuotaExhaustedDialog,\n} from '@/components/shared';\nimport {\n useListNotesQuery,\n usePinNoteMutation,\n useUnpinNoteMutation,\n useDeleteNoteMutation,\n useCreateNoteMutation,\n useUpdateNoteMutation,\n useReplaceNoteTagsMutation,\n useAvailableNoteTagsQuery,\n useShareNoteMutation,\n ListNotesDocument,\n} from '@/generated/wspace-operations';\nimport { NoteVisibility as GqlNoteVisibility } from '@/generated/wspace-types';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useFeatureFlag } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { isQuotaExhaustedError, markQuotaHandledLocally } from '@/utils/quotaUtils';\nimport { NoteShareDialog, SHARE_REGISTRY } from '@/components/sharing';\n\ntype ViewMode = 'grid' | 'list';\n\ninterface NotesTabPanelProps {\n /** 'vibe' = single vibe context; 'agent' = multi-vibe agent context */\n context: 'vibe' | 'agent';\n /** For vibe context: the single vibeId to scope notes to */\n vibeId?: string;\n /** For agent context: the vibes belonging to the agent */\n agentVibes?: { id: string; name: string }[];\n}\n\n/**\n * Reusable notes panel for embedding as a tab in detail pages.\n * Supports vibe-scoped and agent-scoped contexts.\n */\nexport function NotesTabPanel({ context, vibeId, agentVibes = [] }: NotesTabPanelProps) {\n const { t } = useI18n();\n const navigate = useNavigate();\n const apolloClient = useApolloClient();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n\n const canListNotes = hasPermission(VIBE_PERMISSIONS.NOTE_LIST);\n const canCreateNote = hasPermission(VIBE_PERMISSIONS.NOTE_CREATE);\n\n // Generic note sharing (full users/link/vibe dialog). Gated by the\n // `vibecontrols.sharing.note` flag (default ON) + note share permission.\n // Distinct from the legacy `onShare` visibility toggle, which stays as-is.\n const noteSharingFlagEnabled = useFeatureFlag(\n SHARE_REGISTRY.note.featureFlag,\n SHARE_REGISTRY.note.defaultEnabled\n );\n const canShareNote = hasPermission(SHARE_REGISTRY.note.permission);\n const noteSharingEnabled = noteSharingFlagEnabled && canShareNote;\n const [genericShareNote, setGenericShareNote] = useState<Note | null>(null);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [vibeFilter, setVibeFilter] = useState('all');\n const [tagFilter, setTagFilter] = useState('all');\n const [visibilityFilter, setVisibilityFilter] = useState<'all' | 'PRIVATE' | 'WORKSPACE'>('all');\n const [showPinnedOnly, setShowPinnedOnly] = useState(false);\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showEditor, setShowEditor] = useState(false);\n const [selectedNote, setSelectedNote] = useState<Note | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n\n // Evict cached notes list and refetch — bypasses stale cache-and-network data\n const evictAndRefetch = (refetch: () => void) => {\n apolloClient.cache.evict({ id: 'ROOT_QUERY', fieldName: 'notes' });\n apolloClient.cache.gc();\n refetch();\n };\n\n const [pinNote] = usePinNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [unpinNote] = useUnpinNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [deleteNote] = useDeleteNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [createNote] = useCreateNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [updateNote] = useUpdateNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [replaceNoteTags] = useReplaceNoteTagsMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [shareNote] = useShareNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n\n const shouldSkipQueries = permissionsLoading || !canListNotes;\n\n const { data: availableTagsData } = useAvailableNoteTagsQuery({ skip: shouldSkipQueries });\n\n // Build query filter based on context\n const queryFilter = useMemo(() => {\n const visibilityVal =\n visibilityFilter !== 'all' ? (visibilityFilter as GqlNoteVisibility) : undefined;\n\n if (context === 'vibe' && vibeId) {\n return {\n vibeId,\n isPinned: showPinnedOnly ? true : undefined,\n visibility: visibilityVal,\n };\n }\n // Agent context: fetch all notes, filter client-side\n return {\n vibeId: vibeFilter === 'none' ? null : vibeFilter !== 'all' ? vibeFilter : undefined,\n isPinned: showPinnedOnly ? true : undefined,\n visibility: visibilityVal,\n };\n }, [context, vibeId, vibeFilter, showPinnedOnly, visibilityFilter]);\n\n const { data, loading, error, refetch } = useListNotesQuery({\n variables: { filter: queryFilter, pagination: { limit: 50 } },\n fetchPolicy: 'cache-and-network',\n skip: shouldSkipQueries,\n });\n\n // Vibe filter options (agent context only)\n const vibeOptions = useMemo(() => {\n if (context !== 'agent') return [];\n return [\n { value: 'all', label: 'All Vibes' },\n { value: 'none', label: 'No Vibe (General)' },\n ...agentVibes.map((v) => ({ value: v.id, label: v.name })),\n ];\n }, [context, agentVibes]);\n\n // Tag label lookup by ID — used when federation doesn't return nested tag object\n type AvailableTag = { id: string; key: string; label: string; color?: string | null };\n const tagLabelMap = useMemo((): Map<string, AvailableTag> => {\n const tags = (availableTagsData?.availableNoteTags ?? []) as AvailableTag[];\n return new Map(tags.map((tag) => [tag.id, tag]));\n }, [availableTagsData?.availableNoteTags]);\n\n // Map GraphQL response to Note type\n const notes: Note[] = useMemo(() => {\n const rawNotes = data?.notes?.items ?? [];\n const mapped = rawNotes.map((note: NonNullable<typeof rawNotes>[number]) => ({\n id: note.id,\n workspaceId: note.workspaceId,\n vibeId: note.vibeId || null,\n title: note.title,\n content: note.content,\n tags: note.tags || [],\n tagAssignments:\n note.tagAssignments?.map((a: NonNullable<typeof note.tagAssignments>[number]) => {\n // `a.tag` may be null when federation doesn't resolve the tag entity.\n // Fall back to the availableNoteTags lookup so the label always shows.\n const resolvedTag = a.tag\n ? { id: a.tag.id, label: a.tag.label, key: a.tag.key, color: a.tag.color }\n : tagLabelMap.has(a.tagId)\n ? {\n id: a.tagId,\n label: tagLabelMap.get(a.tagId)!.label,\n key: tagLabelMap.get(a.tagId)!.key,\n color: tagLabelMap.get(a.tagId)!.color ?? null,\n }\n : undefined;\n return {\n id: a.id,\n tagId: a.tagId,\n tag: resolvedTag,\n entityType: a.entityType,\n entityId: a.entityId,\n source: a.source,\n assignedBy: a.assignedBy,\n assignedAt: a.assignedAt,\n };\n }) || [],\n isPinned: note.isPinned,\n visibility: (note.visibility ?? 'WORKSPACE') as NoteVisibility,\n createdBy: note.createdBy ?? '',\n metadata: (note.metadata as Record<string, unknown>) || {},\n createdAt: note.createdAt,\n updatedAt: note.updatedAt,\n vibe: note.vibe ? { id: note.vibe.id, name: note.vibe.name } : undefined,\n }));\n\n // Agent context: client-side filter to only agent's vibes\n if (context === 'agent' && agentVibes.length > 0) {\n const vibeIdSet = new Set(agentVibes.map((v) => v.id));\n return mapped.filter((n: Note) => n.vibeId && vibeIdSet.has(n.vibeId));\n }\n\n return mapped;\n }, [data?.notes?.items, context, agentVibes, tagLabelMap]);\n\n // Tag filter options\n const tagOptions = useMemo(() => {\n const tags = availableTagsData?.availableNoteTags ?? [];\n return [\n { value: 'all', label: 'All Tags' },\n ...tags.map((tag: AvailableTag) => ({\n value: tag.id,\n label: tag.label,\n })),\n ];\n }, [availableTagsData?.availableNoteTags]);\n\n // Client-side search and tag filtering\n const filteredNotes = useMemo(() => {\n return notes.filter((note) => {\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n note.title.toLowerCase().includes(query) ||\n note.content.toLowerCase().includes(query) ||\n note.tagAssignments?.some((a) => a.tag?.label?.toLowerCase().includes(query));\n if (!matchesSearch) return false;\n }\n if (tagFilter !== 'all') {\n const hasTag = note.tagAssignments?.some((a) => a.tagId === tagFilter);\n if (!hasTag) return false;\n }\n return true;\n });\n }, [notes, searchQuery, tagFilter]);\n\n // Sort: pinned first, then by updatedAt\n const sortedNotes = useMemo(() => {\n return [...filteredNotes].sort((a, b) => {\n if (a.isPinned && !b.isPinned) return -1;\n if (!a.isPinned && b.isPinned) return 1;\n return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();\n });\n }, [filteredNotes]);\n\n const handleNoteSelect = (note: Note) => {\n setSelectedNote(note);\n setShowEditor(true);\n };\n\n const handleCreateNote = () => {\n setSelectedNote(null);\n setShowEditor(true);\n };\n\n const handlePinNote = async (note: Note) => {\n try {\n if (note.isPinned) {\n await unpinNote({ variables: { id: note.id } });\n } else {\n await pinNote({ variables: { id: note.id } });\n }\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to toggle pin for note:', err);\n }\n };\n\n const handleDeleteNote = async (note: Note) => {\n try {\n await deleteNote({ variables: { id: note.id } });\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to delete note:', err);\n }\n };\n\n const handleSaveNote = async (noteData: NoteEditorSubmitData) => {\n const closeEditorForQuota = () => {\n setShowEditor(false);\n setSelectedNote(null);\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n };\n\n try {\n let noteId: string | undefined;\n\n // In vibe context, always force the vibeId\n const effectiveVibeId = context === 'vibe' ? vibeId : noteData.vibeId;\n\n if (noteData.id) {\n let result;\n try {\n result = await updateNote({\n variables: {\n id: noteData.id,\n input: {\n title: noteData.title,\n content: noteData.content,\n visibility: noteData.visibility\n ? (noteData.visibility as GqlNoteVisibility)\n : undefined,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n closeEditorForQuota();\n return;\n }\n\n throw err;\n }\n noteId = result.data?.updateNote?.id;\n } else {\n let result;\n try {\n result = await createNote({\n variables: {\n input: {\n title: noteData.title || '',\n content: noteData.content || '',\n vibeId: effectiveVibeId,\n visibility: noteData.visibility\n ? (noteData.visibility as GqlNoteVisibility)\n : GqlNoteVisibility.Workspace,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n closeEditorForQuota();\n return;\n }\n\n throw err;\n }\n noteId = result.data?.createNote?.id;\n }\n\n // Assign tags\n if (noteId && noteData.selectedTagIds && noteData.selectedTagIds.length > 0) {\n await replaceNoteTags({\n variables: { input: { noteId, tagIds: noteData.selectedTagIds } },\n });\n } else if (noteId && noteData.selectedTagIds?.length === 0 && noteData.id) {\n await replaceNoteTags({\n variables: { input: { noteId, tagIds: [] } },\n });\n }\n\n setShowEditor(false);\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to save note:', err);\n }\n };\n\n const handleShareNote = async (note: Note) => {\n const newVisibility =\n note.visibility === 'PRIVATE' ? GqlNoteVisibility.Workspace : GqlNoteVisibility.Private;\n try {\n await shareNote({ variables: { id: note.id, visibility: newVisibility } });\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to toggle note visibility:', err);\n }\n };\n\n // Opens the generic Share dialog for a note (users / link / vibe).\n const handleShareNoteAccess = noteSharingEnabled\n ? (note: Note) => setGenericShareNote(note)\n : undefined;\n\n // Stats\n const stats = {\n total: notes.length,\n pinned: notes.filter((n) => n.isPinned).length,\n };\n\n // Vibe options for editor\n const vibeOptionsForEditor = useMemo(() => {\n if (context === 'vibe') return [];\n return agentVibes;\n }, [context, agentVibes]);\n\n // Permission guard\n if (!permissionsLoading && permissions && !canListNotes) {\n return (\n <AccessDenied\n title=\"Access Denied\"\n description=\"You don't have permission to view notes.\"\n details=\"Required permission: note:list\"\n />\n );\n }\n\n if (loading && notes.length === 0) {\n return <LoadingState message=\"Loading notes...\" />;\n }\n\n if (error && notes.length === 0) {\n return (\n <ErrorState title=\"Failed to load notes\" message={error.message} onRetry={() => refetch()} />\n );\n }\n\n const isFiltered =\n searchQuery ||\n (context === 'agent' && vibeFilter !== 'all') ||\n tagFilter !== 'all' ||\n showPinnedOnly;\n\n return (\n <div className=\"space-y-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold text-text-primary\">Notes</h3>\n <p className=\"text-sm text-text-secondary\">\n {stats.total} {stats.total === 1 ? 'note' : 'notes'}, {stats.pinned} pinned\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title=\"Refresh notes\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n <PermissionButton permission={VIBE_PERMISSIONS.NOTE_CREATE}>\n <button\n type=\"button\"\n onClick={handleCreateNote}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n New Note\n </button>\n </PermissionButton>\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder=\"Search notes...\"\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title=\"Grid view\"\n >\n <Grid className=\"size-4 text-text-secondary\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title=\"List view\"\n >\n <List className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n {context === 'agent' && (\n <FilterDropdown\n label=\"Vibe\"\n value={vibeFilter}\n options={vibeOptions}\n onChange={setVibeFilter}\n />\n )}\n <FilterDropdown\n label=\"Tag\"\n value={tagFilter}\n options={tagOptions}\n onChange={setTagFilter}\n />\n {/* Visibility filter */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-0.5\">\n <button\n type=\"button\"\n onClick={() => setVisibilityFilter('all')}\n className={`px-2.5 py-1 text-xs rounded transition-colors ${visibilityFilter === 'all' ? 'bg-bg-surface text-text-primary shadow-sm' : 'text-text-muted hover:text-text-secondary'}`}\n >\n All\n </button>\n <button\n type=\"button\"\n onClick={() => setVisibilityFilter('WORKSPACE')}\n className={`flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors ${visibilityFilter === 'WORKSPACE' ? 'bg-bg-surface text-text-primary shadow-sm' : 'text-text-muted hover:text-text-secondary'}`}\n >\n <Globe className=\"size-3\" />\n Shared\n </button>\n <button\n type=\"button\"\n onClick={() => setVisibilityFilter('PRIVATE')}\n className={`flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors ${visibilityFilter === 'PRIVATE' ? 'bg-bg-surface text-text-primary shadow-sm' : 'text-text-muted hover:text-text-secondary'}`}\n >\n <Lock className=\"size-3\" />\n Private\n </button>\n </div>\n </div>\n </div>\n\n {/* Pinned toggle */}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setShowPinnedOnly(!showPinnedOnly)}\n className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded-full transition-colors ${\n showPinnedOnly\n ? 'bg-status-warning-bg text-status-warning-text'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80'\n }`}\n >\n <Pin className=\"size-3\" />\n Pinned ({stats.pinned})\n </button>\n </div>\n\n {/* Results count */}\n {isFiltered ? (\n <p className=\"text-sm text-text-secondary\">\n Showing {sortedNotes.length} of {notes.length} notes\n {loading && <span className=\"ml-2 text-text-muted\">(refreshing…)</span>}\n </p>\n ) : null}\n\n {/* Notes display */}\n <AnimatePresence mode=\"wait\">\n {sortedNotes.length > 0 ? (\n viewMode === 'grid' ? (\n <MotionList\n key=\"grid\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {sortedNotes.map((note) => (\n <MotionItem key={note.id}>\n <NoteCard\n note={note}\n onOpen={handleNoteSelect}\n onEdit={handleNoteSelect}\n onPin={handlePinNote}\n onUnpin={handlePinNote}\n onDelete={handleDeleteNote}\n onShare={handleShareNote}\n onShareAccess={handleShareNoteAccess}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <NoteList\n key=\"list\"\n notes={sortedNotes}\n onOpen={handleNoteSelect}\n onEdit={handleNoteSelect}\n onPin={handlePinNote}\n onUnpin={handlePinNote}\n onDelete={handleDeleteNote}\n onShare={handleShareNote}\n onShareAccess={handleShareNoteAccess}\n />\n )\n ) : (\n <EmptyState\n icon={<FileText className=\"size-8 text-text-secondary\" />}\n title={isFiltered ? 'No notes found' : 'No notes yet'}\n description={\n isFiltered\n ? 'Try adjusting your search or filters'\n : 'Create your first note to start documenting your work'\n }\n action={\n !isFiltered && canCreateNote ? (\n <button\n type=\"button\"\n onClick={handleCreateNote}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n New Note\n </button>\n ) : undefined\n }\n />\n )}\n </AnimatePresence>\n\n {/* Note Editor Modal */}\n {showEditor && (\n <NoteEditor\n note={selectedNote || undefined}\n vibeOptions={vibeOptionsForEditor}\n defaultVibeId={context === 'vibe' ? vibeId : undefined}\n hideVibeSelect={context === 'vibe'}\n onSubmit={handleSaveNote}\n onCancel={() => setShowEditor(false)}\n />\n )}\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title=\"Note Quota Reached\"\n message=\"You've used all available note quota on your current plan. Upgrade your plan to create or update more notes.\"\n />\n\n {/* Generic note sharing dialog (users / link / vibe) */}\n {genericShareNote && (\n <NoteShareDialog\n note={{\n id: genericShareNote.id,\n title: genericShareNote.title,\n vibeId: genericShareNote.vibeId,\n }}\n open={Boolean(genericShareNote)}\n onOpenChange={(next) => {\n if (!next) setGenericShareNote(null);\n }}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { useNoteShareController as e } from "./useNoteShareController.js";
2
+ import { getStoredWorkspaceId as t } from "@burdenoff/fe-libs/shared/graphql";
3
+ import { jsx as n } from "react/jsx-runtime";
4
+ import { ShareDialog as r } from "@burdenoff/fe-libs/sharing";
5
+ //#region src/components/sharing/NoteShareDialog.tsx
6
+ function i({ note: i, open: a, onOpenChange: o }) {
7
+ let s = e(i);
8
+ return /* @__PURE__ */ n(r, {
9
+ resource: {
10
+ type: "note",
11
+ id: i.id,
12
+ name: i.title,
13
+ vibeId: i.vibeId ?? void 0
14
+ },
15
+ controller: s,
16
+ open: a,
17
+ onOpenChange: o,
18
+ workspaceId: t() ?? void 0
19
+ });
20
+ }
21
+ //#endregion
22
+ export { i as NoteShareDialog };
23
+
24
+ //# sourceMappingURL=NoteShareDialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NoteShareDialog.js","names":[],"sources":["../../../src/components/sharing/NoteShareDialog.tsx"],"sourcesContent":["/**\n * `NoteShareDialog` — generic-sharing dialog for a VibecontrolsNote.\n *\n * Distinct from the legacy `onShare` handler on `NoteCard`, which only toggles a\n * note's PRIVATE/WORKSPACE visibility. This dialog drives the full\n * user/link/vibe sharing flow via {@link useNoteShareController}. It is rendered\n * once at the panel level with the currently-selected note; pass `note={null}`\n * to keep it closed.\n *\n * Gating (RBAC permission + the `vibecontrols.sharing.note` flag, default ON) is\n * applied by the caller via {@link SHARE_REGISTRY}; this component assumes it is\n * only mounted when sharing is permitted.\n */\n\nimport { ShareDialog } from '@burdenoff/fe-libs/sharing';\nimport { getStoredWorkspaceId } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useNoteShareController } from './useNoteShareController';\n\nexport interface NoteShareDialogProps {\n note: { id: string; title: string; vibeId?: string | null };\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\nexport function NoteShareDialog({ note, open, onOpenChange }: NoteShareDialogProps) {\n const controller = useNoteShareController(note);\n\n return (\n <ShareDialog\n resource={{\n type: 'note',\n id: note.id,\n name: note.title,\n vibeId: note.vibeId ?? undefined,\n }}\n controller={controller}\n open={open}\n onOpenChange={onOpenChange}\n workspaceId={getStoredWorkspaceId() ?? undefined}\n />\n );\n}\n"],"mappings":";;;;;AAyBA,SAAgB,EAAgB,EAAE,SAAM,SAAM,mBAAsC;CAClF,IAAM,IAAa,EAAuB,EAAK;AAE/C,QACE,kBAAC,GAAD;EACE,UAAU;GACR,MAAM;GACN,IAAI,EAAK;GACT,MAAM,EAAK;GACX,QAAQ,EAAK,UAAU,KAAA;GACxB;EACW;EACN;EACQ;EACd,aAAa,GAAsB,IAAI,KAAA;EACvC,CAAA"}
@@ -0,0 +1,24 @@
1
+ import "react";
2
+ import "lucide-react";
3
+ import { gql as e } from "@apollo/client";
4
+ import "@apollo/client/react";
5
+ import "react/jsx-runtime";
6
+ import "@burdenoff/fe-libs/sharing";
7
+ e`
8
+ query SharesSharedWithMe($resourceType: String) {
9
+ sharesSharedWithMe(resourceType: $resourceType) {
10
+ total
11
+ items {
12
+ id
13
+ resourceType
14
+ resourceId
15
+ role
16
+ status
17
+ createdAt
18
+ }
19
+ }
20
+ }
21
+ `;
22
+ //#endregion
23
+
24
+ //# sourceMappingURL=SharedWithMePanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SharedWithMePanel.js","names":[],"sources":["../../../src/components/sharing/SharedWithMePanel.tsx"],"sourcesContent":["/**\n * `SharedWithMePanel` — lightweight read-only surface listing resources that\n * have been shared with the current actor, backed by the `sharesSharedWithMe`\n * query (`@rbac(scopeType: \"self\")`). Optional / low-effort companion to the\n * outbound Share dialog; renders nothing when the actor has no inbound shares.\n *\n * Uses an inline `gql` document (the share ops are not in the codegen snapshot).\n */\n\nimport { useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Share2 } from 'lucide-react';\nimport { roleLabel, type ShareRole } from '@burdenoff/fe-libs/sharing';\n\ninterface SharedWithMeGrant {\n id: string;\n resourceType: string;\n resourceId: string;\n role: ShareRole;\n status: string;\n createdAt: string;\n}\n\ninterface SharedWithMeData {\n sharesSharedWithMe: {\n items: SharedWithMeGrant[];\n total: number;\n };\n}\n\nconst SHARED_WITH_ME = gql`\n query SharesSharedWithMe($resourceType: String) {\n sharesSharedWithMe(resourceType: $resourceType) {\n total\n items {\n id\n resourceType\n resourceId\n role\n status\n createdAt\n }\n }\n }\n`;\n\nexport interface SharedWithMePanelProps {\n /** Optional resource-type filter (e.g. \"vibe\", \"note\"). */\n resourceType?: string;\n /** Localized section heading. */\n heading?: string;\n}\n\nexport function SharedWithMePanel({ resourceType, heading }: SharedWithMePanelProps) {\n const { data } = useQuery<SharedWithMeData, { resourceType?: string }>(SHARED_WITH_ME, {\n variables: { resourceType },\n fetchPolicy: 'cache-and-network',\n errorPolicy: 'ignore',\n });\n\n const items = useMemo(() => data?.sharesSharedWithMe.items ?? [], [data]);\n\n if (items.length === 0) return null;\n\n return (\n <section\n data-testid=\"shared-with-me-panel\"\n className=\"rounded-lg border border-border-default bg-bg-surface p-4 space-y-3\"\n >\n <header className=\"flex items-center gap-2\">\n <Share2 className=\"size-4 text-text-secondary\" />\n <h3 className=\"text-sm font-semibold text-text-primary\">{heading ?? 'Shared with me'}</h3>\n </header>\n <ul className=\"space-y-2\">\n {items.map((grant) => (\n <li\n key={grant.id}\n className=\"flex items-center justify-between gap-3 rounded-md border border-border-subtle bg-bg-sunken px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {grant.resourceType} · {grant.resourceId.slice(0, 8)}\n </p>\n <p className=\"text-xs text-text-secondary\">{roleLabel(grant.role)}</p>\n </div>\n <span className=\"text-xs text-text-tertiary\">{grant.status}</span>\n </li>\n ))}\n </ul>\n </section>\n );\n}\n"],"mappings":";;;;;;AA+BuB,CAAG"}
@@ -0,0 +1,42 @@
1
+ import { SHARE_REGISTRY as e } from "./shareRegistry.js";
2
+ import { useVibeShareController as t } from "./useVibeShareController.js";
3
+ import { useState as n } from "react";
4
+ import { Share2 as r } from "lucide-react";
5
+ import { getStoredWorkspaceId as i } from "@burdenoff/fe-libs/shared/graphql";
6
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
7
+ import { useFeatureFlag as s } from "@burdenoff/fe-libs/shared/feature-flags";
8
+ import { PermissionGate as c } from "@burdenoff/fe-libs/shared/components";
9
+ import { ShareDialog as l } from "@burdenoff/fe-libs/sharing";
10
+ //#region src/components/sharing/VibeShareButton.tsx
11
+ function u({ vibe: u, label: d, showLabel: f = !0 }) {
12
+ let [p, m] = n(!1), h = t(u), g = e.vibe;
13
+ return s(g.featureFlag, g.defaultEnabled) ? /* @__PURE__ */ o(c, {
14
+ permission: g.permission,
15
+ children: [/* @__PURE__ */ o("button", {
16
+ type: "button",
17
+ "data-testid": "vibe-share-button",
18
+ onClick: () => m(!0),
19
+ title: d,
20
+ className: "flex items-center gap-1.5 px-2.5 py-1.5 sm:px-3 sm:py-2 text-xs sm:text-sm bg-bg-sunken text-text-primary rounded-md hover:bg-bg-sunken/80 transition-colors",
21
+ children: [/* @__PURE__ */ a(r, { className: "size-3.5 sm:size-4" }), f ? /* @__PURE__ */ a("span", {
22
+ className: "hidden sm:inline",
23
+ children: d
24
+ }) : null]
25
+ }), /* @__PURE__ */ a(l, {
26
+ resource: {
27
+ type: "vibe",
28
+ id: u.id,
29
+ name: u.name,
30
+ vibeId: u.id
31
+ },
32
+ controller: h,
33
+ open: p,
34
+ onOpenChange: m,
35
+ workspaceId: i() ?? void 0
36
+ })]
37
+ }) : null;
38
+ }
39
+ //#endregion
40
+ export { u as VibeShareButton };
41
+
42
+ //# sourceMappingURL=VibeShareButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VibeShareButton.js","names":[],"sources":["../../../src/components/sharing/VibeShareButton.tsx"],"sourcesContent":["/**\n * `VibeShareButton` — the Share affordance for a Vibe detail page.\n *\n * Self-contained: it owns the open/close state, builds the share controller via\n * {@link useVibeShareController}, and renders the generic fe-libs\n * `<ShareDialog>`. Visibility is gated by the `vibe` entry in\n * {@link SHARE_REGISTRY} (RBAC permission + the `vibecontrols.sharing.vibe`\n * feature flag, which defaults ON). Hooks run unconditionally; the component\n * returns `null` only after the flag check so React hook ordering stays stable.\n */\n\nimport { useState } from 'react';\nimport { Share2 } from 'lucide-react';\nimport { ShareDialog } from '@burdenoff/fe-libs/sharing';\nimport { PermissionGate } from '@burdenoff/fe-libs/shared/components';\nimport { useFeatureFlag } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { getStoredWorkspaceId } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { SHARE_REGISTRY } from './shareRegistry';\nimport { useVibeShareController } from './useVibeShareController';\n\nexport interface VibeShareButtonProps {\n vibe: { id: string; name: string };\n /** Localized button label + tooltip. */\n label: string;\n /** Show the text label alongside the icon (defaults to icon-only on small screens). */\n showLabel?: boolean;\n}\n\nexport function VibeShareButton({ vibe, label, showLabel = true }: VibeShareButtonProps) {\n const [open, setOpen] = useState(false);\n const controller = useVibeShareController(vibe);\n const entry = SHARE_REGISTRY.vibe;\n const enabled = useFeatureFlag(entry.featureFlag, entry.defaultEnabled);\n\n if (!enabled) return null;\n\n return (\n <PermissionGate permission={entry.permission}>\n <button\n type=\"button\"\n data-testid=\"vibe-share-button\"\n onClick={() => setOpen(true)}\n title={label}\n className=\"flex items-center gap-1.5 px-2.5 py-1.5 sm:px-3 sm:py-2 text-xs sm:text-sm bg-bg-sunken text-text-primary rounded-md hover:bg-bg-sunken/80 transition-colors\"\n >\n <Share2 className=\"size-3.5 sm:size-4\" />\n {showLabel ? <span className=\"hidden sm:inline\">{label}</span> : null}\n </button>\n <ShareDialog\n resource={{ type: 'vibe', id: vibe.id, name: vibe.name, vibeId: vibe.id }}\n controller={controller}\n open={open}\n onOpenChange={setOpen}\n workspaceId={getStoredWorkspaceId() ?? undefined}\n />\n </PermissionGate>\n );\n}\n"],"mappings":";;;;;;;;;;AA6BA,SAAgB,EAAgB,EAAE,SAAM,UAAO,eAAY,MAA8B;CACvF,IAAM,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,IAAa,EAAuB,EAAK,EACzC,IAAQ,EAAe;AAK7B,QAJgB,EAAe,EAAM,aAAa,EAAM,eAAe,GAKrE,kBAAC,GAAD;EAAgB,YAAY,EAAM;YAAlC,CACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAY;GACZ,eAAe,EAAQ,GAAK;GAC5B,OAAO;GACP,WAAU;aALZ,CAOE,kBAAC,GAAD,EAAQ,WAAU,sBAAuB,CAAA,EACxC,IAAY,kBAAC,QAAD;IAAM,WAAU;cAAoB;IAAa,CAAA,GAAG,KAC1D;MACT,kBAAC,GAAD;GACE,UAAU;IAAE,MAAM;IAAQ,IAAI,EAAK;IAAI,MAAM,EAAK;IAAM,QAAQ,EAAK;IAAI;GAC7D;GACN;GACN,cAAc;GACd,aAAa,GAAsB,IAAI,KAAA;GACvC,CAAA,CACa;MArBE"}
@@ -0,0 +1,6 @@
1
+ import "./shareRegistry.js";
2
+ import "./useVibeShareController.js";
3
+ import "./useNoteShareController.js";
4
+ import "./VibeShareButton.js";
5
+ import "./NoteShareDialog.js";
6
+ import "./SharedWithMePanel.js";
@@ -0,0 +1,36 @@
1
+ //#region src/components/sharing/shareControllerTypes.ts
2
+ var e = "\n id\n granteeEmail\n granteeUserId\n role\n status\n visibility\n shareUrl\n expiresAt\n createdAt\n";
3
+ function t(e) {
4
+ return {
5
+ id: e.id,
6
+ granteeEmail: e.granteeEmail,
7
+ granteeUserId: e.granteeUserId,
8
+ role: e.role,
9
+ status: e.status,
10
+ visibility: e.visibility,
11
+ shareUrl: e.shareUrl,
12
+ expiresAt: e.expiresAt,
13
+ createdAt: e.createdAt
14
+ };
15
+ }
16
+ function n(e) {
17
+ let n = [], r = [];
18
+ for (let i of e) {
19
+ let e = t(i);
20
+ e.visibility === "PUBLIC_LINK" ? r.push(e) : n.push(e);
21
+ }
22
+ return {
23
+ grants: n,
24
+ links: r
25
+ };
26
+ }
27
+ var r = [
28
+ "VIEWER",
29
+ "COMMENTER",
30
+ "EDITOR",
31
+ "MANAGER"
32
+ ];
33
+ //#endregion
34
+ export { e as SHARE_GRANT_FIELDS, r as VIBE_SHARE_ROLES, n as partitionGrants };
35
+
36
+ //# sourceMappingURL=shareControllerTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shareControllerTypes.js","names":[],"sources":["../../../src/components/sharing/shareControllerTypes.ts"],"sourcesContent":["/**\n * Shared GraphQL fragment shapes + mapping helpers for the generic-sharing\n * controllers (`useVibeShareController`, `useNoteShareController`).\n *\n * The generic-sharing operations (`shareVibe`, `Vibe.shares`, `myRole`, …) are\n * NOT in the committed `graphql/wspace.graphql` snapshot the codegen reads, so\n * we hand-write the documents with inline `gql` and project the responses onto\n * the fe-libs `ShareGrantView` contract here. The backend `ShareGrant` type and\n * its enums (`ShareGrantStatus`, `ShareGrantVisibility`) line up 1:1 with the\n * fe-libs `ShareStatus` / `ShareVisibility` string unions, so the projection is\n * a structural copy with no value remapping.\n */\n\nimport type { ShareGrantView, ShareRole } from '@burdenoff/fe-libs/sharing';\n\n/** Raw `ShareGrant` selection returned by the inline documents below. */\nexport interface ShareGrantNode {\n id: string;\n granteeEmail: string | null;\n granteeUserId: string | null;\n role: ShareRole;\n status: ShareGrantView['status'];\n visibility: ShareGrantView['visibility'];\n shareUrl: string | null;\n expiresAt: string | null;\n createdAt: string;\n}\n\n/** The `__typename`-less GraphQL selection string shared across documents. */\nexport const SHARE_GRANT_FIELDS = `\n id\n granteeEmail\n granteeUserId\n role\n status\n visibility\n shareUrl\n expiresAt\n createdAt\n`;\n\n/** Map a backend `ShareGrant` node onto the fe-libs `ShareGrantView`. */\nexport function toShareGrantView(node: ShareGrantNode): ShareGrantView {\n return {\n id: node.id,\n granteeEmail: node.granteeEmail,\n granteeUserId: node.granteeUserId,\n role: node.role,\n status: node.status,\n visibility: node.visibility,\n shareUrl: node.shareUrl,\n expiresAt: node.expiresAt,\n createdAt: node.createdAt,\n };\n}\n\n/**\n * Split a resource's grants into user grants vs public links by visibility.\n * `PUBLIC_LINK` grants surface in the \"Public links\" section of the dialog;\n * everything else is a user grant.\n */\nexport function partitionGrants(nodes: ShareGrantNode[]): {\n grants: ShareGrantView[];\n links: ShareGrantView[];\n} {\n const grants: ShareGrantView[] = [];\n const links: ShareGrantView[] = [];\n for (const node of nodes) {\n const view = toShareGrantView(node);\n if (view.visibility === 'PUBLIC_LINK') {\n links.push(view);\n } else {\n grants.push(view);\n }\n }\n return { grants, links };\n}\n\n/** Canonical role ladder exposed by every VibeControls share controller. */\nexport const VIBE_SHARE_ROLES: ShareRole[] = ['VIEWER', 'COMMENTER', 'EDITOR', 'MANAGER'];\n"],"mappings":";AA6BA,IAAa,IAAqB;AAalC,SAAgB,EAAiB,GAAsC;AACrE,QAAO;EACL,IAAI,EAAK;EACT,cAAc,EAAK;EACnB,eAAe,EAAK;EACpB,MAAM,EAAK;EACX,QAAQ,EAAK;EACb,YAAY,EAAK;EACjB,UAAU,EAAK;EACf,WAAW,EAAK;EAChB,WAAW,EAAK;EACjB;;AAQH,SAAgB,EAAgB,GAG9B;CACA,IAAM,IAA2B,EAAE,EAC7B,IAA0B,EAAE;AAClC,MAAK,IAAM,KAAQ,GAAO;EACxB,IAAM,IAAO,EAAiB,EAAK;AACnC,EAAI,EAAK,eAAe,gBACtB,EAAM,KAAK,EAAK,GAEhB,EAAO,KAAK,EAAK;;AAGrB,QAAO;EAAE;EAAQ;EAAO;;AAI1B,IAAa,IAAgC;CAAC;CAAU;CAAa;CAAU;CAAU"}
@@ -0,0 +1,58 @@
1
+ import { VIBE_PERMISSIONS as e } from "../../constants/permissions.js";
2
+ //#region src/components/sharing/shareRegistry.ts
3
+ var t = (e) => `vibecontrols.sharing.${e}`, n = {
4
+ vibe: {
5
+ permission: e.VIBE_MANAGE,
6
+ featureFlag: t("vibe"),
7
+ defaultEnabled: !0
8
+ },
9
+ note: {
10
+ permission: e.NOTE_UPDATE,
11
+ featureFlag: t("note"),
12
+ defaultEnabled: !0
13
+ },
14
+ session: {
15
+ permission: e.SESSION_UPDATE,
16
+ featureFlag: t("session"),
17
+ defaultEnabled: !1
18
+ },
19
+ vibedeck: {
20
+ permission: e.VIBEDECK_SHARE,
21
+ featureFlag: t("vibedeck"),
22
+ defaultEnabled: !1
23
+ },
24
+ catalog: {
25
+ permission: e.VIBE_MANAGE,
26
+ featureFlag: t("catalog"),
27
+ defaultEnabled: !1
28
+ },
29
+ component: {
30
+ permission: e.VIBE_MANAGE,
31
+ featureFlag: t("component"),
32
+ defaultEnabled: !1
33
+ },
34
+ template: {
35
+ permission: e.VIBE_MANAGE,
36
+ featureFlag: t("template"),
37
+ defaultEnabled: !1
38
+ },
39
+ scorecard: {
40
+ permission: e.VIBE_MANAGE,
41
+ featureFlag: t("scorecard"),
42
+ defaultEnabled: !1
43
+ },
44
+ target: {
45
+ permission: e.TARGET_UPDATE,
46
+ featureFlag: t("target"),
47
+ defaultEnabled: !1
48
+ },
49
+ docs: {
50
+ permission: e.DOCS_PAGE_UPDATE,
51
+ featureFlag: t("docs"),
52
+ defaultEnabled: !1
53
+ }
54
+ };
55
+ //#endregion
56
+ export { n as SHARE_REGISTRY };
57
+
58
+ //# sourceMappingURL=shareRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shareRegistry.js","names":[],"sources":["../../../src/components/sharing/shareRegistry.ts"],"sourcesContent":["/**\n * Central registry mapping a VibeControls entity type to the RBAC permission\n * that gates its Share affordance plus the platform feature flag that toggles\n * the generic-sharing UI for that entity.\n *\n * Why a registry: the generic sharing dialog (`@burdenoff/fe-libs/sharing`) is\n * entity-agnostic. Each surface that wants a Share button looks its entity up\n * here so the permission string + feature flag stay in one place rather than\n * being re-typed at every call site.\n *\n * Rollout posture:\n * - `vibe` / `note` ship ON (`defaultEnabled: true`) — the backend share\n * mutations + the FE controllers in this folder are complete for them.\n * - `session` / `vibedeck` keep `defaultEnabled: false`: they already have\n * bespoke share dialogs (`SessionShareDialog`, `VibeDeckShareDialog`) that we\n * must NOT break, so the generic surface stays dark until they migrate.\n * - The long-tail entries (catalog/component/template/scorecard/target/docs)\n * are declared for completeness but are wired to default-OFF flags and have\n * no Share button mounted yet. They exist so a future PR only has to flip the\n * flag + drop in a controller, not invent the registry shape.\n */\n\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\n\n/** A single entity's sharing wiring. */\nexport interface ShareRegistryEntry {\n /** RBAC permission required to open the Share dialog for this entity. */\n permission: string;\n /** Platform feature flag key that toggles the generic-sharing surface. */\n featureFlag: string;\n /**\n * Default value for {@link useFeatureFlag} when the flag is absent.\n * `true` = generic sharing live for this entity; `false` = hidden until the\n * flag is explicitly enabled (long-tail + legacy-dialog entities).\n */\n defaultEnabled: boolean;\n}\n\n/** Every entity type the registry knows about. */\nexport type ShareEntityType =\n | 'vibe'\n | 'note'\n | 'session'\n | 'vibedeck'\n | 'catalog'\n | 'component'\n | 'template'\n | 'scorecard'\n | 'target'\n | 'docs';\n\nconst flagKey = (entity: ShareEntityType): string => `vibecontrols.sharing.${entity}`;\n\nexport const SHARE_REGISTRY: Record<ShareEntityType, ShareRegistryEntry> = {\n // ── finishable: generic sharing ON ───────────────────────────────────────\n vibe: {\n permission: VIBE_PERMISSIONS.VIBE_MANAGE,\n featureFlag: flagKey('vibe'),\n defaultEnabled: true,\n },\n note: {\n permission: VIBE_PERMISSIONS.NOTE_UPDATE,\n featureFlag: flagKey('note'),\n defaultEnabled: true,\n },\n\n // ── legacy bespoke dialogs: generic surface default-OFF ───────────────────\n session: {\n permission: VIBE_PERMISSIONS.SESSION_UPDATE,\n featureFlag: flagKey('session'),\n defaultEnabled: false,\n },\n vibedeck: {\n permission: VIBE_PERMISSIONS.VIBEDECK_SHARE,\n featureFlag: flagKey('vibedeck'),\n defaultEnabled: false,\n },\n\n // ── long-tail: declared, not wired, default-OFF ───────────────────────────\n catalog: {\n permission: VIBE_PERMISSIONS.VIBE_MANAGE,\n featureFlag: flagKey('catalog'),\n defaultEnabled: false,\n },\n component: {\n permission: VIBE_PERMISSIONS.VIBE_MANAGE,\n featureFlag: flagKey('component'),\n defaultEnabled: false,\n },\n template: {\n permission: VIBE_PERMISSIONS.VIBE_MANAGE,\n featureFlag: flagKey('template'),\n defaultEnabled: false,\n },\n scorecard: {\n permission: VIBE_PERMISSIONS.VIBE_MANAGE,\n featureFlag: flagKey('scorecard'),\n defaultEnabled: false,\n },\n target: {\n permission: VIBE_PERMISSIONS.TARGET_UPDATE,\n featureFlag: flagKey('target'),\n defaultEnabled: false,\n },\n docs: {\n permission: VIBE_PERMISSIONS.DOCS_PAGE_UPDATE,\n featureFlag: flagKey('docs'),\n defaultEnabled: false,\n },\n};\n\n/** Lookup helper; returns `undefined` for unknown entity types. */\nexport function getShareRegistryEntry(entity: ShareEntityType): ShareRegistryEntry | undefined {\n return SHARE_REGISTRY[entity];\n}\n"],"mappings":";;AAmDA,IAAM,KAAW,MAAoC,wBAAwB,KAEhE,IAA8D;CAEzE,MAAM;EACJ,YAAY,EAAiB;EAC7B,aAAa,EAAQ,OAAO;EAC5B,gBAAgB;EACjB;CACD,MAAM;EACJ,YAAY,EAAiB;EAC7B,aAAa,EAAQ,OAAO;EAC5B,gBAAgB;EACjB;CAGD,SAAS;EACP,YAAY,EAAiB;EAC7B,aAAa,EAAQ,UAAU;EAC/B,gBAAgB;EACjB;CACD,UAAU;EACR,YAAY,EAAiB;EAC7B,aAAa,EAAQ,WAAW;EAChC,gBAAgB;EACjB;CAGD,SAAS;EACP,YAAY,EAAiB;EAC7B,aAAa,EAAQ,UAAU;EAC/B,gBAAgB;EACjB;CACD,WAAW;EACT,YAAY,EAAiB;EAC7B,aAAa,EAAQ,YAAY;EACjC,gBAAgB;EACjB;CACD,UAAU;EACR,YAAY,EAAiB;EAC7B,aAAa,EAAQ,WAAW;EAChC,gBAAgB;EACjB;CACD,WAAW;EACT,YAAY,EAAiB;EAC7B,aAAa,EAAQ,YAAY;EACjC,gBAAgB;EACjB;CACD,QAAQ;EACN,YAAY,EAAiB;EAC7B,aAAa,EAAQ,SAAS;EAC9B,gBAAgB;EACjB;CACD,MAAM;EACJ,YAAY,EAAiB;EAC7B,aAAa,EAAQ,OAAO;EAC5B,gBAAgB;EACjB;CACF"}
@@ -0,0 +1,131 @@
1
+ import { SHARE_GRANT_FIELDS as e, VIBE_SHARE_ROLES as t, partitionGrants as n } from "./shareControllerTypes.js";
2
+ import { useCallback as r, useMemo as i } from "react";
3
+ import { gql as a } from "@apollo/client";
4
+ import { useMutation as o, useQuery as s } from "@apollo/client/react";
5
+ //#region src/components/sharing/useNoteShareController.ts
6
+ var c = a`
7
+ query NoteShares($id: ID!) {
8
+ note(id: $id) {
9
+ id
10
+ title
11
+ hasShares
12
+ myRole
13
+ shares {
14
+ ${e}
15
+ }
16
+ }
17
+ }
18
+ `, l = a`
19
+ mutation ShareVibecontrolsNote($input: ShareEntityInput!) {
20
+ shareVibecontrolsNote(input: $input) {
21
+ ${e}
22
+ }
23
+ }
24
+ `, u = a`
25
+ mutation RevokeVibecontrolsNoteShare($input: RevokeShareInput!) {
26
+ revokeVibecontrolsNoteShare(input: $input) {
27
+ id
28
+ }
29
+ }
30
+ `, d = a`
31
+ mutation UpdateVibecontrolsNoteShareRole($input: UpdateShareRoleInput!) {
32
+ updateVibecontrolsNoteShareRole(input: $input) {
33
+ id
34
+ role
35
+ }
36
+ }
37
+ `, f = a`
38
+ mutation CreateVibecontrolsNoteShareLink($input: CreateShareGrantLinkInput!) {
39
+ createVibecontrolsNoteShareLink(input: $input) {
40
+ ${e}
41
+ }
42
+ }
43
+ `, p = a`
44
+ mutation RevokeVibecontrolsNoteShareLink($input: RevokeShareInput!) {
45
+ revokeVibecontrolsNoteShareLink(input: $input) {
46
+ id
47
+ }
48
+ }
49
+ `;
50
+ function m(e) {
51
+ let { data: a, loading: m, refetch: h } = s(c, {
52
+ variables: { id: e.id },
53
+ skip: !e.id,
54
+ fetchPolicy: "cache-and-network"
55
+ }), [g] = o(l), [_] = o(u), [v] = o(d), [y] = o(f), [b] = o(p), { grants: x, links: S } = i(() => n(a?.note?.shares ?? []), [a?.note?.shares]);
56
+ return {
57
+ loading: m,
58
+ grants: x,
59
+ links: S,
60
+ refetch: r(() => {
61
+ h();
62
+ }, [h]),
63
+ createUserShare: r(async ({ email: t, role: n, message: r, expiresAt: i }) => {
64
+ await g({ variables: { input: {
65
+ resourceId: e.id,
66
+ email: t,
67
+ role: n,
68
+ message: r,
69
+ expiresAt: i
70
+ } } }), h();
71
+ }, [
72
+ g,
73
+ e.id,
74
+ h
75
+ ]),
76
+ createLinkShare: r(async ({ role: t, expiresAt: n }) => {
77
+ let r = await y({ variables: { input: {
78
+ resourceId: e.id,
79
+ role: t,
80
+ expiresAt: n ?? null
81
+ } } });
82
+ return h(), { shareUrl: r.data?.createVibecontrolsNoteShareLink.shareUrl ?? "" };
83
+ }, [
84
+ y,
85
+ e.id,
86
+ h
87
+ ]),
88
+ updateRole: r(async (t, n) => {
89
+ await v({ variables: { input: {
90
+ resourceId: e.id,
91
+ shareId: t,
92
+ role: n
93
+ } } }), h();
94
+ }, [
95
+ v,
96
+ e.id,
97
+ h
98
+ ]),
99
+ revoke: r(async (t) => {
100
+ await _({ variables: { input: {
101
+ resourceId: e.id,
102
+ shareId: t
103
+ } } }), h();
104
+ }, [
105
+ _,
106
+ e.id,
107
+ h
108
+ ]),
109
+ revokeLink: r(async (t) => {
110
+ await b({ variables: { input: {
111
+ resourceId: e.id,
112
+ shareId: t
113
+ } } }), h();
114
+ }, [
115
+ b,
116
+ e.id,
117
+ h
118
+ ]),
119
+ capabilities: {
120
+ supportsUsers: !0,
121
+ supportsLink: !0,
122
+ supportsVibeShare: !!e.vibeId,
123
+ supportsExpiry: !0,
124
+ roles: t
125
+ }
126
+ };
127
+ }
128
+ //#endregion
129
+ export { m as useNoteShareController };
130
+
131
+ //# sourceMappingURL=useNoteShareController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNoteShareController.js","names":[],"sources":["../../../src/components/sharing/useNoteShareController.ts"],"sourcesContent":["/**\n * `useNoteShareController` — binds the generic fe-libs `ShareDialog` to the\n * VibeControls backend share mutations for a VibecontrolsNote.\n *\n * Mirrors `useVibeShareController` but targets the note-scoped mutations\n * (`shareVibecontrolsNote`, `revokeVibecontrolsNoteShare`, …) confirmed against\n * the deployed subgraph schema. `supportsVibeShare` is only enabled when the\n * note is attached to a vibe (`vibeId` present) — a standalone note has no vibe\n * audience to share with.\n */\n\nimport { useCallback, useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport type { ShareController, ShareRole } from '@burdenoff/fe-libs/sharing';\n\nimport {\n SHARE_GRANT_FIELDS,\n VIBE_SHARE_ROLES,\n partitionGrants,\n type ShareGrantNode,\n} from './shareControllerTypes';\n\nconst NOTE_SHARES_QUERY = gql`\n query NoteShares($id: ID!) {\n note(id: $id) {\n id\n title\n hasShares\n myRole\n shares {\n ${SHARE_GRANT_FIELDS}\n }\n }\n }\n`;\n\nconst SHARE_NOTE = gql`\n mutation ShareVibecontrolsNote($input: ShareEntityInput!) {\n shareVibecontrolsNote(input: $input) {\n ${SHARE_GRANT_FIELDS}\n }\n }\n`;\n\nconst REVOKE_NOTE_SHARE = gql`\n mutation RevokeVibecontrolsNoteShare($input: RevokeShareInput!) {\n revokeVibecontrolsNoteShare(input: $input) {\n id\n }\n }\n`;\n\nconst UPDATE_NOTE_SHARE_ROLE = gql`\n mutation UpdateVibecontrolsNoteShareRole($input: UpdateShareRoleInput!) {\n updateVibecontrolsNoteShareRole(input: $input) {\n id\n role\n }\n }\n`;\n\nconst CREATE_NOTE_SHARE_LINK = gql`\n mutation CreateVibecontrolsNoteShareLink($input: CreateShareGrantLinkInput!) {\n createVibecontrolsNoteShareLink(input: $input) {\n ${SHARE_GRANT_FIELDS}\n }\n }\n`;\n\nconst REVOKE_NOTE_SHARE_LINK = gql`\n mutation RevokeVibecontrolsNoteShareLink($input: RevokeShareInput!) {\n revokeVibecontrolsNoteShareLink(input: $input) {\n id\n }\n }\n`;\n\ninterface NoteSharesData {\n note: {\n id: string;\n title: string;\n hasShares: boolean;\n myRole: ShareRole | null;\n shares: ShareGrantNode[];\n } | null;\n}\n\ninterface ShareNoteData {\n shareVibecontrolsNote: ShareGrantNode;\n}\n\ninterface CreateNoteShareLinkData {\n createVibecontrolsNoteShareLink: ShareGrantNode;\n}\n\n/**\n * Build a {@link ShareController} for the given Note. `vibeId` (when set) drives\n * the \"Everyone with Vibe\" mode in the dialog.\n */\nexport function useNoteShareController(note: {\n id: string;\n title: string;\n vibeId?: string | null;\n}): ShareController {\n const { data, loading, refetch } = useQuery<NoteSharesData, { id: string }>(NOTE_SHARES_QUERY, {\n variables: { id: note.id },\n skip: !note.id,\n fetchPolicy: 'cache-and-network',\n });\n\n const [shareNote] = useMutation<ShareNoteData>(SHARE_NOTE);\n const [revokeNoteShare] = useMutation(REVOKE_NOTE_SHARE);\n const [updateNoteShareRole] = useMutation(UPDATE_NOTE_SHARE_ROLE);\n const [createNoteShareLink] = useMutation<CreateNoteShareLinkData>(CREATE_NOTE_SHARE_LINK);\n const [revokeNoteShareLink] = useMutation(REVOKE_NOTE_SHARE_LINK);\n\n const { grants, links } = useMemo(\n () => partitionGrants(data?.note?.shares ?? []),\n [data?.note?.shares]\n );\n\n const doRefetch = useCallback(() => {\n void refetch();\n }, [refetch]);\n\n const createUserShare = useCallback<ShareController['createUserShare']>(\n async ({ email, role, message, expiresAt }) => {\n await shareNote({\n variables: { input: { resourceId: note.id, email, role, message, expiresAt } },\n });\n void refetch();\n },\n [shareNote, note.id, refetch]\n );\n\n const createLinkShare = useCallback<NonNullable<ShareController['createLinkShare']>>(\n async ({ role, expiresAt }) => {\n const result = await createNoteShareLink({\n variables: { input: { resourceId: note.id, role, expiresAt: expiresAt ?? null } },\n });\n void refetch();\n return { shareUrl: result.data?.createVibecontrolsNoteShareLink.shareUrl ?? '' };\n },\n [createNoteShareLink, note.id, refetch]\n );\n\n const updateRole = useCallback<NonNullable<ShareController['updateRole']>>(\n async (grantId, role) => {\n await updateNoteShareRole({\n variables: { input: { resourceId: note.id, shareId: grantId, role } },\n });\n void refetch();\n },\n [updateNoteShareRole, note.id, refetch]\n );\n\n const revoke = useCallback<NonNullable<ShareController['revoke']>>(\n async (grantId) => {\n await revokeNoteShare({ variables: { input: { resourceId: note.id, shareId: grantId } } });\n void refetch();\n },\n [revokeNoteShare, note.id, refetch]\n );\n\n const revokeLink = useCallback<NonNullable<ShareController['revokeLink']>>(\n async (grantId) => {\n await revokeNoteShareLink({\n variables: { input: { resourceId: note.id, shareId: grantId } },\n });\n void refetch();\n },\n [revokeNoteShareLink, note.id, refetch]\n );\n\n return {\n loading,\n grants,\n links,\n refetch: doRefetch,\n createUserShare,\n createLinkShare,\n updateRole,\n revoke,\n revokeLink,\n capabilities: {\n supportsUsers: true,\n supportsLink: true,\n supportsVibeShare: Boolean(note.vibeId),\n supportsExpiry: true,\n roles: VIBE_SHARE_ROLES,\n },\n };\n}\n"],"mappings":";;;;;AAuBA,IAAM,IAAoB,CAAG;;;;;;;;UAQnB,EAAmB;;;;GAMvB,IAAa,CAAG;;;QAGd,EAAmB;;;GAKrB,IAAoB,CAAG;;;;;;GAQvB,IAAyB,CAAG;;;;;;;GAS5B,IAAyB,CAAG;;;QAG1B,EAAmB;;;GAKrB,IAAyB,CAAG;;;;;;;AA8BlC,SAAgB,EAAuB,GAInB;CAClB,IAAM,EAAE,SAAM,YAAS,eAAY,EAAyC,GAAmB;EAC7F,WAAW,EAAE,IAAI,EAAK,IAAI;EAC1B,MAAM,CAAC,EAAK;EACZ,aAAa;EACd,CAAC,EAEI,CAAC,KAAa,EAA2B,EAAW,EACpD,CAAC,KAAmB,EAAY,EAAkB,EAClD,CAAC,KAAuB,EAAY,EAAuB,EAC3D,CAAC,KAAuB,EAAqC,EAAuB,EACpF,CAAC,KAAuB,EAAY,EAAuB,EAE3D,EAAE,WAAQ,aAAU,QAClB,EAAgB,GAAM,MAAM,UAAU,EAAE,CAAC,EAC/C,CAAC,GAAM,MAAM,OAAO,CACrB;AAuDD,QAAO;EACL;EACA;EACA;EACA,SAzDgB,QAAkB;AAC7B,MAAS;KACb,CAAC,EAAQ,CAAC;EAwDX,iBAtDsB,EACtB,OAAO,EAAE,UAAO,SAAM,YAAS,mBAAgB;AAIxC,GAHL,MAAM,EAAU,EACd,WAAW,EAAE,OAAO;IAAE,YAAY,EAAK;IAAI;IAAO;IAAM;IAAS;IAAW,EAAE,EAC/E,CAAC,EACG,GAAS;KAEhB;GAAC;GAAW,EAAK;GAAI;GAAQ,CAC9B;EA+CC,iBA7CsB,EACtB,OAAO,EAAE,SAAM,mBAAgB;GAC7B,IAAM,IAAS,MAAM,EAAoB,EACvC,WAAW,EAAE,OAAO;IAAE,YAAY,EAAK;IAAI;IAAM,WAAW,KAAa;IAAM,EAAE,EAClF,CAAC;AAEF,UADK,GAAS,EACP,EAAE,UAAU,EAAO,MAAM,gCAAgC,YAAY,IAAI;KAElF;GAAC;GAAqB,EAAK;GAAI;GAAQ,CACxC;EAqCC,YAnCiB,EACjB,OAAO,GAAS,MAAS;AAIlB,GAHL,MAAM,EAAoB,EACxB,WAAW,EAAE,OAAO;IAAE,YAAY,EAAK;IAAI,SAAS;IAAS;IAAM,EAAE,EACtE,CAAC,EACG,GAAS;KAEhB;GAAC;GAAqB,EAAK;GAAI;GAAQ,CACxC;EA4BC,QA1Ba,EACb,OAAO,MAAY;AAEZ,GADL,MAAM,EAAgB,EAAE,WAAW,EAAE,OAAO;IAAE,YAAY,EAAK;IAAI,SAAS;IAAS,EAAE,EAAE,CAAC,EACrF,GAAS;KAEhB;GAAC;GAAiB,EAAK;GAAI;GAAQ,CACpC;EAqBC,YAnBiB,EACjB,OAAO,MAAY;AAIZ,GAHL,MAAM,EAAoB,EACxB,WAAW,EAAE,OAAO;IAAE,YAAY,EAAK;IAAI,SAAS;IAAS,EAAE,EAChE,CAAC,EACG,GAAS;KAEhB;GAAC;GAAqB,EAAK;GAAI;GAAQ,CACxC;EAYC,cAAc;GACZ,eAAe;GACf,cAAc;GACd,mBAAmB,EAAQ,EAAK;GAChC,gBAAgB;GAChB,OAAO;GACR;EACF"}