# GAP-E03: CSV/table graph import Status: implemented and browser-verified; full local release gate passed, PR #34 merged as `009c686` after green current-head hosted CI. Priority P2, medium feature effort plus the triggered TASK-136 scaffold groundwork. Decision: [ADR 0037](../decisions/0037-csv-adapter-and-package-scaffolding.md). Execution: [remaining backlog ledger](../task-plans/remaining-backlog-execution-ledger.md). ## Outcome Import node/edge tables using explicit column mappings, with physical record-start line numbers for syntax, row shape, missing values, duplicate IDs and missing endpoints. Bad input leaves the current graph unchanged. Users can preview a table or use the graph convenience helper; neither function accesses an engine or fetches data. Existing core validation and record mapping remain authoritative. ## Package and API `@graphora/adapter-csv` depends only on core and exposes: - `parseCsvTable(text, options?)`: `{ ok: true, table }` or a failure result. The table has `columns`, `headerLine` and `rows`; each row has `line` and `values`. Values are strings keyed by exact header names. Table/row/value containers are frozen, with safe own properties even for headers such as `__proto__`. - `parseCsvGraph(input, options?)`: `{ ok: true, graph }` or a failure result. `input.nodes` has `{ text, columns: { id, label? } }`; optional `input.edges` has `{ text, columns: { source, target, id?, label? } }`. Mapped column names are explicit, exact and case-sensitive. Unmapped columns remain in node/edge `data` as the parsed row values. The successful graph is ordinary canonical `RawGraph`, which callers may further transform and revalidate. - A failure has `{ ok: false, issues, issuesTruncated }` and no table/graph. Issues have a stable code, message, optional physical `line`, `column` name, one-based field `columnIndex`, configuration `field`, and `table` (`nodes` or `edges`) for graph imports. Core issues retain their original code/metadata; do not invent a line for a nonlocal/unattributable core error. Required ID/source/target cells must not be empty or whitespace-only. Retain all other whitespace and do not coerce numbers: `001` remains the string `001`. Optional edge-ID cells that are blank are omitted so core assigns its existing IDs. Explicit duplicates and missing endpoint values/references are diagnosed at their actual source rows before graph validation. If any stage fails, there is no partial-import graph. No formula evaluation or data callbacks occur. ## Delimited-text contract Options apply independently to each table: | Option | Default | Contract | | --- | --- | --- | | `delimiter` | `,` | One character other than quote, CR, LF or BOM; supports semicolon and tab | | `maxCharacters` | `10000000` | Positive safe integer; JavaScript string length before parsing | | `maxRows` | `100000` | Nonnegative safe integer; data records, excluding header and empty lines | | `maxColumns` | `256` | Positive safe integer; bounds fields before allocating an oversized row | | `maxIssues` | `100` | Positive safe integer; expose `issuesTruncated` when further errors exist | Invalid options/configuration return an issue rather than silently falling back. A leading BOM is removed. Accept CRLF, LF and CR as record separators; retain newlines inside quotes exactly. Quotes may begin only at a field start; doubled quotes escape one quote; after a closing quote only delimiter, record separator or EOF is valid. A missing closing quote is a syntax error. Physical line counting treats CRLF as one line even inside quoted records. Skip physically empty records, not quoted empty fields or whitespace-only records. The first nonempty record is a required header. Reject empty/whitespace-only or exact duplicate header names. Retain nonempty header whitespace/case literally. Data rows must have exactly the header width. Header-only tables are valid and can represent an empty graph. Structural syntax failure stops parsing; validly separated row-shape errors may be collected up to the issue limit. Never label failed/truncated validation as a successful parse. Use an iterative scan and slices/one-time unescaping, not repeated whole-input splits or unbounded recursive parsing. This is synchronous text parsing with bounded inputs/results, not a promise of hard latency or streaming behavior. Binary XLSX parsing is outside this slice; CSV exports and TSV text are covered. ## Graph validation and publication Parse node and edge inputs independently, attach their source-table context, and validate configured columns before mapping rows. Detect required cell failures, normalized duplicate explicit IDs and missing endpoints with line provenance. Reuse `createRawGraphFromRecords` / core validation for the successful final graph, including generated-edge collision policy. Preserve core errors if final validation finds an additional issue, rather than pretending an invalid graph is importable. The example replaces the engine graph only after `ok: true`, then runs an existing layout. Parser/mapper failure displays diagnostics while retaining prior data, version and Canvas. This is atomic validated replacement, not SOTA-15 incremental reconciliation or remote loading. No import-side effects occur in the adapter. ## Scaffold groundwork Promote the accepted runbook in this feature's preparation. A tested CLI takes a focused family name and existing local dependencies, supports dry-run, refuses existing/unsafe targets and prevalidates configuration before writes. It creates manifest, tsconfig, README and entrypoint plus root TS references/alias. It prints remaining test, API and example wiring steps. Use it to create `adapter-csv`; add actual public API/test/example wiring explicitly, without publishing metadata. ## Acceptance plan - Quoted delimiters, doubled quotes, multiline fields, all supported line endings, BOM, trailing separator/EOF, empty lines and preserved Unicode/whitespace. - Missing/duplicate headers, mismatched field counts, malformed quotes and each limit, with physical line/field metadata and honest issue truncation. - Custom mappings, string ID preservation, duplicate node/edge IDs, absent cells, missing endpoints, optional/generated edge IDs and core collision errors. - Prototype-like headers do not mutate object prototypes. Parser tables are frozen; successful graph output follows the existing RawGraph/data-reference boundary. - Package script dry-run/no-overwrite/invalid-name tests and real workspace build. - Runnable CSV/TSV example with column selection and textual diagnostics; Chromium verifies an actual successful graph change and failed import preserving Canvas, data and version. Include a multiline-row diagnostic and narrow viewport. - Build/typecheck, meaningful unit tests, lint/format, API generation, complete site/browser/archive and hosted CI before PR merge. Keep specified, implemented, verified and merged statuses distinct throughout execution. ## Verification evidence At `e7e1944`, integrated main through PR #33: 506 unit tests, 88 Chromium tests, TypeScript, lint, formatting, generated API consistency, eight deployment tests, all 22 example previews/site links and release archive passed. The dedicated CSV suite has 44 cases; package creation adds three meaningful filesystem tests. `tests/e2e/csv-import.spec.ts` checks real Canvas pixels plus source IDs/version, multiline duplicate provenance and TSV column mapping at 390px width. Self-review checked iterative parser boundaries, quote/CRLF handling, safe own header properties, callback-free mapping, exact ID preservation, nonlocal core error provenance and public package boundaries. No core/engine/renderer runtime was modified. Hosted CI and merge are tracked in the execution ledger; this is Chromium evidence, not a claim for physical devices or native spreadsheets.