From 8c06f77a73536f68866cfeece498afeef1c323a3 Mon Sep 17 00:00:00 2001 From: Will Zimmerman Date: Sat, 2 May 2026 22:00:10 -0700 Subject: [PATCH 1/3] fix: rename simulation Cost placeholder to disambiguate UI vs runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "⛽ (cost estimated at deploy)" reads ambiguously — "at deploy" can be parsed as a future-tense moment ("when you click deploy") or as a reference to a UI step. The intent is the latter: the user sees the real cost estimate at the **Deploy step** in the workflow builder, not after the workflow runs. Capitalize "Deploy" to flag it as the UI action and switch from "estimated" to "shown" so the line reads concretely: ⛽ (cost shown at the Deploy step) Updates Telegram + email formatters, tests, and the change-doc. --- core/taskengine/summarizer_format_email.go | 2 +- core/taskengine/summarizer_format_telegram.go | 4 ++-- core/taskengine/summarizer_format_test.go | 8 ++++---- .../changes/20260502-notification-cost-line-and-runner.md | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/taskengine/summarizer_format_email.go b/core/taskengine/summarizer_format_email.go index ba9278f6..9e825ae8 100644 --- a/core/taskengine/summarizer_format_email.go +++ b/core/taskengine/summarizer_format_email.go @@ -335,7 +335,7 @@ func buildFeesSectionHTML(s Summary) string { if s.Workflow != nil && s.Workflow.IsSimulation { // Heading omitted — the placeholder line carries enough context on its own. return `
` + - `

⛽ (cost estimated at deploy)

` + + `

⛽ (cost shown at the Deploy step)

` + `
` } diff --git a/core/taskengine/summarizer_format_telegram.go b/core/taskengine/summarizer_format_telegram.go index 48e7e14e..88821414 100644 --- a/core/taskengine/summarizer_format_telegram.go +++ b/core/taskengine/summarizer_format_telegram.go @@ -250,11 +250,11 @@ func formatSubjectWithBoldName(subject string) string { // Format: "⛽ Cost: 0.000003 ETH ($0.01), 1.2 USDC ($1.20)" — native // token first, comma-separated, USD parenthetical per token. Unpriceable // tokens render as "$?". For simulations the line collapses to the static -// "⛽ (cost estimated at deploy)" placeholder. Returns "" when there's +// "⛽ (cost shown at the Deploy step)" placeholder. Returns "" when there's // nothing to render. func formatTelegramCostLine(s Summary) string { if s.Workflow != nil && s.Workflow.IsSimulation { - return "⛽ (cost estimated at deploy)\n" + return "⛽ (cost shown at the Deploy step)\n" } if s.Fees == nil || len(s.Fees.Total) == 0 { return "" diff --git a/core/taskengine/summarizer_format_test.go b/core/taskengine/summarizer_format_test.go index cf3007e9..65a99b72 100644 --- a/core/taskengine/summarizer_format_test.go +++ b/core/taskengine/summarizer_format_test.go @@ -1069,7 +1069,7 @@ func TestFormatTelegramFromStructured_RunnerAndFees(t *testing.T) { if strings.Contains(out, "(~") || strings.Contains(out, "Value fee:") { t.Errorf("Telegram should not render gas-units detail or Value fee line, got:\n%s", out) } - if strings.Contains(out, "(cost estimated at deploy)") { + if strings.Contains(out, "(cost shown at the Deploy step)") { t.Errorf("deployed run should not show simulation placeholder, got:\n%s", out) } @@ -1290,7 +1290,7 @@ func mustBigInt(s string) *big.Int { } // TestFormatTelegramFromStructured_Simulation_PlaceholderCost confirms simulation -// runs render the "(cost estimated at deploy)" placeholder instead of fake-precision +// runs render the "(cost shown at the Deploy step)" placeholder instead of fake-precision // numbers. Sim gas prices are conservative chain defaults, so any specific ETH/gas // figure would mislead the user. func TestFormatTelegramFromStructured_Simulation_PlaceholderCost(t *testing.T) { @@ -1318,7 +1318,7 @@ func TestFormatTelegramFromStructured_Simulation_PlaceholderCost(t *testing.T) { out := FormatForMessageChannels(summary, "telegram", nil) - if !strings.Contains(out, "⛽ (cost estimated at deploy)") { + if !strings.Contains(out, "⛽ (cost shown at the Deploy step)") { t.Errorf("simulation should render the deploy-time placeholder, got:\n%s", out) } for _, banned := range []string{"Cost:", "0.0000105 ETH", "21 K gas", "platform fee"} { @@ -1729,7 +1729,7 @@ func TestComposeSummary_SimulateTaskFromClientPayload(t *testing.T) { } // The simulation Cost placeholder uses italic text; allow that but no other italics. - if strings.Contains(telegram, "") && !strings.Contains(telegram, "(cost estimated at deploy)") { + if strings.Contains(telegram, "") && !strings.Contains(telegram, "(cost shown at the Deploy step)") { t.Errorf("Telegram should not contain italic annotation other than the cost placeholder, got:\n%s", telegram) } diff --git a/docs/changes/20260502-notification-cost-line-and-runner.md b/docs/changes/20260502-notification-cost-line-and-runner.md index e24fee28..7fb42526 100644 --- a/docs/changes/20260502-notification-cost-line-and-runner.md +++ b/docs/changes/20260502-notification-cost-line-and-runner.md @@ -45,7 +45,7 @@ Single-line, multi-token, native unit first with USD parenthetical per token: | ETH gas + USDC value-fee | `⛽ Cost: 0.000003 ETH ($0.01), 1.2 USDC ($1.20)` | | Unpriceable ERC20 | `⛽ Cost: 0.000003 ETH ($0.01), 0.005 PEPE ($?)` | | Read-only deployed (no on-chain steps) | (line omitted) | -| Simulation | `⛽ (cost estimated at deploy)` — static, no numbers | +| Simulation | `⛽ (cost shown at the Deploy step)` — static, no numbers | Telegram drops bullets, gas-units, and value-fee detail; email matches. Breakdowns live on the dashboard. Simulation collapses to the placeholder because sim gas prices are conservative chain defaults rather than real network conditions — any specific number would mislead. @@ -83,7 +83,7 @@ To make simulation cogs[] non-empty in the first place, the Tenderly client now - `TestPercentOfRaw` — value-fee percentage math against raw amounts. - `TestTokenBucketToTokenTotal` — stablecoin shortcut, zero-rounding omission, missing price service path. - `TestFormatTelegramFromStructured_RunnerAndFees` and `TestFormatTelegramFromStructured_MultiToken_USDPlaceholder` — end-to-end render assertions. -- Production verification: the next deployed run on Sepolia after `cba1ac8` showed the Telegram block ordering with Runner-and-Network folded onto one line; the simulation summary correctly rendered `⛽ (cost estimated at deploy)` after `4f784c9` (`vm.IsSimulation` flowing through to the formatter). +- Production verification: the next deployed run on Sepolia after `cba1ac8` showed the Telegram block ordering with Runner-and-Network folded onto one line; the simulation summary correctly rendered `⛽ (cost shown at the Deploy step)` after `4f784c9` (`vm.IsSimulation` flowing through to the formatter). - The aggregator's existing tests pass with the price service possibly nil: executor (`executor.go:321,478`), fee estimator (`fee_estimator.go:195`), and the new `buildTotalsFromVM` all guard against `nil` rather than panicking. ## Cross-repo coordination From 914fb2a0386fb0764515abfa4b9e9585cff78388 Mon Sep 17 00:00:00 2001 From: Will Zimmerman Date: Sat, 2 May 2026 22:52:05 -0700 Subject: [PATCH 2/3] fix: reword simulation Cost placeholder to "cost will show before deploy" Future-tense + "before deploy" reads as a clear instruction (the user will see the cost prior to clicking Deploy) without ambiguity around what "deploy" means as a noun. Replaces the prior "(cost shown at the Deploy step)" wording across Telegram, email, tests, and the change-doc. --- core/taskengine/summarizer_format_email.go | 2 +- core/taskengine/summarizer_format_telegram.go | 4 ++-- core/taskengine/summarizer_format_test.go | 8 ++++---- .../changes/20260502-notification-cost-line-and-runner.md | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/taskengine/summarizer_format_email.go b/core/taskengine/summarizer_format_email.go index 9e825ae8..05dbcfa8 100644 --- a/core/taskengine/summarizer_format_email.go +++ b/core/taskengine/summarizer_format_email.go @@ -335,7 +335,7 @@ func buildFeesSectionHTML(s Summary) string { if s.Workflow != nil && s.Workflow.IsSimulation { // Heading omitted — the placeholder line carries enough context on its own. return `
` + - `

⛽ (cost shown at the Deploy step)

` + + `

⛽ (cost will show before deploy)

` + `
` } diff --git a/core/taskengine/summarizer_format_telegram.go b/core/taskengine/summarizer_format_telegram.go index 88821414..97beaea1 100644 --- a/core/taskengine/summarizer_format_telegram.go +++ b/core/taskengine/summarizer_format_telegram.go @@ -250,11 +250,11 @@ func formatSubjectWithBoldName(subject string) string { // Format: "⛽ Cost: 0.000003 ETH ($0.01), 1.2 USDC ($1.20)" — native // token first, comma-separated, USD parenthetical per token. Unpriceable // tokens render as "$?". For simulations the line collapses to the static -// "⛽ (cost shown at the Deploy step)" placeholder. Returns "" when there's +// "⛽ (cost will show before deploy)" placeholder. Returns "" when there's // nothing to render. func formatTelegramCostLine(s Summary) string { if s.Workflow != nil && s.Workflow.IsSimulation { - return "⛽ (cost shown at the Deploy step)\n" + return "⛽ (cost will show before deploy)\n" } if s.Fees == nil || len(s.Fees.Total) == 0 { return "" diff --git a/core/taskengine/summarizer_format_test.go b/core/taskengine/summarizer_format_test.go index 65a99b72..7b5a797d 100644 --- a/core/taskengine/summarizer_format_test.go +++ b/core/taskengine/summarizer_format_test.go @@ -1069,7 +1069,7 @@ func TestFormatTelegramFromStructured_RunnerAndFees(t *testing.T) { if strings.Contains(out, "(~") || strings.Contains(out, "Value fee:") { t.Errorf("Telegram should not render gas-units detail or Value fee line, got:\n%s", out) } - if strings.Contains(out, "(cost shown at the Deploy step)") { + if strings.Contains(out, "(cost will show before deploy)") { t.Errorf("deployed run should not show simulation placeholder, got:\n%s", out) } @@ -1290,7 +1290,7 @@ func mustBigInt(s string) *big.Int { } // TestFormatTelegramFromStructured_Simulation_PlaceholderCost confirms simulation -// runs render the "(cost shown at the Deploy step)" placeholder instead of fake-precision +// runs render the "(cost will show before deploy)" placeholder instead of fake-precision // numbers. Sim gas prices are conservative chain defaults, so any specific ETH/gas // figure would mislead the user. func TestFormatTelegramFromStructured_Simulation_PlaceholderCost(t *testing.T) { @@ -1318,7 +1318,7 @@ func TestFormatTelegramFromStructured_Simulation_PlaceholderCost(t *testing.T) { out := FormatForMessageChannels(summary, "telegram", nil) - if !strings.Contains(out, "⛽ (cost shown at the Deploy step)") { + if !strings.Contains(out, "⛽ (cost will show before deploy)") { t.Errorf("simulation should render the deploy-time placeholder, got:\n%s", out) } for _, banned := range []string{"Cost:", "0.0000105 ETH", "21 K gas", "platform fee"} { @@ -1729,7 +1729,7 @@ func TestComposeSummary_SimulateTaskFromClientPayload(t *testing.T) { } // The simulation Cost placeholder uses italic text; allow that but no other italics. - if strings.Contains(telegram, "") && !strings.Contains(telegram, "(cost shown at the Deploy step)") { + if strings.Contains(telegram, "") && !strings.Contains(telegram, "(cost will show before deploy)") { t.Errorf("Telegram should not contain italic annotation other than the cost placeholder, got:\n%s", telegram) } diff --git a/docs/changes/20260502-notification-cost-line-and-runner.md b/docs/changes/20260502-notification-cost-line-and-runner.md index 7fb42526..85c88b81 100644 --- a/docs/changes/20260502-notification-cost-line-and-runner.md +++ b/docs/changes/20260502-notification-cost-line-and-runner.md @@ -45,7 +45,7 @@ Single-line, multi-token, native unit first with USD parenthetical per token: | ETH gas + USDC value-fee | `⛽ Cost: 0.000003 ETH ($0.01), 1.2 USDC ($1.20)` | | Unpriceable ERC20 | `⛽ Cost: 0.000003 ETH ($0.01), 0.005 PEPE ($?)` | | Read-only deployed (no on-chain steps) | (line omitted) | -| Simulation | `⛽ (cost shown at the Deploy step)` — static, no numbers | +| Simulation | `⛽ (cost will show before deploy)` — static, no numbers | Telegram drops bullets, gas-units, and value-fee detail; email matches. Breakdowns live on the dashboard. Simulation collapses to the placeholder because sim gas prices are conservative chain defaults rather than real network conditions — any specific number would mislead. @@ -83,7 +83,7 @@ To make simulation cogs[] non-empty in the first place, the Tenderly client now - `TestPercentOfRaw` — value-fee percentage math against raw amounts. - `TestTokenBucketToTokenTotal` — stablecoin shortcut, zero-rounding omission, missing price service path. - `TestFormatTelegramFromStructured_RunnerAndFees` and `TestFormatTelegramFromStructured_MultiToken_USDPlaceholder` — end-to-end render assertions. -- Production verification: the next deployed run on Sepolia after `cba1ac8` showed the Telegram block ordering with Runner-and-Network folded onto one line; the simulation summary correctly rendered `⛽ (cost shown at the Deploy step)` after `4f784c9` (`vm.IsSimulation` flowing through to the formatter). +- Production verification: the next deployed run on Sepolia after `cba1ac8` showed the Telegram block ordering with Runner-and-Network folded onto one line; the simulation summary correctly rendered `⛽ (cost will show before deploy)` after `4f784c9` (`vm.IsSimulation` flowing through to the formatter). - The aggregator's existing tests pass with the price service possibly nil: executor (`executor.go:321,478`), fee estimator (`fee_estimator.go:195`), and the new `buildTotalsFromVM` all guard against `nil` rather than panicking. ## Cross-repo coordination From 22645e77b2a5b3fb1eb80d2f0b9f2e0238616adb Mon Sep 17 00:00:00 2001 From: Will Zimmerman Date: Sat, 2 May 2026 22:57:21 -0700 Subject: [PATCH 3/3] fix: reword simulation Cost placeholder to "see cost estimate before deploy" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Estimate" sets the right expectation — what the user sees at the Deploy step is a forecast, not a fixed price. Imperative voice ("see ...") reads as a clear instruction. Singular ("estimate") matches the actual UI: the Deploy step shows one Cost line, not a list. --- core/taskengine/summarizer_format_email.go | 2 +- core/taskengine/summarizer_format_telegram.go | 4 ++-- core/taskengine/summarizer_format_test.go | 8 ++++---- .../changes/20260502-notification-cost-line-and-runner.md | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/taskengine/summarizer_format_email.go b/core/taskengine/summarizer_format_email.go index 05dbcfa8..f83d6ef3 100644 --- a/core/taskengine/summarizer_format_email.go +++ b/core/taskengine/summarizer_format_email.go @@ -335,7 +335,7 @@ func buildFeesSectionHTML(s Summary) string { if s.Workflow != nil && s.Workflow.IsSimulation { // Heading omitted — the placeholder line carries enough context on its own. return `
` + - `

⛽ (cost will show before deploy)

` + + `

⛽ (see cost estimate before deploy)

` + `
` } diff --git a/core/taskengine/summarizer_format_telegram.go b/core/taskengine/summarizer_format_telegram.go index 97beaea1..034b7afb 100644 --- a/core/taskengine/summarizer_format_telegram.go +++ b/core/taskengine/summarizer_format_telegram.go @@ -250,11 +250,11 @@ func formatSubjectWithBoldName(subject string) string { // Format: "⛽ Cost: 0.000003 ETH ($0.01), 1.2 USDC ($1.20)" — native // token first, comma-separated, USD parenthetical per token. Unpriceable // tokens render as "$?". For simulations the line collapses to the static -// "⛽ (cost will show before deploy)" placeholder. Returns "" when there's +// "⛽ (see cost estimate before deploy)" placeholder. Returns "" when there's // nothing to render. func formatTelegramCostLine(s Summary) string { if s.Workflow != nil && s.Workflow.IsSimulation { - return "⛽ (cost will show before deploy)\n" + return "⛽ (see cost estimate before deploy)\n" } if s.Fees == nil || len(s.Fees.Total) == 0 { return "" diff --git a/core/taskengine/summarizer_format_test.go b/core/taskengine/summarizer_format_test.go index 7b5a797d..74de99d2 100644 --- a/core/taskengine/summarizer_format_test.go +++ b/core/taskengine/summarizer_format_test.go @@ -1069,7 +1069,7 @@ func TestFormatTelegramFromStructured_RunnerAndFees(t *testing.T) { if strings.Contains(out, "(~") || strings.Contains(out, "Value fee:") { t.Errorf("Telegram should not render gas-units detail or Value fee line, got:\n%s", out) } - if strings.Contains(out, "(cost will show before deploy)") { + if strings.Contains(out, "(see cost estimate before deploy)") { t.Errorf("deployed run should not show simulation placeholder, got:\n%s", out) } @@ -1290,7 +1290,7 @@ func mustBigInt(s string) *big.Int { } // TestFormatTelegramFromStructured_Simulation_PlaceholderCost confirms simulation -// runs render the "(cost will show before deploy)" placeholder instead of fake-precision +// runs render the "(see cost estimate before deploy)" placeholder instead of fake-precision // numbers. Sim gas prices are conservative chain defaults, so any specific ETH/gas // figure would mislead the user. func TestFormatTelegramFromStructured_Simulation_PlaceholderCost(t *testing.T) { @@ -1318,7 +1318,7 @@ func TestFormatTelegramFromStructured_Simulation_PlaceholderCost(t *testing.T) { out := FormatForMessageChannels(summary, "telegram", nil) - if !strings.Contains(out, "⛽ (cost will show before deploy)") { + if !strings.Contains(out, "⛽ (see cost estimate before deploy)") { t.Errorf("simulation should render the deploy-time placeholder, got:\n%s", out) } for _, banned := range []string{"Cost:", "0.0000105 ETH", "21 K gas", "platform fee"} { @@ -1729,7 +1729,7 @@ func TestComposeSummary_SimulateTaskFromClientPayload(t *testing.T) { } // The simulation Cost placeholder uses italic text; allow that but no other italics. - if strings.Contains(telegram, "") && !strings.Contains(telegram, "(cost will show before deploy)") { + if strings.Contains(telegram, "") && !strings.Contains(telegram, "(see cost estimate before deploy)") { t.Errorf("Telegram should not contain italic annotation other than the cost placeholder, got:\n%s", telegram) } diff --git a/docs/changes/20260502-notification-cost-line-and-runner.md b/docs/changes/20260502-notification-cost-line-and-runner.md index 85c88b81..0897080c 100644 --- a/docs/changes/20260502-notification-cost-line-and-runner.md +++ b/docs/changes/20260502-notification-cost-line-and-runner.md @@ -45,7 +45,7 @@ Single-line, multi-token, native unit first with USD parenthetical per token: | ETH gas + USDC value-fee | `⛽ Cost: 0.000003 ETH ($0.01), 1.2 USDC ($1.20)` | | Unpriceable ERC20 | `⛽ Cost: 0.000003 ETH ($0.01), 0.005 PEPE ($?)` | | Read-only deployed (no on-chain steps) | (line omitted) | -| Simulation | `⛽ (cost will show before deploy)` — static, no numbers | +| Simulation | `⛽ (see cost estimate before deploy)` — static, no numbers | Telegram drops bullets, gas-units, and value-fee detail; email matches. Breakdowns live on the dashboard. Simulation collapses to the placeholder because sim gas prices are conservative chain defaults rather than real network conditions — any specific number would mislead. @@ -83,7 +83,7 @@ To make simulation cogs[] non-empty in the first place, the Tenderly client now - `TestPercentOfRaw` — value-fee percentage math against raw amounts. - `TestTokenBucketToTokenTotal` — stablecoin shortcut, zero-rounding omission, missing price service path. - `TestFormatTelegramFromStructured_RunnerAndFees` and `TestFormatTelegramFromStructured_MultiToken_USDPlaceholder` — end-to-end render assertions. -- Production verification: the next deployed run on Sepolia after `cba1ac8` showed the Telegram block ordering with Runner-and-Network folded onto one line; the simulation summary correctly rendered `⛽ (cost will show before deploy)` after `4f784c9` (`vm.IsSimulation` flowing through to the formatter). +- Production verification: the next deployed run on Sepolia after `cba1ac8` showed the Telegram block ordering with Runner-and-Network folded onto one line; the simulation summary correctly rendered `⛽ (see cost estimate before deploy)` after `4f784c9` (`vm.IsSimulation` flowing through to the formatter). - The aggregator's existing tests pass with the price service possibly nil: executor (`executor.go:321,478`), fee estimator (`fee_estimator.go:195`), and the new `buildTotalsFromVM` all guard against `nil` rather than panicking. ## Cross-repo coordination