refactor: one operating-workspace context for editors acting on a session's workspace

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Diego Imbert
2026-09-17 16:45:19 +02:00
co-authored by Claude Opus 5
parent 29abd63de6
commit acbdb285c9
100 changed files with 603 additions and 469 deletions
@@ -3,11 +3,13 @@
import Select from './select/Select.svelte'
import { fetchAvailableModels, AI_PROVIDERS } from './copilot/lib'
import type { AIProvider, ProviderConfig } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import ResourcePicker from './ResourcePicker.svelte'
import Toggle from './Toggle.svelte'
import { saveConfig, removeConfig, isSameAsStoredConfig } from './aiProviderStorage'
import AIReasoningEffortPicker from './AIReasoningEffortPicker.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
value: ProviderConfig | undefined
@@ -26,7 +28,7 @@
workspace = undefined
}: Props = $props()
let effectiveWorkspace = $derived(workspace ?? $workspaceStore ?? '')
let effectiveWorkspace = $derived(workspace ?? $operatingWorkspace ?? '')
let value = $derived.by(() => {
if (!_uncheckedValue || typeof _uncheckedValue !== 'object') return undefined
+4 -2
View File
@@ -41,12 +41,14 @@
import { safeSelectItems } from './select/utils.svelte'
import S3ArgInput from './common/fileUpload/S3ArgInput.svelte'
import { base } from '$lib/base'
import { workspaceStore } from '$lib/stores'
import { getJsonSchemaFromResource } from './schema/jsonSchemaResource.svelte'
import AIProviderPicker from './AIProviderPicker.svelte'
import TextInput from './text_input/TextInput.svelte'
import FileInput from './common/fileInput/FileInput.svelte'
import { randomUUID } from '$lib/utils/uuid'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
label?: string
@@ -823,7 +825,7 @@
/>
{/await}
{:else if inputCat == 'object' && format?.startsWith('jsonschema-')}
{#await getJsonSchemaFromResource(format.substring('jsonschema-'.length), workspace ?? $workspaceStore ?? '')}
{#await getJsonSchemaFromResource(format.substring('jsonschema-'.length), workspace ?? $operatingWorkspace ?? '')}
<Loader2 class="animate-spin" />
{:then schema}
{#if !schema || !schema.properties}
@@ -1,9 +1,11 @@
<script lang="ts">
import Select from './select/Select.svelte'
import { WorkspaceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { RefreshCcw } from 'lucide-svelte'
import { Button } from './common'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface ChannelItem {
channel_id?: string
@@ -21,8 +23,8 @@
showRefreshButton?: boolean
onError?: (error: Error) => void
onSelectedChannelChange?: (channel: ChannelItem | undefined) => void
/** Workspace to list Teams channels from; defaults to the nav
* `$workspaceStore`. A forked session passes its acting workspace. */
/** Workspace to list Teams channels from; defaults to the operating workspace (see
* `useOperatingWorkspace`). */
workspace?: string
}
@@ -40,7 +42,7 @@
workspace = undefined
}: Props = $props()
let effectiveWorkspace = $derived(workspace ?? $workspaceStore)
let effectiveWorkspace = $derived(workspace ?? $operatingWorkspace)
let isFetching = $state(false)
let loadedChannels = $state<ChannelItem[]>([])
@@ -4,7 +4,6 @@
const bubble = createBubbler()
import type { Schema } from '$lib/common'
import { VariableService, type ScriptLang } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { Button } from './common'
import ItemPicker from './ItemPicker.svelte'
import VariableEditor from './VariableEditor.svelte'
@@ -36,6 +35,9 @@
import Section from '$lib/components/Section.svelte'
import Editor from './Editor.svelte'
import AddPropertyV2 from './schema/AddPropertyV2.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
// export let openEditTab: () => void = () => {}
const dispatch = createEventDispatcher()
@@ -128,7 +130,7 @@
workspace = undefined
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
$effect.pre(() => {
if (args == undefined) {
@@ -31,7 +31,7 @@
import type { Schema, SupportedLanguage } from '$lib/common'
import { base } from '$lib/base'
import { enterpriseLicense, workspaceStore } from '$lib/stores'
import { enterpriseLicense } from '$lib/stores'
import MsTeamsIcon from '$lib/components/icons/MSTeamsIcon.svelte'
import { classNames, emptySchema, emptyString, sendUserToast, tryEvery } from '$lib/utils'
import MultiSelect from '$lib/components/select/MultiSelect.svelte'
@@ -61,6 +61,9 @@
import SmtpConfigurationStatus from './common/smtp/SmtpConfigurationStatus.svelte'
import { SettingService } from '$lib/gen'
import { isSmtpSettingsValid } from './instanceSettings/SmtpSettings.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
const slackRecoveryHandler = hubPaths.slackRecoveryHandler
const slackHandlerScriptPath = hubPaths.slackErrorHandler
@@ -81,9 +84,8 @@
customHandlerKind?: 'flow' | 'script'
customTabTooltip?: import('svelte').Snippet
noMargin?: boolean
/** Workspace for handler lookup / settings / test jobs. Defaults to the
* nav `$workspaceStore`; a trigger editor in a forked session passes its
* acting workspace so the handler is resolved and saved there. */
/** Workspace for handler lookup / settings / test jobs. Defaults to the operating
* workspace (see `useOperatingWorkspace`). */
workspace?: string
/** Offer the instance critical alert channels as a destination. Workspace-level
* error handling only: schedules and triggers have no such setting. */
@@ -106,7 +108,7 @@
showInstanceAlerts = false
}: Props = $props()
let effectiveWorkspace = $derived(workspace ?? $workspaceStore)
let effectiveWorkspace = $derived(workspace ?? $operatingWorkspace)
// Carry the acting workspace onto the "create from template" route when an
// explicit override is set, so a forked session creates the handler script
// there. `customScriptTemplate` already has a query string (`?hub=…`).
@@ -33,15 +33,13 @@
import { Button, ButtonType } from '$lib/components/common'
import S3FilePicker from '$lib/components/S3FilePicker.svelte'
import { VolumeService } from '$lib/gen'
import {
globalDbManagerDrawer,
globalS3FilePickerExplorer,
userStore,
workspaceStore
} from '$lib/stores'
import { globalDbManagerDrawer, globalS3FilePickerExplorer, userStore } from '$lib/stores'
import { isS3Uri } from '$lib/utils'
import { Database, File, HardDriveIcon } from 'lucide-svelte'
import DucklakeIcon from './icons/DucklakeIcon.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
const {
asset,
@@ -69,7 +67,7 @@
} = $props()
let dbManagerDrawer = $derived(globalDbManagerDrawer.val)
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
const assetUri = $derived(formatAsset(asset))
// Contexts with a select/upload flow pass their own picker; everything else
// (e.g. the resources list) falls back to the global read-only explorer.
@@ -1,5 +1,5 @@
<script lang="ts">
import { workspaceStore, enterpriseLicense, userStore } from '$lib/stores'
import { enterpriseLicense, userStore } from '$lib/stores'
import Popover from './meltComponents/Popover.svelte'
import Button from './common/button/Button.svelte'
import { Loader2, Github, RotateCw, Plus, Minus, Download, AlertTriangle } from 'lucide-svelte'
@@ -18,6 +18,9 @@
type GitHubAppState
} from '$lib/githubApp'
import RepositorySelector from './RepositorySelector.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
resourceType: string
@@ -70,14 +73,14 @@
let showGitHubApp = $derived(
resourceType === 'git_repository' &&
$workspaceStore &&
$operatingWorkspace &&
($userStore?.is_admin || $userStore?.is_super_admin)
)
// Load GitHub installations when conditions are met
$effect(() => {
if (showGitHubApp && $enterpriseLicense && $workspaceStore) {
loadGithubInstallations(githubState, $workspaceStore).catch((error) => {
if (showGitHubApp && $enterpriseLicense && $operatingWorkspace) {
loadGithubInstallations(githubState, $operatingWorkspace).catch((error) => {
console.error('Failed to load GitHub installations:', error)
})
}
@@ -113,11 +116,11 @@
}
async function handleDeleteInstallation(installationId: number) {
if (!$workspaceStore) return
if (!$operatingWorkspace) return
try {
await deleteInstallation($workspaceStore, installationId, () =>
loadGithubInstallations(githubState, $workspaceStore!)
await deleteInstallation($operatingWorkspace, installationId, () =>
loadGithubInstallations(githubState, $operatingWorkspace!)
)
} catch (error) {
console.error('Failed to delete installation:', error)
@@ -125,11 +128,11 @@
}
async function handleAddInstallation(installationId: number, workspaceId: string) {
if (!$workspaceStore) return
if (!$operatingWorkspace) return
try {
await addInstallationToWorkspace($workspaceStore, installationId, workspaceId, () =>
loadGithubInstallations(githubState, $workspaceStore!)
await addInstallationToWorkspace($operatingWorkspace, installationId, workspaceId, () =>
loadGithubInstallations(githubState, $operatingWorkspace!)
)
} catch (error) {
console.error('Failed to add installation:', error)
@@ -137,22 +140,22 @@
}
async function handleExportInstallation(installationId: number) {
if (!$workspaceStore) return
if (!$operatingWorkspace) return
try {
await exportInstallation($workspaceStore, installationId)
await exportInstallation($operatingWorkspace, installationId)
} catch (error) {
console.error('Failed to export installation:', error)
}
}
async function handleImportInstallation() {
if (!$workspaceStore) return
if (!$operatingWorkspace) return
try {
await importInstallation($workspaceStore, githubState.importJwt, () => {
await importInstallation($operatingWorkspace, githubState.importJwt, () => {
githubState.importJwt = ''
loadGithubInstallations(githubState, $workspaceStore!)
loadGithubInstallations(githubState, $operatingWorkspace!)
})
} catch (error) {
console.error('Failed to import installation:', error)
@@ -160,17 +163,17 @@
}
function handleRefreshInstallations() {
if (!$workspaceStore) return
if (!$operatingWorkspace) return
loadGithubInstallations(githubState, $workspaceStore).catch((error) => {
loadGithubInstallations(githubState, $operatingWorkspace).catch((error) => {
console.error('Failed to refresh installations:', error)
})
}
function handleInstallClickWithPopover() {
if (!$workspaceStore) return
if (!$operatingWorkspace) return
handleInstallClick(githubState, $workspaceStore, () => {
handleInstallClick(githubState, $operatingWorkspace, () => {
githubAppPopover?.open()
})
}
@@ -297,9 +300,9 @@
target="_blank"
disabled={githubState.isCheckingInstallation}
on:click={() => {
if ($workspaceStore) {
startInstallationCheck(githubState, $workspaceStore, () =>
loadGithubInstallations(githubState, $workspaceStore!)
if ($operatingWorkspace) {
startInstallationCheck(githubState, $operatingWorkspace, () =>
loadGithubInstallations(githubState, $operatingWorkspace!)
)
}
}}
@@ -1,5 +1,5 @@
<script lang="ts">
import { workspaceStore, userStore, enterpriseLicense } from '$lib/stores'
import { userStore, enterpriseLicense } from '$lib/stores'
import { GitSyncService, type GitlabProject } from '$lib/gen'
import { sendUserToast } from '$lib/toast'
import Popover from './meltComponents/Popover.svelte'
@@ -8,6 +8,9 @@
import TextInput from './text_input/TextInput.svelte'
import Select from './select/Select.svelte'
import { GitBranch, Gitlab, Loader2 } from 'lucide-svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
resourceType: string
@@ -30,7 +33,7 @@
onArgsUpdate
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
let baseUrl = $state('https://gitlab.com')
let token = $state('')
@@ -1,10 +1,12 @@
<script lang="ts">
import { HelpersService, ResourceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { Button, Drawer, DrawerContent } from './common'
import { GitBranch, Loader2, FolderOpen } from 'lucide-svelte'
import Select from './select/Select.svelte'
import { createEventDispatcher } from 'svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
open: boolean
@@ -27,7 +29,7 @@
workspace: workspaceProp = undefined
}: Props = $props()
let ws = $derived(workspaceProp ?? $workspaceStore)
let ws = $derived(workspaceProp ?? $operatingWorkspace)
const dispatch = createEventDispatcher<{
selected: {
@@ -7,7 +7,9 @@
import { DataTable } from '$lib/components/table'
import HistoricList from './HistoricList.svelte'
import { Loader2 } from 'lucide-svelte'
import { workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
runnableId?: string | undefined
@@ -32,7 +34,7 @@
workspace = undefined
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
let historicList: HistoricList | undefined = $state(undefined)
const dispatch = createEventDispatcher()
@@ -1,11 +1,13 @@
<script lang="ts">
import { VariableService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { watch } from 'runed'
import { Plus } from 'lucide-svelte'
import { Button } from './common'
import ItemPicker from './ItemPicker.svelte'
import VariableEditor from './VariableEditor.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
/** The transforms being edited. A picked variable is written into `pickForField`'s. */
@@ -26,7 +28,7 @@
variableEditor = $bindable()
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
watch(
() => ws,
@@ -1,7 +1,6 @@
<script lang="ts">
import type { Schema } from '$lib/common'
import { type InputTransform } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { allTrue, type DynamicInput as DynamicInputTypes } from '$lib/utils'
import { untrack } from 'svelte'
import StepInputsGen from './copilot/StepInputsGen.svelte'
@@ -12,6 +11,9 @@
import type ItemPicker from './ItemPicker.svelte'
import type VariableEditor from './VariableEditor.svelte'
import ResizeTransitionWrapper from './common/ResizeTransitionWrapper.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
schema: Schema | { properties?: Record<string, any> }
@@ -54,7 +56,7 @@
workspace
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
let inputCheck: { [id: string]: boolean } = $state({})
@@ -2,7 +2,9 @@
import Badge from './common/badge/Badge.svelte'
import Button from './common/button/Button.svelte'
import { Plus, Tag, X } from 'lucide-svelte'
import { workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
labels: string[] | undefined
@@ -53,7 +55,7 @@
async function loadExistingLabels() {
try {
const resp = await fetch(`/api/w/${workspace ?? $workspaceStore}/labels/list`)
const resp = await fetch(`/api/w/${workspace ?? $operatingWorkspace}/labels/list`)
if (resp.ok) existingLabels = await resp.json()
} catch {}
}
@@ -1,6 +1,5 @@
<script lang="ts">
import { ResourceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { getContext, untrack } from 'svelte'
import DarkModeObserver from './DarkModeObserver.svelte'
import { Button, Drawer, DrawerContent } from './common'
@@ -11,6 +10,9 @@
import IconedResourceType from './IconedResourceType.svelte'
import { addResourceTitle } from './resourceTypeDisplay'
import { loadResourceTypeDisplayName } from './displayNameLoaders'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
value: string | undefined
@@ -35,7 +37,7 @@
let open = $state(false)
let refreshCount = $state(0)
const appViewerContext = getContext<AppViewerContext>('AppViewerContext')
let ws = $derived(workspace ?? appViewerContext?.workspace ?? $workspaceStore)
let ws = $derived(workspace ?? appViewerContext?.workspace ?? $operatingWorkspace)
let collection = $state(value ? [{ value, label: value }] : [])
@@ -139,7 +141,7 @@
title="App connection"
class="w-full h-full"
src="{base}/embed_connect?resource_type={resourceType}&workspace={appViewerContext?.workspace ??
$workspaceStore}&express=false"
$operatingWorkspace}&express=false"
/> -->
</DrawerContent>
</Drawer>
@@ -1,11 +1,14 @@
<script lang="ts">
import { VariableService } from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { userStore } from '$lib/stores'
import { ephemeralSecretPrefix, mintEphemeralSecret } from './secretArgUtils'
import { sendUserToast } from '$lib/toast'
import { Button } from './common'
import Password from './Password.svelte'
import { untrack } from 'svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
value?: string | undefined
@@ -18,7 +21,7 @@
let { value = $bindable(undefined), disabled, minRows, workspace }: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
let path = $state('')
// Workspace the variable at `path` actually lives in; `ws` can move away from it.
+7 -6
View File
@@ -26,7 +26,7 @@
AzureTriggerService,
EmailTriggerService
} from '$lib/gen'
import { superadmin, userStore, workspaceStore, type UserExt } from '$lib/stores'
import { superadmin, userStore, type UserExt } from '$lib/stores'
import { createEventDispatcher, getContext, untrack } from 'svelte'
import { writable } from 'svelte/store'
import { Alert, Button } from './common'
@@ -45,6 +45,9 @@
import Select from './select/Select.svelte'
import { twMerge } from 'tailwind-merge'
import InputError from './InputError.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
type PathKind =
| 'resource'
@@ -81,10 +84,8 @@
disableEditing?: boolean
size?: 'sm' | 'md'
drawerOffset?: number
/** Workspace the folder list and path-existence checks run against.
* Defaults to the navigation `$workspaceStore`; pass the session's acting
* workspace when the editor operates on a workspace other than the one the
* top nav points at (see the sessions preview / dev-workspace flows). */
/** Workspace the folder list and path-existence checks run against. Defaults to the
* operating workspace (see `useOperatingWorkspace`). */
workspaceOverride?: string
/** The user acting in `workspaceOverride`, for the owner suggestion and the folder
* write flags. Omit it to stand in the navigation `$userStore`, who is a member of
@@ -121,7 +122,7 @@
warnOnRename = true
}: Props = $props()
let ws = $derived(workspaceOverride ?? $workspaceStore)
let ws = $derived(workspaceOverride ?? $operatingWorkspace)
// Sole place this component falls back to the ambient user, and only for a caller that
// passed none; everything below reads `user`, so a caller acting on another workspace is
// never mixed with the navigation user's memberships.
@@ -87,7 +87,9 @@
<script lang="ts">
import TextInput from '$lib/components/text_input/TextInput.svelte'
import Badge from '$lib/components/common/badge/Badge.svelte'
import { workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
import { untrack } from 'svelte'
type Props = {
@@ -101,9 +103,8 @@
error?: string | boolean
textInputClass?: string
onkeyup?: (e: KeyboardEvent) => void
/** Workspace whose paths feed the autocomplete. Defaults to the navigation
* `$workspaceStore`; pass the acting workspace when the editor operates on
* a workspace other than the one the top nav points at. */
/** Workspace whose paths feed the autocomplete. Defaults to the operating workspace
* (see `useOperatingWorkspace`). */
workspace?: string
}
@@ -121,7 +122,7 @@
workspace = undefined
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
let inputEl: TextInput | undefined = $state(undefined)
export function focus() {
@@ -9,7 +9,6 @@
} from '$lib/gen'
import { canWrite } from '$lib/utils'
import { createEventDispatcher, onDestroy, untrack } from 'svelte'
import { workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { clearJsonSchemaResourceCache } from './schema/jsonSchemaResource.svelte'
import ResourceForm from './ResourceForm.svelte'
@@ -22,6 +21,9 @@
import { UserDraftDbSyncer } from '$lib/userDraftDbSyncer.svelte'
import { setLocalDraftHint } from '$lib/localDraftHints.svelte'
import { onUserInput } from '$lib/userDraftEditGate'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
canSave?: boolean
@@ -74,7 +76,7 @@
// Sole ambient read in this file: the acting workspace is an input, and only its
// default comes from the navigation store.
let effectiveWorkspace = $derived(workspace ?? $workspaceStore!)
let effectiveWorkspace = $derived(workspace ?? $operatingWorkspace!)
// Fallback to `effectiveWorkspace` insulates against reactify-style
// parents that re-spread props without `selected` — otherwise it
// transiently resets and the form below remounts on every keystroke.
@@ -5,7 +5,6 @@
import { History, Loader2, Save } from 'lucide-svelte'
import WsSpecificVersions from './WsSpecificVersions.svelte'
import { workspaceStore } from '$lib/stores'
import { isOwner } from '$lib/utils'
import { useActingUser } from '$lib/actingUser.svelte'
import LocalDraftBanner from './LocalDraftBanner.svelte'
@@ -21,6 +20,9 @@
import IconedResourceType from './IconedResourceType.svelte'
import { addResourceTitle } from './resourceTypeDisplay'
import { loadResourceTypeDisplayName } from './displayNameLoaders'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let {
workspace = undefined,
@@ -64,7 +66,7 @@
let selected: string | undefined = $state(undefined)
let viewJsonSchema = $state(false)
let effectiveWorkspace = $derived(workspace ?? $workspaceStore!)
let effectiveWorkspace = $derived(workspace ?? $operatingWorkspace!)
// The editor renders whichever workspace-specific variant `selected` points at, so history has
// to follow it too — otherwise a restore would write over the variant the user is not looking at.
let historyWorkspace = $derived(selected ?? effectiveWorkspace)
@@ -8,7 +8,7 @@
import Path from './Path.svelte'
import LabelsInput from './LabelsInput.svelte'
import Required from './Required.svelte'
import { workspaceStore, type UserExt } from '$lib/stores'
import { type UserExt } from '$lib/stores'
import SchemaForm from './SchemaForm.svelte'
import SimpleEditor from './SimpleEditor.svelte'
import FilesetEditor from './FilesetEditor.svelte'
@@ -25,6 +25,9 @@
import SyncResourceTypes from './SyncResourceTypes.svelte'
import Label from './Label.svelte'
import ResourcePathHint from './ResourcePathHint.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
path: string
@@ -84,7 +87,7 @@
onCredentialStored
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
let editDescription = $state(false)
let rawCode: string | undefined = $state(undefined)
@@ -1,6 +1,5 @@
<script lang="ts">
import { ResourceService, WorkspaceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { onMount, untrack } from 'svelte'
import AppConnect from './AppConnectDrawer.svelte'
import ResourceEditorDrawer from './ResourceEditorDrawer.svelte'
@@ -12,6 +11,9 @@
import ExploreAssetButton, { assetCanBeExplored } from './ExploreAssetButton.svelte'
import DropdownV2 from './DropdownV2.svelte'
import { appIconComponent } from './icons'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
initialValue?: string | undefined
@@ -62,7 +64,7 @@
onValueChange = undefined
}: Props = $props()
let effectiveWorkspace = $derived(workspace ?? $workspaceStore!)
let effectiveWorkspace = $derived(workspace ?? $operatingWorkspace!)
if (initialValue && value == undefined) {
value = initialValue
@@ -1,7 +1,6 @@
<script lang="ts">
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { ResourceService, type ResourceVersion } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { Skeleton } from '$lib/components/common'
import Button from './common/button/Button.svelte'
import HighlightCode from './HighlightCode.svelte'
@@ -12,6 +11,9 @@
import VersionListItem from './VersionListItem.svelte'
import { displayDate } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let {
path,
@@ -27,7 +29,7 @@
onRestore?: () => void
} = $props()
let effectiveWorkspace = $derived(workspace ?? $workspaceStore!)
let effectiveWorkspace = $derived(workspace ?? $operatingWorkspace!)
let versions = $state<ResourceVersion[] | undefined>(undefined)
// Which row is highlighted, updated on click. `loaded` is dropped the moment the selection
@@ -7,7 +7,9 @@
import Select from './select/Select.svelte'
import { FileUp } from 'lucide-svelte'
import { SettingService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
import { resource } from 'runed'
interface Props {
@@ -54,7 +56,7 @@
* asset lives in a different workspace than this picker was mounted for. */
let workspaceOverride: string | undefined = $state(undefined)
let effectiveWorkspace = $derived(workspaceOverride ?? workspace)
let ws = $derived(effectiveWorkspace ?? $workspaceStore)
let ws = $derived(effectiveWorkspace ?? $operatingWorkspace)
let uploadModalOpen = $state(false)
let allFilesByKey: Record<
@@ -10,7 +10,6 @@
Trash,
MoveRight
} from 'lucide-svelte'
import { workspaceStore } from '$lib/stores'
import {
CancelablePromise,
HelpersService,
@@ -48,6 +47,9 @@
import FileUploadModal from './common/fileUpload/FileUploadModal.svelte'
import S3FilePreview from './S3FilePreview.svelte'
import { twMerge } from 'tailwind-merge'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let deletionModalOpen = $state(false)
let fileDeletionInProgress = $state(false)
@@ -137,7 +139,7 @@
testConnectionRequest = HelpersService.datasetStorageTestConnection
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
let rootPath = $state(initialRootPath)
let rootPathNestingLevel = $derived(1 * (rootPath.split('/').length - 1))
@@ -8,7 +8,6 @@
// need to hand it a `fileKey`. CSV separator/header are local state so
// the user can re-preview the same file with different parsing flags.
import { FileX2, Loader2 } from 'lucide-svelte'
import { workspaceStore } from '$lib/stores'
import {
HelpersService,
type CancelablePromise,
@@ -20,6 +19,9 @@
import { displayDate, displaySize, emptyString } from '$lib/utils'
import { twMerge } from 'tailwind-merge'
import ExpandableImage from '$lib/components/common/image/ExpandableImage.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
fileKey: string | undefined
@@ -115,7 +117,7 @@
// existence after an upstream run completes — moving from the
// "not yet materialized" empty state to the actual preview without
// requiring the user to re-click the asset.
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
$effect(() => {
const key = fileKey
@@ -1,12 +1,14 @@
<script lang="ts">
import { displayDate } from '$lib/utils.js'
import { InputService, type CreateInput, type RunnableType } from '$lib/gen/index.js'
import { workspaceStore } from '$lib/stores.js'
import { Button } from '$lib/components/common'
import { Save } from 'lucide-svelte'
import { sendUserToast } from '$lib/utils.js'
import { createEventDispatcher } from 'svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
const dispatch = createEventDispatcher()
@@ -30,7 +32,7 @@
workspace = undefined
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
let savingInputs = $state(false)
@@ -1,7 +1,7 @@
<script lang="ts">
import { Button } from '$lib/components/common'
import { InputService, type Input, type RunnableType } from '$lib/gen/index.js'
import { userStore, workspaceStore } from '$lib/stores.js'
import { userStore } from '$lib/stores.js'
import { sendUserToast } from '$lib/utils.js'
import { createEventDispatcher, onDestroy, untrack } from 'svelte'
import { Trash2, Save, Pencil } from 'lucide-svelte'
@@ -12,6 +12,9 @@
import InfiniteList from './InfiniteList.svelte'
import { twMerge } from 'tailwind-merge'
import SavedInputsPickerViewer from './SavedInputsPickerViewer.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
previewArgs?: any
@@ -36,7 +39,7 @@
workspace = undefined
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
interface EditableInput extends Input {
isEditing?: boolean
@@ -4,7 +4,6 @@
const bubble = createBubbler()
import type { Schema } from '$lib/common'
import { VariableService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { allTrue, computeShow, type DynamicInput } from '$lib/utils'
import { Button } from './common'
import ItemPicker from './ItemPicker.svelte'
@@ -26,6 +25,9 @@
import type { ComponentCustomCSS } from './apps/types'
import ResizeTransitionWrapper from './common/ResizeTransitionWrapper.svelte'
import { twMerge } from 'tailwind-merge'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
schema: Schema | any
@@ -122,7 +124,7 @@
actions: actions_render = undefined
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
const dispatch = createEventDispatcher()
@@ -1,7 +1,6 @@
<script lang="ts">
import { ScriptService, FlowService, type Script, AppService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { base } from '$lib/base'
import { createEventDispatcher, untrack } from 'svelte'
@@ -18,6 +17,9 @@
import FlowIcon from './home/FlowIcon.svelte'
import DarkModeObserver from './DarkModeObserver.svelte'
import { truncate } from '$lib/utils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
initialPath?: string | undefined
@@ -30,9 +32,8 @@
allowEdit?: boolean
allowView?: boolean
clearable?: boolean
/** Workspace to list runnables from. Defaults to the navigation
* `$workspaceStore`; pass the session's acting workspace so a forked
* session lists its own scripts/flows/apps rather than the parent's. */
/** Workspace to list runnables from. Defaults to the operating workspace (see
* `useOperatingWorkspace`). */
workspace?: string
}
@@ -50,7 +51,7 @@
workspace = undefined
}: Props = $props()
let effectiveWorkspace = $derived(workspace ?? $workspaceStore)
let effectiveWorkspace = $derived(workspace ?? $operatingWorkspace)
// Only carry the workspace onto Edit/View routes when an explicit override
// was passed, so existing callers' links are unchanged.
let wsParam = $derived(workspace ? `?workspace=${encodeURIComponent(workspace)}` : '')
@@ -13,7 +13,9 @@
import Button from './common/button/Button.svelte'
import Tooltip from './meltComponents/Tooltip.svelte'
import { sendUserToast } from '$lib/toast'
import { workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
import { tryEvery } from '$lib/utils'
interface Props {
@@ -268,7 +270,7 @@ export async function main(bucket: any, api_token: string) {
loading = true
const resourceScript = scripts[resourceType]
const workspace = workspaceOverride ?? $workspaceStore!
const workspace = workspaceOverride ?? $operatingWorkspace!
const objectStorageArgs: Record<string, any> | undefined =
resourceType in objectStorageBody ? objectStorageBody[resourceType](args) : undefined
@@ -1,7 +1,6 @@
<script lang="ts">
import { VariableService, WorkspaceService } from '$lib/gen'
import { createEventDispatcher, untrack } from 'svelte'
import { workspaceStore } from '$lib/stores'
import { Button } from './common'
import Drawer from './common/drawer/Drawer.svelte'
import DrawerContent from './common/drawer/DrawerContent.svelte'
@@ -26,6 +25,9 @@
import LocalDraftBanner from './LocalDraftBanner.svelte'
import { isEncryptedDraftValue } from '$lib/encryptedDraft'
import { setLocalDraftHint } from '$lib/localDraftHints.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
const dispatch = createEventDispatcher()
@@ -53,7 +55,7 @@
} = $props()
// Sole ambient read in this file: the acting workspace is an input, and only its
// default comes from the navigation store.
let curWs = $derived(workspace ?? $workspaceStore)
let curWs = $derived(workspace ?? $operatingWorkspace)
let editPath: string | undefined = $state(undefined)
@@ -10,10 +10,13 @@
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import { Loader2, RotateCcw } from 'lucide-svelte'
import autosize from '$lib/autosize'
import { workspaceStore, type UserExt } from '$lib/stores'
import { type UserExt } from '$lib/stores'
import { isOwner } from '$lib/utils'
import { isEncryptedDraftValue } from '$lib/encryptedDraft'
import EncryptedDraftField from './EncryptedDraftField.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Variable {
value: string
@@ -56,7 +59,7 @@
actingUser
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
// Loading the deployed secret overwrites the draft row this form shares with the AI
// chat, so every path that would trigger it has to be blocked while that row stages a
@@ -24,7 +24,7 @@
import EmailTriggerEditor from '$lib/components/triggers/email/EmailTriggerEditor.svelte'
import ScheduleEditor from '$lib/components/triggers/schedules/ScheduleEditor.svelte'
import WebhookEditor from '$lib/components/triggers/webhook/WebhookEditor.svelte'
import { setTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { setOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
// Owns the native-trigger drawer wiring for the pipeline canvas: the nine
// editor instances, the create/edit dispatch by kind, and the delete
@@ -40,9 +40,8 @@
type Props = { onUpdate: () => void; mountTriggerEditors: boolean; workspace?: string }
let { onUpdate, mountTriggerEditors, workspace: triggerWorkspace }: Props = $props()
// Register the trigger-workspace resolver for the whole editor subtree (the
// nine editors + the delete handler below). See triggerWorkspace.ts.
setTriggerWorkspace(() => triggerWorkspace ?? $workspaceStore)
// The nine editors below act on this workspace (see operatingWorkspace.svelte.ts).
setOperatingWorkspace(() => triggerWorkspace)
let kafkaEditor: KafkaTriggerEditor | undefined = $state()
let mqttEditor: MqttTriggerEditor | undefined = $state()
@@ -0,0 +1,29 @@
import { getContext, setContext } from 'svelte'
import { fromStore, toStore, type Readable } from 'svelte/store'
import { workspaceStore } from '$lib/stores'
// The workspace a subtree acts on. An AI session edits its (possibly forked) workspace while
// `workspaceStore` stays on the navigation workspace, and a component that reads the navigation
// store directly writes to the parent from inside a fork's editor. So the hosts that embed an
// editor for another workspace set it once, and every component under them reads
// `useOperatingWorkspace()` instead of `$workspaceStore` — nothing has to thread it through props.
// Outside such a host it is the navigation store itself.
const KEY = Symbol('operatingWorkspace')
const navigation = fromStore(workspaceStore)
/** Declare the workspace this subtree acts on. A getter, read wherever the workspace is used;
* resolving to nothing defers to the enclosing host. Reads context: call during component
* initialisation. */
export function setOperatingWorkspace(resolve: () => string | undefined): void {
const outer = getContext<(() => string | undefined) | undefined>(KEY)
setContext(KEY, outer ? () => resolve() ?? outer() : resolve)
}
/** The workspace this component acts on, as a store. Reads context: call during component
* initialisation. Falls back to the navigation workspace where no host set one, or where the
* host's resolves to nothing. */
export function useOperatingWorkspace(): Readable<string | undefined> {
const resolve = getContext<(() => string | undefined) | undefined>(KEY)
return resolve ? toStore(() => resolve() ?? navigation.current) : workspaceStore
}
@@ -0,0 +1,60 @@
import { readdirSync, readFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
const here = dirname(fileURLToPath(import.meta.url))
// Components an AI session mounts for a workspace other than the navigation one: every trigger
// editor, and the pickers and forms they and the variable/resource editors are built from. One of
// them reading the navigation store writes to the parent from inside a fork's editor, and nothing
// else would catch it — so they read `useOperatingWorkspace()` instead.
const LEAVES = [
'AIProviderPicker.svelte',
'ArgInput.svelte',
'ChannelSelector.svelte',
'EditableSchemaForm.svelte',
'ErrorOrRecoveryHandler.svelte',
'ExploreAssetButton.svelte',
'GitHubAppIntegration.svelte',
'GitLabIntegration.svelte',
'GitRepoResourcePicker.svelte',
'HistoricInputs.svelte',
'InputTransformPickers.svelte',
'InputTransformSchemaForm.svelte',
'LabelsInput.svelte',
'LightweightResourcePicker.svelte',
'PasswordArgInput.svelte',
'Path.svelte',
'PathNameAutocomplete.svelte',
'ResourceEditor.svelte',
'ResourceEditorDrawer.svelte',
'ResourceForm.svelte',
'ResourcePicker.svelte',
'ResourceVersionHistory.svelte',
'S3FilePicker.svelte',
'S3FilePickerInner.svelte',
'S3FilePreview.svelte',
'SaveInputsButton.svelte',
'SavedInputsPicker.svelte',
'SchemaForm.svelte',
'ScriptPicker.svelte',
'TestConnection.svelte',
'VariableEditor.svelte',
'VariableForm.svelte'
]
function svelteFilesUnder(dir: string): string[] {
return readdirSync(join(here, dir), { recursive: true, encoding: 'utf-8' })
.filter((f) => f.endsWith('.svelte'))
.map((f) => join(dir, f))
}
describe('components under a session editor', () => {
it('read the operating workspace, never the navigation store', () => {
const offenders = [...svelteFilesUnder('triggers'), ...LEAVES].filter((f) =>
/\bworkspaceStore\b/.test(readFileSync(join(here, f), 'utf-8'))
)
expect(offenders).toEqual([])
})
})
@@ -2,7 +2,6 @@
import { Settings } from 'lucide-svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import Select from '$lib/components/select/Select.svelte'
import { workspaceStore } from '$lib/stores'
import {
createDatatablesResource,
createSchemasResource,
@@ -10,10 +9,10 @@
toSchemaItems
} from './datatableUtils.svelte'
import { Button } from '../common'
import { getRawAppOperatingWorkspace } from './rawAppWorkspace'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const getOpWs = getRawAppOperatingWorkspace()
let opWs = $derived(getOpWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
let opWs = $derived($operatingWorkspace)
interface Props {
/** Currently selected datatable */
@@ -1,5 +1,4 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import { WorkspaceService } from '$lib/gen'
import Drawer from '../common/drawer/Drawer.svelte'
import DrawerContent from '../common/drawer/DrawerContent.svelte'
@@ -12,12 +11,12 @@
import DBManagerContent from '../DBManagerContent.svelte'
import type { DbInput } from '../dbTypes'
import type { SelectedTable } from '../DBManager.svelte'
import { getRawAppOperatingWorkspace } from './rawAppWorkspace'
import { useDbManagerTag } from '../dbManagerTag.svelte'
import DbWorkerTagButton from '../DbWorkerTagButton.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const getOpWs = getRawAppOperatingWorkspace()
let opWs = $derived(getOpWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
let opWs = $derived($operatingWorkspace)
interface Props {
onAdd?: (ref: DataTableRef) => void
@@ -15,7 +15,7 @@
// import { addWmillClient } from './utils'
import RawAppBackgroundRunner from './RawAppBackgroundRunner.svelte'
import { workspaceStore } from '$lib/stores'
import { setRawAppOperatingWorkspace } from './rawAppWorkspace'
import { setOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
import { useLocalStorageValue } from '$lib/svelte5Utils.svelte'
import {
WMILL_TS_PATH,
@@ -210,9 +210,8 @@
// embedded in a session preview (autosaveWorkspace), else the navigation
// workspace. Deploy/save/background-runner must target it, not $workspaceStore.
const opWorkspace = $derived(autosaveWorkspace ?? $workspaceStore)
// Expose it to the sidebar sub-components (inline scripts, datatable/shared-UI
// drawers, DB selector) so their lookups target the app's workspace too.
setRawAppOperatingWorkspace(() => opWorkspace)
// Everything under the editor acts on it too (see operatingWorkspace.svelte.ts).
setOperatingWorkspace(() => opWorkspace)
// The path autosaves land on, which is what the session preview loads the app by.
const draftStoragePath = $derived(autosavePath ?? liveEditorDraftStoragePath)
@@ -23,8 +23,6 @@
import { resource } from 'runed'
import { usePreparedAssetSqlQueries } from '$lib/infer.svelte'
import AssetsDropdownButton from '../assets/AssetsDropdownButton.svelte'
import { workspaceStore } from '$lib/stores'
import { getRawAppOperatingWorkspace } from './rawAppWorkspace'
import { SvelteSet } from 'svelte/reactivity'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { editor as meditor } from 'monaco-editor'
@@ -42,6 +40,7 @@
getDebugErrorMessage
} from '$lib/components/debug'
import TextInput from '../text_input/TextInput.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
inlineScript: (InlineScript & { language: ScriptLang }) | undefined
@@ -78,8 +77,8 @@
delete_after_secs = $bindable()
}: Props = $props()
const getOpWs = getRawAppOperatingWorkspace()
let opWs = $derived(getOpWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
let opWs = $derived($operatingWorkspace)
let diffEditor = $state() as DiffEditor | undefined
let validCode = $state(true)
@@ -26,13 +26,13 @@
import LogViewer from '$lib/components/LogViewer.svelte'
import DisplayResult from '$lib/components/DisplayResult.svelte'
import RunButton from '$lib/components/RunButton.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { userStore } from '$lib/stores'
import { isHubFlowPath } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { getRawAppOperatingWorkspace } from './rawAppWorkspace'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const getOpWs = getRawAppOperatingWorkspace()
let opWs = $derived(getOpWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
let opWs = $derived($operatingWorkspace)
type RunnableWithInlineScript = RunnableWithFields & {
inlineScript?: InlineScript & { language: ScriptLang }
@@ -1,11 +1,10 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import RawAppInlineScripRunnable, { type Runnable } from './RawAppInlineScriptRunnable.svelte'
import { createScriptFromInlineScript } from '../apps/editor/inlineScriptsPanel/utils'
import { getRawAppOperatingWorkspace } from './rawAppWorkspace'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const getOpWs = getRawAppOperatingWorkspace()
let opWs = $derived(getOpWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
let opWs = $derived($operatingWorkspace)
interface Props {
runnables: Record<string, Runnable>
@@ -13,11 +13,11 @@
import { fieldTypeToTsType } from '../apps/utils'
import type { InputType } from '../apps/inputType'
import Select from '$lib/components/select/Select.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { getRawAppOperatingWorkspace } from './rawAppWorkspace'
import { userStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const getOpWs = getRawAppOperatingWorkspace()
let opWs = $derived(getOpWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
let opWs = $derived($operatingWorkspace)
// Build ctx properties with current user's actual values
let ctxProperties = $derived([
@@ -1,15 +1,14 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import { WorkspaceService } from '$lib/gen'
import { sendUserToast } from '$lib/toast'
import Drawer from '../common/drawer/Drawer.svelte'
import DrawerContent from '../common/drawer/DrawerContent.svelte'
import Editor from '$lib/components/Editor.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { getRawAppOperatingWorkspace } from './rawAppWorkspace'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const getOpWs = getRawAppOperatingWorkspace()
let opWs = $derived(getOpWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
let opWs = $derived($operatingWorkspace)
let open = $state(false)
let files: Record<string, string> = $state({})
@@ -1,10 +1,9 @@
<script lang="ts">
import { Sparkles, Plus, List, Ban, ExternalLinkIcon, Loader2 } from 'lucide-svelte'
import type { Policy } from '$lib/gen'
import { superadmin, userStore, workspaceStore } from '$lib/stores'
import { superadmin, userStore } from '$lib/stores'
import { base } from '$lib/base'
import { sendUserToast } from '$lib/toast'
import { getRawAppOperatingWorkspace } from './rawAppWorkspace'
import Modal from '$lib/components/common/modal/Modal.svelte'
import Button from '$lib/components/common/button/Button.svelte'
import TextInput from '$lib/components/text_input/TextInput.svelte'
@@ -29,6 +28,7 @@
import RawAppDataTableList from './RawAppDataTableList.svelte'
import RawAppDataTableDrawer from './RawAppDataTableDrawer.svelte'
import FileEditorIcon from './FileEditorIcon.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
export type RawAppTemplatePickerResult = {
files: Record<string, string>
@@ -64,8 +64,8 @@
let preWhitelistedTables = $state<DataTableRef[]>([])
let dataTableDrawer: RawAppDataTableDrawer | undefined = $state()
const getOpWs = getRawAppOperatingWorkspace()
let opWs = $derived(getOpWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
let opWs = $derived($operatingWorkspace)
const datatables = createDatatablesResource(() => opWs)
const schemas = createSchemasResource(
@@ -1,22 +0,0 @@
import { getContext, setContext } from 'svelte'
// The workspace a raw-app editor operates on. In a session preview this is the
// session's acting workspace, which differs from the navigation `$workspaceStore`
// (a session deliberately leaves the nav store on the workspace the top nav
// points at). RawAppEditor provides it once; the sidebar sub-components (inline
// scripts, datatable/shared-UI drawers, DB selector, …) read it so their lookups
// target the workspace the app actually lives in rather than the nav workspace.
//
// A getter (not a value) so the live `$derived` opWorkspace is read reactively at
// each call site. Consumers fall back to `$workspaceStore` when unset — e.g. the
// full-page app editor, where the nav workspace IS the operating workspace.
const KEY = 'RawAppOperatingWorkspace'
export function setRawAppOperatingWorkspace(get: () => string | undefined): void {
setContext(KEY, get)
}
export function getRawAppOperatingWorkspace(): (() => string | undefined) | undefined {
return getContext(KEY)
}
@@ -5,7 +5,7 @@
import { ResourceService } from '$lib/gen'
import VariableEditor from '$lib/components/VariableEditor.svelte'
import ResourceEditorDrawer from '$lib/components/ResourceEditorDrawer.svelte'
import { setTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { setOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
import { TRIGGER_PAGES, type PageItemRef, type TriggerKind } from './previewPaths'
import type { SessionRuntime } from './sessionRuntime.svelte'
@@ -29,9 +29,7 @@
'aiChatManager',
untrack(() => runtime.manager)
)
// A session acts on its (possibly forked) workspace without switching the navigation
// store, and the trigger editors read theirs from this seam.
setTriggerWorkspace(() => workspaceId)
setOperatingWorkspace(() => workspaceId)
type TriggerEditorHandle = { openEdit: (path: string, isFlow: boolean) => Promise<void> }
type EditorModule = { default: any }
@@ -9,6 +9,7 @@
import { itemDisplayName } from './previewRouter'
import SessionItemNotFound from './SessionItemNotFound.svelte'
import { setEditorStoragePath } from '../editorStoragePathContext'
import { setOperatingWorkspace } from '../operatingWorkspace.svelte'
let {
runtime,
@@ -51,6 +52,7 @@
// component instance keeps the first runtime's manager — so descendants may
// rely on its presence, not its identity.
setContext('aiChatManager', runtime.manager)
setOperatingWorkspace(() => workspaceId)
// This tab's storage path, for the editor below: several tabs are mounted at
// once and only this one knows which item each is open on.
@@ -8,7 +8,9 @@
import { isCloudHosted } from '$lib/cloud'
import { CloudOff } from 'lucide-svelte'
import { isServiceAvailable } from './native/utils'
import { workspaceStore } from '$lib/stores'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
setDropdownWidthToButtonWidth?: boolean
@@ -41,15 +43,15 @@
let githubAvailable = $state(false)
async function setNextcloudState() {
nextcloudAvailable = await isServiceAvailable('nextcloud', $workspaceStore!)
nextcloudAvailable = await isServiceAvailable('nextcloud', $operatingWorkspace!)
}
async function setGoogleState() {
googleAvailable = await isServiceAvailable('google', $workspaceStore!)
googleAvailable = await isServiceAvailable('google', $operatingWorkspace!)
}
async function setGithubState() {
githubAvailable = await isServiceAvailable('github', $workspaceStore!)
githubAvailable = await isServiceAvailable('github', $operatingWorkspace!)
}
setNextcloudState()
@@ -26,7 +26,6 @@
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { Popover } from '$lib/components/meltComponents'
import { CaptureService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { isObject, sendUserToast } from '$lib/utils'
import { triggerIconMap } from './utils'
import { formatDateShort } from '$lib/utils'
@@ -37,6 +36,9 @@
import { twMerge } from 'tailwind-merge'
import { FlaskConical } from 'lucide-svelte'
import Alert from '../common/alert/Alert.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
disabled?: boolean | undefined
@@ -139,7 +141,7 @@
if (!captureInfo.path) return
const captures = await CaptureService.listCaptures({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
runnableKind: captureInfo.isFlow ? 'flow' : 'script',
path: captureInfo.path,
triggerKind: captureType,
@@ -167,7 +169,7 @@
try {
const captures = await CaptureService.listCaptures({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
runnableKind: captureInfo.isFlow ? 'flow' : 'script',
path: captureInfo.path,
triggerKind: captureType,
@@ -213,7 +215,7 @@
try {
isLoadingBigPayload = true
const fullCapture = await CaptureService.getCapture({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
id: capture.id
})
@@ -10,7 +10,6 @@
import { createEventDispatcher, onDestroy, untrack } from 'svelte'
import { type TriggerKind } from '../triggers'
import { CaptureService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { type CaptureTriggerKind } from '$lib/gen'
import CaptureButton from '$lib/components/triggers/CaptureButton.svelte'
import InfiniteList from '../InfiniteList.svelte'
@@ -19,6 +18,9 @@
import type { Capture } from '$lib/gen'
import { AwsIcon, MqttIcon, AmqpIcon } from '../icons'
import GoogleCloudIcon from '../icons/GoogleCloudIcon.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
path: string
@@ -33,9 +35,8 @@
limitPayloadSize?: boolean
noBorder?: boolean
captureActiveIndicator?: boolean | undefined
// Workspace to scope capture list/get/delete calls to. Defaults to the nav
// `$workspaceStore`; an AI-session live editor passes the session's acting
// workspace (a fork) so captures hit the right workspace.
// Workspace to scope capture list/get/delete calls to. Defaults to the operating
// workspace (see `useOperatingWorkspace`).
workspace?: string
}
@@ -55,7 +56,7 @@
workspace = undefined
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let ws = $derived(workspace ?? $operatingWorkspace)
let selected: number | undefined = $state(undefined)
let testKind: 'preprocessor' | 'main' = $state('main')
@@ -1,5 +1,4 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import { CaptureService, type CaptureConfig, type CaptureTriggerKind } from '$lib/gen'
import { onDestroy, untrack } from 'svelte'
import { sendUserToast, sleep } from '$lib/utils'
@@ -18,6 +17,9 @@
import GcpCapture from './gcp/GcpCapture.svelte'
import AzureCapture from './azure/AzureCapture.svelte'
import EmailCapture from './email/EmailCapture.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
isFlow: boolean
@@ -63,7 +65,7 @@
is_flow: isFlow,
trigger_config: args && Object.keys(args).length > 0 ? args : undefined
},
workspace: $workspaceStore!
workspace: $operatingWorkspace!
})
return true
} catch (error) {
@@ -94,7 +96,7 @@
async function getCaptureConfigs() {
const captureConfigsList = await CaptureService.getCaptureConfigs({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
runnableKind: isFlow ? 'flow' : 'script',
path
})
@@ -123,7 +125,7 @@
while (captureActive) {
if (i % 3 === 0) {
await CaptureService.pingCaptureConfig({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
triggerKind: captureType,
runnableKind: isFlow ? 'flow' : 'script',
path
@@ -4,9 +4,9 @@
type OnBehalfOfDetails
} from '$lib/components/OnBehalfOfSelector.svelte'
import { useFolderDefaultPermissionedAs } from '$lib/components/useFolderDefaultPermissionedAs.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { userStore } from '$lib/stores'
import { AlertTriangle } from 'lucide-svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
/** Current permissioned_as value from the trigger (e.g., 'u/admin') */
@@ -22,8 +22,8 @@
}
let { permissionedAs, onPermissionedAsChange, path = undefined }: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
const canPreserve = $derived(
$userStore?.is_admin || ($userStore?.groups ?? []).includes('wm_deployers')
@@ -10,10 +10,9 @@
WebsocketTriggerService,
GcpTriggerService
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { sendUserToast } from '$lib/toast'
import Button from '../common/button/Button.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
kind: 'websocket' | 'nats' | 'kafka' | 'postgres' | 'sqs' | 'mqtt' | 'amqp' | 'gcp'
@@ -23,8 +22,8 @@
}
let { kind, args, noButton = false, testLoading = $bindable(false) }: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
const kindToName: { [key: string]: string } = {
websocket: 'WebSocket',
@@ -13,8 +13,7 @@
import { stripBase, TRIGGER_PAGES, SCHEDULES_PATH } from '$lib/components/sessions/previewPaths'
import { pageDrawerSessionSource } from '../sessions/pageDrawerSession'
import { page } from '$app/state'
import { workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
import TriggerHistoryButton from './TriggerHistoryButton.svelte'
interface Props {
@@ -63,8 +62,8 @@
triggerPath,
triggerKind
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
const canSave = $derived((permissions === 'write' && edit) || permissions === 'create')
@@ -1,7 +1,6 @@
<script lang="ts">
import { History } from 'lucide-svelte'
import { TriggerService, type TriggerHistoryEntry } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { displayDate } from '$lib/utils'
import Button from '../common/button/Button.svelte'
import Drawer from '../common/drawer/Drawer.svelte'
@@ -9,8 +8,8 @@
import Badge from '../common/badge/Badge.svelte'
import Skeleton from '../common/skeleton/Skeleton.svelte'
import TriggerHistoryChanges from './TriggerHistoryChanges.svelte'
import { getTriggerWorkspace } from './triggerWorkspace'
import type { TriggerType } from './utils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
/** Trigger kind as the backend records it: `schedule`, `http`, `kafka`, … */
@@ -22,8 +21,8 @@
// An AI session can edit a trigger in a workspace that is not the nav one;
// the whole trigger subtree reads its workspace through this seam.
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let drawer: Drawer | undefined = $state()
let entries: TriggerHistoryEntry[] | undefined = $state(undefined)
@@ -19,8 +19,6 @@
TriggerMode
} from '$lib/gen/types.gen'
import Button from '../common/button/Button.svelte'
import { workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { JobService, TriggerService } from '$lib/gen'
import { sendUserToast } from '$lib/toast'
import Cell from '$lib/components/table/Cell.svelte'
@@ -39,6 +37,7 @@
errorHandlerArgs,
slackErrorHandlerHubPathEnding
} from '../ErrorOrRecoveryHandler.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
type Props = {
triggerPath: string
@@ -49,8 +48,8 @@
}
let { triggerKind, triggerPath, onToggleMode, hasChanged, runnableConfig }: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let shouldShowModal = $state(false)
let queuedJobs = $state<QueuedJob[]>([])
@@ -2,13 +2,13 @@
import { run } from 'svelte/legacy'
import { FlowService, ScriptService, UserService, type TruncatedToken } from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { userStore } from '$lib/stores'
import { getContext } from 'svelte'
import { Skeleton } from '../common'
import Label from '../Label.svelte'
import type { TriggerContext } from '../triggers'
import { capitalize } from '$lib/utils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
isFlow: boolean
@@ -17,8 +17,8 @@
}
let { isFlow, path, labelPrefix }: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
const { triggersCount } = getContext<TriggerContext>('TriggerContext')
@@ -1,5 +1,5 @@
<script lang="ts">
import { userStore, workspaceStore } from '$lib/stores'
import { userStore } from '$lib/stores'
import FlowCard from '../flows/common/FlowCard.svelte'
import { getContext, onDestroy, createEventDispatcher } from 'svelte'
import type { TriggerContext } from '$lib/components/triggers'
@@ -38,6 +38,9 @@
import { sendUserToast } from '$lib/toast'
import Alert from '../common/alert/Alert.svelte'
import type { FlowEditorContext } from '../flows/types'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
noEditor: boolean
@@ -137,13 +140,13 @@
try {
if (nativeServiceName) {
await NativeTriggerService.deleteNativeTrigger({
workspace: $workspaceStore ?? '',
workspace: $operatingWorkspace ?? '',
serviceName: nativeServiceName,
externalId: triggerPath ?? ''
})
} else if (deleteHandler) {
await deleteHandler()({
workspace: $workspaceStore ?? '',
workspace: $operatingWorkspace ?? '',
path: triggerPath ?? ''
})
} else {
@@ -187,7 +190,7 @@
if (triggerType === 'schedule') {
await triggersState.fetchSchedules(
triggersCount,
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
undefined,
@@ -196,7 +199,7 @@
} else if (triggerType === 'websocket') {
await triggersState.fetchWebsocketTriggers(
triggersCount,
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
$userStore
@@ -204,7 +207,7 @@
} else if (triggerType === 'postgres') {
await triggersState.fetchPostgresTriggers(
triggersCount,
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
$userStore
@@ -212,7 +215,7 @@
} else if (triggerType === 'kafka') {
await triggersState.fetchKafkaTriggers(
triggersCount,
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
$userStore
@@ -220,7 +223,7 @@
} else if (triggerType === 'nats') {
await triggersState.fetchNatsTriggers(
triggersCount,
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
$userStore
@@ -228,7 +231,7 @@
} else if (triggerType === 'gcp') {
await triggersState.fetchGcpTriggers(
triggersCount,
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
$userStore
@@ -236,7 +239,7 @@
} else if (triggerType === 'azure') {
await triggersState.fetchAzureTriggers(
triggersCount,
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
$userStore
@@ -244,7 +247,7 @@
} else if (triggerType === 'sqs') {
await triggersState.fetchSqsTriggers(
triggersCount,
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
$userStore
@@ -252,7 +255,7 @@
} else if (triggerType === 'mqtt') {
await triggersState.fetchMqttTriggers(
triggersCount,
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
$userStore
@@ -260,7 +263,7 @@
} else if (triggerType === 'amqp') {
await triggersState.fetchAmqpTriggers(
triggersCount,
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
$userStore
@@ -268,7 +271,7 @@
} else if (triggerType === 'http') {
await triggersState.fetchHttpTriggers(
triggersCount,
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
$userStore
@@ -276,7 +279,7 @@
} else if (triggerType === 'email') {
await triggersState.fetchEmailTriggers(
triggersCount,
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
$userStore
@@ -285,7 +288,7 @@
await triggersState.fetchNativeTriggers(
triggersCount,
'nextcloud',
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
$userStore
@@ -294,7 +297,7 @@
await triggersState.fetchNativeTriggers(
triggersCount,
'google',
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
$userStore
@@ -303,7 +306,7 @@
await triggersState.fetchNativeTriggers(
triggersCount,
'github',
$workspaceStore,
$operatingWorkspace,
currentPath,
isFlow,
$userStore
@@ -13,8 +13,7 @@
import TestTriggerConnection from '../TestTriggerConnection.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import TestingBadge from '../testingBadge.svelte'
import { workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
can_write?: boolean
@@ -36,8 +35,8 @@
showTestingBadge = false
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
// Toggling the exchange binding on/off. Off means the queue is consumed
// directly with no exchange binding.
@@ -11,8 +11,7 @@
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import Path from '$lib/components/Path.svelte'
import TriggerRunnablePicker from '$lib/components/triggers/TriggerRunnablePicker.svelte'
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { usedTriggerKinds, userStore } from '$lib/stores'
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
import { withForkConflictRetry } from '$lib/utils/forkConflict'
import Section from '$lib/components/Section.svelte'
@@ -43,6 +42,7 @@
import { deepEqual } from 'fast-equals'
import { useTriggerDraftSync } from '../useTriggerDraftSync.svelte'
import LocalDraftBanner from '$lib/components/LocalDraftBanner.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
useDrawer?: boolean
@@ -82,8 +82,8 @@
onReset = undefined,
cloudDisabled = false
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let amqp_resource_path: string = $state('')
let drawer: Drawer | undefined = $state(undefined)
@@ -10,8 +10,7 @@
import type { AzureMode, AzureArmResource } from '$lib/gen'
import { AzureTriggerService } from '$lib/gen'
import { emptyStringTrimmed } from '$lib/utils'
import { workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
import { RefreshCw } from 'lucide-svelte'
interface Props {
@@ -39,8 +38,8 @@
event_type_filters = $bindable(),
path = ''
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
type Edition = 'basic' | 'namespace'
type Delivery = 'push' | 'pull'
@@ -9,8 +9,7 @@
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import Path from '$lib/components/Path.svelte'
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { usedTriggerKinds, userStore } from '$lib/stores'
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
import { withForkConflictRetry } from '$lib/utils/forkConflict'
import { Loader2 } from 'lucide-svelte'
@@ -41,6 +40,7 @@
import Tab from '$lib/components/common/tabs/Tab.svelte'
import TriggerRetriesAndErrorHandler from '../TriggerRetriesAndErrorHandler.svelte'
import TriggerAdvancedBadges from '../TriggerAdvancedBadges.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
let drawer: Drawer | undefined = $state(undefined)
let initialPath = $state('')
@@ -115,8 +115,8 @@
onReset?: () => void
cloudDisabled?: boolean
} = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let hasChanged = $derived(!deepEqual(getAzureConfig(), originalConfig ?? {}))
const azureConfig = $derived.by(getAzureConfig)
@@ -1,10 +1,12 @@
<script lang="ts">
import Label from '$lib/components/Label.svelte'
import { workspaceStore } from '$lib/stores'
import { base32 } from 'rfc4648'
import ClipboardPanel from '../../details/ClipboardPanel.svelte'
import CaptureSection, { type CaptureInfo } from '../CaptureSection.svelte'
import { fade } from 'svelte/transition'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
isFlow?: boolean
@@ -26,7 +28,7 @@
function getCaptureEmail() {
const cleanedPath = path.replaceAll('/', '.')
const plainPrefix = `capture+${$workspaceStore}+${(isFlow ? 'flow.' : '') + cleanedPath}`
const plainPrefix = `capture+${$operatingWorkspace}+${(isFlow ? 'flow.' : '') + cleanedPath}`
const encodedPrefix = base32
.stringify(new TextEncoder().encode(plainPrefix), {
pad: false
@@ -3,7 +3,6 @@
import Label from '$lib/components/Label.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import { AlertTriangle } from 'lucide-svelte'
import { workspaceStore } from '$lib/stores'
import { SCRIPT_VIEW_SHOW_CREATE_TOKEN_BUTTON } from '$lib/consts'
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
@@ -13,12 +12,16 @@
import { emptyString } from '$lib/utils'
import UserSettings from '$lib/components/UserSettings.svelte'
import TextInput from '$lib/components/text_input/TextInput.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let requestType: 'runnableVersion' | 'path' = $state('path')
function emailAddress() {
const pathOrHash = requestType === 'runnableVersion' ? runnableVersion : path.replaceAll('/', '.')
const plainPrefix = `${$workspaceStore}+${
const pathOrHash =
requestType === 'runnableVersion' ? runnableVersion : path.replaceAll('/', '.')
const plainPrefix = `${$operatingWorkspace}+${
(requestType === 'runnableVersion' ? 'hash.' : isFlow ? 'flow.' : '') + pathOrHash
}+${token}`
const encodedPrefix = base32
@@ -1,5 +1,5 @@
<script lang="ts">
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { enterpriseLicense, userStore } from '$lib/stores'
import UserSettings from '../../UserSettings.svelte'
import { generateRandomString } from '$lib/utils'
import HighlightTheme from '../../HighlightTheme.svelte'
@@ -10,6 +10,9 @@
import Section from '../../Section.svelte'
import DefaultEmailConfigSection from './DefaultEmailConfigSection.svelte'
import { getEmailDomain } from './utils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
let userSettings: UserSettings | undefined = $state(undefined)
interface Props {
@@ -55,7 +58,7 @@
token = e.detail
triggerTokens?.listTokens()
}}
newTokenWorkspace={$workspaceStore}
newTokenWorkspace={$operatingWorkspace}
newTokenLabel={`email-${$userStore?.username ?? 'superadmin'}-${generateRandomString(4)}`}
{scopes}
/>
@@ -70,7 +73,14 @@
<Skeleton layout={[[18]]} />
{:else}
{#if emailDomain}
<DefaultEmailConfigSection {runnableVersion} {token} {path} {isFlow} {userSettings} {emailDomain} />
<DefaultEmailConfigSection
{runnableVersion}
{token}
{path}
{isFlow}
{userSettings}
{emailDomain}
/>
{:else}
<div>
<Alert title="Email triggers are disabled" size="xs" type="warning">
@@ -1,11 +1,13 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import Label from '$lib/components/Label.svelte'
// import { page } from '$app/state'
import type { CaptureInfo } from '../CaptureSection.svelte'
import CaptureSection from '../CaptureSection.svelte'
import { fade } from 'svelte/transition'
import ClipboardPanel from '$lib/components/details/ClipboardPanel.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
local_part: string | undefined
@@ -27,7 +29,7 @@
captureLoading = false
}: Props = $props()
let captureEmail = $derived(`capture+${$workspaceStore}-${local_part}@${emailDomain}`)
let captureEmail = $derived(`capture+${$operatingWorkspace}-${local_part}@${emailDomain}`)
</script>
{#if captureInfo}
@@ -2,8 +2,7 @@
import { Alert } from '$lib/components/common'
import Required from '$lib/components/Required.svelte'
import Section from '$lib/components/Section.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { userStore } from '$lib/stores'
// import { page } from '$app/state'
import { getEmailAddress, getEmailDomain } from './utils'
import { isCloudHosted } from '$lib/cloud'
@@ -12,6 +11,7 @@
import { untrack } from 'svelte'
import { EmailTriggerService } from '$lib/gen'
import ClipboardPanel from '$lib/components/details/ClipboardPanel.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
initialTriggerPath?: string | undefined
dirtyLocalPart?: boolean
@@ -35,8 +35,8 @@
isDraftOnly = true,
showTestingBadge = false
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let validateTimeout: number | undefined = undefined
@@ -18,8 +18,7 @@
type Retry,
type TriggerMode
} from '$lib/gen'
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { usedTriggerKinds, userStore } from '$lib/stores'
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
import Section from '$lib/components/Section.svelte'
import { Loader2 } from 'lucide-svelte'
@@ -39,6 +38,7 @@
import LocalDraftBanner from '$lib/components/LocalDraftBanner.svelte'
import TriggerSuspendedJobsAlert from '../TriggerSuspendedJobsAlert.svelte'
import TriggerSuspendedJobsModal from '../TriggerSuspendedJobsModal.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
let {
useDrawer = true,
@@ -57,8 +57,8 @@
trigger = undefined,
customSaveBehavior = undefined
} = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
// Form data state
let initialPath = $state('')
@@ -1,10 +1,12 @@
<script lang="ts">
import type { CaptureInfo } from '../CaptureSection.svelte'
import CaptureSection from '../CaptureSection.svelte'
import { workspaceStore } from '$lib/stores'
import { Url } from '$lib/components/common'
import { fade } from 'svelte/transition'
import { base } from '$lib/base'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
captureInfo?: CaptureInfo | undefined
@@ -30,7 +32,7 @@
if (!captureInfo) {
return
}
return `${window.location.origin}${base}/api/w/${$workspaceStore}/capture_u/gcp/${
return `${window.location.origin}${base}/api/w/${$operatingWorkspace}/capture_u/gcp/${
captureInfo.isFlow ? 'flow' : 'script'
}/${captureInfo.path}`
}
@@ -15,8 +15,7 @@
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
import { base } from '$lib/base'
import Toggle from '$lib/components/Toggle.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { userStore } from '$lib/stores'
import { Button, Url } from '$lib/components/common'
import TextInput from '$lib/components/text_input/TextInput.svelte'
@@ -25,11 +24,12 @@
import TestingBadge from '../testingBadge.svelte'
import Select from '$lib/components/select/Select.svelte'
import { safeSelectItems } from '$lib/components/select/utils.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
// Declared before `DEFAULT_PUSH_CONFIG` / the `base_endpoint` prop default,
// which call `getBaseUrl()` (a `wsId` reader) during component init.
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let topic_items: string[] = $state([])
let subscription_items: string[] = $state([])
@@ -274,7 +274,10 @@
travel as `?project_id=`, dirty the config, and reach the column as a
value `empty_as_none` does not trim away. -->
<TextInput
bind:value={() => project_id ?? '', (v) => (project_id = emptyStringTrimmed(v) ? undefined : v)}
bind:value={
() => project_id ?? '',
(v) => (project_id = emptyStringTrimmed(v) ? undefined : v)
}
inputProps={{
placeholder: 'my-gcp-project',
disabled: !can_write,
@@ -9,8 +9,7 @@
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import Path from '$lib/components/Path.svelte'
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { usedTriggerKinds, userStore } from '$lib/stores'
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
import { withForkConflictRetry } from '$lib/utils/forkConflict'
import { Loader2 } from 'lucide-svelte'
@@ -44,6 +43,7 @@
import TriggerAdvancedBadges from '../TriggerAdvancedBadges.svelte'
import Subsection from '$lib/components/Subsection.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
let drawer: Drawer | undefined = $state(undefined)
let initialPath = $state('')
@@ -125,8 +125,8 @@
onReset?: () => void
cloudDisabled?: boolean
} = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let hasChanged = $derived(!deepEqual(getGcpConfig(), originalConfig ?? {}))
const gcpConfig = $derived.by(getGcpConfig)
@@ -11,7 +11,7 @@
type OpenapiV3Info,
type WebhookFilters
} from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { userStore } from '$lib/stores'
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
@@ -35,6 +35,9 @@
import CreateToken from '$lib/components/settings/CreateToken.svelte'
import Alert from '$lib/components/common/alert/Alert.svelte'
import { safeSelectItems } from '$lib/components/select/utils.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
type HttpRouteAndWebhook = WebhookFilters | OpenapiHttpRouteFilters
@@ -148,7 +151,7 @@
const info = buildInfo()
isGeneratingOpenapiSpec = true
openapiDocument = await OpenapiService.generateOpenapiSpec({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
requestBody: {
openapi_spec_format,
info,
@@ -222,7 +225,7 @@
{/snippet}
<CopyableCodeBlock
code={`token=${emptyString(token) ? '' : token}; \\
curl -X POST "${window.location.origin}${base}/api/w/${$workspaceStore!}/openapi/generate" \\
curl -X POST "${window.location.origin}${base}/api/w/${$operatingWorkspace!}/openapi/generate" \\
-H "Authorization: Bearer $token" \\
-H "Content-Type: application/json" \\
-d '${JSON.stringify(obj)}'`}
@@ -1,6 +1,4 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import Label from '$lib/components/Label.svelte'
import CopyableCodeBlock from '$lib/components/details/CopyableCodeBlock.svelte'
import { bash } from 'svelte-highlight/languages'
@@ -11,6 +9,7 @@
import { isObject } from '$lib/utils'
import { Url } from '$lib/components/common'
import { fade } from 'svelte/transition'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
route_path: string | undefined
@@ -33,8 +32,8 @@
isFlow = false,
captureLoading = false
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let captureURL = $derived(
`${location.origin}${base}/api/w/${wsId}/capture_u/http/${
@@ -4,8 +4,7 @@
import Section from '$lib/components/Section.svelte'
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { userStore } from '$lib/stores'
import { HttpTriggerService, SettingService } from '$lib/gen'
// import { page } from '$app/state'
import { getHttpRoute } from './utils'
@@ -14,6 +13,7 @@
import TestingBadge from '../testingBadge.svelte'
import { untrack } from 'svelte'
import TextInput from '$lib/components/text_input/TextInput.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
initialTriggerPath?: string | undefined
@@ -42,8 +42,8 @@
isDraftOnly = true,
showTestingBadge = false
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let validateTimeout: number | undefined = undefined
@@ -22,8 +22,7 @@
type Retry,
type TriggerMode
} from '$lib/gen'
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { usedTriggerKinds, userStore } from '$lib/stores'
import {
canWrite,
capitalize,
@@ -78,6 +77,7 @@
import TriggerSuspendedJobsModal from '../TriggerSuspendedJobsModal.svelte'
import UserSettings from '$lib/components/UserSettings.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
let {
useDrawer = true,
@@ -96,8 +96,8 @@
trigger = undefined,
customSaveBehavior = undefined
} = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
// Form data state
let initialPath = $state('')
@@ -16,13 +16,16 @@
import RouteEditor from './RouteEditor.svelte'
import { generateHttpTriggerFromOpenApi, type Source } from './utils'
import { isCloudHosted } from '$lib/cloud'
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
import { usedTriggerKinds, userStore } from '$lib/stores'
import FileInput from '../../common/fileInput/FileInput.svelte'
import { emptyStringTrimmed, sendUserToast } from '$lib/utils'
import FolderPicker from '../../FolderPicker.svelte'
import Required from '$lib/components/Required.svelte'
import { Drawer, DrawerContent } from '$lib/components/common'
import { get } from 'svelte/store'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
type Props = {
closeFn: () => Promise<void>
@@ -108,7 +111,7 @@
try {
isCreating = true
const message = await HttpTriggerService.createHttpTriggers({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
requestBody: httpTriggers
})
sendUserToast(message)
@@ -314,7 +317,7 @@
<div class="text-primary">
{httpTrigger.http_method.toUpperCase()}
{isCloudHosted() || httpTrigger.workspaced_route || globalHttpWorkspacedRoute
? $workspaceStore! + '/' + httpTrigger.route_path
? $operatingWorkspace! + '/' + httpTrigger.route_path
: httpTrigger.route_path}
</div>
<div class="text-secondary text-xs truncate text-left font-light">
@@ -12,8 +12,7 @@
import Path from '$lib/components/Path.svelte'
import TriggerRunnablePicker from '$lib/components/triggers/TriggerRunnablePicker.svelte'
import { KafkaTriggerService, type ErrorHandler, type Retry, type TriggerMode } from '$lib/gen'
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { usedTriggerKinds, userStore } from '$lib/stores'
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
import { withForkConflictRetry } from '$lib/utils/forkConflict'
import Section from '$lib/components/Section.svelte'
@@ -38,6 +37,7 @@
import type { FilterNode } from '../filters'
import Select from '$lib/components/select/Select.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
useDrawer?: boolean
@@ -77,8 +77,8 @@
onDelete = undefined,
onReset = undefined
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let drawer: Drawer | undefined = $state()
let is_flow: boolean = $state(false)
@@ -3,11 +3,10 @@
import Section from '$lib/components/Section.svelte'
import Subsection from '$lib/components/Subsection.svelte'
import SchemaForm from '../../SchemaForm.svelte'
import { workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import TestTriggerConnection from '../TestTriggerConnection.svelte'
import TestingBadge from '../testingBadge.svelte'
import { untrack } from 'svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
path: string
@@ -26,8 +25,8 @@
can_write = true,
showTestingBadge = false
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
const kafkaConfigSchema = {
$schema: 'http://json-schema.org/draft-07/schema#',
@@ -103,7 +102,6 @@
/>
</Subsection>
</div>
</div>
</Section>
</div>
@@ -16,8 +16,7 @@
import Tooltip from '$lib/components/Tooltip.svelte'
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
import TestingBadge from '../testingBadge.svelte'
import { workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
can_write?: boolean
@@ -39,8 +38,8 @@
showTestingBadge = false
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
const isValidSubscribeTopics = (subscribe_topics: MqttSubscribeTopic[]): boolean => {
if (
@@ -11,8 +11,7 @@
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import Path from '$lib/components/Path.svelte'
import TriggerRunnablePicker from '$lib/components/triggers/TriggerRunnablePicker.svelte'
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { usedTriggerKinds, userStore } from '$lib/stores'
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
import { withForkConflictRetry } from '$lib/utils/forkConflict'
import Section from '$lib/components/Section.svelte'
@@ -47,6 +46,7 @@
import { deepEqual } from 'fast-equals'
import { useTriggerDraftSync } from '../useTriggerDraftSync.svelte'
import LocalDraftBanner from '$lib/components/LocalDraftBanner.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
useDrawer?: boolean
@@ -86,8 +86,8 @@
onReset = undefined,
cloudDisabled = false
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let mqtt_resource_path: string = $state('')
let drawer: Drawer | undefined = $state(undefined)
@@ -8,7 +8,7 @@
getTemplatePath,
saveNativeTriggerFromCfg
} from './utils'
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
import { usedTriggerKinds, userStore } from '$lib/stores'
import { canWrite, emptyString, sendUserToast } from '$lib/utils'
import { Button } from '$lib/components/common'
import TextInput from '$lib/components/text_input/TextInput.svelte'
@@ -27,6 +27,9 @@
import { deepEqual } from 'fast-equals'
import type { Snippet } from 'svelte'
import Alert from '$lib/components/common/alert/Alert.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
service: NativeServiceName
@@ -204,7 +207,7 @@
try {
const fullTrigger = await NativeTriggerService.getNativeTrigger({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
serviceName: service,
externalId: externalIdOrPath
})
@@ -308,7 +311,7 @@
enabled = next
try {
await NativeTriggerService.setNativeTriggerEnabled({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
serviceName: service,
externalId,
requestBody: { enabled: next }
@@ -335,7 +338,7 @@
// before anything can pause it again.
isRecreate ? { ...saveCfg, enabled } : saveCfg,
!isNew,
$workspaceStore!,
$operatingWorkspace!,
usedTriggerKinds
)
if (newExternalId) {
@@ -345,7 +348,7 @@
if (isRecreate && oldExternalIdToDelete) {
try {
await NativeTriggerService.deleteNativeTrigger({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
serviceName: service,
externalId: oldExternalIdToDelete
})
@@ -4,7 +4,7 @@
import type { ExtendedNativeTrigger } from './utils'
import { getServiceConfig } from './utils'
import { canWrite, sendUserToast } from '$lib/utils'
import { userStore, workspaceStore } from '$lib/stores'
import { userStore } from '$lib/stores'
import TriggerModeToggle from '$lib/components/triggers/TriggerModeToggle.svelte'
import Skeleton from '$lib/components/common/skeleton/Skeleton.svelte'
import Button from '$lib/components/common/button/Button.svelte'
@@ -16,6 +16,9 @@
import Alert from '$lib/components/common/alert/Alert.svelte'
import GoogleDriveIcon from '$lib/components/icons/GoogleDriveIcon.svelte'
import GoogleCalendarIcon from '$lib/components/icons/GoogleCalendarIcon.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
type TriggerW = ExtendedNativeTrigger & { marked?: any }
@@ -51,7 +54,7 @@
const enabled = mode === 'enabled'
try {
await NativeTriggerService.setNativeTriggerEnabled({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
serviceName: service,
externalId: trigger.external_id,
requestBody: { enabled }
@@ -76,7 +79,7 @@
isDeleting = true
try {
await NativeTriggerService.deleteNativeTrigger({
workspace: $workspaceStore!,
workspace: $operatingWorkspace!,
serviceName: service,
externalId: triggerToDelete.external_id
})
@@ -4,9 +4,11 @@
import MultiSelect from '$lib/components/select/MultiSelect.svelte'
import { NativeTriggerService } from '$lib/gen/services.gen'
import type { GithubRepoEntry } from '$lib/gen/types.gen'
import { workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { Loader2 } from 'lucide-svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
serviceConfig: Record<string, any>
@@ -57,14 +59,14 @@
let selectedEvents = $state<string[]>(externalData?.events ?? serviceConfig.events ?? ['push'])
async function loadRepos() {
if (!$workspaceStore) {
if (!$operatingWorkspace) {
repos = []
return
}
loading = true
try {
repos = await NativeTriggerService.listGithubRepos({
workspace: $workspaceStore
workspace: $operatingWorkspace
})
} catch (err: any) {
console.error('Failed to load GitHub repositories:', err)
@@ -76,7 +78,7 @@
}
$effect(() => {
if ($workspaceStore) {
if ($operatingWorkspace) {
loadRepos()
}
})
@@ -1,11 +1,13 @@
<script lang="ts">
import { NativeTriggerService } from '$lib/gen/services.gen'
import type { GoogleCalendarEntry } from '$lib/gen/types.gen'
import { workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/utils'
import { Badge } from '$lib/components/common'
import { Loader2, X, RefreshCw } from 'lucide-svelte'
import GoogleCalendarIcon from '$lib/components/icons/GoogleCalendarIcon.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
calendarId: string
@@ -13,22 +15,18 @@
disabled?: boolean
}
let {
calendarId = $bindable(),
calendarName = $bindable(),
disabled = false
}: Props = $props()
let { calendarId = $bindable(), calendarName = $bindable(), disabled = false }: Props = $props()
let calendars = $state<GoogleCalendarEntry[]>([])
let loading = $state(false)
async function loadCalendars() {
if (!$workspaceStore) return
if (!$operatingWorkspace) return
loading = true
try {
calendars = await NativeTriggerService.listGoogleCalendars({
workspace: $workspaceStore
workspace: $operatingWorkspace
})
if (calendarId && !calendarName) {
const found = calendars.find((c) => c.id === calendarId)
@@ -55,7 +53,7 @@
}
$effect(() => {
if ($workspaceStore) {
if ($operatingWorkspace) {
loadCalendars()
}
})
@@ -1,23 +1,16 @@
<script lang="ts">
import { NativeTriggerService } from '$lib/gen/services.gen'
import type { GoogleDriveFile, SharedDriveEntry } from '$lib/gen/types.gen'
import { workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/utils'
import { Button } from '$lib/components/common'
import {
Loader2,
Folder,
File,
ChevronRight,
Search,
X,
RefreshCw,
Check
} from 'lucide-svelte'
import { Loader2, Folder, File, ChevronRight, Search, X, RefreshCw, Check } from 'lucide-svelte'
import GoogleDriveIcon from '$lib/components/icons/GoogleDriveIcon.svelte'
import TextInput from '$lib/components/text_input/TextInput.svelte'
import { Debounced, watch } from 'runed'
import { untrack } from 'svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
resourceId: string
@@ -25,11 +18,7 @@
disabled?: boolean
}
let {
resourceId = $bindable(),
resourceName = $bindable(),
disabled = false
}: Props = $props()
let { resourceId = $bindable(), resourceName = $bindable(), disabled = false }: Props = $props()
type BreadcrumbItem = { id: string; name: string }
type DriveTab = 'my_drive' | 'shared_with_me' | 'shared_drives'
@@ -49,7 +38,7 @@
let loadVersion = 0
async function loadFiles(pageToken?: string) {
if (!$workspaceStore) return
if (!$operatingWorkspace) return
const version = ++loadVersion
@@ -63,7 +52,7 @@
try {
const params: Parameters<typeof NativeTriggerService.listGoogleDriveFiles>[0] = {
workspace: $workspaceStore,
workspace: $operatingWorkspace,
pageToken
}
@@ -101,7 +90,7 @@
}
async function loadSharedDrives() {
if (!$workspaceStore) return
if (!$operatingWorkspace) return
loadingFiles = true
files = []
@@ -109,7 +98,7 @@
try {
sharedDrives = await NativeTriggerService.listGoogleSharedDrives({
workspace: $workspaceStore
workspace: $operatingWorkspace
})
} catch (err: any) {
sendUserToast(`Failed to load shared drives: ${err.body || err.message}`, true)
@@ -161,7 +150,7 @@
// Initial load when workspace is available
$effect(() => {
if ($workspaceStore) {
if ($operatingWorkspace) {
untrack(() => loadFiles())
}
})
@@ -169,28 +158,37 @@
// React to debounced search changes (skip initial)
watch(
() => debouncedSearch.current,
() => { loadFiles() },
() => {
loadFiles()
},
{ lazy: true }
)
</script>
<div class="flex flex-col gap-2 border rounded-md p-2 bg-surface">
{#if resourceId}
<div class="flex items-center gap-2 px-2 py-1 rounded bg-surface-selected text-secondary text-xs border">
<div
class="flex items-center gap-2 px-2 py-1 rounded bg-surface-selected text-secondary text-xs border"
>
<GoogleDriveIcon width="14px" height="14px" />
<span>
Selected: <strong>{resourceName || resourceId}</strong>
</span>
<button
class="ml-auto text-tertiary hover:text-secondary"
onclick={() => { resourceId = ''; resourceName = '' }}
onclick={() => {
resourceId = ''
resourceName = ''
}}
{disabled}
>
<X size={14} />
</button>
</div>
{:else}
<div class="flex items-center gap-2 px-2 py-1 rounded text-tertiary text-xs border border-dashed">
<div
class="flex items-center gap-2 px-2 py-1 rounded text-tertiary text-xs border border-dashed"
>
<GoogleDriveIcon width="14px" height="14px" />
<span>No file selected</span>
</div>
@@ -198,21 +196,27 @@
<div class="flex items-center gap-1 text-2xs">
<button
class="px-2 py-0.5 rounded {activeTab === 'my_drive' ? 'bg-surface-selected font-semibold' : 'hover:bg-surface-hover'}"
class="px-2 py-0.5 rounded {activeTab === 'my_drive'
? 'bg-surface-selected font-semibold'
: 'hover:bg-surface-hover'}"
onclick={() => switchTab('my_drive')}
{disabled}
>
My Drive
</button>
<button
class="px-2 py-0.5 rounded {activeTab === 'shared_with_me' ? 'bg-surface-selected font-semibold' : 'hover:bg-surface-hover'}"
class="px-2 py-0.5 rounded {activeTab === 'shared_with_me'
? 'bg-surface-selected font-semibold'
: 'hover:bg-surface-hover'}"
onclick={() => switchTab('shared_with_me')}
{disabled}
>
Shared with me
</button>
<button
class="px-2 py-0.5 rounded {activeTab === 'shared_drives' ? 'bg-surface-selected font-semibold' : 'hover:bg-surface-hover'}"
class="px-2 py-0.5 rounded {activeTab === 'shared_drives'
? 'bg-surface-selected font-semibold'
: 'hover:bg-surface-hover'}"
onclick={() => switchTab('shared_drives')}
{disabled}
>
@@ -222,7 +226,10 @@
<button
class="p-1 text-tertiary hover:text-secondary"
title="Refresh"
onclick={() => activeTab === 'shared_drives' && currentParentId === 'root' ? loadSharedDrives() : loadFiles()}
onclick={() =>
activeTab === 'shared_drives' && currentParentId === 'root'
? loadSharedDrives()
: loadFiles()}
{disabled}
>
<RefreshCw size={12} />
@@ -237,7 +244,10 @@
size="xs"
class="!pl-7"
/>
<Search size={14} class="absolute left-2 top-1/2 -translate-y-1/2 text-tertiary pointer-events-none" />
<Search
size={14}
class="absolute left-2 top-1/2 -translate-y-1/2 text-tertiary pointer-events-none"
/>
{#if searchQuery}
<button
class="absolute right-2 top-1/2 -translate-y-1/2 text-tertiary hover:text-secondary"
@@ -277,9 +287,7 @@
</div>
{:else if activeTab === 'shared_drives' && currentParentId === 'root'}
{#if sharedDrives.length === 0}
<div class="text-center py-4 text-xs text-tertiary">
No shared drives found
</div>
<div class="text-center py-4 text-xs text-tertiary"> No shared drives found </div>
{:else}
{#each sharedDrives as drive (drive.id)}
<div
@@ -1,13 +1,15 @@
<script lang="ts">
import { NativeTriggerService } from '$lib/gen/services.gen'
import type { NextCloudEventType } from '$lib/gen/types.gen'
import { workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/utils'
import { Button } from '$lib/components/common'
import SchemaForm from '$lib/components/SchemaForm.svelte'
import Section from '$lib/components/Section.svelte'
import { Loader2 } from 'lucide-svelte'
import { getNextcloudSchema } from '../../utils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
serviceConfig: Record<string, any>
@@ -38,7 +40,7 @@
let eventsError = $state<string | undefined>(undefined)
async function loadAvailableEvents() {
if (!$workspaceStore) {
if (!$operatingWorkspace) {
availableEvents = []
return
}
@@ -47,7 +49,7 @@
eventsError = undefined
try {
const events = await NativeTriggerService.listNextCloudEvents({
workspace: $workspaceStore!
workspace: $operatingWorkspace!
})
availableEvents = events
serviceSchema = getNextcloudSchema(events)
@@ -84,7 +86,7 @@
let externalDataApplied = $state(false)
$effect(() => {
if ($workspaceStore) {
if ($operatingWorkspace) {
loadAvailableEvents()
}
})
@@ -11,8 +11,7 @@
import Path from '$lib/components/Path.svelte'
import TriggerRunnablePicker from '$lib/components/triggers/TriggerRunnablePicker.svelte'
import { NatsTriggerService, type ErrorHandler, type Retry, type TriggerMode } from '$lib/gen'
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { usedTriggerKinds, userStore } from '$lib/stores'
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
import { withForkConflictRetry } from '$lib/utils/forkConflict'
import Section from '$lib/components/Section.svelte'
@@ -33,6 +32,7 @@
import LocalDraftBanner from '$lib/components/LocalDraftBanner.svelte'
import TriggerSuspendedJobsAlert from '../TriggerSuspendedJobsAlert.svelte'
import TriggerSuspendedJobsModal from '../TriggerSuspendedJobsModal.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
useDrawer?: boolean
@@ -73,8 +73,8 @@
onDelete = undefined,
onReset = undefined
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let drawer: Drawer | undefined = $state(undefined)
let is_flow: boolean = $state(false)
@@ -1,13 +1,12 @@
<script lang="ts">
import Section from '$lib/components/Section.svelte'
import Subsection from '$lib/components/Subsection.svelte'
import { workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import ResourcePicker from '$lib/components/ResourcePicker.svelte'
import SchemaForm from '$lib/components/SchemaForm.svelte'
import TestTriggerConnection from '../TestTriggerConnection.svelte'
import TestingBadge from '../testingBadge.svelte'
import { createEventDispatcher, untrack } from 'svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
defaultValues?: Record<string, any> | undefined
@@ -35,8 +34,8 @@
can_write = true,
showTestingBadge = false
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let otherArgsValid = $state(false)
let globalError = $derived(
@@ -2,10 +2,9 @@
import { Button } from '$lib/components/common'
import Tooltip from '$lib/components/Tooltip.svelte'
import { PostgresTriggerService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { sendUserToast } from '$lib/toast'
import { emptyString } from '$lib/utils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
let loadingConfiguration = $state(false)
@@ -49,14 +48,14 @@
}
interface Props {
can_write: boolean;
postgres_resource_path: string;
checkConnection?: any | undefined;
can_write: boolean
postgres_resource_path: string
checkConnection?: any | undefined
}
let { can_write, postgres_resource_path, checkConnection = undefined }: Props = $props();
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
let { can_write, postgres_resource_path, checkConnection = undefined }: Props = $props()
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
</script>
{#if postgres_resource_path}
@@ -19,8 +19,7 @@
type Retry,
type TriggerMode
} from '$lib/gen'
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { usedTriggerKinds, userStore } from '$lib/stores'
import { canWrite, emptyString, emptyStringTrimmed, sendUserToast } from '$lib/utils'
import { withForkConflictRetry } from '$lib/utils/forkConflict'
import Section from '$lib/components/Section.svelte'
@@ -55,6 +54,7 @@
import { useTriggerDraftSync } from '../useTriggerDraftSync.svelte'
import LocalDraftBanner from '$lib/components/LocalDraftBanner.svelte'
import { capitalize } from '$lib/utils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
useDrawer?: boolean
@@ -94,8 +94,8 @@
onDelete = undefined,
onReset = undefined
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let drawer: Drawer | undefined = $state(undefined)
let is_flow: boolean = $state(false)
@@ -1,25 +1,24 @@
<script lang="ts">
import { run } from 'svelte/legacy';
import { run } from 'svelte/legacy'
import { Button } from '$lib/components/common'
import Select from '$lib/components/select/Select.svelte'
import { safeSelectItems } from '$lib/components/select/utils.svelte'
import type { Relations } from '$lib/gen'
import { PostgresTriggerService } from '$lib/gen/services.gen'
import { workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { sendUserToast } from '$lib/toast'
import { emptyString } from '$lib/utils'
import { RefreshCw } from 'lucide-svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
items?: string[];
can_write?: boolean;
publication_name?: string;
postgres_resource_path?: string;
relations?: Relations[] | undefined;
transaction_to_track?: string[];
disabled?: boolean;
items?: string[]
can_write?: boolean
publication_name?: string
postgres_resource_path?: string
relations?: Relations[] | undefined
transaction_to_track?: string[]
disabled?: boolean
}
let {
@@ -30,9 +29,9 @@
relations = $bindable(undefined),
transaction_to_track = $bindable([]),
disabled = false
}: Props = $props();
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
}: Props = $props()
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let loadingPublication: boolean = $state(false)
let deletingPublication: boolean = $state(false)
@@ -113,7 +112,7 @@
listDatabasePublication()
run(() => {
publication_name && getAllRelations()
});
})
</script>
<div class="flex gap-1">
@@ -3,17 +3,16 @@
import Select from '$lib/components/select/Select.svelte'
import { safeSelectItems } from '$lib/components/select/utils.svelte'
import { PostgresTriggerService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { sendUserToast } from '$lib/toast'
import { emptyString } from '$lib/utils'
import { RefreshCw } from 'lucide-svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
edit: boolean;
replication_slot_name?: string;
postgres_resource_path?: string;
disabled?: boolean;
edit: boolean
replication_slot_name?: string
postgres_resource_path?: string
disabled?: boolean
}
let {
@@ -21,9 +20,9 @@
replication_slot_name = $bindable(''),
postgres_resource_path = '',
disabled = false
}: Props = $props();
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
}: Props = $props()
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let deletingSlot: boolean = $state(false)
let loadingSlot: boolean = $state(false)
@@ -30,7 +30,7 @@
type Schedule,
type ErrorHandler
} from '$lib/gen'
import { enterpriseLicense, workspaceStore } from '$lib/stores'
import { enterpriseLicense } from '$lib/stores'
import { canWrite, emptyString, formatCron, sendUserToast, cronV1toV2 } from '$lib/utils'
import { base } from '$lib/base'
import Section from '$lib/components/Section.svelte'
@@ -50,8 +50,8 @@
import TextInput from '$lib/components/text_input/TextInput.svelte'
import { twMerge } from 'tailwind-merge'
import PermissionedAsLine from '../PermissionedAsLine.svelte'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { useActingUser } from '$lib/actingUser.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
let {
useDrawer = true,
@@ -138,8 +138,8 @@
let selectedPermissionedAs = $state<string | undefined>(undefined)
let preservePermissionedAs = $state(false)
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
// `undefined` while the lookup is in flight or after it failed; the checks below then
// refuse rather than fall back to rights that belong to another workspace.
const acting = useActingUser(() => wsId)
@@ -159,9 +159,9 @@
emptyString(errorHandlerExtraArgs['channel'])) ||
!can_write
)
// Carry the acting workspace onto "create from template" routes when a
// session override is set, so the script is created in the session workspace.
const wsParam = $derived(triggerWs?.() ? `&workspace=${encodeURIComponent(wsId!)}` : '')
// Carry the acting workspace onto "create from template" routes, so the script is created
// in the workspace this schedule lives in.
const wsParam = $derived(wsId ? `&workspace=${encodeURIComponent(wsId)}` : '')
const scheduleCfg = $derived.by(getScheduleCfg)
const draftSync = useTriggerDraftSync({
@@ -13,11 +13,10 @@
import VariableEditor from '$lib/components/VariableEditor.svelte'
import { Button } from '$lib/components/common'
import { VariableService, type AwsAuthResourceType } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import TestingBadge from '../testingBadge.svelte'
import MultiSelect from '$lib/components/select/MultiSelect.svelte'
import { safeSelectItems } from '$lib/components/select/utils.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
can_write?: boolean
@@ -40,8 +39,8 @@
message_attributes = $bindable([]),
showTestingBadge = false
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
async function loadVariables() {
return await VariableService.listVariable({ workspace: wsId ?? '' })
@@ -9,8 +9,7 @@
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import Path from '$lib/components/Path.svelte'
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { usedTriggerKinds, userStore } from '$lib/stores'
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
import { withForkConflictRetry } from '$lib/utils/forkConflict'
import { Loader2 } from 'lucide-svelte'
@@ -39,6 +38,7 @@
import { deepEqual } from 'fast-equals'
import { useTriggerDraftSync } from '../useTriggerDraftSync.svelte'
import LocalDraftBanner from '$lib/components/LocalDraftBanner.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
useDrawer?: boolean
@@ -78,8 +78,8 @@
onDelete = undefined,
onReset = undefined
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let drawer: Drawer | undefined = $state(undefined)
let is_flow: boolean = $state(false)
@@ -1,29 +0,0 @@
import { getContext, setContext } from 'svelte'
const TRIGGER_WORKSPACE_KEY = 'triggerWorkspace'
/**
* Context seam letting the native-trigger editors operate on a workspace other
* than the globally-active `$workspaceStore`. An AI session can run against a
* (possibly forked) workspace that differs from the nav workspace WITHOUT
* switching `$workspaceStore` (see `SessionPicker`), so a host that embeds the
* trigger editors in that context registers a resolver here.
*
* Every workspace-scoped backend call / navigation inside the trigger subtree
* reads its workspace via {@link getTriggerWorkspace} and falls back to
* `$workspaceStore` when no resolver is set the default everywhere outside
* such a host, so behavior there is unchanged. Mirrors the AI chat manager's
* `operatingWorkspace` resolver.
*/
export function setTriggerWorkspace(resolver: () => string | undefined): void {
setContext(TRIGGER_WORKSPACE_KEY, resolver)
}
/**
* The trigger-workspace resolver set by an embedding host, or `undefined` when
* none is set (fall back to `$workspaceStore`). Read once during component init;
* use in a `$derived`: `const ws = $derived(triggerWs?.() ?? $workspaceStore)`.
*/
export function getTriggerWorkspace(): (() => string | undefined) | undefined {
return getContext<(() => string | undefined) | undefined>(TRIGGER_WORKSPACE_KEY)
}
@@ -4,9 +4,11 @@
import { isObject } from '$lib/utils'
import CopyableCodeBlock from '../../details/CopyableCodeBlock.svelte'
import CaptureSection, { type CaptureInfo } from '../CaptureSection.svelte'
import { workspaceStore } from '$lib/stores'
import { Url } from '$lib/components/common'
import { fade } from 'svelte/transition'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
const operatingWorkspace = useOperatingWorkspace()
interface Props {
isFlow?: boolean
@@ -33,7 +35,7 @@
)
let captureUrl = $derived(
`${location.origin}/api/w/${$workspaceStore}/capture_u/webhook/${
`${location.origin}/api/w/${$operatingWorkspace}/capture_u/webhook/${
isFlow ? 'flow' : 'script'
}/${path}`
)
@@ -18,11 +18,11 @@
// import { page } from '$app/state'
import { base } from '$lib/base'
import TriggerTokens from '../TriggerTokens.svelte'
import { workspaceStore, userStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { userStore } from '$lib/stores'
import UserSettings from '../../UserSettings.svelte'
import { generateRandomString } from '$lib/utils'
import TextInput from '$lib/components/text_input/TextInput.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
isFlow?: boolean
@@ -43,8 +43,8 @@
triggerTokens = $bindable(undefined),
scopes = []
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
const WEBHOOK_BASE_URL = $derived(`${location.origin}${base}/api/w/${wsId}/jobs`)
@@ -8,10 +8,9 @@
import { sendUserToast } from '$lib/utils'
import type { Schema } from '$lib/common'
import { FlowService, ScriptService, type Flow, type Script } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import TestTriggerConnection from '../TestTriggerConnection.svelte'
import TestingBadge from '$lib/components/triggers/testingBadge.svelte'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
url: string | undefined
@@ -32,8 +31,8 @@
isValid = $bindable(false),
showTestingBadge = false
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let areRunnableArgsValid: boolean = $state(true)
@@ -25,8 +25,7 @@
type ErrorHandler,
type TriggerMode
} from '$lib/gen'
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
import { getTriggerWorkspace } from '$lib/components/triggers/triggerWorkspace'
import { usedTriggerKinds, userStore } from '$lib/stores'
import { canWrite, emptySchema, emptyString, sendUserToast } from '$lib/utils'
import { withForkConflictRetry } from '$lib/utils/forkConflict'
import Section from '$lib/components/Section.svelte'
@@ -55,6 +54,7 @@
import TriggerSuspendedJobsAlert from '../TriggerSuspendedJobsAlert.svelte'
import TriggerSuspendedJobsModal from '../TriggerSuspendedJobsModal.svelte'
import { capitalize } from '$lib/utils'
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
interface Props {
useDrawer?: boolean
@@ -95,8 +95,8 @@
onReset = undefined,
cloudDisabled = false
}: Props = $props()
const triggerWs = getTriggerWorkspace()
const wsId = $derived(triggerWs?.() ?? $workspaceStore)
const operatingWorkspace = useOperatingWorkspace()
const wsId = $derived($operatingWorkspace)
let drawer: Drawer | undefined = $state()
let is_flow: boolean = $state(false)