stop editing a dashboard's name from resetting its panels
The edit branch of the dashboard form rebuilt the layout from the template on every submit, then wrote it. So renaming a dashboard, or fixing a typo in its description, silently threw away however its panels had been arranged and whichever apps were in them. The template is a seed picked once at creation; it is not a description of the dashboard as it now stands. It is now only re-applied when the user actually picks a different one. A rename also dropped `ws-terminals-<id>` and `ws-host-terminals-<id>` without carrying them over, so every shell the dashboard held was abandoned: the panels came back empty and the processes stayed alive with nothing pointing at them. Both maps now move to the new key with the layout. The order those keys go into the PATCH body is load-bearing and now says so — the server walks the object in insertion order, `ws-layout-<new>` upserts the row while `ws-terminals-<new>` only updates one, and `ws-layout-<old>: null` deletes. Written the other way round the terminals 404. Verified against the running server rather than by reading: seeded a dashboard with a layout and both terminal maps, sent the rename PATCH exactly as the client now builds it, and read the rows back — layout, terminals and host terminals all arrived under the new id and the old row was gone. The no-op case (same id, same template) now writes nothing at all instead of PATCHing the layout back to itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -308,20 +308,40 @@ const CreatePanel = () => {
|
||||
),
|
||||
);
|
||||
|
||||
const wsLayout = templates[templateIdx]?.layout() ?? createDefaultLayout();
|
||||
const currentState = queryClient.getQueryData<Record<string, unknown>>(['DASHBOARD_STATE']) ?? {};
|
||||
const newLayoutKey = `ws-layout-${newId}`;
|
||||
|
||||
// Rebuild from the template only when the user actually picked a different one. This ran
|
||||
// unconditionally, so renaming a dashboard — or fixing a typo in its description — threw away
|
||||
// however its panels had been arranged and whichever apps were in them. A template is a seed
|
||||
// chosen once at creation; it is not a description of the dashboard as it now stands.
|
||||
const kept = currentState[`ws-layout-${editingId}`];
|
||||
const templateChanged = dashboards.find((w) => w.id === editingId)?.templateIdx !== templateIdx;
|
||||
const keepable = !!kept && typeof kept === 'object' && !Array.isArray(kept);
|
||||
const wsLayout =
|
||||
!templateChanged && keepable ? kept : (templates[templateIdx]?.layout() ?? createDefaultLayout());
|
||||
|
||||
if (idChanged) {
|
||||
// A rename is the same dashboard under a new key, so everything keyed on the old id moves with it.
|
||||
// Dropping the terminal maps without carrying them over abandoned every shell the dashboard held:
|
||||
// the panels came back empty and the processes stayed alive with nothing pointing at them.
|
||||
const carried: Record<string, unknown> = { [newLayoutKey]: wsLayout };
|
||||
for (const prefix of ['ws-terminals', 'ws-host-terminals']) {
|
||||
const value = currentState[`${prefix}-${editingId}`];
|
||||
if (value !== undefined) carried[`${prefix}-${newId}`] = value;
|
||||
}
|
||||
const oldKeys = [`ws-layout-${editingId}`, `ws-terminals-${editingId}`, `ws-host-terminals-${editingId}`];
|
||||
const cleaned = { ...currentState };
|
||||
for (const k of oldKeys) delete cleaned[k];
|
||||
queryClient.setQueryData(['DASHBOARD_STATE'], { ...cleaned, [newLayoutKey]: wsLayout });
|
||||
queryClient.setQueryData(['DASHBOARD_STATE'], { ...cleaned, ...carried });
|
||||
|
||||
const patch: Record<string, unknown> = { [newLayoutKey]: wsLayout };
|
||||
// Key order is load-bearing: the server walks this object in insertion order, `ws-layout-<new>`
|
||||
// upserts the row and `ws-terminals-<new>` only *updates* one, so the new key must be written
|
||||
// before the old `ws-layout-<old>: null` deletes the row it was copied from.
|
||||
const patch: Record<string, unknown> = { ...carried };
|
||||
for (const k of oldKeys) patch[k] = null;
|
||||
persistDashboardState(client, queryClient, patch);
|
||||
} else {
|
||||
} else if (wsLayout !== kept) {
|
||||
queryClient.setQueryData(['DASHBOARD_STATE'], { ...currentState, [newLayoutKey]: wsLayout });
|
||||
persistDashboardState(client, queryClient, { [newLayoutKey]: wsLayout });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user