Skip to content

feat: file truncation and empty PDF warnings for user testing - #14

Merged
aiedwardyi merged 2 commits into
mainfrom
feat/file-upload-warnings
Apr 8, 2026
Merged

aiedwardyi merged 2 commits into
mainfrom
feat/file-upload-warnings

Conversation

@aiedwardyi

@aiedwardyi aiedwardyi commented Apr 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • parseFile now returns a structured ParseResult with a warning field (truncated | empty)
  • Files exceeding 50k characters show: "file is too long - only the first ~20 pages were included"
  • Scanned/image-based PDFs with no extractable text show: "no text could be extracted (may be a scanned document)"
  • Warnings appear as auto-dismissing amber toast (5s) on both homepage and chat page
  • Homepage passes warnings via sessionStorage to the chat page since navigation happens immediately after parsing
  • Both EN and KO translations

Context

User test tomorrow involves uploading legal documents. Long contracts and scanned PDFs are common in legal workflows, and users need to know when content is missing.

Test results

  • Upload a file >50k characters (280k char test file) - [...file truncated] visible in prompt, warning passed to chat page
  • Upload supported small file - no warning, debate runs normally
  • Upload unsupported file type - existing "Supported formats" toast appears
  • Warnings display correctly in KO locale
  • Build passes cleanly

Return structured ParseResult from parseFile with warning field for
truncated (>50k chars) and empty (scanned/image-based) files.

Show warnings as auto-dismissing amber toast on both homepage and
chat page. Homepage passes warnings via sessionStorage since it
navigates away before the user can see them.

Both EN and KO translations included.
Match chat page header pattern - settings gets its own icon button,
avatar dropdown only contains sign out.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds user-facing warnings when uploaded documents are empty (e.g., scanned PDFs with no extractable text) or truncated due to a 50k character limit, and propagates those warnings across the home → chat navigation flow.

Changes:

  • parseFile now returns a structured ParseResult with an optional warning (truncated | empty).
  • Homepage and chat input accumulate parse warnings and display them as auto-dismissing amber toasts (plus sessionStorage handoff to chat).
  • Adds EN/KO localized warning strings and extends toast auto-dismiss to 5s.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
src/lib/file-parser.ts Introduces ParseResult + warning signaling for empty/truncated parses.
src/components/MessageInput.tsx Displays parse warnings in-chat and supports an initial warning passed from the chat page.
src/app/page.tsx Collects warnings during initial file parsing and stores them in sessionStorage for the chat page.
src/app/chat/page.tsx Reads stored warnings from sessionStorage and passes them into MessageInput for display.
Comments suppressed due to low confidence (1)

