diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts
index dcd1b195..69790de0 100644
--- a/src/main/services/team/TeamProvisioningService.ts
+++ b/src/main/services/team/TeamProvisioningService.ts
@@ -3624,18 +3624,33 @@ export class TeamProvisioningService {
media_type: 'application/pdf',
data: att.data,
},
+ title: att.filename,
});
} else if (att.mimeType === 'text/plain') {
// Text file → document block with text source (decode base64 → UTF-8)
- contentBlocks.push({
- type: 'document',
- source: {
- type: 'text',
- media_type: 'text/plain',
- data: Buffer.from(att.data, 'base64').toString('utf-8'),
- },
- title: att.filename,
- });
+ const decoded = Buffer.from(att.data, 'base64').toString('utf-8');
+ if (decoded.includes('\uFFFD')) {
+ // Non-UTF-8 file: fallback to base64 document to avoid garbled content
+ contentBlocks.push({
+ type: 'document',
+ source: {
+ type: 'base64',
+ media_type: 'text/plain',
+ data: att.data,
+ },
+ title: att.filename,
+ });
+ } else {
+ contentBlocks.push({
+ type: 'document',
+ source: {
+ type: 'text',
+ media_type: 'text/plain',
+ data: decoded,
+ },
+ title: att.filename,
+ });
+ }
} else {
// Image (default) → image block
contentBlocks.push({
diff --git a/src/renderer/components/team/dialogs/TaskCommentInput.tsx b/src/renderer/components/team/dialogs/TaskCommentInput.tsx
index 5694fb7c..55cdcda6 100644
--- a/src/renderer/components/team/dialogs/TaskCommentInput.tsx
+++ b/src/renderer/components/team/dialogs/TaskCommentInput.tsx
@@ -2,6 +2,7 @@ import { useCallback, useMemo, useRef, useState } from 'react';
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
import { ImageLightbox } from '@renderer/components/team/attachments/ImageLightbox';
+import { FileIcon } from '@renderer/components/team/editor/FileIcon';
import { MemberBadge } from '@renderer/components/team/MemberBadge';
import { MentionableTextarea } from '@renderer/components/ui/MentionableTextarea';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
@@ -19,7 +20,7 @@ import {
stripEncodedTaskReferenceMetadata,
} from '@renderer/utils/taskReferenceUtils';
import { MAX_TEXT_LENGTH } from '@shared/constants';
-import { categorizeFile, getEffectiveMimeType } from '@shared/constants/attachments';
+import { categorizeFile, getEffectiveMimeType, isImageMime } from '@shared/constants/attachments';
import { Mic, Paperclip, Send, Trash2, X } from 'lucide-react';
import type { MentionSuggestion } from '@renderer/types/mention';
@@ -254,25 +255,40 @@ export const TaskCommentInput = ({
{/* Pending attachment previews */}
{pendingAttachments.length > 0 ? (
- {pendingAttachments.map((att, idx) => (
-
setLightboxIndex(idx)}
- >
-

-
+ );
+ })}
) : null}
@@ -280,13 +296,15 @@ export const TaskCommentInput = ({
setLightboxIndex(null)}
- slides={pendingAttachments.map((att) => ({
- src: att.previewUrl,
- alt: att.filename,
- title: att.filename,
- }))}
+ slides={pendingAttachments
+ .filter((att) => isImageMime(att.mimeType))
+ .map((att) => ({
+ src: att.previewUrl,
+ alt: att.filename,
+ title: att.filename,
+ }))}
index={lightboxIndex}
- showCounter={pendingAttachments.length > 1}
+ showCounter={pendingAttachments.filter((a) => isImageMime(a.mimeType)).length > 1}
/>
) : null}
diff --git a/src/renderer/hooks/useAttachments.ts b/src/renderer/hooks/useAttachments.ts
index 07f024d7..48210114 100644
--- a/src/renderer/hooks/useAttachments.ts
+++ b/src/renderer/hooks/useAttachments.ts
@@ -270,19 +270,17 @@ export function useAttachments(options?: UseAttachmentsOptions): UseAttachmentsR
const items = event.clipboardData?.items;
if (!items) return;
- const supportedFiles: File[] = [];
+ const pastedFiles: File[] = [];
for (const item of Array.from(items)) {
if (item.kind === 'file') {
const file = item.getAsFile();
- if (file && categorizeFile(file) !== 'unsupported') {
- supportedFiles.push(file);
- }
+ if (file) pastedFiles.push(file);
}
}
- if (supportedFiles.length > 0) {
+ if (pastedFiles.length > 0) {
event.preventDefault();
- void addFiles(supportedFiles);
+ void addFiles(pastedFiles);
}
},
[addFiles]
diff --git a/src/renderer/hooks/useComposerDraft.ts b/src/renderer/hooks/useComposerDraft.ts
index 9b0554a0..81be33f6 100644
--- a/src/renderer/hooks/useComposerDraft.ts
+++ b/src/renderer/hooks/useComposerDraft.ts
@@ -420,19 +420,17 @@ export function useComposerDraft(teamName: string): UseComposerDraftResult {
const items = event.clipboardData?.items;
if (!items) return;
- const supportedFiles: File[] = [];
+ const pastedFiles: File[] = [];
for (const item of Array.from(items)) {
if (item.kind === 'file') {
const file = item.getAsFile();
- if (file && categorizeFile(file) !== 'unsupported') {
- supportedFiles.push(file);
- }
+ if (file) pastedFiles.push(file);
}
}
- if (supportedFiles.length > 0) {
+ if (pastedFiles.length > 0) {
event.preventDefault();
- void addFiles(supportedFiles);
+ void addFiles(pastedFiles);
}
},
[addFiles]
diff --git a/src/renderer/utils/attachmentUtils.ts b/src/renderer/utils/attachmentUtils.ts
index d1ec8a85..9b23efcf 100644
--- a/src/renderer/utils/attachmentUtils.ts
+++ b/src/renderer/utils/attachmentUtils.ts
@@ -22,6 +22,9 @@ export function validateAttachment(file: File): { valid: true } | { valid: false
if (cat === 'unsupported') {
return { valid: false, error: `Unsupported file type: ${file.name}` };
}
+ if (file.size === 0) {
+ return { valid: false, error: `File "${file.name}" is empty` };
+ }
if (file.size > MAX_FILE_SIZE) {
return { valid: false, error: `File "${file.name}" exceeds 10MB limit` };
}
diff --git a/tsconfig.json b/tsconfig.json
index 7711a5ed..d1b421bc 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,8 +1,8 @@
{
"compilerOptions": {
- "target": "ES2020",
+ "target": "ES2023",
"module": "ESNext",
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,