diff --git a/frontend/src/lib/components/sessions/PreviewRouterPicker.svelte b/frontend/src/lib/components/sessions/PreviewRouterPicker.svelte index 359c73c998..e8c6da3016 100644 --- a/frontend/src/lib/components/sessions/PreviewRouterPicker.svelte +++ b/frontend/src/lib/components/sessions/PreviewRouterPicker.svelte @@ -168,21 +168,35 @@ section or the flat layout. // saying "show me this", so it lands on the deployed page, and `navigate` // would otherwise keep whichever side the re-pointed tab happened to be on. // A draft-only item opens its editor, unless this user cannot save there. + function itemTarget(item: WorkspaceItem): PreviewTarget { + return { type: 'item', item, mode: canEdit(item) ? previewModeFor(item) : 'view' } + } function tagItems(nodes: DrillNode[]): DrillNode[] { return nodes.map((n) => n.type === 'leaf' - ? { - ...n, - data: { - type: 'item', - item: n.data, - mode: canEdit(n.data) ? previewModeFor(n.data) : 'view' - } - } + ? { ...n, data: itemTarget(n.data) } : { ...n, children: tagItems(n.children) } ) } + // The mode is saved with the tab and never re-checked, and `canEdit` refuses until the + // rights and protection rules arrive: a draft-only item picked before then would open in a + // viewer that has nothing to show. Hold the pick until both have answered. + let heldItem = $state(undefined) + $effect(() => { + if (!heldItem || !permissions.settled) return + const item = heldItem + heldItem = undefined + untrack(() => onPick(itemTarget(item))) + }) + function pick(target: PreviewTarget) { + if (target.type === 'item' && target.item.draftOnly && !permissions.settled) { + heldItem = target.item + } else { + onPick(target) + } + } + // Open the item's editor. Offered on every item row this user can edit, including the // draft-only ones a plain click already opens there: a row that drops the button to say // "you were getting this anyway" reads as one that cannot be edited. @@ -392,7 +406,7 @@ section or the flat layout. onPick(leaf.data)} + onPick={(leaf) => pick(leaf.data)} initialScope={computedInitialScope} {initialHighlight} {externalFilter} diff --git a/frontend/src/lib/components/sessions/sessionPermissions.svelte.ts b/frontend/src/lib/components/sessions/sessionPermissions.svelte.ts index 0db9ac09e7..3e9437e7e4 100644 --- a/frontend/src/lib/components/sessions/sessionPermissions.svelte.ts +++ b/frontend/src/lib/components/sessions/sessionPermissions.svelte.ts @@ -37,6 +37,11 @@ export function useSessionReadOnly(workspace: () => string | undefined) { get known() { const ws = workspace() return !!ws && (verdictFor(ws) ?? false) + }, + /** Whether the rules were read, a failed read included, which settles as read-only. */ + get settled() { + const ws = workspace() + return !!ws && verdictFor(ws) !== undefined } } } @@ -75,6 +80,11 @@ export function useSessionPermissions( get readOnly() { return readOnly }, + /** Whether `canEditItem` answers for real rather than refusing while it waits for this + * user's rights or the protection rules. */ + get settled() { + return user.resolved(workspace()) && protectedWorkspace.settled + }, /** Whether this user can save the drafts the `kind` editor writes. */ canOpenEditor: (kind: string) => !readOnly && rights.roleCanDraft(kind), /** The same, and this item's own permissions let them write it. */