Skip to content

feat(platform): chat archive, file upload improvements & provider edit dialog#1236

Merged
Israeltheminer merged 5 commits into
mainfrom
feat/chat-archive-file-upload-providers
Apr 9, 2026
Merged

feat(platform): chat archive, file upload improvements & provider edit dialog#1236
Israeltheminer merged 5 commits into
mainfrom
feat/chat-archive-file-upload-providers

Conversation

@Israeltheminer
Copy link
Copy Markdown
Collaborator

@Israeltheminer Israeltheminer commented Apr 9, 2026

Summary

  • Chat archive: Add archive/unarchive functionality for chat threads — backend mutations, queries, sidebar section with archived threads, and context menu actions
  • File upload improvements: Batch file count limit, total size cap, sequential uploads with progress tracking, and better error handling for the chat file upload flow
  • Provider edit dialog: Replace the panel-based provider editing with a cleaner dialog component
  • Model selector enhancements: Add tag icons (chat, vision, embedding) to the model selector for better discoverability
  • Chat actions: Add report problem and archive/unarchive to the chat context menu
  • i18n: Updated English and German translation strings for all new features

Test plan

  • Verify archiving a chat from the context menu moves it to the archived section
  • Verify unarchiving restores the thread to the active list
  • Test file uploads with multiple files to confirm batch limits and progress
  • Test provider edit dialog opens and saves correctly
  • Verify model tag icons render in the model selector
  • Check German translations load correctly

Summary by CodeRabbit

  • New Features

    • Archive and restore chat conversations.
    • Upload spreadsheet files (CSV, XLS, XLSX) to chats.
    • Display model capability tags in model selection dropdowns.
  • Improvements

    • Enhanced file handling with improved name display and file size formatting.
    • Add attachment limits: maximum 10 files per message, 25 MB total size.
    • Improved UI styling for dropdowns and form elements.
    • Better dark mode theme adjustments.
  • Bug Fixes

    • Fixed file preview truncation and readability.

… dialog

- Add archive/unarchive functionality for chat threads with dedicated
  sidebar section and context menu actions
- Improve file upload with batch limits, total size cap, sequential
  uploads with progress, and better error handling
- Replace provider edit panel with a dialog-based editing flow
- Add model tag icons (chat, vision, embedding) to model selector
- Add report problem action to chat context menu
- Enhance chat history sidebar with archived threads section
- Update i18n strings for new features (en, de)
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

…d test

- fix formatting in chat-input.tsx
- remove unused export for getFileTypeLabelKey and ModelTag type
- update delete_chat_thread test to expect 'deleted' status
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 9, 2026

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

This pull request introduces comprehensive chat thread archiving functionality, enhanced file attachment handling, and UI theme refinements. It adds archive/unarchive mutations and queries in the backend, integrates archived chat listing in the history sidebar, and introduces file upload constraints (max count and total size). Shared utility functions for file size formatting and filename ellipsis are extracted. Spreadsheet file support is added to chat uploads and backend processing. Multiple UI components transition from bg-popover to bg-muted background styling. Provider management is significantly refactored with new edit dialog and table actions. Translation keys are expanded for archive features, file types, and provider management flows.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the three main changes: chat archive functionality, file upload improvements, and provider edit dialog implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/chat-archive-file-upload-providers

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
services/platform/app/features/chat/components/arena/arena-model-selector.tsx (1)

43-73: 🧹 Nitpick | 🔵 Trivial

Consider extracting shared model metadata logic to a custom hook.

The modelInfoMap and renderTagIcons implementations are nearly identical to model-selector.tsx. Consider extracting this shared logic into a custom hook like useModelMetadata(providers, t) that returns { modelInfoMap, renderTagIcons, getDisplayName }.