src/app/page.tsx:378

  • fileWarnings are computed here but never surfaced if the login gate triggers (you return before storing them or showing a toast). This means gated users won’t see the new truncated/empty warnings, and it also conflicts with the PR description claiming homepage warnings. Consider either showing them via setFileError(...) before returning, and/or persisting them in the pending debate/sessionStorage so the chat page can still display them after login.
      const { text: messageText, fileWarnings } = await buildPromptWithFiles()
      if (!messageText) return

      if (shouldShowLoginGate(!!session?.user)) {
        savePendingDebate({

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import { Send, Square, Paperclip, X, FileText, File, Loader2 } from "lucide-react"
import { cn } from "@/lib/utils"
import { parseFile, SUPPORTED_EXTENSIONS } from "@/lib/file-parser"
import type { ParseWarning } from "@/lib/file-parser"

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

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

ParseWarning is imported but never used. With the Next.js TypeScript ESLint config this will typically fail linting; please remove the unused type import (or use it to type parsed.warning).

Suggested change
import type { ParseWarning } from "@/lib/file-parser"

Copilot uses AI. Check for mistakes.
Comment on lines +72 to +76
const warnings: string[] = []
const results = await Promise.allSettled(
attachedFiles.map(async (af) => {
const content = await parseFile(af.file)
if (content && !content.startsWith('[Unsupported')) {
return `--- File: ${af.file.name} ---\n${content}`
const parsed = await parseFile(af.file)
if (parsed.warning === 'empty') {

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

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

warnings is being mutated (via push) from within the async callbacks passed to Promise.allSettled. This makes the warning order dependent on parse completion timing, and the subsequent warnings.join("\n") will not render line breaks in HTML unless the toast uses white-space: pre-line (otherwise newlines collapse). Prefer returning warnings alongside each parse result (then flatMap in the original file order), and render as a list / join with a visible delimiter or apply a whitespace-pre-line class.

Copilot uses AI. Check for mistakes.
Comment thread src/app/page.tsx
Comment on lines +328 to +333
const fileWarnings: string[] = []

if (files.length > 0) {
const results = await Promise.allSettled(
files.map(async (af) => {
const content = await parseFile(af.file)
if (content && !content.startsWith("[Unsupported")) {
return `--- File: ${af.file.name} ---\n${content}`
const parsed = await parseFile(af.file)

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

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

fileWarnings is mutated via push inside the async files.map used by Promise.allSettled, which makes warning ordering depend on parse completion timing. Consider returning { content, warning } from each mapper and then collecting warnings from the settled results in the original files order for deterministic UX.

Copilot uses AI. Check for mistakes.
Comment thread src/app/page.tsx
@@ -382,6 +394,9 @@ export default function Home() {
locale,
}
sessionStorage.setItem("quorum_config", JSON.stringify(config))

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

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

This only sets quorum_file_warnings when warnings exist, but it never clears the key when fileWarnings is empty. If stale data is present in sessionStorage (e.g., from a prior flow that didn’t reach /chat), the chat page may display outdated warnings. Consider sessionStorage.removeItem("quorum_file_warnings") before this conditional, or always set it to an empty array.

Suggested change
sessionStorage.setItem("quorum_config", JSON.stringify(config))
sessionStorage.setItem("quorum_config", JSON.stringify(config))
sessionStorage.removeItem("quorum_file_warnings")

Copilot uses AI. Check for mistakes.
Comment thread src/app/chat/page.tsx
const warningsRaw = sessionStorage.getItem("quorum_file_warnings")
if (warningsRaw) {
sessionStorage.removeItem("quorum_file_warnings")
try { setFileWarning(JSON.parse(warningsRaw).join("\n")) } catch { /* ignore */ }

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

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

JSON.parse(warningsRaw).join("\n") assumes the stored value parses to an array. If it parses to a non-array (or array contains non-strings), this will throw and warnings will be silently dropped. Consider validating with Array.isArray(...) and string-coercing/filtering elements before joining. Also note that \n won’t render as line breaks in the toast unless you apply white-space: pre-line (or render as a list).

Suggested change
try { setFileWarning(JSON.parse(warningsRaw).join("\n")) } catch { /* ignore */ }
try {
const parsedWarnings: unknown = JSON.parse(warningsRaw)
const normalizedWarnings = Array.isArray(parsedWarnings)
? parsedWarnings
.filter((warning): warning is Exclude<typeof warning, null | undefined> => warning != null)
.map((warning) => String(warning))
.join("\n")
: typeof parsedWarnings === "string"
? parsedWarnings
: ""
if (normalizedWarnings) {
setFileWarning(normalizedWarnings)
}
} catch { /* ignore */ }

Copilot uses AI. Check for mistakes.
Comment thread src/lib/file-parser.ts
Comment on lines +27 to 33
if (!result.trim()) {
return { text: '', warning: 'empty' }
}

if (result.length > MAX_FILE_CHARS) {
return result.slice(0, MAX_FILE_CHARS) + '\n[...file truncated]'
return { text: result.slice(0, MAX_FILE_CHARS) + '\n[...file truncated]', warning: 'truncated' }
}

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

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

New warning behavior (empty / truncated) and truncation logic is introduced here, but there are no unit tests covering it. Since the repo already uses Vitest for src/lib/* utilities, consider adding tests for parseFile using .txt inputs to verify: (1) whitespace-only returns { text: "", warning: "empty" }, (2) >MAX chars returns warning truncated and includes the truncation marker, (3) small files return no warning.

Copilot uses AI. Check for mistakes.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

})
)

if (warnings.length > 0) setFileError(warnings.join('\n'))

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

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

warnings.join('\n') will not render as multiple lines in the toast because the warning is displayed as plain text in a <div> (HTML collapses newlines). If multiple files trigger warnings, they’ll appear as a single wrapped line. Consider either rendering warnings as a list / inserting <br />s, or adding a whitespace-pre-line (or similar) style to the warning container, or joining with a visible delimiter (e.g., ).

Suggested change
if (warnings.length > 0) setFileError(warnings.join('\n'))
if (warnings.length > 0) setFileError(warnings.join(''))

Copilot uses AI. Check for mistakes.
Comment thread src/app/page.tsx
Comment on lines 326 to +399
@@ -351,15 +363,15 @@ export default function Home() {
}
}

return messageText || null
return { text: messageText || null, fileWarnings }
}

const handleSubmit = async () => {
if (isParsing || (!prompt.trim() && files.length === 0)) return

setIsParsing(true)
try {
const messageText = await buildPromptWithFiles()
const { text: messageText, fileWarnings } = await buildPromptWithFiles()
if (!messageText) return

if (shouldShowLoginGate(!!session?.user)) {
@@ -382,6 +394,9 @@ export default function Home() {
locale,
}
sessionStorage.setItem("quorum_config", JSON.stringify(config))
if (fileWarnings.length > 0) {
sessionStorage.setItem("quorum_file_warnings", JSON.stringify(fileWarnings))
}

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

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

buildPromptWithFiles now collects fileWarnings, but handleSubmit never displays them on the homepage when submission is blocked (e.g., !messageText early return or the login gate path). This means users can upload a scanned/empty PDF and get no warning, and warnings are also lost when savePendingDebate is used. Consider setting fileError from fileWarnings before any early return, and persisting fileWarnings alongside the pending debate (or otherwise ensuring the chat page can still show them after login).

Copilot uses AI. Check for mistakes.
@aiedwardyi
aiedwardyi merged commit 3095a4b into main Apr 8, 2026
6 checks passed
@aiedwardyi
aiedwardyi deleted the feat/file-upload-warnings branch April 8, 2026 15:34
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.

2 participants