# Fix list - archive

**This list is closed. The live one is `docs/guides/Fix List.md`.**

Two lists ran alongside each other and disagreed in places, which is worse
than having one. On 22 August the three items still open here were moved to
the live list, and this became what it had mostly already become: the record
of what was fixed up to 2026-08-18 and how well each claim was supported at
the time.

Nothing here is open, and nothing should be added to it. It is kept because
every entry below is a defect that actually happened, together with the
reasoning that found it.

Last reconciled against the source: 2026-08-18, build 2026.08.18.128.

## How to read the status of an item

Three different things have been called "fixed" in this project, and conflating
them has cost real test cycles. They are kept apart here.

| Status | What it means |
| --- | --- |
| **Confirmed** | The developer has seen it working in the running application. |
| **Verified by test** | An automated check proves it, and the check would fail without the fix. Not yet seen on screen. |
| **Believed fixed** | The code changed and it compiles, but nothing proves it. Treat with suspicion. |
| **Open** | Not started, or attempted and withdrawn. |

Everything below marked *Verified by test* is verified against the **plan** the
analyser produces. The runtime is verified separately and thinly: a live form
is built and a pack applied to it, but only the `Width` property is asserted,
so six of the seven layout properties reach the screen unchecked. That is open
item 7, and it is why several items marked fixed in earlier rounds came back.
Until item 7 is widened, *Verified by test* on a layout item means less than it
sounds.

---

## What was fixed, and how well each claim is supported

The three items that were still open here moved to the live list on
22 August. Everything below is closed.

### 1. The pilot executable deployed twice to the outboard drive
**Fixed:** 2026-08-17, build .111, commit `ea14c91`.

Two steps copied the executable: the build step, and final processing after it.
Final processing now deploys language packs only and reports what the build
step did; the build step is the sole copier and labels its log line. The
hand-picked folder button still copies, because clicking it is the request.

The deeper fault was that a copy made outside the build step can only be of
whatever was built last time — the route by which a stale executable reaches
the drive looking fresh. Where no build has run, the log now says so.

**Behaviour change to watch:** a run with "build now" unticked no longer
deploys an executable at all. That is deliberate, and it is logged.

### 2. Overwrite authorisation control sat too low to be seen
**Fixed:** 2026-08-17, build .111, commit `ea14c91`.

Moved from Y=422 to Y=292 on the deployment page, directly beneath the
destination list it governs, with the buttons and summary below it. Geometry in
the `.fmx`, so the page stays editable in the IDE.

### 2b. Completion page: the progress log had no room
**Fixed:** 2026-08-17, build .111, commit `ea14c91`.

The command box and the four buttons moved to the foot of the page; the log
took the reclaimed height, 130 pixels instead of 76.

### 3. Build output folders outside the project tree were rejected
**Fixed:** 2026-08-17, build .111, commit `ea14c91`.

`FindBuildOutputDirectory` required every candidate to sit inside the project
tree. That test belongs to the folders the tool guesses at; a folder the
project file names is the project's own answer to where it builds, and a
project may legitimately build to another drive. Rejecting it fell back to a
stale in-tree copy — failure disguised as success. Declared paths are trusted
now, guessed ones still are not.

### 4. No space between a grid's bottom edge and the controls beneath it
**Fixed:** 2026-08-17, build .109.

Marked fixed once before on the strength of a different form, which was wrong.
Checked against the Groups and Playlist forms this time.

### 5. A caption above its field grew down into it
**Fixed:** 2026-08-17, build .111, commit `a075157`.

Attempted three times before and withdrawn each time, always by trying to grow
the caption *upwards*, which moved captions into a panel on `Form1` and into
the grid on `Groups`. The lever was wrong. Holding a caption level with the
field it labels removes the need to grow it at all: `lblTestRecipient` takes
the empty margin on its left and keeps its top, its height and its text size.

### 6. Text-bearing components the scanner did not read (Tier 1)
**Fixed:** 2026-08-17, build .112, commit `b53716b`.
**Proved by:** `contracts/formscan`, `tools/run_form_scan_contracts.ps1`.

