# Retired forms — deprecated from a version, and how a deployed app keeps rebuilding

> When the platform or a kit retires a spelling, new source that writes it is refused, naming what replaced it. An app that is already deployed keeps rebuilding on new releases for one more minor version, with a warning on its version that names the form and the release that will refuse it. `osy migrate code` rewrites the retired forms in a project.

<!-- id: project-deprecations · area: project · stability: stable · html: https://osysharp.com/reference/project/deprecations/ -->

## Summary        {#summary}
Osy# does not keep an old spelling alive beside a new one: when a form is renamed or removed, source that writes the old
form is refused, and the refusal names what to write instead. That is the right answer for code you are editing — and the
wrong one for an app that is already serving people, which a platform release rebuilds from the source it was deployed
with. So every retired form carries two versions:

| | what it means |
|---|---|
| **deprecated since** | the release that retired it. From here on, compiling SOURCE that writes it is refused. |
| **refused from** | the release from which even a deployed app's rebuild refuses it — by default the next minor. |

Between the two, a release rebuilds a deployed app that still writes the form exactly as before, and records a warning on
the app's version naming the form and its deadline. Before the deadline, deploy the app again from its source, written the
new way — `osy migrate code` rewrites every form that has a mechanical rewrite.

## Signature      {#signature}
```console title="rewriting a project's retired forms"
$ osy migrate code              # rewrite this project's retired forms, then validate again
$ osy migrate code --dry-run    # say what would be rewritten, write nothing
$ osy migrate code --json       # every rewrite, and every form left for you
```
```osy title="a kit retiring a former signature of one of its controls" syntax
// in the kit's package.osy
deprecated "<Control>(<its parameters as they were declared>)" {
  since "<platform version>";        // required
  use   "<what to write instead>";   // required
  why   "<one line>";                // optional
  refusedFrom "<platform version>";  // optional: the next minor after `since` when left out
}
```

## Description    {#description}

### What happens when I compile a retired form?   {#from-source}
`osy validate`, `osy compile` and a deploy refuse it, with the compiler's sentence for that form and the replacement:

```text
model/jobs.osy:3:2  ERROR  `[OwnUnitOfWork]` is now `[CommitsAlone]` — write `[CommitsAlone]` on 'Record'. The meaning
is unchanged: its writes commit by themselves when it returns and survive the caller failing. (`osy migrate code` rewrites it.)
```

The last sentence is there when the rewrite is mechanical. A form whose replacement behaves differently in some case is
refused without one, and the message says what differs.

### What happens to an app that is already deployed?   {#rebuild}
A platform release rebuilds every deployed app from the source its version recorded. When that source writes a retired
form and the release is before the form's **refused from**, the rebuild rewrites the form in memory, compiles, and serves
the app as it was. The version keeps the source it was deployed with, and records the form on its history:

```text
model/jobs.osy:3 uses [OwnUnitOfWork], refused from 0.17.0 — write [CommitsAlone]
```

The same line is logged as a warning by the release, and listed by the operator's check of every deployed app
(`osycloud platform compile-recorded`), so the apps still using a retired form are known before the release that refuses
them.

### When does a deployed app stop rebuilding?   {#window}
From **refused from** on, the rebuild refuses the form like any other source, and says why:

```text
model/jobs.osy:3  ERROR  DEPRECATED_FORM  this recorded version uses [OwnUnitOfWork], which a rebuild accepted until 0.17.0
(this platform is 0.17.0) — deploy it again from its source, written [CommitsAlone].
```

Deploying the app again from its project, written the new way, is the whole fix: the new version records the new source.

### How do I rewrite my project?   {#migrate}
Run `osy migrate code` in the project. It validates the project, takes the rewrite each refusal of a retired form carries,
writes it into the file, and validates again — a second round can find forms the first could not see (a call on what a
retired factory returned is refused only once the factory is rewritten). It stops when nothing more can be rewritten, and
lists every retired form it left in place with what to write by hand.

Only the project's own files are written. A retired form inside a kit the project uses is the kit's to retire: the
command names it, and a newer version of the kit (`osy update`) is how the fix arrives.

After it, the project compiles as the current language writes it:

```osy title="each retired form, written as it is now" test app=project-deprecations
entity Reading {
  [MaxLength(100)] string Text = "";
  security { allow read, create when IsAuthenticated; }
}

[CommitsAlone] void Record(string t) { new Reading { Text = t }; }

TimeSpan OffsetNow() { return TimeZoneInfo.FindSystemTimeZoneById("UTC").GetUtcOffset(DateTime.UtcNow); }

DateTime WallClock(DateTime instant) { return TimeZoneInfo.ConvertTimeFromUtc(instant, TimeZoneInfo.FindSystemTimeZoneById("UTC")); }

int ZonesOnOffer() { return TimeZoneInfo.GetSystemTimeZones().ToArray().Length; }
```

