fix(frontend): follow the operating workspace in step input forms (#10834)

* fix(frontend): follow the operating workspace in step input forms

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(frontend): rethrow auth errors and wire remaining variable pickers

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(frontend): use runed watch for picker workspace reloads

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
AlexRV12
2026-08-26 00:47:46 +02:00
committed by GitHub
co-authored by Claude Opus 5
parent 8a6dc27236
commit 6b73145e72
5 changed files with 102 additions and 27 deletions
@@ -15,6 +15,7 @@
import PropertyEditor from './schema/PropertyEditor.svelte'
import SimpleEditor from './SimpleEditor.svelte'
import { createEventDispatcher, untrack } from 'svelte'
import { watch } from 'runed'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import Label from './Label.svelte'
@@ -173,6 +174,11 @@
let itemPicker: ItemPicker | undefined = $state(undefined)
let variableEditor: VariableEditor | undefined = $state(undefined)
watch(
() => ws,
() => itemPicker?.reloadItems()
)
let keys: string[] = $state(
(Array.isArray(schema?.order)
? [...schema.order]
@@ -1,9 +1,10 @@
<script lang="ts">
import type { Schema } from '$lib/common'
import { VariableService, WorkspaceService, type InputTransform } from '$lib/gen'
import { CancelError, VariableService, WorkspaceService, type InputTransform } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { allTrue, type DynamicInput as DynamicInputTypes } from '$lib/utils'
import { untrack } from 'svelte'
import { resource, watch } from 'runed'
import { Button } from './common'
import StepInputsGen from './copilot/StepInputsGen.svelte'
import type { PickableProperties } from './flows/previousResults'
@@ -81,21 +82,37 @@
let itemPicker: ItemPicker | undefined = $state(undefined)
let variableEditor: VariableEditor | undefined = $state(undefined)
let s3StorageConfigured = $state(true)
async function checkS3Storage() {
try {
if (ws) {
const settings = await WorkspaceService.getPublicSettings({ workspace: ws })
s3StorageConfigured = settings.large_file_storage?.s3_resource_path !== undefined
const settings = resource(
() => ws,
async (ws, _previousWs, { onCleanup }) => {
if (!ws) return undefined
const req = WorkspaceService.getPublicSettings({ workspace: ws })
// `resource` keeps whatever lands last: cancel a superseded request so a slow
// reply for a workspace we have left cannot overwrite the current one.
onCleanup(() => req.cancel())
try {
return { ws, settings: await req }
} catch (err) {
if (!(err instanceof CancelError)) {
console.error('Failed to fetch workspace settings:', err)
}
return undefined
}
} catch (error) {
console.error('Failed to fetch workspace settings:', error)
s3StorageConfigured = true
}
}
)
// Assume configured until this workspace's own answer lands: the warning must not
// linger from the previous workspace, nor appear merely because the fetch failed.
let s3StorageConfigured = $derived.by(() => {
const loaded = settings.current
return loaded && loaded.ws === ws
? loaded.settings.large_file_storage?.s3_resource_path !== undefined
: true
})
checkS3Storage()
watch(
() => ws,
() => itemPicker?.reloadItems()
)
let keys: string[] = $state([])
$effect(() => {
+54 -14
View File
@@ -1,10 +1,11 @@
<script lang="ts">
import { RotateCw } from 'lucide-svelte'
import { Button, Drawer, Skeleton } from './common'
import { Alert, Button, Drawer, Skeleton } from './common'
import DrawerContent from './common/drawer/DrawerContent.svelte'
import NoItemFound from './home/NoItemFound.svelte'
import IconedResourceType from './IconedResourceType.svelte'
import SearchItems from './SearchItems.svelte'
import { sendUserToast } from '$lib/toast'
type Item = Record<string, any>
@@ -39,22 +40,62 @@
}: Props = $props()
let loading = $state(false)
let loadError: string | undefined = $state(undefined)
let items: Item[] | undefined = $state([])
let filteredItems: Item[] | undefined = $state([])
let filter = $state('')
export function openDrawer() {
loading = true
loadItems()
// Only the newest load may write `items`: a slower earlier request can resolve last.
// Skeletons replace a list that is known-stale; the refresh button omits them so a
// known-good list does not flicker.
let loadSeq = 0
function load(showSkeleton = false): Promise<void> {
const seq = ++loadSeq
if (showSkeleton) {
loading = true
}
return loadItems()
.then((v) => {
items = v
if (seq === loadSeq) {
items = v
loadError = undefined
}
})
.catch((err) => {
if (seq !== loadSeq) return
// Drop the list rather than keep offering entries the failed load may have
// superseded. `loadError` then has to carry the reason, or an empty list reads
// as an empty workspace. An empty body must not win over the message, or the
// error state is skipped for a falsy `loadError` — hence `||`, not `??`.
items = []
loadError = err.body || err.message || String(err)
// 401/403 are handled globally by onunhandledrejection (logout, privilege
// toast). No caller awaits load(), so rethrowing still reaches it.
if (err?.status === 401 || err?.status === 403) {
throw err
}
sendUserToast(`Failed to load ${itemName.toLowerCase()}s: ${loadError}`, true)
})
.finally(() => {
loading = false
if (seq === loadSeq) {
loading = false
}
})
}
export function openDrawer() {
load(true)
drawer?.openDrawer?.()
}
/** Re-runs `loadItems` against what it closes over now. No-op while closed —
* opening reloads anyway. */
export function reloadItems() {
if (drawer?.isOpen()) {
load(true)
}
}
let drawer: Drawer | undefined = $state()
let refreshing = $state(false)
@@ -94,14 +135,9 @@
<Button
on:click={() => {
refreshing = true
loadItems()
.then((v) => {
items = v
})
.finally(() => {
loading = false
refreshing = false
})
load().finally(() => {
refreshing = false
})
}}
iconOnly
startIcon={{ icon: RotateCw, classes: loading || refreshing ? 'animate-spin' : '' }}
@@ -111,6 +147,10 @@
{#each new Array(3) as _}
<Skeleton layout={[[5], 0.2]} />
{/each}
{:else if loadError}
<Alert type="error" size="xs" title="Failed to load {itemName.toLowerCase()}s">
{loadError}
</Alert>
{:else if !items?.length}
<div class="text-center text-sm text-primary mt-2">
{@html noItemMessage}
@@ -15,6 +15,7 @@
import { Plus } from 'lucide-svelte'
import ArgInput from './ArgInput.svelte'
import { createEventDispatcher, untrack } from 'svelte'
import { watch } from 'runed'
import { deepEqual } from 'fast-equals'
import {
dragHandleZone,
@@ -155,6 +156,11 @@
let itemPicker: ItemPicker | undefined = $state(undefined)
let variableEditor: VariableEditor | undefined = $state(undefined)
watch(
() => ws,
() => itemPicker?.reloadItems()
)
let resourceTypes: string[] | undefined = $state(undefined)
async function loadResourceTypes() {
@@ -1,6 +1,7 @@
<script lang="ts">
import { Alert } from '$lib/components/common'
import { getContext, setContext } from 'svelte'
import { watch } from 'runed'
import type { PropPickerWrapperContext } from '../propPicker/PropPickerWrapper.svelte'
import { writable } from 'svelte/store'
import type { FlowEditorContext } from '../types'
@@ -229,6 +230,11 @@
let variablePicker: ItemPicker | undefined = $state(undefined)
let pickForKey: string | undefined = $state(undefined)
watch(
() => opWs,
() => variablePicker?.reloadItems()
)
setContext<PropPickerWrapperContext>('PropPickerWrapper', {
inputMatches: writable(undefined),
connectProp: () => {},