Every Tier 1 class from the survey is now read on both frameworks: `TText`,
`TPanel.Caption`, `TCornerButton`, `TExpander`, designer-authored list and tree
items, base column classes, `TToolButton`, `TStatusPanel`, `TListColumn`,
`THeaderSection`, `TListGroup`, `TCoolBand`, `TTaskDialog`, `Tabs.Strings`, and
file-dialog `Title`.

The larger find was structural. DFM collections were not understood at all, so
the `end` closing a collection item closed the component that *owned* the
collection; every property after the first collection on a form was
misattributed, and once the stack unwound past the form the rest of the file
was skipped in silence. Any VCL form with a status bar or list view near the
top was quietly losing text below it.

Remaining scope moved to item 6b above.

### FireMonkey labels wrap unless told not to, and we assumed the opposite
**Fixed:** 2026-08-17, build .114.

A property absent from a form file is not a property set to False: it is the
framework's default, and the two frameworks disagree. A FireMonkey label wraps
unless told otherwise; a VCL one does not. The analyser read the absence as
False, so every FireMonkey caption was believed not to wrap.

The consequence was silent. A plan that kept a caption on one line matched what
we thought the form already did, so no rule was written - there was nothing to
change - and the label wrapped at run time regardless, into whatever height it
already had. The hero banner is the clearest case: planned as one line at 27.2
points, drawn as two inside a box built for one, losing the top and bottom of
both. The same fault explains a navigator caption sitting higher than its
neighbours: "Cancelar" is the only word in that row long enough to reach the
edge of its 63-pixel box, and two lines centred in eighteen pixels ride up.

The default is now taken from the file extension, and switching wrapping off is
stated explicitly rather than left to be inferred.

### Bold text was measured as though it were regular
**Fixed:** 2026-08-17, build .114.

Every measurement went through a layout with the point size set and the weight
left alone, so a bold caption was measured light and reported as fitting a box
it overruns. FireMonkey writes the weight as a binary record rather than a
readable set; the second byte carries it on the TFontWeight scale, where
Regular is four and Bold is seven. The VCL writes a readable set. Both are read
now, and semi-bold and heavier are measured as bold.

### The glossary fix was written where the Wizard overwrites it
**Fixed:** 2026-08-17, build .114.

The shortened email caption was put into the workspace glossary under AppData
and lost on the next run, because the Wizard prefers a staged glossary in
`export/localization-review/<project>/<language>/project-glossary.json` and
saves that over the workspace copy. The staged file is the durable one, and it
is in the repository where it belongs.

The term also named a semantic concept. A term that names one only matches an
entry carrying the same concept, and this entry carries none, so it would never
have matched even had it survived. Left blank now.

### The grid was being translated, and frames grew without saying so
**Fixed:** 2026-08-18, build .128.
**Raised:** 2026-08-18 by the developer: the playlist grid in Spanish, the
scheduling check box clipped for the fifth time, and the note on the email page
hanging out of its panel.

**Grid cells are the application's data and are left alone now.** The runtime
walked every cell of every string grid and translated anything it recognised.
Song titles came back in Spanish, and a row reading
`--Random Music Generator-1--Do Not Delete--` - a marker the application
matches on by name - was rewritten. Worse, the substitution has no reliable
inverse, so choosing English again did not bring the titles back: the developer
was left with Spanish rows in an English application and no way to undo them.
A column heading is interface text and is still translated. What sits beneath
it belongs to whoever entered it.

**A frame that grows now says so.** Position changes were emitted for any
control the planner moved, but size changes only for controls with translated
text. A frame enlarged to hold its contents carries no text of its own, so its
growth was planned and never written down. The scheduling panel is the plain
case: the check box was given the fifty-six pixels its two lines need and the
panel kept the thirty-three it was drawn with, so the words were clipped by a
container that had room to spare everywhere except in the pack. That is why
this one came back five times - each fix corrected the plan, and the plan was
never the problem. The note on the email page is the same fault: its panel now
grows from 34 to 52.

**And the contracts can see the pack at last.** They asserted the plan only,
which is how a correct plan that reaches nobody passed twice - once for
wrapping, once for this. A contract may now require that a property actually
appears among the emitted proposals, and contract 19 does exactly that for a
frame and the control inside it. With the emission removed it reports that the
plan may be right and the pack still silent.

