Repository navigation
Release v0.7.0: data you can trust - #126
Conversation
Reference-lookup mappings in externalId/relatedField mode produce keys
such as `Account: { External_Key__c: ... }`. The validator only knew the
reference field name (`AccountId`), so every row failed with
`Unknown field "Account"` and the guided import could never be confirmed.
The validator now resolves relationship keys via the reference field's
relationshipName (falling back to `__c` -> `__r` / `FooId` -> `Foo`, as the
import UI does), applies the reference field's createable/updateable
rules, requires exactly one non-empty scalar match value, and treats a
required reference field as supplied when its relationship key is
present. Callers can optionally pass describe fields of the referenced
object to require an External ID or idLookup match field.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copy to target sent Added and Changed records as a single insert when the match field was not an External ID, duplicating every Changed record. The comparison now also selects Id (never compared), inserts only Added records, and updates Changed records by the target record's Id with just the differing fields. External ID match fields keep the single upsert. The typed COPY confirmation now shows the insert/update (or upsert) counts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…#48, #51) - AppRoot: route every dataset swap through replaceDataset, which also clears rows cleaned from the previous file, so a file uploaded on Import (or a retry/rollback/snapshot dataset) is pushed as itself rather than as the last cleansed file. The Cleanser -> Import hand-off is unchanged (#44). - AppRoot: pass the org context to the Advanced -> Data Push screen so the production typed confirmation and warnings apply there too (#48). - DataPushScreen: invalidate mapped records, validation, dry run and the reachable stages whenever the dataset or its cleaned rows change, and let Retry / Prepare Delete Push flag their restored state so the automap effect no longer overwrites restored manual mappings (#51). - Regression tests drive the real AppRoot and DataPushScreen flows. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
StorageService.setLocal now checks getBytesInUse/getManifest before every write and the background module registers chrome.commands/chrome.alarms listeners at import time. The shared chrome mock lacked these, so every local-storage write and every background-module import threw in tests (offscreen-bulk-push, push-checkpoints, migrationStorage suites failed on main). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Row identity is now carried end to end instead of being reconstructed by positional zips: - #47 Bulk: after a job completes, both successfulResults and failedResults are fetched and mapped back to input indices by fingerprinting the cells we uploaded (Bulk 2.0 rejects synthetic key columns and does not promise result order). Completion payloads, push results, and history now carry per-row errors plus ids/idRecordIndexes, so Retry Failed Rows and the error file work for Bulk pushes. Unidentifiable rows report index -1 rather than a guess. - #47 REST: each returned ID is recorded with its input index, so parallel batches and HTTP 204 upsert updates (no ID) no longer shift IDs onto the wrong rows. buildPushOutcomeDatasets attaches IDs only via idRecordIndexes. - #46: DataMapper returns sourceIndexes (mapped -> source position); retry and outcome builders translate push indices through it, and rows dropped at mapping are no longer reported as successes. - #45: the ingest CSV header is the union of keys across all records, explicit null is sent as #N/A so blank-means-clear works on Bulk, and values with \r are quoted. Bulk result getters now reject non-OK responses. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ring main did not typecheck: OFFSCREEN_TOKEN_REFRESH was a MessageType without a PayloadMap entry, and MigrationWorkspaceScreen read failedRecords/ids off the string[] its awaitPushResult returned. awaitPushResult now returns the stored result and target IDs are paired with source rows through idRecordIndexes (#47) instead of a positional zip. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PR #107 (issue #93) intentionally changed buildSoql to emit `= null` / `!= null`, the documented SOQL form, but did not update the two builder tests that still asserted the old `= NULL` output. The expectation is obsolete; the assertions are kept and pointed at the new output. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- background: drop SalesforceApiClient import left unused by #116 and make y/mo const in computeNextRunAt (#112). - storage: drop SCHEMA_CACHE_TTL import added unused by #114. - ExportModal: remove the redundant `\[` escape in the sheet-name regex (#119); the character class is unchanged. - FieldRecommendations: remove redundant role="list" on <ul> (#123). - SoqlPreview: remove role="textbox"/aria-readonly from the read-only <pre> (#123). It announced a text field that cannot be focused or edited; the aria-label is kept. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ocks The handler suites added in #125 never passed: - background-storage-handlers / background-auth-handlers invoked only the last registered chrome.runtime.onMessage listener, assuming it was the MessageBus. The background also registers a raw scheduler-control listener after the bus, so every MessageBus message came back NOT_HANDLED. Add tests/mocks/runtimeMessages.ts, which offers the message to each listener like Chrome does, and use it in both suites. - AUTH_LOGOUT expected getOrg() to return undefined for a removed org; StorageService.getOrg is typed and implemented to return null. Assert null, and also assert the active org is cleared. - SalesforceAuth.login test mocked chrome.tabs.query callback-style, but auth.ts uses the MV3 promise form, so the mock threw "callback is not a function". Mock a resolved promise instead. No assertions were weakened; handler behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PR #118 (issue #65) intentionally made rawCall reject absolute URLs whose origin differs from the org instanceUrl, so the Bearer token cannot be sent to another host. The old "uses an absolute URL verbatim" test still targeted https://other.example.com and so asserted the token-leaking behaviour the fix removed. That expectation is obsolete; replace it with a same-origin verbatim test plus a test that the cross-origin call is rejected with URL_ORIGIN_MISMATCH and no fetch is made. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#50) DataPushScreen now describes the objects referenced by relationship lookups (cached per tab) and passes them to validation and the dry run, so a lookup matching on a non-External ID field is caught before push. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Two fix PRs made the Bulk API path corrupt field values: - PR #118 (issue #66) applied the spreadsheet formula-injection guard to the Bulk *ingest* CSV. Any value starting with - + = @ was uploaded with a leading apostrophe: -5 failed on number fields and '+44 ... phones or '@Handles were stored with the apostrophe. The REST path never did this, so the same import behaved differently above the Bulk threshold. #66 is about files opened in Excel; exports keep their guard. - PR #119 (issue #80) enabled Papa dynamicTyping on Bulk query results, which coerces Text fields: "02134" -> 2134, 18-digit numbers lose precision, "true" -> true. This broke the existing bulk-query test (Id "001" -> 1). Reverted; correct typing needs field describe data. Adds tests/unit/bulk-csv-fidelity.test.ts for both directions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Follow-up to 9f6f7b3, which fixed the PR #110 merge bug (the poller returned string[] while the consumer read result.ids/failedRecords, so every migrated object threw, no ID map was saved and child lookups were never remapped - Copy between orgs, #43) but kept two defects and had no test: - awaitPushResult still waited for ids.length > 0. A push where every record failed stores a result with ids: [], so the run polled for the full 5 minutes, then marked the object "done" with nothing reported. It now returns the first stored result (results are only written on completion), and a real timeout fails the object. - Per-record push failures are surfaced in the object's errors. The poller and the idRecordIndexes pairing move to ui/utils/migrationPush with unit tests. Also restores the em dashes PR #110 turned into mojibake and drops the BOM it added. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PR #114 (#56/#63/#94/#99) added a pre-write quota check to setLocal with two defects: - Infinite recursion: when usage is above 85% of quota and an old terminal push exists, setLocal -> evictForQuota -> pruneTerminalPushes -> setLocal -> evictForQuota ... re-reads the not-yet-pruned map each time and never returns, hanging every local write (push checkpoints, history, settings). Eviction now prunes via a shared helper and writes directly to chrome.storage.local, never back through setLocal. - Any failure of the usage check or eviction (e.g. getBytesInUse rejecting) turned the caller's write into a StorageError. The check is now best-effort; the write and its quota-error retry still run. The chrome mock returned stored object references from get(), so callers could mutate "storage" without set(). That masked the recursion above. get() now returns a copy, as chrome.storage does. The new tests/unit/storage-quota.test.ts hangs on the old code and passes now. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lockfile-only refresh from `npm audit fix` (brace-expansion, browserslist, fast-uri, serialize-javascript and friends). package-lock.json is unchanged since the last green CI run; these advisories were published since then. Production audit stays clean. `npm run audit:dev` still fails: braces <=3.0.3 (GHSA-vfj7-8cjw-p6xm, pulled in by jest 29 via micromatch) has no patched release. Clearing it needs the jest 30 major upgrade, which is not part of this change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…udit advisories Clears 37 high-severity dev advisories (braces <=3.0.3 via jest 29's micromatch, GHSA-vfj7-8cjw-p6xm) that kept the CI audit:dev step red. typescript-eslint 8 flagged one unused catch binding. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The clone wizard still zipped returned IDs onto source rows positionally, skipping only rows listed in failedRecords. Bulk and multi-threaded REST results arrive out of order, and Bulk failures with an unidentifiable row (-1) are absent from failedRecords, so child records were re-parented to the wrong targets. pairClonedIds now maps each ID through idRecordIndexes. IDs whose index is unknown, out of range, shared by another ID, or marked failed are left unmapped and reported as a warning in the execution log instead of being guessed. The screen also reuses migrationPush.awaitPushResult, so a push in which every record failed no longer polls until the five-minute timeout. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rows excluded by mapRecords were never pushed and appeared in neither the
success nor the error download. buildPushOutcomeDatasets now takes the
source-indexed mapping errors and writes every unpushed source row to the
error file ("Not pushed (mapping error): ...") with its WaveLinkSourceRow,
in source order alongside push failures. The error-file button also shows
when only mapping drops occurred.
Retry Failed Rows still re-sends only push failures: mapping-dropped rows
would fail mapping again with the same mappings, so the retry toast now
says how many were left out and points to the error file.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The CSV exporter stringified every value before applying the #66 formula-injection guard, so genuine negative numbers exported as the text '-5. Guard string cells only, matching the Excel exporter; numbers and booleans pass through unchanged while string cells starting with = + - @ tab or CR are still neutralised. The Bulk upload CSV remains unguarded (183a36f). Refs #66 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Bulk API 2.0 query results are CSV, so every value reached exports as a string while REST returned typed JSON (#80). PR #119 guessed types from the CSV text and corrupted Text fields; 183a36f reverted it. Type each page in the background from the queried object's describe (via the schema cache): boolean -> boolean; int/double/currency/percent -> number only when exactly representable (otherwise kept as string); empty cell -> null like REST; dates, datetimes, Ids and text stay strings. Relationship columns such as Account.Owner.Name resolve via each lookup's referenceTo; polymorphic or unresolvable columns stay strings. The queried object comes from the job info `object` field, memoised per job. Describe or status failures degrade to strings. Also extracts describeSObjectCached, reused by SF_DESCRIBE_SOBJECT. Refs #80 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Bump to 0.7.0, add the changelog, apply the new store name and summary, align store and privacy permission declarations with the manifest (unlimitedStorage added, activeTab removed), record the nebula-dev validation run, and extend validate:salesforce with v0.7.0 checks. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 35c1bb1677
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const queue = unclaimed.get(fingerprint(columns.map(c => (c === undefined ? '' : row[c] ?? '')))); | ||
| return queue && queue.length > 0 ? queue.shift()! : -1; |
There was a problem hiding this comment.
Treat duplicate Bulk fingerprints as unidentified
When multiple input rows serialize to identical cells, they share this queue, but Salesforce does not preserve result order and this function claims every success before processing failures. The returned IDs can therefore be assigned to the wrong source indices; clone and migration workflows then use those indices to build ID maps and may re-parent child records to the wrong cloned parent. Ambiguous fingerprints should produce index -1 rather than being claimed in input order.
Useful? React with 👍 / 👎.
| const sourceIndex = toSourceRecordIndex(error.recordIndex, sourceIndexes); | ||
| if (!inSource(sourceIndex)) continue; | ||
| messages.set(sourceIndex, [...(messages.get(sourceIndex) ?? []), error.message]); |
There was a problem hiding this comment.
Keep unidentified failures out of success downloads
When a Bulk failure has recordIndex: -1—an explicitly supported outcome when a result row cannot be fingerprinted—this branch silently discards it. The later loop consequently places every pushed row without another mapped error into the success dataset, so a failed row can appear in the success download while the error download is empty; unresolved failures must remain visible without classifying all unmatched rows as successful.
Useful? React with 👍 / 👎.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Summary
Ships every fix from the 2026-08-31 audit in one store update, and restores green CI on
main.The eight open v0.7.0 issues
CI on
mainwas red from #107 to #125. Fixing it found real bugs in earlier merged fixes, now corrected:"true"text. Typing now comes from field metadata instead.Also included:
npm run validate:salesforceextended with the v0.7.0 checks.docs/launch-kit.md.unlimitedStorageadded,activeTabremoved.Test plan
npm run typecheck,npm run lint -- --max-warnings=0npm run audit:prodandnpm run audit:dev: 0 vulnerabilitiesnpm run test:coverage -- --runInBand: 62 suites, 613 tests. Every fix has a regression test, each confirmed to fail without its fix.npm run package:wavelink-0.7.0.zippasses the Manifest V3 smoke check.npm run validate:salesforceon thenebula-devdev org, read-only and write mode: 13/13 checks pass, and cleanup passed. Covers Bulk per-row identity, ragged columns, verbatim values,#N/Aclearing, and Bulk vs REST value types.🤖 Generated with Claude Code