From 32b132bd4fba76e23a0d351014b897274e44bd12 Mon Sep 17 00:00:00 2001 From: re-pixel Date: Mon, 8 Jun 2026 12:20:32 +0200 Subject: [PATCH 1/7] feat: add run() function to expression runtime Signed-off-by: re-pixel --- .../contexts/node_configuration_builder.go | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/pkg/workers/contexts/node_configuration_builder.go b/pkg/workers/contexts/node_configuration_builder.go index f127e895ce..efe5ea4274 100644 --- a/pkg/workers/contexts/node_configuration_builder.go +++ b/pkg/workers/contexts/node_configuration_builder.go @@ -3,6 +3,7 @@ package contexts import ( "encoding/json" "fmt" + "os" "regexp" "sort" "strconv" @@ -317,6 +318,13 @@ func (b *NodeConfigurationBuilder) ResolveExpressionWithExtraVariables(expressio return b.resolvePreviousPayload(depth) }), + expr.Function("run", func(params ...any) (any, error) { + if len(params) != 0 { + return nil, fmt.Errorf("run() takes no arguments") + } + + return b.resolveRunPayload() + }), } vm, err := expr.Compile(expression, exprOptions...) @@ -717,6 +725,67 @@ func (b *NodeConfigurationBuilder) resolveRootPayload() (any, error) { return payload, nil } +// resolveRunPayload exposes the current run to expressions via run(). +// It returns id, url, and started_at (a time.Time) for the run that the +// current node belongs to, resolved from the builder's root event. +func (b *NodeConfigurationBuilder) resolveRunPayload() (any, error) { + if b.rootEventID == nil { + return nil, fmt.Errorf("run() is not available in this context: no run found") + } + + run, err := models.FindCanvasRunByRootEventInTransaction(b.tx, *b.rootEventID) + if err != nil { + return nil, fmt.Errorf("run() could not resolve the current run: %w", err) + } + + payload := map[string]any{ + "id": run.ID.String(), + } + + if run.CreatedAt != nil { + payload["started_at"] = *run.CreatedAt + } + + url, err := b.buildRunURL(run) + if err != nil { + return nil, err + } + payload["url"] = url + + return payload, nil +} + +func (b *NodeConfigurationBuilder) buildRunURL(run *models.CanvasRun) (string, error) { + canvas, err := models.FindCanvasWithoutOrgScopeInTransaction(b.tx, b.workflowID) + if err != nil { + return "", fmt.Errorf("run() could not resolve the organization for the run: %w", err) + } + + return fmt.Sprintf( + "%s/%s/apps/%s?view=runs&run=%s", + runBaseURL(), + canvas.OrganizationID.String(), + b.workflowID.String(), + run.ID.String(), + ), nil +} + +// runBaseURL mirrors the server's base URL resolution so run().url points at +// the SuperPlane UI both in production (BASE_URL) and local development. +func runBaseURL() string { + baseURL := os.Getenv("BASE_URL") + if baseURL != "" { + return baseURL + } + + port := os.Getenv("PORT") + if port == "" { + port = "8000" + } + + return fmt.Sprintf("http://localhost:%s", port) +} + func populateFromInputOrRoot(messageChain map[string]any, inputMap map[string]any, rootEvent *models.CanvasEvent, refToNodeID map[string]string) map[string]string { chainRefs := make(map[string]string, len(refToNodeID)) for nodeRef, nodeID := range refToNodeID { @@ -836,6 +905,7 @@ var reservedExpressionIdentifiers = map[string]struct{}{ "config": {}, "root": {}, "previous": {}, + "run": {}, "ctx": {}, } From e5803e2f2bf8a6133a5d8569a880187648b2af70 Mon Sep 17 00:00:00 2001 From: re-pixel Date: Mon, 8 Jun 2026 12:21:24 +0200 Subject: [PATCH 2/7] feat: validate run() in expression validator Signed-off-by: re-pixel --- pkg/configuration/expressionvalidation/validator.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/configuration/expressionvalidation/validator.go b/pkg/configuration/expressionvalidation/validator.go index fe904d21e6..bf0c3c3489 100644 --- a/pkg/configuration/expressionvalidation/validator.go +++ b/pkg/configuration/expressionvalidation/validator.go @@ -136,6 +136,10 @@ func checkTopLevelCall(name string, args []ast.Node) error { return fmt.Errorf("previous() depth must be an integer literal") } } + case "run": + if len(args) != 0 { + return fmt.Errorf("run() takes no arguments, got %d", len(args)) + } } return nil } @@ -174,6 +178,7 @@ func compileWithStubEnv(body string, knownNodeNames map[string]struct{}, extraEn exprruntime.DateFunctionOption(), expr.Function("root", func(params ...any) (any, error) { return nil, nil }), expr.Function("previous", func(params ...any) (any, error) { return nil, nil }), + expr.Function("run", func(params ...any) (any, error) { return nil, nil }), } if _, err := expr.Compile(body, opts...); err != nil { From b523af2dbc400e35314b599bdedb65de0afb2c41 Mon Sep 17 00:00:00 2001 From: re-pixel Date: Mon, 8 Jun 2026 12:23:53 +0200 Subject: [PATCH 3/7] test: cover run() Signed-off-by: re-pixel --- .../expressionvalidation/validator_test.go | 5 ++ .../node_configuration_builder_expr_test.go | 16 ++++ .../node_configuration_builder_test.go | 73 +++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/pkg/configuration/expressionvalidation/validator_test.go b/pkg/configuration/expressionvalidation/validator_test.go index 46fcc894ac..11ae7885c6 100644 --- a/pkg/configuration/expressionvalidation/validator_test.go +++ b/pkg/configuration/expressionvalidation/validator_test.go @@ -47,6 +47,9 @@ func TestValidateExpression_Valid(t *testing.T) { {name: "root call", raw: `root().data.ref`}, {name: "previous no args", raw: `previous()`}, {name: "previous with int", raw: `previous(2)`}, + {name: "run call", raw: `run().url`}, + {name: "run id", raw: `run().id`}, + {name: "run started_at", raw: `run().started_at`}, {name: "memory find", raw: `memory.find('users', {id: 1})`}, {name: "memory findFirst", raw: `memory.findFirst('users', {id: 1})`}, {name: "string builtins chain", raw: `lower(trim($['Build'].name))`, knownNames: []string{"Build"}}, @@ -83,6 +86,8 @@ func TestValidateExpression_BadArity(t *testing.T) { {name: "previous too many", raw: `previous(1, 2)`, wantErr: "previous() accepts zero or one argument"}, {name: "previous string literal", raw: `previous('a')`, wantErr: "previous() depth must be an integer literal"}, {name: "previous float literal", raw: `previous(1.5)`, wantErr: "previous() depth must be an integer literal"}, + {name: "run with int", raw: `run(1)`, wantErr: "run() takes no arguments"}, + {name: "run with string", raw: `run('x')`, wantErr: "run() takes no arguments"}, {name: "memory.find missing matches", raw: `memory.find('ns')`, wantErr: "memory.find() requires a namespace and matches"}, {name: "memory.find no args", raw: `memory.find()`, wantErr: "memory.find() requires a namespace and matches"}, {name: "memory.findFirst no args", raw: `memory.findFirst()`, wantErr: "memory.findFirst() requires a namespace and matches"}, diff --git a/pkg/workers/contexts/node_configuration_builder_expr_test.go b/pkg/workers/contexts/node_configuration_builder_expr_test.go index 7148234056..3c45294a08 100644 --- a/pkg/workers/contexts/node_configuration_builder_expr_test.go +++ b/pkg/workers/contexts/node_configuration_builder_expr_test.go @@ -37,6 +37,22 @@ func TestNodeConfigurationBuilder_ResolveExpressionWithExtraVariables_RejectsRes require.Contains(t, err.Error(), "reserved") } +func TestNodeConfigurationBuilder_ResolveExpression_RunWithoutRunContext(t *testing.T) { + b := NewNodeConfigurationBuilder(nil, uuid.Nil).WithInput(map[string]any{}) + + _, err := b.ResolveExpression(`run()`) + require.Error(t, err) + require.Contains(t, err.Error(), "no run found") +} + +func TestNodeConfigurationBuilder_ResolveExpression_RunRejectsArguments(t *testing.T) { + b := NewNodeConfigurationBuilder(nil, uuid.Nil).WithInput(map[string]any{}) + + _, err := b.ResolveExpression(`run(1)`) + require.Error(t, err) + require.Contains(t, err.Error(), "run() takes no arguments") +} + func TestNodeConfigurationBuilder_ResolveExpression_UsesConfiguredExpressionVariables(t *testing.T) { b := NewNodeConfigurationBuilder(nil, uuid.Nil). WithInput(map[string]any{}). diff --git a/pkg/workers/contexts/node_configuration_builder_test.go b/pkg/workers/contexts/node_configuration_builder_test.go index ac5d4e1bf2..6b3724323f 100644 --- a/pkg/workers/contexts/node_configuration_builder_test.go +++ b/pkg/workers/contexts/node_configuration_builder_test.go @@ -2,7 +2,9 @@ package contexts import ( "encoding/json" + "fmt" "testing" + "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" @@ -78,6 +80,77 @@ func Test_NodeConfigurationBuilder_WorkflowLevelNode_Root(t *testing.T) { assert.Equal(t, "42", result["count"]) } +func Test_NodeConfigurationBuilder_RunFunction(t *testing.T) { + r := support.Setup(t) + defer r.Close() + + triggerNode := "trigger-1" + componentNode := "component-1" + canvas, _ := support.CreateCanvas( + t, + r.Organization.ID, + r.User, + []models.CanvasNode{ + { + NodeID: triggerNode, + Name: triggerNode, + Type: models.NodeTypeTrigger, + Ref: datatypes.NewJSONType(models.NodeRef{Trigger: &models.TriggerRef{Name: "start"}}), + }, + { + NodeID: componentNode, + Name: componentNode, + Type: models.NodeTypeComponent, + Ref: datatypes.NewJSONType(models.NodeRef{Component: &models.ComponentRef{Name: "noop"}}), + }, + }, + []models.Edge{ + {SourceID: triggerNode, TargetID: componentNode, Channel: "default"}, + }, + ) + + rootEvent := support.EmitCanvasEventForNodeWithData(t, canvas.ID, triggerNode, "default", nil, map[string]any{"user": "john"}) + + // + // Associate the root event with a run so run() can resolve it. + // + run, err := models.FindOrCreateCanvasRunForRootEventInTransaction(database.Conn(), rootEvent) + require.NoError(t, err) + + builder := NewNodeConfigurationBuilder(database.Conn(), canvas.ID). + WithRootEvent(&rootEvent.ID). + WithInput(map[string]any{triggerNode: map[string]any{"user": "john"}}) + + t.Run("returns id, url, and started_at", func(t *testing.T) { + result, err := builder.ResolveExpression(`run()`) + require.NoError(t, err) + + payload, ok := result.(map[string]any) + require.True(t, ok) + + assert.Equal(t, run.ID.String(), payload["id"]) + + expectedURLSuffix := fmt.Sprintf("/%s/apps/%s?view=runs&run=%s", canvas.OrganizationID.String(), canvas.ID.String(), run.ID.String()) + assert.Contains(t, payload["url"], expectedURLSuffix) + + startedAt, ok := payload["started_at"].(time.Time) + require.True(t, ok) + require.NotNil(t, run.CreatedAt) + assert.WithinDuration(t, *run.CreatedAt, startedAt, time.Second) + }) + + t.Run("fields are usable in templates", func(t *testing.T) { + result, err := builder.Build(map[string]any{ + "runID": "{{ run().id }}", + "runURL": "{{ run().url }}", + }) + require.NoError(t, err) + assert.Equal(t, run.ID.String(), result["runID"]) + assert.Contains(t, result["runURL"], run.ID.String()) + assert.Contains(t, result["runURL"], "view=runs") + }) +} + func Test_NodeConfigurationBuilder_JSONNumberTemplateUsesOriginalToken(t *testing.T) { builder := NewNodeConfigurationBuilder(nil, uuid.New()). WithInput(map[string]any{ From 0902250d790f1576d47a6ba53a09f17940761775 Mon Sep 17 00:00:00 2001 From: re-pixel Date: Mon, 8 Jun 2026 12:26:44 +0200 Subject: [PATCH 4/7] feat: add run() to UI autocomplete and preview Signed-off-by: re-pixel --- web_src/src/components/AutoCompleteInput/core.ts | 6 ++++++ web_src/src/lib/exprEvaluator.ts | 3 +++ 2 files changed, 9 insertions(+) diff --git a/web_src/src/components/AutoCompleteInput/core.ts b/web_src/src/components/AutoCompleteInput/core.ts index 02f2274608..f87836dff9 100644 --- a/web_src/src/components/AutoCompleteInput/core.ts +++ b/web_src/src/components/AutoCompleteInput/core.ts @@ -75,6 +75,12 @@ export const EXPR_FUNCTIONS: readonly ExprFunction[] = [ "Returns the payload from the immediate predecessor that emitted this event. Provide depth to walk upstream.", example: "previous(2).data.image.version", }, + { + name: "run", + snippet: "run().", + description: "Returns the current run, exposing its id, url, and started_at.", + example: "run().url", + }, // String { name: "trim", diff --git a/web_src/src/lib/exprEvaluator.ts b/web_src/src/lib/exprEvaluator.ts index 69b72a9937..ba25b1581c 100644 --- a/web_src/src/lib/exprEvaluator.ts +++ b/web_src/src/lib/exprEvaluator.ts @@ -889,6 +889,9 @@ function evaluate(node: ASTNode, context: Record): unknown { return null; }; } + if (node.name === "run") { + return () => (context.__run as unknown) ?? null; + } if (node.name in BUILTIN_FUNCTIONS) { return BUILTIN_FUNCTIONS[node.name]; } From a15fde5e5a100302bf8f4153ae90ca0c21cd18f8 Mon Sep 17 00:00:00 2001 From: re-pixel Date: Mon, 8 Jun 2026 12:32:26 +0200 Subject: [PATCH 5/7] docs: document run() expression function Signed-off-by: re-pixel --- docs/components/Core.mdx | 2 ++ docs/components/Slack.mdx | 2 +- pkg/components/filter/filter.go | 1 + pkg/components/if/if.go | 1 + pkg/integrations/slack/send_text_message.go | 2 +- 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/components/Core.mdx b/docs/components/Core.mdx index 5536b87387..860de35ec5 100644 --- a/docs/components/Core.mdx +++ b/docs/components/Core.mdx @@ -441,6 +441,7 @@ The expression has access to: - **$**: The run context data - **root()**: Access to the root event data - **previous()**: Access to previous node outputs (optionally with depth parameter) +- **run()**: Access to the current run's metadata — `run().id`, `run().url` (a direct link to the run in the SuperPlane UI), and `run().started_at` (a timestamp) ### Examples @@ -600,6 +601,7 @@ The expression has access to: - **$**: The run context data - **root()**: Access to the root event data - **previous()**: Access to previous node outputs (optionally with depth parameter) +- **run()**: Access to the current run's metadata — `run().id`, `run().url` (a direct link to the run in the SuperPlane UI), and `run().started_at` (a timestamp) ### Examples diff --git a/docs/components/Slack.mdx b/docs/components/Slack.mdx index 03008d8a86..3b8b4274d0 100644 --- a/docs/components/Slack.mdx +++ b/docs/components/Slack.mdx @@ -102,7 +102,7 @@ The Send Text Message component sends a text message to a Slack channel. ### Configuration - **Channel**: Select the Slack channel to send the message to -- **Text**: The message text to send (supports expressions and Slack markdown formatting) +- **Text**: The message text to send (supports expressions and Slack markdown formatting). For example, link to the current run with `Deploy started: <{{ run().url }}|view run>`. ### Output diff --git a/pkg/components/filter/filter.go b/pkg/components/filter/filter.go index 1930f6859f..17c65c9cb2 100644 --- a/pkg/components/filter/filter.go +++ b/pkg/components/filter/filter.go @@ -57,6 +57,7 @@ The expression has access to: - **$**: The run context data - **root()**: Access to the root event data - **previous()**: Access to previous node outputs (optionally with depth parameter) +- **run()**: Access to the current run's metadata — ` + "`run().id`" + `, ` + "`run().url`" + ` (a direct link to the run in the SuperPlane UI), and ` + "`run().started_at`" + ` (a timestamp) ## Examples diff --git a/pkg/components/if/if.go b/pkg/components/if/if.go index 7ca77bf5d1..b44002a43c 100644 --- a/pkg/components/if/if.go +++ b/pkg/components/if/if.go @@ -64,6 +64,7 @@ The expression has access to: - **$**: The run context data - **root()**: Access to the root event data - **previous()**: Access to previous node outputs (optionally with depth parameter) +- **run()**: Access to the current run's metadata — ` + "`run().id`" + `, ` + "`run().url`" + ` (a direct link to the run in the SuperPlane UI), and ` + "`run().started_at`" + ` (a timestamp) ## Examples diff --git a/pkg/integrations/slack/send_text_message.go b/pkg/integrations/slack/send_text_message.go index bca3fc5e3b..35f45b3daf 100644 --- a/pkg/integrations/slack/send_text_message.go +++ b/pkg/integrations/slack/send_text_message.go @@ -51,7 +51,7 @@ func (c *SendTextMessage) Documentation() string { ## Configuration - **Channel**: Select the Slack channel to send the message to -- **Text**: The message text to send (supports expressions and Slack markdown formatting) +- **Text**: The message text to send (supports expressions and Slack markdown formatting). For example, link to the current run with ` + "`Deploy started: <{{ run().url }}|view run>`" + `. ## Output From 0792a2400ef72a79f5393166fb0fab3f744c302c Mon Sep 17 00:00:00 2001 From: re-pixel Date: Wed, 10 Jun 2026 11:06:44 +0200 Subject: [PATCH 6/7] fix: wire run Signed-off-by: re-pixel --- pkg/components/memorywrite/list.go | 1 + pkg/components/memorywrite/list_test.go | 2 +- .../AutoCompleteInput/AutoCompleteInput.tsx | 5 ++++ .../components/AutoCompleteInput/core.spec.ts | 14 ++++++++++ .../src/components/AutoCompleteInput/core.ts | 5 ++++ .../pages/app/buildAutocompleteExampleObj.ts | 27 +++++++++++++++++++ web_src/src/pages/app/index.tsx | 4 +++ 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/pkg/components/memorywrite/list.go b/pkg/components/memorywrite/list.go index abbde773ff..76698296d8 100644 --- a/pkg/components/memorywrite/list.go +++ b/pkg/components/memorywrite/list.go @@ -25,6 +25,7 @@ var ( "config": {}, "root": {}, "previous": {}, + "run": {}, "ctx": {}, } diff --git a/pkg/components/memorywrite/list_test.go b/pkg/components/memorywrite/list_test.go index 3ca7238fd5..4e52cb5137 100644 --- a/pkg/components/memorywrite/list_test.go +++ b/pkg/components/memorywrite/list_test.go @@ -52,7 +52,7 @@ func TestListMode_ValidateRequiresSource(t *testing.T) { } func TestListMode_ValidateRejectsReservedAndInvalidNames(t *testing.T) { - cases := []string{"$", "memory", "1bad", "with space", ""} + cases := []string{"$", "memory", "config", "root", "previous", "run", "ctx", "1bad", "with space", ""} for _, name := range cases { t.Run(fmt.Sprintf("rejects %q", name), func(t *testing.T) { err := ListMode{IterateList: true, ListSource: "list", ItemVariable: name}.Validate() diff --git a/web_src/src/components/AutoCompleteInput/AutoCompleteInput.tsx b/web_src/src/components/AutoCompleteInput/AutoCompleteInput.tsx index f50ace8195..ae5f2488b5 100644 --- a/web_src/src/components/AutoCompleteInput/AutoCompleteInput.tsx +++ b/web_src/src/components/AutoCompleteInput/AutoCompleteInput.tsx @@ -704,6 +704,11 @@ export const AutoCompleteInput = forwardRef { expect(labels).toContain("image"); }); + it("suggests run() metadata fields after dot", () => { + const suggestions = getSuggestions("run().", "run().".length, { + __run: { + id: "abc", + url: "https://app.superplane.com/org/apps/canvas?view=runs&run=abc", + started_at: "2026-01-01T00:00:00Z", + }, + }); + const labels = suggestions.map((item) => item.label); + expect(labels).toContain("id"); + expect(labels).toContain("url"); + expect(labels).toContain("started_at"); + }); + it("suggests previous(n) payload fields after dot", () => { const suggestions = getSuggestions("previous(2).", "previous(2).".length, { __previousByDepth: { "2": { build: { id: "abc" } } }, diff --git a/web_src/src/components/AutoCompleteInput/core.ts b/web_src/src/components/AutoCompleteInput/core.ts index f87836dff9..6a8c79972e 100644 --- a/web_src/src/components/AutoCompleteInput/core.ts +++ b/web_src/src/components/AutoCompleteInput/core.ts @@ -1411,6 +1411,11 @@ function normalizeSpecialFunctionExpr(expr: string): string | null { return `__previousByDepth["${depth}"]${expr.slice(previousMatch[0].length)}`; } + const runMatch = expr.match(/^run\(\)/); + if (runMatch) { + return `__run${expr.slice(runMatch[0].length)}`; + } + return expr; } diff --git a/web_src/src/pages/app/buildAutocompleteExampleObj.ts b/web_src/src/pages/app/buildAutocompleteExampleObj.ts index 5169ad6072..5689998f14 100644 --- a/web_src/src/pages/app/buildAutocompleteExampleObj.ts +++ b/web_src/src/pages/app/buildAutocompleteExampleObj.ts @@ -10,8 +10,30 @@ export type AutocompleteExampleContext = { visibleNodeEventsMap: Record; allComponentsByName: Map; allTriggersByName: Map; + organizationId?: string; + canvasId?: string; }; +// Representative run id used purely to preview the shape of run() in the editor; +// the real id is only known at runtime. +const EXAMPLE_RUN_ID = "f47ac10b-58cc-4372-a567-0e02b2c3d479"; + +// buildRunExample mirrors the server's run() payload so the autocomplete can +// surface run().id / run().url / run().started_at and show a representative preview. +function buildRunExample(organizationId?: string, canvasId?: string): Record { + const base = typeof window !== "undefined" ? window.location.origin : ""; + const url = + base && organizationId && canvasId + ? `${base}/${organizationId}/apps/${canvasId}?view=runs&run=${EXAMPLE_RUN_ID}` + : ""; + + return { + id: EXAMPLE_RUN_ID, + url, + started_at: new Date().toISOString(), + }; +} + function collectChainNodeIds( nodeId: string, currentNode: ComponentsNode | undefined, @@ -182,6 +204,7 @@ type BuildNamedExampleObjInput = { previousByDepth: Record; canvasNodes: ComponentsNode[]; incomingNodeIdsByTargetId: Map; + runExample: Record; }; function buildNamedExampleObj({ @@ -193,6 +216,7 @@ function buildNamedExampleObj({ previousByDepth, canvasNodes, incomingNodeIdsByTargetId, + runExample, }: BuildNamedExampleObjInput): Record | null { const rootNodeId = canvasNodes.find((node) => { if (!node.id || !chainNodeIds.has(node.id)) return false; @@ -244,6 +268,8 @@ function buildNamedExampleObj({ namedExampleObj.__previousByDepth = exampleObj.__previousByDepth; } + namedExampleObj.__run = runExample; + const currentNodeName = currentNode?.name?.trim(); const currentNodeId = currentNode?.id; if (currentNodeName) { @@ -289,6 +315,7 @@ export function buildAutocompleteExampleObj( nodeNamesById, nodeMetadata, previousByDepth, + runExample: buildRunExample(context.organizationId, context.canvasId), canvasNodes: context.canvasNodes, incomingNodeIdsByTargetId: context.incomingNodeIdsByTargetId, }); diff --git a/web_src/src/pages/app/index.tsx b/web_src/src/pages/app/index.tsx index 320d04e052..880666a849 100644 --- a/web_src/src/pages/app/index.tsx +++ b/web_src/src/pages/app/index.tsx @@ -2305,6 +2305,8 @@ export function AppPage() { visibleNodeEventsMap, allComponentsByName, allTriggersByName, + organizationId, + canvasId, }), [ canvasNodes, @@ -2314,6 +2316,8 @@ export function AppPage() { visibleNodeEventsMap, allComponentsByName, allTriggersByName, + organizationId, + canvasId, ], ); const getAutocompleteExampleObj = useCallback( From 265d3770098da1bd961af5dfedb24893e1f73e6e Mon Sep 17 00:00:00 2001 From: re-pixel Date: Wed, 10 Jun 2026 12:11:04 +0200 Subject: [PATCH 7/7] fix: remove unneccessary orgid and canvasid threading Signed-off-by: re-pixel --- .../pages/app/buildAutocompleteExampleObj.ts | 19 ++++++++++--------- web_src/src/pages/app/index.tsx | 4 ---- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/web_src/src/pages/app/buildAutocompleteExampleObj.ts b/web_src/src/pages/app/buildAutocompleteExampleObj.ts index 5689998f14..52a16a24f7 100644 --- a/web_src/src/pages/app/buildAutocompleteExampleObj.ts +++ b/web_src/src/pages/app/buildAutocompleteExampleObj.ts @@ -10,8 +10,6 @@ export type AutocompleteExampleContext = { visibleNodeEventsMap: Record; allComponentsByName: Map; allTriggersByName: Map; - organizationId?: string; - canvasId?: string; }; // Representative run id used purely to preview the shape of run() in the editor; @@ -20,12 +18,15 @@ const EXAMPLE_RUN_ID = "f47ac10b-58cc-4372-a567-0e02b2c3d479"; // buildRunExample mirrors the server's run() payload so the autocomplete can // surface run().id / run().url / run().started_at and show a representative preview. -function buildRunExample(organizationId?: string, canvasId?: string): Record { - const base = typeof window !== "undefined" ? window.location.origin : ""; - const url = - base && organizationId && canvasId - ? `${base}/${organizationId}/apps/${canvasId}?view=runs&run=${EXAMPLE_RUN_ID}` - : ""; +// The example url is derived from the current app page location +// (`/{org}/apps/{appId}`), which matches the real run link format. +function buildRunExample(): Record { + let url = ""; + if (typeof window !== "undefined") { + const { origin, pathname } = window.location; + const appPath = pathname.match(/^\/[^/]+\/apps\/[^/]+/)?.[0] ?? pathname; + url = `${origin}${appPath}?view=runs&run=${EXAMPLE_RUN_ID}`; + } return { id: EXAMPLE_RUN_ID, @@ -315,7 +316,7 @@ export function buildAutocompleteExampleObj( nodeNamesById, nodeMetadata, previousByDepth, - runExample: buildRunExample(context.organizationId, context.canvasId), + runExample: buildRunExample(), canvasNodes: context.canvasNodes, incomingNodeIdsByTargetId: context.incomingNodeIdsByTargetId, }); diff --git a/web_src/src/pages/app/index.tsx b/web_src/src/pages/app/index.tsx index 880666a849..320d04e052 100644 --- a/web_src/src/pages/app/index.tsx +++ b/web_src/src/pages/app/index.tsx @@ -2305,8 +2305,6 @@ export function AppPage() { visibleNodeEventsMap, allComponentsByName, allTriggersByName, - organizationId, - canvasId, }), [ canvasNodes, @@ -2316,8 +2314,6 @@ export function AppPage() { visibleNodeEventsMap, allComponentsByName, allTriggersByName, - organizationId, - canvasId, ], ); const getAutocompleteExampleObj = useCallback(