### The runtime harness reaches past a single label
**Fixed:** 2026-08-18, build .127. Narrows item 7b to 7c.

A check box and a grid column were added to the sample form and are asserted
through the same seven layout properties as the label: the caption translated,
a width applied, wrapping applied, a column heading translated and its width
applied. Neither had ever been exercised, and they fail differently from a
label - a check box has a tick box taking room beside its text, and a column is
reached through the grid that owns it rather than as a control in its own
right.

### The last step says what it does now
**Fixed:** 2026-08-18, build .126. Closes item 11.

The completion page asked "Build the application now?" and offered four
combinations behind a button reading "Build and Deploy Selected Targets". Its
one job that nothing else does is copying the built application to the folders
entered in Step 3; the building half repeated what final processing had done
seconds earlier, and a destination folder can hold one executable however many
were offered.

It reads as the deployment step it is. The title asks whether to deploy, the
text says plainly that compiling has already happened and that this is the only
thing that reaches the folders, and the button says where the application is
going. Building became a tick, off by default, for the one case final
processing cannot cover: a configuration that has never been built and so has
no folder for it to find.

The two lists offer one platform and one configuration rather than four
combinations, defaulting to Win32 and Release - the build a user of the
application would be given. The log names both. The platform matters as much as
the configuration and is the easier of the two to get wrong in silence: a
folder set up for a thirty-two bit application carries thirty-two bit libraries
beside it, and a sixty-four bit executable dropped in cannot load them.

### Tier 2 coverage, the unfinished button, and the VCL round trip
**Fixed:** 2026-08-18, build .125. Closes items 6b and 9, and the VCL half of
the round-trip work.

**Tier 2 collections are read.** `TListView.Items`, `TTreeView.Items`,
`TButtonGroup.Items` and `TImageCollection.Items` are DFM collections, so they
only became reachable once collections were walked properly. `Items` means a
different thing to each owner, so the owner decides which: a list row, a tree
node, a button, an image description. A fixture covers a list view with rows
and columns, a status bar and a label after both, so the walking is proved as
well as the reading.

`TDBGrid` column titles remain **unverified**, as before. `Vcl.DBGrids` is not
shipped as source, the rule was written from the documentation, and only a real
data-aware grid can confirm it. That waits on item 8.

**The unfinished button is gone.** `btnFinishLocalizationReview` sat hidden and
disabled on the completion page, wired to reopen Localization Review, and
nothing ever showed or enabled it. Review is reopened from code when final
processing reaches that point, which is the path the developer actually
travels, so the button offered nobody anything. Removed, with the assertion
that guarded it.

**The VCL runtime has the same snapshot the FireMonkey one got.** Position,
size, font size and colour, automatic sizing and wrapping are taken before the
first language is applied and restored on the way back, so a control the
analyser never wrote a rule for is still returned to what it was. That side
carries no fitting heuristics and so was less exposed, but the gap was the same
gap. Its round trip is tested the same way, knocking a control the pack does
not mention out of shape first; without the snapshot restore the test fails on
that control.

### The runtime suite runs again, and had three wrong answers waiting
**Fixed:** 2026-08-18, build .124. Closes item 10.

`FoundationSmokeTests` still used units removed in `9b0b9e2`, so the runner
stopped there and never reached the runtime tests below it. Six routines
depended on that removed target-editing feature and went with it, along with
the runner phase that built four generated sample projects and read their
window titles - all of it working on kits those same tests produced.

The suite then ran for the first time in a long while, and three of its
expectations were wrong. Two described decisions that had since been reversed
deliberately: `Items.Add` and canvas drawing calls are no longer harvested,
because in real applications they carry data rows, log lines and generated
markup far more often than captions. Both now assert the opposite, so the
decision is written down rather than merely implemented. The third still
expected the workspace to live inside the project under a `Localization`
folder; it moved beside the user's other application data, so that opening a
project no longer writes into its tree.

A negative assertion was added for the first two. A scanner that claims too
much is the harder fault to see - the extra strings look like work rather than
like a mistake, and they reach the translator and are paid for.

