Waldrop · Beta testing

Testing Guide

A walkthrough for beta testers, and a checklist for QA. Start at section 0, upload a file, then poke at whatever interests you. You do not need to know anything about Sui or Walrus to be useful here — if something confuses you, that is itself worth reporting.
Surface: app.waldrop.xyz Network: Sui Mainnet + Walrus Mainnet Package: 0x2f548bb4…c20b45 Plan for testing: Starter (0 USDC in beta) Last reviewed: 2026-08-13
New here? Do this, in this order
  1. Section 0 — sign in, get a little SUI, pick the Starter plan, upload one small file. About 15 minutes.
  2. Upload from wherever you keep files — your laptop, Google Drive, Dropbox, S3. Section 1 has one short table per source; only do the ones you actually use.
  3. Try a transform or two — section 3. You do not need to test all of them.
  4. Make a folder, drag files into it, share one — section 4.
  5. Anything odd? File it using the template in section 11.
You do not have to work through this document top to bottom. Use the parts that match what you are testing.
What the tags mean: P0 must work — if one of these fails, report it. P1 should work — worth reporting, less urgent. P2 edge cases — only if you are being thorough.

0 · First run — from zero to first upload

This is mainnet. There is no faucet, blobs cost real WAL, and every register_blob burns real gas. Walk this section end-to-end once before touching anything below — most "bugs" reported during beta are really a step skipped here.

The Free plan cannot upload
Free is capped at 0 GB of storage on chain — it exists to let you look around, not to store anything. Every upload test in this document requires Starter, which is priced at 0 USDC during beta. If uploads fail with a storage limit error, check your plan before filing anything.

0.1 Sign in P0

Two ways in, both fully supported. Pick whichever you have — you do not need a crypto wallet to test Waldrop.

A
Sign in with Google
No wallet, no extension, no seed phrase. A Sui address is created for you behind the scenes (zkLogin). Easiest way to start.
B
Connect a Sui wallet
Slush, Suiet, Nightly, Ethos, Surf — any Sui wallet. Use this if you already hold SUI, since you can fund yourself.
Case Priority Expected
Open app.waldrop.xyz in a clean/incognito window P0 Landing loads. Both sign-in options are offered
A — Sign in with Google P0 Comes back signed in, header shows a 0x… address
B — Connect a Sui wallet P0 Wallet popup appears, approving connects, header shows your wallet address
Reload the page P0 Still signed in, same address. A changed address means your files and funds are stranded — stop and report
Sign out, sign back in the same way P0 Same address again
Switch between the two methods P1 They are different accounts with different addresses and separate files. That is expected, not a bug
Sign in with a second Google account or wallet P1 Empty file list. No data visible from the first account
Why "same address" is a P0 check
Everything you own — SUI, your plan, your uploaded files — belongs to your address. If it changes between sessions, all of it becomes unreachable and looks like data loss. With Google sign-in the address is derived rather than stored, so this is worth confirming once before you go further. With a wallet it should never change.

0.2 Get mainnet SUI P0

You need gas. There is no mainnet faucet — SUI has to come from somewhere real.

01
From the team
Paste your 0x… address in the beta channel. Fastest route, and how most testers should start.
02
Exchange withdrawal
Buy SUI on any exchange listing it, withdraw to your address on the Sui network — not a wrapped/bridged variant.
03
How much
0.5 SUI covers a full test session comfortably. Each register_blob burns roughly 0.008 SUI.
Case Priority Expected
Balance shows in the header after funding P0 Header reads e.g. 0.8509 SUI, updates within ~10s of the transfer landing
Attempt an upload with 0 SUI P1 Clear "insufficient gas" message. Not a raw wallet error or a silent hang

0.3 Subscribe to Starter P0

Starter is 0 USDC during beta, so you do not need USDC — only enough SUI for the transaction gas.

