Part VIII · Reach and shipDistribution artifacts
Chapter 52source checked

Distribution: artifacts, channels, and what actually ships

T3 Code distributes several deliberately different products: an npm CLI that contains a bundled web client, platform-specific Electron installers built from a staged closure, a release-controlled hosted web channel, store binaries plus fingerprint-gated mobile OTAs, and checksum-pinned AUR packages derived from the published Linux AppImage. The release matrix—not every target the builder knows—defines what is actually shipped.

What this chapter resolves
  • Identify the concrete artifact and publication boundary for CLI, desktop, hosted web, mobile, and AUR delivery.
  • Trace how the npm CLI embeds the web renderer and why its exact version publishes before the GitHub Release.
  • Separate the desktop builder's supported platform targets from the four artifacts release CI currently produces.
  • Explain release-controlled hosted channels and fingerprint-gated mobile updates without calling either an arbitrary-commit deployment.
  • Follow a marketing download link and an AUR package back to a named GitHub Release asset.

“T3 Code release” does not name one file. It is a coordinated set of artifacts with different consumers, packaging closures, update mechanisms, and trust boundaries: an npm executable, desktop installers, a hosted web deployment, native mobile builds and JavaScript updates, and an Arch package that repackages a release asset. The important question is therefore not “what command builds T3 Code?” but which artifact is this consumer actually receiving, and what gate certified it?

1. One source revision fans out into several delivery contracts

The monorepo can produce a server/CLI bundle, a web build, desktop process output, and mobile app output, but their distribution boundaries diverge quickly. The CLI is an npm package named t3; desktop is an Electron-builder product with installer and updater side files; the hosted web app is a channel alias; mobile has native-store binaries and compatible OTA bundles; the AUR packages consume a published AppImage rather than independently publishing a desktop build.

The release workflow first resolves stable versus nightly metadata, pins all later jobs to its chosen ref, and gives the channel a version, tag, npm dist-tag, and “latest” policy. Quality, relay configuration, desktop builds, and resource-monitor artifacts feed into CLI publication. Only a successful npm publication unlocks the GitHub Release. A successful GitHub Release then unlocks the AUR handoff and hosted web deploy. This is dependency ordering, not an assertion that all downstream consumer updates happen simultaneously.

Figure 52.1 · One revision, several artifact contractssolid arrows describe published or deployed products; dashed arrows are dependency gates
Distribution artifacts and gatesDiagram loading

Zoom with the controls, +/−, or Ctrl/⌘ + trackpad scroll. Enable Pan to drag, use two-finger scrolling, or use the arrow keys. 0 fits the diagram; Esc leaves Pan or expanded view.

Distribution artifacts and gates
Text equivalent

A release starts from a pinned source revision. Web output is copied into the CLI package, which publishes to npm. Desktop builds create macOS arm64 and x64 DMGs, a Linux x64 AppImage, and a Windows x64 NSIS installer, then those files go to GitHub Releases. AUR reads the Linux AppImage and digest from that release. Hosted web deploys one release-controlled latest or nightly channel. Mobile store binaries and OTA updates take their own EAS path; OTA requires a matching native fingerprint.

Figure 52.1. A pinned release ref builds the web and server output. The CLI package embeds the web output and publishes to npm before GitHub Release publication. Desktop jobs stage desktop, server, web, dependencies, native resources, and platform packaging into four currently active installer lanes; their files are attached to GitHub Releases. The same release gates an AUR workflow that checksum-pins the released Linux AppImage, while hosted web deploys to the chosen latest or nightly channel. Mobile CI separately reconciles store builds and publishes an OTA only when a matching native fingerprint exists.

2. The npm artifact is a server executable plus an embedded renderer

apps/server/package.json declares a public executable: t3 resolves to ./dist/bin.mjs, and npm publishes only dist. The CLI build first runs the server bundle, then looks for apps/web/dist. When that web output exists it is copied into apps/server/dist/client; development icon replacements are applied there as a post-copy product adjustment. That means the npm artifact can carry both the server entrypoints and a renderer payload—rather than downloading the web product at install time.