**And one real regression of mine, which is the point of having the suite.**
Tightening the literal joiner earlier today broke `ShowMessage` capture: the
argument extractor never stopped at the call's closing bracket, so it returned
`'text');` rather than `'text'`, and a careful reader refuses that. The loose
joiner had been hiding it by ignoring trailing characters. The extractor stops
where the argument stops now. The markup scanner keeps the loose reading, which
is right for it alone: markup really is built in pieces, and taking only what
sits between tags cannot mistake code for a caption, because code carries no
tags.

Both suites now pass on Win32 and Win64.

### An apostrophe in a comment silenced the rest of the file
**Fixed:** 2026-08-18, build .123. Closes item 14, found the same day.
**Proved by:** `contracts/pascalscan`.

The statement collector walked the source line by line with no notion of
comments. An apostrophe inside one - `the first VCL pilot's log`, `don't`, `the
developer's choice` - opens a string literal as far as a naive reader is
concerned, and it never closes, so every statement after that point in the file
was swallowed into one unterminated literal and nothing further was scanned.

Nothing announced it. The unit yielded fewer strings than it held, and which
ones depended on where the apostrophe fell. It was found only because the
fixture written to test something else had an apostrophe in its own comment and
reported nothing at all.

Comments are now understood: `//` to end of line, braces with their nesting
carried across lines, and the older parenthesis-star form. Quotes and comments
are tracked together, since each decides the other - a brace inside a string
opens no comment, and a quote inside a comment opens no string. A literal still
open at the end of a line yields nothing rather than running on into the rest of
the file, so a construct this scanner does not understand costs one line
instead of a unit.

Also removed here: text was being claimed twice, once by the pass that reads
returned values and once by the assignment pass, so the same words reached the
review under two keys. The first pass exists for statements that return more
than one value, and now declines where there is only one.

### The scanner was inventing strings, and discarding real ones
**Fixed:** 2026-08-18, build .122. Closes item 12.
**Proved by:** `contracts/pascalscan`, `tools/run_pascal_scan_contracts.ps1`.

Four faults, all in how literals were judged, and they compounded.

**Literals were glued across whatever sat between them.** `ExtractLiteralPhrase`
picked every quoted run out of an expression and concatenated them:

```pascal
Result := 'logs' + PathDelim + 'PlayLog.txt';
```

became `logsPlayLog.txt` - a file name with its separator missing,
which is neither a path nor a caption and so passes the very guard written to
reject both. The first VCL pilot's play log was translated on the strength of it, and the
application reads that file by name. At scale the same fault swallowed 4,874
characters of Pascal, every literal in a long run of code glued end to end, and
sent it to the translator, which rendered `begin`, `end` and `then` into
Spanish and put the result in the shipped pack. Literals are now joined only
when the expression is literals and the addition operator and nothing else.

**Fragments of an assembled string were claimed as captions.** With the gluing
stopped, the first literal alone was taken instead - `logs` from the path above,
`Total: ` from `'Total: ' + IntToStr(N) + ' items'`. Where a statement builds
its value from more than one term, the words a person sees are the assembled
whole and no piece of it is a caption. Nothing is claimed from such a statement
now.

**The guard was never consulted on the joining branch.** The branch that
decodes a plain literal asks whether the text reads as something a person would
see; the branch that joined literals did not ask at all. It does now.

**And the file-name test was rejecting ordinary sentences.** Written as a count
of characters after the final dot, it rejected anything ending in a full stop,
because nothing is fewer than four characters. Every explanatory note written
as a proper sentence was silently discarded - invisibly, since the words simply
never reached the catalogue. A file name is now identified as what it is: no
whitespace, and a short run of letters or digits after its last dot. The
length limit was widened to suit at the same time, because a short cap is right
for a token and wrong for prose.

Across the whole of the first VCL pilot the scanner now claims no file names and no
oversized captures.

**Found while writing the fixture, and worth knowing:** the statement collector
does not skip comments, so an apostrophe inside one - `the user's` - opens a
string literal that never closes and silently swallows the rest of the file.
The fixture avoids apostrophes for that reason. It is recorded as item 14.

