diff --git a/frontend/src/lib/components/ImportSetupStep.svelte b/frontend/src/lib/components/ImportSetupStep.svelte
index f1b4662764..ef44bf9ce1 100644
--- a/frontend/src/lib/components/ImportSetupStep.svelte
+++ b/frontend/src/lib/components/ImportSetupStep.svelte
@@ -84,7 +84,13 @@
// Split because the two say different things to the user: one data table was never
// created, the other exists and could not be read. Telling someone to set up what they
// have already set up is how a warning stops being believed.
- const unmadeTables = $derived(rows.filter((r) => r.status === 'unconfigured'))
+ // Grouped by what it costs the project, not by row status. A row whose migrations failed is
+ // configured, but its tables are as absent as one that was never created and the remedy is
+ // the same — so they share a message. An `unknown` row is the odd one: it is set up, and
+ // whether its tables are there is precisely what could not be established.
+ const missingTables = $derived(
+ rows.filter((r) => r.status === 'unconfigured' || r.status === 'failed')
+ )
const uncheckedTables = $derived(rows.filter((r) => r.status === 'unknown'))
/** Rows the user has not dealt with, of either kind. */
const outstanding = $derived(pendingTables.length + blanks.filter((b) => !b.done).length)
@@ -360,26 +366,38 @@
// hub export. A hub is not ours — `hub_base_url` is an instance setting and the
// wizard can be pointed at any of them — so a name carrying an event-bearing
// element would otherwise run script in this authenticated origin.
- const names = pendingTables.map((r) => escapeHtml(r.name)).join(', ')
- const one = pendingTables.length === 1
- // An `unknown` row is set up — only its schema could not be read — so it cannot be
- // told it is "not set up". Where the list mixes the two, say the weaker thing that
- // is true of both rather than the stronger one that is false of half.
- const anyUnmade = unmadeTables.length > 0
- const confirmed = await confirmationModal.ask({
- title: anyUnmade ? 'The project will not run' : 'This has not been verified',
- confirmationText: 'Skip anyway',
- type: anyUnmade ? 'danger' : 'info',
- children: anyUnmade
- ? `${one ? 'The data table' : 'The data tables'} ${names} ` +
- `${one ? 'is' : 'are'} not set up, so the tables this project's apps and flows read ` +
- `do not exist. Every one of them will fail as soon as it opens.
` +
+ // One block per outcome, the way the footer alert does it. A single sentence over a
+ // mixed list has to be wrong about half of it: an `unknown` data table is set up —
+ // only its schema could not be read — so naming it under "not set up" tells the user
+ // to do something they have already done.
+ const missing = missingTables
+ const unverified = uncheckedTables
+ const listOf = (rs: Row[]) => rs.map((r) => escapeHtml(r.name)).join(', ')
+ const blocks: string[] = []
+ if (missing.length > 0) {
+ const one = missing.length === 1
+ blocks.push(
+ `The tables ${one ? 'the data table' : 'the data tables'} ${listOf(missing)} ` +
+ `${one ? 'holds' : 'hold'} do not exist, and this project's apps and flows read ` +
+ `them. Every one of those fails as soon as it opens.
` +
`Setting ${one ? 'it' : 'them'} up later from workspace settings creates the ` +
`connection but not the tables — only this step runs the project's migration.`
- : `${one ? 'The data table' : 'The data tables'} ${names} ` +
+ )
+ }
+ if (unverified.length > 0) {
+ const one = unverified.length === 1
+ blocks.push(
+ `${one ? 'The data table' : 'The data tables'} ${listOf(unverified)} ` +
`${one ? 'is' : 'are'} set up, but ${one ? 'its' : 'their'} schema could not be ` +
`read, so whether this project's tables exist is unknown. Its apps and flows will ` +
`fail wherever they query a table that is missing.`
+ )
+ }
+ const confirmed = await confirmationModal.ask({
+ title: missing.length > 0 ? 'The project will not run' : 'This has not been verified',
+ confirmationText: 'Skip anyway',
+ type: missing.length > 0 ? 'danger' : 'info',
+ children: blocks.join('