Case Priority Expected
Open /plans P0 Four tiers listed. Starter shows its beta price
Subscribe to Starter, approve the transaction P0 Succeeds. Sidebar storage meter switches to 0 KB / 40.0 GB and the plan label reads Starter
Reject the transaction in the wallet instead P1 App returns to idle with no error toast. A user cancelling is not a failure
Try to subscribe again while already subscribed P1 Blocked or shown as "current plan" — never a duplicate Subscription object
Reload after subscribing P0 Plan persists. It lives on chain, so a reload must not lose it
Live plan limits (read from mainnet)
Tier Storage Files Max epochs SEAL
Free 0 GB 20 1
Starter 40 GB 5,000 26
Pro 200 GB 50,000 53
Enterprise unlimited unlimited 53
One Walrus epoch is 14 days, so Starter's 26-epoch cap is about a year of storage.

0.4 First upload — the smoke test P0

Do this once with a small local CSV before testing any connector. If it fails, nothing below is worth running.

Step Priority Expected
Upload → Local Files → drop a small CSV (< 1 MB) P0 File listed with name and size
Continue through config, leave defaults P0 Storage Settings sidebar shows file, size, format, publisher, folder
Run the upload, approve the transaction P0 Progress advances through encoding → publisher → registering → done
Open /files P0 File appears with a blob id, size, and expiry
Click through to the blob detail page P0 Contents render or download correctly
Sidebar storage meter P1 Increases by roughly the file size
Stop-and-report conditions
Do not continue testing — and do not file a normal bug — if any of these happen. They indicate something structurally wrong, and further testing will produce noise:

0.5 Environment hygiene P1

Once the smoke test passes, keep the session clean. Stale state causes more false positives than real defects.

01
Browser P0
Incognito, or clear cache between runs. Stale OAuth cookies are the single biggest false-positive source for Dropbox and Drive tests.
02
DevTools P1
Network + Console open. Catches silent failures and unhandled promise rejections that never surface in the UI.
03
Service health P0
Check app.waldrop.xyz/api/health first. It reports engine, publisher, aggregator and Sui RPC separately — if one is down, failures below are that, not a bug.
04
Connector creds P1
Have S3 / Azure / GCS keys and a Drive + Dropbox account ready before starting section 1, so a missing credential doesn't stall a run.
Check health before filing anything
curl https://app.waldrop.xyz/api/health returns 200 ok, 200 degraded (a non-critical component is down) or 503 down (a critical one is). Paste that output into any bug report — it separates "the product is broken" from "Walrus was having a moment".

1 · Step 1 — Source selection

Eight source tiles. Each connector has its own credential model and failure surface. Test the picker grid first, then each connector.

Connector coverage: Five connectors are required for every release: Local Files, Public URL, SFTP, Dropbox, Google Drive — all free to set up. Three are optional: AWS S3, GCS, Azure — paid cloud accounts required. Test these when you have credentials; don't block releases on them. Mark untested ones as "n/a" in the release log rather than "blocked".

1.1 General source picker

Case Priority Expected
All 8 tiles render (Local, URL, S3, GCS, Azure, SFTP, Drive, Dropbox) P0 No missing icons, no broken grid
Stepper shows "1 Source" active (amber circle) P0 Steps 2, 3 dimmed
Each tile clickable, routes to its config step P1 URL updates to ?source=<name>
Tile pills color-correct (Cloud / Server / Upload / Fetch) P1 Amber per design palette
Keyboard nav: Tab cycles tiles, Enter opens P2 Focus ring visible

1.2 Local Files SOURCE

Case Priority Expected
Drag-drop one CSV P0 File listed with name + size + ✓
Click-to-browse picks one file P0 Same result as drag-drop
Upload a 100 MB file P0 Accepted, Continue enables
Upload 21 MB file (over limit) P0 Accepted — the app sets no per-file cap on local files
Drop 5 small CSVs at once P1 All listed, total size shown, "Each file separately" default
Drop .xlsx — auto-converts to .csv P1 UI shows converted indicator, content matches
Drop .json + .parquet P1 Both detected with correct file-type pill
Drop a binary (.png) P1 Marked "stored as-is", pipeline disabled
Drop a zero-byte file P2 Rejected clearly OR uploaded as empty blob — no silent freeze
Drop Unicode filename (परीक्षण.csv) P2 Name preserved through upload + BlobRef
Drop 19.9 MB file (just under limit) P2 Accepts cleanly, no off-by-one