### Returning to the original language gave back the words but not the form
**Fixed:** 2026-08-18, build .121. **FireMonkey only.**
**Raised:** 2026-08-18 by the developer: "the original settings before another
language is engaged are not saved, and if you go to a language and come back,
things are out of whack".

One property was remembered - the position - and only for controls the parent
did not place. Everything else a translation writes was written and never
recorded: size, text size, colour, wrapping, automatic sizing.

Restoring worked by re-applying each pack rule's original value, which reaches
only the controls the analyser wrote a rule for, and only the properties named
in those rules. That would nearly serve if nothing else moved anything, but the
run-time fitting resizes controls the analyser never mentioned. Those changes
had no rule to undo them and no record to restore from. They were unreachable:
the words came back and the geometry stayed wherever the last language left it.

A control now has its whole state taken before the first language is applied -
position, size, text size and colour, wrapping, trimming, styled settings and
automatic sizing - and that snapshot is what a return to the source language
restores from. It covers every control rather than the ruled ones, it holds the
form as it actually was rather than as the scan recorded it, and it is applied
after the text, because a control that sizes itself resizes when its text
changes. The order within it mirrors the applying order for the same reason:
automatic sizing off first, put back last, or a width will not stick.

The snapshot is taken once and guarded, so a second language cannot overwrite
it and quietly make a translation the original.

Proved by the round trip in `FMXRuntimeSmokeTests`, which deliberately knocks a
control the pack says nothing about out of shape before restoring, standing in
for the fitting. Without the snapshot restore the test fails on the first
width.

**VCL is not affected**: that runtime carries no fitting heuristics, but it also
has no snapshot, so its restore is still limited to what the rules cover. Worth
doing there too when a VCL application is put through the pipeline (item 8).

### A frame was enlarged to hold its contents, then its contents were pulled back
**Fixed:** 2026-08-18, build .120.
**Raised:** 2026-08-18, "the habilitar programacion container needs to be
enlarged in height to fit the text".

Phase 3a enlarges a frame so that its contents fit. Phase 3b then holds every
control inside its frame, and measured against `ClampContainer.Width` and
`.Height` - the size the frame was *drawn* at, not the size it had just been
given. The second pass undid the first in the same breath.

The scheduling check box is the case. Its panel was grown from 33 to 64 to hold
two lines of Spanish; the check box was then clamped back to 25, because that
is what fitted the panel before it grew. The second line was cut off inside a
panel with room to spare, and the pack shipped no height rule for the check box
at all, so nothing at run time could have rescued it.

Traced by instrumenting the phases rather than reasoning about them:

```
after phase 2   h=50   after 2a  h=56   after 3a  h=56   after 3b  h=25
```

The clamp now measures against the larger of the frame's planned and designed
size. The check box comes out 144 by 56 at its full designed text size, inside
a panel of 64.

### The runtime carries a second layout engine, and it was overruling the plan
**Fixed:** 2026-08-18, build .119.
**Severity:** this is the reason a corrected plan so often changed nothing on
screen, or made things worse.

`DAT.Runtime.FMX.pas` has its own fitting heuristics - `FitLabel`, `FitButton`,
`FitCheckBox` - which measure the translated text afresh at run time and resize
the control to suit. They are meant to stand aside wherever the analyser has
already decided something, and `HasExplicitLayoutRule` is the guard that
arranges it.

That guard counted four properties: `Width`, `Height`, `Position.X`,
`Position.Y`. The runtime applies nine. So a control the analyser had spoken
about in any other terms - wrapping, a text size - looked untouched to the
guard, and the heuristics resized it against the plan.

Two of the developer's reports were this, and nothing else:

- The test-recipient caption was shipped `WordWrap = False` and nothing more,
  because at its designed width the shortened text fits on one line. `FitLabel`
  clamps a label to `NearestSameRowRightEdgeLimit`, and the edit box overlapping
  its row starts twenty-seven pixels away, so the caption was squeezed to the
  minimum label width and wrapped into a column one word wide: "Email / del /
  destin / atario:".
- `BtnRecalcEaster` was shipped a smaller `FontSize` and nothing more.
  `FitButton` widened it instead, until it covered the caption beside it.

