Recipes

Weather

Current temperature in the bar; popover with a 3-day forecast via forEach.

Demonstrates external HTTP via curl + jq, forEach over a forecast array, and multi-binding interpolation.

Requires jq (brew install jq). Uses the free wttr.in service — no auth needed but the response is heavyweight, so we extract only what we render.

yaml
id: weather
interval: 900   # 15 minutes
order: 60
location: tray
click:
    url: "https://wttr.in"
command: |
    curl -fsSL --max-time 6 "https://wttr.in/?format=j1" 2>/dev/null \
      | jq -c '{
          temp_f: (.current_condition[0].temp_F | tonumber),
          temp_c: (.current_condition[0].temp_C | tonumber),
          condition: .current_condition[0].weatherDesc[0].value,
          humidity: (.current_condition[0].humidity | tonumber),
          city: .nearest_area[0].areaName[0].value,
          forecast: [.weather[0:3] | .[] | {
            date: .date,
            max_f: (.maxtempF | tonumber),
            min_f: (.mintempF | tonumber),
            cond: .hourly[4].weatherDesc[0].value
          }]
        }' \
      || echo '{"temp_f":null,"condition":"offline"}'
view:
    hstack:
      spacing: 4
      children:
        - symbol: { name: cloud.sun, size: 13, color: "#5AC8FA" }
        - text:
            content: "${temp_f}°"
            size: 11
            weight: semibold
            design: rounded
            monospacedDigit: true
hover:
    vstack:
      spacing: 10
      alignment: leading
      frame: { width: 240 }
      children:
        - hstack:
            children:
              - vstack:
                  alignment: leading
                  spacing: 0
                  children:
                    - text: { content: "${city}", size: 11, color: secondary, weight: medium }
                    - text: { content: "${condition}", size: 13 }
              - spacer: {}
              - text:
                  content: "${temp_f}°"
                  size: 28
                  weight: semibold
                  design: rounded
                  monospacedDigit: true
        - hstack:
            spacing: 12
            children:
              - text: { content: "${temp_c:%.0f}°C", size: 11, color: secondary }
              - text: { content: "Humidity ${humidity}%", size: 11, color: secondary }
        - divider: {}
        - text: { content: "Forecast", size: 10, weight: medium, color: secondary }
        - forEach:
            in: "${forecast}"
            stack: vstack
            spacing: 6
            alignment: leading
            template:
              hstack:
                spacing: 8
                children:
                  - text: { content: "${item.date}", size: 11, design: monospaced }
                  - text: { content: "${item.cond}", size: 11, color: secondary, lineLimit: 1 }
                  - spacer: {}
                  - text:
                      content: "${item.max_f:%.0f}° / ${item.min_f:%.0f}°"
                      size: 11
                      weight: medium
                      design: rounded
                      monospacedDigit: true

What this teaches

  • Async HTTPcurl returns JSON, jq reshapes it, the widget binds to the reshaped fields.
  • Graceful failure — the || echo '{"temp_f":null,"condition":"offline"}' fallback means a network outage shows "offline" instead of breaking the widget.
  • forEach for forecast rows — a single template renders three days from ${forecast}.
  • Multi-binding text"${item.max_f:%.0f}° / ${item.min_f:%.0f}°" interpolates two values into one string with format specs each.

Building on this

  • Pin to a specific city by appending ?location=Berlin or a postcode to the wttr.in URL.
  • Switch to Celsius primary by swapping temp_f and temp_c bindings.
  • Add a "feels like" row to the popover by extracting .current_condition[0].FeelsLikeF in the jq pipeline.