# Guide: Update a Deployed App

ChainRender apps are immutable — you don't *change* a deployed app, you publish a **new version** and point your
name at it, through the on-chain `SubnameVersionRegistry`. Updates are safe by design: timelocked, observable, and
append-only.

> **These are contract calls.** Versioning lives in the on-chain `SubnameVersionRegistry` (there's no SDK wrapper
> yet — you call it directly with viem/ethers). The examples below show the contract functions; wire them with your
> wallet library of choice.

## Publish a new version

```ts
// via viem (illustrative) — call the versioned registry directly
await wallet.writeContract({
  address: subnameVersionRegistry,
  abi: versionRegistryAbi,
  functionName: "queueVersion",
  args: [
    "myapp",          // your subname label
    newAppHash,       // the new version's component hash (already registered)
    timelockSeconds,  // your name's delay (>= the registry minimum; can only grow)
    releaseNote,      // an optional bytes32 content-addressed release note (0x0 for none)
  ],
});
```

The new version is **queued**, not instant. It goes live only after your name's **timelock** elapses — a public,
observable delay. Until then, the current version keeps resolving (no silent swap), and anyone can see the pending
version via `pending(labelHash)`.

## How a viewer resolves the current version

Your app's consumers resolve the *current* version — a free `view` call:

```ts
const currentHash = await client.readContract({
  address: subnameVersionRegistry, abi: versionRegistryAbi,
  functionName: "resolveCurrent", args: [labelHash],   // labelHash = keccak256(label)
});
// then render currentHash the usual way
```

Resolving through the name (`myapp.chainrender.eth`) will follow the current version only once a wildcard resolver
that reads `resolveCurrent()` is wired to `chainrender.eth` — not yet the case. Call `resolveCurrent` directly as above.

## Pinning (for cautious consumers)

A consumer who doesn't want to auto-follow updates **pins** to a specific version and ignores the pointer entirely:

```ts
const pinned = await client.readContract({
  address: subnameVersionRegistry, abi: versionRegistryAbi,
  functionName: "resolveAt", args: [labelHash, versionIndex],   // any historical version, forever
});
```

Every version ever published stays resolvable at its index — **append-only, immutable lineage.**

## Sealing a name (freeze / renounce)

When your app is stable and you want to guarantee it never changes again, **freeze** the name — no more versions
can be queued, and the current version becomes permanent (Grade C → A for consumers following the name):

```ts
await wallet.writeContract({
  address: subnameVersionRegistry, abi: versionRegistryAbi,
  functionName: "freeze", args: ["myapp"],
});
```

This is the "upgrade-then-renounce" pattern, standardized: mutable while you stabilize, then immutable forever.

## The rules that keep updates safe

- **Only the name's controller** can queue versions (a stranger can't hijack your name).
- **Timelocks can only grow, never shrink** — a name can't weaken its own safety after the fact.
- **The registry is ownerless** — no admin can touch your versions, change the rules, or force an update.
- **The version's hash must exist** (be registered + enforcer-graded) — you can't point at unregistered bytes.

## Cosmetic vs. substantive — don't over-version

Only publish a new version for a **substantive** change (features, text, actions). A **cosmetic** change (color,
spacing, font) is a render-time theme change — free, no version, no redeploy. See
**[Theme Without Redeploying](./theme-without-redeploy.md)**.

## Related
- **[Versioning & Updates](../concepts/versioning.md)** — the model + reasoning.
- **[Registering a Subname](./registering-a-name.md)** · **[Content Addressing](../concepts/content-addressing.md)**