The guard now consults the same list the applying pass uses, held in one place
so the two cannot drift apart again.

**Not yet covered by a test.** A fixture reproducing the fitting's intervention
needs a particular geometry - a neighbour close enough on the row to clamp
against - and the sample form has not got one. The evidence for this fix is the
measurement above, not a failing test, so it is worth re-reading if these
symptoms return.

### A button row drawn centred stays centred
**Fixed:** 2026-08-18, build .119.
**Raised:** 2026-08-18, "buttons not centered in multimedia box".

The media controls were drawn with thirty-eight pixels to the left of the row
and thirty-five to the right of it, which is centred in a group box 361 wide.
Once the buttons grew for the longer Spanish captions the row was held by its
left edge and allowed to extend rightwards, ending with thirty-eight on the
left and five on the right: a row that reads as having slipped rather than one
that was placed. A row drawn with equal margins is centred again after packing.

### Two buttons were carried across the page into the left margin
**Fixed:** 2026-08-18, build .118.
**Proved by:** contract 18, `button_keeps_its_place_when_text_grows`.
**Raised:** 2026-08-18 by the developer, on the liturgical silence page, after
three rounds of my looking at the wrong thing in the picture.

The misalignment was not in the captions at all. Measured against the design:

```
BtnRecalcEaster     drawn 134,413  right edge 263   planned  8,413  right edge 137
btnClearOtherDates  drawn 156,510  right edge 263   planned 14,510  right edge 121
```

Two buttons drawn as a pair against a shared right edge of 263, carried 126 and
142 pixels to the left margin, arriving ragged against each other and attached
to nothing, while the space they left behind went unused.

The cause is a Phase 2 treatment written for captions: a caption pinned against
the field it names has no room on its right, and forms usually leave a margin
on the far left that nothing occupies, so it slides back into that margin to
find its width. Sound for a caption. A button names nothing to its right, so
sliding it towards a field moves it away from whatever it was drawn against and
buys nothing at all. The treatment is now captions only.

One other thing turned up while looking, and is fixed with it. The guard meant
to stop a control drifting too far read

```pascal
RequiredLeft - Follower.Left <= MaximumDrift
```

which is a subtraction, not a distance. Pushed to the right it restrains;
pushed to the left the difference is negative and the test passes however far
the control travels. It is a magnitude now. That guard did not cause this - the
slide above did - but it is why nothing caught it on the way past.

### A caption above its box was pushed away from it
**Fixed:** 2026-08-18, build .117.
**Proved by:** contract 17, `caption_above_field_takes_its_column`.
**Raised:** 2026-08-18 by the developer, on the email settings page: "it needs
to be wrapped and aligned with the left edge of the box".

There are two ways a caption can sit against its field and they want opposite
treatment. Beside the field, the caption keeps its right edge and takes the
empty margin to its left, because the right edge is the one the reader follows
across to the box. Above the field, the pairing is read down the column, and
the caption and the box line up on the left.

Only the first was implemented, so a caption written above its box was widened
leftwards when the translation outgrew it, walking it off the box it names and
out over whatever lay to the left. On the email page that produced a line of
words beginning well left of the field and running past it.

A caption directly above a field now takes that field's left edge and width and
wraps inside them, with the height taken upwards so its bottom stays against
the box. It is bounded on both sides: never onto the field, and never up into
whatever sits above in the same column.

Where the band between the two is too small to hold the lines at a modest size,
the treatment is declined and the caption is left as the earlier passes placed
it. That is the case on the email page itself with the full machine wording:
the button above leaves about thirty pixels, and two lines will not fit them
without dropping to two thirds of the designed size. A caption reaching that
point is saying the text is too long for the space the form allows, which is a
question for the wording. The shortened glossary term settles that one.

### A hard line break counted for nothing
**Fixed:** 2026-08-18, build .116.
**Raised:** 2026-08-17 by the developer as "image 3, text not aligned", on the
liturgical silence page.

The silencing note beside its check box is two sentences with a line break
between them, written into the form file as one string carrying `#13#10`. Both
measurements treated it as a single run of text, so the analyser asked how wide
that run was and divided by the control's width to count lines. That answers a
question the control never asks. A line the author wrote cannot share a line
with the next one, however much room is left over.

