cd ../blog

The Work Items Microsoft Left Inside Your Canvas App

Seven code comments in the control templates, and what each one tells you about the platform's actual roadmap.

</>

The Work Items Microsoft Left Inside Your Canvas App

Seven code comments in the control templates, and what each one tells you about the platform’s actual roadmap.


Control templates ship as XML, and XML has comments. Nobody strips them.

So every canvas app in the world carries a handful of notes written by the engineers who built the controls — including bug numbers, open questions, and things they intended to delete. They are not a leak; they are in a file Microsoft hands you deliberately. But they were clearly written for an internal audience, and they are more candid about the platform’s state than any documentation.

Here is all seven, with what each one actually tells you.

1. A deprecated property, waiting on a migration nobody wrote

From the label template:

<!-- IsErrorMessage is deprecated. Delete once a document converter is written
     to migrate IsErrorMessage=true -> Live=Live.Assertive -->
<property name="IsErrorMessage" datatype="Boolean" defaultValue="false" hidden="true">

This is the most informative comment in the set. It tells you:

  • IsErrorMessage is dead, but still shipping, because removing it would break existing apps.
  • The intended replacement is Live=Live.Assertive — the ARIA live-region property.
  • The blocker is a document converter: the migration tooling that rewrites apps on load. It has not been written.

That last point is the interesting one. The platform has a migration mechanism — the modern controls use one extensively — and this property has been sitting in the queue for it long enough that both our 2023 and 2026 samples carry the identical comment.

If you are still setting IsErrorMessage, the intended replacement is Live. That is not documented anywhere; it is in this comment.

2. An open design question, asked on two controls

From label:

<!-- Behavior Properties -->
<!-- TASK: 85476: Do Behavior properties make sense as input only? -->
<appMagic:includeProperty name="OnSelect" direction="in" />

And from timer, the same question with different punctuation:

<!-- TASK: 85476 - Do behavior properties make sense as input only? -->

Same work item, 85476, quoted in two templates. Someone was working through the control library asking whether OnSelect and friends should be input-only, wrote the question inline as they went, and the answer never came back.

The direction="in" on the line below is the provisional decision. Behaviour properties are declared as inputs, pending a design call that has not happened.

Worth knowing if you have ever wondered why behaviour properties are awkward in components and why you cannot read them back. It is not a deliberate design; it is an open question with a placeholder answer.

3. A theme gap

From button:

<!-- TASK: 4548082: Add the Color and Fill in default theme json -->

The button’s Color and Fill are supposed to come from the default theme and do not, which means the button’s colours are set in the template rather than resolved from the palette like everything else.

If you have ever built a custom theme and found that buttons need special-casing — this is why. The theme file carries 116 named styles and 71 palette entries, and the button’s two most important colour properties were never wired into them.

4. A naming inconsistency, known and unfixed

From gallery:

<!-- TASK 93096 - Custom Gallery: Change ImageGallery variant name to match
     the control name (upper-case). -->

A gallery variant is named inconsistently with its control. Trivial in isolation, and it tells you something structural: gallery variant names are a public surface — they appear in .pa.yaml — so renaming one is a breaking change. A cosmetic fix became permanent because the cost of correcting it exceeds the cost of leaving it wrong.

Anyone writing tooling that matches on variant names should assume more of these exist.

5. The hidden properties have a deletion ticket

From datepicker:

<!-- RDBug 5136801:- Remove the hidden properties -->

The date picker carries twelve hidden calendar-styling properties — CalendarCellHeight, SelectedDateFill, MonthColor, WeekColor, DayColor, CalendarHeaderFill and more. Every one is something makers regularly ask for and are told is not possible.

This comment settles it: they are not an unlockable API, they are debt with an open bug requesting removal. Do not build on them, and do not hope for them.

Note the tracker prefix changes here — RDBug rather than TASK. Two different bug databases are quoted in the same control library.

6. A feature request written into the control

From attachments:

<!-- Remove maximum restriction on MaxAttachmentSize;
     make MaxAttachments input/output -->

No ticket number, just an intent. MaxAttachmentSize has a ceiling someone wanted to lift, and MaxAttachments should be readable as an output and is not.

If you need to read how many attachments a user has added, this comment explains why you cannot.

7. The accessibility heuristic, confessed

The best one. From image:

