@vibefield/rosetta-room 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -2
- package/dist/HistoryRoom.d.ts +98 -0
- package/dist/HistoryRoom.js +440 -0
- package/dist/TranscriptReader.d.ts +22 -0
- package/dist/TranscriptReader.js +72 -0
- package/dist/components/ColdStart.d.ts +4 -0
- package/dist/components/ColdStart.js +5 -0
- package/dist/components/FilterChips.d.ts +4 -0
- package/dist/components/FilterChips.js +5 -0
- package/dist/components/HostAct.d.ts +9 -0
- package/dist/components/HostAct.js +16 -0
- package/dist/components/HostMenu.d.ts +41 -0
- package/dist/components/HostMenu.js +216 -0
- package/dist/components/IngestStatus.d.ts +4 -0
- package/dist/components/IngestStatus.js +20 -0
- package/dist/components/ListActivityNotice.d.ts +5 -0
- package/dist/components/ListActivityNotice.js +4 -0
- package/dist/components/ListSections.d.ts +20 -0
- package/dist/components/ListSections.js +39 -0
- package/dist/components/RenameHeading.d.ts +9 -0
- package/dist/components/RenameHeading.js +75 -0
- package/dist/components/SearchStrip.d.ts +5 -0
- package/dist/components/SearchStrip.js +8 -0
- package/dist/components/SummaryMetrics.d.ts +13 -0
- package/dist/components/SummaryMetrics.js +9 -0
- package/dist/components/SummaryPanels.d.ts +35 -0
- package/dist/components/SummaryPanels.js +117 -0
- package/dist/components/Transcript.d.ts +46 -0
- package/dist/components/Transcript.js +358 -0
- package/dist/components/charts/ActivityChartDetail.d.ts +13 -0
- package/dist/components/charts/ActivityChartDetail.js +78 -0
- package/dist/components/charts/CalendarHeatmap.d.ts +16 -0
- package/dist/components/charts/CalendarHeatmap.js +55 -0
- package/dist/components/charts/DayActivityChart.d.ts +8 -0
- package/dist/components/charts/DayActivityChart.js +71 -0
- package/dist/components/charts/ModelsChartDetail.d.ts +7 -0
- package/dist/components/charts/ModelsChartDetail.js +19 -0
- package/dist/components/charts/ProjectChartDialog.d.ts +19 -0
- package/dist/components/charts/ProjectChartDialog.js +39 -0
- package/dist/components/charts/Streamgraph.d.ts +16 -0
- package/dist/components/charts/Streamgraph.js +94 -0
- package/dist/components/charts/Treemap.d.ts +26 -0
- package/dist/components/charts/Treemap.js +81 -0
- package/dist/components/charts/index.d.ts +9 -0
- package/dist/components/charts/index.js +4 -0
- package/dist/components/charts/streamgraph-layout.d.ts +23 -0
- package/dist/components/charts/streamgraph-layout.js +114 -0
- package/dist/components/charts/treemap-layout.d.ts +32 -0
- package/dist/components/charts/treemap-layout.js +140 -0
- package/dist/components/charts/types.d.ts +65 -0
- package/dist/components/charts/types.js +8 -0
- package/dist/components/file-viewer/FileViewerBrowsers.d.ts +10 -0
- package/dist/components/file-viewer/FileViewerBrowsers.js +65 -0
- package/dist/components/file-viewer/FileViewerDialog.d.ts +33 -0
- package/dist/components/file-viewer/FileViewerDialog.js +82 -0
- package/dist/components/file-viewer/FileViewerHeader.d.ts +26 -0
- package/dist/components/file-viewer/FileViewerHeader.js +17 -0
- package/dist/components/file-viewer/ImageGalleryDialog.d.ts +14 -0
- package/dist/components/file-viewer/ImageGalleryDialog.js +98 -0
- package/dist/components/file-viewer/diff/index.d.ts +2 -0
- package/dist/components/file-viewer/diff/index.js +2 -0
- package/dist/components/file-viewer/diff/parse.d.ts +2 -0
- package/dist/components/file-viewer/diff/parse.js +164 -0
- package/dist/components/file-viewer/diff/types.d.ts +44 -0
- package/dist/components/file-viewer/diff/types.js +7 -0
- package/dist/components/file-viewer/gallery-source.d.ts +11 -0
- package/dist/components/file-viewer/gallery-source.js +75 -0
- package/dist/components/file-viewer/image-resource.d.ts +19 -0
- package/dist/components/file-viewer/image-resource.js +60 -0
- package/dist/components/file-viewer/index.d.ts +5 -0
- package/dist/components/file-viewer/index.js +8 -0
- package/dist/components/file-viewer/markdown/blocks.d.ts +2 -0
- package/dist/components/file-viewer/markdown/blocks.js +277 -0
- package/dist/components/file-viewer/markdown/index.d.ts +4 -0
- package/dist/components/file-viewer/markdown/index.js +35 -0
- package/dist/components/file-viewer/markdown/inline.d.ts +10 -0
- package/dist/components/file-viewer/markdown/inline.js +178 -0
- package/dist/components/file-viewer/markdown/render.d.ts +9 -0
- package/dist/components/file-viewer/markdown/render.js +78 -0
- package/dist/components/file-viewer/markdown/types.d.ts +57 -0
- package/dist/components/file-viewer/markdown/types.js +11 -0
- package/dist/components/file-viewer/minimap/CodeMinimap.d.ts +10 -0
- package/dist/components/file-viewer/minimap/CodeMinimap.js +296 -0
- package/dist/components/file-viewer/minimap/layout.d.ts +60 -0
- package/dist/components/file-viewer/minimap/layout.js +117 -0
- package/dist/components/file-viewer/minimap/palette.d.ts +2 -0
- package/dist/components/file-viewer/minimap/palette.js +73 -0
- package/dist/components/file-viewer/minimap/raster.d.ts +19 -0
- package/dist/components/file-viewer/minimap/raster.js +122 -0
- package/dist/components/file-viewer/registry.d.ts +25 -0
- package/dist/components/file-viewer/registry.js +182 -0
- package/dist/components/file-viewer/syntax/grammars.d.ts +16 -0
- package/dist/components/file-viewer/syntax/grammars.js +221 -0
- package/dist/components/file-viewer/syntax/index.d.ts +8 -0
- package/dist/components/file-viewer/syntax/index.js +9 -0
- package/dist/components/file-viewer/syntax/languages.d.ts +14 -0
- package/dist/components/file-viewer/syntax/languages.js +115 -0
- package/dist/components/file-viewer/syntax/tokenize-config.d.ts +2 -0
- package/dist/components/file-viewer/syntax/tokenize-config.js +132 -0
- package/dist/components/file-viewer/syntax/tokenize-css.d.ts +2 -0
- package/dist/components/file-viewer/syntax/tokenize-css.js +169 -0
- package/dist/components/file-viewer/syntax/tokenize-markup.d.ts +2 -0
- package/dist/components/file-viewer/syntax/tokenize-markup.js +124 -0
- package/dist/components/file-viewer/syntax/tokenize.d.ts +9 -0
- package/dist/components/file-viewer/syntax/tokenize.js +289 -0
- package/dist/components/file-viewer/syntax/types.d.ts +74 -0
- package/dist/components/file-viewer/syntax/types.js +13 -0
- package/dist/components/file-viewer/types.d.ts +111 -0
- package/dist/components/file-viewer/types.js +8 -0
- package/dist/components/file-viewer/use-file-content.d.ts +26 -0
- package/dist/components/file-viewer/use-file-content.js +144 -0
- package/dist/components/file-viewer/views/BinaryView.d.ts +2 -0
- package/dist/components/file-viewer/views/BinaryView.js +34 -0
- package/dist/components/file-viewer/views/CodeView.d.ts +2 -0
- package/dist/components/file-viewer/views/CodeView.js +46 -0
- package/dist/components/file-viewer/views/DiffBody.d.ts +7 -0
- package/dist/components/file-viewer/views/DiffBody.js +77 -0
- package/dist/components/file-viewer/views/DiffView.d.ts +2 -0
- package/dist/components/file-viewer/views/DiffView.js +11 -0
- package/dist/components/file-viewer/views/ImageView.d.ts +9 -0
- package/dist/components/file-viewer/views/ImageView.js +28 -0
- package/dist/components/file-viewer/views/MarkdownView.d.ts +9 -0
- package/dist/components/file-viewer/views/MarkdownView.js +20 -0
- package/dist/components/file-viewer/views/SvgView.d.ts +9 -0
- package/dist/components/file-viewer/views/SvgView.js +10 -0
- package/dist/components/git/ChangeTree.d.ts +20 -0
- package/dist/components/git/ChangeTree.js +35 -0
- package/dist/components/git/CommitViewer.d.ts +12 -0
- package/dist/components/git/CommitViewer.js +85 -0
- package/dist/components/git/GitChanges.d.ts +17 -0
- package/dist/components/git/GitChanges.js +70 -0
- package/dist/components/git/GitHistory.d.ts +9 -0
- package/dist/components/git/GitHistory.js +58 -0
- package/dist/components/git/co-author.d.ts +14 -0
- package/dist/components/git/co-author.js +55 -0
- package/dist/components/git/git-file-source.d.ts +18 -0
- package/dist/components/git/git-file-source.js +53 -0
- package/dist/components/git/git-host.d.ts +24 -0
- package/dist/components/git/git-host.js +130 -0
- package/dist/components/git/index.d.ts +10 -0
- package/dist/components/git/index.js +12 -0
- package/dist/components/git/path-tree.d.ts +23 -0
- package/dist/components/git/path-tree.js +73 -0
- package/dist/components/git/session-files.d.ts +9 -0
- package/dist/components/git/session-files.js +50 -0
- package/dist/components/git/types.d.ts +92 -0
- package/dist/components/git/types.js +9 -0
- package/dist/components/projects/ProjectActivitySparkline.d.ts +6 -0
- package/dist/components/projects/ProjectActivitySparkline.js +12 -0
- package/dist/components/projects/ProjectList.d.ts +25 -0
- package/dist/components/projects/ProjectList.js +43 -0
- package/dist/components/projects/ProjectListItem.d.ts +19 -0
- package/dist/components/projects/ProjectListItem.js +78 -0
- package/dist/components/projects/ProjectsColumn.d.ts +19 -0
- package/dist/components/projects/ProjectsColumn.js +36 -0
- package/dist/components/projects/index.d.ts +8 -0
- package/dist/components/projects/index.js +4 -0
- package/dist/components/search/MatchStepper.d.ts +9 -0
- package/dist/components/search/MatchStepper.js +14 -0
- package/dist/components/search/SearchLauncher.d.ts +12 -0
- package/dist/components/search/SearchLauncher.js +13 -0
- package/dist/components/search/SearchSheet.d.ts +29 -0
- package/dist/components/search/SearchSheet.js +230 -0
- package/dist/components/search/index.d.ts +8 -0
- package/dist/components/search/index.js +4 -0
- package/dist/components/search/shortcut.d.ts +25 -0
- package/dist/components/search/shortcut.js +32 -0
- package/dist/components/sessions/SessionList.d.ts +24 -0
- package/dist/components/sessions/SessionList.js +76 -0
- package/dist/components/sessions/SessionListItem.d.ts +13 -0
- package/dist/components/sessions/SessionListItem.js +58 -0
- package/dist/components/sessions/SessionsColumn.d.ts +26 -0
- package/dist/components/sessions/SessionsColumn.js +32 -0
- package/dist/components/sessions/index.d.ts +9 -0
- package/dist/components/sessions/index.js +4 -0
- package/dist/components/sessions/session-display.d.ts +8 -0
- package/dist/components/sessions/session-display.js +18 -0
- package/dist/components/sessions/types.d.ts +7 -0
- package/dist/components/sessions/types.js +1 -0
- package/dist/components/transcript/Activity.d.ts +45 -0
- package/dist/components/transcript/Activity.js +75 -0
- package/dist/components/transcript/Anchor.d.ts +8 -0
- package/dist/components/transcript/Anchor.js +23 -0
- package/dist/components/transcript/Attachments.d.ts +21 -0
- package/dist/components/transcript/Attachments.js +138 -0
- package/dist/components/transcript/AuthoredMessage.d.ts +19 -0
- package/dist/components/transcript/AuthoredMessage.js +17 -0
- package/dist/components/transcript/Compaction.d.ts +9 -0
- package/dist/components/transcript/Compaction.js +50 -0
- package/dist/components/transcript/DayPill.d.ts +38 -0
- package/dist/components/transcript/DayPill.js +68 -0
- package/dist/components/transcript/Document.d.ts +32 -0
- package/dist/components/transcript/Document.js +503 -0
- package/dist/components/transcript/Favicon.d.ts +32 -0
- package/dist/components/transcript/Favicon.js +71 -0
- package/dist/components/transcript/HeadOfDocument.d.ts +19 -0
- package/dist/components/transcript/HeadOfDocument.js +44 -0
- package/dist/components/transcript/Ledger.d.ts +53 -0
- package/dist/components/transcript/Ledger.js +182 -0
- package/dist/components/transcript/LineDetail.d.ts +24 -0
- package/dist/components/transcript/LineDetail.js +81 -0
- package/dist/components/transcript/Marker.d.ts +33 -0
- package/dist/components/transcript/Marker.js +21 -0
- package/dist/components/transcript/MessagePreview.d.ts +12 -0
- package/dist/components/transcript/MessagePreview.js +58 -0
- package/dist/components/transcript/Prompt.d.ts +20 -0
- package/dist/components/transcript/Prompt.js +55 -0
- package/dist/components/transcript/Prose.d.ts +19 -0
- package/dist/components/transcript/Prose.js +109 -0
- package/dist/components/transcript/ReceiptPill.d.ts +10 -0
- package/dist/components/transcript/ReceiptPill.js +10 -0
- package/dist/components/transcript/ResponseHead.d.ts +9 -0
- package/dist/components/transcript/ResponseHead.js +36 -0
- package/dist/components/transcript/Subthread.d.ts +19 -0
- package/dist/components/transcript/Subthread.js +96 -0
- package/dist/components/transcript/Team.d.ts +53 -0
- package/dist/components/transcript/Team.js +185 -0
- package/dist/components/transcript/Thinking.d.ts +24 -0
- package/dist/components/transcript/Thinking.js +50 -0
- package/dist/components/transcript/TimelineMeta.d.ts +9 -0
- package/dist/components/transcript/TimelineMeta.js +161 -0
- package/dist/components/transcript/TranscriptLoading.d.ts +10 -0
- package/dist/components/transcript/TranscriptLoading.js +10 -0
- package/dist/components/transcript/WebDetail.d.ts +8 -0
- package/dist/components/transcript/WebDetail.js +29 -0
- package/dist/components/transcript/Workflow.d.ts +12 -0
- package/dist/components/transcript/Workflow.js +208 -0
- package/dist/components/transcript/activity-plan.d.ts +84 -0
- package/dist/components/transcript/activity-plan.js +209 -0
- package/dist/components/transcript/attachment-file-source.d.ts +23 -0
- package/dist/components/transcript/attachment-file-source.js +103 -0
- package/dist/components/transcript/attachment-gallery.d.ts +12 -0
- package/dist/components/transcript/attachment-gallery.js +33 -0
- package/dist/components/transcript/detail-parts.d.ts +50 -0
- package/dist/components/transcript/detail-parts.js +295 -0
- package/dist/components/transcript/disclosure-state.d.ts +6 -0
- package/dist/components/transcript/disclosure-state.js +32 -0
- package/dist/components/transcript/disclosure-store.d.ts +8 -0
- package/dist/components/transcript/disclosure-store.js +31 -0
- package/dist/components/transcript/document-model.d.ts +15 -0
- package/dist/components/transcript/document-model.js +104 -0
- package/dist/components/transcript/document-plan.d.ts +28 -0
- package/dist/components/transcript/document-plan.js +145 -0
- package/dist/components/transcript/fold-preference.d.ts +17 -0
- package/dist/components/transcript/fold-preference.js +63 -0
- package/dist/components/transcript/glyphs.d.ts +15 -0
- package/dist/components/transcript/glyphs.js +47 -0
- package/dist/components/transcript/index.d.ts +23 -0
- package/dist/components/transcript/index.js +22 -0
- package/dist/components/transcript/message-file-source.d.ts +15 -0
- package/dist/components/transcript/message-file-source.js +31 -0
- package/dist/components/transcript/message-preview.d.ts +9 -0
- package/dist/components/transcript/message-preview.js +46 -0
- package/dist/components/transcript/read-file-source.d.ts +7 -0
- package/dist/components/transcript/read-file-source.js +34 -0
- package/dist/components/transcript/reader-context.d.ts +13 -0
- package/dist/components/transcript/reader-context.js +55 -0
- package/dist/components/transcript/reading-identity.d.ts +2 -0
- package/dist/components/transcript/reading-identity.js +52 -0
- package/dist/components/transcript/scroll-to-row.d.ts +2 -0
- package/dist/components/transcript/scroll-to-row.js +13 -0
- package/dist/components/transcript/thread-index.d.ts +14 -0
- package/dist/components/transcript/thread-index.js +40 -0
- package/dist/components/transcript/thread-navigation.d.ts +5 -0
- package/dist/components/transcript/thread-navigation.js +3 -0
- package/dist/components/transcript/timeline-meta-file-source.d.ts +6 -0
- package/dist/components/transcript/timeline-meta-file-source.js +55 -0
- package/dist/components/transcript/timeline-meta-state.d.ts +12 -0
- package/dist/components/transcript/timeline-meta-state.js +24 -0
- package/dist/components/ui/ColumnHeading.d.ts +27 -0
- package/dist/components/ui/ColumnHeading.js +7 -0
- package/dist/components/ui/ConfirmDialog.d.ts +14 -0
- package/dist/components/ui/ConfirmDialog.js +22 -0
- package/dist/components/ui/DialogShell.d.ts +18 -0
- package/dist/components/ui/DialogShell.js +40 -0
- package/dist/components/ui/EmptyState.d.ts +10 -0
- package/dist/components/ui/EmptyState.js +6 -0
- package/dist/components/ui/ListSummaryHeader.d.ts +9 -0
- package/dist/components/ui/ListSummaryHeader.js +6 -0
- package/dist/components/ui/ListToolbar.d.ts +6 -0
- package/dist/components/ui/ListToolbar.js +5 -0
- package/dist/components/ui/ResultGroupHeader.d.ts +9 -0
- package/dist/components/ui/ResultGroupHeader.js +5 -0
- package/dist/components/ui/SegmentedControl.d.ts +16 -0
- package/dist/components/ui/SegmentedControl.js +9 -0
- package/dist/components/ui/SummaryMetric.d.ts +13 -0
- package/dist/components/ui/SummaryMetric.js +10 -0
- package/dist/components/ui/Veil.d.ts +7 -0
- package/dist/components/ui/Veil.js +5 -0
- package/dist/components/ui/index.d.ts +20 -0
- package/dist/components/ui/index.js +10 -0
- package/dist/components/worktree/WorktreeColumn.d.ts +22 -0
- package/dist/components/worktree/WorktreeColumn.js +170 -0
- package/dist/components/worktree/WorktreeFileTree.d.ts +19 -0
- package/dist/components/worktree/WorktreeFileTree.js +60 -0
- package/dist/components/worktree/WorktreeHeader.d.ts +32 -0
- package/dist/components/worktree/WorktreeHeader.js +81 -0
- package/dist/components/worktree/WorktreeList.d.ts +8 -0
- package/dist/components/worktree/WorktreeList.js +12 -0
- package/dist/components/worktree/index.d.ts +12 -0
- package/dist/components/worktree/index.js +7 -0
- package/dist/components/worktree/types.d.ts +17 -0
- package/dist/components/worktree/types.js +3 -0
- package/dist/components/worktree/use-worktree-guest.d.ts +21 -0
- package/dist/components/worktree/use-worktree-guest.js +92 -0
- package/dist/components/worktree/worktree-file-commands.d.ts +19 -0
- package/dist/components/worktree/worktree-file-commands.js +112 -0
- package/dist/components/worktree/worktree-file-source.d.ts +15 -0
- package/dist/components/worktree/worktree-file-source.js +75 -0
- package/dist/components/worktree/worktree-host.d.ts +31 -0
- package/dist/components/worktree/worktree-host.js +242 -0
- package/dist/fixtures/index.d.ts +24 -0
- package/dist/fixtures/index.js +292 -0
- package/dist/host-inputs.d.ts +231 -0
- package/dist/host-inputs.js +35 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +14 -0
- package/dist/lib/activity-details.d.ts +23 -0
- package/dist/lib/activity-details.js +78 -0
- package/dist/lib/activity-order.d.ts +32 -0
- package/dist/lib/activity-order.js +86 -0
- package/dist/lib/agent-paths.d.ts +6 -0
- package/dist/lib/agent-paths.js +19 -0
- package/dist/lib/agents.d.ts +15 -0
- package/dist/lib/agents.js +60 -0
- package/dist/lib/catalog-status.d.ts +3 -0
- package/dist/lib/catalog-status.js +10 -0
- package/dist/lib/chart-data.d.ts +14 -0
- package/dist/lib/chart-data.js +151 -0
- package/dist/lib/day-activity.d.ts +21 -0
- package/dist/lib/day-activity.js +56 -0
- package/dist/lib/format.d.ts +24 -0
- package/dist/lib/format.js +116 -0
- package/dist/lib/hooks.d.ts +118 -0
- package/dist/lib/hooks.js +608 -0
- package/dist/lib/icons.d.ts +21 -0
- package/dist/lib/icons.js +49 -0
- package/dist/lib/image-media.d.ts +6 -0
- package/dist/lib/image-media.js +30 -0
- package/dist/lib/ingest-activity.d.ts +50 -0
- package/dist/lib/ingest-activity.js +151 -0
- package/dist/lib/ingest.d.ts +25 -0
- package/dist/lib/ingest.js +70 -0
- package/dist/lib/model-labels.d.ts +5 -0
- package/dist/lib/model-labels.js +28 -0
- package/dist/lib/model.d.ts +73 -0
- package/dist/lib/model.js +141 -0
- package/dist/lib/motion.d.ts +13 -0
- package/dist/lib/motion.js +80 -0
- package/dist/lib/room-storage.d.ts +13 -0
- package/dist/lib/room-storage.js +40 -0
- package/dist/lib/search-results.d.ts +27 -0
- package/dist/lib/search-results.js +83 -0
- package/dist/lib/search-sheet.d.ts +119 -0
- package/dist/lib/search-sheet.js +282 -0
- package/dist/lib/sections.d.ts +19 -0
- package/dist/lib/sections.js +36 -0
- package/dist/lib/session-activity.d.ts +5 -0
- package/dist/lib/session-activity.js +15 -0
- package/dist/lib/session-leases.d.ts +15 -0
- package/dist/lib/session-leases.js +61 -0
- package/dist/lib/spend.d.ts +42 -0
- package/dist/lib/spend.js +112 -0
- package/dist/lib/tint.d.ts +9 -0
- package/dist/lib/tint.js +53 -0
- package/dist/lib/transcript/collaboration.d.ts +97 -0
- package/dist/lib/transcript/collaboration.js +423 -0
- package/dist/lib/transcript/families.d.ts +212 -0
- package/dist/lib/transcript/families.js +906 -0
- package/dist/lib/transcript/favicons.d.ts +54 -0
- package/dist/lib/transcript/favicons.js +183 -0
- package/dist/lib/transcript/head-of-document.d.ts +27 -0
- package/dist/lib/transcript/head-of-document.js +274 -0
- package/dist/lib/transcript/head-state.d.ts +182 -0
- package/dist/lib/transcript/head-state.js +305 -0
- package/dist/lib/transcript/history-window.d.ts +13 -0
- package/dist/lib/transcript/history-window.js +52 -0
- package/dist/lib/transcript/images.d.ts +8 -0
- package/dist/lib/transcript/images.js +60 -0
- package/dist/lib/transcript/index.d.ts +15 -0
- package/dist/lib/transcript/index.js +8 -0
- package/dist/lib/transcript/ledger.d.ts +143 -0
- package/dist/lib/transcript/ledger.js +513 -0
- package/dist/lib/transcript/protocol.d.ts +23 -0
- package/dist/lib/transcript/protocol.js +1 -0
- package/dist/lib/transcript/sittings.d.ts +75 -0
- package/dist/lib/transcript/sittings.js +147 -0
- package/dist/lib/transcript/tail-scroll.d.ts +36 -0
- package/dist/lib/transcript/tail-scroll.js +167 -0
- package/dist/lib/transcript/tail-state.d.ts +17 -0
- package/dist/lib/transcript/tail-state.js +30 -0
- package/dist/lib/transcript/turns.d.ts +192 -0
- package/dist/lib/transcript/turns.js +576 -0
- package/dist/lib/transcript/use-transcript-opening.d.ts +12 -0
- package/dist/lib/transcript/use-transcript-opening.js +31 -0
- package/dist/lib/transcript/use-transcript-tail.d.ts +25 -0
- package/dist/lib/transcript/use-transcript-tail.js +158 -0
- package/dist/lib/transcript/vocabulary.d.ts +119 -0
- package/dist/lib/transcript/vocabulary.js +293 -0
- package/dist/lib/transcript/web-address.d.ts +6 -0
- package/dist/lib/transcript/web-address.js +18 -0
- package/dist/lib/transcript/workflow.d.ts +31 -0
- package/dist/lib/transcript/workflow.js +133 -0
- package/dist/lib/use-activity-order.d.ts +2 -0
- package/dist/lib/use-activity-order.js +23 -0
- package/dist/lib/use-day-usage.d.ts +8 -0
- package/dist/lib/use-day-usage.js +31 -0
- package/dist/lib/use-deferred-list-order.d.ts +26 -0
- package/dist/lib/use-deferred-list-order.js +142 -0
- package/dist/lib/use-ingest-activity.d.ts +2 -0
- package/dist/lib/use-ingest-activity.js +49 -0
- package/dist/lib/use-list-motion.d.ts +4 -0
- package/dist/lib/use-list-motion.js +67 -0
- package/dist/ports.d.ts +222 -0
- package/dist/ports.js +3 -0
- package/dist/room-context.d.ts +60 -0
- package/dist/room-context.js +87 -0
- package/dist/styles/activity-chart.css +127 -0
- package/dist/styles/attachments.css +29 -0
- package/dist/styles/favicon.css +73 -0
- package/dist/styles/history-room.css +18 -0
- package/dist/styles/host-inputs.css +250 -0
- package/dist/styles/image-gallery.css +30 -0
- package/dist/styles/ingest.css +51 -0
- package/dist/styles/message-preview.css +34 -0
- package/dist/styles/models-chart.css +66 -0
- package/dist/styles/project-chart.css +19 -0
- package/dist/styles/reader.css +33 -0
- package/dist/styles/room.css +1411 -0
- package/dist/styles/search.css +252 -0
- package/dist/styles/transcript-activity.css +70 -0
- package/dist/styles/transcript-head.css +220 -0
- package/dist/styles/transcript-loading.css +57 -0
- package/dist/styles/transcript.css +1226 -0
- package/package.json +61 -4
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SessionRow } from "@vibefield/rosetta-protocol";
|
|
2
|
+
import type { ArrangedSection } from "../../lib/sections.js";
|
|
3
|
+
import type { SessionFacts } from "./types.js";
|
|
4
|
+
export interface SessionListProps {
|
|
5
|
+
/** Sessions in display order, normally newest first. */
|
|
6
|
+
readonly sessions: readonly SessionRow[];
|
|
7
|
+
readonly factsBySessionId: ReadonlyMap<string, SessionFacts>;
|
|
8
|
+
readonly getRuntimeKind: (runtimeId: string) => string;
|
|
9
|
+
readonly selectedSessionId: string | null;
|
|
10
|
+
readonly currentTimestamp: number;
|
|
11
|
+
/** A backfill is running — unindexed sessions cook rather than lie. */
|
|
12
|
+
readonly duringBackfill: boolean;
|
|
13
|
+
/** The census is still naming sessions: the empty state yields to the
|
|
14
|
+
* section's veil, because only the ready may claim emptiness. */
|
|
15
|
+
readonly pending: boolean;
|
|
16
|
+
readonly emptyState?: {
|
|
17
|
+
readonly title: string;
|
|
18
|
+
readonly description: string;
|
|
19
|
+
} | undefined;
|
|
20
|
+
readonly onSessionSelect: (session: SessionRow) => void;
|
|
21
|
+
/** The host's sections (R3), arranged; null draws the days it always did. */
|
|
22
|
+
readonly sections?: readonly ArrangedSection<SessionRow>[] | null | undefined;
|
|
23
|
+
}
|
|
24
|
+
export declare function SessionList(props: SessionListProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { dayLabel } from "../../lib/format.js";
|
|
3
|
+
import { sessionLastActive } from "../../lib/session-activity.js";
|
|
4
|
+
import { useListMotion } from "../../lib/use-list-motion.js";
|
|
5
|
+
import { activityDay } from "../../lib/activity-order.js";
|
|
6
|
+
import { useDeferredListOrder } from "../../lib/use-deferred-list-order.js";
|
|
7
|
+
import { ListActivityNotice } from "../ListActivityNotice.js";
|
|
8
|
+
import { FoldLine, SectionHead, useOpenFolds } from "../ListSections.js";
|
|
9
|
+
import { EmptyState } from "../ui/index.js";
|
|
10
|
+
import { SessionListItem } from "./SessionListItem.js";
|
|
11
|
+
const sessionId = (session) => session.id;
|
|
12
|
+
function groupSessionsByDay(positions, sessions, currentTimestamp) {
|
|
13
|
+
const groups = [];
|
|
14
|
+
for (const { id, day } of positions) {
|
|
15
|
+
const session = sessions.get(id);
|
|
16
|
+
if (!session)
|
|
17
|
+
continue;
|
|
18
|
+
const label = dayLabel(day, currentTimestamp);
|
|
19
|
+
const currentGroup = groups.at(-1);
|
|
20
|
+
if (currentGroup?.day === day) {
|
|
21
|
+
currentGroup.sessions.push(session);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
groups.push({ day, label, sessions: [session] });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return groups;
|
|
28
|
+
}
|
|
29
|
+
export function SessionList(props) {
|
|
30
|
+
const scope = `browse:${props.sessions[0]?.project_id ?? "empty"}`;
|
|
31
|
+
const sections = props.sections ?? null;
|
|
32
|
+
const folds = useOpenFolds(sections, sessionId, props.selectedSessionId);
|
|
33
|
+
const position = (session) => ({ id: session.id, day: activityDay(sessionLastActive(session)) });
|
|
34
|
+
// A folded section's rows are the reader's to open, so they never wait on
|
|
35
|
+
// the held geometry: only the open runs go through it.
|
|
36
|
+
const layout = useDeferredListOrder(sections === null
|
|
37
|
+
? props.sessions.map(position)
|
|
38
|
+
: sections.flatMap(entry => entry.section.fold !== undefined ? [] :
|
|
39
|
+
entry.rows.map(session => ({ ...position(session), section: entry.index }))), scope);
|
|
40
|
+
const byId = new Map(props.sessions.map(session => [session.id, session]));
|
|
41
|
+
const days = groupSessionsByDay(layout.items, byId, props.currentTimestamp);
|
|
42
|
+
const motionRef = useListMotion(JSON.stringify(layout.items), scope, layout.scrollRef);
|
|
43
|
+
const drawRow = (session) => (_jsx(SessionListItem, { session: session, facts: props.factsBySessionId.get(session.id) ?? null, runtimeKind: props.getRuntimeKind(session.runtime_id), isSelected: session.id === props.selectedSessionId, currentTimestamp: props.currentTimestamp, duringBackfill: props.duringBackfill, onSessionSelect: props.onSessionSelect }, `session:${session.id}`));
|
|
44
|
+
// Headers and rows share one key space. Moving from Yesterday to Today
|
|
45
|
+
// preserves the actual button, its keyboard focus and its animation.
|
|
46
|
+
const drawDays = (groups, keyPrefix) => groups.flatMap((group) => [
|
|
47
|
+
_jsx("div", { className: "ah-day", children: group.sessions.some(session => activityDay(sessionLastActive(session)) !== group.day)
|
|
48
|
+
? "Activity updated" : group.label }, `day:${keyPrefix}${group.day}`),
|
|
49
|
+
...group.sessions.map(drawRow),
|
|
50
|
+
]);
|
|
51
|
+
const drawRun = (entry, rows) => entry.order === "room"
|
|
52
|
+
? drawDays(groupSessionsByDay(rows.map(position), byId, props.currentTimestamp), `${entry.index}:`)
|
|
53
|
+
: rows.map(drawRow);
|
|
54
|
+
const sectioned = sections === null ? null : sections.flatMap((entry) => {
|
|
55
|
+
if (entry.section.fold !== undefined) {
|
|
56
|
+
if (entry.rows.length === 0 && entry.section.fold.count === undefined)
|
|
57
|
+
return [];
|
|
58
|
+
const open = folds.isOpen(entry.section.key);
|
|
59
|
+
return [
|
|
60
|
+
_jsx(FoldLine, { arranged: entry, open: open, onToggle: () => folds.toggle(entry.section.key) }, `section:${entry.section.key}`),
|
|
61
|
+
...(open ? drawRun(entry, entry.rows) : []),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
const rows = layout.items.flatMap(item => item.section === entry.index ? byId.get(item.id) ?? [] : []);
|
|
65
|
+
if (rows.length === 0)
|
|
66
|
+
return [];
|
|
67
|
+
return [
|
|
68
|
+
...(entry.section.title === undefined ? [] : [
|
|
69
|
+
_jsx(SectionHead, { sectionKey: entry.section.key, children: entry.section.title }, `section:${entry.section.key}`),
|
|
70
|
+
]),
|
|
71
|
+
...drawRun(entry, rows),
|
|
72
|
+
];
|
|
73
|
+
});
|
|
74
|
+
return (_jsxs("div", { className: "ah-list-view", ...layout.hostProps, children: [_jsxs("div", { className: "ah-scroll ah-session-list ah-motion-list", ref: motionRef, onScroll: layout.onScroll, children: [sectioned ?? drawDays(days, ""), props.sessions.length === 0 && !props.pending && (_jsx(EmptyState, { title: props.emptyState?.title ?? "No sessions here yet", description: props.emptyState?.description ??
|
|
75
|
+
"Sessions appear as rosettad reads this project's agent stores." }))] }), _jsx(ListActivityNotice, { pending: layout.pending, onShow: layout.apply, noun: "sessions" })] }));
|
|
76
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SessionRow } from "@vibefield/rosetta-protocol";
|
|
2
|
+
import type { SessionFacts } from "./types.js";
|
|
3
|
+
export interface SessionListItemProps {
|
|
4
|
+
readonly session: SessionRow;
|
|
5
|
+
readonly facts: SessionFacts | null;
|
|
6
|
+
readonly runtimeKind: string;
|
|
7
|
+
readonly isSelected: boolean;
|
|
8
|
+
readonly currentTimestamp: number;
|
|
9
|
+
/** A backfill is running — an unindexed session's dot says "indexing". */
|
|
10
|
+
readonly duringBackfill: boolean;
|
|
11
|
+
readonly onSessionSelect: (session: SessionRow) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function SessionListItem({ session, facts, runtimeKind, isSelected, currentTimestamp, duringBackfill, onSessionSelect, }: SessionListItemProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { AgentMark } from "../../lib/agents.js";
|
|
3
|
+
import { duration, lastActive, tokenText } from "../../lib/format.js";
|
|
4
|
+
import { isSessionCooking } from "../../lib/ingest.js";
|
|
5
|
+
import { sessionLastActive } from "../../lib/session-activity.js";
|
|
6
|
+
import { spendDescription, spendText } from "../../lib/spend.js";
|
|
7
|
+
import { getSessionStatus, getSessionTitle } from "./session-display.js";
|
|
8
|
+
import { useHostInputs } from "../../host-inputs.js";
|
|
9
|
+
import { HostAct } from "../HostAct.js";
|
|
10
|
+
export function SessionListItem({ session, facts, runtimeKind, isSelected, currentTimestamp, duringBackfill, onSessionSelect, }) {
|
|
11
|
+
const status = getSessionStatus(session, duringBackfill);
|
|
12
|
+
// The cooking card: identity is real from the skim; the figure slots are
|
|
13
|
+
// what is still unknown, and they alone carry the processing state. A
|
|
14
|
+
// facts row whose figures have not landed — a cooking session still
|
|
15
|
+
// counting from zero — is not a claim: the shimmer keeps the slot.
|
|
16
|
+
const cookingState = isSessionCooking(session);
|
|
17
|
+
const landedFacts = facts !== null && (facts.messages > 0 || !cookingState) ? facts : null;
|
|
18
|
+
const isCooking = cookingState && landedFacts === null;
|
|
19
|
+
const title = getSessionTitle(session);
|
|
20
|
+
const lastActiveLabel = lastActive(sessionLastActive(session), currentTimestamp);
|
|
21
|
+
const roleLabel = session.role === "main" ? null : session.role;
|
|
22
|
+
const accessibleMetadata = [
|
|
23
|
+
roleLabel === null ? null : `${roleLabel} session`,
|
|
24
|
+
landedFacts === null ? null : `${landedFacts.messages} messages`,
|
|
25
|
+
landedFacts !== null && landedFacts.tokens > 0
|
|
26
|
+
? `${tokenText(landedFacts.tokens)} tokens`
|
|
27
|
+
: null,
|
|
28
|
+
landedFacts !== null && landedFacts.tokens > 0
|
|
29
|
+
? spendDescription(landedFacts.spend, tokenText)
|
|
30
|
+
: null,
|
|
31
|
+
].filter((value) => value !== null);
|
|
32
|
+
const spendFigure = landedFacts !== null && landedFacts.tokens > 0
|
|
33
|
+
? spendText(landedFacts.spend)
|
|
34
|
+
: null;
|
|
35
|
+
// R4: the host's id slot, and its act or the reason at the row's end.
|
|
36
|
+
const decoration = useHostInputs().sessionRow?.(session) ?? null;
|
|
37
|
+
const hostId = decoration?.id ?? null;
|
|
38
|
+
const act = decoration?.act ?? null;
|
|
39
|
+
const reason = act === null ? (decoration?.reason ?? null) : null;
|
|
40
|
+
const className = `ah-session-list-item${isSelected ? " is-selected" : ""}`;
|
|
41
|
+
const accessibleName = [
|
|
42
|
+
title,
|
|
43
|
+
status.label,
|
|
44
|
+
...accessibleMetadata,
|
|
45
|
+
`last active ${lastActiveLabel}`,
|
|
46
|
+
].join(" · ");
|
|
47
|
+
// With an act the row holds two buttons, and a button cannot hold another:
|
|
48
|
+
// the row becomes a box, one button covers it (the selection, named as the
|
|
49
|
+
// row always was), and the act sits above it. The rest is drawn for the eye.
|
|
50
|
+
const shown = act === null ? undefined : true;
|
|
51
|
+
const last = (_jsx("span", { className: "ah-last", "data-live": lastActiveLabel === "now" ? "" : undefined, "aria-hidden": shown, children: lastActiveLabel }));
|
|
52
|
+
const idSlot = hostId === null ? (_jsx("span", { className: "ah-session-list-item__native-id", title: `Native session ${session.native_session_id}`, children: _jsx("span", { children: session.native_session_id.slice(0, 18) }) })) : (_jsx("span", { className: "ah-session-list-item__native-id", "data-host": "", title: decoration?.idTitle, "aria-hidden": shown, children: hostId }));
|
|
53
|
+
const body = (_jsxs(_Fragment, { children: [_jsx("i", { className: "ah-session-list-item__status", "data-tone": status.tone ?? undefined, "data-live": status.isLive ? "" : undefined, title: status.label, "aria-hidden": "true" }), _jsxs("span", { className: "ah-session-list-item__header", "aria-hidden": shown, children: [_jsx("strong", { title: title, children: title }), _jsx(AgentMark, { kind: runtimeKind })] }), _jsxs("span", { className: "ah-session-list-item__metadata", "aria-hidden": shown, children: [roleLabel !== null && (_jsxs(_Fragment, { children: [_jsx("span", { children: roleLabel }), _jsx("span", { children: "\u00B7" })] })), landedFacts !== null && (_jsxs(_Fragment, { children: [_jsxs("span", { children: [landedFacts.messages, " msgs"] }), _jsx("span", { children: "\u00B7" })] })), isCooking && (_jsxs(_Fragment, { children: [_jsx("span", { className: "ah-cook", "aria-hidden": "true" }), _jsx("span", { children: "\u00B7" })] })), _jsx("span", { title: "Span of recorded conversation activity", children: session.activity ? duration(session.activity.first_at, session.activity.last_at) : "—" }), landedFacts !== null && landedFacts.tokens > 0 && (_jsxs(_Fragment, { children: [_jsx("span", { children: "\u00B7" }), _jsx("span", { children: tokenText(landedFacts.tokens) })] })), landedFacts !== null && spendFigure !== null && (_jsxs(_Fragment, { children: [_jsx("span", { children: "\u00B7" }), _jsx("span", { title: spendDescription(landedFacts.spend, tokenText), children: spendFigure })] }))] }), _jsxs("span", { className: "ah-session-list-item__footer", children: [idSlot, act === null && reason === null ? last : (_jsxs("span", { className: "ah-session-list-item__end", children: [act !== null ? _jsx(HostAct, { act: act, place: "row" }) : (_jsx("span", { className: "ah-session-list-item__reason", children: reason })), last] }))] })] }));
|
|
54
|
+
if (act === null) {
|
|
55
|
+
return (_jsx("button", { type: "button", className: className, "data-motion-key": session.id, "data-role": session.role, "data-cooking": isCooking ? "" : undefined, "aria-current": isSelected ? "true" : undefined, "aria-label": accessibleName, onClick: () => onSessionSelect(session), children: body }));
|
|
56
|
+
}
|
|
57
|
+
return (_jsxs("div", { className: className, "data-motion-key": session.id, "data-role": session.role, "data-cooking": isCooking ? "" : undefined, "data-host-act": "", children: [_jsx("button", { type: "button", className: "ah-session-list-item__hit", "aria-current": isSelected ? "true" : undefined, "aria-label": accessibleName, onClick: () => onSessionSelect(session) }), body] }));
|
|
58
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { IngestProgress, SessionRow, UsageResponse } from "@vibefield/rosetta-protocol";
|
|
3
|
+
import type { RoomAct } from "../../host-inputs.js";
|
|
4
|
+
import type { ProjectViewModel } from "../../lib/model.js";
|
|
5
|
+
import type { SessionFacts } from "./types.js";
|
|
6
|
+
export interface SessionsColumnProps {
|
|
7
|
+
readonly project: ProjectViewModel | null;
|
|
8
|
+
/** Browse-mode sessions in display order. */
|
|
9
|
+
readonly sessions: readonly SessionRow[];
|
|
10
|
+
readonly factsBySessionId: ReadonlyMap<string, SessionFacts>;
|
|
11
|
+
readonly getRuntimeKind: (runtimeId: string) => string;
|
|
12
|
+
readonly selectedSessionId: string | null;
|
|
13
|
+
readonly usage: UsageResponse | null;
|
|
14
|
+
readonly activityGridStartTimestamp: number;
|
|
15
|
+
readonly currentTimestamp: number;
|
|
16
|
+
/** The daemon's cold-start block; null on a daemon too old to carry it. */
|
|
17
|
+
readonly ingest: IngestProgress | null;
|
|
18
|
+
/** `null` means the browser build has no desktop worktree integration. */
|
|
19
|
+
readonly isWorktreeVisible: boolean | null;
|
|
20
|
+
readonly onToggleWorktree: () => void;
|
|
21
|
+
readonly onSessionSelect: (session: SessionRow) => void;
|
|
22
|
+
/** R5: the host's acts after the head's own, and its card under the head. */
|
|
23
|
+
readonly hostActs?: readonly RoomAct[] | null | undefined;
|
|
24
|
+
readonly hostCard?: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
export declare function SessionsColumn({ project, sessions, factsBySessionId, getRuntimeKind, selectedSessionId, usage, activityGridStartTimestamp, currentTimestamp, ingest, isWorktreeVisible, onToggleWorktree, onSessionSelect, hostActs, hostCard, }: SessionsColumnProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { useHostInputs } from "../../host-inputs.js";
|
|
4
|
+
import { arrangeSections } from "../../lib/sections.js";
|
|
5
|
+
import { FilterChips } from "../FilterChips.js";
|
|
6
|
+
import { isCensusRunning, isIngestActive } from "../../lib/ingest.js";
|
|
7
|
+
import { ProjectSummaryPanel } from "../SummaryPanels.js";
|
|
8
|
+
import { ListSummaryHeader, ListToolbar } from "../ui/index.js";
|
|
9
|
+
import { SessionList } from "./SessionList.js";
|
|
10
|
+
export function SessionsColumn({ project, sessions, factsBySessionId, getRuntimeKind, selectedSessionId, usage, activityGridStartTimestamp, currentTimestamp, ingest, isWorktreeVisible, onToggleWorktree, onSessionSelect, hostActs = null, hostCard = null, }) {
|
|
11
|
+
const ingestActive = isIngestActive(ingest);
|
|
12
|
+
const censusRunning = isCensusRunning(ingest);
|
|
13
|
+
// R3: the host's sections over this project's sessions, in the room's order.
|
|
14
|
+
const { sessionSections, sessionFilters } = useHostInputs();
|
|
15
|
+
const projectId = project?.row.id ?? null;
|
|
16
|
+
const sections = useMemo(() => {
|
|
17
|
+
const given = projectId === null ? null : (sessionSections?.(projectId) ?? null);
|
|
18
|
+
return given === null ? null : arrangeSections(given, sessions, (session) => session.id);
|
|
19
|
+
}, [sessionSections, projectId, sessions]);
|
|
20
|
+
// R7: the host's chips over this project's list.
|
|
21
|
+
const filters = projectId === null ? null : (sessionFilters?.(projectId) ?? null);
|
|
22
|
+
// The head counts what the list holds: with sections, what they draw.
|
|
23
|
+
const visibleSessionCount = sections === null
|
|
24
|
+
? sessions.length
|
|
25
|
+
: sections.reduce((total, entry) => total + entry.rows.length, 0);
|
|
26
|
+
return (_jsxs("div", { className: "ah-col", children: [project === null ? (_jsx(ListSummaryHeader, { label: "Sessions", count: visibleSessionCount, singularNoun: "session" })) : (_jsx(ProjectSummaryPanel, { project: project, visibleSessionCount: visibleSessionCount, usage: usage, activityGridStartTimestamp: activityGridStartTimestamp, currentTimestamp: currentTimestamp, ingestActive: ingestActive, censusRunning: censusRunning, isWorktreeVisible: isWorktreeVisible, onToggleWorktree: onToggleWorktree, hostActs: hostActs })), project !== null && hostCard !== null && hostCard !== undefined && hostCard !== false && (_jsx("div", { className: "ah-hostcard", children: hostCard })), filters !== null && filters.length > 0 && (_jsx(ListToolbar, { children: _jsx(FilterChips, { filters: filters }) })), censusRunning && _jsx("p", { className: "ah-ingest-note", children: "More conversations will appear as discovery finishes." }), _jsx("div", { className: "ah-veilhost", children: _jsx(SessionList, { sessions: sessions, factsBySessionId: factsBySessionId, getRuntimeKind: getRuntimeKind, selectedSessionId: selectedSessionId, currentTimestamp: currentTimestamp, duringBackfill: ingestActive, pending: censusRunning, emptyState: project === null
|
|
27
|
+
? {
|
|
28
|
+
title: "No project selected",
|
|
29
|
+
description: "Pick a project to browse its sessions.",
|
|
30
|
+
}
|
|
31
|
+
: undefined, onSessionSelect: onSessionSelect, sections: sections }) })] }));
|
|
32
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { getSessionStatus, getSessionTitle } from "./session-display.js";
|
|
2
|
+
export type { SessionStatus } from "./session-display.js";
|
|
3
|
+
export { SessionList } from "./SessionList.js";
|
|
4
|
+
export type { SessionListProps } from "./SessionList.js";
|
|
5
|
+
export { SessionListItem } from "./SessionListItem.js";
|
|
6
|
+
export type { SessionListItemProps } from "./SessionListItem.js";
|
|
7
|
+
export { SessionsColumn } from "./SessionsColumn.js";
|
|
8
|
+
export type { SessionsColumnProps } from "./SessionsColumn.js";
|
|
9
|
+
export type { SessionFacts, } from "./types.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SessionRow } from "@vibefield/rosetta-protocol";
|
|
2
|
+
export interface SessionStatus {
|
|
3
|
+
readonly tone: "green" | "orange" | null;
|
|
4
|
+
readonly isLive: boolean;
|
|
5
|
+
readonly label: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function getSessionStatus(session: SessionRow, duringBackfill?: boolean): SessionStatus;
|
|
8
|
+
export declare function getSessionTitle(session: SessionRow): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function getSessionStatus(session, duringBackfill = false) {
|
|
2
|
+
switch (session.stream_state) {
|
|
3
|
+
case "streaming":
|
|
4
|
+
// This is ingestion completeness, never evidence of an active agent.
|
|
5
|
+
return {
|
|
6
|
+
tone: "orange",
|
|
7
|
+
isLive: false,
|
|
8
|
+
label: duringBackfill ? "indexing" : "history incomplete",
|
|
9
|
+
};
|
|
10
|
+
case "pending":
|
|
11
|
+
return { tone: "orange", isLive: false, label: "not yet indexed" };
|
|
12
|
+
default:
|
|
13
|
+
return { tone: null, isLive: false, label: "history indexed" };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function getSessionTitle(session) {
|
|
17
|
+
return session.title ?? session.summary ?? session.native_session_id;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { TimelineEntry } from "@vibefield/rosetta-protocol";
|
|
3
|
+
import type { ActivityItem } from "./activity-plan.js";
|
|
4
|
+
import type { AttachmentFact, Segment } from "../../lib/transcript/index.js";
|
|
5
|
+
import type { EarlierState } from "../../lib/transcript/head-state.js";
|
|
6
|
+
import "../../styles/transcript-activity.css";
|
|
7
|
+
export declare function revealActivityTarget(element: HTMLElement): void;
|
|
8
|
+
export declare function ActivityGroup({ items, rows, partial, forceOpen, highlightedId, alive, onOpenAttachment, onReceipt, earlier, onLoadEarlier, teamLoading, teamError, onRetryTeam, children }: {
|
|
9
|
+
readonly items: readonly ActivityItem[];
|
|
10
|
+
readonly rows: readonly TimelineEntry[];
|
|
11
|
+
readonly partial: boolean;
|
|
12
|
+
readonly earlier: EarlierState;
|
|
13
|
+
readonly onLoadEarlier: () => void;
|
|
14
|
+
readonly teamLoading: boolean;
|
|
15
|
+
readonly teamError: string | null;
|
|
16
|
+
readonly onRetryTeam: () => void;
|
|
17
|
+
readonly forceOpen: boolean;
|
|
18
|
+
readonly highlightedId: string | null;
|
|
19
|
+
readonly alive: (row: TimelineEntry) => boolean;
|
|
20
|
+
readonly onOpenAttachment: (fact: AttachmentFact, row?: readonly AttachmentFact[]) => void;
|
|
21
|
+
readonly onReceipt: (text: string) => void;
|
|
22
|
+
readonly children: ReactNode;
|
|
23
|
+
}): import("react").JSX.Element;
|
|
24
|
+
/** The second disclosure is independent of the room's outer fold preference. */
|
|
25
|
+
export declare function ActivityEvent({ domId, timelineId, label, summary, at, glyph, hit, forceOpen, failed, children }: {
|
|
26
|
+
readonly domId: string;
|
|
27
|
+
readonly timelineId: string;
|
|
28
|
+
readonly label: string;
|
|
29
|
+
readonly summary?: ReactNode;
|
|
30
|
+
readonly at?: number | null;
|
|
31
|
+
readonly glyph?: ReactNode;
|
|
32
|
+
readonly hit?: boolean;
|
|
33
|
+
readonly forceOpen?: boolean;
|
|
34
|
+
readonly failed?: boolean;
|
|
35
|
+
readonly children: ReactNode;
|
|
36
|
+
}): import("react").JSX.Element;
|
|
37
|
+
export declare function ContextEvent({ segment, domId, hit, forceOpen, onReceipt }: {
|
|
38
|
+
readonly segment: Extract<Segment, {
|
|
39
|
+
kind: "marker" | "summary";
|
|
40
|
+
}>;
|
|
41
|
+
readonly domId: string;
|
|
42
|
+
readonly hit: boolean;
|
|
43
|
+
readonly forceOpen: boolean;
|
|
44
|
+
readonly onReceipt: (text: string) => void;
|
|
45
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useMemo, useRef } from "react";
|
|
3
|
+
import { clock } from "../../lib/format.js";
|
|
4
|
+
import { activityRowKey, summarizeActivity } from "./activity-plan.js";
|
|
5
|
+
import { useGroupDisclosure, useRowDisclosure } from "./disclosure-state.js";
|
|
6
|
+
import { useFoldPreference } from "./fold-preference.js";
|
|
7
|
+
import { FamilyGlyph, LineChevron } from "./glyphs.js";
|
|
8
|
+
import { MessagePreview } from "./MessagePreview.js";
|
|
9
|
+
import { timelineImages } from "../../lib/transcript/images.js";
|
|
10
|
+
import { ActivityImagesContext, Attachments, useAttachmentActions } from "./Attachments.js";
|
|
11
|
+
import { scrollRowTo } from "./scroll-to-row.js";
|
|
12
|
+
import "../../styles/transcript-activity.css";
|
|
13
|
+
export function revealActivityTarget(element) {
|
|
14
|
+
if (element.getClientRects().length === 0)
|
|
15
|
+
return;
|
|
16
|
+
const scroller = element.closest(".tr-scroll");
|
|
17
|
+
if (scroller !== null)
|
|
18
|
+
scrollRowTo(scroller, element, "center");
|
|
19
|
+
const target = element.matches("button, summary, [tabindex]") ? element
|
|
20
|
+
: element.querySelector("summary, button, [tabindex]");
|
|
21
|
+
target?.focus({ preventScroll: true });
|
|
22
|
+
}
|
|
23
|
+
export function ActivityGroup({ items, rows, partial, forceOpen, highlightedId, alive, onOpenAttachment, onReceipt, earlier, onLoadEarlier, teamLoading, teamError, onRetryTeam, children }) {
|
|
24
|
+
const preference = useFoldPreference();
|
|
25
|
+
const keys = rows.map(activityRowKey);
|
|
26
|
+
const [override, setOverride] = useGroupDisclosure(keys, preference.revision);
|
|
27
|
+
const expanded = override ?? (!preference.folded || forceOpen || highlightedId !== null);
|
|
28
|
+
const listId = useId();
|
|
29
|
+
const root = useRef(null);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (forceOpen || highlightedId !== null)
|
|
32
|
+
setOverride(true);
|
|
33
|
+
// Arrival patches change keys, but must not reopen a deliberately shut run.
|
|
34
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
35
|
+
}, [forceOpen, highlightedId]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (highlightedId === null || !expanded)
|
|
38
|
+
return;
|
|
39
|
+
const frame = requestAnimationFrame(() => {
|
|
40
|
+
const target = root.current?.querySelector(`[id][data-timeline-id="${CSS.escape(highlightedId)}"]`);
|
|
41
|
+
if (target !== null && target !== undefined)
|
|
42
|
+
revealActivityTarget(target);
|
|
43
|
+
});
|
|
44
|
+
return () => cancelAnimationFrame(frame);
|
|
45
|
+
}, [expanded, highlightedId]);
|
|
46
|
+
const summary = useMemo(() => summarizeActivity(items), [items]);
|
|
47
|
+
const images = useMemo(() => timelineImages(rows), [rows]);
|
|
48
|
+
const { open: openImage, reveal } = useAttachmentActions(onOpenAttachment, onReceipt, images);
|
|
49
|
+
const running = summary.runningRows.some(alive);
|
|
50
|
+
const text = summary.nouns.map(noun => [noun.text, noun.names.join(", "), noun.more > 0 ? `… ${noun.more} more` : ""].filter(Boolean).join(" ")).join(" · ");
|
|
51
|
+
const range = summary.firstAt === null ? null : summary.lastAt === null || summary.firstAt === summary.lastAt
|
|
52
|
+
? clock(summary.firstAt) : `${clock(summary.firstAt)}–${clock(summary.lastAt)}`;
|
|
53
|
+
const label = [partial ? "More activity to load" : null, teamLoading ? "Loading team activity" : null, text, summary.failed > 0 ? `${summary.failed} failed` : null,
|
|
54
|
+
summary.interrupted > 0 ? `${summary.interrupted} interrupted` : null,
|
|
55
|
+
summary.cancelled > 0 ? `${summary.cancelled} cancelled` : null, running ? "Running" : null, range].filter(Boolean).join(" · ");
|
|
56
|
+
return _jsxs("div", { className: "tr-seg tr-activity", ref: root, "data-partial": partial || undefined, children: [_jsxs("button", { type: "button", className: "tr-line tr-fold tr-activity__head", "aria-expanded": expanded, "aria-controls": listId, "aria-label": label, title: label, onClick: () => {
|
|
57
|
+
if (!expanded && partial)
|
|
58
|
+
onLoadEarlier();
|
|
59
|
+
setOverride(!expanded);
|
|
60
|
+
}, children: [_jsx("span", { className: "tr-line__g chev", children: _jsx(LineChevron, {}) }), _jsxs("span", { className: "tr-line__v", children: [partial && _jsx("span", { children: "More activity to load \u00B7 " }), teamLoading && _jsx("span", { children: "Loading team activity \u00B7 " }), summary.nouns.map((noun, index) => _jsxs("span", { children: [index > 0 && " · ", noun.text, noun.names.length > 0 && _jsxs(_Fragment, { children: [" ", _jsxs("span", { className: "names", children: [noun.names.join(", "), noun.more > 0 ? ` … ${noun.more} more` : ""] })] })] }, noun.key))] }), _jsxs("span", { className: "tr-line__r", children: [summary.failed > 0 && _jsxs("b", { children: [summary.failed, " failed"] }), summary.interrupted > 0 && _jsxs("span", { children: [summary.interrupted, " interrupted"] }), summary.cancelled > 0 && _jsxs("span", { children: [summary.cancelled, " cancelled"] }), running && _jsxs("span", { className: "tr-activity__running", children: [_jsx("i", { "aria-hidden": "true" }), "Running"] }), range !== null && _jsx("span", { className: "t", children: range })] })] }), _jsx(Attachments, { facts: images, onOpenAttachment: openImage, reveal: reveal }), _jsx(ActivityImagesContext.Provider, { value: openImage, children: _jsxs("div", { id: listId, className: "tr-activity__list", hidden: !expanded, children: [partial && _jsx("div", { className: "tr-activity__loading", role: "status", children: earlier.kind === "fetching" || earlier.kind === "revealing" ? "Loading earlier activity…" : _jsxs(_Fragment, { children: [_jsx("span", { children: earlier.kind === "failed" ? "Could not load earlier activity." : "This activity continues in earlier history." }), _jsx("button", { type: "button", onClick: onLoadEarlier, children: earlier.kind === "failed" ? "Retry" : "Load more activity" })] }) }), (teamLoading || teamError !== null) && _jsxs("div", { className: "tr-activity__loading", role: "status", children: [_jsx("span", { children: teamError !== null ? "Could not load team messages." : "Loading team messages…" }), teamError !== null && _jsx("button", { type: "button", onClick: onRetryTeam, children: "Retry" })] }), children] }) })] });
|
|
61
|
+
}
|
|
62
|
+
/** The second disclosure is independent of the room's outer fold preference. */
|
|
63
|
+
export function ActivityEvent({ domId, timelineId, label, summary, at, glyph, hit = false, forceOpen = false, failed = false, children }) {
|
|
64
|
+
const [open, setOpen] = useRowDisclosure(domId);
|
|
65
|
+
useEffect(() => { if (hit || forceOpen)
|
|
66
|
+
setOpen(true); }, [hit, forceOpen, setOpen]);
|
|
67
|
+
return _jsxs("details", { className: "tr-activity-event", id: domId, "data-timeline-id": timelineId, "data-hit": hit || undefined, "data-failed": failed || undefined, open: open || forceOpen, onToggle: event => setOpen(event.currentTarget.open), children: [_jsxs("summary", { children: [_jsx("span", { className: "tr-activity-event__glyph", children: glyph ?? _jsx(FamilyGlyph, { glyph: "doc" }) }), _jsxs("span", { className: "tr-activity-event__label", children: [_jsx("strong", { children: label }), summary !== undefined && _jsx("span", { children: summary })] }), at != null && _jsx("time", { children: clock(at) }), _jsx("span", { className: "chev", children: _jsx(LineChevron, {}) })] }), (open || forceOpen) && _jsx("div", { className: "tr-activity-event__body", children: children })] });
|
|
68
|
+
}
|
|
69
|
+
export function ContextEvent({ segment, domId, hit, forceOpen, onReceipt }) {
|
|
70
|
+
const summary = segment.kind === "summary";
|
|
71
|
+
const label = summary ? `${segment.purpose} summary` : "Session notice";
|
|
72
|
+
const canonical = segment.row.body.type === "summary" ? segment.row.body.text
|
|
73
|
+
: segment.kind === "marker" && segment.notice.type === "command_output" ? segment.notice.output : null;
|
|
74
|
+
return _jsx(ActivityEvent, { domId: domId, timelineId: segment.row.timeline_id, label: label, summary: summary ? undefined : segment.text, at: segment.at, hit: hit, forceOpen: forceOpen, failed: segment.kind === "marker" && segment.tone === "failed", children: _jsx("div", { className: "tr-prose", children: _jsx(MessagePreview, { messageKey: domId, role: "agent", title: label, matched: hit, text: segment.text, recordedText: canonical, onReceipt: onReceipt }) }) });
|
|
75
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface AnchorProps {
|
|
2
|
+
readonly at: number | null;
|
|
3
|
+
/** `<session>#<timeline_id>` — what the clipboard gets. */
|
|
4
|
+
readonly sessionId: string;
|
|
5
|
+
readonly timelineId: string;
|
|
6
|
+
readonly onReceipt: (text: string) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function Anchor({ at, sessionId, timelineId, onReceipt }: AnchorProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// T-4 — every segment stays pointable-at.
|
|
3
|
+
//
|
|
4
|
+
// Decided Q3 A: the time lives in the MARGIN when the column has one, and is
|
|
5
|
+
// revealed on hover when it does not. That is a container query, not a media
|
|
6
|
+
// query, because the margin appears and disappears with the rail, not with the
|
|
7
|
+
// window — `.tr-col` is the container and `transcript.css` holds the rule.
|
|
8
|
+
//
|
|
9
|
+
// The anchor keeps its precision: a pill says the day and the minute of
|
|
10
|
+
// resumption, the anchor keeps HH:MM:SS. Time decomposes — day, sitting,
|
|
11
|
+
// second — and no level repeats another's fact.
|
|
12
|
+
import { clock } from "../../lib/format.js";
|
|
13
|
+
export function Anchor({ at, sessionId, timelineId, onReceipt }) {
|
|
14
|
+
if (at === null)
|
|
15
|
+
return null;
|
|
16
|
+
const stamp = clock(at);
|
|
17
|
+
return (_jsx("button", { type: "button", className: "tr-anchor", title: "Copy an anchor to this message", onClick: () => {
|
|
18
|
+
navigator.clipboard
|
|
19
|
+
.writeText(`${sessionId}#${timelineId}`)
|
|
20
|
+
.then(() => onReceipt(`Anchor copied · ${stamp}`))
|
|
21
|
+
.catch(() => onReceipt("The clipboard refused the anchor"));
|
|
22
|
+
}, children: stamp }));
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AttachmentFact } from "../../lib/transcript/index.js";
|
|
2
|
+
import type { AttachmentOpener } from "./attachment-gallery.js";
|
|
3
|
+
import "../../styles/attachments.css";
|
|
4
|
+
/** Explicitly shared only by messages whose images are gathered in this run. */
|
|
5
|
+
export declare const ActivityImagesContext: import("react").Context<AttachmentOpener | null>;
|
|
6
|
+
export declare function useAttachmentActions(onOpenAttachment: AttachmentOpener, onReceipt: (text: string) => void, facts: readonly AttachmentFact[]): {
|
|
7
|
+
open: AttachmentOpener;
|
|
8
|
+
reveal: {
|
|
9
|
+
key: string;
|
|
10
|
+
request: number;
|
|
11
|
+
} | null;
|
|
12
|
+
};
|
|
13
|
+
export declare function attachmentStatus(fact: AttachmentFact): string;
|
|
14
|
+
export declare function Attachments({ facts, onOpenAttachment, reveal }: {
|
|
15
|
+
readonly facts: readonly AttachmentFact[];
|
|
16
|
+
readonly reveal?: {
|
|
17
|
+
readonly key: string;
|
|
18
|
+
readonly request: number;
|
|
19
|
+
} | null;
|
|
20
|
+
readonly onOpenAttachment: AttachmentOpener;
|
|
21
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useId, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { fmtBytes } from "../../lib/format.js";
|
|
4
|
+
import { useAttachmentReader } from "./attachment-file-source.js";
|
|
5
|
+
import { useImageUrl, useImageVisibility } from "../file-viewer/image-resource.js";
|
|
6
|
+
import "../../styles/attachments.css";
|
|
7
|
+
/** Explicitly shared only by messages whose images are gathered in this run. */
|
|
8
|
+
export const ActivityImagesContext = createContext(null);
|
|
9
|
+
export function useAttachmentActions(onOpenAttachment, onReceipt, facts) {
|
|
10
|
+
const [reveal, setReveal] = useState(null);
|
|
11
|
+
const attachments = useAttachmentReader();
|
|
12
|
+
const open = (fact, row = facts) => {
|
|
13
|
+
if (attachments.canOpen(fact))
|
|
14
|
+
onOpenAttachment(fact, row);
|
|
15
|
+
else {
|
|
16
|
+
setReveal(old => ({ key: fact.key, request: (old?.request ?? 0) + 1 }));
|
|
17
|
+
onReceipt(attachmentStatus(fact));
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
return { open, reveal };
|
|
21
|
+
}
|
|
22
|
+
export function attachmentStatus(fact) {
|
|
23
|
+
const source = fact.ref?.source;
|
|
24
|
+
if (source?.type === "unavailable") {
|
|
25
|
+
switch (source.reason) {
|
|
26
|
+
case "omitted": return "Image omitted from the recording";
|
|
27
|
+
case "invalid_data": return "Recorded image data is invalid";
|
|
28
|
+
case "unsupported_media": return "Image format is not supported";
|
|
29
|
+
case "not_recorded": return "Image data was not recorded";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return "Image data was not recorded";
|
|
33
|
+
}
|
|
34
|
+
function Tile({ fact, index, onOpen }) {
|
|
35
|
+
const [failedUrl, setFailedUrl] = useState(null);
|
|
36
|
+
const [attempt, setAttempt] = useState(0);
|
|
37
|
+
const figureRef = useRef(null);
|
|
38
|
+
const openRef = useRef(null);
|
|
39
|
+
const retryRef = useRef(null);
|
|
40
|
+
const restoreFocus = useRef(false);
|
|
41
|
+
const visible = useImageVisibility(figureRef);
|
|
42
|
+
const attachments = useAttachmentReader();
|
|
43
|
+
const resource = fact.kind === "image" ? attachments.imageResource(fact) : null;
|
|
44
|
+
const { url, pending } = useImageUrl(resource, window.devicePixelRatio > 2 ? "tile2x" : "tile", attempt);
|
|
45
|
+
// A source that could not address a recorded image failed to load it, the
|
|
46
|
+
// same fact to a reader as a broken response, and Retry asks it again.
|
|
47
|
+
const refused = resource !== null && !pending && url === null;
|
|
48
|
+
const failed = refused || (url !== null && failedUrl === url);
|
|
49
|
+
useLayoutEffect(() => {
|
|
50
|
+
if (!restoreFocus.current)
|
|
51
|
+
return;
|
|
52
|
+
(failed ? retryRef.current : openRef.current)?.focus({ preventScroll: true });
|
|
53
|
+
restoreFocus.current = false;
|
|
54
|
+
}, [attempt, failed]);
|
|
55
|
+
const label = fact.ref?.label ?? `${fact.kind === "image" ? "Image" : "Attachment"} ${index + 1}`;
|
|
56
|
+
const details = [label, fact.name, fact.caption, fact.byteLen === null ? null : fmtBytes(fact.byteLen)].filter(Boolean).join(" · ");
|
|
57
|
+
return _jsx("figure", { ref: figureRef, "data-attachment-id": fact.key, title: details, children: resource === null || failed ? (_jsxs("div", { className: "tr-att__tile tr-att__tile--missing", tabIndex: -1, "aria-label": details, children: [_jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.3", "aria-hidden": "true", children: [_jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "4" }), _jsx("circle", { cx: "8", cy: "8", r: "1.5" }), _jsx("path", { d: "m3 17 5-5 4 4 3-3 6 6" })] }), _jsx("span", { children: failed ? "Could not load image" : fact.kind === "image" ? attachmentStatus(fact) : "Attachment preview unavailable" }), failed && _jsx("button", { type: "button", ref: retryRef, className: "tr-att__retry", onClick: () => {
|
|
58
|
+
restoreFocus.current = true;
|
|
59
|
+
setAttempt(value => value + 1);
|
|
60
|
+
setFailedUrl(null);
|
|
61
|
+
}, children: "Retry" })] })) : (_jsx("button", { type: "button", ref: openRef, className: "tr-att__tile", "aria-label": `Open ${label}${fact.name ? ` — ${fact.name}` : ""}`, "aria-haspopup": "dialog", onClick: () => onOpen(fact), children: visible && url !== null && _jsx("img", { src: url, alt: fact.name ?? label, decoding: "async", onError: () => {
|
|
62
|
+
restoreFocus.current = Boolean(figureRef.current?.contains(document.activeElement));
|
|
63
|
+
setFailedUrl(url);
|
|
64
|
+
} }, url) })) });
|
|
65
|
+
}
|
|
66
|
+
/** Scroll just this rail: image navigation must never move the room or transcript. */
|
|
67
|
+
function revealTile(rail, tile) {
|
|
68
|
+
const bounds = rail.getBoundingClientRect();
|
|
69
|
+
const target = tile.getBoundingClientRect();
|
|
70
|
+
if (target.left < bounds.left + 4)
|
|
71
|
+
rail.scrollLeft += target.left - bounds.left - 4;
|
|
72
|
+
else if (target.right > bounds.right - 4)
|
|
73
|
+
rail.scrollLeft += target.right - bounds.right + 4;
|
|
74
|
+
}
|
|
75
|
+
export function Attachments({ facts, onOpenAttachment, reveal }) {
|
|
76
|
+
const id = useId();
|
|
77
|
+
const attachments = useAttachmentReader();
|
|
78
|
+
const gallery = useRef(null);
|
|
79
|
+
const handledReveal = useRef(undefined);
|
|
80
|
+
const [edges, setEdges] = useState({ before: false, after: false });
|
|
81
|
+
useLayoutEffect(() => {
|
|
82
|
+
const rail = gallery.current;
|
|
83
|
+
if (!rail)
|
|
84
|
+
return;
|
|
85
|
+
const update = () => {
|
|
86
|
+
const before = rail.scrollLeft > 2;
|
|
87
|
+
const after = rail.scrollLeft + rail.clientWidth < rail.scrollWidth - 2;
|
|
88
|
+
setEdges(old => old.before === before && old.after === after ? old : { before, after });
|
|
89
|
+
};
|
|
90
|
+
update();
|
|
91
|
+
const observer = new ResizeObserver(update);
|
|
92
|
+
observer.observe(rail);
|
|
93
|
+
rail.addEventListener("scroll", update, { passive: true });
|
|
94
|
+
return () => { observer.disconnect(); rail.removeEventListener("scroll", update); };
|
|
95
|
+
}, [facts.length]);
|
|
96
|
+
useLayoutEffect(() => {
|
|
97
|
+
if (!reveal || handledReveal.current === reveal || !gallery.current)
|
|
98
|
+
return;
|
|
99
|
+
const figure = [...gallery.current.querySelectorAll("[data-attachment-id]")]
|
|
100
|
+
.find(element => element.dataset.attachmentId === reveal.key);
|
|
101
|
+
const target = figure?.querySelector(".tr-att__tile");
|
|
102
|
+
if (!target)
|
|
103
|
+
return;
|
|
104
|
+
target.focus({ preventScroll: true });
|
|
105
|
+
revealTile(gallery.current, target);
|
|
106
|
+
// A missing-image mention may sit far above its strip in the full message.
|
|
107
|
+
// Keep vertical scrolling inside the nearest reading surface.
|
|
108
|
+
const scroller = gallery.current.closest(".ah-fileview__scroll, .tr-scroll");
|
|
109
|
+
if (scroller) {
|
|
110
|
+
const bounds = scroller.getBoundingClientRect();
|
|
111
|
+
const rect = target.getBoundingClientRect();
|
|
112
|
+
if (rect.top < bounds.top)
|
|
113
|
+
scroller.scrollTop += rect.top - bounds.top - 12;
|
|
114
|
+
else if (rect.bottom > bounds.bottom)
|
|
115
|
+
scroller.scrollTop += rect.bottom - bounds.bottom + 12;
|
|
116
|
+
}
|
|
117
|
+
handledReveal.current = reveal;
|
|
118
|
+
}, [reveal, facts]);
|
|
119
|
+
if (facts.length === 0)
|
|
120
|
+
return null;
|
|
121
|
+
const page = (direction) => gallery.current?.scrollBy({ left: direction * gallery.current.clientWidth * 0.8,
|
|
122
|
+
behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "instant" : "smooth" });
|
|
123
|
+
return _jsxs("div", { className: "tr-attachments", role: "group", "aria-label": `${facts.length} ${facts.length === 1 ? "attachment" : "attachments"}`, children: [_jsx("div", { className: "tr-att", id: id, ref: gallery, onFocusCapture: event => {
|
|
124
|
+
const tile = event.target.closest(".tr-att__tile");
|
|
125
|
+
if (tile && gallery.current)
|
|
126
|
+
revealTile(gallery.current, tile);
|
|
127
|
+
}, onKeyDown: event => {
|
|
128
|
+
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key) || !gallery.current)
|
|
129
|
+
return;
|
|
130
|
+
const tiles = [...gallery.current.querySelectorAll(".tr-att__tile")];
|
|
131
|
+
const index = tiles.findIndex(tile => tile.contains(event.target));
|
|
132
|
+
if (index < 0)
|
|
133
|
+
return;
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
const next = event.key === "Home" ? 0 : event.key === "End" ? tiles.length - 1 : Math.max(0, Math.min(tiles.length - 1, index + (event.key === "ArrowRight" ? 1 : -1)));
|
|
136
|
+
tiles[next]?.focus({ preventScroll: true });
|
|
137
|
+
}, children: facts.map((fact, index) => _jsx(Tile, { fact: fact, index: index, onOpen: selected => onOpenAttachment(selected, facts) }, `${fact.key}:${attachments.assetUrl(fact) ?? "reference"}`)) }), (edges.before || edges.after) && _jsx("button", { className: "tr-att__nav tr-att__nav--previous", type: "button", "aria-label": "Previous images", "aria-disabled": !edges.before, "data-edge": !edges.before || undefined, "aria-controls": id, onClick: () => page(-1), children: "\u2039" }), (edges.before || edges.after) && _jsx("button", { className: "tr-att__nav tr-att__nav--next", type: "button", "aria-label": "Next images", "aria-disabled": !edges.after, "data-edge": !edges.after || undefined, "aria-controls": id, onClick: () => page(1), children: "\u203A" })] });
|
|
138
|
+
}
|