stop the app editor crashing on a grid-less draft (#10203)

* fix(apps): default a missing grid so the app preview does not crash

* fix(apps): migrate the adopted draft so the editor matches the viewer

* docs: tighten grid-normalization comments to durable constraints

* test: drop the migrateApp grid-default test
This commit is contained in:
Ruben Fiszel
2026-07-20 12:33:14 +02:00
committed by GitHub
parent 83a354f831
commit 1abfe49f7e
2 changed files with 22 additions and 5 deletions
@@ -127,7 +127,15 @@
if (appDraftHandle) UserDraft.stopSync('app', appDraftPath)
// Prefer a prior-session autosave over the prop (the route seeds an empty
// template on `new_draft=true`; it calls `UserDraft.remove` to force a reset).
const stateApp = $state(untrack(() => appDraftHandle?.draft ?? app))
// A draft is a stored value that can predate any schema migration, so whichever
// value is adopted needs the same normalization as the prop above.
const stateApp = $state(
untrack(() => {
const adopted = appDraftHandle?.draft ?? app
migrateApp(adopted)
return adopted
})
)
const appStore = writable<App>(stateApp)
// Mirror `stateApp` mutations into the autosave cell. The first mirror is the
// seed — `acquireEntry`'s `skipNextWrite` swallows it; later writes POST.
+13 -4
View File
@@ -4,10 +4,11 @@ import { allItems } from './editor/appUtilsCore'
/**
* Normalize an `App` in place to the current schema: default `hiddenInlineScripts`
* type, migrate the legacy `doNotRecomputeOnInputChanged` flag, and default
* `fullHeight` on every grid item. Lives in its own light module (no app-editor
* component imports) so non-editor callers e.g. the localStorage→DB draft
* migration can reuse it without pulling the whole `apps/utils` graph.
* type, migrate the legacy `doNotRecomputeOnInputChanged` flag, default a missing
* `grid`, and default `fullHeight` on every grid item. Lives in its own light
* module (no app-editor component imports) so non-editor callers, e.g. the
* localStorage→DB draft migration, can reuse it without pulling the whole
* `apps/utils` graph.
*/
export function migrateApp(app: App) {
;(app?.hiddenInlineScripts ?? []).forEach((x) => {
@@ -22,6 +23,14 @@ export function migrateApp(app: App) {
}
})
// A stored app value can have no `grid` at all: a persisted draft row is the
// confirmed case, and the editor renders a draft in place of the deployed
// value. The grid components dereference it unguarded, so normalize it here,
// the one hook every load path runs through.
if (!Array.isArray(app.grid)) {
app.grid = []
}
allItems(app.grid, app.subgrids).forEach((x) => {
gridColumns.forEach((column: number) => {
if (x?.[column]?.fullHeight === undefined) {