The note was planned at two lines and drawn at four, which is why it appeared
with its head and feet cut off: 240 by 47 pixels where it needed 240 by 90.

Both measurements now split on the breaks the author wrote. Width is the widest
authored line rather than all of them end to end, which would report a caption
needing the room of a paragraph. Line count is the sum over the pieces, each
wrapping on its own.

Worth noting for anything similar: `lblLogNote` on the same page reads as two
sentences but has no break in it, only a space, so it wraps as one run and its
three-line plan was right all along. The two look identical on screen and are
not the same thing at all.

### Deployment sent the debug build to the drive
**Fixed:** 2026-08-18, build .115.
**Raised:** 2026-08-17 by the developer, from executable sizes on the drive
varying between roughly 28 and 55 million bytes between runs.

The varying size was never corruption. It was different build configurations
reaching the same folder:

```
Win32 Debug     58,224,025
Win32 Release   21,430,272
Win64 Debug     67,637,817
Win64 Release   29,532,672
```

The executable on the drive matched Win32 Debug to the byte, and the remembered
28 million is Win64 Release. Before the deploy-once fix the copy sat inside the
platform and configuration loop, so the last combination built won, which is
Win64 Release. After it, the first won, which is Win32 Debug. Neither was a
choice anybody made, and the size moved because the rule moved.

Release is now preferred wherever it was built, on the build page and on the
hand-picked folder button alike. What reaches the drive should be what a user
of the application would be given: a debug build is about two and a half times
the size, carries a symbol table naming every routine and variable, links the
unoptimised runtime, and turns range and overflow checking on, so it can raise
where a release build carries on.

The platform is still the first selected rather than chosen, and the log now
states it. That matters more than it looks: the developer's drive carries
`libeay32.dll` and `ssleay32.dll`, which are thirty-two bit, so a sixty-four
bit executable deployed there could not load them and anything using TLS would
fail. Any run that ended on Win64 Release put exactly that on the drive.
Choosing the platform deliberately, or refusing to change the architecture of
an existing deployment without saying so, is left as part of item 11.

### Labels never received their text size at run time
**Fixed:** 2026-08-17, build .113.
**Found by:** widening the runtime harness under item 7.

The FireMonkey runtime reached wrapping, text size and font colour through
`AComponent is TTextControl`. There are two families of text control in
FireMonkey and a `TLabel` belongs to the other one: it descends from
`TPresentedTextControl` and is not a `TTextControl` at all. The compiler
refuses to compare those two types directly; written against a `TComponent` the
test compiles and is quietly False for every label on every form.

So for labels, which are most of the text on any form, the runtime read each
font-size rule out of the pack and did nothing with it. The analyser would
decide a caption needed to be a point smaller, write the rule, the runtime
would load it, and the size on screen would not change. That is why font-size
corrections were reported as not applied round after round while the plan was
correct every time.

Both families implement `ITextSettings`, so the runtime asks for the interface
now. On the sample form this took the applied-property count from 24 to 26 and
the restore count from 6 to 8: two rules per control that had been discarded in
silence.

### Wizard pages were laid out against a height they do not have
**Fixed:** 2026-08-17, build .113.

A page's real height is settled at run time, because the tab control fills its
card, and it is nothing like the figure stored in the form file. Pages were
laid out against the stored figure, so content ran past the bottom edge and was
clipped. Both the deployment page and the completion page were over.

`StudioFormSmokeTests` now measures the tab control that is actually there and
fails any page whose content reaches past it, in absolute coordinates, with a
count check so that a walk finding nothing cannot pass for a walk finding no
faults. Verified by reintroducing the overflow and watching it fail.

### The executable was still deployed more than once
**Fixed:** 2026-08-17, build .113.

The earlier fix stopped final processing copying the executable, but the copy
inside the build loop remained, and that loop runs once per selected platform
and configuration. With both platforms selected the same destination was
written twice; with both platforms and both configurations, four times. Each
copy overwrote the last, so what ended up on the drive was whichever
combination finished last rather than anything anyone chose.