💡 Example shared hook
// hooks/use-model-metadata.ts
export function useModelMetadata(providers: Provider[], t: TFunction) {
  const modelInfoMap = useMemo(() => {
    const map = new Map<string, { displayName: string; description?: string; tags: string[] }>();
    for (const provider of providers) {
      if (!provider || !('models' in provider) || !Array.isArray(provider.models)) continue;
      for (const model of provider.models) {
        map.set(model.id, {
          displayName: model.displayName,
          description: model.description || undefined,
          tags: model.tags ?? [],
        });
      }
    }
    return map;
  }, [providers]);

  const renderTagIcons = useCallback(
    (option: SearchableSelectOption): ReactNode => {
      const info = modelInfoMap.get(option.value);
      if (!info?.tags.length) return null;
      return <ModelTagIcons tags={info.tags} t={t} />;
    },
    [modelInfoMap, t],
  );

  const getDisplayName = useCallback(
    (modelId: string) => modelInfoMap.get(modelId)?.displayName ?? getModelShortName(modelId),
    [modelInfoMap],
  );

  return { modelInfoMap, renderTagIcons, getDisplayName };
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/platform/app/features/chat/components/arena/arena-model-selector.tsx`
around lines 43 - 73, Extract the duplicated model metadata logic into a hook
named useModelMetadata(providers, t): move the modelInfoMap useMemo and
renderTagIcons useCallback into that hook and add a getDisplayName(modelId)
callback that falls back to getModelShortName; update arena-model-selector.tsx
to call useModelMetadata(providers, t) and use the returned { modelInfoMap,
renderTagIcons, getDisplayName } instead of its local modelInfoMap and
renderTagIcons; ensure the hook returns the same behavior (ModelTagIcons usage,
tags defaulting to [], description undefined) and preserves dependencies.
services/platform/convex/lib/agent_chat/start_agent_chat.ts (1)

33-51: 🧹 Nitpick | 🔵 Trivial

Consider extracting formatFileSize to the shared file-types.ts module.

This function duplicates the logic in services/platform/app/features/chat/components/message-bubble/file-displays.tsx. Since file-types.ts is already a shared module imported by both frontend and backend code, consolidating this helper there would reduce duplication and ensure consistent formatting across the codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/convex/lib/agent_chat/start_agent_chat.ts` around lines 33
- 51, The local formatFileSize function in start_agent_chat.ts duplicates
formatting logic present in file-displays.tsx; move this helper into the shared
file-types.ts module and import it where needed. Specifically, remove the
formatFileSize implementation from
services/platform/convex/lib/agent_chat/start_agent_chat.ts, add the same
implementation (or better, canonical version) to
services/platform/app/features/shared/file-types.ts (or the existing
file-types.ts), export it as formatFileSize, and replace the local call sites in
start_agent_chat.ts (and in message-bubble/file-displays.tsx if applicable) to
import { formatFileSize } from the shared file-types.ts to avoid duplication and
keep formatting consistent.
services/platform/app/features/conversations/components/message-editor/file-attachments-list.tsx (1)

41-47: ⚠️ Potential issue | 🟡 Minor

Add accessible label to the remove button.

The button lacks an accessible name for screen reader users. Add an aria-label that describes the action.

🔧 Proposed fix for accessibility
             <button
               type="button"
               onClick={() => onRemove(file.id)}
               className="hover:bg-background ml-1 rounded p-0.5"
+              aria-label={`Remove ${file.file?.name ?? 'file'}`}
             >
-              <XIcon className="size-3" />
+              <XIcon className="size-3" aria-hidden="true" />
             </button>

As per coding guidelines: "ALWAYS provide text alternatives for non-text content (alt for images, aria-label for icon buttons) for accessibility".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/platform/app/features/conversations/components/message-editor/file-attachments-list.tsx`
around lines 41 - 47, The remove button in file-attachments-list.tsx (the button
that calls onRemove(file.id) and renders <XIcon />) lacks an accessible name;
add an aria-label (for example aria-label={`Remove ${file.name}`} or a generic
aria-label="Remove attachment") to the button element so screen readers can
describe the action; update the JSX for the button that wraps the XIcon to
include the aria-label attribute and ensure it reflects the file being removed
when possible.
services/platform/app/features/conversations/components/message.tsx (1)

156-182: ⚠️ Potential issue | 🟡 Minor

Potential browser download blocking with multiple files.

When multiple attachments become ready simultaneously, the loop at lines 169-176 creates and clicks anchor elements in rapid succession. Many browsers block rapid sequential programmatic downloads as a popup/download protection measure.

Consider adding a small delay between downloads or using a different approach for batch downloads.

💡 Suggested approach with staggered downloads
     // All pending files now have URLs — trigger downloads and clear state
+    let delay = 0;
     for (const att of readyAttachments) {
       pendingDownloadFiles.current.delete(att.filename);
       if (att.url) {
-        const a = document.createElement('a');
-        a.href = att.url;
-        a.download = att.filename;
-        a.click();
+        setTimeout(() => {
+          const a = document.createElement('a');
+          a.href = att.url!;
+          a.download = att.filename;
+          a.click();
+        }, delay);
+        delay += 150; // Small stagger to avoid browser blocking
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/features/conversations/components/message.tsx` around
lines 156 - 182, The loop in the useEffect that iterates readyAttachments and
programmatically clicks anchors (using pendingDownloadFiles.current,
downloadingMessageId, message.attachments and setDownloadingMessageId) can
trigger browser download-blocking when multiple files are started too fast;
modify the logic to stagger downloads (e.g., iterate readyAttachments with a
small async delay between clicks or queue them via
requestAnimationFrame/setTimeout) or switch to a single zipped download,
ensuring you still delete filenames from pendingDownloadFiles.current and only
call setDownloadingMessageId(null) after all staggered downloads complete;
locate the readyAttachments processing inside the useEffect and implement the
delay/queue there.
services/platform/app/routes/dashboard/$id/settings/providers/$providerName.tsx (1)

639-649: ⚠️ Potential issue | 🟡 Minor

Hardcoded placeholder text should be internationalized.

The placeholder "e.g., 1536" is user-facing text that should use the translation hook for i18n compliance.

Proposed fix
           {form.tags.includes('embedding') && (
             <Input
               label={t('providers.dimensions')}
               type="number"
               value={form.dimensions}
               onChange={(e) =>
                 setForm((f) => ({ ...f, dimensions: e.target.value }))
               }
-              placeholder="e.g., 1536"
+              placeholder={t('providers.dimensionsPlaceholder')}
             />
           )}

Add the translation key to your message files:

"dimensionsPlaceholder": "e.g., 1536"

As per coding guidelines: "Do NOT hardcode text, use the translation hooks/functions instead for user-facing UI".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/platform/app/routes/dashboard/`$id/settings/providers/$providerName.tsx
around lines 639 - 649, The placeholder "e.g., 1536" in the Input rendered when
form.tags includes 'embedding' is hardcoded; replace it with a translation
lookup (use the same translation hook t used for the label, e.g.
t('providers.dimensionsPlaceholder')) and add the corresponding key
("providers.dimensionsPlaceholder": "e.g., 1536") to the message files so the
placeholder is internationalized; update the Input placeholder prop to use
t(...) instead of the literal string.
services/platform/app/features/chat/components/message-bubble/file-displays.tsx (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Pipeline format check failed.

The lint pipeline reports a format check failure. Run the formatter to fix:

bunx oxfmt -c ../../.oxfmtrc.json --write services/platform/app/features/chat/components/message-bubble/file-displays.tsx
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/platform/app/features/chat/components/message-bubble/file-displays.tsx`
at line 1, The file
services/platform/app/features/chat/components/message-bubble/file-displays.tsx
failed the formatting check; run the project's formatter (oxfmt) with the
repository config to reformat this file, save the changes, and re-run the
lint/pipeline so the formatted file passes CI (ensure you target
file-displays.tsx and commit the updated file).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@services/platform/app/components/ui/data-display/file-preview-card.tsx`:
- Around line 7-10: The component file imports shared helpers formatFileSize and
middleEllipsis from a feature module, inverting module boundaries; extract these
functions into a proper shared utility (e.g., a new lib or components/ui utils
module) and update the import in the file that uses them (the FilePreviewCard
component) to import from that new shared location; ensure exports are updated
so formatFileSize and middleEllipsis are re-exported from the shared module and
all other usages are updated to the new import path.

In `@services/platform/app/components/ui/forms/select.tsx`:
- Around line 118-125: The size-specific height classes inside the cn(...) call
should be moved into a cva variant: create a cva (e.g., selectBase or
selectClasses) that includes the shared classes and a variants: { size: {
default: 'h-10', sm: 'h-8', lg: 'h-12' } } and a defaultVariant for size; then
replace the inline size conditionals in the className expression by calling that
cva with { size } and keep the boolean error handling and className prop merged
via cn(selectClasses({ size }), error && 'border-destructive
focus-visible:ring-destructive', className). Reference the existing cn(...)
invocation, the size prop/variable, and the error boolean when making the
change.

In `@services/platform/app/features/chat/components/chat-actions.tsx`:
- Around line 138-202: Extract the duplicated DeleteDialog JSX into a single
shared variable or helper component (e.g., const deleteDialog or
<ChatDeleteDialog />) and reuse it in both branches instead of rendering it
twice; include the same props: open={isDeleteDialogOpen},
onOpenChange={setIsDeleteDialogOpen}, title={tChat('deleteChat')},
deleteText={tChat('deleteChat')}, isDeleting={isDeleting},
onDelete={handleDelete}, and preserve the existing description logic that builds
the parts from tChat('deleteConfirmation', { title: '\x00' }) and uses
chat.title so behavior is identical in both the isArchived branch and the normal
branch.

In `@services/platform/app/features/chat/components/chat-history-sidebar.tsx`:
- Around line 380-394: Add accessible state and relationship for the archived
chats toggle: on the button that calls setShowArchived and uses showArchived
(the toggle controlling the collapsible), add aria-expanded={showArchived} and
aria-controls pointing to an id; then give the collapsible content element (the
container rendered/hidden by showArchived) that same id so screen readers can
associate the button with the collapsible region. Ensure the id is unique (e.g.,
archived-chats-panel) and update any conditional rendering to keep the id
present on the container when hidden.

In
`@services/platform/app/features/chat/components/message-bubble/file-displays.tsx`:
- Around line 28-44: Move the duplicated helpers formatFileSize and
middleEllipsis out of message-bubble/file-displays.tsx into a shared utility
module (e.g. create a new file like lib/utils/file.ts) and export them so other
components can import them; update imports in message-bubble/file-displays.tsx,
file-preview-card.tsx, chat-input.tsx, message.tsx (and any other consumers) to
import formatFileSize and middleEllipsis from the new shared module, ensuring
the exported function names and signatures remain unchanged.

In `@services/platform/app/features/chat/hooks/use-convex-file-upload.ts`:
- Around line 139-157: The current check in use-convex-file-upload rejects the
whole acceptedFiles batch if existingSize + incomingSize exceeds
CHAT_MAX_TOTAL_SIZE; change this to allow partial uploads by iterating through
acceptedFiles and accepting files until the remaining quota (CHAT_MAX_TOTAL_SIZE
- existingSize) is exhausted, build a new sizeAcceptedFiles array, call toast
with t('totalSizeExceeded') and t('someFilesSkippedDueToSize', { skipped: ... })
when any files were skipped, and return early only if sizeAcceptedFiles.length
=== 0; update code paths that use acceptedFiles to use sizeAcceptedFiles and
keep attachmentsRef.current updated accordingly.

In
`@services/platform/app/features/settings/providers/components/providers-table.tsx`:
- Around line 183-191: The ProviderRowActions component currently destructures
an unused provider as _provider; remove that unused prop from the component
signature and its props type and eliminate _provider from the destructuring
(leave only onEdit and onDelete with their types), then update any call sites
that currently pass a provider to ProviderRowActions so they stop passing that
argument (or, if the provider is actually needed, instead use the provider
inside ProviderRowActions); ensure TypeScript types for ProviderRowActions match
the new prop shape.

In
`@services/platform/app/features/settings/providers/hooks/use-providers-table-config.tsx`:
- Around line 44-48: The loading skeleton metadata is still set to
meta.skeleton.type = 'badge' but the column's cell renderer (cell: ({ row }) =>
... using row.original.modelCount) now renders plain muted text, causing a
loading-state mismatch; update the meta.skeleton entry for this column (or
remove it) to use a text/plain or default skeleton variant that matches the Text
span, e.g., change meta.skeleton.type from 'badge' to a text/line variant or
drop meta.skeleton entirely so the loading UI matches the cell implementation in
the cell function referencing row.original.modelCount.

In `@services/platform/app/globals.css`:
- Line 135: The dark theme has collapsed the semantic tokens --card and --muted
to the same value and uses inconsistent formatting; update the CSS token
definitions so --card has a distinct HSL lightness (different from --muted) to
restore visual hierarchy (affects components like selectable-row.tsx which rely
on bg-card), and normalize the token formatting to the same space-separated HSL
notation used by --muted to avoid accidental diffs; keep the change limited to
the dark theme token block and ensure the new --card value contrasts
appropriately with --muted.

In
`@services/platform/app/routes/dashboard/`$id/settings/providers/$providerName.tsx:
- Around line 515-564: The list rendering in the config.models.map callback uses
the array index as the React key on the TableRow (key={index}), which can cause
reordering bugs; change it to use the model's unique id instead (use
key={model.id}) in the TableRow for stable identity, and ensure the mapped item
has a reliable id (from model.id) when calling openEditDialog, Trash2
onClick/setDeleteIndex remains unchanged.
- Around line 1-2: The file's formatting failed the pipeline; run the project's
formatter to fix whitespace/ordering issues in the import block (e.g., the
createFileRoute and Link import and the ChevronRight/Pencil/Trash2 icon imports)
by running the repository's oxfmt command (bunx oxfmt with the repo
.oxfmtrc.json and --write) against this file, then re-run lint/CI to confirm the
format check passes.

In `@services/platform/convex/lib/attachments/process_attachments.ts`:
- Around line 181-189: The debug logging is referencing a nonexistent property
result.sheetCount; update the uses of the parseExcel result (the variable result
returned by ctx.runAction of
internal.node_only.documents.internal_actions.parseExcel) to use
result.sheets.length for the sheet count (e.g., in the debugLog call that logs
fileName, sheet count, totalRows) and make the identical change at the other
occurrence around the later block (previously lines 347-349) so both debugLog
locations read result.sheets.length instead of result.sheetCount.
- Around line 379-381: The failure detection currently compares on
attachment.fileName which can mask failures when filenames are duplicated;
update the filter in the failedSpreadsheets computation to compare fileId
instead: replace the some(...) predicate that uses d.attachment.fileName ===
a.fileName with a comparison of d.attachment.fileId === a.fileId (or
d.attachment.fileId === a.attachment.fileId if spreadsheetAttachments entries
mirror parsedSpreadsheets structure) so failures are tracked by unique fileId
rather than name.

In `@services/platform/convex/threads/queries.ts`:
- Around line 159-174: The getThreadStatus query lacks a returns validator for
runtime schema consistency; update the query declaration for getThreadStatus
(the query({...}) handler) to include a returns validator (e.g., returns:
v.union([v.string(), v.null()] ) or returns: v.nullable(v.string())/appropriate
type) so the runtime schema and docs match the handler that returns
metadata?.status ?? null; ensure the validator references the same expected
value shape as metadata.status.

In `@services/platform/lib/shared/schemas/providers.ts`:
- Line 5: Remove the unnecessary public export of the inferred type ModelTag
from providers.ts: stop exporting ModelTag (leave modelTagSchema as-is) so the
type remains internal until an in-repo consumer requires it; update any
references in this module to use the local type (ModelTag) but do not export it,
and run the build/linters to ensure the knip unused-export warning is
suppressed. Ensure you only change the export declaration for ModelTag and not
the modelTagSchema symbol.

In `@services/platform/messages/de.json`:
- Around line 2474-2479: The translation key "unarchive" currently uses
"Wiederherstellen" which is inconsistent; update the value for the "unarchive"
key in services/platform/messages/de.json to "Dearchivieren" so it matches the
rest of the archive UI (leave keys "archive", "archiveSuccess", "archiveFailed",
"unarchiveSuccess", and "unarchiveFailed" unchanged).

---

Outside diff comments:
In
`@services/platform/app/features/chat/components/arena/arena-model-selector.tsx`:
- Around line 43-73: Extract the duplicated model metadata logic into a hook
named useModelMetadata(providers, t): move the modelInfoMap useMemo and
renderTagIcons useCallback into that hook and add a getDisplayName(modelId)
callback that falls back to getModelShortName; update arena-model-selector.tsx
to call useModelMetadata(providers, t) and use the returned { modelInfoMap,
renderTagIcons, getDisplayName } instead of its local modelInfoMap and
renderTagIcons; ensure the hook returns the same behavior (ModelTagIcons usage,
tags defaulting to [], description undefined) and preserves dependencies.

In
`@services/platform/app/features/chat/components/message-bubble/file-displays.tsx`:
- Line 1: The file
services/platform/app/features/chat/components/message-bubble/file-displays.tsx
failed the formatting check; run the project's formatter (oxfmt) with the
repository config to reformat this file, save the changes, and re-run the
lint/pipeline so the formatted file passes CI (ensure you target
file-displays.tsx and commit the updated file).

In
`@services/platform/app/features/conversations/components/message-editor/file-attachments-list.tsx`:
- Around line 41-47: The remove button in file-attachments-list.tsx (the button
that calls onRemove(file.id) and renders <XIcon />) lacks an accessible name;
add an aria-label (for example aria-label={`Remove ${file.name}`} or a generic
aria-label="Remove attachment") to the button element so screen readers can
describe the action; update the JSX for the button that wraps the XIcon to
include the aria-label attribute and ensure it reflects the file being removed
when possible.

In `@services/platform/app/features/conversations/components/message.tsx`:
- Around line 156-182: The loop in the useEffect that iterates readyAttachments
and programmatically clicks anchors (using pendingDownloadFiles.current,
downloadingMessageId, message.attachments and setDownloadingMessageId) can
trigger browser download-blocking when multiple files are started too fast;
modify the logic to stagger downloads (e.g., iterate readyAttachments with a
small async delay between clicks or queue them via
requestAnimationFrame/setTimeout) or switch to a single zipped download,
ensuring you still delete filenames from pendingDownloadFiles.current and only
call setDownloadingMessageId(null) after all staggered downloads complete;
locate the readyAttachments processing inside the useEffect and implement the
delay/queue there.

In
`@services/platform/app/routes/dashboard/`$id/settings/providers/$providerName.tsx:
- Around line 639-649: The placeholder "e.g., 1536" in the Input rendered when
form.tags includes 'embedding' is hardcoded; replace it with a translation
lookup (use the same translation hook t used for the label, e.g.
t('providers.dimensionsPlaceholder')) and add the corresponding key
("providers.dimensionsPlaceholder": "e.g., 1536") to the message files so the
placeholder is internationalized; update the Input placeholder prop to use
t(...) instead of the literal string.

In `@services/platform/convex/lib/agent_chat/start_agent_chat.ts`:
- Around line 33-51: The local formatFileSize function in start_agent_chat.ts
duplicates formatting logic present in file-displays.tsx; move this helper into
the shared file-types.ts module and import it where needed. Specifically, remove
the formatFileSize implementation from
services/platform/convex/lib/agent_chat/start_agent_chat.ts, add the same
implementation (or better, canonical version) to
services/platform/app/features/shared/file-types.ts (or the existing
file-types.ts), export it as formatFileSize, and replace the local call sites in
start_agent_chat.ts (and in message-bubble/file-displays.tsx if applicable) to
import { formatFileSize } from the shared file-types.ts to avoid duplication and
keep formatting consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9ed7cbfa-582c-4f3d-b13d-5046e83f8de5

📥 Commits

Reviewing files that changed from the base of the PR and between 243b048 and ed02765.

⛔ Files ignored due to path filters (1)
  • services/platform/convex/_generated/api.d.ts is excluded by !**/_generated/**
📒 Files selected for processing (45)
  • services/platform/app/components/ui/data-display/file-preview-card.tsx
  • services/platform/app/components/ui/feedback/badge.tsx
  • services/platform/app/components/ui/forms/date-range-picker.module.css
  • services/platform/app/components/ui/forms/searchable-select.tsx
  • services/platform/app/components/ui/forms/select.tsx
  • services/platform/app/components/ui/navigation/navigation-menu.tsx
  • services/platform/app/components/ui/overlays/dropdown-menu.tsx
  • services/platform/app/components/ui/overlays/popover.tsx
  • services/platform/app/features/automations/components/automation-assistant/chat-input.tsx
  • services/platform/app/features/chat/components/arena/arena-model-selector.tsx
  • services/platform/app/features/chat/components/canvas/canvas-pane.tsx
  • services/platform/app/features/chat/components/chat-actions.tsx
  • services/platform/app/features/chat/components/chat-history-sidebar.tsx
  • services/platform/app/features/chat/components/chat-input.tsx
  • services/platform/app/features/chat/components/chat-interface.tsx
  • services/platform/app/features/chat/components/message-bubble/file-displays.tsx
  • services/platform/app/features/chat/components/model-selector.tsx
  • services/platform/app/features/chat/components/model-tag-icons.tsx
  • services/platform/app/features/chat/hooks/mutations.ts
  • services/platform/app/features/chat/hooks/queries.ts
  • services/platform/app/features/chat/hooks/use-convex-file-upload.ts
  • services/platform/app/features/conversations/components/message-editor/file-attachments-list.tsx
  • services/platform/app/features/conversations/components/message.tsx
  • services/platform/app/features/documents/components/team-multi-select.tsx
  • services/platform/app/features/settings/providers/components/provider-add-dialog.tsx
  • services/platform/app/features/settings/providers/components/provider-edit-dialog.tsx
  • services/platform/app/features/settings/providers/components/provider-edit-panel.tsx
  • services/platform/app/features/settings/providers/components/providers-page.tsx
  • services/platform/app/features/settings/providers/components/providers-table.tsx
  • services/platform/app/features/settings/providers/hooks/use-providers-table-config.tsx
  • services/platform/app/globals.css
  • services/platform/app/routes/dashboard/$id/settings/providers/$providerName.tsx
  • services/platform/convex/lib/agent_chat/start_agent_chat.ts
  • services/platform/convex/lib/attachments/process_attachments.ts
  • services/platform/convex/providers/file_actions.ts
  • services/platform/convex/threads/archive_chat_thread.ts
  • services/platform/convex/threads/delete_chat_thread.ts
  • services/platform/convex/threads/list_archived_threads.ts
  • services/platform/convex/threads/mutations.ts
  • services/platform/convex/threads/queries.ts
  • services/platform/convex/threads/validators.ts
  • services/platform/lib/shared/file-types.ts
  • services/platform/lib/shared/schemas/providers.ts
  • services/platform/messages/de.json
  • services/platform/messages/en.json
💤 Files with no reviewable changes (2)
  • services/platform/app/features/settings/providers/components/providers-page.tsx
  • services/platform/app/features/settings/providers/components/provider-edit-panel.tsx
👮 Files not reviewed due to content moderation or server errors (8)
  • services/platform/messages/en.json
  • services/platform/app/features/chat/components/model-tag-icons.tsx
  • services/platform/app/features/settings/providers/components/provider-edit-dialog.tsx
  • services/platform/app/features/settings/providers/components/provider-add-dialog.tsx
  • services/platform/app/components/ui/overlays/popover.tsx
  • services/platform/app/features/documents/components/team-multi-select.tsx
  • services/platform/convex/threads/delete_chat_thread.ts
  • services/platform/app/components/ui/overlays/dropdown-menu.tsx

Comment on lines +7 to +10
import {
formatFileSize,
middleEllipsis,
} from '@/app/features/chat/components/message-bubble/file-displays';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Avoid importing shared UI helpers from a feature module.

services/platform/app/components/ui/data-display/file-preview-card.tsx now depends on services/platform/app/features/chat/..., which inverts module boundaries. Extract formatFileSize/middleEllipsis to a shared utility module (e.g., under lib or components/ui utilities) and import from there.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/components/ui/data-display/file-preview-card.tsx`
around lines 7 - 10, The component file imports shared helpers formatFileSize
and middleEllipsis from a feature module, inverting module boundaries; extract
these functions into a proper shared utility (e.g., a new lib or components/ui
utils module) and update the import in the file that uses them (the
FilePreviewCard component) to import from that new shared location; ensure
exports are updated so formatFileSize and middleEllipsis are re-exported from
the shared module and all other usages are updated to the new import path.

Comment on lines 118 to 125
className={cn(
'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-lg border border-transparent bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 ring-1 ring-border focus-visible:ring-primary transition-[border-color,box-shadow] duration-150',
'flex w-full items-center justify-between whitespace-nowrap rounded-lg border border-transparent bg-input px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 ring-1 ring-border focus-visible:ring-primary transition-[border-color,box-shadow] duration-150',
error && 'border-destructive focus-visible:ring-destructive',
size === 'default' && 'h-10',
size === 'sm' && 'h-8',
size === 'lg' && 'h-12',
className,
)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Use cva for the size variant instead of conditional cn() patterns.

Per coding guidelines, named variants like size: 'default' | 'sm' | 'lg' should use cva for consistency. The current implementation uses conditional cn() which is reserved for boolean states.

♻️ Proposed refactor using cva
+const selectTriggerVariants = cva(
+  'flex w-full items-center justify-between whitespace-nowrap rounded-lg border border-transparent bg-input px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 ring-1 ring-border focus-visible:ring-primary transition-[border-color,box-shadow] duration-150',
+  {
+    variants: {
+      size: {
+        default: 'h-10',
+        sm: 'h-8',
+        lg: 'h-12',
+      },
+    },
+    defaultVariants: {
+      size: 'default',
+    },
+  },
+);

// Then in the component:
         <SelectPrimitive.Trigger
           ref={ref}
           id={id}
           className={cn(
-            'flex w-full items-center justify-between whitespace-nowrap rounded-lg border border-transparent bg-input px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 ring-1 ring-border focus-visible:ring-primary transition-[border-color,box-shadow] duration-150',
+            selectTriggerVariants({ size }),
             error && 'border-destructive focus-visible:ring-destructive',
-            size === 'default' && 'h-10',
-            size === 'sm' && 'h-8',
-            size === 'lg' && 'h-12',
             className,
           )}

As per coding guidelines: "ALWAYS USE cva for named variants (e.g., size: 'sm' | 'md' | 'lg', variant: 'primary' | 'secondary'). But DO NOT use cva for boolean states."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
className={cn(
'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-lg border border-transparent bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 ring-1 ring-border focus-visible:ring-primary transition-[border-color,box-shadow] duration-150',
'flex w-full items-center justify-between whitespace-nowrap rounded-lg border border-transparent bg-input px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 ring-1 ring-border focus-visible:ring-primary transition-[border-color,box-shadow] duration-150',
error && 'border-destructive focus-visible:ring-destructive',
size === 'default' && 'h-10',
size === 'sm' && 'h-8',
size === 'lg' && 'h-12',
className,
)}
const selectTriggerVariants = cva(
'flex w-full items-center justify-between whitespace-nowrap rounded-lg border border-transparent bg-input px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 ring-1 ring-border focus-visible:ring-primary transition-[border-color,box-shadow] duration-150',
{
variants: {
size: {
default: 'h-10',
sm: 'h-8',
lg: 'h-12',
},
},
defaultVariants: {
size: 'default',
},
},
);
// Then in the component:
className={cn(
selectTriggerVariants({ size }),
error && 'border-destructive focus-visible:ring-destructive',
className,
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/components/ui/forms/select.tsx` around lines 118 - 125,
The size-specific height classes inside the cn(...) call should be moved into a
cva variant: create a cva (e.g., selectBase or selectClasses) that includes the
shared classes and a variants: { size: { default: 'h-10', sm: 'h-8', lg: 'h-12'
} } and a defaultVariant for size; then replace the inline size conditionals in
the className expression by calling that cva with { size } and keep the boolean
error handling and className prop merged via cn(selectClasses({ size }), error
&& 'border-destructive focus-visible:ring-destructive', className). Reference
the existing cn(...) invocation, the size prop/variable, and the error boolean
when making the change.

Comment on lines +138 to +202
if (isArchived) {
return (
<ActionRow gap={1}>
<Tooltip content={tChat('unarchive')} side="bottom">
<Button
variant="ghost"
className="p-1"
size="icon"
onClick={handleUnarchive}
disabled={isUnarchiving}
aria-label={tChat('unarchive')}
>
<ArchiveRestore className="size-4" />
</Button>
</Tooltip>

<Tooltip content={tCommon('actions.delete')} side="bottom">
<Button
variant="ghost"
className="p-1"
size="icon"
onClick={() => setIsDeleteDialogOpen(true)}
aria-label={tCommon('actions.delete')}
>
<Trash2 className="size-4" />
</Button>
</Tooltip>

<DeleteDialog
open={isDeleteDialogOpen}
onOpenChange={setIsDeleteDialogOpen}
title={tChat('deleteChat')}
description={
<>
{(() => {
const parts = tChat('deleteConfirmation', {
title: '\x00',
}).split('\x00');
if (parts.length < 2) {
return tChat('deleteConfirmation', { title: chat.title });
}
return (
<>
{parts[0]}
<Text as="span" variant="body" className="font-semibold">
{chat.title}
</Text>
{parts[1]}
</>
);
})()}
<br />
<br />
<Text as="span" variant="muted">
{tChat('deletePermanentMessage')}
</Text>
</>
}
deleteText={tChat('deleteChat')}
isDeleting={isDeleting}
onDelete={handleDelete}
/>
</ActionRow>
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider extracting the DeleteDialog to reduce duplication.

The DeleteDialog component is rendered with nearly identical props in both the isArchived branch and the normal branch. Extracting it to a shared location would reduce code duplication.

♻️ Suggested refactor

Extract the DeleteDialog rendering to a variable before the conditional return:

const deleteDialog = (
  <DeleteDialog
    open={isDeleteDialogOpen}
    onOpenChange={setIsDeleteDialogOpen}
    title={tChat('deleteChat')}
    description={
      <>
        {(() => {
          const parts = tChat('deleteConfirmation', { title: '\x00' }).split('\x00');
          if (parts.length < 2) {
            return tChat('deleteConfirmation', { title: chat.title });
          }
          return (
            <>
              {parts[0]}
              <Text as="span" variant="body" className="font-semibold">
                {chat.title}
              </Text>
              {parts[1]}
            </>
          );
        })()}
        <br />
        <br />
        <Text as="span" variant="muted">
          {tChat('deletePermanentMessage')}
        </Text>
      </>
    }
    deleteText={tChat('deleteChat')}
    isDeleting={isDeleting}
    onDelete={handleDelete}
  />
);

// Then use {deleteDialog} in both branches

Also applies to: 245-278

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/features/chat/components/chat-actions.tsx` around lines
138 - 202, Extract the duplicated DeleteDialog JSX into a single shared variable
or helper component (e.g., const deleteDialog or <ChatDeleteDialog />) and reuse
it in both branches instead of rendering it twice; include the same props:
open={isDeleteDialogOpen}, onOpenChange={setIsDeleteDialogOpen},
title={tChat('deleteChat')}, deleteText={tChat('deleteChat')},
isDeleting={isDeleting}, onDelete={handleDelete}, and preserve the existing
description logic that builds the parts from tChat('deleteConfirmation', {
title: '\x00' }) and uses chat.title so behavior is identical in both the
isArchived branch and the normal branch.

Comment on lines +380 to +394
{/* Archived chats — pinned to bottom */}
<div className="border-border shrink-0 border-t py-4">
<button
type="button"
onClick={() => setShowArchived((prev) => !prev)}
className="text-muted-foreground hover:text-foreground flex items-center gap-1 px-2 text-sm font-medium transition-colors"
>
<ChevronRightIcon
className={cn(
'size-3.5 transition-transform',
showArchived && 'rotate-90',
)}
/>
{t('archived.title')}
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider adding aria-expanded for the collapsible section.

The toggle button controls a collapsible section. For improved accessibility, add aria-expanded to indicate the section's state to screen readers.

♿ Accessibility improvement
         <button
           type="button"
           onClick={() => setShowArchived((prev) => !prev)}
           className="text-muted-foreground hover:text-foreground flex items-center gap-1 px-2 text-sm font-medium transition-colors"
+          aria-expanded={showArchived}
+          aria-controls="archived-chats-section"
         >

And add the corresponding id to the collapsible content:

         {showArchived && (
-          <Stack gap={1} className="mt-1 max-h-48 overflow-y-auto">
+          <Stack gap={1} id="archived-chats-section" className="mt-1 max-h-48 overflow-y-auto">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/features/chat/components/chat-history-sidebar.tsx`
around lines 380 - 394, Add accessible state and relationship for the archived
chats toggle: on the button that calls setShowArchived and uses showArchived
(the toggle controlling the collapsible), add aria-expanded={showArchived} and
aria-controls pointing to an id; then give the collapsible content element (the
container rendered/hidden by showArchived) that same id so screen readers can
associate the button with the collapsible region. Ensure the id is unique (e.g.,
archived-chats-panel) and update any conditional rendering to keep the id
present on the container when hidden.

Comment on lines +28 to +44
export function formatFileSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

export function middleEllipsis(name: string, maxLength: number): string {
if (name.length <= maxLength) return name;
const extIndex = name.lastIndexOf('.');
const ext = extIndex > 0 ? name.slice(extIndex) : '';
const base = extIndex > 0 ? name.slice(0, extIndex) : name;
const available = maxLength - ext.length - 1; // 1 for the ellipsis char
if (available < 4) return name.slice(0, maxLength - 1) + '\u2026';
const front = Math.ceil(available / 2);
const back = Math.floor(available / 2);
return base.slice(0, front) + '\u2026' + base.slice(-back) + ext;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider moving shared utilities to a dedicated module.

These helper functions (formatFileSize, middleEllipsis) are used across multiple components (per the PR summary: file-preview-card.tsx, chat-input.tsx, message.tsx, etc.). Consider extracting them to a shared utility location like @/lib/utils/file.ts to improve discoverability and maintainability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/platform/app/features/chat/components/message-bubble/file-displays.tsx`
around lines 28 - 44, Move the duplicated helpers formatFileSize and
middleEllipsis out of message-bubble/file-displays.tsx into a shared utility
module (e.g. create a new file like lib/utils/file.ts) and export them so other
components can import them; update imports in message-bubble/file-displays.tsx,
file-preview-card.tsx, chat-input.tsx, message.tsx (and any other consumers) to
import formatFileSize and middleEllipsis from the new shared module, ensuring
the exported function names and signatures remain unchanged.

Comment on lines +181 to +189
const result = await ctx.runAction(
internal.node_only.documents.internal_actions.parseExcel,
{ storageId: toId<'_storage'>(attachment.fileId) },
);
debugLog('Parsed spreadsheet', {
fileName: attachment.fileName,
sheetCount: result.sheetCount,
totalRows: result.totalRows,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Use result.sheets.length here instead of result.sheetCount.

The paired parseExcel action returns sheets and totalRows, not sheetCount, so these reads become undefined at runtime and can also break type-checking.

🐛 Proposed fix
         debugLog('Parsed spreadsheet', {
           fileName: attachment.fileName,
-          sheetCount: result.sheetCount,
+          sheetCount: result.sheets.length,
           totalRows: result.totalRows,
         });
@@
-        text: `\n\n---\n**Spreadsheet: ${attachment.fileName}** (fileId: ${attachment.fileId}, ${result.sheetCount} sheet${result.sheetCount !== 1 ? 's' : ''}, ${result.totalRows} rows)\n\n${sheetTexts.join('\n\n')}\n---\n`,
+        text: `\n\n---\n**Spreadsheet: ${attachment.fileName}** (fileId: ${attachment.fileId}, ${result.sheets.length} sheet${result.sheets.length !== 1 ? 's' : ''}, ${result.totalRows} rows)\n\n${sheetTexts.join('\n\n')}\n---\n`,

Also applies to: 347-349

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/convex/lib/attachments/process_attachments.ts` around lines
181 - 189, The debug logging is referencing a nonexistent property
result.sheetCount; update the uses of the parseExcel result (the variable result
returned by ctx.runAction of
internal.node_only.documents.internal_actions.parseExcel) to use
result.sheets.length for the sheet count (e.g., in the debugLog call that logs
fileName, sheet count, totalRows) and make the identical change at the other
occurrence around the later block (previously lines 347-349) so both debugLog
locations read result.sheets.length instead of result.sheetCount.

Comment on lines +379 to +381
const failedSpreadsheets = spreadsheetAttachments.filter(
(a) =>
!parsedSpreadsheets.some((d) => d.attachment.fileName === a.fileName),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Track spreadsheet failures by fileId, not fileName.

If two uploads share the same filename, one successful parse will mask the other failure and the second file never reaches the fallback tool-processing path. Compare on attachment.fileId here.

🩹 Proposed fix
   const failedSpreadsheets = spreadsheetAttachments.filter(
     (a) =>
-      !parsedSpreadsheets.some((d) => d.attachment.fileName === a.fileName),
+      !parsedSpreadsheets.some((d) => d.attachment.fileId === a.fileId),
   );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const failedSpreadsheets = spreadsheetAttachments.filter(
(a) =>
!parsedSpreadsheets.some((d) => d.attachment.fileName === a.fileName),
const failedSpreadsheets = spreadsheetAttachments.filter(
(a) =>
!parsedSpreadsheets.some((d) => d.attachment.fileId === a.fileId),
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/convex/lib/attachments/process_attachments.ts` around lines
379 - 381, The failure detection currently compares on attachment.fileName which
can mask failures when filenames are duplicated; update the filter in the
failedSpreadsheets computation to compare fileId instead: replace the some(...)
predicate that uses d.attachment.fileName === a.fileName with a comparison of
d.attachment.fileId === a.fileId (or d.attachment.fileId === a.attachment.fileId
if spreadsheetAttachments entries mirror parsedSpreadsheets structure) so
failures are tracked by unique fileId rather than name.

Comment on lines +159 to +174
export const getThreadStatus = query({
args: { threadId: v.string() },
handler: async (ctx, args) => {
const authUser = await getAuthUserIdentity(ctx);
if (!authUser) {
return null;
}

const metadata = await ctx.db
.query('threadMetadata')
.withIndex('by_threadId', (q) => q.eq('threadId', args.threadId))
.first();

return metadata?.status ?? null;
},
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider adding a returns validator for consistency.

The query implementation is correct. However, based on learnings from this codebase, thread-related queries typically specify returns validators for runtime schema consistency and documentation purposes (e.g., isThreadGenerating at line 63 has returns: v.boolean()).

📝 Suggested improvement
 export const getThreadStatus = query({
   args: { threadId: v.string() },
+  returns: v.union(
+    v.literal('active'),
+    v.literal('archived'),
+    v.literal('deleted'),
+    v.null(),
+  ),
   handler: async (ctx, args) => {

Based on learnings: "For threads, getOrCreateSubThreadAtomic declares a returns validator... Internal/public mutations in this area are expected to specify returns validators for runtime schema and docs consistency."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/convex/threads/queries.ts` around lines 159 - 174, The
getThreadStatus query lacks a returns validator for runtime schema consistency;
update the query declaration for getThreadStatus (the query({...}) handler) to
include a returns validator (e.g., returns: v.union([v.string(), v.null()] ) or
returns: v.nullable(v.string())/appropriate type) so the runtime schema and docs
match the handler that returns metadata?.status ?? null; ensure the validator
references the same expected value shape as metadata.status.

Comment thread services/platform/lib/shared/schemas/providers.ts Outdated
Comment thread services/platform/messages/de.json Outdated
Comment on lines +2474 to +2479
"archive": "Archivieren",
"unarchive": "Wiederherstellen",
"archiveSuccess": "Chat archiviert",
"archiveFailed": "Chat konnte nicht archiviert werden",
"unarchiveSuccess": "Chat wiederhergestellt",
"unarchiveFailed": "Chat konnte nicht wiederhergestellt werden",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep the “unarchive” wording consistent with the rest of the archive UI.

Wiederherstellen reads more like restore-after-delete, while the surrounding archive flows already use Dearchivieren. Reusing the same term here will make the new chat action easier to recognize.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/messages/de.json` around lines 2474 - 2479, The translation
key "unarchive" currently uses "Wiederherstellen" which is inconsistent; update
the value for the "unarchive" key in services/platform/messages/de.json to
"Dearchivieren" so it matches the rest of the archive UI (leave keys "archive",
"archiveSuccess", "archiveFailed", "unarchiveSuccess", and "unarchiveFailed"
unchanged).

Israeltheminer and others added 3 commits April 9, 2026 09:36
- Replace disabled chat input with an archived banner showing archive
  icon, message text, and an Unarchive button (per design vhWot)
- Show archived thread count badge next to Archived section header
  in the sidebar (per design DdFT9)
- Always fetch archived threads so count is visible in collapsed state
- Add archivedBanner i18n strings (en, de)
- extract formatFileSize/middleEllipsis to @/lib/utils/format/file
  to fix module boundary violation in file-preview-card
- remove unused provider param from ProviderRowActions
- fix skeleton type from badge to text for models column
- add aria-expanded to archived section toggle for accessibility
- normalize --card CSS variable formatting in dark theme
- align German unarchive wording to "dearchivieren" for consistency
@Israeltheminer Israeltheminer merged commit 9bfc932 into main Apr 9, 2026
8 checks passed
@Israeltheminer Israeltheminer deleted the feat/chat-archive-file-upload-providers branch April 9, 2026 09:05
@Israeltheminer
Copy link
Copy Markdown
Collaborator Author

Fixes #1228

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant