feat: file truncation and empty PDF warnings for user testing - #14
Conversation
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.
There was a problem hiding this comment.
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:
parseFilenow returns a structuredParseResultwith an optionalwarning(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
fileWarningsare computed here but never surfaced if the login gate triggers (youreturnbefore 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 viasetFileError(...)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" |
There was a problem hiding this comment.
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).
| import type { ParseWarning } from "@/lib/file-parser" |
| 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') { |
There was a problem hiding this comment.
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.
| 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) |
There was a problem hiding this comment.
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.
| @@ -382,6 +394,9 @@ export default function Home() { | |||
| locale, | |||
| } | |||
| sessionStorage.setItem("quorum_config", JSON.stringify(config)) | |||
There was a problem hiding this comment.
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.
| sessionStorage.setItem("quorum_config", JSON.stringify(config)) | |
| sessionStorage.setItem("quorum_config", JSON.stringify(config)) | |
| sessionStorage.removeItem("quorum_file_warnings") |
| const warningsRaw = sessionStorage.getItem("quorum_file_warnings") | ||
| if (warningsRaw) { | ||
| sessionStorage.removeItem("quorum_file_warnings") | ||
| try { setFileWarning(JSON.parse(warningsRaw).join("\n")) } catch { /* ignore */ } |
There was a problem hiding this comment.
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).
| 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 */ } |
| 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' } | ||
| } |
There was a problem hiding this comment.
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.
748f7a7 to
d3766eb
Compare
There was a problem hiding this comment.
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')) |
There was a problem hiding this comment.
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., •).
| if (warnings.length > 0) setFileError(warnings.join('\n')) | |
| if (warnings.length > 0) setFileError(warnings.join(' • ')) |
| @@ -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)) | |||
| } | |||
There was a problem hiding this comment.
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).
Summary
parseFilenow returns a structuredParseResultwith awarningfield (truncated|empty)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
[...file truncated]visible in prompt, warning passed to chat page