Every combination is built first and one is deployed afterwards. The log names
which, and says plainly that the others stayed in their build-output folders.

### Screen defects reported 2026-08-17 (third round)
**Fixed:** build .113.

1. **Completion page buttons partially hidden.** The page-height fault above.
2. **Executable still deployed twice.** The build-loop fault above.
3. **A date caption still lost its first word.** It cannot wrap, because the
   next control sits directly beneath it, and could not grow, so it overran a
   one-line box and, being right-aligned, lost its beginning rather than its
   end. Captions that cannot wrap are now sized against the width actually
   planned, taking any free margin beside them first, with a floor so this
   cannot make text illegible: applied without that floor it reduced a check
   box to sixty per cent of its designed size.
4. **The email caption was too long.** Shortened in the glossary to "Email del
   destinatario:", which needs no layout change at all and sits level with the
   field as drawn.
5. **The folder note was still cut through the middle.** It is prose with
   ninety pixels of empty space below it, and it was being shrunk to force it
   onto one line instead of being allowed to wrap. A size chosen so the words
   only just reach the edge depends on the measurement being exact; where it is
   a shade optimistic the text wraps anyway, into a height fixed for one line.
   Paragraphs with room below now wrap at their designed size.

### Screen defects reported 2026-08-17 (second round)
**Fixed:** build .110, commit `b7cbb40`.

1. **Media button row: one button larger than the others.** The row shared a
   width but not a height or text size, so the longest caption alone wrapped
   and stood twice as tall. Two causes: the fitting size was solved by scaling
   the whole measured width, but padding does not shrink with point size, so
   the text stayed a fraction too wide and wrapped anyway; and wrapping was
   decided per button rather than for the row.
2. **Stacked buttons of different widths.** Vertical stacks were never
   collected as sets at all — only horizontal rows were.
3. **Navigator captions not aligned.** The Silence Schedule caption row drifted
   right of the buttons it names, cumulatively, worst at the last caption.
   Underneath was a false statement in the code: `LinesFittingBelow` measured
   the room below a control and then discarded its own answer, reporting at
   least two lines however little room it found.
4. **Settings captions truncated from the left.** Pinned `AutoSize` controls
   were given no height, so they kept the single line the form was drawn for.
   A right-aligned caption loses its *beginning* that way.
5. **Paragraph did not fit.** Same root cause as 4.

### Screen defects reported 2026-08-17 (first round)
**Fixed:** builds .105 to .109, commits `5753746`, `5e31620`.

Text overflowing its container; grid-bottom spacing; over-wrapped long text;
glossary term for "Usuario"; caption alignment and padding; compaction of a
wrapped block; and the untranslated word "Song" — a scan-key collision in which
both arms of a conditional keyed the same catalog entry, so the second was
discarded as a duplicate. The scanner had been finding it all along.

---

## Known-good regression suites

| Suite | Covers | Run with |
| --- | --- | --- |
| Layout contracts (16) | Analyser decisions on purpose-built forms | `tools/run_layout_contracts.ps1` |
| The first VCL pilot layout smoke (9 forms) | Analyser decisions on the real catalog | `bin/Tests/Win32/LayoutFittingSmokeTests.exe <catalog>` |
| Form scan contracts (2) | What the scanner reads out of a form | `tools/run_form_scan_contracts.ps1` |
| Scanner smoke | What the Pascal scanner claims from one unit | `bin/Tests/Win32/ScannerSmokeTests.exe <file.pas>` |

The layout and form-scan runners reject a stale harness: if a source file is
newer than the built executable the run stops rather than reporting on code
that is no longer the code being changed.

| FMX runtime smoke | A pack applied to a live FMX form | `tools/tests/FMXRuntimeSmokeTests.dpr` |
| VCL runtime smoke | A pack applied to a live VCL form | `tools/tests/VCLRuntimeSmokeTests.dpr` |

The two runtime suites are **not currently reachable through their runner**:
`RunRuntimeSmokeTests.ps1` fails on an unrelated rotted test before it gets to
them. See open item 10. They compile and pass when built directly.

**What no suite covers well:** the runtime, beyond the `Width` property. See
open item 7.