apps/server/package.json:1–22 ↗verbatim · json · ff0b0d77
{
  "name": "t3",
  "version": "0.0.40",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/pingdotgg/t3code",
    "directory": "apps/server"
  },
  "bin": {
    "t3": "./dist/bin.mjs"
  },
  "files": [
    "dist"
  ],
  "type": "module",
  "scripts": {
    "dev": "node --watch src/bin.ts",
    "build:bundle": "vp pack && vp pack src/service-launcher.ts --out-dir dist --no-clean",
    "start": "node dist/bin.mjs",
    "typecheck": "tsc --noEmit",
    "test": "vp test run"
Read this as: The server package names the `t3` executable and limits npm's published payload to `dist`.
apps/server/scripts/cli.ts:143–176 ↗verbatim · typescript · 4ad46f56
const buildCmd = Command.make(
  "build",
  {
    verbose: Flag.boolean("verbose").pipe(Flag.withDefault(false)),
  },
  (config) =>
    Effect.gen(function* () {
      const path = yield* Path.Path;
      const fs = yield* FileSystem.FileSystem;
      const repoRoot = yield* RepoRoot;
      const serverDir = path.join(repoRoot, "apps/server");
 
      yield* Effect.log("[cli] Running tsdown...");
      yield* runCommand(
        ChildProcess.make(process.execPath, ["--run", "build:bundle"], {
          cwd: serverDir,
          stdout: config.verbose ? "inherit" : "ignore",
          stderr: "inherit",
          shell: false,
        }),
      );
 
      const webDist = path.join(repoRoot, "apps/web/dist");
      const clientTarget = path.join(serverDir, "dist/client");
 
      if (yield* fs.exists(webDist)) {
        yield* fs.copy(webDist, clientTarget);
        yield* applyDevelopmentIconOverrides(repoRoot, serverDir);
        yield* Effect.log("[cli] Bundled web app into dist/client");
      } else {
        yield* Effect.logWarning("[cli] Web dist not found — skipping client bundle.");
      }
    }),
).pipe(Command.withDescription("Build the server package (tsdown + bundle web client)."));
Read this as: The CLI build creates the server bundle, copies the web distribution into `dist/client` when present, and logs the resulting composition.

The build step can warn and continue if apps/web/dist is missing. Publication is stricter: before it rewrites temporary publish metadata it verifies dist/bin.mjs, dist/service-launcher.mjs, and dist/client/index.html. The publish path then resolves workspace catalog/override dependencies into an npm-ready manifest, applies publish icon overrides, invokes package publication with the selected dist-tag/version, and restores the original local metadata and icons in its release finalizer. That restore is a workspace-cleanliness guarantee; it does not undo an already completed npm publication.

3. Desktop packaging constructs a closure, then CI selects four lanes

The desktop artifact builder does more than wrap apps/desktop. It requires emitted desktop, resource, and server directories; verifies that the server bundle is self-contained enough for its runtime needs; requires the embedded server/dist/client/index.html; applies channel-appropriate web branding; and stages Electron output, resources, server output, production dependencies, patches, and platform-specific native materials into a temporary application tree.

The platform split matters. macOS and Linux package a merged application tree. The Windows packaging path deliberately keeps desktop main-process dependencies in app.asar and builds a separate server asar sidecar; it also carries the Windows-to-WSL server support needed by that product. This is an implementation choice about runtime resolution and payload shape, not a claim that every desktop consumer launches a local WSL server.

scripts/build-desktop-artifact.ts:2059–2107 ↗verbatim · typescript · 85de88ac
        }
      });
 
    yield* restoreRelativeSymlinks(source, destination);
  },
);
 