### Which forms are retired?   {#retired}
| retired form | write instead | deprecated since | refused from | rewrite |
|---|---|---|---|---|
| `[OwnUnitOfWork]` | `[CommitsAlone]` | 0.16.0 | 0.17.0 | yes |
| `Zone` (a type) | `TimeZoneInfo` | 0.16.0 | 0.17.0 | yes |
| `Zone.Of(id)`, `Zone(id)` | `TimeZoneInfo.FindSystemTimeZoneById(id)` | 0.16.0 | 0.17.0 | yes |
| `Zone.Declared` | `TimeZoneInfo.GetSystemTimeZones()` (as an array: `.ToArray()`) | 0.16.0 | 0.17.0 | yes |
| `instant.InZone(zone)` | `TimeZoneInfo.ConvertTimeFromUtc(instant, zone)` | 0.16.0 | 0.17.0 | yes |
| `zone.OffsetAt(instant)` | `zone.GetUtcOffset(instant)` | 0.16.0 | 0.17.0 | yes |
| `zone.IsDst(instant)` | `zone.IsDaylightSavingTime(instant)` | 0.16.0 | 0.17.0 | yes |
| `zone.Resolve(date, time)` | `TimeZoneInfo.ConvertTimeToUtc(date.ToDateTime(time), zone)` | 0.16.0 | 0.17.0 | yes — kept exact: C# throws on a time inside a spring-forward gap, where `Resolve` read it forward, so the rewrite asks `zone.IsInvalidTime` first and reads it forward by the gap's width. If your times never fall in a gap, the plain C# call is the same |
| `ProgressBar(int percent = 0, Tone tone = Tone.Primary, string label = "")` (Osysharp.Ui) | `ProgressBar(label, percent, tone)` | 0.16.0 | 0.17.0 | yes |
| `Facts(string label = "")` (Osysharp.Ui) | `Facts(label)` | 0.16.0 | 0.17.0 | yes |
| `ZonePicker(string label, Binding<Zone> value, bool disabled = false)` (Osysharp.Ui) | `TimeZonePicker(label, value, disabled)` | 0.16.2 | 0.18.0 | yes — the name |
| `ZoneWords(Zone zone)` (Osysharp.Ui) | `TimeZoneWords(zone)` | 0.16.2 | 0.18.0 | yes — the name |

### What does the ProgressBar rewrite write?   {#control-rewrite}
A call that binds a control only as its kit declared it before is rewritten with named arguments, onto the control as it
is now. A parameter the control now requires, and the former signature gave a default, takes that default — so a rebuilt
app renders exactly what it rendered:

```text
ProgressBar(72, Tone.Success)    →    ProgressBar(label: "", percent: 72, tone: Tone.Success)
```

`osy migrate code` says when it wrote a former default. Replace `label: ""` with what the bar measures: a progress bar
with no name is announced by a screen reader as a percentage of nothing.

### How does a kit retire part of its own surface?   {#kit-deprecations}
A kit declares the former signature of a control in its own `package.osy`, one `deprecated` entry per retired form:

```osy syntax
package Acme.Gauges {
  version     "2.0.0";
  minPlatform "0.16.1";
  model "*.osy";

  deprecated "Gauge(int value = 0, string caption = \"\")" {
    since "0.16.1";
    use   "Gauge(caption, value)";
    why   "a gauge says what it measures, so `caption` is required and first";
  }
}
```

The value is the control's name and parameter list exactly as they were declared. A call that does not bind the control as
it is now, and does bind the former signature, is refused as the retired form — with its rewrite when every former
parameter still exists and every newly required one had a default. `since` is the platform release the kit's change ships
with, and the window runs from there to `refusedFrom` like the platform's own.

### How does a kit rename a control or a function?   {#kit-renames}
The same entry, with the new name in `use`. It works for a component and for a public function:

```osy syntax
deprecated "Dial(int value)" {
  since "0.17.2";
  use   "Gauge(value)";
  why   "every control in the kit is named after what it shows";
}
```

A call to the former name is refused as the retired form, and its rewrite replaces the name and keeps the arguments as
written. A rebuild applies it, and so does `osy migrate code`.

### Does retiring a form need a major version of the kit?   {#kit-versions}
No. A form with a `deprecated` entry is still offered: an app that wrote it keeps rebuilding, and `osy migrate code`
rewrites its source. So `osy publish` and `osy package pack --since` don't count it as removed, and the kit's next version
can be a minor. Without an entry, the form is removed, and the kit has to take a new major version. Deleting an entry later
removes the form, so that release needs a major too.

## See also      {#see-also}
- [Moving runs onto the version you just deployed](https://osysharp.com/reference/project/migrating-runs/) — `osy migrate` without `code` moves workflow runs, not source
- [[CommitsAlone] — a function whose writes land by themselves](https://osysharp.com/reference/function/commits-alone/) — `[CommitsAlone]`, the attribute `[OwnUnitOfWork]` became
- [Time zones — TimeZoneInfo](https://osysharp.com/reference/stdlib/zones/) — `TimeZoneInfo`, the type `Zone` became
- [package.osy](https://osysharp.com/reference/project/package/) — the rest of `package.osy`
- [minPlatform — the oldest platform an app runs on](https://osysharp.com/reference/project/min-platform/) — the oldest platform an app runs on
- [Deploying while workflows are running](https://osysharp.com/reference/project/app-versions/) — what a deployed version is
