the structure is the author's, the sizes are the user's
a locked screen persisted its whole layout tree, and `locked` means the user cannot add, move or close a panel — so the only thing in that blob they could actually change was how big things are. everything else was a copy of the author's design sitting in the user's slot, where it could only go stale or get damaged. it did both. STALE: ship a new defaultLayout and nobody who had opened the screen ever saw it. their copy won forever, with no way back a person could reach. DAMAGED: WorkspaceView's appTypes guard replaces an unrecognised appType with the fallback and writes the repair back — right for a user-authored dashboard, catastrophic here. today a plugin layout naming a panel that did not exist yet was rewritten to two copies of its first panel and SAVED. it survived every reload and every fix, and the row had to be deleted from postgres. twice, because an open tab wrote its in-memory copy back over the first delete. useAuthoredLayout takes the structure from source on every render and persists only sizes. a stale structure cannot exist, a repair has nothing to write itself into, and a layout change reaches everybody on the next load — no version to bump, no migration, nothing for an author to remember. the corruption stops being repaired and becomes unrepresentable. a group whose child count no longer matches keeps the author's numbers: two sizes cannot describe three panels, and applying them anyway would leave the new one with the remainder. that guard is what makes shipping a layout change safe rather than merely possible. what is stored is source-with-your-sizes, a valid layout because the route and a CHECK constraint both demand one — written and never read as structure. the asymmetry is the design. PluginScreen only, for now. every core screen is locked and has the same bug; that is a mechanical sweep of one call each and wants its own commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { LayoutNode, AppRegistryMeta } from 'officerdev';
|
||||
import { WorkspaceView } from 'officerdev';
|
||||
import { useDashboardState } from 'state/useDashboardState';
|
||||
import { useAuthoredLayout } from 'state/useAuthoredLayout';
|
||||
|
||||
// The screen every plugin route renders. THE PLUGIN DOES NOT RENDER A SCREEN.
|
||||
//
|
||||
@@ -27,7 +27,12 @@ export function PluginScreen({
|
||||
layout: LayoutNode;
|
||||
}) {
|
||||
// Per-user and per-plugin, so two plugins never share a layout and a user's arrangement is their own.
|
||||
const workspace = useDashboardState<LayoutNode>(`screens/plugin/${appName}`, layout);
|
||||
//
|
||||
// `useAuthoredLayout`, not `useDashboardState`: the STRUCTURE here is the plugin author's and comes from
|
||||
// `layout` on every render, while only the sizes are the user's and persisted. That is what lets a plugin
|
||||
// ship a new layout and have it reach people who have already opened the screen — and it is what stops
|
||||
// the `appTypes` repair below from being written back as though the user had chosen it. See the hook.
|
||||
const workspace = useAuthoredLayout(`screens/plugin/${appName}`, layout);
|
||||
const allowed = panels.map((panel) => panel.key);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user