Menu
Get si

Code windows and shows

Code in a macOS, Windows or terminal window, with callouts you place by hand and lines that glide from one step to the next.

Any code fence becomes a window with window=mac. Fences that share an id= become one show: the lines that stay glide to their new place, new ones fade in. On a slide each step is a click; in a page it gets ← → and Play.

Live#

The retry, step by step#

The function that sends an order to the payment provider is twelve lines long today. It took four changes to get there, and each one fixed a night we remember.

retry.go

One attempt

func send(order Order) error {
	resp, err := client.Post(url, order)
	if err != nil {
		return err
	}
	return check(resp)
}
  1. One attempt: a dropped connection loses the order.

Retry five times

func send(order Order) error {
	var last error
	for try := 1; try <= 5; try++ {
		resp, err := client.Post(url, order)
		if err == nil {
			return check(resp)
		}
		last = err
	}
	return last
}
  1. Five tries, back to back.

Back off

func send(order Order) error {
	var last error
	for try := 1; try <= 5; try++ {
		resp, err := client.Post(url, order)
		if err == nil {
			return check(resp)
		}
		last = err
		time.Sleep(backoff(try))
	}
	return last
}
  1. Wait 0.2 s, 0.4 s, 0.8 s… before the next try.

Only what can recover

func send(order Order) error {
	var last error
	for try := 1; try <= 5; try++ {
		resp, err := client.Post(url, order)
		if err == nil {
			return check(resp)
		}
		if !isNetwork(err) {
			return err // a decline is final
		}
		last = err
		time.Sleep(backoff(try) + jitter())
	}
	return last
}
  1. Declines are final: never retry a refused card.
  2. Jitter spreads the retries of a thousand orders.

The same rule in the .NET gateway#

The courier app talks to payments through a small gateway written in C#. It uses the same rule, through a retry policy instead of a loop.

PaymentClient.cs - Gateway
public sealed class PaymentClient
{
    private static readonly ResiliencePipeline Retry = new ResiliencePipelineBuilder()
        .AddRetry(new() { MaxRetryAttempts = 4, BackoffType = DelayBackoffType.Exponential, UseJitter = true })
        .Build();

    public Task<Receipt> SendAsync(Order order) =>
        Retry.ExecuteAsync(async ct => await _http.PostAsJsonAsync<Receipt>("/charge", order, ct)).AsTask();
}
  1. Four retries, growing waits, with jitter.
Gateway.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Polly.Core" Version="8.4.1" />
  </ItemGroup>
</Project>

Proving it before Thursday#

The test that guards the rule runs a hundred times before every release.

checkout
go test ./payments -run TestRetryGivesUpOnDeclines -count=100
ok  	northwind/payments	3.214s

From The retry, step by step

Open the whole example

The Markdown behind it#

examples/code/retry.md, its first 60 lines:

markdown
---
type: article
title: The retry, step by step
description: How the checkout's send() grew from one attempt to a retry that waits, spreads out and gives up on the right errors, one change at a time.
author: Jonas Berg · Payments team
date: 6 November 2026
theme: studio
footer: Northwind Logistics engineering
---

# The retry, step by step

The function that sends an order to the payment provider is twelve lines
long today. It took four changes to get there, and each one fixed a night we
remember.

```go id=retry title="retry.go" window=mac theme=night backdrop=dusk numbers transition=morph step="One attempt"
func send(order Order) error {
	resp, err := client.Post(url, order)
	if err != nil {
		return err
	}
	return check(resp)
}
```
<!-- callout: 2 One attempt: a dropped connection loses the order. at=60,-54 -->

```go id=retry title="retry.go" window=mac theme=night backdrop=dusk numbers transition=morph step="Retry five times"
func send(order Order) error {
	var last error
	for try := 1; try <= 5; try++ {
		resp, err := client.Post(url, order)
		if err == nil {
			return check(resp)
		}
		last = err
	}
	return last
}
```
<!-- callout: 3 Five tries, back to back. at=60,-46 -->

```go id=retry title="retry.go" window=mac theme=night backdrop=dusk numbers transition=morph step="Back off" hl=9
func send(order Order) error {
	var last error
	for try := 1; try <= 5; try++ {
		resp, err := client.Post(url, order)
		if err == nil {
			return check(resp)
		}
		last = err
		time.Sleep(backoff(try))
	}
	return last
}
```
<!-- callout: 9 Wait 0.2 s, 0.4 s, 0.8 s… before the next try. at=60,30 -->

```go id=retry title="retry.go" window=mac theme=night backdrop=dusk numbers transition=morph step="Only what can recover" hl=8-10,12
func send(order Order) error {
…

Key options#

Write What it does
window= mac, windows, terminal, none
theme= backdrop= night, paper, dusk · dusk, ocean, mint, graphite
numbers hl=4,9 line numbers, highlighted lines
id=retry transition= a show: morph, fade, slide
<!-- callout: 9 text at=60,30 --> a numbered note placed by hand

Commands#

Shell
si --web .                       # the Studio's Window view
si code retry.md --png --scale 2
si code retry.md --gif --mp4

The rules#