Designing imports and exports as a recoverable transaction
A CSV import is a state transition. Treating it as a file-picker feature misses the part that matters: what happens to the user's current work when the next file is wrong?
QuoteBack is an estimate-review workspace built by TSHC Product Foundry. Its import accepts up to 100 anonymous estimates. During testing, an invalid file exposed the need to preserve an already-loaded review. The final implementation keeps that review when the new import fails. This article was prepared with AI assistance and checked against the component source.
Parse, validate, then commit
Keep the incoming file separate from the current collection. Parse it into candidate rows. Validate the complete candidate. Only replace application state after all rows have passed.
Here is an illustrative React pattern, simplified from the product's import boundary:
function importText(text: string) {
try {
const candidate = parseAndValidate(text);
setEstimates(candidate);
setError("");
setSelectedId(null);
} catch (error) {
setError(error instanceof Error ? error.message : "Import failed");
// Current estimates remain intact.
}
}
The key property is the location of the state write. Do not clear the current list before parsing and then attempt to reconstruct it from an error path.
Also distinguish selection from data. If a valid new import succeeds, an old selected identifier should not keep pointing at a previous item. QuoteBack clears selection and returns the review to its Open stage after a successful import.
CSV syntax is not the data schema
A syntactically valid CSV can still be an invalid estimate collection. Separate those decisions in both code and tests.
The parser must handle quoted commas and escaped double quotes. It should reject an unclosed quoted field and unexpected non-whitespace text after a closing quote. A row such as a quoted reference followed immediately by stray text deserves an error, not silent acceptance.
Then validate the schema: required headers, unique references, field counts and numeric rules. QuoteBack requires an estimate reference, value, days and touches. Values must be positive. Days and touches must be nonnegative whole numbers. Duplicate references are rejected. Optional outcome and sample flags use explicit allowed values.
A permissive numeric conversion is not enough. A blank cell can become zero, and a numeric parser can accept the beginning of a string while ignoring the rest. Decide which complete strings are allowed before converting them.
For this product, the limits are deliberate: 1–100 records, a 1 MB file limit and numeric bounds. These are implementation choices for a small review tool, not a claim that this parser is suitable for arbitrarily large datasets.
Recovery needs a useful error
An alert saying “something went wrong” avoids a crash but does little to help the user finish the job. Distinguish invalid content from a file-read failure.
The implemented workflow reports invalid numbers, duplicate references, missing headers or malformed fields. A file-read failure asks the user to try a UTF-8 CSV. The next improvement would be a structured row-and-column error list instead of relying on short strings; that is a proposed refinement, not a shipped feature.
Define the round-trip invariant
An export is only portable if the important state survives re-import.
For QuoteBack, that includes the reference, numeric inputs, outcome status and whether the row is fictional demo data. A Revisit item should not silently become Open. A sample item should not become evidence for local ranking.
type ReviewStatus = "OPEN" | "WON" | "LOST" | "LATER";
type PortableRow = {
estimate_id: string;
value: number;
days: number;
touches: number;
status: ReviewStatus;
is_demo: boolean;
};
An exported priority score is useful to a reader, but the current implementation computes the score again from validated inputs and current local evidence when importing. That distinction belongs in the contract: preserve decision state, recompute derived state.
Escape for the next consumer
React text rendering and report generation are separate paths. An HTML report assembled as a string must escape imported references itself. CSV values destined for spreadsheets need handling for formula-like prefixes.
QuoteBack quotes CSV cells, doubles embedded quotes and prefixes text beginning with formula-like characters with an apostrophe. That mitigation can change how a reference appears after re-import and spreadsheet applications can behave differently. It is a tradeoff to test with the target spreadsheet, not a universal safety guarantee.
Test transitions, not just examples
A useful acceptance matrix includes: a valid two-row import; a quoted comma in a reference; an unclosed quote; stray text after a closed quote; duplicate references; negative values; fractional integer fields; a valid export re-import; and a rejected import while a useful collection is already loaded.
QuoteBack's real file-import check confirmed the last case: the invalid CSV produced an error and the two existing estimates remained. Pure-function edge checks and export state tests supplemented that browser test.
The principle is transferable: a destructive transition should earn its state write.
Disclosure: TSHC makes QuoteBack. Product context: https://compliancechief8.gumroad.com/l/quoteback-founding-pilot?utm_source=developer_csv&utm_medium=blog&utm_campaign=quoteback_launch
About Safwan Bey
Safwan Bey is Chairman and CEO of Trinlah Sovereign Holdings Corporation (TSHC), a human rights consultant, SDG education advocate, and author of Benji and the SDGs: A Fox’s Journey to Save the World. Through TSHC Product Foundry, he leads work on practical digital products, data tools and education resources. This article is published by TSHC with AI assistance.
Top comments (0)