1.3 Public URL SOURCE

Case Priority Expected
Fetch a public CSV URL P0 File downloaded, preview shows rows
Fetch a 404 URL P0 "URL fetch failed: 404" — no silent failure
Fetch non-HTTPS (http://...) P1 Rejected with security message OR upgraded to HTTPS
Fetch URL returning > 100 MB P1 Server aborts mid-stream, clear error
Fetch JSON URL P1 Detected as JSON, pipeline applies
Follow redirect (302 → 200) P2 Lands the final bytes
SSRF probe: http://169.254.169.254/... P2 Blocked. Same for localhost, 127.0.0.1, 10.*

1.4 AWS S3 SOURCE OPTIONAL

Case Priority Expected
Valid bucket + region + access key + secret P0 Bucket listing loads
Wrong secret access key P0 "S3 authentication failed — check Access Key ID and Secret"
Typo'd bucket name P0 "Bucket not found"
Wrong region (bucket us-east-1, entered eu-west-2) P0 Region mismatch OR bucket-not-found
Key has ListBucket but not GetObject P1 Listing works, fetch fails with permission error
Pick S3 object > 100 MB P1 Rejected with size limit message
Object key with special chars (spaces, +, %) P1 Fetched correctly, no URL-encoding bugs
KMS-encrypted objects P2 Works with KMS perms, clean error without
Verify credentials not logged P2 Grep server logs for access key value — zero hits

1.6 Azure Blob SOURCE OPTIONAL

Case Priority Expected
Valid bucket + fresh ya29. token P0 Listing loads
Expired token (> 1h old) P0 "GCS auth failed — provide a valid access token"
Token without devstorage.read_only scope P0 Permission error, not generic auth
Service account JSON instead of OAuth P1 Documented path works
Bucket name with dots (my.bucket.name) P1 No HTTPS cert issues
Token with leading whitespace (paste bug) P2 Trimmed OR clear format error

1.6 Azure Blob SOURCE

Case Priority Expected
Valid account + container + SAS starting with ? P0 Container listing loads
SAS pasted without leading ? P0 Explicit "must start with '?'" — not silent fail
SAS scoped without Read permission P0 "SAS token needs Read permission on this blob"
Expired SAS (past se= param) P1 Clean expiry error
SAS oversold with Write+Delete P1 Works (read-only used) — document as known harmless
Container name with Unicode P2 Properly URL-encoded

1.7 SFTP SOURCE

Case Priority Expected
Host + port 22 + valid username + password P0 Connection success, listing loads
SSH private key auth (paste OpenSSH key) P0 Same result as password
SSRF block: localhost, 127.0.0.1, 10.0.0.1, 192.168.1.1 P0 All rejected with "private/loopback not allowed"
Wrong password P0 "Auth failed" — not generic "connection failed"
Non-default port (e.g. 2222) P1 Works
Invalid host (nonexistent.example.invalid) P1 DNS error, fast fail (< 5s)
Malformed private key (truncated paste) P1 "Invalid private key format" — not stack trace
SFTP file > 100 MB P1 Verify limit (20 vs 25 MB), fix docs or code
Path with deep nesting P2 Works
Path points to directory not file P2 Error clarifies the difference

1.8 Google Drive SOURCE

Case Priority Expected
OAuth sign-in → approve → return P0 "Connected ✓", file list loads
Service account JSON paste flow P0 Currently shows "not yet wired" — verify intentional
SA email not added to file share list P0 File invisible (expected). UX hints about sharing
OAuth env vars missing in .env.local P1 "Drive OAuth not configured — set GDRIVE_CLIENT_ID …"
Consent screen Testing mode + non-test-user P1 Google blocks at OAuth, error visible
State cookie expired (wait > 10 min on OAuth screen) P1 "OAuth state mismatch (possible CSRF) — please retry"
OAuth token expired (1h) — try upload after wait P1 Clean re-auth prompt or auto-refresh
Google Workspace doc (not regular file) P2 Skip or export-to-format; no silent fail

1.9 Dropbox SOURCE

Case Priority Expected
OAuth: Connect Dropbox → approve → return P0 "Connected ✓", List Files works
Paste-token (sl.B…) instead of OAuth P0 List Files works without OAuth
Revoke app in Dropbox, retry upload P0 "Dropbox auth failed — token expired or invalid"
Dev-mode app + non-dev-user account P1 Dropbox blocks, clear error
App with files.content.read not ticked P1 Listing works, fetch fails — scope issue surfaced
Dropbox Paper / shared link target P2 Skipped or "unsupported" error

2 · Step 2 — Config

Storage duration slider + upload strategy radio + sidebar settings. Verify state persists across step navigation.

2.1 Storage duration slider

Case Priority Expected
Slider min = 1 epoch (14 days on mainnet) P0 Cannot go below 1
Slider max = 26 epochs P0 Cannot exceed 26. "Max" button → 26
Drag slider → epoch count + day estimate update live P0 "~1 day" → "~12 days" → "~26 days"
Value persists Step 2 → Step 3 → back P1 Retained, not reset to 1
Plan tier cap (e.g. Free = max 7 epochs) P1 Auto-clamps OR shows upgrade hint

2.2 Upload strategy radio

Case Priority Expected
"Each file separately" default (recommended) P0 Amber-highlighted card
Switch to "Bundle into archive" P0 Tooltip explains "one tx for whole batch"
Switch to "Quilt batching" P0 Notes about ≤ 10 MB + "encryption not supported yet"
Quilt + Encrypt simultaneously P0 UI prevents OR errors clearly at upload
Quilt with one file > 10 MB P1 Rejected: "Quilt is for ≤ 10 MB files"
Strategy persists across step nav P1 Doesn't reset on every back nav

2.3 Storage Settings sidebar

Case Priority Expected
Sidebar shows File, Size, Format, Publisher, Encryption P1 All 5 populated. Encryption = SEAL · threshold when on
Upload Method pill matches Step 2 strategy P1 "Quilt batch" green pill when Quilt chosen
Estimated Cost — disclaimer or live estimate P1 "Walrus pricing not loaded yet" OR FROST/SUI estimate
Share Access — paste Sui address, click Add P2 Bundled into upload PTB, visible in BlobStore viewers
Share Access — malformed address (no 0x) P2 Validation error before submit

3 · Step 3 — Transform pipeline

Five categories, twenty-three transforms. Each tested with happy path + 1-2 edge cases. Use one baseline CSV across every transform so output diffs stay interpretable.

Baseline test CSV

Use this for every transform test:

match_no,stage,team_a,team_b,score_a,score_b,date
1,Group A,Hitters,Kings,45,42,2026-04-01
2,Group B,Bosses,Royals,38,41,2026-04-02
3,Group A,Stump Smashers,Lions,52,49,2026-04-03
4,Group B,Highvoltage,Warriors,30,35,2026-04-04
5,Group A,Hitters,Stump Smashers,48,46,2026-04-05

Add a row with null score_b to test null handling. Gives you nulls, numeric + string columns, duplicate stage values for filtering, and dates for sorting.

3.1 General pipeline behavior

Case Priority Expected
All 5 category buttons render and toggle P0 Filter, Shape, Enrich, Security, Aggregate
Each category reveals transforms with themed color P0 Amber, green, blue, coral, purple
Empty-state placeholder visible when no steps P0 "No transform steps — data stored as-is"
Refresh Preview → Data Preview reflects pipeline P0 15-row preview matches applied transforms
Add 3 transforms in order, all apply cumulatively P1 Preview shows compound result
Remove middle transform from 3-step pipeline P1 Remaining 2 still apply, preview re-renders
Reorder transforms (drag/drop if supported) P1 Output reflects new order
Pipeline of 10+ transforms P2 No UI lag, preview computes < 3s
Beginners: you can stop here
Every transform behaves the same way — add it, fill in its fields, watch the preview update, then reorder or remove it. Try that with two or three of your choice and section 3 is covered.

The tables below are an exhaustive per-transform reference. Open them only if you are chasing a problem in one specific transform.
Per-transform reference — every field and edge case (Filter, Shape, Enrich, Security, Aggregate)

3.2 Filter & Select 6 TRANSFORMS

Transform · Case Priority Expected
Filter: stage == "Group A" P0 3 rows remain
Filter: score_a > 40 P0 Correct rows kept
Select: keep match_no, team_a, team_b P0 Other columns gone
Limit: first 2 rows P0 Exactly 2 rows
Sample: random 2 rows P0 2 rows, different on each Refresh
Deduplicate: after injecting duplicate row P0 Duplicate removed
Validate: schema score_a is integer P0 Passes clean; fails clearly on injected string
Filter with empty result set P1 0 rows, no crash
Select non-existent column P1 "Column not found"
Limit 0 or negative P1 Validation prevents OR returns 0 rows cleanly

3.3 Shape & Transform 9 TRANSFORMS

Transform · Case Priority Expected
Rename: team_a → home_team P0 Header changes
Cast: match_no int → string P0 String-styled values
Sort: by score_a desc P0 Row order matches
Fill Null: missing score_b0 P0 Nulls replaced
Expression: diff = score_a - score_b P0 New column with correct values
Replace: "Group" → "Pool" in stage P0 Values updated
Split: date on - → y/m/d P0 3 new columns
Flatten: on nested JSON column P0 Nested keys become top-level
Map: uppercase on team_a P0 Values uppercased
Rename to existing column name P1 Validation error or auto-suffix
Cast string → int with non-numeric value P1 Null'd with warning OR error with row number
Expression: divide by zero / null operand P1 Null result, no crash
Split: delimiter not in string P1 Single column with original, rest null

3.4 Enrich & Combine 4 TRANSFORMS

Transform · Case Priority Expected
Concat: team_a + " vs " + team_b → matchup P0 "Hitters vs Kings" etc.
Join: with second CSV on key column P0 Joined rows visible; verify left/inner/outer
Lookup: stage code → stage name from static table P0 Resolved column appears
Window: rolling 2-row sum of score_a P0 Window column correct
Join with no matching keys P1 Left rows preserved with nulls OR dropped — matches mode
Lookup key not in lookup table P1 Null returned, no crash
Window over column with nulls P1 Handled per documented semantics

3.5 Security & Privacy 2 TRANSFORMS

Transform · Case Priority Expected
Mask: redact team_a to *** P0 Preview shows masked values
Encrypt: SEAL-encrypt score_a column P0 Ciphertext placeholder; sidebar pill = SEAL · threshold
Encrypt + Quilt strategy combo P0 Blocked per Quilt's "encryption not supported yet"
Free plan tier + Encrypt P1 Blocked at register_blob OR earlier with upgrade prompt
Mask format-preserving (emails → x***@***.com) P1 Works if documented
Encrypt multiple columns P2 All columns encrypted under single SEAL identity

3.6 Aggregate & Analyze 3 TRANSFORMS

Transform · Case Priority Expected
Aggregate: group by stage, sum score_a P0 2 rows (Group A, B) with sums
Aggregate: count, avg, min, max P0 Each function returns correct value
Pivot: rows → columns on stage P0 New "Group A" / "Group B" columns
Unpivot: collapse score_a + score_b into one column P0 Row count doubles, metric/value pair
Aggregate on column with nulls P1 Nulls excluded per documented behavior
Pivot creating duplicate column names P1 Auto-suffixed or clear error

4 · Folders

Folders are flat — no nesting, by design. "General" is not a real folder: it is the absence of one, so it can never be renamed, deleted or shared. The cap is 10 folders per store, admin-adjustable on chain.

A move is a permission change
Folder access is resolved at decrypt time, not snapshotted when the folder was shared. Dropping a file into a shared folder makes it readable by that folder's viewers immediately, and moving it out revokes just as fast. Most of the P0 cases below exist to check the UI says so before you sign.

4.1 Create, rename, delete FOLDERS

Case Priority Expected
/files → "+ New folder" → name it → approve P0 Card appears in the FOLDERS grid showing "0 files"
Read the create dialog before confirming P0 Warns that folder names are public on chain. If this text is missing, that is a P0 bug — users cannot discover it anywhere else
Submit an empty name P1 Blocked client-side, button disabled
Paste a 200-character name P1 Blocked at 128 bytes with a byte counter — not a character count
Name it with emoji, e.g. 40 × 🎉 P2 Counter reflects bytes (emoji cost 4 each), so ~32 emoji hits the cap. A character counter here would be a bug
Create an 11th folder P1 "+ New folder" is hidden, or the call aborts with a limit message — never a raw Move abort code
Rename a folder, reload P0 New name persists (it is on chain)
Delete an empty folder P0 Removed from the grid
Try to delete a folder containing files P0 Delete is disabled with "Move N files out first". It must not let you sign a transaction that is guaranteed to abort
Check the "General" card's ⋯ menu P1 There is no ⋯ menu. General is not an on-chain object

4.2 Moving files FOLDERS

Case Priority Expected
Drag a file row (grip handle) onto a folder card P0 Card highlights amber, ghost chip follows the cursor, drop moves the file
Select 3 files, drag one of them P0 All 3 move together — dragging a selected row drags the whole selection
Drag an unselected row while others are selected P1 Only that one moves
Drop on empty space P1 Nothing happens, no transaction
Press Escape mid-drag P2 Drag cancels cleanly
Open a folder, drag a file onto the "All files" breadcrumb P1 Moves back to General — this is the only way out once inside a folder
Use checkboxes + "Move to…" bar instead P0 Same result. This path must work — it is the keyboard-accessible one
On a phone: drag using the grip handle P0 Works with a finger, and the list still scrolls when you swipe anywhere other than the grip
Move a file into a folder, then reload P0 File stays in the folder; counts on both cards are correct
Move the same file to the same folder twice P1 Counts do not drift. A folder showing "1 file" that is actually empty can never be deleted

4.3 Folder sharing SECURITY

Case Priority Expected
Folder ⋯ → Share → add a second address P0 Address listed; card shows a copper dot and "shared N"
Read the share dialog before confirming P0 States it shares the folder including files added later — not a snapshot of N files
Add your own address P1 Blocked with "you already have access"
Add the same address twice P1 Blocked before signing
Enter a malformed address P1 Validation message, Grant disabled
Drag a file INTO the shared folder P0 Confirmation appears naming how many people gain access. Button reads "Move and share", not "Move"
Check that file's row in /files P0 Shows a VIA FOLDER · N badge, distinct from the per-file SHARED badge — they are revoked in different places
Sign in as the shared-with account → Shared with Me P0 Can see and decrypt files in that folder
Owner moves the file OUT, viewer refreshes P0 Access is gone for new decryptions. Note: SEAL keys already issued stay valid until they expire — the dialog says so, and that is correct behaviour, not a bug
Revoke the viewer, refresh as them P0 Folder no longer listed

4.4 Upload straight into a folder

Case Priority Expected
Upload wizard → Storage Settings → Folder dropdown P0 Lists General + every folder. Defaults to General
Pick a shared folder P0 Warns "N people will be able to read this upload" before the upload runs
Complete the upload P0 File lands in that folder in one transaction — never uploaded to General then moved
Start a second upload afterwards P1 Picker is back to General. A sticky folder would silently drop the next file into a shared one

6 · End-to-end happy paths

Once per-step suites pass, run full E2E flows. Each should complete in under 30 seconds on a 1 MB file.

E2E-1 · Local CSV → no transforms → plaintext upload

Drop 1 MB CSV → Continue → no transforms → wallet sign → success. Result page shows blob_id + suivision link. On-chain confirm < 5s. Re-fetch via Files tab — bytes match original SHA-256.

E2E-2 · Local CSV → Filter + Sort → encrypted upload

Filter (score_a > 40) + Sort (date asc) + Encrypt. Wallet signs once (SEAL encrypt needs no signature). BlobRef has encrypted: true + sealMarker set. Decrypt prompts SEAL session sig → returns filtered + sorted CSV.

E2E-3 · S3 → Aggregate → Quilt batching

Pick 5 small CSVs from S3 (≤ 1 MB each). Aggregate group-by-stage. Strategy: Quilt. Single Walrus blob, each output addressable. Single register_blob tx — cheaper than 5 separate uploads.

E2E-4 · SFTP → Mask PII → Bundle into archive

Fetch 3 CSVs from SFTP. Mask the email column. Strategy: Bundle (tar). Post-upload: download bundle, untar locally, verify PII is masked in all 3 files.

E2E-5 · Drive → Encrypt + Share with second wallet

OAuth Drive, pick a CSV. Encrypt + add second Sui address as viewer. Wallet 1 signs upload. Switch to Wallet 2, fresh session, open from Shared with Me — decrypt succeeds. Try a third (unauthorized) wallet — seal_approve denies with explicit "not authorized".

E2E-6 · Resume after register failure

Drain wallet to ~0.001 SUI. Upload — publisher PUT succeeds, register_blob fails with InsufficientGasError. UI surfaces "wallet needs N MIST + Retry". Top up the address (no mainnet faucet — exchange withdrawal or the beta channel), click Retry → calls registerOnly with the checkpoint, no re-PUT, success.

7 · Limits & quotas

Resolved 2026-08-13 — the guide was right This document previously claimed a 20 MB per-file cap pending AWS verification. Verified against the source: no 20 MB constant exists anywhere in the codebase. All five cloud connectors enforce MAX_BYTES = 100 * 1024 * 1024. The Connectors guide's 100 MB was correct; this checklist was stale. Every limit in the table below is now read from code, with the constant named so it can be re-checked rather than trusted.
Surface Current limit Test case Expected
Local Files no explicit cap Drop a 150 MB file Accepted by the app. Bounded downstream by the publisher's --max-body-size and browser memory
S3 / GCS / Azure / Dropbox / Drive 100 MB / file Pick a 101 MB object Rejected pre-fetch on Content-Length: "File too large… limit is 100 MB per file"
S3 / GCS / Azure / Dropbox / Drive 100 MB / file Object whose Content-Length under-reports its real size Stream aborts mid-transfer at the cap — capStream enforces it byte-by-byte, not just on the declared header
Quilt patch 10 MB / file Add an 11 MB file to a Quilt batch Rejected: "Quilt requires ≤ 10 MB"
Quilt batch total 250 MB Batch summing to 260 MB Rejected before upload starts
Bundle (tar) 250 MB total Bundle summing to 260 MB Rejected: browsers can't reliably allocate one buffer past ~300 MB, and tar mode needs the whole archive in memory
Total storage Per plan (40 GB Starter) Upload exceeding quota Block at register_blob with plan-tier error
Blobs per BlobStore Per plan tier File #N+1 on Free Block with upgrade prompt
Storage duration 1–26 epochs (Starter cap; 53 on Pro) Try epoch 0 / 27 Slider clamps, API rejects

8 · Error handling

Every SDK error class should surface as a useful UI message — never a stack trace, never a silent failure.

Error class Trigger UI must show
InsufficientGasError Drain wallet, upload "Top up wallet" + Retry button. No faucet link — mainnet has none
RegistrationError Reject tx in wallet Retry register without re-upload (checkpoint flow)
BlobStoreNotFoundError Read blobs for non-subscribed address Empty state, not "error"
BlobNotFoundError Fetch with invalid blob_id "Blob not found — id malformed or expired"
AggregatorError Walrus aggregator 5xx "Walrus is having trouble — retry shortly"
DecryptionError Decrypt as unauthorized wallet "You don't have access to this blob"
SealNotInstalledError Should never happen in dapp If seen → file P0 bug
Plan-tier denied Free tier tries Encrypt "Encryption requires Starter+ — Upgrade"
Subscription expired Past-expiry Subscription "Subscription expired — renew to upload"
Recovery actions P0
Every error displays a recovery CTA — Top up / Retry / Upgrade / Reconnect / Re-auth. Never a dead message.
No raw traces P0
Stack traces only in DevTools console, never visible to users.
Offline detection P1
Network drops mid-upload → UI pauses, offers retry when back online.
Wallet popup closed P1
User closes wallet mid-sig → "Upload cancelled", checkpoint preserved.

9 · Cross-browser & responsive

Target Priority Notes
Brave (latest, macOS) P0 Primary dev browser
Chrome (latest) P0 All flows pass
Safari (latest macOS) P1 Webkit-specific: file picker, drag-drop, SubtleCrypto
Firefox (latest) P1 All flows pass
Mobile Safari (iOS) P1 iOS file picker sheet
Mobile Chrome (Android) P2 Android share sheet
Viewports: 1920, 1440, 1024, 768, 390 P1 No overflow, sidebar collapses < 1024
Light mode (if supported) P2 If not, enforce dark — don't half-break
Wallets: Sui Wallet, Slush, Phantom, Suiet P1 Each signs register_blob PTB

10 · Regression list

Fill as you ship fixes: Specific bugs found in past releases. Re-test these every time. Every fix that ships leaves a row here — otherwise the same bug ships again three milestones later.
Bug Found in Test
(empty — fill as bugs are fixed)
Example: SAS without `?` silently failed M2 Paste SAS without `?` → must show explicit error
Example: Quilt + Encrypt didn't block at UI M2 Quilt → Encrypt step → must block before Continue

11 · Bug report template

When something fails, log it in this exact format:

### Bug — <one-line summary>

**Surface:** First run / Step 1 / Step 2 / Step 3 / Folders / Morse / E2E
**Connector / Transform:** <name>
**Priority:** P0 / P1 / P2
**Browser + device:** Brave 1.x macOS / Safari iOS 18 / etc.
**Sign-in:** Google / Slush / Suiet / other wallet
**Plan:** Free / Starter / Pro
**Network:** Mainnet
**Build:** <commit sha>
**/api/health at the time:** <paste — separates a product bug from a
dependency outage>

**Steps to reproduce:**
1. ...
2. ...
3. ...

**Expected:** ...

**Actual:** ...

**Screenshot / video:** [attach]

**Console errors:** [paste]

**Network tab:** [request/response if relevant]

**Wallet tx (if got that far):** <suivision link>

Coverage summary

Section P0 P1 P2 Total
0 · First run — from zero to first upload 20 9 0 29
1 · Step 1 — Source selection 28 26 14 68
2 · Step 2 — Config 7 7 2 16
3 · Step 3 — Transform pipeline 31 17 2 50
4 · Folders 20 12 2 34
8 · Error handling 2 2 0 4
9 · Cross-browser & responsive 2 5 2 9
Total 110 78 22 210

Run all P0 (~85 cases) before any release. Time estimate: 3-4 hours solo, 1.5-2 hours with two testers in parallel.