Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/mobile/src/components/FilePreviewModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ async function resolvePreviewItem(
}
if (!("environmentId" in item)) return { source: { ...item, name } };
// Refresh expired URLs before handing them to Quick Look or ACTION_VIEW, which retain them.
const url = await refreshAssetUrl(item.environmentId, item.resource, signal)
const url = await refreshAssetUrl(item.environmentId, item.resource, signal, item.scope)
.then((value) => {
if (!value) throw new Error("Reconnect to this environment and try again.");
return value;
Expand Down
6 changes: 5 additions & 1 deletion apps/mobile/src/components/FilePreviewModal.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ export type FilePreviewItem = FilePreviewMetadata &
(
| { readonly uri: string }
| { readonly attachment: FileBackedComposerAttachment }
| { readonly environmentId: EnvironmentId; readonly resource: AssetResource }
| {
readonly environmentId: EnvironmentId;
readonly resource: AssetResource;
readonly scope?: string | undefined;
}
);

export type FilePreviewSource = FilePreviewItem & {
Expand Down
9 changes: 7 additions & 2 deletions apps/mobile/src/components/VideoPreviewModal.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ function NativeVideoPreview(props: {
const resolvePlaybackUrl = useEffectEvent(async (): Promise<string> => {
if (source.type !== "media") throw new Error("Could not load this video.");
if (!("resource" in source)) return source.uri;
const url = await refreshAssetUrl(source.environmentId, source.resource);
const url = await refreshAssetUrl(
source.environmentId,
source.resource,
undefined,
source.scope,
);
const playbackUrl = mediaVideoPreviewUri(source, url);
if (playbackUrl !== null) return playbackUrl;
throw new Error(
Expand All @@ -61,7 +66,7 @@ function NativeVideoPreview(props: {
});
const reauthorize = useEffectEvent(() => {
if (source.type === "media" && "resource" in source) {
void refreshAssetUrl(source.environmentId, source.resource);
void refreshAssetUrl(source.environmentId, source.resource, undefined, source.scope);
}
});

Expand Down
5 changes: 3 additions & 2 deletions apps/mobile/src/components/VideoPreviewModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ function useMediaPlayback(source: MediaVideoPreviewSource): PlaybackState {
const environmentId = "environmentId" in source ? source.environmentId : null;
const resource = "resource" in source ? source.resource : null;
const connection = usePreparedConnection(environmentId);
const asset = useAssetUrlState(environmentId, resource);
const refreshAssetUrl = useRefreshAssetUrl(environmentId, resource);
const scope = "resource" in source ? source.scope : undefined;
const asset = useAssetUrlState(environmentId, resource, scope);
const refreshAssetUrl = useRefreshAssetUrl(environmentId, resource, scope);
const uri = mediaVideoPreviewUri(source, asset._tag === "Success" ? asset.url : null);
return {
uri,
Expand Down
59 changes: 40 additions & 19 deletions apps/mobile/src/features/threads/ThreadFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@
import { useFileChipShare } from "./useFileChipShare";
import { useOpenLinkedThread } from "./useOpenLinkedThread";
import {
MarkdownImageAssetScopeContext,
MarkdownImageAvailableWidthContext,
ThreadMarkdownImage,
ThreadMarkdownImageUnavailable,
Expand Down Expand Up @@ -822,10 +823,10 @@
}

interface MarkdownLinkHandlers {
readonly onLinkPress: (href: string) => void;
readonly onLinkPress: (href: string, scope?: string) => void;
readonly fileContextMenu: (href: string) => MarkdownFileContextMenu | undefined;
readonly onFileContextMenuAction: (href: string, actionId: string) => void;
readonly onImagePress?: (href: string) => void;
readonly onFileContextMenuAction: (href: string, actionId: string, scope?: string) => void;
readonly onImagePress?: (href: string, scope?: string) => void;
readonly resolveImageSource?: MarkdownImageSourceResolver;
readonly fileEmbedLabel?: (href: string, standalone: boolean) => string | null;
readonly linkCustomization?: (href: string) => MarkdownLinkCustomization | undefined;
Expand Down Expand Up @@ -907,6 +908,12 @@
readonly renderImage: MarkdownImageRenderer;
readonly skills?: ReadonlyArray<SelectableMarkdownSkill> | undefined;
}) {
const scope = useContext(MarkdownImageAssetScopeContext);
const resolveImageSource = props.linkHandlers.resolveImageSource;
const scopedImageSource = useCallback<MarkdownImageSourceResolver>(
(image) => resolveImageSource?.(image, scope) ?? null,
[resolveImageSource, scope],
);
const liveMarkdown = useLiveThreadLinkLabels(props.markdown, props.environmentId);
const segments = useMemo(() => splitCodexArtifactTemplateMarkdown(liveMarkdown), [liveMarkdown]);
const threadIcons = useThreadChipIcons(props.environmentId, liveMarkdown);
Expand Down Expand Up @@ -936,6 +943,12 @@
skills={props.skills}
textStyle={props.markdownStyles.nativeTextStyle}
{...linkHandlers}
onLinkPress={(href) => props.linkHandlers.onLinkPress(href, scope)}
onImagePress={(href) => props.linkHandlers.onImagePress?.(href, scope)}
onFileContextMenuAction={(href, actionId) =>
props.linkHandlers.onFileContextMenuAction(href, actionId, scope)
}
resolveImageSource={scopedImageSource}
renderImage={props.renderImage}
/>
);
Expand Down Expand Up @@ -1760,15 +1773,17 @@
>
{renderedText.trim().length > 0 ? (
<MarkdownImageAvailableWidthContext value={props.markdownContentWidth}>
<AssistantMarkdownContent
markdown={renderedText}
environmentId={props.environmentId}
markdownStyles={styles}
linkHandlers={props.markdownLinkHandlers}
onUseArtifactTemplate={props.onUseArtifactTemplate}
renderImage={props.renderMarkdownImage}
skills={props.skills}
/>
<MarkdownImageAssetScopeContext value={message.id}>

Check failure on line 1776 in apps/mobile/src/features/threads/ThreadFeed.tsx

View workflow job for this annotation

GitHub Actions / Test

[react] src/lib/filePreview.test.ts > ThreadInlineFilePreview playback > preserves a pending file lookup on assistant attachment video continued playback

Error: [vitest] No "MarkdownImageAssetScopeContext" export is defined on the "../features/threads/ThreadMarkdownImage" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("../features/threads/ThreadMarkdownImage"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ renderFeedEntry src/features/threads/ThreadFeed.tsx:1776:14 ❯ Object.t68 [as renderItem] src/features/threads/ThreadFeed.tsx:3092:12 ❯ src/lib/filePreview.test.ts:205:52 ❯ KeyboardAwareLegendList src/lib/filePreview.test.ts:204:16 ❯ Object.react_stack_bottom_frame ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:28571:20 ❯ renderWithHooks ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:8030:22 ❯ updateFunctionComponent ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:10569:19

Check failure on line 1776 in apps/mobile/src/features/threads/ThreadFeed.tsx

View workflow job for this annotation

GitHub Actions / Test

[react] src/lib/filePreview.test.ts > ThreadInlineFilePreview playback > cancels a pending file lookup on assistant attachment video native resume

Error: [vitest] No "MarkdownImageAssetScopeContext" export is defined on the "../features/threads/ThreadMarkdownImage" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("../features/threads/ThreadMarkdownImage"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ renderFeedEntry src/features/threads/ThreadFeed.tsx:1776:14 ❯ Object.t68 [as renderItem] src/features/threads/ThreadFeed.tsx:3092:12 ❯ src/lib/filePreview.test.ts:205:52 ❯ KeyboardAwareLegendList src/lib/filePreview.test.ts:204:16 ❯ Object.react_stack_bottom_frame ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:28571:20 ❯ renderWithHooks ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:8030:22 ❯ updateFunctionComponent ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:10569:19

Check failure on line 1776 in apps/mobile/src/features/threads/ThreadFeed.tsx

View workflow job for this annotation

GitHub Actions / Test

[react] src/lib/filePreview.test.ts > ThreadInlineFilePreview playback > cancels a pending file lookup on assistant attachment video initial play

Error: [vitest] No "MarkdownImageAssetScopeContext" export is defined on the "../features/threads/ThreadMarkdownImage" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("../features/threads/ThreadMarkdownImage"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ renderFeedEntry src/features/threads/ThreadFeed.tsx:1776:14 ❯ Object.t68 [as renderItem] src/features/threads/ThreadFeed.tsx:3092:12 ❯ src/lib/filePreview.test.ts:205:52 ❯ KeyboardAwareLegendList src/lib/filePreview.test.ts:204:16 ❯ Object.react_stack_bottom_frame ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:28571:20 ❯ renderWithHooks ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:8030:22 ❯ updateFunctionComponent ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:10569:19

Check failure on line 1776 in apps/mobile/src/features/threads/ThreadFeed.tsx

View workflow job for this annotation

GitHub Actions / Test

[react] src/lib/filePreview.test.ts > ThreadInlineFilePreview playback > cancels a pending file lookup on assistant attachment audio initial play

Error: [vitest] No "MarkdownImageAssetScopeContext" export is defined on the "../features/threads/ThreadMarkdownImage" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("../features/threads/ThreadMarkdownImage"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ renderFeedEntry src/features/threads/ThreadFeed.tsx:1776:14 ❯ Object.t68 [as renderItem] src/features/threads/ThreadFeed.tsx:3092:12 ❯ src/lib/filePreview.test.ts:205:52 ❯ KeyboardAwareLegendList src/lib/filePreview.test.ts:204:16 ❯ Object.react_stack_bottom_frame ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:28571:20 ❯ renderWithHooks ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:8030:22 ❯ updateFunctionComponent ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:10569:19
<AssistantMarkdownContent
markdown={renderedText}
environmentId={props.environmentId}
markdownStyles={styles}
linkHandlers={props.markdownLinkHandlers}
onUseArtifactTemplate={props.onUseArtifactTemplate}
renderImage={props.renderMarkdownImage}
skills={props.skills}
/>
</MarkdownImageAssetScopeContext>
</MarkdownImageAvailableWidthContext>
) : null}
<MessageAttachmentList
Expand Down Expand Up @@ -2278,7 +2293,7 @@
source
);
};
const onMarkdownLinkPress = (href: string, imageEmbed = false) => {
const onMarkdownLinkPress = (href: string, scope?: string, imageEmbed = false) => {
markdownLinkRequest.current?.abort();
const request = new AbortController();
markdownLinkRequest.current = request;
Expand All @@ -2301,6 +2316,7 @@
threadId: props.threadId,
workspaceRoot: props.workspaceRoot,
imageEmbed,
scope,
});
if (media && media.kind !== "file") {
void Haptics.selectionAsync();
Expand Down Expand Up @@ -2436,7 +2452,7 @@
),
onLinkPress: onMarkdownLinkPress,
linkCustomization: enrichedThreadLinkCustomization,
onImagePress: (href) => onMarkdownLinkPress(href, true),
onImagePress: (href, scope) => onMarkdownLinkPress(href, scope, true),
resolveImageSource,
fileContextMenu: (href) => {
const skill = enrichedSkillReference(href, props.skills);
Expand Down Expand Up @@ -2467,7 +2483,7 @@
const target = resolveFileChipTarget(href, props.workspaceRoot);
return target ? fileChipMenu(target) : undefined;
},
onFileContextMenuAction: (href, actionId) => {
onFileContextMenuAction: (href, actionId, scope) => {
const skill = enrichedSkillReference(href, props.skills);
if (skill) {
const instructionsPath = enrichedSkillInstructionsPath(skill);
Expand All @@ -2490,7 +2506,7 @@
return;
}
case "open-file":
onMarkdownLinkPress(href);
onMarkdownLinkPress(href, scope);
return;
case "save":
shareFileChip(target);
Expand Down Expand Up @@ -2519,7 +2535,7 @@
<ThreadInlineFilePreview
key={image.href}
source={media.source}
onOpen={() => onMarkdownLinkPress(image.href, true)}
onOpen={() => onMarkdownLinkPress(image.href, undefined, true)}
onPlaybackStart={onInlinePlaybackStart}
/>
);
Expand All @@ -2534,7 +2550,7 @@
<Text
accessibilityRole="link"
accessibilityLabel={`Open ${name}`}
onPress={() => onMarkdownLinkPress(image.href, true)}
onPress={() => onMarkdownLinkPress(image.href, undefined, true)}
style={{
color: markdownStyles.assistant.nativeTextStyle.linkColor,
fontSize: markdownStyles.assistant.nativeTextStyle.fontSize,
Expand Down Expand Up @@ -3333,7 +3349,12 @@
const images =
entry.type === "message"
? [
...messageImageGallery(entry.message, { environmentId, threadId, workspaceRoot }),
...messageImageGallery(entry.message, {
environmentId,
threadId,
workspaceRoot,
scope: entry.message.role === "assistant" ? entry.message.id : undefined,
}),
...composerImageGallery(entry.pendingMessage?.attachments ?? []),
]
: [];
Expand Down
14 changes: 12 additions & 2 deletions apps/mobile/src/features/threads/ThreadMarkdownImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import {
/** Use known feed width immediately; later image measurement would move already-positioned rows. */
export const MarkdownImageAvailableWidthContext = createContext(0);

/** Later messages must re-sign workspace files that may have changed. */
export const MarkdownImageAssetScopeContext = createContext<string | undefined>(undefined);

export function ThreadMarkdownImageView(props: {
readonly uri: string | null;
readonly sourceKey: string;
Expand Down Expand Up @@ -243,7 +246,12 @@ export function ThreadMarkdownImage(props: {
readonly onPressPreview: (source: FilePreviewSource) => void;
}) {
const withGallery = useFilePreviewGallery();
const assetUrl = useAssetUrlState(props.environmentId, props.resource);
const scope = useContext(MarkdownImageAssetScopeContext);
const assetUrl = useAssetUrlState(props.environmentId, props.resource, scope);
const actionsSource =
props.actionsSource && "resource" in props.actionsSource
? { ...props.actionsSource, scope }
: props.actionsSource;

return (
<ThreadMarkdownImageView
Expand All @@ -269,7 +277,7 @@ export function ThreadMarkdownImage(props: {
: undefined
}
alt={props.alt}
actionsSource={props.actionsSource}
actionsSource={actionsSource}
onPressPreview={(source) => {
const { uri: _uri, ...rest } = source as FilePreviewSource & { uri?: string };
props.onPressPreview(
Expand All @@ -278,6 +286,8 @@ export function ThreadMarkdownImage(props: {
gallery: undefined,
environmentId: props.environmentId,
resource: props.resource,
scope,
actionsSource,
}),
);
}}
Expand Down
15 changes: 13 additions & 2 deletions apps/mobile/src/features/threads/ThreadMarkdownVideo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createContext, useContext } from "react";
import { MarkdownImageAssetScopeContext } from "./ThreadMarkdownImage";
import { MediaVideoPlayer } from "../../components/MediaVideoPlayer";
import { useAssetUrlState, useRefreshAssetUrl } from "../../state/assets";
import {
Expand All @@ -14,17 +15,23 @@
readonly thumbnailVisible?: boolean;
readonly onPlaybackStart?: () => void;
}) {
const { source } = props;
const scope = useContext(MarkdownImageAssetScopeContext);
const source =
"resource" in props.source
? { ...props.source, scope: props.source.scope ?? scope }
: props.source;
const feedVisible = useContext(ThreadMediaVisibleContext);
const visible = props.thumbnailVisible ?? feedVisible;
const thumbnailKey = mediaVideoThumbnailKey(source);
const asset = useAssetUrlState(
"environmentId" in source ? source.environmentId : null,
"resource" in source ? source.resource : null,
"resource" in source ? source.scope : undefined,
);
const refreshAssetUrl = useRefreshAssetUrl(
"environmentId" in source ? source.environmentId : null,
"resource" in source ? source.resource : null,
"resource" in source ? source.scope : undefined,
);
const uri = mediaVideoPreviewUri(source, asset._tag === "Success" ? asset.url : null);
return (
Expand All @@ -33,7 +40,7 @@
uri={uri}
resolvePlaybackUri={
"resource" in source
? async () => mediaVideoPreviewUri(source, await refreshAssetUrl())

Check failure on line 43 in apps/mobile/src/features/threads/ThreadMarkdownVideo.tsx

View workflow job for this annotation

GitHub Actions / Test

[react] src/lib/filePreview.test.ts > ThreadInlineFilePreview playback > preserves a pending file lookup on user markdown video continued playback

Error: [vitest] No "MarkdownImageAssetScopeContext" export is defined on the "../features/threads/ThreadMarkdownImage" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("../features/threads/ThreadMarkdownImage"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ ThreadMarkdownVideo src/features/threads/ThreadMarkdownVideo.tsx:43:76 ❯ Object.react_stack_bottom_frame ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:28571:20 ❯ renderWithHooks ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:8030:22 ❯ updateFunctionComponent ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:10569:19 ❯ beginWork ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:12205:18 ❯ runWithFiberInDEV ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:1048:13 ❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:19176:22 ❯ workLoopSync ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:19004:41

Check failure on line 43 in apps/mobile/src/features/threads/ThreadMarkdownVideo.tsx

View workflow job for this annotation

GitHub Actions / Test

[react] src/lib/filePreview.test.ts > ThreadInlineFilePreview playback > cancels a pending file lookup on user markdown video native resume

Error: [vitest] No "MarkdownImageAssetScopeContext" export is defined on the "../features/threads/ThreadMarkdownImage" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("../features/threads/ThreadMarkdownImage"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ ThreadMarkdownVideo src/features/threads/ThreadMarkdownVideo.tsx:43:76 ❯ Object.react_stack_bottom_frame ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:28571:20 ❯ renderWithHooks ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:8030:22 ❯ updateFunctionComponent ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:10569:19 ❯ beginWork ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:12205:18 ❯ runWithFiberInDEV ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:1048:13 ❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:19176:22 ❯ workLoopSync ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:19004:41

Check failure on line 43 in apps/mobile/src/features/threads/ThreadMarkdownVideo.tsx

View workflow job for this annotation

GitHub Actions / Test

[react] src/lib/filePreview.test.ts > ThreadInlineFilePreview playback > cancels a pending file lookup on user markdown video initial play

Error: [vitest] No "MarkdownImageAssetScopeContext" export is defined on the "../features/threads/ThreadMarkdownImage" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("../features/threads/ThreadMarkdownImage"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ ThreadMarkdownVideo src/features/threads/ThreadMarkdownVideo.tsx:43:76 ❯ Object.react_stack_bottom_frame ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:28571:20 ❯ renderWithHooks ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:8030:22 ❯ updateFunctionComponent ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:10569:19 ❯ beginWork ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:12205:18 ❯ runWithFiberInDEV ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:1048:13 ❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:19176:22 ❯ workLoopSync ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:19004:41

Check failure on line 43 in apps/mobile/src/features/threads/ThreadMarkdownVideo.tsx

View workflow job for this annotation

GitHub Actions / Test

[react] src/lib/filePreview.test.ts > ThreadInlineFilePreview playback > preserves a pending file lookup on user attachment video continued playback

Error: [vitest] No "MarkdownImageAssetScopeContext" export is defined on the "../features/threads/ThreadMarkdownImage" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("../features/threads/ThreadMarkdownImage"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ ThreadMarkdownVideo src/features/threads/ThreadMarkdownVideo.tsx:43:76 ❯ Object.react_stack_bottom_frame ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:28571:20 ❯ renderWithHooks ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:8030:22 ❯ updateFunctionComponent ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:10569:19 ❯ beginWork ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:12205:18 ❯ runWithFiberInDEV ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:1048:13 ❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:19176:22 ❯ workLoopSync ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:19004:41

Check failure on line 43 in apps/mobile/src/features/threads/ThreadMarkdownVideo.tsx

View workflow job for this annotation

GitHub Actions / Test

[react] src/lib/filePreview.test.ts > ThreadInlineFilePreview playback > cancels a pending file lookup on user attachment video native resume

Error: [vitest] No "MarkdownImageAssetScopeContext" export is defined on the "../features/threads/ThreadMarkdownImage" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("../features/threads/ThreadMarkdownImage"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ ThreadMarkdownVideo src/features/threads/ThreadMarkdownVideo.tsx:43:76 ❯ Object.react_stack_bottom_frame ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:28571:20 ❯ renderWithHooks ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:8030:22 ❯ updateFunctionComponent ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:10569:19 ❯ beginWork ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:12205:18 ❯ runWithFiberInDEV ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:1048:13 ❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:19176:22 ❯ workLoopSync ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:19004:41

Check failure on line 43 in apps/mobile/src/features/threads/ThreadMarkdownVideo.tsx

View workflow job for this annotation

GitHub Actions / Test

[react] src/lib/filePreview.test.ts > ThreadInlineFilePreview playback > cancels a pending file lookup on user attachment video initial play

Error: [vitest] No "MarkdownImageAssetScopeContext" export is defined on the "../features/threads/ThreadMarkdownImage" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("../features/threads/ThreadMarkdownImage"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ ThreadMarkdownVideo src/features/threads/ThreadMarkdownVideo.tsx:43:76 ❯ Object.react_stack_bottom_frame ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:28571:20 ❯ renderWithHooks ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:8030:22 ❯ updateFunctionComponent ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:10569:19 ❯ beginWork ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:12205:18 ❯ runWithFiberInDEV ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:1048:13 ❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:19176:22 ❯ workLoopSync ../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom-client.development.js:19004:41
: undefined
}
name={source.name}
Expand All @@ -42,7 +49,11 @@
visible={visible}
onPlaybackStart={props.onPlaybackStart}
unavailable={"resource" in source && asset._tag === "Failure"}
actionsSource={source.actionsSource}
actionsSource={
source.actionsSource && "resource" in source.actionsSource
? { ...source.actionsSource, scope: "resource" in source ? source.scope : undefined }
: source.actionsSource
}
/>
);
}
1 change: 1 addition & 0 deletions apps/mobile/src/features/threads/messageImageGallery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export function messageImageGallery(
environmentId: EnvironmentId;
threadId: ThreadId;
workspaceRoot: string | null | undefined;
scope?: string | undefined;
},
): FilePreviewItem[] {
const images: FilePreviewItem[] = [];
Expand Down
16 changes: 12 additions & 4 deletions apps/mobile/src/features/threads/thread-work-log.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ import { notificationChildThreadId } from "@t3tools/client-runtime/state/thread-
import type { MarkdownImageRenderer } from "../../native/SelectableMarkdownText";
import type { FilePreviewSource } from "../../components/FilePreviewModal";
import { FilePreviewGallery } from "../../components/FilePreviewGallery";
import { ThreadMarkdownImage } from "./ThreadMarkdownImage";
import { MarkdownImageAssetScopeContext, ThreadMarkdownImage } from "./ThreadMarkdownImage";
import Animated, {
FadeIn,
FadeOut,
Expand Down Expand Up @@ -530,7 +530,11 @@ export function ThreadWorkLog(props: ThreadWorkLogProps) {
return (
<ScrollView nestedScrollEnabled className="ml-7 max-h-96 py-1">
{props.activities.map((row) => (
<View key={row.id}>{props.renderReasoning(row.workEntry.detail ?? "")}</View>
<View key={row.id}>
<MarkdownImageAssetScopeContext value={row.workEntry.id}>
{props.renderReasoning(row.workEntry.detail ?? "")}
</MarkdownImageAssetScopeContext>
</View>
))}
</ScrollView>
);
Expand Down Expand Up @@ -1166,7 +1170,9 @@ const ThreadWorkLogRow = function ThreadWorkLogRow(
) : null}
{viewedImagePath ? (
<View className="pb-1.5">
{props.renderImage({ href: viewedImagePath, alt: null, title: null })}
<MarkdownImageAssetScopeContext value={row.workEntry.id}>
{props.renderImage({ href: viewedImagePath, alt: null, title: null })}
</MarkdownImageAssetScopeContext>
</View>
) : null}
<FilePreviewGallery
Expand Down Expand Up @@ -1195,7 +1201,9 @@ const ThreadWorkLogRow = function ThreadWorkLogRow(
contentContainerStyle={{ paddingRight: 8 }}
>
{reasoning ? (
props.renderReasoning(reasoning.text)
<MarkdownImageAssetScopeContext value={row.workEntry.id}>
{props.renderReasoning(reasoning.text)}
</MarkdownImageAssetScopeContext>
) : call ? (
[
call.command,
Expand Down
1 change: 1 addition & 0 deletions apps/mobile/src/lib/filePreviewGallery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export function filePreviewKey(source: FilePreviewItem): string {
Object.entries(source.resource)
.filter(([key]) => key !== "mimeType" && key !== "fileName")
.sort(([a], [b]) => a.localeCompare(b)),
source.scope ?? null,
]);
if ("attachment" in source) return `attachment:${source.attachment.id}`;
return source.uri;
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/src/lib/markdownMedia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function resolveMarkdownMediaPreview(
readonly workspaceRoot: string | null | undefined;
readonly captured?: boolean;
readonly imageEmbed?: boolean;
readonly scope?: string | undefined;
},
):
| { readonly kind: "image"; readonly source: FilePreviewSource }
Expand Down Expand Up @@ -139,6 +140,7 @@ export function resolveMarkdownMediaPreview(
: {
environmentId: input.environmentId,
resource: media.resource,
scope: input.scope,
...(srcFragment ? { srcFragment } : {}),
};
const actionsSource: MediaActionsSource =
Expand All @@ -149,6 +151,7 @@ export function resolveMarkdownMediaPreview(
environmentId: input.environmentId,
threadId: input.threadId,
resource: media.resource,
scope: input.scope,
name,
mimeType,
};
Expand Down
1 change: 1 addition & 0 deletions apps/mobile/src/lib/mediaActionsSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ export type MediaActionsSource = {
readonly environmentId: EnvironmentId;
readonly threadId?: ThreadId;
readonly resource: AssetResource;
readonly scope?: string | undefined;
}
);
Loading
Loading