@stigmer/react 3.7.0 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/attachment/AttachmentChipList.d.ts.map +1 -1
- package/attachment/AttachmentChipList.js +30 -1
- package/attachment/AttachmentChipList.js.map +1 -1
- package/attachment/attachment-utils.d.ts +11 -0
- package/attachment/attachment-utils.d.ts.map +1 -1
- package/attachment/attachment-utils.js +23 -0
- package/attachment/attachment-utils.js.map +1 -1
- package/attachment/clipboard.d.ts +51 -0
- package/attachment/clipboard.d.ts.map +1 -0
- package/attachment/clipboard.js +89 -0
- package/attachment/clipboard.js.map +1 -0
- package/attachment/index.d.ts +6 -1
- package/attachment/index.d.ts.map +1 -1
- package/attachment/index.js +4 -1
- package/attachment/index.js.map +1 -1
- package/attachment/prepare-image.d.ts +43 -0
- package/attachment/prepare-image.d.ts.map +1 -0
- package/attachment/prepare-image.js +162 -0
- package/attachment/prepare-image.js.map +1 -0
- package/attachment/useAttachments.d.ts.map +1 -1
- package/attachment/useAttachments.js +23 -3
- package/attachment/useAttachments.js.map +1 -1
- package/attachment/vision-fit.d.ts +50 -0
- package/attachment/vision-fit.d.ts.map +1 -0
- package/attachment/vision-fit.js +76 -0
- package/attachment/vision-fit.js.map +1 -0
- package/channel/AgentChannelsPanel.d.ts.map +1 -1
- package/channel/AgentChannelsPanel.js +21 -2
- package/channel/AgentChannelsPanel.js.map +1 -1
- package/channel/ChannelConversationsDialog.d.ts +12 -4
- package/channel/ChannelConversationsDialog.d.ts.map +1 -1
- package/channel/ChannelConversationsDialog.js +18 -24
- package/channel/ChannelConversationsDialog.js.map +1 -1
- package/channel/providers.d.ts +23 -0
- package/channel/providers.d.ts.map +1 -1
- package/channel/providers.js +4 -0
- package/channel/providers.js.map +1 -1
- package/channel/useSaveAgentChannel.d.ts +5 -4
- package/channel/useSaveAgentChannel.d.ts.map +1 -1
- package/channel/useSaveAgentChannel.js +18 -4
- package/channel/useSaveAgentChannel.js.map +1 -1
- package/composer/ComposerToolbar.d.ts +1 -1
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/SessionComposer.d.ts +4 -2
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +51 -5
- package/composer/SessionComposer.js.map +1 -1
- package/conversation/ConversationAttentionBanner.d.ts +22 -0
- package/conversation/ConversationAttentionBanner.d.ts.map +1 -0
- package/conversation/ConversationAttentionBanner.js +25 -0
- package/conversation/ConversationAttentionBanner.js.map +1 -0
- package/conversation/ConversationComposer.d.ts +41 -0
- package/conversation/ConversationComposer.d.ts.map +1 -0
- package/conversation/ConversationComposer.js +57 -0
- package/conversation/ConversationComposer.js.map +1 -0
- package/conversation/ConversationControlBanner.d.ts +52 -0
- package/conversation/ConversationControlBanner.d.ts.map +1 -0
- package/conversation/ConversationControlBanner.js +59 -0
- package/conversation/ConversationControlBanner.js.map +1 -0
- package/conversation/ConversationListPane.d.ts +68 -0
- package/conversation/ConversationListPane.d.ts.map +1 -0
- package/conversation/ConversationListPane.js +120 -0
- package/conversation/ConversationListPane.js.map +1 -0
- package/conversation/ConversationTimelineView.d.ts +46 -0
- package/conversation/ConversationTimelineView.d.ts.map +1 -0
- package/conversation/ConversationTimelineView.js +166 -0
- package/conversation/ConversationTimelineView.js.map +1 -0
- package/conversation/ConversationsWorkbench.d.ts +63 -0
- package/conversation/ConversationsWorkbench.d.ts.map +1 -0
- package/conversation/ConversationsWorkbench.js +165 -0
- package/conversation/ConversationsWorkbench.js.map +1 -0
- package/conversation/conversationPresentation.d.ts +137 -0
- package/conversation/conversationPresentation.d.ts.map +1 -0
- package/conversation/conversationPresentation.js +230 -0
- package/conversation/conversationPresentation.js.map +1 -0
- package/conversation/index.d.ts +26 -0
- package/conversation/index.d.ts.map +1 -0
- package/conversation/index.js +14 -0
- package/conversation/index.js.map +1 -0
- package/conversation/polling.d.ts +39 -0
- package/conversation/polling.d.ts.map +1 -0
- package/conversation/polling.js +39 -0
- package/conversation/polling.js.map +1 -0
- package/conversation/useConversation.d.ts +61 -0
- package/conversation/useConversation.d.ts.map +1 -0
- package/conversation/useConversation.js +153 -0
- package/conversation/useConversation.js.map +1 -0
- package/conversation/useConversationList.d.ts +98 -0
- package/conversation/useConversationList.d.ts.map +1 -0
- package/conversation/useConversationList.js +200 -0
- package/conversation/useConversationList.js.map +1 -0
- package/conversation/useConversationParticipation.d.ts +63 -0
- package/conversation/useConversationParticipation.d.ts.map +1 -0
- package/conversation/useConversationParticipation.js +84 -0
- package/conversation/useConversationParticipation.js.map +1 -0
- package/conversation/useConversationTimeline.d.ts +71 -0
- package/conversation/useConversationTimeline.d.ts.map +1 -0
- package/conversation/useConversationTimeline.js +152 -0
- package/conversation/useConversationTimeline.js.map +1 -0
- package/conversation/useConversationsWantsHumanCount.d.ts +42 -0
- package/conversation/useConversationsWantsHumanCount.d.ts.map +1 -0
- package/conversation/useConversationsWantsHumanCount.js +40 -0
- package/conversation/useConversationsWantsHumanCount.js.map +1 -0
- package/index.d.ts +6 -2
- package/index.d.ts.map +1 -1
- package/index.js +11 -2
- package/index.js.map +1 -1
- package/internal/tooltip.d.ts.map +1 -1
- package/internal/tooltip.js +7 -1
- package/internal/tooltip.js.map +1 -1
- package/internal/useAutoScroll.d.ts +25 -4
- package/internal/useAutoScroll.d.ts.map +1 -1
- package/internal/useAutoScroll.js +55 -15
- package/internal/useAutoScroll.js.map +1 -1
- package/internal/useFetch.d.ts.map +1 -1
- package/internal/useFetch.js +14 -4
- package/internal/useFetch.js.map +1 -1
- package/models/ModelSelector.d.ts +24 -5
- package/models/ModelSelector.d.ts.map +1 -1
- package/models/ModelSelector.js +31 -9
- package/models/ModelSelector.js.map +1 -1
- package/models/registry.d.ts +3 -2
- package/models/registry.d.ts.map +1 -1
- package/models/registry.js.map +1 -1
- package/models/service-tier.d.ts +1 -1
- package/models/service-tier.d.ts.map +1 -1
- package/models/service-tier.js +1 -1
- package/models/service-tier.js.map +1 -1
- package/package.json +4 -4
- package/schedule/ScheduleDetailView.js +1 -1
- package/schedule/ScheduleDetailView.js.map +1 -1
- package/schedule/ScheduleForm.d.ts.map +1 -1
- package/schedule/ScheduleForm.js +7 -6
- package/schedule/ScheduleForm.js.map +1 -1
- package/sharing/useSaveAgentShare.d.ts +10 -1
- package/sharing/useSaveAgentShare.d.ts.map +1 -1
- package/sharing/useSaveAgentShare.js +9 -0
- package/sharing/useSaveAgentShare.js.map +1 -1
- package/sidebar/WorkspaceSidebar.d.ts +12 -4
- package/sidebar/WorkspaceSidebar.d.ts.map +1 -1
- package/sidebar/WorkspaceSidebar.js +19 -7
- package/sidebar/WorkspaceSidebar.js.map +1 -1
- package/src/attachment/AttachmentChipList.tsx +45 -1
- package/src/attachment/__tests__/AttachmentChipList.test.tsx +103 -0
- package/src/attachment/__tests__/attachment-utils.test.ts +54 -0
- package/src/attachment/__tests__/clipboard.test.ts +110 -0
- package/src/attachment/__tests__/prepare-image.browser.test.ts +166 -0
- package/src/attachment/__tests__/prepare-image.test.ts +32 -0
- package/src/attachment/__tests__/vision-fit.test.ts +93 -0
- package/src/attachment/attachment-utils.ts +27 -0
- package/src/attachment/clipboard.ts +102 -0
- package/src/attachment/index.ts +13 -0
- package/src/attachment/prepare-image.ts +179 -0
- package/src/attachment/useAttachments.ts +26 -2
- package/src/attachment/vision-fit.ts +90 -0
- package/src/channel/AgentChannelsPanel.tsx +33 -2
- package/src/channel/ChannelConversationsDialog.tsx +22 -25
- package/src/channel/__tests__/AgentChannelsPanel.test.tsx +36 -5
- package/src/channel/__tests__/ChannelConversationsDialog.test.tsx +38 -1
- package/src/channel/__tests__/useSaveAgentChannel.test.tsx +26 -0
- package/src/channel/providers.ts +27 -0
- package/src/channel/useSaveAgentChannel.ts +18 -4
- package/src/composer/ComposerToolbar.tsx +1 -1
- package/src/composer/SessionComposer.tsx +75 -6
- package/src/composer/__tests__/SessionComposer-paste.test.tsx +263 -0
- package/src/composer/__tests__/SessionComposer-serviceTier.test.tsx +4 -3
- package/src/composer/__tests__/SessionComposer-uploadGate.test.tsx +239 -0
- package/src/conversation/ConversationAttentionBanner.tsx +77 -0
- package/src/conversation/ConversationComposer.tsx +156 -0
- package/src/conversation/ConversationControlBanner.tsx +193 -0
- package/src/conversation/ConversationListPane.tsx +459 -0
- package/src/conversation/ConversationTimelineView.tsx +435 -0
- package/src/conversation/ConversationsWorkbench.tsx +378 -0
- package/src/conversation/__tests__/ConversationAttentionBanner.test.tsx +82 -0
- package/src/conversation/__tests__/ConversationComposer.test.tsx +160 -0
- package/src/conversation/__tests__/ConversationControlBanner.test.tsx +190 -0
- package/src/conversation/__tests__/ConversationListPane.test.tsx +372 -0
- package/src/conversation/__tests__/ConversationTimelineView.layout.test.tsx +160 -0
- package/src/conversation/__tests__/ConversationTimelineView.test.tsx +405 -0
- package/src/conversation/__tests__/ConversationsWorkbench.test.tsx +860 -0
- package/src/conversation/__tests__/a11y/conversation-surfaces.a11y.test.tsx +235 -0
- package/src/conversation/__tests__/a11y/harness.tsx +100 -0
- package/src/conversation/__tests__/conversation-tooltips.layout.test.tsx +167 -0
- package/src/conversation/__tests__/conversationPollingComposition.test.tsx +138 -0
- package/src/conversation/__tests__/conversationPresentation.test.ts +289 -0
- package/src/conversation/__tests__/useConversation.test.tsx +243 -0
- package/src/conversation/__tests__/useConversationList.test.tsx +492 -0
- package/src/conversation/__tests__/useConversationParticipation.test.tsx +190 -0
- package/src/conversation/__tests__/useConversationTimeline.test.tsx +258 -0
- package/src/conversation/__tests__/useConversationsWantsHumanCount.test.tsx +102 -0
- package/src/conversation/conversationPresentation.ts +305 -0
- package/src/conversation/index.ts +80 -0
- package/src/conversation/polling.ts +41 -0
- package/src/conversation/useConversation.ts +221 -0
- package/src/conversation/useConversationList.ts +328 -0
- package/src/conversation/useConversationParticipation.ts +178 -0
- package/src/conversation/useConversationTimeline.ts +244 -0
- package/src/conversation/useConversationsWantsHumanCount.ts +86 -0
- package/src/index.ts +78 -1
- package/src/internal/__tests__/fake-timer-slices.ts +29 -0
- package/src/internal/__tests__/useAutoScroll.layout.test.tsx +143 -0
- package/src/internal/__tests__/useAutoScroll.test.tsx +62 -5
- package/src/internal/__tests__/useFetch-poll-under-render-pressure.test.tsx +79 -0
- package/src/internal/tooltip.tsx +6 -1
- package/src/internal/useAutoScroll.ts +66 -20
- package/src/internal/useFetch.ts +15 -4
- package/src/models/ModelSelector.tsx +81 -49
- package/src/models/__tests__/ModelSelector-serviceTier.test.tsx +105 -21
- package/src/models/registry.ts +3 -2
- package/src/models/service-tier.ts +1 -1
- package/src/schedule/ScheduleDetailView.tsx +1 -1
- package/src/schedule/ScheduleForm.tsx +7 -6
- package/src/schedule/__tests__/scheduleCreation.test.tsx +2 -2
- package/src/sharing/__tests__/useSaveAgentShare.test.tsx +55 -1
- package/src/sharing/__tests__/useShareToolReadiness.test.tsx +1 -0
- package/src/sharing/useSaveAgentShare.ts +19 -1
- package/src/sidebar/WorkspaceSidebar.tsx +62 -3
- package/src/sidebar/__tests__/WorkspaceSidebar.test.tsx +56 -1
- package/styles.css +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSaveAgentShare.js","sourceRoot":"","sources":["../../src/sharing/useSaveAgentShare.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,0DAA0D,CAAC;AAE9F,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"useSaveAgentShare.js","sourceRoot":"","sources":["../../src/sharing/useSaveAgentShare.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import type { Organization } from "@stigmer/protos/ai/stigmer/tenancy/organizati
|
|
|
3
3
|
import type { RecentActivityEntry } from "../activity/types.js";
|
|
4
4
|
import type { RenderSidebarLink } from "./types.js";
|
|
5
5
|
/** Primary navigation destinations in the workspace sidebar. */
|
|
6
|
-
export type WorkspaceNavId = "new-session" | "dashboard" | "library";
|
|
6
|
+
export type WorkspaceNavId = "new-session" | "dashboard" | "conversations" | "library";
|
|
7
7
|
/**
|
|
8
8
|
* Recent-activity data for the sidebar's Recents section — the return
|
|
9
9
|
* shape of `useRecentActivity`, narrowed to what the sidebar renders.
|
|
@@ -40,6 +40,14 @@ export interface WorkspaceSidebarProps {
|
|
|
40
40
|
* timestamp column, inside the row.
|
|
41
41
|
*/
|
|
42
42
|
readonly renderEntryAccessory?: (entry: RecentActivityEntry) => ReactNode;
|
|
43
|
+
/**
|
|
44
|
+
* Count for the Conversations row's badge: how many conversations
|
|
45
|
+
* want a human right now (`useConversationsWantsHumanCount`). Data as
|
|
46
|
+
* props per the chrome contract — hosts fetch, deterministic hosts
|
|
47
|
+
* (tours) pass a fixture or omit. Hidden at `0`/omitted; the display
|
|
48
|
+
* caps at "99+" while the accessible name keeps the real number.
|
|
49
|
+
*/
|
|
50
|
+
readonly conversationsBadgeCount?: number;
|
|
43
51
|
/** Footer content — typically the host app's `UserMenu` wrapper. */
|
|
44
52
|
readonly footer: ReactNode;
|
|
45
53
|
/** Reflected as `aria-expanded` on the collapse toggle. @default true */
|
|
@@ -58,8 +66,8 @@ export interface WorkspaceSidebarProps {
|
|
|
58
66
|
}
|
|
59
67
|
/**
|
|
60
68
|
* The console's workspace-zone sidebar: org switcher, primary navigation
|
|
61
|
-
* (New Session / Dashboard / Library), time-bucketed
|
|
62
|
-
* and a user footer.
|
|
69
|
+
* (New Session / Dashboard / Conversations / Library), time-bucketed
|
|
70
|
+
* recent activity, and a user footer.
|
|
63
71
|
*
|
|
64
72
|
* This is the same component the Stigmer web console and desktop app
|
|
65
73
|
* render — hosts differ only in what they return from `renderLink`
|
|
@@ -83,5 +91,5 @@ export interface WorkspaceSidebarProps {
|
|
|
83
91
|
* />
|
|
84
92
|
* ```
|
|
85
93
|
*/
|
|
86
|
-
export declare function WorkspaceSidebar({ activeNav, renderLink, recentActivity, activeSessionId, activeExecutionId, renderEntryAccessory, footer, isOpen, onCollapse, onOrgChanged, now, }: WorkspaceSidebarProps): import("react/jsx-runtime").JSX.Element;
|
|
94
|
+
export declare function WorkspaceSidebar({ activeNav, renderLink, recentActivity, activeSessionId, activeExecutionId, renderEntryAccessory, conversationsBadgeCount, footer, isOpen, onCollapse, onOrgChanged, now, }: WorkspaceSidebarProps): import("react/jsx-runtime").JSX.Element;
|
|
87
95
|
//# sourceMappingURL=WorkspaceSidebar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceSidebar.d.ts","sourceRoot":"","sources":["../../src/sidebar/WorkspaceSidebar.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"WorkspaceSidebar.d.ts","sourceRoot":"","sources":["../../src/sidebar/WorkspaceSidebar.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAUtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2DAA2D,CAAC;AAC9F,OAAO,KAAK,EAAE,mBAAmB,EAAuB,MAAM,sBAAsB,CAAC;AAYrF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAMpD,gEAAgE;AAChE,MAAM,MAAM,cAAc,GACtB,aAAa,GACb,WAAW,GACX,eAAe,GACf,SAAS,CAAC;AAEd;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACjD,gEAAgE;IAChE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACtD;AAED,0CAA0C;AAC1C,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAC3C,mEAAmE;IACnE,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IACvC,gDAAgD;IAChD,QAAQ,CAAC,cAAc,EAAE,wBAAwB,CAAC;IAClD,+DAA+D;IAC/D,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,oEAAoE;IACpE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,SAAS,CAAC;IAC1E;;;;;;OAMG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAC1C,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,kDAAkD;IAClD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,oDAAoD;IACpD,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAC;IACpD;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,SAAgB,EAChB,UAAU,EACV,cAAc,EACd,eAAsB,EACtB,iBAAwB,EACxB,oBAAoB,EACpB,uBAAuB,EACvB,MAAM,EACN,MAAa,EACb,UAAU,EACV,YAAY,EACZ,GAAG,GACJ,EAAE,qBAAqB,2CAqFvB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { memo, useMemo } from "react";
|
|
4
|
-
import { Plus, LayoutDashboard, Library, MessageSquare, Workflow, } from "lucide-react";
|
|
4
|
+
import { Plus, LayoutDashboard, Library, MessageSquare, MessagesSquare, Workflow, } from "lucide-react";
|
|
5
5
|
import { groupRecentActivityByTime } from "../activity/group-activity.js";
|
|
6
6
|
import { formatRelativeTime } from "../activity/format-relative-time.js";
|
|
7
7
|
import { recentActivityStatusBadge } from "../activity/entry-status-badge.js";
|
|
@@ -10,8 +10,8 @@ import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent, } from "../in
|
|
|
10
10
|
import { SidebarChrome, SidebarSeparator, navRowClassName } from "./chrome.js";
|
|
11
11
|
/**
|
|
12
12
|
* The console's workspace-zone sidebar: org switcher, primary navigation
|
|
13
|
-
* (New Session / Dashboard / Library), time-bucketed
|
|
14
|
-
* and a user footer.
|
|
13
|
+
* (New Session / Dashboard / Conversations / Library), time-bucketed
|
|
14
|
+
* recent activity, and a user footer.
|
|
15
15
|
*
|
|
16
16
|
* This is the same component the Stigmer web console and desktop app
|
|
17
17
|
* render — hosts differ only in what they return from `renderLink`
|
|
@@ -35,24 +35,36 @@ import { SidebarChrome, SidebarSeparator, navRowClassName } from "./chrome.js";
|
|
|
35
35
|
* />
|
|
36
36
|
* ```
|
|
37
37
|
*/
|
|
38
|
-
export function WorkspaceSidebar({ activeNav = null, renderLink, recentActivity, activeSessionId = null, activeExecutionId = null, renderEntryAccessory, footer, isOpen = true, onCollapse, onOrgChanged, now, }) {
|
|
38
|
+
export function WorkspaceSidebar({ activeNav = null, renderLink, recentActivity, activeSessionId = null, activeExecutionId = null, renderEntryAccessory, conversationsBadgeCount, footer, isOpen = true, onCollapse, onOrgChanged, now, }) {
|
|
39
39
|
const { entries, isLoading = false, error = null } = recentActivity;
|
|
40
40
|
const groups = useMemo(() => groupRecentActivityByTime(entries, now), [entries, now]);
|
|
41
|
-
return (_jsxs(SidebarChrome, { ariaLabel: "Main navigation", isOpen: isOpen, onCollapse: onCollapse, onOrgChanged: onOrgChanged, footer: footer, children: [_jsx(PrimaryNavRow, { id: "new-session", href: "/", label: "New Session", icon: Plus, active: activeNav === "new-session", renderLink: renderLink }), _jsx(PrimaryNavRow, { id: "dashboard", href: "/dashboard", label: "Dashboard", icon: LayoutDashboard, active: activeNav === "dashboard", renderLink: renderLink }), _jsx(PrimaryNavRow, { id: "library", href: "/library", label: "Library", icon: Library, active: activeNav === "library", renderLink: renderLink }), _jsx(SidebarSeparator, {}), _jsx(ScrollArea, { className: "flex-1", children: _jsxs("div", { className: "p-3", children: [_jsx("p", { className: "text-sidebar-muted-foreground mb-2 px-1 text-[11px] font-semibold tracking-wider uppercase", children: "Recents" }), isLoading ? (_jsx(RecentsSkeletons, {})) : error ? (_jsx(RecentsError, { message: error.message })) : groups.length === 0 ? (_jsx(RecentsEmptyState, {})) : (_jsx(ActivityGroupList, { groups: groups, activeSessionId: activeSessionId, activeExecutionId: activeExecutionId, renderLink: renderLink, renderEntryAccessory: renderEntryAccessory, now: now }))] }) })] }));
|
|
41
|
+
return (_jsxs(SidebarChrome, { ariaLabel: "Main navigation", isOpen: isOpen, onCollapse: onCollapse, onOrgChanged: onOrgChanged, footer: footer, children: [_jsx(PrimaryNavRow, { id: "new-session", href: "/", label: "New Session", icon: Plus, active: activeNav === "new-session", renderLink: renderLink }), _jsx(PrimaryNavRow, { id: "dashboard", href: "/dashboard", label: "Dashboard", icon: LayoutDashboard, active: activeNav === "dashboard", renderLink: renderLink }), _jsx(PrimaryNavRow, { id: "conversations", href: "/conversations", label: "Conversations", icon: MessagesSquare, active: activeNav === "conversations", renderLink: renderLink, accessory: conversationsBadgeCount !== undefined && conversationsBadgeCount > 0 ? (_jsx(WantsHumanBadge, { count: conversationsBadgeCount })) : null }), _jsx(PrimaryNavRow, { id: "library", href: "/library", label: "Library", icon: Library, active: activeNav === "library", renderLink: renderLink }), _jsx(SidebarSeparator, {}), _jsx(ScrollArea, { className: "flex-1", children: _jsxs("div", { className: "p-3", children: [_jsx("p", { className: "text-sidebar-muted-foreground mb-2 px-1 text-[11px] font-semibold tracking-wider uppercase", children: "Recents" }), isLoading ? (_jsx(RecentsSkeletons, {})) : error ? (_jsx(RecentsError, { message: error.message })) : groups.length === 0 ? (_jsx(RecentsEmptyState, {})) : (_jsx(ActivityGroupList, { groups: groups, activeSessionId: activeSessionId, activeExecutionId: activeExecutionId, renderLink: renderLink, renderEntryAccessory: renderEntryAccessory, now: now }))] }) })] }));
|
|
42
42
|
}
|
|
43
43
|
// ---------------------------------------------------------------------------
|
|
44
44
|
// Internals
|
|
45
45
|
// ---------------------------------------------------------------------------
|
|
46
|
-
function PrimaryNavRow({ id, href, label, icon: Icon, active, renderLink, }) {
|
|
46
|
+
function PrimaryNavRow({ id, href, label, icon: Icon, active, renderLink, accessory = null, }) {
|
|
47
47
|
return (_jsx("div", { className: "flex-none px-3 py-1", children: renderLink({
|
|
48
48
|
id,
|
|
49
49
|
href,
|
|
50
50
|
active,
|
|
51
51
|
className: navRowClassName(active),
|
|
52
52
|
"aria-current": active ? "page" : undefined,
|
|
53
|
-
children: (_jsxs(_Fragment, { children: [_jsx(Icon, { className: "size-4 shrink-0" }), label] })),
|
|
53
|
+
children: (_jsxs(_Fragment, { children: [_jsx(Icon, { className: "size-4 shrink-0" }), label, accessory] })),
|
|
54
54
|
}) }));
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* The Conversations row's count pill: how many conversations want a
|
|
58
|
+
* human right now (DD-011 D-f — `needs_attention` OR awaiting-reply
|
|
59
|
+
* while human-held). The visible number caps at "99+"; the accessible
|
|
60
|
+
* name states the meaning with the real number, because a bare "104"
|
|
61
|
+
* read aloud after "Conversations" says nothing.
|
|
62
|
+
*/
|
|
63
|
+
function WantsHumanBadge({ count }) {
|
|
64
|
+
return (_jsxs("span", { className: "ml-auto flex shrink-0 items-center", children: [_jsx("span", { "aria-hidden": "true", className: "bg-sidebar-primary text-sidebar-primary-foreground rounded-full px-1.5 py-0.5 text-[10px] leading-none font-semibold tabular-nums", children: count > 99 ? "99+" : count }), _jsx("span", { className: "sr-only", children: count === 1
|
|
65
|
+
? "1 conversation needs a human"
|
|
66
|
+
: `${count} conversations need a human` })] }));
|
|
67
|
+
}
|
|
56
68
|
function ActivityGroupList({ groups, activeSessionId, activeExecutionId, renderLink, renderEntryAccessory, now, }) {
|
|
57
69
|
return (_jsx(TooltipProvider, { children: _jsx("div", { className: "space-y-4", children: groups.map((group) => (_jsxs("div", { children: [_jsx("p", { className: "text-sidebar-muted-foreground mb-1 px-2 text-[10px] font-medium tracking-wider uppercase", children: group.label }), _jsx("ul", { className: "space-y-0.5", role: "list", children: group.entries.map((entry) => (_jsx(ActivityEntry, { entry: entry, activeSessionId: activeSessionId, activeExecutionId: activeExecutionId, renderLink: renderLink, renderEntryAccessory: renderEntryAccessory, now: now }, entry.id))) })] }, group.label))) }) }));
|
|
58
70
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceSidebar.js","sourceRoot":"","sources":["../../src/sidebar/WorkspaceSidebar.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,IAAI,EAAE,OAAO,EAAkB,MAAM,OAAO,CAAC;AACtD,OAAO,EACL,IAAI,EACJ,eAAe,EACf,OAAO,EACP,aAAa,EACb,QAAQ,GAET,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EACL,OAAO,EACP,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"WorkspaceSidebar.js","sourceRoot":"","sources":["../../src/sidebar/WorkspaceSidebar.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
3
4
|
import { cn } from "@stigmer/theme";
|
|
4
5
|
import type { AttachmentEntry } from "./useAttachments.js";
|
|
5
6
|
import { formatFileSize } from "./attachment-utils.js";
|
|
@@ -103,7 +104,12 @@ function AttachmentChip({
|
|
|
103
104
|
>
|
|
104
105
|
{isUploading && <ChipSpinner />}
|
|
105
106
|
{isError && <ErrorDot />}
|
|
106
|
-
{!isUploading && !isError &&
|
|
107
|
+
{!isUploading && !isError &&
|
|
108
|
+
(entry.contentType.startsWith("image/") ? (
|
|
109
|
+
<ChipThumbnail file={entry.file} />
|
|
110
|
+
) : (
|
|
111
|
+
<FileIcon />
|
|
112
|
+
))}
|
|
107
113
|
|
|
108
114
|
<span className="truncate">{entry.file.name}</span>
|
|
109
115
|
|
|
@@ -136,6 +142,44 @@ function AttachmentChip({
|
|
|
136
142
|
);
|
|
137
143
|
}
|
|
138
144
|
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Image thumbnail — a #284 ask: a pasted screenshot's chip shows the image,
|
|
147
|
+
// not a generic file glyph
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Tiny preview of an image attachment, driven by an object URL over the
|
|
152
|
+
* locally-held `File` (no fetch — the bytes are already in memory).
|
|
153
|
+
*
|
|
154
|
+
* The URL is created in an effect, not a memo: StrictMode double-invokes
|
|
155
|
+
* memos and would leak the first URL without a revoke. The effect cleanup
|
|
156
|
+
* revokes on removal, unmount, and file change. Until the effect runs (or
|
|
157
|
+
* if it never gets a URL) the generic icon renders instead.
|
|
158
|
+
*
|
|
159
|
+
* `aria-hidden`: the chip's own `aria-label` already names the file; a
|
|
160
|
+
* 16px thumbnail adds nothing for screen readers.
|
|
161
|
+
*/
|
|
162
|
+
function ChipThumbnail({ file }: { readonly file: File }) {
|
|
163
|
+
const [url, setUrl] = useState<string | null>(null);
|
|
164
|
+
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
const objectUrl = URL.createObjectURL(file);
|
|
167
|
+
setUrl(objectUrl);
|
|
168
|
+
return () => URL.revokeObjectURL(objectUrl);
|
|
169
|
+
}, [file]);
|
|
170
|
+
|
|
171
|
+
if (!url) return <FileIcon />;
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<img
|
|
175
|
+
src={url}
|
|
176
|
+
alt=""
|
|
177
|
+
aria-hidden="true"
|
|
178
|
+
className="h-4 w-4 shrink-0 rounded-sm object-cover"
|
|
179
|
+
/>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
139
183
|
// ---------------------------------------------------------------------------
|
|
140
184
|
// Inline SVG icons — kept minimal and consistent with SessionComposer icons
|
|
141
185
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { render, cleanup, waitFor } from "@testing-library/react";
|
|
3
|
+
import { AttachmentChipList } from "../AttachmentChipList.js";
|
|
4
|
+
import type { AttachmentEntry } from "../useAttachments.js";
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
function entry(overrides: Partial<AttachmentEntry> = {}): AttachmentEntry {
|
|
11
|
+
return {
|
|
12
|
+
id: overrides.id ?? "entry-1",
|
|
13
|
+
file:
|
|
14
|
+
overrides.file ??
|
|
15
|
+
new File([new Uint8Array([0x89, 0x50, 0x4e, 0x47])], "shot.png", {
|
|
16
|
+
type: "image/png",
|
|
17
|
+
}),
|
|
18
|
+
phase: overrides.phase ?? "ready",
|
|
19
|
+
contentType: overrides.contentType ?? "image/png",
|
|
20
|
+
storageKey: overrides.storageKey ?? "attachments/test/shot.png",
|
|
21
|
+
error: overrides.error ?? null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function renderChips(entries: AttachmentEntry[]) {
|
|
26
|
+
return render(
|
|
27
|
+
<AttachmentChipList entries={entries} onRemove={vi.fn()} onRetry={vi.fn()} />,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
cleanup();
|
|
33
|
+
vi.restoreAllMocks();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Tests
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
describe("AttachmentChipList — image thumbnails", () => {
|
|
41
|
+
it("renders an object-URL thumbnail for a ready image entry", async () => {
|
|
42
|
+
const { container } = renderChips([entry()]);
|
|
43
|
+
|
|
44
|
+
await waitFor(() => {
|
|
45
|
+
const img = container.querySelector("img");
|
|
46
|
+
expect(img).toBeTruthy();
|
|
47
|
+
expect(img!.getAttribute("src")).toContain("blob:");
|
|
48
|
+
expect(img!.getAttribute("aria-hidden")).toBe("true");
|
|
49
|
+
expect(img!.getAttribute("alt")).toBe("");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("keeps the generic file icon for non-image entries", () => {
|
|
54
|
+
const md = entry({
|
|
55
|
+
file: new File(["# notes"], "notes.md", { type: "text/markdown" }),
|
|
56
|
+
contentType: "text/markdown",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const { container } = renderChips([md]);
|
|
60
|
+
|
|
61
|
+
expect(container.querySelector("img")).toBeNull();
|
|
62
|
+
expect(container.querySelector("svg")).toBeTruthy();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("shows no thumbnail while uploading or errored (spinner / error dot own the slot)", () => {
|
|
66
|
+
const uploading = entry({ id: "up", phase: "uploading", storageKey: null });
|
|
67
|
+
const errored = entry({ id: "err", phase: "error", error: "boom", storageKey: null });
|
|
68
|
+
|
|
69
|
+
const { container } = renderChips([uploading, errored]);
|
|
70
|
+
|
|
71
|
+
expect(container.querySelector("img")).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("revokes the object URL on unmount", async () => {
|
|
75
|
+
const revokeSpy = vi.spyOn(URL, "revokeObjectURL");
|
|
76
|
+
const { container, unmount } = renderChips([entry()]);
|
|
77
|
+
|
|
78
|
+
await waitFor(() => expect(container.querySelector("img")).toBeTruthy());
|
|
79
|
+
const src = container.querySelector("img")!.getAttribute("src")!;
|
|
80
|
+
|
|
81
|
+
unmount();
|
|
82
|
+
|
|
83
|
+
expect(revokeSpy).toHaveBeenCalledWith(src);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("revokes the object URL when the entry is removed from the list", async () => {
|
|
87
|
+
const revokeSpy = vi.spyOn(URL, "revokeObjectURL");
|
|
88
|
+
const image = entry();
|
|
89
|
+
const { container, rerender } = render(
|
|
90
|
+
<AttachmentChipList entries={[image]} onRemove={vi.fn()} onRetry={vi.fn()} />,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
await waitFor(() => expect(container.querySelector("img")).toBeTruthy());
|
|
94
|
+
const src = container.querySelector("img")!.getAttribute("src")!;
|
|
95
|
+
|
|
96
|
+
rerender(
|
|
97
|
+
<AttachmentChipList entries={[]} onRemove={vi.fn()} onRetry={vi.fn()} />,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
expect(container.querySelector("img")).toBeNull();
|
|
101
|
+
expect(revokeSpy).toHaveBeenCalledWith(src);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { uniquifyFilename } from "../attachment-utils.js";
|
|
3
|
+
|
|
4
|
+
describe("uniquifyFilename", () => {
|
|
5
|
+
const cases: Array<{
|
|
6
|
+
scenario: string;
|
|
7
|
+
name: string;
|
|
8
|
+
taken: string[];
|
|
9
|
+
expected: string;
|
|
10
|
+
}> = [
|
|
11
|
+
{
|
|
12
|
+
scenario: "returns the name unchanged when nothing collides",
|
|
13
|
+
name: "report.pdf",
|
|
14
|
+
taken: [],
|
|
15
|
+
expected: "report.pdf",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
scenario: "suffixes -2 before the extension on first collision",
|
|
19
|
+
name: "report.pdf",
|
|
20
|
+
taken: ["report.pdf"],
|
|
21
|
+
expected: "report-2.pdf",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
scenario: "walks past already-taken suffixes",
|
|
25
|
+
name: "report.pdf",
|
|
26
|
+
taken: ["report.pdf", "report-2.pdf", "report-3.pdf"],
|
|
27
|
+
expected: "report-4.pdf",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
scenario: "handles names without an extension",
|
|
31
|
+
name: "Makefile",
|
|
32
|
+
taken: ["Makefile"],
|
|
33
|
+
expected: "Makefile-2",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
scenario: "treats a leading dot as a hidden-file prefix, not an extension",
|
|
37
|
+
name: ".env",
|
|
38
|
+
taken: [".env"],
|
|
39
|
+
expected: ".env-2",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
scenario: "only splits on the last dot of a multi-dot name",
|
|
43
|
+
name: "backup.tar.gz",
|
|
44
|
+
taken: ["backup.tar.gz"],
|
|
45
|
+
expected: "backup.tar-2.gz",
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
for (const { scenario, name, taken, expected } of cases) {
|
|
50
|
+
it(scenario, () => {
|
|
51
|
+
expect(uniquifyFilename(name, new Set(taken))).toBe(expected);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { extractClipboardFiles } from "../clipboard.js";
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Helpers
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The function only reads `clipboardData.files` as an array-like, so a
|
|
10
|
+
* plain array cast to FileList is a faithful stand-in (same pattern as the
|
|
11
|
+
* composer fileref tests).
|
|
12
|
+
*/
|
|
13
|
+
function clipboardEventWith(files: File[]): { clipboardData: { files: FileList } } {
|
|
14
|
+
return { clipboardData: { files: files as unknown as FileList } };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function imageFile(name: string, type = "image/png"): File {
|
|
18
|
+
return new File([new Uint8Array([0x89, 0x50, 0x4e, 0x47])], name, { type });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const SYNTHESIZED_NAME = /^pasted-image-\d{6}-\d+\.[a-z]+$/;
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Tests
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
describe("extractClipboardFiles", () => {
|
|
28
|
+
it("returns an empty array for a text-only paste (null clipboardData)", () => {
|
|
29
|
+
expect(extractClipboardFiles({ clipboardData: null })).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("returns an empty array when the clipboard carries no files", () => {
|
|
33
|
+
expect(extractClipboardFiles(clipboardEventWith([]))).toEqual([]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("renames the browser's generic image.png to a synthesized name", () => {
|
|
37
|
+
const [file] = extractClipboardFiles(clipboardEventWith([imageFile("image.png")]));
|
|
38
|
+
|
|
39
|
+
expect(file.name).toMatch(SYNTHESIZED_NAME);
|
|
40
|
+
expect(file.name.endsWith(".png")).toBe(true);
|
|
41
|
+
expect(file.type).toBe("image/png");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("renames generic names for every image flavor browsers produce", () => {
|
|
45
|
+
const cases: Array<[string, string, string]> = [
|
|
46
|
+
["image.jpg", "image/jpeg", ".jpg"],
|
|
47
|
+
["image.jpeg", "image/jpeg", ".jpg"],
|
|
48
|
+
["image.gif", "image/gif", ".gif"],
|
|
49
|
+
["image.webp", "image/webp", ".webp"],
|
|
50
|
+
["image.tiff", "image/tiff", ".tiff"],
|
|
51
|
+
["IMAGE.PNG", "image/png", ".png"],
|
|
52
|
+
// Empty name with an image MIME (some WebKit paste payloads).
|
|
53
|
+
["", "image/png", ".png"],
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
for (const [name, type, expectedExt] of cases) {
|
|
57
|
+
const [file] = extractClipboardFiles(clipboardEventWith([imageFile(name, type)]));
|
|
58
|
+
expect(file.name, `input name '${name}'`).toMatch(SYNTHESIZED_NAME);
|
|
59
|
+
expect(file.name.endsWith(expectedExt), `'${file.name}' should end with ${expectedExt}`).toBe(true);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("gives two images pasted together distinct names", () => {
|
|
64
|
+
const files = extractClipboardFiles(
|
|
65
|
+
clipboardEventWith([imageFile("image.png"), imageFile("image.png")]),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
expect(files).toHaveLength(2);
|
|
69
|
+
expect(files[0].name).not.toBe(files[1].name);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("gives images from separate pastes distinct names (session inputs/ dir accumulates)", () => {
|
|
73
|
+
const [first] = extractClipboardFiles(clipboardEventWith([imageFile("image.png")]));
|
|
74
|
+
const [second] = extractClipboardFiles(clipboardEventWith([imageFile("image.png")]));
|
|
75
|
+
|
|
76
|
+
expect(first.name).not.toBe(second.name);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("keeps the real name of a file pasted from a file manager", () => {
|
|
80
|
+
const [file] = extractClipboardFiles(
|
|
81
|
+
clipboardEventWith([imageFile("architecture-sketch.png")]),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
expect(file.name).toBe("architecture-sketch.png");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("does not rename a non-image file even when named image.png", () => {
|
|
88
|
+
const pdf = new File([new Uint8Array([0x25, 0x50])], "image.png", {
|
|
89
|
+
type: "application/pdf",
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const [file] = extractClipboardFiles(clipboardEventWith([pdf]));
|
|
93
|
+
expect(file.name).toBe("image.png");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("preserves bytes and metadata across a rename", async () => {
|
|
97
|
+
const original = new File([new Uint8Array([1, 2, 3, 4])], "image.png", {
|
|
98
|
+
type: "image/png",
|
|
99
|
+
lastModified: 1723200000000,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const [renamed] = extractClipboardFiles(clipboardEventWith([original]));
|
|
103
|
+
|
|
104
|
+
expect(renamed.type).toBe("image/png");
|
|
105
|
+
expect(renamed.lastModified).toBe(1723200000000);
|
|
106
|
+
expect(new Uint8Array(await renamed.arrayBuffer())).toEqual(
|
|
107
|
+
new Uint8Array([1, 2, 3, 4]),
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
});
|