---
title: Release
description: A release console from CI build to a staged rollout a person approves.
category: TemplateData
status: beta
since: 0.6.0
tags: [template, release, pipeline, rollout, approval]
syncStatus: reviewed
verified: false
---

## Usage

### Staged rollout
:::TuffDemoWrapper{demo="TemplateReleaseDemo" code-lang="vue" description="The pipeline runs through, then waits at the approval gate for your click. Versions, checksums, and signatures are sample data."}
---
code: |
  <script setup lang="ts">
  import { reactive, ref } from 'vue'
  import TemplateFrame from './TemplateFrame.vue'

  const steps = reactive({ build: 'completed', sign: 'active', notarize: 'wait', stage: 'wait', rollout: 'wait' })
  const gate = ref<'hidden' | 'waiting' | 'allowed' | 'denied'>('hidden')
  // Until the first rollout, the target is the 5% the gate asks for
  const rollout = reactive({ started: false, current: 0, target: 5, halted: false })

  // CI steps advance on a schedule until staging is done; nothing is scheduled after that
  function run() {
    later(1000, () => Object.assign(steps, { sign: 'completed', notarize: 'active' }))
    // …notarize, stage…
    later(4300, () => {
      steps.rollout = 'active'
      gate.value = 'waiting'
    })
  }

  // Reached only when the reader clicks Start rollout
  function onApprove({ remember }: { remember: boolean }) {
    gate.value = 'allowed'
    autoHalt.value = remember // the checkbox reads "Pause automatically if a guardrail fails"
    Object.assign(rollout, { started: true, current: 5, target: 25 })
  }
  </script>

  <template>
    <TemplateFrame title="Release" :height="600" @enter="run">
      <template #default="{ width, height }">
        <header class="release__head">
          <TxVersionCapsule v-model:panel="panel" version="v2.5.0-demo.4" channel="DEMO">
            <template #download="{ close }">
              <!-- No href on the builds: sample builds have no download -->
              <TxVersionDownloadPanel :notice="demoNotice" :builds="builds" @select="close()" />
            </template>
          </TxVersionCapsule>
          <TxTag label="Sample data" variant="plain" />
        </header>

        <!-- :active="-1" keeps every step's status controlled, so a halted rollout can show as an error -->
        <TxSteps size="small" :active="-1">
          <TxStep v-for="key in STEP_KEYS" :key="key" :title="titles[key]" :status="steps[key]" :clickable="false" />
        </TxSteps>

        <TxDataTable :columns="columns" :data="artifacts" :max-height="width >= 960 ? Math.round(height * 0.42) : undefined">
          <template #cell-sha="{ row }">
            <code>{{ row.hash.slice(0, 4) }}…{{ row.hash.slice(-4) }}</code>
            <TxCopyButton :text="`SAMPLE-${row.hash}`" copy-label="Copy the sample sha256" />
          </template>
        </TxDataTable>
        <div class="release__log">
          <TxCodeStream :code="log" :revealed-lines="revealed" :min-height="0" />
        </div>

        <aside class="release__rail">
          <TxToolConfirmation
            v-if="gate === 'waiting'"
            tool-name="rollout.start" risk="execute"
            allow-label="Start rollout" deny-label="Not yet" remember-label="Pause automatically if a guardrail fails"
            @approve="onApprove" @deny="onDeny"
          />
          <TxTextMorph :text="rollout.current" />
          <TxSegmentedSlider v-model="rollout.target" :segments="[1, 5, 25, 50, 100].map(value => ({ value, label: `${value}%` }))" />
          <TxButton :disabled="!!promoteReason" @click="openModal('promote')">Promote to {{ rollout.target }}%</TxButton>
          <TxSignalMeter v-for="row in guardrails" :key="row.key" :value="row.level" :tone="row.tone" :label="row.label" />
          <TxTimeline>
            <TxTimelineItem v-for="entry in timeline" :key="entry.id" :title="entry.text" :time="entry.time" :color="entry.color" />
          </TxTimeline>
        </aside>

        <TxModal v-model="modal.open" :title="modalTitle">…</TxModal>
      </template>
    </TemplateFrame>
  </template>
---
:::

## Overview

One release of the Tuff desktop app; the pipeline runs itself, and anything that ships to users waits for your click.

- Once in view the pipeline runs through signing, notarization, and staging in about four seconds, then stops at the approval gate with no timer; reduced motion renders everything up to the gate at once.
- Start rollout rolls the share to 5%, and promotion unlocks once the guardrails fill in; Not yet only stages the release, and you can ask again at any time.
- The gate's checkbox reads "Pause automatically if a guardrail fails": the one automatic action on the page, and it only moves the safe way.
- Promote, halt, and resume each ask for a second click; while a guardrail fails or the target isn't above the current share, Promote is disabled and says why.
- "Simulate a crash-rate dip" drops crash-free sessions below 99.50%: with automatic pausing checked the rollout halts at once; otherwise promotion is blocked.
- Builds in the version capsule's download panel are samples with no download, and Esc in an open panel closes only the panel; a copied sha256 carries a `SAMPLE-` prefix.
- Wide (≥ 960px) is three columns; the docs column (640–959px) switches between Log, Artifacts, and Notes; narrow (< 640px) makes Rollout the first tab.

## Customization

- To wire real CI, replace the timed steps in `pipelinePlan()` with events from your build system; each event updates one step and appends a log line.
- Use TxToolConfirmation for actions like the first rollout and let it wait indefinitely; its "remember" checkbox can't be hidden, so give it a meaning that points the safe way.
- TxSteps' `active` index overrides each step's `status`: to show an error state such as a halt, pass `:active="-1"` and control every step yourself.
- Make sample data obvious: a pre-release tag that never ships, checksums and signatures labeled as samples, and no `href` on TxVersionDownloadPanel builds.

## Related components

| Region | Components | Role |
|---|---|---|
| Header | [TxVersionCapsule](/docs/dev/components/version-capsule), [TxTag](/docs/dev/components/tag), [TxWorkingIndicator](/docs/dev/components/working-indicator), [TxStatusBadge](/docs/dev/components/status-badge), [TxButton](/docs/dev/components/button) | Sample version, state, and Halt |
| Pipeline | [TxSteps](/docs/dev/components/steps) | Five controlled pipeline steps |
| Artifacts | [TxDataTable](/docs/dev/components/data-table), [TxCopyButton](/docs/dev/components/button), [TxStatusBadge](/docs/dev/components/status-badge) | Five artifacts with sample checksums |
| Log | [TxCodeStream](/docs/dev/components/code-stream) | Build log, line by line |
| Release notes | [TxMarkdownView](/docs/dev/components/markdown-view) | Notes for the selected version |
| Rollout | [TxTextMorph](/docs/dev/components/text-morph), [TxProgressBar](/docs/dev/components/progress-bar), [TxSegmentedSlider](/docs/dev/components/segmented-slider), [TxAlert](/docs/dev/components/alert) | Share, target stage, and Promote |
| Approval gate | [TxToolConfirmation](/docs/dev/components/tool-confirmation) | Waits for you before rollout |
| Guardrails | [TxSignalMeter](/docs/dev/components/signal-meter), [TxSwitch](/docs/dev/components/switch), [TxSparkChart](/docs/dev/components/spark-chart) | Three guardrails and a simulator |
| This release | [TxTimeline](/docs/dev/components/timeline) | Release history, newest first |
| Confirmation and feedback | [TxModal](/docs/dev/components/modal), [TxToastPanel](/docs/dev/components/toast-panel) | Second click and outcome notices |
