Skip to content

Commit 8e55270

Browse files
committed
make loading of larger protos robust
while i'm here: enable loading broken local zips via CD recovery, not just sendsafely zips. Main change here is to standardize on one file loading path that is chunked and handles large protos, cleaning up the handling of proto decoding which previously had different bugs on different paths related to string sizes or being split across chunks. Also tidy up naming of tables and tracking, and start de-registering the blob for each chunk after it is loaded.
1 parent ea9f192 commit 8e55270

File tree

11 files changed

+775
-622
lines changed

11 files changed

+775
-622
lines changed

src/App.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,53 @@ function AppContent() {
253253

254254
return (
255255
<>
256+
{/* Malformed zip recovery banner */}
257+
{state.recoveryInfo && (
258+
<div
259+
style={{
260+
position: "fixed",
261+
top: "16px",
262+
left: "50%",
263+
transform: "translateX(-50%)",
264+
backgroundColor: "var(--bg-secondary)",
265+
border: "1px solid var(--accent-warning)",
266+
borderRadius: "8px",
267+
padding: "12px 16px",
268+
display: "flex",
269+
alignItems: "center",
270+
gap: "12px",
271+
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
272+
zIndex: 1000,
273+
maxWidth: "600px",
274+
}}
275+
>
276+
<div style={{ fontSize: "20px" }}>⚠️</div>
277+
<div style={{ flex: 1, fontSize: "13px" }}>
278+
<strong style={{ color: "var(--accent-warning)" }}>
279+
Zip file appears malformed or truncated
280+
</strong>
281+
<div style={{ color: "var(--text-muted)", marginTop: "4px" }}>
282+
File listing for {state.recoveryInfo.entriesCount.toLocaleString()} files was decoded, but could be incomplete
283+
</div>
284+
</div>
285+
<button
286+
onClick={() => dispatch({ type: "SET_RECOVERY_INFO", recoveryInfo: null })}
287+
style={{
288+
padding: "6px 12px",
289+
backgroundColor: "transparent",
290+
color: "var(--text-primary)",
291+
border: "1px solid var(--border-color)",
292+
borderRadius: "4px",
293+
cursor: "pointer",
294+
fontSize: "12px",
295+
fontWeight: "500",
296+
}}
297+
>
298+
Dismiss
299+
</button>
300+
</div>
301+
)}
302+
256303
<div
257304
className={`app-container ${
258305
!sidebarVisible || isStackgazerActive ? "sidebar-collapsed" : ""

src/components/DropZone.tsx

Lines changed: 16 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,8 @@ interface SendSafelyModalProps {
7272
setLoading: (loading: boolean) => void;
7373
loadingMessage: string;
7474
setLoadingMessage: (message: string) => void;
75-
recoveryEntriesCount: number | null;
76-
setRecoveryEntriesCount: (count: number | null) => void;
7775
fileLoading: boolean;
7876
setFileLoading: (loading: boolean) => void;
79-
waitForWorkers: () => Promise<import("../state/types").IWorkerManager>;
8077
}
8178

8279
function SendSafelyModalContent({
@@ -104,11 +101,8 @@ function SendSafelyModalContent({
104101
setLoading,
105102
loadingMessage,
106103
setLoadingMessage,
107-
recoveryEntriesCount,
108-
setRecoveryEntriesCount,
109104
fileLoading,
110105
setFileLoading,
111-
waitForWorkers,
112106
}: SendSafelyModalProps) {
113107
const [showOtherFiles, setShowOtherFiles] = useState(false);
114108
const [forceStep1, setForceStep1] = useState(false);
@@ -872,7 +866,7 @@ function SendSafelyModalContent({
872866
</div>
873867
</div>
874868

875-
{fileLoading && recoveryEntriesCount === null && (
869+
{fileLoading && (
876870
<div style={{ textAlign: "center", padding: "20px" }}>
877871
<span className="loading-spinner-small" />
878872
<div
@@ -887,66 +881,6 @@ function SendSafelyModalContent({
887881
</div>
888882
)}
889883

890-
{recoveryEntriesCount !== null && (
891-
<div style={{ padding: "20px" }}>
892-
<div
893-
style={{
894-
textAlign: "center",
895-
marginBottom: "16px",
896-
fontSize: "24px",
897-
}}
898-
>
899-
⚠️
900-
</div>
901-
<div
902-
style={{
903-
marginBottom: "12px",
904-
fontSize: "13px",
905-
fontWeight: "bold",
906-
color: "var(--accent-warning)",
907-
textAlign: "center",
908-
}}
909-
>
910-
Zip file appears malformed or truncated
911-
</div>
912-
<div
913-
style={{
914-
marginBottom: "20px",
915-
fontSize: "12px",
916-
color: "var(--text-muted)",
917-
textAlign: "center",
918-
}}
919-
>
920-
File listing for {recoveryEntriesCount.toLocaleString()}{" "}
921-
files was decoded, but could be incomplete
922-
</div>
923-
<div style={{ display: "flex", justifyContent: "center" }}>
924-
<button
925-
onClick={async () => {
926-
// User confirmed - clear recovery warning and tell ZIP worker to proceed
927-
setRecoveryEntriesCount(null);
928-
const workerManager = await waitForWorkers();
929-
await workerManager.proceedWithRecovery();
930-
// ZIP worker will emit initializeComplete and continue with normal flow
931-
// Modal will close when we transition to table-loading stage
932-
}}
933-
style={{
934-
padding: "10px 24px",
935-
backgroundColor: "var(--accent-primary)",
936-
color: "#fff",
937-
border: "none",
938-
borderRadius: "4px",
939-
cursor: "pointer",
940-
fontSize: "13px",
941-
fontWeight: "500",
942-
}}
943-
>
944-
Proceed
945-
</button>
946-
</div>
947-
</div>
948-
)}
949-
950884
{fileError && (
951885
<div
952886
style={{
@@ -1022,8 +956,7 @@ function DropZone() {
1022956
const [modalDismissable, setModalDismissable] = useState(false);
1023957
const [fileLoading, setFileLoading] = useState(false);
1024958

1025-
// CD recovery warning state - shown in modal when recovery is used
1026-
const [recoveryEntriesCount, setRecoveryEntriesCount] = useState<number | null>(null);
959+
// CD recovery warning state is now in global state (not local)
1027960

1028961
// Single function to close the SendSafely modal
1029962
const closeSendSafelyModal = useCallback(() => {
@@ -1033,7 +966,7 @@ function DropZone() {
1033966
setPackageError(null);
1034967
setPackageLoading(false);
1035968
setFileLoading(false);
1036-
setRecoveryEntriesCount(null);
969+
// Note: don't reset recoveryInfo - it persists to show banner after modal closes
1037970
}, []);
1038971

1039972
// Debug page state
@@ -1175,10 +1108,11 @@ function DropZone() {
11751108
closeSendSafelyModal();
11761109
}
11771110
},
1178-
onCdScanningComplete: (entriesCount: number) => {
1179-
// ZIP recovery was used - show warning in modal
1180-
// Keep fileLoading = true so we stay in step 4
1181-
setRecoveryEntriesCount(entriesCount);
1111+
onCdScanningComplete: async (entriesCount: number) => {
1112+
// ZIP recovery was used - store info for banner and auto-proceed
1113+
dispatch({ type: "SET_RECOVERY_INFO", recoveryInfo: {entriesCount} });
1114+
const workerManager = await waitForWorkers();
1115+
await workerManager.proceedWithRecovery();
11821116
},
11831117
onSendStackFileToIframe: (path: string, content: string, name?: string) => {
11841118
// Detect mode based on file path
@@ -1543,6 +1477,11 @@ function DropZone() {
15431477

15441478
// Stack data is now sent directly by controller via onSendStackDataToIframe callback
15451479
},
1480+
onCdScanningComplete: async (entriesCount: number) => {
1481+
// ZIP recovery was used - store info for banner and auto-proceed
1482+
dispatch({ type: "SET_RECOVERY_INFO", recoveryInfo: {entriesCount} });
1483+
await workerManager.proceedWithRecovery();
1484+
},
15461485
onSendStackFileToIframe: (path: string, content: string, name?: string) => {
15471486
// Detect mode based on file path
15481487
const isLabeled = path.includes("stacks_with_labels.txt");
@@ -1982,7 +1921,7 @@ function DropZone() {
19821921
justifyContent: "center",
19831922
}}
19841923
onClick={() => {
1985-
if (modalDismissable && recoveryEntriesCount === null) {
1924+
if (modalDismissable) {
19861925
closeSendSafelyModal();
19871926
}
19881927
}}
@@ -2045,20 +1984,16 @@ function DropZone() {
20451984
</button>
20461985
<button
20471986
onClick={() => {
2048-
// Don't allow closing modal while recovery warning is shown
2049-
if (recoveryEntriesCount === null) {
2050-
closeSendSafelyModal();
2051-
}
1987+
closeSendSafelyModal();
20521988
}}
20531989
style={{
20541990
background: "transparent",
20551991
border: "none",
20561992
color: "var(--text-secondary)",
20571993
fontSize: "18px",
2058-
cursor: recoveryEntriesCount === null ? "pointer" : "not-allowed",
1994+
cursor: "pointer",
20591995
padding: "0 4px",
20601996
lineHeight: "1",
2061-
opacity: recoveryEntriesCount === null ? 1 : 0.5,
20621997
}}
20631998
>
20641999
×
@@ -2249,11 +2184,8 @@ function DropZone() {
22492184
setLoading={setLoading}
22502185
loadingMessage={loadingMessage}
22512186
setLoadingMessage={setLoadingMessage}
2252-
recoveryEntriesCount={recoveryEntriesCount}
2253-
setRecoveryEntriesCount={setRecoveryEntriesCount}
22542187
fileLoading={fileLoading}
22552188
setFileLoading={setFileLoading}
2256-
waitForWorkers={waitForWorkers}
22572189
/>
22582190
)}
22592191
</div>

src/components/ErrorViewer.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ function ErrorViewer({
2424
}: ErrorViewerProps) {
2525
const { state, dispatch } = useApp();
2626
const [isLoading, setIsLoading] = useState(false);
27+
const [loadError, setLoadError] = useState<string | null>(null);
2728

2829
const handleViewFile = (filePath?: string) => {
2930
const pathToOpen = filePath || sourceFile;
@@ -47,6 +48,7 @@ function ErrorViewer({
4748
const tableNameForLoading = fullTableName || tableName;
4849

4950
setIsLoading(true);
51+
setLoadError(null); // Clear any previous error
5052
try {
5153
// Load the table with only the non-error files
5254
await state.workerManager.loadSingleTable({
@@ -75,7 +77,8 @@ function ErrorViewer({
7577
}, 100);
7678
} catch (err) {
7779
console.error("Failed to load available data:", err);
78-
alert(`Failed to load data: ${err instanceof Error ? err.message : String(err)}`);
80+
const errorMessage = err instanceof Error ? err.message : String(err);
81+
setLoadError(errorMessage);
7982
} finally {
8083
setIsLoading(false);
8184
}
@@ -170,6 +173,13 @@ function ErrorViewer({
170173
>
171174
{isLoading ? "Loading..." : `Load Available Data (${availableFiles.length} nodes, ${formatSize(totalSize)})`}
172175
</button>
176+
177+
{loadError && (
178+
<div style={{ marginTop: "16px", padding: "12px", backgroundColor: "var(--error-bg, #3c1f1f)", border: "1px solid var(--error-border, #f44336)", borderRadius: "4px" }}>
179+
<h4 style={{ margin: "0 0 8px 0", color: "var(--error-color, #f44336)" }}>Failed to Load Data</h4>
180+
<pre style={{ margin: 0, whiteSpace: "pre-wrap", wordBreak: "break-word", fontSize: "0.9em" }}>{loadError}</pre>
181+
</div>
182+
)}
173183
</div>
174184
)}
175185
</div>

0 commit comments

Comments
 (0)