<!-- When the image is meant to be clickable, a <button> is overlaid on top of
     the actual image. Unfortunately, the control does not know if OnSelect
     behavior is specified by the user, so we use TabIndex >= 0 as a heuristic
     that the image should be a button. When the image is intended to be
     non-interactive... -->

Note the word “unfortunately.”

The platform cannot introspect whether you have written an OnSelect formula. So it guesses from TabIndex, and that guess decides whether a screen reader announces your image as a button or skips it entirely.

You can see the same heuristic in the label control’s binding:

role: properties.TabIndex() >= 0 ? 'button' : 'presentation'

This is the single most actionable thing in the whole set. If you make an image or label clickable, set TabIndex to 0. Not for keyboard navigation — for the semantics. Without it the control renders as presentation and assistive technology cannot see it. The App Checker rule acc-TabIndexShouldBeDefinedForInteractiveControl exists precisely because of this — it fired 20 times in one app we examined and 229 times in another.

Update: this one got fixed. In the modern control generation rolling out from February 2026 — modernText, modernTextInput, modernCombobox, modernDatePicker — the TabIndex property does not exist at all. Zero occurrences in any of the four templates. In its place, all four declare:

<appMagic:includeProperty name="AccessibleLabel" />

Accessibility semantics stop being inferred from a numeric property and become something you state explicitly. Microsoft’s control update guide lists “TabIndex removed” for Icon and “AcceptsFocus removed” for Button and Info Button, so the retirement is deliberate and broad.

It is the only item in this list that has been resolved rather than carried forward — and it was resolved by deleting the property the heuristic depended on. If you have customised TabIndex or Tooltip on a control that offers an update, expect to lose it: neither survives into the new generation.

Bonus: the paperwork nobody finished

Every classic control carries this:

<license type="text/html"><![CDATA[<p>TODO:  Need license text here.</p>]]></license>
<description><![CDATA[LABEL
      Control description here.]]></description>

A licence field with a TODO, and a description field containing the words “Control description here.” Identical in the 2023 and 2026 exports, so it has been that way for at least three years, in every canvas app on earth.

There is also one comment that is simply a good clarification, and deserves credit:

<!-- This is not an 'Auto' property. This is for allowing the label to grow
     vertically to display the content -->
<appMagic:includeProperty name="AutoHeight" ... />

The control library has a whole family of auto* capabilities — autoFill, autoBorders, autoPointerViewState — where “auto” means “the framework handles this.” AutoHeight is unrelated despite the name, and someone took the trouble to say so.

What to take from this

Not schadenfreude. These are seven reasonable engineering notes from people shipping a large product under constraint, and every one of them is more useful than its absence would be.

What they collectively tell you is where the platform’s soft spots are, in the platform’s own words:

  • Behaviour properties have an unresolved design (TASK 85476).
  • Button theming was never completed (TASK 4548082).
  • Variant names are wrong and will stay wrong (TASK 93096).
  • The date picker’s styling hooks are being removed, not opened (RDBug 5136801).
  • Accessibility semantics rest on a TabIndex heuristic the code itself calls unfortunate.
  • Deprecated properties persist because the migration tooling to retire them was never built.

That last one is the theme. Almost every comment here describes something that could not be cleaned up because there was no safe path to change a public surface. It is the same reason your .msapp still serialises 97% boilerplate and the classic control library has not moved in three years: canvas apps cannot break, so nothing can be removed.

Read that way, these seven comments are a decent short course in what it costs to maintain backward compatibility at Microsoft’s scale.


Methodology

Sample: 15 classic control templates from two production .msapp exports (2023-02-14, 2026-02-16). Comments are quoted verbatim from the XML in References/Templates.json; ellipsis marks truncation only.

unzip -q app.msapp -d app/
python3 - <<'EOF'
import json, re
for t in json.load(open('app/References/Templates.json'))['UsedTemplates']:
    for c in re.findall(r'<!--(.*?)-->', t['Template'], re.S):
        c = ' '.join(c.split())
        if re.search(r'TODO|TASK|Bug|deprecat|Remove |Unfortunately', c, re.I):
            print(f"[{t['Name']}] {c}")
EOF

Work-item numbers are quoted as they appear in the shipped templates. They reference internal trackers that are not publicly accessible, so the surrounding interpretation is inference from the comment text alone.