Standard controls: the audit and the target shape
"activities display not very pretty. are we using standard controls library? check all user interfaces and all apps if they use standard user interface components" — maintainer, 2026-09-16
The Governance activity page rendered its whole record — state, inputs, a gates table, the
signatures, the log — as one markdown string built with a StringBuilder, wrapped the state in
a hand-styled <span>-like pill with literal hex colours, and used ✅ ❌ ⏳ as the gate indicator.
That is the shape the maintainer called not very pretty, and the audit below found it is the
majority shape in this repo, not the exception.
What the audit measured
| Count | |
|---|---|
Source/*.cs files that build UiControls (layout areas) |
116 |
| …that hit at least one of the four anti-patterns below | ~101 |
Files using Skins.Card |
0 |
Files using Controls.Badge |
4 |
Files using Controls.DataGrid / ToDataGrid |
15 |
Files using Controls.Html(...) |
25 |
Files with a literal #rrggbb colour in a style |
44 |
The four anti-patterns, each measured against the platform's own guidance
(Badges, Icons & Status, DataGrid,
Layout Areas, the /layout-area skill):
- A — a record rendered as markdown.
**Field:** valuelines,| a | b |pipe tables and```fences assembled from a typed content record and handed toControls.Markdown. Markdown is for prose; a record has fields and a list has rows. - B — raw HTML for layout.
Controls.Html(handBuiltMarkup)for cards, tables, heroes and trees. Forbidden by AGENTS.md since 2026-09-15 for anything but pre-rendered rich text. - C — a card, pill or table emulated with inline CSS.
border: 1px solid …; border-radius …; padding …on a stack,display:inline-block; padding: 2px 8pxon a label, and aStateColorshelper returning#e8f5e9/#c62828. Literal colours ignore the theme — dark mode renders a pastel pill on a dark surface. - D — emoji as the status indicator. ✅ ❌ ⏳ ⚠️ 🔴 in a cell or a label, and in one place a dialog parsing the emoji prefix of a status string to decide its phase.
The target shape
Every page is composed from the platform's controls and nothing else:
- State →
Controls.Badge. Fluent appearances (Accentfor active,Neutralfor resting) where they fit; otherwise the portal's status tokens —var(--success, #107c10),var(--error, #d13438),var(--warning-fill-rest, …)/var(--warning-foreground, …)— with a Fluent default as the fallback. Never a literal colour by itself, never an emoji. - A section → a
Controls.StackwithSkins.Card.Controls.H4(title)as the heading, an optional badge beside it (the gate tally), the body underneath. No border CSS. - A list →
Controls.DataGridover a plain row record withPropertyColumnControl<string>columns named bynameof(Row.X).ToCamelCase(). Inputs, gates, signatures, signers, stages, readings, places: rows, not markdown tables. - A label/value fact →
Controls.Subject(label)+Controls.Body(value)in a horizontal stack. Prose (a message, a rationale) staysControls.Body; a log stays a fencedControls.Markdownblock, which is what a fence is for. - Actions →
Controls.ToolbarwithAppearance.Accenton the primary button. - Texts → a
*Textsrecord per language (ActivityTexts.For(host.ViewerLocale())), so a page never shows a raw localization key. The platform catalog's generic verbs (ui.accept,ui.reject) are reused; everything specific to the page lives beside it. - The rows and the badge styling are pure functions asserted in
Test/, so a page's content is tested without rendering it.
Governance/Activity and Governance/Standard are the first two pages in this shape (#1981).
Read them as the reference.
Paid down so far
| PR | Module | What moved |
|---|---|---|
| #1981 | Governance | Activity + Standard: badges, cards, grids, toolbar, texts per language |
| #1983 | ten gallery pages | the shared hand-rolled card line → Skins.Card; a Card example in DefaultViews |
| #1985 | Essentials | OperationRequest: state badge, facts, cards, toolbar, plain progress line |
| #1986 | Hosting | Issue, DeploymentStatus, TriageStatus (counts as an int-typed grid), TriageItem (facts, links, code-view logs) |
| #1987 | Signature | signers grid, card thumbnail, facts for the credential summaries, token colours, desk cards |
| #1988 | Approvals | token colours, toolbars, badge for the desk error |
| #1989 | Providers | the setup page as a card, an Invalid badge |
| #1990 | MyAi | source rows as cards |
| #1991 | Training | the tour's prose panel as a card |
| #1992 | Feedback | cards, category badge, status badges |
Three lessons from the first pass, so the next one does not repeat them: a skin and the stack it
wraps both bind the control's style — padding goes on an inner stack, never on the skinned one;
a count column is PropertyColumnControl<int> — the grid deserializes each cell as the
column's type and a string column over numbers throws per row; and a page whose tests read the
direct area ids of a card (the Signature desk) needs those ids on the card's direct children,
not on a nested content stack.
The remaining debt, per module
Letters are the anti-patterns above. A module is done when its line can be deleted.
| Module | Files | Anti-patterns |
|---|---|---|
| AppleMaps | Maps/Source/MapsAreas.cs, AmapsConnect.cs |
A, D |
| AppleMusic | Music/Source/MusicAreas.cs, AmConnect.cs |
A, B, D |
| AppleWeather | Weather/Source/WeatherAreas.cs, AwConnect.cs |
A, B, D |
| Chess | GambitHunt/Source/GambitHuntLayoutAreas.cs, Game/Source/ChessGameLayoutAreas.cs, ChessBoardView.cs, ChessNativeBoard.cs |
B, C, D |
| Collaboration | Review/Source/ReviewLayoutAreas.cs (13 Controls.Html sites) |
B, C, D |
| Cornerstone | Article/Source/ArticleLayoutAreas.cs, Pricing/Source/PricingLayoutAreas.cs |
B |
| DataModelling | DataModelExplorer/Source/DataModelExplorerLayoutAreas.cs, DataModelExplorerLogic.cs |
A |
| Edu | CourseCatalog, LearningJourney, CourseInvite, Exercise, Quiz, Workbook, AnswerSheet layout areas |
A, B, C, D |
| Essentials | Email/Source/EmailLayoutAreas.cs |
A, C, D |
Account/Source/AccountAreas.cs, GoConnect.cs |
A, B, D | |
| HomeAssistant | Home/Source/HomeAreas.cs, HaConnect.cs |
A, B, D |
| Hosting | 16 files — Admin, Backup, BackupStore, Build (2), ConfigAudit, Deployment, FleetConsole (3), InstanceAction (2), LogEntry, ModuleInventory, PlatformBuildInbox, RepoHealth |
A, C, D |
| ICloud | Calendar/Source/CalendarAreas.cs, IcConnect.cs |
A, B, D |
| MyAi | Panel/Source/AiSettingsAreas.cs — the ✅/❌ status messages |
D |
| Providers | ProvidersApp/Source/ProviderSetupAreas.cs (status messages), ProviderSetup.cs (models as markdown rows) |
A, D |
| RemoteControl | Console/Source/ConsoleAreas.cs, Screen/Source/ScreenLayoutAreas.cs |
A, B, D |
| RolePlay | Character, Playroom (2), Scenery, Story layout areas |
A, B, C, D |
| Signature | the transient action messages (RefreshMessage, the login status lines) |
D |
| Store | Catalog (4), Plugin (3), Core (2), Coupon, Publishing, Installer (2), Maintenance (3) |
A, B, C, D |
Not counted: the canvas and SVG renderers (DoublePendulum, FractalStars, ThreeBody), where
Controls.Html carries a drawing rather than a layout; the Tests areas, whose pass/fail table
is read by CI; and the package covers' hero blocks, which are pre-rendered rich text by design.
The clean references to align against, from the same audit: DefaultViews/Gallery,
Analysis/Gallery (the only KpiStrip), Approvals/Approval, Publish/Deck, Edu/Module,
Edu/Lesson, Essentials/Mailbox, Hosting/LogEntry.
How to re-run the audit
for p in 'Skins.Card' 'Controls.Badge' 'Controls.DataGrid' 'Controls.Html(' 'StringBuilder' \
'border-radius' '#[0-9a-fA-F]\{6\}'; do
printf '%4s %s\n' "$(grep -rl --include='*.cs' -e "$p" --exclude-dir=src --exclude-dir=.worktrees \
--exclude-dir=clients --exclude-dir=app --exclude-dir=e2e . | grep '/Source/' | wc -l)" "$p"
done
The per-file classification was a read of every one of the 116 layout-area files; re-read a module before deleting its line, the counts above only say where to look.