const verifyPackagedBundleIsSelfContained = Effect.fn("verifyPackagedBundleIsSelfContained")(
  function* (input: { readonly asarPath: string; readonly verbose: boolean }) {
    const fs = yield* FileSystem.FileSystem;
    const path = yield* Path.Path;
 
    const probeRoot = yield* fs.makeTempDirectoryScoped({
      prefix: "t3code-bundle-selfcheck-",
    });
    const extractedApp = path.join(probeRoot, "extracted");
    const probeApp = path.join(probeRoot, "app");
    yield* Effect.try({
      try: () => extractAll(input.asarPath, extractedApp),
      catch: (cause) =>
        new BundleNotSelfContainedError({
          exitCode: -1,
          output: `Could not extract ${input.asarPath} for the bundle self-containment check: ${String(cause)}`,
        }),
    });
    // Keep the existing symlink isolation guard even though the sidecar stage
    // is hoisted and should be physical. A future package-manager layout change
    // must not let the probe resolve through the build tree.
    yield* copyDirectoryPreservingSymlinks(extractedApp, probeApp);
 
    // Guard the guard: if anything above the probe provides a node_modules, a
    // missing dependency would resolve there and the check would pass while the
    // packaged tree is broken.
    for (const candidate of ancestorNodeModulesPaths(probeApp, path.sep)) {
      if (yield* fs.exists(candidate).pipe(Effect.orElseSucceed(() => false))) {
        return yield* new BundleNotSelfContainedError({
          exitCode: -1,
          output: `Refusing to report success: ${candidate} is visible from the probe directory, so bare imports could resolve outside the packaged tree. Remove or rename it, or point TMPDIR somewhere without one.`,
        });
      }
    }
 
    const entryPoint = path.join(probeApp, "apps/server/dist/bin.mjs");
    if (!(yield* fs.exists(entryPoint).pipe(Effect.orElseSucceed(() => false)))) {
      return yield* new BundleNotSelfContainedError({
        exitCode: -1,
        output: `Expected the server entry at ${entryPoint}.`,
      });
    }
Read this as: The builder fixes product/artifact naming, packed resources, update publication configuration, and macOS packaging/protocol settings.
scripts/build-desktop-artifact.ts:2133–2174 ↗verbatim · typescript · 72d73898
      {
        label: "server sidecar self-containment check (node bin.mjs --version)",
        verbose: input.verbose,
      },
    ).pipe(
      // Printing a version should be immediate. A regression that blocks (on
      // stdin, a port, a lock) would otherwise hang release CI until the job
      // times out with nothing useful in the log.
      Effect.timeout(BUNDLE_SELF_CHECK_TIMEOUT),
      Effect.catchTags({
        TimeoutError: () =>
          Effect.fail(
            new BundleNotSelfContainedError({
              exitCode: -1,
              output: `The packaged bundle did not print its version within ${Duration.toSeconds(BUNDLE_SELF_CHECK_TIMEOUT)}s; it is hanging rather than failing to resolve.`,
            }),
          ),
        BuildCommandFailedError: (error) =>
          Effect.fail(
            new BundleNotSelfContainedError({
              exitCode: error.exitCode,
              output: `${error.stderrTail ?? ""}${error.stdoutTail ?? ""}`.trim(),
            }),
          ),
      }),
    );
  },
);
 
export const stageLinuxCaptureHelper = Effect.fn("stageLinuxCaptureHelper")(function* (input: {
  readonly backend: "kde" | "hyprland";
  readonly repoRoot: string;
  readonly stageResourcesDir: string;
  readonly arch: typeof BuildArch.Type;
  readonly verbose: boolean;
}) {
  const fs = yield* FileSystem.FileSystem;
  const path = yield* Path.Path;
  const [rustTarget] = resolveResourceMonitorRustTargets("linux", input.arch);
  // Release CI restores these binaries from a cache keyed on the crate sources and
  // skips the Rust toolchain on a hit, so the build must be skippable too.
  const reuseHelpers = yield* Config.boolean("T3CODE_DESKTOP_REUSE_LINUX_CAPTURE_HELPERS").pipe(
Read this as: Linux receives its executable/protocol/desktop configuration; Windows receives NSIS/differential-package and executable/signing configuration.

electron-builder receives an explicit platform flag, architecture, and --publish never; packaging jobs upload their artifacts, then the release job merges the relevant updater manifests and attaches DMGs, ZIPs, AppImages, EXEs, blockmaps, and YAML manifests to GitHub Releases. The builder’s macOS dmg target includes a ZIP updater artifact, which explains why a GitHub Release can have more files than the user-facing installer matrix names.

Shipped matrix vs. possible matrix

Release CI entry Product installer target Status in inspected workflow
macOS arm64 DMG (plus updater ZIP) active
macOS x64 DMG (plus updater ZIP) active
Linux x64 AppImage active
Windows x64 NSIS installer active
Windows arm64 NSIS configuration is structurally possible commented out; not shipped
.github/workflows/release.yml:342–397 ↗verbatim · yaml · ea2ed73a
    runs-on: blacksmith-8vcpu-ubuntu-2404
    timeout-minutes: 15
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          ref: ${{ needs.resolve_commit.outputs.ref }}
          sparse-checkout: |
            /*
            !/.repos/
          sparse-checkout-cone-mode: false
 
      - name: Setup Vite+
        uses: voidzero-dev/setup-vp@v1
        with:
          node-version-file: package.json
          cache: true
          run-install: |
            args:
              - --filter=t3...
 
      - name: Build node-pty linux-x64 prebuild
        shell: bash
        run: |
          set -euo pipefail
          # Resolve node-pty from apps/server (where it's a dependency) and build
          # its native binary from source for Linux. node-addon-api resolves from
          # node-pty's own dependency tree, so node-gyp has everything it needs.
          pty_pkg="$(node -e "console.log(require.resolve('node-pty/package.json', { paths: ['$GITHUB_WORKSPACE/apps/server'] }))")"
          pty_dir="$(dirname "$pty_pkg")"
          ( cd "$pty_dir" && npx --yes node-gyp rebuild )
          mkdir -p wsl-prebuild
          cp "$pty_dir/build/Release/pty.node" wsl-prebuild/pty.node
          file wsl-prebuild/pty.node
 
      - name: Upload node-pty linux-x64 prebuild
        uses: actions/upload-artifact@v7
        with:
          name: wsl-node-pty-x64
          path: wsl-prebuild/pty.node
          if-no-files-found: error
 
  build:
    name: Build ${{ matrix.label }}
    # build_wsl_node_pty stays in `needs` so it runs first and its artifact is
    # available to download, but only the Windows matrix entry consumes it. We
    # therefore gate the job on preflight + relay (must succeed) WITHOUT requiring
    # build_wsl_node_pty, so a failed Linux prebuild doesn't skip the macOS/Linux
    # builds. `!cancelled()` (not `!failure()`) lets the job run even when
    # build_wsl_node_pty failed; the Windows-only download step below then fails
    # that single platform if the prebuild is missing.
    needs: [preflight, relay_public_config, build_wsl_node_pty]
    if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' }}
    runs-on: ${{ matrix.runner }}
    timeout-minutes: 30
    env:
Read this as: Release CI declares four active desktop builds and leaves the Windows arm64 entry commented out.

4. Hosted web and mobile use channels, but their compatibility gates differ

The hosted web app deliberately disables Vercel’s normal Git deployment trigger. Release CI performs the deployment, chooses latest for stable or nightly for a nightly release, and aliases the deployment to that channel domain. The public router uses a long-lived secure cookie to route app.t3.codes requests to the selected channel origin. Thus “latest” is a release result, not necessarily the newest source commit in the repository.

Mobile has a separate production workflow. It runs from Linux because its Expo fingerprint needs to match the build environment, reconciles store builds when the declared mobile version needs one, and publishes an OTA only for each platform with at least one completed production build matching the current native fingerprint. The mobile config explicitly uses the fingerprint runtime-version policy: native dependencies, config plugins, and patches participate in the compatibility identity.

.github/workflows/mobile-eas-production.yml:263–286 ↗verbatim · yaml · fe28375e
      - name: Publish fingerprint-gated OTA
        if: steps.expo-token.outputs.present == 'true' && github.event_name == 'push'
        working-directory: apps/mobile
        env:
          EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
        run: |
          message="$(git log -1 --pretty=%s | head -c 120) ($(git rev-parse --short=9 HEAD))"
          for platform in ios android; do
            # eas-cli prints an environment-loaded notice to stdout before the
            # JSON even with --json, so discard everything before the document.
            hash="$(eas fingerprint:generate --platform "$platform" --environment production --json --non-interactive | sed -n '/^{/,$p' | jq -er '.hash | select(type == "string" and length > 0)')"
            matching="$(eas build:list --platform "$platform" --build-profile production --status finished --fingerprint-hash "$hash" --limit 1 --json --non-interactive | jq 'length')"
            if [ "$matching" -gt 0 ]; then
              eas update \
                --channel production \
                --environment production \
                --platform "$platform" \
                --message "$message" \
                --non-interactive
              echo ":white_check_mark: $platform: OTA published to production (fingerprint \`$hash\`)" >> "$GITHUB_STEP_SUMMARY"
            else
              echo ":warning: $platform: no finished production build matches fingerprint \`$hash\` — OTA skipped; JS changes reach $platform only once a matching build ships" >> "$GITHUB_STEP_SUMMARY"
            fi
          done
Read this as: The production OTA path checks for a finished build whose fingerprint matches before it publishes a platform update.

5. Download resolution and AUR both trust named release assets

The marketing download page fetches the latest GitHub Release and maps cards by asset suffix: arm64/x64 DMGs, an x64 Windows .exe, and an x86_64 AppImage. If the asset lookup or fetch fails, cards fall back to the releases page rather than inventing a direct URL. Mobile cards are intentionally different: they point to the App Store and Google Play, not a GitHub asset.

apps/marketing/src/pages/download.astro:89–132 ↗verbatim · typescript · cd152ea5
        <div class="platform-header">
          <svg class="platform-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M12.504 0c-.155 0-.315.008-.48.021-4.226.333-3.105 4.807-3.17 6.298-.076 1.092-.3 1.953-1.05 3.02-.885 1.051-2.127 2.75-2.716 4.521-.278.832-.41 1.684-.287 2.489a.424.424 0 00-.11.135c-.26.268-.45.6-.663.839-.199.199-.485.267-.797.4-.313.136-.658.269-.864.68-.09.189-.136.394-.132.602 0 .199.027.4.055.536.058.399.116.728.04.97-.249.68-.28 1.145-.106 1.484.174.334.535.47.94.601.81.2 1.91.135 2.774.6.926.466 1.866.67 2.616.47.526-.116.97-.464 1.208-.946.587-.003 1.23-.269 2.26-.334.699-.058 1.574.267 2.577.2.025.134.063.198.114.333l.003.003c.391.778 1.113 1.132 1.884 1.071.771-.06 1.592-.536 2.257-1.306.631-.765 1.683-1.084 2.378-1.503.348-.199.629-.469.649-.853.023-.4-.2-.811-.714-1.376v-.097l-.003-.003c-.17-.2-.25-.535-.338-.926-.085-.401-.182-.786-.492-1.046h-.003c-.059-.054-.123-.067-.188-.135a.357.357 0 00-.19-.064c.431-1.278.264-2.55-.173-3.694-.533-1.41-1.465-2.638-2.175-3.483-.796-1.005-1.576-1.957-1.56-3.368.026-2.152.236-6.133-3.544-6.139zm.529 3.405h.013c.213 0 .396.062.584.198.19.135.33.332.438.533.105.259.158.459.166.724 0-.02.006-.04.006-.06v.105a.086.086 0 01-.004-.021l-.004-.024a1.807 1.807 0 01-.15.706.953.953 0 01-.213.335.71.71 0 00-.088-.042c-.104-.045-.198-.064-.284-.133a1.312 1.312 0 00-.22-.066c.05-.06.146-.133.183-.198.053-.128.082-.264.088-.402v-.02a1.21 1.21 0 00-.061-.4c-.045-.134-.101-.2-.183-.333-.084-.066-.167-.132-.267-.132h-.016c-.093 0-.176.03-.262.132a.8.8 0 00-.205.334 1.18 1.18 0 00-.09.4v.019c.002.089.008.179.02.267-.193-.067-.438-.135-.607-.202a1.635 1.635 0 01-.018-.2v-.02a1.772 1.772 0 01.15-.768c.082-.22.232-.406.43-.533a.985.985 0 01.594-.2zm-2.962.059h.036c.142 0 .27.048.399.135.146.129.264.288.344.465.09.199.14.4.153.667v.004c.007.134.006.2-.002.266v.08c-.03.007-.056.018-.083.024-.152.055-.274.135-.393.2.012-.09.013-.18.003-.267v-.015c-.012-.133-.04-.2-.082-.333a.613.613 0 00-.166-.267.248.248 0 00-.183-.064h-.021c-.071.006-.13.04-.186.132a.552.552 0 00-.12.27.944.944 0 00-.023.33v.015c.012.135.037.2.08.334.046.134.098.2.166.268.01.009.02.018.034.024-.07.057-.117.07-.176.136a.304.304 0 01-.131.068 2.62 2.62 0 01-.275-.402 1.772 1.772 0 01-.155-.667 1.759 1.759 0 01.08-.668 1.43 1.43 0 01.283-.535c.128-.133.26-.2.418-.2zm1.37 1.706c.332 0 .733.065 1.216.399.293.2.523.269 1.052.468h.003c.255.136.405.266.478.399v-.131a.571.571 0 01.016.47c-.123.31-.516.643-1.063.842v.002c-.268.135-.501.333-.775.465-.276.135-.588.292-1.012.267a1.139 1.139 0 01-.448-.067 3.566 3.566 0 01-.322-.198c-.195-.135-.363-.332-.612-.465v-.005h-.005c-.4-.246-.616-.512-.686-.71-.07-.268-.005-.47.193-.6.224-.135.38-.271.483-.336.104-.074.143-.102.176-.131h.002v-.003c.169-.202.436-.47.839-.601.139-.036.294-.065.466-.065zm2.8 2.142c.358 1.417 1.196 3.475 1.735 4.473.286.534.855 1.659 1.102 3.024.156-.005.33.018.513.064.646-1.671-.546-3.467-1.089-3.966-.22-.2-.232-.335-.123-.335.59.534 1.365 1.572 1.646 2.757.13.535.16 1.104.021 1.67.067.028.135.06.205.067 1.032.534 1.413.938 1.23 1.537v-.043c-.06-.003-.12 0-.18 0h-.016c.151-.467-.182-.825-1.065-1.224-.915-.4-1.646-.336-1.77.465-.008.043-.013.066-.018.135-.068.023-.139.053-.209.064-.43.268-.662.669-.793 1.187-.13.533-.17 1.156-.205 1.869v.003c-.02.334-.17.838-.319 1.35-1.5 1.072-3.58 1.538-5.348.334a2.645 2.645 0 00-.402-.533 1.45 1.45 0 00-.275-.333c.182 0 .338-.03.465-.067a.615.615 0 00.314-.334c.108-.267 0-.697-.345-1.163-.345-.467-.931-.995-1.788-1.521-.63-.4-.986-.87-1.15-1.396-.165-.534-.143-1.085-.015-1.645.245-1.07.873-2.11 1.274-2.763.107-.065.037.135-.408.974-.396.751-1.14 2.497-.122 3.854a8.123 8.123 0 01.647-2.876c.564-1.278 1.743-3.504 1.836-5.268.048.036.217.135.289.202.218.133.38.333.59.465.21.201.477.335.876.335.039.003.075.006.11.006.412 0 .73-.134.997-.268.29-.134.52-.334.74-.4h.005c.467-.135.835-.402 1.044-.7zm2.185 8.958c.037.6.343 1.245.882 1.377.588.134 1.434-.333 1.791-.765l.211-.01c.315-.007.577.01.847.268l.003.003c.208.199.305.53.391.876.085.4.154.78.409 1.066.486.527.645.906.636 1.14l.003-.007v.018l-.003-.012c-.015.262-.185.396-.498.595-.63.401-1.746.712-2.457 1.57-.618.737-1.37 1.14-2.036 1.191-.664.053-1.237-.2-1.574-.898l-.005-.003c-.21-.4-.12-1.025.056-1.69.176-.668.428-1.344.463-1.897.037-.714.076-1.335.195-1.814.12-.465.308-.797.641-.984l.045-.022zm-10.814.049h.01c.053 0 .105.005.157.014.376.055.706.333 1.023.752l.91 1.664.003.003c.243.533.754 1.064 1.189 1.637.434.598.77 1.131.729 1.57v.006c-.057.744-.48 1.148-1.125 1.294-.645.135-1.52.002-2.395-.464-.968-.536-2.118-.469-2.857-.602-.369-.066-.61-.2-.723-.4-.11-.2-.113-.602.123-1.23v-.004l.002-.003c.117-.334.03-.752-.027-1.118-.055-.401-.083-.71.043-.94.16-.334.396-.4.69-.533.294-.135.64-.202.915-.47h.002v-.002c.256-.268.445-.601.668-.838.19-.201.38-.336.663-.336zm7.159-9.074c-.435.201-.945.535-1.488.535-.542 0-.97-.267-1.28-.466-.154-.134-.28-.268-.373-.335-.164-.134-.144-.333-.074-.333.109.016.129.134.199.2.096.066.215.2.36.333.292.2.68.467 1.167.467.485 0 1.053-.267 1.398-.466.195-.135.445-.334.648-.467.156-.136.149-.267.279-.267.128.016.034.134-.147.332a8.097 8.097 0 01-.69.468zm-1.082-1.583V5.64c-.006-.02.013-.042.029-.05.074-.043.18-.027.26.004.063 0 .16.067.15.135-.006.049-.085.066-.135.066-.055 0-.092-.043-.141-.068-.052-.018-.146-.008-.163-.065zm-.551 0c-.02.058-.113.049-.166.066-.047.025-.086.068-.14.068-.05 0-.13-.02-.136-.068-.01-.066.088-.133.15-.133.08-.031.184-.047.259-.005.019.009.036.03.03.05v.02h.003z"/></svg>
          <h2 class="platform-name">Linux</h2>
        </div>
        <div class="download-cards">
          <a class="download-card" data-asset="x86_64.AppImage" href={RELEASES_URL}>
            <span class="card-icon">
              <Image class="card-icon--stable" src={iconStable} width={28} height={28} loading="lazy" {...imageProps} />
              <Image class="card-icon--nightly" src={iconNightly} width={28} height={28} loading="lazy" {...imageProps} />
            </span>
            <span class="card-text">
              <span class="card-arch">x86_64</span>
              <span class="card-format">AppImage</span>
            </span>
          </a>
        </div>
      </section>
 
      <!-- Mobile. Stable only, there is no nightly mobile build. -->
      <section class="platform-section platform-section--mobile" data-rise style="--d:420ms">
        <div class="platform-header">
          <svg class="platform-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="7" y="2" width="10" height="20" rx="2.5"/><path d="M11 18.5h2"/></svg>
          <h2 class="platform-name">Mobile</h2>
        </div>
        <div class="download-cards">
          <a class="download-card" href={IOS_APP_STORE_URL} target="_blank" rel="noopener noreferrer">
            <span class="card-text">
              <span class="card-arch">iPhone &amp; iPad</span>
              <span class="card-format">App Store</span>
            </span>
          </a>
          <a class="download-card" href={ANDROID_PLAY_STORE_URL} target="_blank" rel="noopener noreferrer">
            <span class="card-text">
              <span class="card-arch">Android</span>
              <span class="card-format">Google Play</span>
            </span>
          </a>
        </div>
      </section>
 
      <section class="platform-section platform-section--cli" data-rise style="--d:420ms">
        <div class="platform-header">
          <svg class="platform-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 7l5 5-5 5M12 17h7"/></svg>
          <h2 class="platform-name">Terminal</h2>
Read this as: The marketing page resolves expected desktop suffixes against the latest GitHub Release and falls back to the release index on errors.

The AUR workflow begins only after GitHub Release publication. Its release script accepts either a stable semver tag or the prescribed nightly tag pattern; selects t3code-bin or t3code-nightly-bin accordingly; reads the exact AppImage digest from that GitHub Release; reads the LICENSE at the same tag; edits the matching PKGBUILD; validates it with namcap and makepkg; then pushes only if its AUR SSH credential exists. A missing key is intentionally a “validated, not published” end state.

packaging/aur/scripts/release.sh:9–44 ↗verbatim · bash · fc2a09bb
if [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
  pkgname='t3code-bin'
elif [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-nightly\.[0-9]{8}\.[0-9]+$ ]]; then
  pkgname='t3code-nightly-bin'
else
  echo "Release $tag does not publish an AUR package."
  exit 0
fi
 
version="${tag#v}"
pkgver="${version//-/_}"
asset_name="T3-Code-${version}-x86_64.AppImage"
release_json="$(gh api "repos/$repo/releases/tags/$tag")"
asset_digest="$(jq -r --arg name "$asset_name" \
  '.assets[] | select(.name == $name) | .digest' <<<"$release_json")"
appimage_sha256="${asset_digest#sha256:}"
 
if [[ ! "$appimage_sha256" =~ ^[0-9a-f]{64}$ ]]; then
  echo "Release $tag is missing $asset_name or its SHA-256 digest." >&2
  exit 1
fi
 
work_dir="$(mktemp -d)"
trap 'rm -rf -- "$work_dir"' EXIT
gh api -H 'Accept: application/vnd.github.raw' \
  "repos/$repo/contents/LICENSE?ref=$tag" > "$work_dir/LICENSE"
license_sha256="$(sha256sum "$work_dir/LICENSE" | awk '{print $1}')"
 
package_dir="$repo_root/packaging/aur/$pkgname"
cd "$package_dir"
sed -Ei \
  -e "s/^pkgver=.*/pkgver=$pkgver/" \
  -e "s/^pkgrel=.*/pkgrel=$pkgrel/" \
  -e "/# AppImage$/s/'[0-9a-f]{64}'/'$appimage_sha256'/" \
  -e "/# upstream license$/s/'[0-9a-f]{64}'/'$license_sha256'/" \
  PKGBUILD
Read this as: The AUR release script chooses a stable or nightly package from the tag and obtains the exact AppImage digest from that release before changing package metadata.

Work the artifact factory

Choose a delivery lane below and advance it one gate at a time. The desktop lane is useful for testing the crucial distinction: the builder can describe Windows arm64, but the final gate reports that the inspected release matrix does not activate it.

Interactive artifact factory · Chapter 37

Choose a delivery lane, then inspect its gates

Each lane has a different output and failure boundary. Advance manually; no state progresses on its own.

Delivery lane

npm CLI · Gate 1 of 4

  1. 1Build server
  2. 2Embed renderer
  3. 3Assert publish inputs
  4. 4Publish exact version

npm CLI

Build server

The server bundle emits dist/bin.mjs and the service launcher.

Output
t3 package
Failure boundary
Bundled code is not yet a complete product surface.
Lane rule
The published package contains dist only; publishing first proves the exact server version clients will invoke.
Static artifact map
LaneOutputLast gate
npm CLIt3 packagePublish exact version — The release is held if npm publication fails.
Desktop releaseDMG · AppImage · NSISRelease four lanes — Windows arm64 is commented out in the release matrix: capability is not a published artifact.
Hosted + mobileChannel deploy · store build · OTAGate OTA — Native drift skips OTA instead of shipping JavaScript into an incompatible binary.
AUR bridgeArch package metadataValidate then publish — Without the AUR SSH key, validation completes but publication is skipped.

The durable reading rule is: source builds components; packaging assembles a consumer-specific closure; release policy selects and publishes a subset; channel and platform compatibility gates decide what a particular client may receive. Treating those as one step hides the real failure boundaries.

T3
Source-locked editionRead against 859304b78 · 10 Sept 2026
Book search

Find a concept, module, or source path

Type two or more characters.