`. `GetRowStyle(rowIndex)` resolves EditRowStyle/InsertRowStyle > AlternatingRowStyle > RowStyle. FieldHeaderStyle applied to the first `` (header cell) of each data row.
-- **DetailsView Caption + PageCount (WI-28):** Added `Caption` (string), `CaptionAlign` (TableCaptionAlign enum, default NotSet), `PageCount` (int, computed from Items.Count()). Caption renders `` element with CSS from `GetCaptionStyle()` — same pattern as GridView. `GetCaptionStyle()` maps TableCaptionAlign values to `caption-side` or `text-align` CSS.
-- **FormView remaining events (WI-31):** Added `ModeChanged` (EventCallback) — fires after mode transition completes in every command handler (cancel, edit, insert, update). Added `ItemCommand` (EventCallback) — fires for all commands before specific handling. Created `FormViewCommandEventArgs` extending `CommandEventArgs`. Added `ItemCreated` (EventCallback) — fires on first render in `OnAfterRenderAsync`. Added `PageIndexChanging` and `PageIndexChanged` (EventCallback) — with cancellation support via `Cancel` property. Added "page" command handler in `HandleCommandArgs` supporting "next"/"prev"/"first"/"last"/numeric page arguments.
-- **FormView style sub-components + Pager + Caption (WI-33):** Created `IFormViewStyleContainer` interface with 7 properties (RowStyle, EditRowStyle, InsertRowStyle, HeaderStyle, FooterStyle, EmptyDataRowStyle, PagerStyle). FormView implements the interface. Created 7 sub-component pairs: FormViewRowStyle, FormViewEditRowStyle, FormViewInsertRowStyle, FormViewHeaderStyle, FormViewFooterStyle, FormViewEmptyDataRowStyle, FormViewPagerStyle. Each uses `[CascadingParameter(Name = "ParentFormView")]`. Added `PagerTemplate` (RenderFragment) — when set, replaces default numeric pager. Added `Caption` (string) and `CaptionAlign` (TableCaptionAlign). `GetCurrentRowStyle()` resolves EditRowStyle/InsertRowStyle > RowStyle based on CurrentMode. Styles applied via `style="@XxxStyle?.ToString()"` on `` elements.
-- **GridView selection support (WI-02):** Added `SelectedIndex` (int, default -1), `AutoGenerateSelectButton` (bool, default false), `SelectedIndexChanging` (EventCallback), `SelectedIndexChanged` (EventCallback). `SelectedRow` returns the data item at `SelectedIndex` from `PagedItems`. `SelectedValue` uses reflection on `DataKeyNames` first key field. `SelectRow(int)` method follows the same pattern as `EditRow`: fires `SelectedIndexChanging`, checks `Cancel`, updates `SelectedIndex`, fires `SelectedIndexChanged`. `ShowCommandColumn` updated to include `AutoGenerateSelectButton`. `GetRowStyle` priority: EditRowStyle > SelectedRowStyle > AlternatingRowStyle > RowStyle. `GridViewRow` gets `IsSelected` parameter. When `AutoGenerateSelectButton=true`, a "Select" link renders in the command column before Edit/Delete. `GridViewSelectEventArgs` has `NewSelectedIndex` (int) + `Cancel` (bool), matching `GridViewEditEventArgs` pattern. `GridViewSelectedRowStyle` child component already existed from WI-05; `IGridViewStyleContainer.SelectedRowStyle` property was added in that same WI.
-- **DataGrid style sub-components (WI-44):** Created `IDataGridStyleContainer` interface with 7 `TableItemStyle` properties (AlternatingItemStyle, ItemStyle, HeaderStyle, FooterStyle, PagerStyle, SelectedItemStyle, EditItemStyle). DataGrid implements the interface with `public TableItemStyle Xxx { get; internal set; } = new TableItemStyle()` pattern + 7 `[Parameter] RenderFragment XxxStyleContent` parameters. Created 7 sub-component pairs: DataGridAlternatingItemStyle, DataGridItemStyle, DataGridHeaderStyle, DataGridFooterStyle, DataGridPagerStyle, DataGridSelectedItemStyle, DataGridEditItemStyle. Each inherits `UiTableItemStyle`, uses `[CascadingParameter(Name = "ParentDataGrid")] IDataGridStyleContainer`. DataGrid.razor wraps style content in ``. Added `Caption` (string), `CaptionAlign` (TableCaptionAlign), `CellPadding` (int, default -1), `CellSpacing` (int, default -1), `GridLines` (GridLines enum), `UseAccessibleHeader` (bool). `GetRowStyle(rowIndex)` resolves EditItemStyle > SelectedItemStyle > AlternatingItemStyle > ItemStyle priority. DataGridRow gets `RowStyle` parameter rendered via `style="@RowStyle?.ToString()"`. Template updated with paging UI (page links in tfoot), footer row, caption, grid lines rules attribute, sort header links. Follows exact GridView WI-05 + WI-07 pattern.
-- **Menu level styles (WI-47):** Created `MenuLevelStyle` class (public constructor, implements IStyle) in `MenuLevelStyle.cs`. Added three `List` parameters to Menu: `LevelMenuItemStyles` (per-depth item styles), `LevelSelectedStyles` (per-depth selected styles), `LevelSubMenuStyles` (per-depth submenu container styles). Index 0 = level 1. Menu exposes `GetLevelMenuItemStyle(depth)`, `GetLevelSelectedStyle(depth)`, `GetLevelSubMenuStyle(depth)` internal helpers. MenuItem.razor applies level styles via `GetItemStyle()`, `GetItemCssClass()`, `GetSubMenuStyle()` code-behind methods. LevelMenuItemStyles override StaticMenuItemStyle/DynamicMenuItemStyle for items at specific levels. LevelSelectedStyles override when item is selected.
-- **Panel BackImageUrl (WI-48):** Added `[Parameter] public string BackImageUrl` to Panel. When set, renders `background-image:url({BackImageUrl})` in the `BuildStyle()` method alongside existing HorizontalAlign, ScrollBars, and Wrap styles.
-- **Login/ChangePassword Orientation + TextLayout (WI-49):** Created `LoginTextLayout` enum in `Enums/LoginTextLayout.cs` (TextOnLeft=0, TextOnTop=1). Added `Orientation` (Orientation enum, default Vertical) and `TextLayout` (LoginTextLayout, default TextOnLeft) parameters to both Login and ChangePassword. Orientation=Horizontal renders fields side-by-side in columns. TextLayout=TextOnTop renders labels above inputs. Used `Enums.Orientation.Vertical` fully-qualified comparison in Razor helpers (`IsVertical`/`IsCpVertical`) to avoid Razor naming collision between parameter name and enum type. Dynamic `TotalColspan`/`CpTotalColspan` adjusts spanning cells for horizontal layout.
-- **DataGrid paging + sorting events (WI-45):** Added `PageIndexChanged` (EventCallback), `SortCommand` (EventCallback), `ItemCreated` (EventCallback), `ItemDataBound` (EventCallback), `SelectedIndexChanged` (EventCallback). Created 3 event args: `DataGridPageChangedEventArgs` (NewPageIndex int), `DataGridSortCommandEventArgs` (SortExpression string, CommandSource object), `DataGridItemEventArgs` (Item object). `GoToPage(int)` updates `CurrentPageIndex` and fires `PageIndexChanged`. `Sort(string)` fires `SortCommand`. Added `PageCount`, `PagedItems`, `SelectedIndex`, `EditItemIndex` properties. DataGrid naming uses Web Forms conventions (ItemStyle not RowStyle, CurrentPageIndex not PageIndex, EditItemIndex not EditIndex).
-- **ListView CRUD events + edit/insert templates (WI-41):** Added 10 events: `ItemCommand` (ListViewCommandEventArgs), `ItemEditing` (ListViewEditEventArgs), `ItemCanceling` (ListViewCancelEventArgs), `ItemDeleting` (ListViewDeleteEventArgs), `ItemDeleted` (ListViewDeletedEventArgs), `ItemInserting` (ListViewInsertEventArgs), `ItemInserted` (ListViewInsertedEventArgs), `ItemUpdating` (ListViewUpdateEventArgs), `ItemUpdated` (ListViewUpdatedEventArgs), `ItemCreated` (EventCallback). Added `EditIndex` (int, default -1), `EditItemTemplate` (RenderFragment), `InsertItemTemplate` (RenderFragment), `EmptyItemTemplate` (RenderFragment). Created `ListViewCancelMode` enum in `Enums/`. Created 9 EventArgs classes following GridView/FormView patterns — simple classes with Cancel bool for pre-events, AffectedRows+Exception for post-events. `HandleCommand(string, object, int)` routes Edit/Cancel/Delete/Insert/Update commands to specific handlers; unknown commands fire `ItemCommand`. `GetItemTemplate(int, bool)` returns `EditItemTemplate` when itemIndex == EditIndex, otherwise alternating logic. `InsertItemTemplate` renders at top or bottom based on `InsertItemPosition`. `EmptyItemTemplate` takes precedence over `EmptyDataTemplate` when both are set. Wired existing `InsertItemPosition` stub parameter. All existing functionality (data binding, GroupItemCount, LayoutTemplate, ItemSeparatorTemplate) preserved.
-
- Team update (2026-02-23): Menu Orientation Orientation parameter collides with enum type name in Razor samples must use local variable with fully-qualified type decided by Jubilee
- Team update (2026-02-23): P2 test observation Login/ChangePassword/CreateUserWizard already inherit BaseStyledComponent, so WI-52 may have been a no-op or template-only change decided by Rogue
- Team update (2026-02-23): Milestone 6 Work Plan ratified 54 WIs across P0/P1/P2 tiers targeting ~345 feature gaps decided by Forge
- Team update (2026-02-23): UI overhaul requested ComponentCatalog (UI-2) and search (UI-8) assigned to Cyclops decided by Jeffrey T. Fritz
+
+
+### Summary: Milestone 6 P0/P1 Implementation (2026-02-23)
+
+**Base class fixes:** DataBoundComponent chain → BaseStyledComponent (WI-07, 14 data controls). BaseListControl created for 5 list controls with DataTextFormatString + AppendDataBoundItems (WI-47/48). CausesValidation added to CheckBox/RadioButton/TextBox (WI-49). Label AssociatedControlID switches span→label (WI-51). Login/ChangePassword/CreateUserWizard → BaseStyledComponent (WI-52). Validator ControlToValidate dual-path: ForwardRef (ControlRef) + string ID via reflection (WI-36).
+
+**Menu overhaul (WI-19/21/23/47/50):** → BaseStyledComponent. Selection tracking (SelectedItem/SelectedValue, MenuItemClick, MenuItemDataBound). MenuEventArgs, Value, Target, ValuePath, SkipLinkText. MaximumDynamicDisplayLevels. Orientation enum + CSS horizontal class. MenuLevelStyle (public IStyle class) with LevelMenuItemStyles/LevelSelectedStyles/LevelSubMenuStyles lists.
+
+### Summary: Milestone 7 Data Control Depth (2026-02-24)
+
+**Style sub-components:** GridView (8 styles, IGridViewStyleContainer), DetailsView (10 styles), FormView (7 styles), DataGrid (7 styles, IDataGridStyleContainer). All follow CascadingParameter pattern with UiTableItemStyle base. Style priority: Edit > Selected > Alternating > Row.
+
+**TreeView enhancements (WI-11/13/15):** TreeNodeStyle (5 props) + 6 sub-components. Selection support (SelectedNode, SelectedNodeChanged, keyboard). ExpandAll/CollapseAll, FindNode, ExpandDepth, NodeIndent, PathSeparator.
+
+**GridView (WI-02/05/07):** Selection (SelectedIndex, AutoGenerateSelectButton, SelectedRow/Value). 10 display props (ShowHeader/Footer, Caption, GridLines, CellPadding/Spacing, EmptyDataTemplate, UseAccessibleHeader).
+
+**Other controls:** FormView events (ModeChanged, ItemCommand, paging with cancellation) + PagerTemplate + Caption (WI-31/33). DetailsView Caption + PageCount (WI-28). DataGrid paging/sorting events (WI-45). ListView 10 CRUD events + EditItemTemplate/InsertItemTemplate (WI-41). Panel BackImageUrl (WI-48). Login/ChangePassword Orientation + TextLayout (WI-49, LoginTextLayout enum).
+
+**Key patterns:** Orientation enum collides with parameter name in Razor — use `Enums.Orientation.Vertical` fully-qualified.
+
+📌 Team update (2026-02-23): Milestone 6 Work Plan ratified — 54 WIs across P0/P1/P2 tiers targeting ~345 feature gaps — decided by Forge
+📌 Team update (2026-02-23): UI overhaul requested — ComponentCatalog (UI-2) and search (UI-8) assigned to Cyclops — decided by Jeffrey T. Fritz
### Milestone 8 Release-Readiness Bug Fixes (2026-02-24)
@@ -72,3 +57,25 @@ Audited 13 controls. Found: AccessKey/ToolTip missing from base class (universal
- **Shared PagerSettings sub-component:** Created `PagerSettings` class (plain C# POCO, not a Blazor component) with all 12 Web Forms PagerSettings properties (Mode, PageButtonCount, First/Last/Next/PreviousPageText, image URLs, Position, Visible). Created `PagerPosition` enum in `Enums/` (PagerButtons already existed). Created `IPagerSettingsContainer` interface in `Interfaces/`. Created `UiPagerSettings` abstract base component following the `UiTableItemStyle` CascadingParameter pattern but for settings instead of styles. Created 3 concrete sub-component pairs: `GridViewPagerSettings`, `FormViewPagerSettings`, `DetailsViewPagerSettings` — each inherits `UiPagerSettings` and uses `[CascadingParameter(Name = "ParentXxx")]` to set properties on the parent's `PagerSettings` instance. Wired into GridView, FormView, DetailsView: added `IPagerSettingsContainer` to each control's interface list, added `PagerSettings` property + `PagerSettingsContent` RenderFragment parameter, rendered `@PagerSettingsContent` inside existing `` block. Key files: `Enums/PagerPosition.cs`, `PagerSettings.cs`, `Interfaces/IPagerSettingsContainer.cs`, `UiPagerSettings.cs`, `GridViewPagerSettings.razor(.cs)`, `FormViewPagerSettings.razor(.cs)`, `DetailsViewPagerSettings.razor(.cs)`.
Team update (2026-02-24): Substitution/Xml formally deferred no implementation needed decided by Beast
Team update (2026-02-24): M8 scope excludes version bump to 1.0 and release decided by Jeffrey T. Fritz
+
+ Team update (2026-02-25): Deployment pipeline patterns established compute Docker version with nbgv before build, gate on secrets, dual NuGet publishing decided by Forge
+
+### Milestone 9 Migration-Fidelity Fixes (2026-02-25)
+
+- **ToolTip → BaseStyledComponent (WI-01):** Added `[Parameter] public string ToolTip { get; set; }` to `BaseStyledComponent.cs`. Removed duplicate ToolTip declarations from 8 components: Button, Calendar, DataList, FileUpload, HyperLink, Image, ImageButton, ImageMap. All controls inheriting BaseStyledComponent (28+) now get ToolTip automatically. Intentionally preserved ToolTip on sub-component types (ChartSeries, DataPoint, MenuItem, TreeNode) and binding fields (MenuItemBinding.ToolTipField, TreeNodeBinding.ToolTipField) since those are semantically different item-level tooltips.
+- **ValidationSummary comma-split bug fix (WI-05):** `AspNetValidationSummary.razor.cs` used `Split(',')[1]` to extract error messages, which truncated messages containing commas. Fixed to use `IndexOf(',')` + `Substring()` to take everything after the first comma. This is a data corruption bug — any validation message with a comma would silently lose content.
+- **SkinID type fix (WI-07):** Changed `SkinID` property in `BaseWebFormsComponent.cs` from `bool` to `string`. Web Forms SkinID is the name of a skin to apply (a string), not a boolean flag. The `[Obsolete]` attribute was preserved since theming is not available in Blazor.
+- **ToolTip rendering in templates (WI-03):** Audited all `.razor` files inheriting BaseStyledComponent (directly or via DataBoundComponent chain). Added `title="@ToolTip"` to outermost HTML elements on 32 components that were missing it. Components already rendering ToolTip (Button, Calendar, DataList, FileUpload, HyperLink, Image, ImageButton, ImageMap) were left alone. Skipped: ListView and Repeater (no wrapper element), all style sub-components (GridViewRowStyle, CalendarDayStyle, etc.), and GridViewRow/DataGridRow (row sub-components). For multi-layout components (CheckBoxList, RadioButtonList, Panel, CheckBox, RadioButton), added title to every branch's outermost element. For Login controls (Login, ChangePassword, CreateUserWizard), added title to inner `` elements since outer `` is a Blazor component. TextBox uses `CalculatedAttributes` dictionary — added ToolTip there. All 1206 tests pass.
+
+ Team update (2026-02-25): Doc audit found DetailsView/DataGrid features needing implementation verification decided by Beast
+ Team update (2026-02-25): Test audit found 5 missing smoke tests (P0: ListView CrudOperations) decided by Colossus
+ Team update (2026-02-25): M9 plan ratified 12 WIs, migration fidelity decided by Forge
+
+### Bug Fix: TreeView caret not rotating on expand/collapse (#361)
+
+- **NodeImage fallback logic restructured (TreeNode.razor.cs lines 176–240):** The `NodeImage` property's three fallback paths (for root, parent, and leaf nodes when `ShowLines=false`) did not check `ShowExpandCollapse`. They relied on `ImageSet.Collapse` being non-empty to determine whether to show expand/collapse images vs `Default_NoExpand.gif`. For any ImageSet where `Collapse` returned empty, nodes would always show `Default_NoExpand.gif` regardless of expanded state. Fixed by adding explicit `if (ParentTreeView.ShowExpandCollapse)` checks in the non-ShowLines paths. Extracted `ExpandCollapseImage(bool expanded)` private helper to DRY the ImageSet→filename resolution with guaranteed fallbacks (`Default_Collapse.gif` / `Default_Expand.gif`). When `ShowExpandCollapse=false`, the method now explicitly returns `Default_NoExpand.gif`.
+- **Key files:** `src/BlazorWebFormsComponents/TreeNode.razor.cs` (NodeImage property + ExpandCollapseImage helper). Template in `TreeNode.razor` was already correct — it only renders `NodeImage` when `ShowExpandCollapse=true`.
+- **Pattern:** TreeView expand/collapse image resolution has three tiers: (1) ShowLines+ShowExpandCollapse → line-style images (Dash/T/L variants), (2) ShowExpandCollapse only → ImageSet images with Default fallback, (3) neither → NoExpand. The `TreeViewImageSet` base class's `Collapse`/`Expand` properties never return empty for built-in sets, but the code must not assume this.
+
+ Team update (2026-02-25): M12 introduces Migration Analysis Tool PoC (`bwfc-migrate` CLI, regex-based ASPX parsing, 3-phase roadmap) decided by Forge
+
diff --git a/.ai-team/agents/forge/history.md b/.ai-team/agents/forge/history.md
index 949c1bd93..99f3300e9 100644
--- a/.ai-team/agents/forge/history.md
+++ b/.ai-team/agents/forge/history.md
@@ -66,22 +66,7 @@ Chart on milestone4 branch substantially complete. Architecture sound: Component
### Summary: Milestone 7 Planning (2026-02-23)
-Planned M7: "Control Depth & Navigation Overhaul" — 51 work items targeting ~138 gap closures. Key findings from post-M6 codebase audit:
-
-- **GridView (~55% post-M6):** Has paging, sorting, editing from M6. Still missing selection (SelectedIndex/SelectedRow/SelectedRowStyle), 6 style sub-components, display props (ShowHeader/ShowFooter/Caption/GridLines/EmptyDataTemplate). Selection is the last major functional gap.
-- **Menu (~42%):** Only got Orientation in M6. Still missing ~35 props — base style props (needs BaseStyledComponent upgrade), selection tracking, MenuItemClick/MenuItemDataBound events, level styles. JS interop complicates base class change.
-- **TreeView (~60%):** Untouched in M6. Has solid core (nodes, data binding, checkboxes, expand/collapse, image sets). Missing node-level styles (TreeNodeStyle objects), functional selection, ExpandAll/CollapseAll, ExpandDepth, FindNode.
-- **FormView (~50%):** M6 added header/footer/empty. Still missing style sub-components, paging events (PageIndexChanging/Changed), ModeChanged/ItemCommand events, PagerSettings.
-- **DetailsView (~70%):** Has strong event coverage (10 CRUD/mode/paging events). Missing all 10 style sub-components, PagerSettings, Caption.
-- **ListView (~42%):** Barely touched. Has excellent templates but missing all 16 CRUD events, editing templates, selection, sorting. Deferred to P2 due to L size.
-- **DataGrid (~55%):** Has command events from original impl + style inheritance from M6. Missing style sub-components, paging/sorting events. Also P2.
-
-**Key patterns confirmed:**
-- Style sub-components are the single biggest systematic remaining gap across all data controls
-- PagerSettings should be a shared type (GridView, FormView, DetailsView all need identical API)
-- Validator ControlToValidate string ID is a migration-blocking mismatch — ForwardRef doesn't match the "paste and it works" migration story
-- Diminishing returns are real: M6 closed ~345 gaps, M7 targets ~138 because remaining gaps require more work per gap (style sub-components, event pipelines vs. base class inheritance)
-- Re-audit must open the milestone — all planning-docs/ files are stale (pre-M6 numbers)
+Planned M7: "Control Depth & Navigation Overhaul" — 51 WIs targeting ~138 gap closures. Per-control coverage: GridView ~55% (missing selection, 6 style sub-components, display props), Menu ~42% (missing ~35 props, selection, events, level styles), TreeView ~60% (missing TreeNodeStyle, selection, ExpandAll/FindNode), FormView ~50% (missing styles, paging events, PagerSettings), DetailsView ~70% (missing 10 style sub-components, PagerSettings, Caption), ListView ~42% (P2, missing 16 CRUD events), DataGrid ~55% (P2). Key insights: style sub-components are biggest systematic gap; PagerSettings should be shared type; re-audit must open milestone.
📌 Team update (2026-02-23): Milestone 7 planned — 51 WIs, ~138 gaps, "Control Depth & Navigation Overhaul". P0: GridView completion + re-audit. P1: TreeView, Menu, DetailsView, FormView, Validators. P2: ListView CRUD, DataGrid, Menu levels. — decided by Forge
@@ -89,3 +74,55 @@ Planned M7: "Control Depth & Navigation Overhaul" — 51 work items targeting ~1
Team update (2026-02-24): Substitution/Xml formally deferred in status.md and README decided by Beast
Team update (2026-02-24): M8 scope excludes version bump to 1.0 and release decided by Jeffrey T. Fritz
Team update (2026-02-24): PagerSettings shared sub-component created for GridView/FormView/DetailsView decided by Cyclops
+
+### Summary: v0.14 Deployment Pipeline Fixes (2026-02-25)
+
+Fixed three deployment pipeline issues on `fix/deployment-workflows` branch:
+
+- **Docker version computation:** `.dockerignore` excludes `.git`, so nbgv can't run inside the Docker build. Solution: compute version with `nbgv get-version` in the workflow BEFORE Docker build, pass as `ARG VERSION` into the Dockerfile, use `-p:Version=$VERSION -p:InformationalVersion=$VERSION` in both `dotnet build` and `dotnet publish`. This pattern (compute outside, inject via build-arg) is the standard approach when `.git` isn't available inside the build context.
+- **Azure App Service webhook:** Added a `curl -sf -X POST` step gated on `AZURE_WEBAPP_WEBHOOK_URL` secret. Uses `|| echo "::warning::..."` fallback so the workflow doesn't fail if the webhook is unavailable. The webhook URL comes from Azure Portal → App Service → Deployment Center.
+- **nuget.org publishing:** Added a second push step after the existing GitHub Packages push, gated on `NUGET_API_KEY` secret. Both pushes use `--skip-duplicate` for idempotency.
+
+**Key file paths:**
+- `.github/workflows/deploy-server-side.yml` — Docker build + push + Azure webhook
+- `.github/workflows/nuget.yml` — NuGet pack + push (GitHub Packages + nuget.org)
+- `samples/AfterBlazorServerSide/Dockerfile` — server-side demo container
+
+**Patterns established:**
+- Secret-gated workflow steps use `if: ${{ secrets.SECRET_NAME != '' }}` for graceful fallback when secrets aren't configured
+- Docker images are tagged with version number (from nbgv), `latest`, and commit SHA
+- Version is computed once via nbgv and shared via GitHub Actions step outputs (`steps.nbgv.outputs.version`)
+
+ Team update (2026-02-25): Deployment pipeline patterns established compute Docker version with nbgv before build, gate on secrets, dual NuGet publishing decided by Forge
+
+### Summary: Milestone 9 Planning (2026-02-25)
+
+Verified all 8 known priority gaps from prior audits against current `dev` branch. Found 7 of 8 already fixed (AccessKey, Image, Label, Validation Display, HyperLink NavigateUrl, DataBound chain, bUnit tests). Confirmed 1 still open (ToolTip not on BaseStyledComponent — 28+ controls missing) and identified 2 new gaps (ValidationSummary comma-split data corruption bug, SkinID bool→string type mismatch).
+
+Planned M9: "Migration Fidelity & Hardening" — 12 work items, ~30 gap closures. P0: ToolTip → BaseStyledComponent (4 WIs, ~28 gaps — highest-leverage remaining base class fix). P1: ValidationSummary comma-split fix + SkinID type fix (3 WIs). P2: Housekeeping — stale branch cleanup, doc gap audit, planning-docs refresh, integration test coverage review, sample navigation audit (5 WIs).
+
+📌 Team update (2026-02-25): Milestone 9 planned — 12 WIs, ~30 gaps, "Migration Fidelity & Hardening". P0: ToolTip base class fix. P1: ValidationSummary bug + SkinID type. P2: Housekeeping. — decided by Forge
+
+ Team update (2026-02-25): Doc audit found 10 gaps across FormView, DetailsView, DataGrid, ChangePassword, PagerSettings decided by Beast
+ Team update (2026-02-25): Nav audit found 4 missing components + 15 missing SubPages decided by Jubilee
+ Team update (2026-02-25): Test audit found 5 missing smoke tests decided by Colossus
+
+### Summary: Milestone 12 Planning — Migration Analysis Tool PoC (2026-02-25)
+
+Planned M12: "Migration Analysis Tool (PoC)" — 13 work items. A CLI tool (`bwfc-migrate`) that analyzes existing Web Forms applications and produces migration reports showing control coverage, gaps, code-behind pattern detection, and complexity scoring.
+
+**Architecture decisions:**
+- Same repo, new project: `src/BlazorWebFormsComponents.MigrationAnalysis/` — mapping table must stay in sync with component library
+- CLI tool via `System.CommandLine`, packaged as `dotnet tool` for easy distribution
+- Regex-based parsing for PoC (not Roslyn) — hard scope boundary to prevent scope creep
+- Control mapping registry derived from `status.md`: 51 supported, 2 deferred, ~15 explicitly unsupported with migration guidance
+- Complexity scoring: Green/Yellow/Red based on control gaps + code-behind pattern density
+- Two output formats: Markdown (human-readable) + JSON (machine-readable)
+- Three-phase roadmap: Phase 1 (M12) = analysis engine + CLI, Phase 2 = Roslyn + scaffolding, Phase 3 = Copilot agent integration
+
+**Key insight:** At 51/53 components, the highest-value work is no longer building components — it's reducing the friction of using the ones we have. A migration analysis tool turns a week of manual evaluation into a 5-second CLI invocation.
+
+📌 Team update (2026-02-25): Milestone 12 planned — 13 WIs, "Migration Analysis Tool PoC". CLI tool for Web Forms app analysis with control mapping, gap identification, complexity scoring, and report generation. — decided by Forge
+
+ Team update (2026-02-25): Consolidated audit reports now use `planning-docs/AUDIT-REPORT-M{N}.md` pattern for all milestone audits decided by Beast
+
diff --git a/.ai-team/agents/jubilee/history.md b/.ai-team/agents/jubilee/history.md
index 9a0f03cec..ac6e03849 100644
--- a/.ai-team/agents/jubilee/history.md
+++ b/.ai-team/agents/jubilee/history.md
@@ -42,3 +42,23 @@ Chart: 8 basic + 4 advanced sample pages (DataBinding, MultiSeries, Styling, Cha
Team update (2026-02-24): Menu auto-ID pattern Menu now auto-generates IDs for JS interop decided by Cyclops
Team update (2026-02-24): M8 scope excludes version bump to 1.0 and release decided by Jeffrey T. Fritz
Team update (2026-02-24): PagerSettings shared sub-component created samples may need PagerSettings demos decided by Cyclops
+
+### M9 Navigation Audit (WI-12)
+
+- Sidebar navigation is driven entirely by `ComponentCatalog.cs` — `NavMenu.razor` iterates over it with SubPages support.
+- Found **4 components** completely missing from ComponentCatalog (Menu, DataBinder, PasswordRecovery, ViewState) — invisible in sidebar.
+- Found **15 SubPage entries** missing across GridView (5), TreeView (2), FormView (3), DetailsView (2), ListView (1), DataGrid (1), Panel (1).
+- All 10 M7/M8 feature pages exist on disk with valid `@page` directives but none appear in sidebar navigation.
+- Some pages are partially reachable via in-page `Nav.razor` components, but TreeView Selection/ExpandCollapse and DetailsView Styles/Caption have no nav links at all.
+- DataList has a SubPage name mismatch: catalog says "Flow" but file is `SimpleFlow.razor`.
+- Report written to `.ai-team/decisions/inbox/jubilee-m9-nav-audit.md`.
+
+� Team update (2026-02-25): ToolTip moved to BaseStyledComponent (28+ controls) decided by Cyclops
+ Team update (2026-02-25): M9 plan ratified 12 WIs, migration fidelity decided by Forge
+ Team update (2026-02-25): Nav audit merged 4 missing components + 15 missing SubPages in ComponentCatalog.cs decided by Jubilee
+
+ Team update (2026-02-25): Consolidated audit reports now use `planning-docs/AUDIT-REPORT-M{N}.md` pattern for all milestone audits decided by Beast
+
+
+ Team update (2026-02-25): M12 introduces Migration Analysis Tool PoC (`bwfc-migrate` CLI, regex-based ASPX parsing, 3-phase roadmap) decided by Forge
+
diff --git a/.ai-team/agents/rogue/history.md b/.ai-team/agents/rogue/history.md
index da2c05db7..770cbbfc0 100644
--- a/.ai-team/agents/rogue/history.md
+++ b/.ai-team/agents/rogue/history.md
@@ -30,9 +30,6 @@ Wrote 44 bUnit tests for P0 base class changes: AccessKey (4), ToolTip (8), Imag
📌 Test pattern: BaseListControl.GetItems() applies DataTextFormatString to both static and data-bound items. AppendDataBoundItems=false replaces static items. When Items is null, static items always show. — Rogue
-📌 Test pattern: Menu Orientation tests require JSInterop.Mode = JSRuntimeMode.Loose and @using BlazorWebFormsComponents.Enums. Login control tests require AuthenticationStateProvider and NavigationManager mock services. — Rogue
-
-
📌 Test pattern: Menu Orientation tests require JSInterop.Mode = JSRuntimeMode.Loose and @using BlazorWebFormsComponents.Enums. Login control tests require AuthenticationStateProvider and NavigationManager mock services. — Rogue
Team update (2026-02-23): BaseDataBoundComponent now inherits BaseStyledComponent removed duplicate IStyle from 11 data controls decided by Cyclops
@@ -83,11 +80,26 @@ Wrote 41 bUnit tests across 6 new test files for P2 features:
📌 Test pattern: Menu Orientation tests require JSInterop.Mode = JSRuntimeMode.Loose and @using BlazorWebFormsComponents.Enums. Login control tests require AuthenticationStateProvider and NavigationManager mock services. — Rogue
- Team update (2026-02-23): BaseDataBoundComponent now inherits BaseStyledComponent removed duplicate IStyle from 11 data controls decided by Cyclops
- Team update (2026-02-23): BaseListControl introduced as shared base for 5 list controls (DataTextFormatString, AppendDataBoundItems) decided by Cyclops
- Team update (2026-02-23): CausesValidation/ValidationGroup added to CheckBox, RadioButton, TextBox decided by Cyclops
- Team update (2026-02-23): Label AssociatedControlID switches rendered element (label vs span) decided by Cyclops
- Team update (2026-02-23): Milestone 6 Work Plan ratified 54 WIs across P0/P1/P2 tiers decided by Forge
-
Team update (2026-02-24): M8 scope excludes version bump to 1.0 and release decided by Jeffrey T. Fritz
Team update (2026-02-24): PagerSettings shared sub-component created may need bUnit tests decided by Cyclops
+
+### Milestone 9:Migration Fidelity QA — WI-02 + WI-06
+
+Wrote 24 bUnit tests across 2 files for migration fidelity work:
+
+**ToolTipTests.razor (WI-02, 20 tests):** Extended existing file with 20 new tests. 9 controls gained ToolTip from BaseStyledComponent: Label (span title), TextBox (input title via CalculatedAttributes), CheckBox (span title), RadioButton (span title), Panel (div title), Table (table title), DropDownList (select title), ListBox (select title), LinkButton (a title). Each tested with ToolTip present and absent. HyperLink regression test added. All 3 regression controls (Button, Image, HyperLink) confirmed working after base class move.
+
+**CommaSplitTests.razor (WI-06, 4 tests):** Validation message format is `Text,ErrorMessage\x1F ValidationGroup`. The comma-split fix uses `IndexOf(',')` + `Substring()` instead of `Split(',')` so commas in ErrorMessage are preserved. Tests: single comma in message, multiple commas, no commas, empty ErrorMessage. All pass.
+
+📌 Test pattern: Validation messages are stored as `Text + "," + ErrorMessage + "\x1F" + ValidationGroup` by BaseValidator. AspNetValidationSummary extracts ErrorMessage using first-comma split (IndexOf + Substring). ErrorMessage with commas is preserved correctly. — Rogue
+
+📌 Test pattern: ToolTip renders as `title` attribute on the outermost element. TextBox adds it via CalculatedAttributes dictionary (not direct markup). CheckBox/RadioButton render title on the wrapping `` when Text is present, on ` ` when no Text. Panel renders on `` (no GroupingText) or `
` (with GroupingText). — Rogue
+
+ Team update (2026-02-25): ToolTip moved to BaseStyledComponent (28+ controls), ValidationSummary comma-split fixed, SkinID boolstring fixed decided by Cyclops
+ Team update (2026-02-25): M9 plan ratified 12 WIs, migration fidelity decided by Forge
+
+ Team update (2026-02-25): TreeView NodeImage now checks ShowExpandCollapse independently of ShowLines; ExpandCollapseImage() helper added (#361) decided by Cyclops
+
+
+ Team update (2026-02-25): M12 introduces Migration Analysis Tool PoC (`bwfc-migrate` CLI, regex-based ASPX parsing, 3-phase roadmap) decided by Forge
+
diff --git a/.ai-team/decisions.md b/.ai-team/decisions.md
index 89302b645..25e7c2bad 100644
--- a/.ai-team/decisions.md
+++ b/.ai-team/decisions.md
@@ -449,12 +449,13 @@ Suggested timeline:
**What:** Going forward, use "milestones" instead of "sprints" for naming work batches. All future planning uses "milestone" terminology.
**Why:** User preference — captured for team memory. Applies retroactively to planning references where practical.
-### 2026-02-23: AccessKey and ToolTip must be added to BaseStyledComponent (consolidated)
+### 2026-02-23: AccessKey must be added to BaseStyledComponent
**By:** Beast, Cyclops
-**What:** `AccessKey` (string) and `ToolTip` (string) are missing from all styled controls. Both are standard `WebControl` properties present on every control inheriting `WebControl` in Web Forms. Beast's audit of 15 editor controls (L–X) and Cyclops's audit of 13 editor controls (A–I) independently confirmed the gap. 7 of 13 A–I controls add ToolTip individually; the remaining 6 lack it entirely.
-**Recommendation:** Add `[Parameter] public string AccessKey { get; set; }` and `[Parameter] public string ToolTip { get; set; }` to `BaseStyledComponent`. This fixes the gap for all 20+ styled controls in one change.
+**What:** `AccessKey` (string) is missing from all styled controls. Standard `WebControl` property. Beast's audit of 15 editor controls (L–X) and Cyclops's audit of 13 editor controls (A–I) independently confirmed the gap.
+**Recommendation:** Add `[Parameter] public string AccessKey { get; set; }` to `BaseStyledComponent`.
**Why:** Universal gap confirmed by two independent audits across 28 controls. Base-class fix is the highest-leverage single change available.
+**Status:** AccessKey added in Milestone 6. ToolTip consolidated into 2026-02-25 entry below.
### 2026-02-23: Label should inherit BaseStyledComponent
@@ -462,12 +463,11 @@ Suggested timeline:
**What:** `Label` currently inherits `BaseWebFormsComponent` but Web Forms `Label` inherits from `WebControl`. This means Label is missing all 9 style properties (CssClass, BackColor, ForeColor, Font, etc.). Also consider `AssociatedControlID` to render `` instead of ``.
**Why:** Label is one of the most commonly used controls. Missing style properties break migration for any page that styles its Labels.
-### 2026-02-23: Substitution and Xml remain permanently deferred
+### 2026-02-25: Substitution and Xml permanently deferred (consolidated)
**By:** Beast
-**What:** Both controls are tightly coupled to server-side ASP.NET infrastructure: Substitution depends on the output caching pipeline (`HttpResponse.WriteSubstitution`); Xml depends on XSLT transformation. Neither maps to Blazor's component model.
-**Recommendation:** Document migration alternatives in `DeferredControls.md`. Substitution → Blazor component lifecycle. Xml → convert XML to C# objects and use Blazor templates.
-**Why:** Reinforces and formalizes the Sprint 3 deferral decision with specific migration guidance.
+**What:** Both controls are tightly coupled to server-side ASP.NET infrastructure: Substitution depends on the output caching pipeline; Xml depends on XSLT transformation. Neither maps to Blazor's component model. Both are formally marked as deferred in status.md and README.md. DeferredControls.md contains migration guidance: Substitution -> Blazor component lifecycle; Xml -> convert XML to C# objects and use Blazor templates.
+**Why:** Architecturally incompatible with Blazor. Marking as deferred (not "Not Started") accurately communicates they will not be implemented.
### 2026-02-23: Chart Type Gallery documentation convention
@@ -540,12 +540,12 @@ Suggested timeline:
**Recommendation:** Add `Display` parameter to `BaseValidator`.
**Why:** Migration-blocking for pages using `Display="Static"`.
-### 2026-02-23: ValidationSummary functional gaps and comma-split bug
+### 2026-02-23: ValidationSummary functional gaps
**By:** Rogue
-**What:** `AspNetValidationSummary` is missing `HeaderText`, `ShowMessageBox`, `ShowSummary`, `ShowValidationErrors`, and `ValidationGroup`. Error message parsing uses `x.Split(',')[1]` which silently corrupts messages containing commas.
-**Recommendation:** Fix comma-split bug immediately (data corruption risk). Prioritize `HeaderText` and `ValidationGroup` (common in multi-form pages).
-**Why:** Comma-split is a latent data corruption bug. Missing properties affect multi-form page migration.
+**What:** `AspNetValidationSummary` is missing `HeaderText`, `ShowMessageBox`, `ShowSummary`, `ShowValidationErrors`, and `ValidationGroup`. Prioritize `HeaderText` and `ValidationGroup` (common in multi-form pages).
+**Why:** Missing properties affect multi-form page migration.
+**Status:** Comma-split bug fixed in M9 (2026-02-25) — see consolidated entry below.
### 2026-02-23: Login controls outer style properties (consolidated)
@@ -1344,12 +1344,6 @@ All P2 features (WI-47 through WI-52) have been tested with 32 bUnit tests acros
Team should be aware that Login/ChangePassword/CreateUserWizard BaseStyledComponent inheritance was already in place — WI-52's implementation may have been a no-op or only required template changes to wire `Style`/`CssClass` to the outer element.
-### Substitution and Xml formally deferred
-
-**By:** Beast
-**What:** Substitution and Xml controls are now formally marked as "⏸️ Deferred" (not "Not Started") in status.md. DeferredControls.md already had migration guidance; status.md and README.md now reflect the permanent deferral. Chart is marked as fully complete with no "Phase 1" qualifier.
-**Why:** These two controls are architecturally incompatible with Blazor (Substitution relies on output caching; Xml relies on XSLT transforms). Marking them as deferred rather than "Not Started" accurately communicates that they will not be implemented, and clears the remaining work count to 0.
-
# Decision: M7 Integration Tests Added (WI-39 + WI-40)
**Author:** Colossus
@@ -1924,3 +1918,78 @@ Removed the `@rendermode InteractiveServer` directive. No other sample page in t
- `dotnet build samples/AfterBlazorClientSide/ --configuration Release` — ✅ passes
- `dotnet build samples/AfterBlazorServerSide/ --configuration Release` — ✅ passes
- `dotnet test src/BlazorWebFormsComponents.Test/ --no-restore` — ✅ passes
+
+### 2026-02-25: Deployment pipeline patterns for Docker versioning, Azure webhook, and NuGet publishing
+**By:** Forge
+**What:** Established three CI/CD patterns: (1) Compute version with nbgv outside Docker build and inject via build-arg, since .dockerignore excludes .git. (2) Gate optional deployment steps on repository secrets with `if: ${{ secrets.SECRET_NAME != '' }}` so workflows don't fail when secrets aren't configured. (3) Dual NuGet publishing always push to GitHub Packages, conditionally push to nuget.org.
+**Why:** The .dockerignore excluding .git is a structural constraint that won't change (it's correct for build performance). Secret-gating ensures the workflows work in forks and PRs where secrets aren't available. Dual NuGet publishing gives us private (GitHub) and public (nuget.org) distribution without duplicating the pack step. These patterns should be followed for any future workflow additions.
+
+### 2026-02-25: Milestone 9 Plan Migration Fidelity & Hardening
+
+**By:** Forge
+**What:** Milestone 9 ratified: 12 WIs, ~30 gap closures. P0: ToolTip BaseStyledComponent (4 WIs, ~28 gaps). P1: ValidationSummary comma-split fix, SkinID boolstring fix (3 WIs). P2: Branch cleanup, doc audit, planning-docs headers, integration test review, nav audit (5 WIs). 7 of 8 prior audit gaps already fixed; 1 confirmed open + 2 newly identified.
+**Why:** ToolTip base class fix has highest blast radius (~28 controls). ValidationSummary is data corruption risk. SkinID type mismatch breaks compiled migration code. Full plan at `planning-docs/MILESTONE9-PLAN.md`.
+
+### 2026-02-25: ToolTip belongs on BaseStyledComponent (consolidated)
+
+**By:** Beast, Cyclops (original audit: 2026-02-23), Cyclops (implementation: 2026-02-25)
+**What:** `[Parameter] public string ToolTip { get; set; }` added to `BaseStyledComponent`. Removed 8 duplicate declarations (Button, Calendar, DataList, FileUpload, HyperLink, Image, ImageButton, ImageMap). 32 templates updated with `title="@ToolTip"`. Sub-component types (ChartSeries, DataPoint, MenuItem, TreeNode) keep their own ToolTip (item-level semantics). All 28+ styled controls now inherit ToolTip automatically.
+**Why:** Web Forms `WebControl.ToolTip` is defined at base class level. Two independent audits (Beast LX, Cyclops AI) confirmed universal gap. Base-class fix is highest-leverage single change.
+
+### 2026-02-25: ValidationSummary comma-split fixed (consolidated)
+
+**By:** Rogue (bug identification: 2026-02-23), Cyclops (fix: 2026-02-25)
+**What:** `AspNetValidationSummary.ValidationMessages` now uses `IndexOf(',')` + `Substring()` instead of `Split(',')[1]` to extract error messages. The field identifier is always before the first comma; everything after is the message.
+**Why:** Error messages containing commas were silently truncated data corruption bug. Original audit by Rogue identified the issue; Cyclops implemented the fix.
+
+### 2026-02-25: SkinID is a string, not a bool
+
+**By:** Cyclops
+**What:** `BaseWebFormsComponent.SkinID` type changed from `bool` to `string`. `[Obsolete]` attribute preserved.
+**Why:** Web Forms `Control.SkinID` is a string containing the skin name. Boolean type breaks any migration code setting `SkinID="MySkin"`.
+
+### 2026-02-25: Documentation gap audit M6-M8 features (WI-09)
+
+**By:** Beast
+**What:** Audited docs against M6-M8 features. Fully documented: GridView, TreeView, Menu, Validators, Login. Gaps found: FormView (ItemCommand, styles, PagerSettings not in Blazor sections), DetailsView (Caption missing, styles/PagerSettings possibly stale), DataGrid (paging status unclear), ChangePassword (Orientation/TextLayout undocumented), PagerSettings (no dedicated doc page).
+**Why:** Ensures documentation accuracy before 1.0. Gaps prioritized P1-P3.
+
+### 2026-02-25: Planning-docs marked as historical snapshots (WI-10)
+
+**By:** Beast
+**What:** Added historical snapshot headers to all 54 per-control audit files and SUMMARY.md in `planning-docs/`. Excluded README.md and MILESTONE*-PLAN.md (still current).
+**Why:** Prevents future contributors from treating pre-M6 gap data as current.
+
+### 2026-02-25: Integration test coverage audit (WI-11)
+
+**By:** Colossus
+**What:** 100 of 105 sample page routes covered by smoke tests. 5 gaps: ListView CrudOperations (M7, P0), Label, Panel/BackImageUrl, LoginControls/Orientation, DataGrid/Styles (all pre-M7, P1). 9 of 10 M7 features have full coverage (smoke + interaction). 57 interaction tests exist.
+**Why:** Read-only audit to identify test coverage gaps before 1.0.
+
+### 2026-02-25: Sample site navigation audit (WI-12)
+
+**By:** Jubilee
+**What:** 4 components missing entirely from ComponentCatalog.cs (Menu, DataBinder, PasswordRecovery, ViewState). 15 SubPages missing across GridView (5), TreeView (2), FormView (3), DetailsView (2), ListView (1), DataGrid (1), Panel (1). DataList "Flow" SubPage name mismatch. All 10 M7/M8 feature pages exist on disk but none linked in sidebar.
+**Why:** Users cannot discover sample pages that aren't in the sidebar navigation.
+
+
+
+### 2026-02-25: Consolidated audit reports use `planning-docs/AUDIT-REPORT-M{N}.md`
+
+**By:** Beast
+**What:** When multiple audits are conducted in a milestone, their findings should be consolidated into a single report at `planning-docs/AUDIT-REPORT-M{N}.md`. The report follows the planning-docs historical snapshot header convention and includes: summary table, per-audit sections (findings + resolution status), additional findings section, and a complete issue tracker appendix. Each finding is mapped to its resolving GitHub Issue with assigned agent.
+**Why:** M9 produced three separate audits (doc gaps, test coverage, sample navigation) with findings scattered across agent history files. A consolidated report makes it easy for Jeff and the team to see all findings in one place, track resolution against M10 issues, and verify coverage. This pattern should be reused for future milestone audits.
+
+### 2026-02-25: TreeView NodeImage must check ShowExpandCollapse independently of ShowLines
+
+**By:** Cyclops
+**Date:** 2026-02-25
+**Issue:** #361
+**What:** The `NodeImage` property in `TreeNode.razor.cs` now explicitly checks `ShowExpandCollapse` in the non-`ShowLines` code paths, rather than relying on `ImageSet.Collapse` being non-empty. A new `ExpandCollapseImage(bool)` helper provides the ImageSet filename with a guaranteed fallback to `Default_Collapse.gif` / `Default_Expand.gif`.
+**Why:** The previous code had a fragile assumption: it used `string.IsNullOrEmpty(ImageSet.Collapse)` as a proxy for "should I show expand/collapse images." The fix makes the intent explicit `ShowExpandCollapse` controls whether expand/collapse images are used, and the ImageSet only controls *which* images. All 51 TreeView tests pass.
+
+### 2026-02-25: Migration Analysis Tool PoC architecture
+**By:** Forge
+**What:** Milestone 12 introduces a Migration Analysis Tool as a CLI (`bwfc-migrate`) in the same repo at `src/BlazorWebFormsComponents.MigrationAnalysis/`. The PoC uses regex-based ASPX parsing (not Roslyn) to extract `` controls, maps them against a registry of all 53 planned BWFC components + ~15 known unsupported controls, analyzes code-behind patterns via regex, scores page complexity (Green/Yellow/Red), and produces Markdown + JSON reports. Packaged as a `dotnet tool`. Three-phase roadmap: M12 = analysis + CLI, Phase 2 = Roslyn + scaffolding, Phase 3 = Copilot agent. 13 work items total.
+**Why:** At 51/53 components complete, the component library is mature. The highest-leverage remaining work is helping developers evaluate and execute migrations using the components we already built. Same-repo placement keeps the control mapping table in sync with the actual component library. Regex over Roslyn prevents scope creep in the PoC — Roslyn is explicitly Phase 2. The tool transforms BlazorWebFormsComponents from a component library into a migration platform.
+
diff --git a/.ai-team/log/2026-02-25-v014-release.md b/.ai-team/log/2026-02-25-v014-release.md
new file mode 100644
index 000000000..3adf0337a
--- /dev/null
+++ b/.ai-team/log/2026-02-25-v014-release.md
@@ -0,0 +1,25 @@
+# v0.14 Release — Final
+
+- **Date:** 2026-02-25
+- **Requested by:** Jeffrey T. Fritz
+
+## What happened
+
+- Fixed 2 broken links in docs:
+ - `ImageMap.md`: corrected HyperLink reference
+ - `Custom-Controls.md`: corrected analyzer path
+- PR #346 created and merged to upstream/dev (docs CI fix)
+- PR #345 (dev → main) passed all CI checks:
+ - Build
+ - Integration Tests
+ - docs
+ - CodeQL
+ - Build Demo Sites
+- PR #345 merged to upstream/main
+- v0.14 release created on FritzAndFriends/BlazorWebFormsComponents with tag
+ - https://github.com/FritzAndFriends/BlazorWebFormsComponents/releases/tag/v0.14
+- Local branches synced: main and dev pushed to origin
+
+## Decisions
+
+- None new. Release completed as planned.
diff --git a/.github/workflows/deploy-server-side.yml b/.github/workflows/deploy-server-side.yml
index b85d53519..e8509ab01 100644
--- a/.github/workflows/deploy-server-side.yml
+++ b/.github/workflows/deploy-server-side.yml
@@ -29,6 +29,19 @@ jobs:
with:
fetch-depth: 0
+ - name: Setup .NET for version computation
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '10.0.x'
+
+ - name: Compute version with nbgv
+ id: nbgv
+ run: |
+ dotnet tool install --global nbgv
+ VERSION=$(nbgv get-version -v NuGetPackageVersion)
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
+ echo "Computed version: $VERSION"
+
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
@@ -45,6 +58,17 @@ jobs:
context: .
file: samples/AfterBlazorServerSide/Dockerfile
push: true
+ build-args: |
+ VERSION=${{ steps.nbgv.outputs.version }}
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.nbgv.outputs.version }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
+
+ - name: Trigger Azure App Service deployment
+ if: ${{ secrets.AZURE_WEBAPP_WEBHOOK_URL != '' }}
+ run: |
+ curl -sf -X POST "${{ secrets.AZURE_WEBAPP_WEBHOOK_URL }}" \
+ -H "Content-Length: 0" \
+ --max-time 60 \
+ || echo "::warning::Azure webhook call failed — the App Service may need to be updated manually"
diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml
index fa97950d3..1207bb382 100644
--- a/.github/workflows/nuget.yml
+++ b/.github/workflows/nuget.yml
@@ -40,6 +40,10 @@ jobs:
- name: Push to GitHub Packages
run: dotnet nuget push ./nupkg/*.nupkg --source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate
+ - name: Push to nuget.org
+ if: ${{ secrets.NUGET_API_KEY != '' }}
+ run: dotnet nuget push ./nupkg/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate
+
- name: Upload NuGet package artifact
uses: actions/upload-artifact@v4
with:
diff --git a/docs/DataControls/GridView.md b/docs/DataControls/GridView.md
index 7f9f5efd2..b18c015af 100644
--- a/docs/DataControls/GridView.md
+++ b/docs/DataControls/GridView.md
@@ -10,6 +10,7 @@ The GridView component is meant to emulate the asp:GridView control in markup an
- **Selection** - `SelectedIndex`, `SelectedRow`, `SelectedValue`, `AutoGenerateSelectButton`, `SelectedIndexChanging`/`SelectedIndexChanged` events
- **Style Sub-Components** - `RowStyle`, `AlternatingRowStyle`, `HeaderStyle`, `FooterStyle`, `SelectedRowStyle`, `EditRowStyle`, `EmptyDataRowStyle`, `PagerStyle`
- **Display Properties** - `ShowHeader`, `ShowFooter`, `Caption`, `CaptionAlign`, `EmptyDataTemplate`, `GridLines`, `UseAccessibleHeader`, `CellPadding`, `CellSpacing`, `ShowHeaderWhenEmpty`
+- `ToolTip` - tooltip text displayed on hover (renders as `title` attribute)
### Blazor Notes
diff --git a/docs/EditorControls/Label.md b/docs/EditorControls/Label.md
index 95e079ce3..f10341a1d 100644
--- a/docs/EditorControls/Label.md
+++ b/docs/EditorControls/Label.md
@@ -3,6 +3,7 @@ It may seem strange that we have a Label component when there already is an HTML
## Blazor Features Supported
- `Text` the text content of the Label component
+- `ToolTip` - tooltip text displayed on hover (renders as `title` attribute)
## WebForms Syntax
diff --git a/docs/EditorControls/TextBox.md b/docs/EditorControls/TextBox.md
index 6e878d1f3..1eed8f6f1 100644
--- a/docs/EditorControls/TextBox.md
+++ b/docs/EditorControls/TextBox.md
@@ -16,6 +16,7 @@ Original Microsoft documentation: https://docs.microsoft.com/en-us/dotnet/api/sy
- Rows and Columns for sizing
- Placeholder text
- All style properties (BackColor, ForeColor, BorderColor, CssClass, Width, Height, etc.)
+- `ToolTip` - tooltip text displayed on hover (renders as `title` attribute)
- TabIndex support
## Web Forms Features NOT Supported
diff --git a/docs/Migration/readme.md b/docs/Migration/readme.md
index 76f04efa3..490b183ef 100644
--- a/docs/Migration/readme.md
+++ b/docs/Migration/readme.md
@@ -148,6 +148,21 @@ This pattern applies to all templated data controls:
**Note:** While it's one extra attribute per component, using `Context="Item"` eliminates the need to learn Blazor-specific `@context` syntax and makes your templates more readable for developers familiar with Web Forms.
+## Common Properties on All Styled Controls
+
+All BlazorWebFormsComponents controls that inherit from `BaseStyledComponent` support a set of common properties that match ASP.NET Web Forms' `WebControl` base class. This means properties like `CssClass`, `BackColor`, `ForeColor`, `Font`, `Width`, `Height`, `BorderColor`, `BorderStyle`, `BorderWidth`, `Enabled`, `Visible`, and **`ToolTip`** are available on every styled control — just as they are in Web Forms.
+
+!!! tip "ToolTip renders as the HTML `title` attribute"
+ The `ToolTip` property renders as the standard HTML `title` attribute, matching Web Forms behavior. This means browser-native tooltip display works automatically.
+
+```razor
+
+
+
+```
+
+You do **not** need to add `ToolTip` support per-control — it is inherited from the shared base and works on all styled controls including Button, TextBox, Label, Panel, CheckBox, DropDownList, GridView, and more.
+
## Step 6 - Pages
Page migration is a process very similar to UserControls, except working on files with the `.aspx` extension.
diff --git a/planning-docs/AUDIT-REPORT-M9.md b/planning-docs/AUDIT-REPORT-M9.md
new file mode 100644
index 000000000..3889f90a3
--- /dev/null
+++ b/planning-docs/AUDIT-REPORT-M9.md
@@ -0,0 +1,134 @@
+> ⚠️ **Historical Snapshot (Milestone 9):** This report consolidates findings from three M9 audits conducted 2026-02-25. Resolution is tracked against Milestone 10 GitHub Issues. For current status, see `status.md` and active milestone plans.
+
+# Milestone 9 Audit Report
+
+> Consolidated findings from three M9 audits. Status tracked against Milestone 10 GitHub Issues.
+
+## Summary
+
+| Audit | Findings | Issues Created | Coverage |
+|-------|----------|----------------|----------|
+| Doc Gaps (Beast, WI-09) | 5 | 1 | 100% |
+| Integration Tests (Colossus, WI-11) | 5 | 1 | 100% |
+| Sample Navigation (Jubilee, WI-12) | 19 | 1 | 100% |
+| **Total** | **29** | **3 (direct) + 6 related** | **100%** |
+
+---
+
+## 1. Documentation Gap Audit (Beast, WI-09)
+
+Beast audited all component documentation pages against features added in Milestones 6–8. GridView, TreeView, Menu, Validators (ControlToValidate), and Login docs were found fully current.
+
+### Findings
+
+| # | Component | Gap Description | Severity |
+|---|-----------|-----------------|----------|
+| D1 | FormView | `ItemCommand` event, styles, and `PagerSettings` not documented in Blazor sections | Medium |
+| D2 | DetailsView | `Caption` missing from docs; styles/PagerSettings listed as unsupported but may be stale after M7 additions | Medium |
+| D3 | DataGrid | Paging listed as unsupported — needs verification against current implementation | Low |
+| D4 | ChangePassword | `Orientation` and `TextLayout` not documented despite Login.md having full coverage of both | Medium |
+| D5 | PagerSettings | No dedicated documentation page exists for the shared sub-component | Low |
+
+### Resolution Status
+
+| Finding | GitHub Issue | Assignee | Status |
+|---------|-------------|----------|--------|
+| D1–D5 (all 5 gaps) | [#359](../../issues/359) — Update 5 doc pages with M6-M8 feature additions | Beast | 🟡 Open |
+
+---
+
+## 2. Integration Test Coverage Audit (Colossus, WI-11)
+
+Colossus audited all sample page `@page` routes against `ControlSampleTests.cs` and `InteractiveComponentTests.cs`. Found 105 sample routes total; 100 covered by smoke tests, 57 interaction tests exist.
+
+### Findings
+
+| # | Sample Page | Route | Priority | Notes |
+|---|-------------|-------|----------|-------|
+| T1 | ListView/CrudOperations | `/ControlSamples/ListView/CrudOperations` | P0 — Highest | M7 feature page, no smoke test |
+| T2 | Label | `/ControlSamples/Label` | P1 | M6 sample page, no smoke test |
+| T3 | Panel/BackImageUrl | `/ControlSamples/Panel/BackImageUrl` | P1 | Missing smoke test |
+| T4 | LoginControls/Orientation | `/ControlSamples/LoginControls/Orientation` | P1 | Missing smoke test |
+| T5 | DataGrid/Styles | `/ControlSamples/DataGrid/Styles` | P2 | Missing smoke test |
+
+All M7 features with interaction tests (GridView Selection/DisplayProperties, TreeView Selection/ExpandCollapse, Menu Selection, FormView Events/Styles, DetailsView Styles/Caption) have full coverage.
+
+### Resolution Status
+
+| Finding | GitHub Issue | Assignee | Status |
+|---------|-------------|----------|--------|
+| T1–T5 (all 5 gaps) | [#358](../../issues/358) — Add 5 missing integration smoke tests | Colossus | 🟡 Open |
+
+---
+
+## 3. Sample Navigation Audit (Jubilee, WI-12)
+
+Jubilee audited sidebar navigation driven by `ComponentCatalog.cs`. Found that `NavMenu.razor` iterates over the catalog with SubPages support — any component or SubPage missing from the catalog is invisible in sidebar navigation.
+
+### Findings
+
+**4 components completely missing from ComponentCatalog.cs:**
+
+| # | Component | Category | Impact |
+|---|-----------|----------|--------|
+| N1 | Menu | Navigation | Invisible in sidebar — no way to reach Menu samples |
+| N2 | DataBinder | Utility | Invisible in sidebar |
+| N3 | PasswordRecovery | Login | Invisible in sidebar |
+| N4 | ViewState | Utility | Invisible in sidebar |
+
+**15 SubPage entries missing across 7 components:**
+
+| # | Component | Missing SubPages | Count |
+|---|-----------|-----------------|-------|
+| N5 | GridView | Selection, DisplayProperties, Sorting, ColumnTypes, Events | 5 |
+| N6 | TreeView | Selection, ExpandCollapse | 2 |
+| N7 | FormView | Events, Styles, PagerSettings | 3 |
+| N8 | DetailsView | Styles, Caption | 2 |
+| N9 | ListView | CrudOperations | 1 |
+| N10 | DataGrid | Styles | 1 |
+| N11 | Panel | BackImageUrl | 1 |
+
+**Additional:** DataList has a SubPage name mismatch — catalog says "Flow" but file is `SimpleFlow.razor`. Some pages are partially reachable via in-page `Nav.razor` components, but TreeView Selection/ExpandCollapse and DetailsView Styles/Caption have no nav links at all.
+
+**Total unreachable pages: 19** (4 missing components + 15 missing SubPages)
+
+### Resolution Status
+
+| Finding | GitHub Issue | Assignee | Status |
+|---------|-------------|----------|--------|
+| N1–N11 (all 19 pages) | [#350](../../issues/350) — Fix 19 unreachable sample pages in ComponentCatalog.cs | Jubilee | 🟡 Open |
+
+---
+
+## 4. Additional Findings (Post-M9)
+
+These issues were discovered during M9 work but fall outside the three formal audits:
+
+| Finding | Description | GitHub Issue | Assignee |
+|---------|-------------|-------------|----------|
+| TreeView caret bug | Caret does not rotate on expand/collapse | [#361](../../issues/361) | Cyclops |
+| Panel.BackImageUrl | Property not implemented | [#351](../../issues/351) | Cyclops |
+| LoginView base class | Needs migration to BaseStyledComponent | [#352](../../issues/352) | Cyclops |
+| PasswordRecovery base class | Needs migration to BaseStyledComponent | [#354](../../issues/354) | Cyclops |
+| ListView CRUD events | 16 missing events | [#356](../../issues/356) | Cyclops |
+| Menu level styles | DynamicMenuStyle, StaticMenuStyle not implemented | [#360](../../issues/360) | Cyclops |
+
+---
+
+## Appendix: Complete M10 Issue Tracker
+
+| Issue # | Title | Assigned Agent | Audit Source |
+|---------|-------|----------------|-------------|
+| [#350](../../issues/350) | Fix 19 unreachable sample pages in ComponentCatalog.cs | Jubilee | Navigation Audit (WI-12) |
+| [#351](../../issues/351) | Add Panel.BackImageUrl property | Cyclops | Component gap |
+| [#352](../../issues/352) | Migrate LoginView to BaseStyledComponent | Cyclops | Component gap |
+| [#354](../../issues/354) | Migrate PasswordRecovery to BaseStyledComponent | Cyclops | Component gap |
+| [#356](../../issues/356) | Implement ListView CRUD events (16 missing) | Cyclops | Component gap |
+| [#358](../../issues/358) | Add 5 missing integration smoke tests | Colossus | Test Audit (WI-11) |
+| [#359](../../issues/359) | Update 5 doc pages with M6-M8 feature additions | Beast | Doc Audit (WI-09) |
+| [#360](../../issues/360) | Implement Menu level styles (DynamicMenuStyle, StaticMenuStyle) | Cyclops | Component gap |
+| [#361](../../issues/361) | TreeView caret does not rotate on expand/collapse | Cyclops | Bug (post-M9) |
+
+---
+
+*Report generated by Beast (Technical Writer) — Milestone 9 audit consolidation.*
diff --git a/planning-docs/AdRotator.md b/planning-docs/AdRotator.md
index 962c5ac0b..ee454711e 100644
--- a/planning-docs/AdRotator.md
+++ b/planning-docs/AdRotator.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# AdRotator — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.adrotator?view=netframework-4.8
diff --git a/planning-docs/BulletedList.md b/planning-docs/BulletedList.md
index 5d8417fd0..a1d1a19cd 100644
--- a/planning-docs/BulletedList.md
+++ b/planning-docs/BulletedList.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# BulletedList — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.bulletedlist?view=netframework-4.8
diff --git a/planning-docs/Button.md b/planning-docs/Button.md
index 4e6a24aed..6d3935147 100644
--- a/planning-docs/Button.md
+++ b/planning-docs/Button.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Button — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.button?view=netframework-4.8
diff --git a/planning-docs/Calendar.md b/planning-docs/Calendar.md
index 057d76e57..833390aaa 100644
--- a/planning-docs/Calendar.md
+++ b/planning-docs/Calendar.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Calendar — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.calendar?view=netframework-4.8
diff --git a/planning-docs/ChangePassword.md b/planning-docs/ChangePassword.md
index 3fb34042c..5976bbefe 100644
--- a/planning-docs/ChangePassword.md
+++ b/planning-docs/ChangePassword.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# ChangePassword — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.changepassword?view=netframework-4.8
diff --git a/planning-docs/Chart.md b/planning-docs/Chart.md
index b31881161..01fb05f99 100644
--- a/planning-docs/Chart.md
+++ b/planning-docs/Chart.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Chart — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.datavisualization.charting.chart?view=netframework-4.8.1
diff --git a/planning-docs/CheckBox.md b/planning-docs/CheckBox.md
index c05998323..8334f0bd9 100644
--- a/planning-docs/CheckBox.md
+++ b/planning-docs/CheckBox.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# CheckBox — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.checkbox?view=netframework-4.8
diff --git a/planning-docs/CheckBoxList.md b/planning-docs/CheckBoxList.md
index 954518922..a0360652a 100644
--- a/planning-docs/CheckBoxList.md
+++ b/planning-docs/CheckBoxList.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# CheckBoxList — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.checkboxlist?view=netframework-4.8
diff --git a/planning-docs/CompareValidator.md b/planning-docs/CompareValidator.md
index 7b63a89ff..b576d4f70 100644
--- a/planning-docs/CompareValidator.md
+++ b/planning-docs/CompareValidator.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# CompareValidator — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.comparevalidator?view=netframework-4.8
diff --git a/planning-docs/CreateUserWizard.md b/planning-docs/CreateUserWizard.md
index 90eb17275..24a7bf060 100644
--- a/planning-docs/CreateUserWizard.md
+++ b/planning-docs/CreateUserWizard.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# CreateUserWizard — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.createuserwizard?view=netframework-4.8
diff --git a/planning-docs/CustomValidator.md b/planning-docs/CustomValidator.md
index 0d9672e1f..f332e9339 100644
--- a/planning-docs/CustomValidator.md
+++ b/planning-docs/CustomValidator.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# CustomValidator — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.customvalidator?view=netframework-4.8
diff --git a/planning-docs/DataGrid.md b/planning-docs/DataGrid.md
index 77ea1cd23..ba75bc442 100644
--- a/planning-docs/DataGrid.md
+++ b/planning-docs/DataGrid.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# DataGrid — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.datagrid?view=netframework-4.8.1
diff --git a/planning-docs/DataList.md b/planning-docs/DataList.md
index 4547e2d47..d5558b601 100644
--- a/planning-docs/DataList.md
+++ b/planning-docs/DataList.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# DataList — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.datalist?view=netframework-4.8.1
diff --git a/planning-docs/DataPager.md b/planning-docs/DataPager.md
index 81acdb57d..7df85894a 100644
--- a/planning-docs/DataPager.md
+++ b/planning-docs/DataPager.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# DataPager — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.datapager?view=netframework-4.8.1
diff --git a/planning-docs/DetailsView.md b/planning-docs/DetailsView.md
index eb3d33854..51c22df01 100644
--- a/planning-docs/DetailsView.md
+++ b/planning-docs/DetailsView.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# DetailsView — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.detailsview?view=netframework-4.8.1
diff --git a/planning-docs/DropDownList.md b/planning-docs/DropDownList.md
index cc9e1f7dd..fe9fa4ef5 100644
--- a/planning-docs/DropDownList.md
+++ b/planning-docs/DropDownList.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# DropDownList — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.dropdownlist?view=netframework-4.8
diff --git a/planning-docs/FileUpload.md b/planning-docs/FileUpload.md
index a1f875373..d83648d7f 100644
--- a/planning-docs/FileUpload.md
+++ b/planning-docs/FileUpload.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# FileUpload — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.fileupload?view=netframework-4.8
diff --git a/planning-docs/FormView.md b/planning-docs/FormView.md
index c07ef8da2..33c8ff331 100644
--- a/planning-docs/FormView.md
+++ b/planning-docs/FormView.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# FormView — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.formview?view=netframework-4.8.1
diff --git a/planning-docs/GridView.md b/planning-docs/GridView.md
index cbad2c2e8..360b8e0ca 100644
--- a/planning-docs/GridView.md
+++ b/planning-docs/GridView.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# GridView — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.gridview?view=netframework-4.8.1
diff --git a/planning-docs/HiddenField.md b/planning-docs/HiddenField.md
index 67997fbe3..fc5f423cf 100644
--- a/planning-docs/HiddenField.md
+++ b/planning-docs/HiddenField.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# HiddenField — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.hiddenfield?view=netframework-4.8
diff --git a/planning-docs/HyperLink.md b/planning-docs/HyperLink.md
index 2bbd6d014..55211aa4f 100644
--- a/planning-docs/HyperLink.md
+++ b/planning-docs/HyperLink.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# HyperLink — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.hyperlink?view=netframework-4.8
diff --git a/planning-docs/Image.md b/planning-docs/Image.md
index 0bc95c699..7a6e739d4 100644
--- a/planning-docs/Image.md
+++ b/planning-docs/Image.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Image — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.image?view=netframework-4.8
diff --git a/planning-docs/ImageButton.md b/planning-docs/ImageButton.md
index 1ed519410..396b748c6 100644
--- a/planning-docs/ImageButton.md
+++ b/planning-docs/ImageButton.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# ImageButton — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.imagebutton?view=netframework-4.8
diff --git a/planning-docs/ImageMap.md b/planning-docs/ImageMap.md
index 8ba4ce24b..d5d9a904c 100644
--- a/planning-docs/ImageMap.md
+++ b/planning-docs/ImageMap.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# ImageMap — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.imagemap?view=netframework-4.8
diff --git a/planning-docs/Label.md b/planning-docs/Label.md
index cc0dfb9cc..bf75f2a34 100644
--- a/planning-docs/Label.md
+++ b/planning-docs/Label.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Label — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.label?view=netframework-4.8
diff --git a/planning-docs/LinkButton.md b/planning-docs/LinkButton.md
index d026864d1..fa40853d8 100644
--- a/planning-docs/LinkButton.md
+++ b/planning-docs/LinkButton.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# LinkButton — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.linkbutton?view=netframework-4.8
diff --git a/planning-docs/ListBox.md b/planning-docs/ListBox.md
index 9e8d3f7a7..ae263cc90 100644
--- a/planning-docs/ListBox.md
+++ b/planning-docs/ListBox.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# ListBox — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.listbox?view=netframework-4.8
diff --git a/planning-docs/ListView.md b/planning-docs/ListView.md
index 38053eef6..6a771cc1c 100644
--- a/planning-docs/ListView.md
+++ b/planning-docs/ListView.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# ListView — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.listview?view=netframework-4.8.1
diff --git a/planning-docs/Literal.md b/planning-docs/Literal.md
index 2e6811554..8f8f97103 100644
--- a/planning-docs/Literal.md
+++ b/planning-docs/Literal.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Literal — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.literal?view=netframework-4.8
diff --git a/planning-docs/Localize.md b/planning-docs/Localize.md
index d720eea19..2e306f682 100644
--- a/planning-docs/Localize.md
+++ b/planning-docs/Localize.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Localize — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.localize?view=netframework-4.8
diff --git a/planning-docs/Login.md b/planning-docs/Login.md
index d1d3a8bdc..4d96e2394 100644
--- a/planning-docs/Login.md
+++ b/planning-docs/Login.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Login — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.login?view=netframework-4.8
diff --git a/planning-docs/LoginName.md b/planning-docs/LoginName.md
index c6aa60c14..143b520b8 100644
--- a/planning-docs/LoginName.md
+++ b/planning-docs/LoginName.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# LoginName — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.loginname?view=netframework-4.8
diff --git a/planning-docs/LoginStatus.md b/planning-docs/LoginStatus.md
index 1d4efaaeb..f04032d01 100644
--- a/planning-docs/LoginStatus.md
+++ b/planning-docs/LoginStatus.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# LoginStatus — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.loginstatus?view=netframework-4.8
diff --git a/planning-docs/LoginView.md b/planning-docs/LoginView.md
index 0d1673f8b..34fd8f6df 100644
--- a/planning-docs/LoginView.md
+++ b/planning-docs/LoginView.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# LoginView — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.loginview?view=netframework-4.8
diff --git a/planning-docs/MILESTONE12-PLAN.md b/planning-docs/MILESTONE12-PLAN.md
new file mode 100644
index 000000000..097aebc48
--- /dev/null
+++ b/planning-docs/MILESTONE12-PLAN.md
@@ -0,0 +1,345 @@
+# Milestone 12 — Migration Analysis Tool (PoC)
+
+**Created:** 2026-02-25
+**Author:** Forge (Lead / Web Forms Reviewer)
+**Branch:** `milestone12/migration-analysis-tool`
+**Baseline:** dev (post-M9, 51/53 components, 1200+ tests)
+
+---
+
+## 1. Vision — The Full Picture
+
+The **Migration Analysis Tool** is a CLI tool + Copilot agent extension that analyzes an existing ASP.NET Web Forms application and produces a detailed migration plan for moving to Blazor Server Interactive using BlazorWebFormsComponents.
+
+At maturity, the tool:
+
+1. **Scans** an entire Web Forms project: `.aspx` pages, `.aspx.cs` code-behind, `.ascx` user controls, `.master` master pages, `Web.config`, `Global.asax`, `App_Code/`, `.asmx` web services, `App_Themes/`
+2. **Identifies** every `` control and server-side construct used in the project
+3. **Maps** each control to its BlazorWebFormsComponents equivalent (or flags it as a gap)
+4. **Analyzes** code-behind patterns: data binding (`Eval()`, `Bind()`, strongly-typed `ItemType`), event handlers (`Page_Load`, `Button_Click`, postback patterns), session/viewstate usage, `HttpContext` dependencies, server-side includes, response redirects
+5. **Scores** each page by migration complexity: trivial (pure markup swap), moderate (needs code-behind refactoring), complex (architectural changes needed)
+6. **Recommends** migration order: easiest pages first, dependency-aware (shared user controls before pages that reference them)
+7. **Generates** scaffolded `.razor` files for trivial pages — strips `asp:` prefixes, converts directives, rewrites code-behind to partial class
+8. **Produces** a structured report (Markdown + JSON) suitable for project planning
+
+### Why This Matters
+
+We built 51 components that match Web Forms controls. But the actual migration workflow is still manual: a developer reads each `.aspx`, mentally maps controls, assesses code-behind, creates `.razor` files by hand. The biggest value we can add now is not more components — it's **reducing the friction of using the components we already have.**
+
+This tool is the natural next step for the BlazorWebFormsComponents ecosystem. It turns a library into a migration platform.
+
+---
+
+## 2. PoC Scope — Milestone 12 Deliverable
+
+The PoC is **not** the full vision. It demonstrates the core analysis pipeline on a representative Web Forms project and produces a readable report. No Roslyn analysis. No scaffolding. No Copilot agent integration. Those are post-PoC.
+
+### What the PoC DOES:
+
+| Capability | Description |
+|------------|-------------|
+| **ASPX parsing** | Parse `.aspx` files to extract `` control declarations with attributes |
+| **Control mapping** | Map each `` control to the BlazorWebFormsComponents equivalent using a hardcoded mapping table derived from our `status.md` |
+| **Gap identification** | Flag controls that have NO equivalent (e.g., ``, ``, ``, ``) |
+| **Page inventory** | List every `.aspx`, `.ascx`, `.master` file in the project with control counts |
+| **Complexity scoring** | Score each page: Green (all controls have equivalents), Yellow (some gaps), Red (major gaps or heavy server-side logic) |
+| **Code-behind pattern detection** | Regex-based detection of common patterns in `.aspx.cs` files: `Page_Load`, `Session[`, `ViewState[`, `Response.Redirect`, `Server.Transfer`, `DataBind()`, `IsPostBack` |
+| **Markdown report** | Structured Markdown report with summary, per-page breakdown, gap list, and recommended migration order |
+| **JSON output** | Machine-readable JSON with the same data for downstream tooling |
+
+### What the PoC does NOT do:
+
+| Excluded | Reason |
+|----------|--------|
+| Roslyn/semantic analysis | Too heavy for a PoC — regex-based pattern detection is sufficient to demonstrate value |
+| Scaffolded `.razor` generation | Requires robust ASP.NET markup parsing + transformation; Phase 2 |
+| Copilot agent/extension | Requires VS Code / GitHub Copilot extension infrastructure; Phase 3 |
+| `Web.config` deep analysis | Authentication, authorization, connection strings — useful but not core to the PoC |
+| NuGet dependency analysis | Tracking which NuGet packages need .NET Core equivalents; Phase 2 |
+| VB.NET code-behind | PoC targets C# only |
+| Custom control analysis | Detecting `WebControl`/`CompositeControl` subclasses; Phase 2 |
+
+### PoC Success Criteria:
+
+1. Run the CLI against the `samples/BeforeWebForms` project (if it exists) or a synthetic test project
+2. Produce a Markdown report listing all pages, controls used, gaps, and complexity scores
+3. Produce a JSON report with the same data
+4. Correctly identify ≥90% of `` controls in the test project
+5. Correctly map controls to their BWFC equivalents per `status.md`
+6. Complete analysis of a 20-page Web Forms project in <5 seconds
+
+---
+
+## 3. Architecture
+
+### 3.1 Project Location
+
+**New project in the same repo:** `src/BlazorWebFormsComponents.MigrationAnalysis/`
+
+Rationale:
+- The mapping table (which Web Forms controls → which BWFC components) is tightly coupled to the component library. Same repo means the mapping stays in sync.
+- Shares the solution file, CI pipeline, and versioning (nbgv).
+- Published as a separate NuGet tool package: `Fritz.BlazorWebFormsComponents.MigrationAnalysis`
+- If/when we build a Copilot agent extension, it can reference this project for its analysis engine.
+
+A separate test project: `src/BlazorWebFormsComponents.MigrationAnalysis.Tests/`
+
+### 3.2 Technology Stack
+
+| Layer | Technology | Why |
+|-------|-----------|-----|
+| **CLI framework** | `System.CommandLine` | Modern .NET CLI framework, supports `--help`, `--output`, `--format` etc. |
+| **ASPX parsing** | Regex + custom tokenizer | Full HTML/ASPX parsing (e.g., HtmlAgilityPack) is overkill for the PoC. `` where key is the Web Forms control name (e.g., `"GridView"`) and value contains the BWFC equivalent, supported attributes, and any migration notes. |
+| **Report generation** | StringBuilder + System.Text.Json | Markdown via string templates. JSON via `System.Text.Json` serialization. |
+| **Target framework** | `net10.0` (console app) | Matches the rest of the repo. Published as a .NET tool. |
+
+### 3.3 Component Map
+
+The control mapping is the heart of the tool. It encodes our 51 implemented components plus all known Web Forms controls we intentionally don't support:
+
+```
+SUPPORTED (51 components):
+ asp:Button → ✅
+ asp:TextBox → ✅
+ asp:GridView → ✅
+ asp:Label → ✅
+ ... (full map derived from status.md)
+
+DEFERRED (2 components):
+ asp:Substitution → ⏸️ No Blazor equivalent
+ asp:Xml → ⏸️ XSLT rarely used
+
+EXPLICITLY UNSUPPORTED (never planned):
+ asp:SqlDataSource → ❌ Use DI + EF Core
+ asp:ObjectDataSource → ❌ Use DI
+ asp:EntityDataSource → ❌ Use DI + EF Core
+ asp:LinqDataSource → ❌ Use LINQ in code
+ asp:XmlDataSource → ❌ Use data services
+ asp:AccessDataSource → ❌ Use EF Core
+ asp:SiteMapDataSource → ❌ Use NavigationManager
+ asp:ScriptManager → ❌ Use Blazor JS interop
+ asp:UpdatePanel → ❌ Blazor is inherently SPA
+ asp:UpdateProgress → ❌ Use Blazor loading patterns
+ asp:Timer → ❌ Use System.Threading.Timer
+ asp:Wizard → ❌ Manual multi-step in Blazor
+ asp:DynamicData → ❌ Not applicable
+```
+
+### 3.4 CLI Interface
+
+```bash
+# Basic analysis
+dotnet bwfc-migrate analyze --project "C:\path\to\WebFormsApp.csproj"
+
+# With output options
+dotnet bwfc-migrate analyze \
+ --project "C:\path\to\WebFormsApp.csproj" \
+ --output "./migration-report" \
+ --format markdown json \
+ --verbose
+
+# Scan a directory (no .csproj required)
+dotnet bwfc-migrate analyze --path "C:\path\to\WebFormsApp\"
+```
+
+### 3.5 Report Structure
+
+```
+Migration Analysis Report
+═══════════════════════════
+
+Project: MyWebFormsApp
+Scanned: 2026-02-25
+Pages: 24 (.aspx) | Controls: 8 (.ascx) | Master Pages: 2 (.master)
+
+Summary
+───────
+✅ Ready for migration: 14 pages (58%)
+⚠️ Needs some custom work: 7 pages (29%)
+❌ Requires significant work: 3 pages (13%)
+
+Control Coverage
+────────────────
+Controls used: 18 unique Web Forms controls
+With BWFC equivalent: 15 (83%)
+No equivalent: 3 (17%) — Wizard, SqlDataSource, ScriptManager
+
+Migration Gaps
+──────────────
+| Control | Pages Using It | Recommendation |
+|------------------|---------------|-----------------------------------|
+| asp:Wizard | 2 | Rewrite as multi-step Blazor form |
+| asp:SqlDataSource| 5 | Replace with EF Core + DI |
+| asp:ScriptManager| 8 | Remove — Blazor handles JS interop|
+
+Code-Behind Patterns
+────────────────────
+| Pattern | Pages | Migration Impact |
+|--------------------|-------|------------------|
+| Page_Load | 22 | → OnInitialized |
+| IsPostBack | 18 | → Remove (no postback in Blazor) |
+| Session["..."] | 9 | → Inject IHttpContextAccessor or use Blazor state |
+| ViewState["..."] | 6 | → Use component parameters or fields |
+| Response.Redirect | 11 | → NavigationManager.NavigateTo |
+| Server.Transfer | 2 | → NavigationManager.NavigateTo |
+| DataBind() | 14 | → Automatic with parameter binding |
+
+Recommended Migration Order
+────────────────────────────
+1. About.aspx (Green — 3 controls, all supported, no code-behind patterns)
+2. Contact.aspx (Green — 5 controls, all supported, simple Page_Load)
+3. Products/List.aspx (Yellow — GridView + SqlDataSource, replace data source)
+...
+
+Per-Page Detail
+───────────────
+### Default.aspx (⚠️ Yellow)
+Controls: Button, Label, TextBox, GridView, SqlDataSource
+Code-behind: Page_Load, IsPostBack, Session["UserName"], DataBind()
+Gaps: SqlDataSource (replace with EF Core service)
+Complexity: Moderate — markup is drop-in, but data access needs refactoring
+```
+
+---
+
+## 4. Work Items
+
+### P0 — Core Analysis Engine (6 WIs)
+
+| ID | Title | Description | Agent | Dependencies | Size | Priority |
+|----|-------|-------------|-------|-------------|------|----------|
+| WI-01 | **Create project skeleton** | Create `src/BlazorWebFormsComponents.MigrationAnalysis/` as a `net10.0` console app. Add `System.CommandLine` NuGet reference. Set up `dotnet tool` packaging in `.csproj` (`true `, `bwfc-migrate `). Add project to `BlazorMeetsWebForms.sln`. Create `src/BlazorWebFormsComponents.MigrationAnalysis.Tests/` xUnit test project. | Cyclops | — | S | P0 |
+| WI-02 | **ASPX control extractor** | Implement `AspxControlExtractor` class that takes a file path (`.aspx`, `.ascx`, `.master`) and returns a list of `ControlReference` objects: `{ ControlName, Attributes (Dictionary), LineNumber, FilePath }`. Use regex to find ` `) and content tags (``). Handle multi-line declarations. Write xUnit tests with sample `.aspx` content covering: single control, multiple controls, nested controls, self-closing, multi-line attributes, non-asp tags (should be ignored). | Cyclops | WI-01 | M | P0 |
+| WI-03 | **Control mapping registry** | Implement `ControlMappingRegistry` class containing the mapping of all 53 planned Web Forms controls to their BWFC equivalents (51 supported, 2 deferred). Add entries for ~15 known unsupported controls (data sources, ScriptManager, UpdatePanel, Wizard, etc.) with migration notes. Each mapping entry: `{ WebFormsName, BwfcEquivalent (nullable), Status (Supported/Deferred/Unsupported), MigrationNote }`. Derive the supported list from `status.md`. Write tests verifying all 51 supported controls map correctly. | Cyclops | WI-01 | M | P0 |
+| WI-04 | **Code-behind pattern analyzer** | Implement `CodeBehindAnalyzer` class that takes a `.cs` file path and returns detected patterns: `Page_Load`, `Page_Init`, `Page_PreRender`, `IsPostBack`, `Session["..."]`, `ViewState["..."]`, `Response.Redirect`, `Server.Transfer`, `Server.MapPath`, `DataBind()`, `FindControl()`, `Eval()`/`Bind()`, `RegisterStartupScript`, `RegisterClientScriptBlock`, `HttpContext.Current`. Each pattern includes migration guidance text. Regex-based — no Roslyn. Write tests with synthetic code-behind samples. | Cyclops | WI-01 | M | P0 |
+| WI-05 | **Project scanner & page inventory** | Implement `ProjectScanner` class that takes a project directory or `.csproj` path and discovers all `.aspx`, `.aspx.cs`, `.aspx.designer.cs`, `.ascx`, `.ascx.cs`, `.master`, `.master.cs`, `Web.config`, `Global.asax`, `Global.asax.cs` files. Returns a `ProjectInventory` with file counts by type, list of pages with their code-behind associations (matching `Default.aspx` ↔ `Default.aspx.cs`), and directory structure. Write tests with a mock file system or temp directory. | Cyclops | WI-01 | M | P0 |
+| WI-06 | **Complexity scorer** | Implement `ComplexityScorer` that takes a page's extracted controls + code-behind patterns and produces a complexity score: `Green` (all controls supported, ≤2 code-behind patterns, no data source controls), `Yellow` (≤2 unsupported controls OR 3-5 code-behind patterns), `Red` (≥3 unsupported controls OR session/viewstate heavy OR uses Server.Transfer/FindControl/RegisterScript). Returns `{ Score, Reasons[] }`. Write tests for each threshold. | Cyclops | WI-02, WI-03, WI-04 | S | P0 |
+
+### P1 — Report Generation & CLI (4 WIs)
+
+| ID | Title | Description | Agent | Dependencies | Size | Priority |
+|----|-------|-------------|-------|-------------|------|----------|
+| WI-07 | **Markdown report generator** | Implement `MarkdownReportGenerator` that takes a `MigrationAnalysisResult` (output of full pipeline) and produces a Markdown report matching the structure in Section 3.5: summary, control coverage, migration gaps table, code-behind patterns table, recommended migration order (sorted by complexity score ascending, then alphabetical), per-page detail. Write tests verifying report structure. | Cyclops | WI-06 | M | P1 |
+| WI-08 | **JSON report generator** | Implement `JsonReportGenerator` that serializes `MigrationAnalysisResult` to indented JSON using `System.Text.Json`. The JSON schema should be documented in a `schema.md` or `report-schema.json` file. Write tests verifying JSON structure and roundtrip deserialization. | Cyclops | WI-06 | S | P1 |
+| WI-09 | **CLI entry point** | Wire up `System.CommandLine` with an `analyze` command accepting `--project` (path to `.csproj`), `--path` (directory path, alternative to `--project`), `--output` (output directory, default: current dir), `--format` (markdown, json, or both; default: markdown), `--verbose` (detailed per-file output). Orchestrate: ProjectScanner → AspxControlExtractor (per file) → CodeBehindAnalyzer (per code-behind) → ComplexityScorer → ReportGenerator. | Cyclops | WI-07, WI-08 | M | P1 |
+| WI-10 | **CLI tests & integration test** | Create a synthetic Web Forms project in the test project (`TestData/SampleWebFormsApp/`) with 5-10 `.aspx` files covering various control combinations, code-behind patterns, and complexity levels. Run the full CLI pipeline against it. Verify the Markdown output contains expected sections and the JSON output deserializes correctly. This is the end-to-end validation. | Rogue | WI-09 | M | P1 |
+
+### P2 — Documentation & Samples (3 WIs)
+
+| ID | Title | Description | Agent | Dependencies | Size | Priority |
+|----|-------|-------------|-------|-------------|------|----------|
+| WI-11 | **Tool documentation** | Write docs for the migration analysis tool in `docs/Migration/MigrationAnalysisTool.md`. Cover: installation (`dotnet tool install`), usage, CLI options, interpreting the report, limitations, and what to do with the results. Link from the main migration guide (`docs/Migration/readme.md`). | Beast | WI-09 | M | P2 |
+| WI-12 | **Sample migration report** | Generate a sample migration report from the test data project and include it in `docs/Migration/SampleMigrationReport.md`. This serves as a reference for users to understand what the tool produces. | Beast | WI-10 | S | P2 |
+| WI-13 | **README update** | Update the repo `README.md` to mention the migration analysis tool under a new "Migration Tooling" section. Brief description + link to full docs. Keep it concise — 3-4 lines. | Beast | WI-11 | XS | P2 |
+
+---
+
+## 5. Phase 2 & Phase 3 Roadmap (Post-PoC)
+
+These are NOT in Milestone 12. Documenting them here for Jeff's visibility.
+
+### Phase 2 — Enhanced Analysis + Scaffolding (Milestone 13 or 14)
+
+| Capability | Description |
+|------------|-------------|
+| **Roslyn-based code-behind analysis** | Replace regex with Roslyn `SyntaxTree` analysis for code-behind. Detect event handler signatures, method call chains, class inheritance. Much higher fidelity. |
+| **Razor scaffolding** | Generate `.razor` files from `.aspx` files: strip `asp:` prefixes, convert `<%@ Page %>` directives to `@page`/`@layout`, move code-behind to partial class. Handle simple cases automatically. |
+| **Custom control detection** | Find classes inheriting `System.Web.UI.WebControl`, `CompositeControl`, `UserControl`. Map to our `CustomControls/WebControl` migration adapter. |
+| **Web.config analysis** | Parse authentication config (``), connection strings, custom handlers, HTTP modules. Produce ASP.NET Core equivalents. |
+| **NuGet dependency analysis** | Read `packages.config` or `PackageReference` items. Check which packages have .NET Core equivalents. |
+| **VB.NET support** | Code-behind analysis for `.aspx.vb` files. |
+| **Attribute-level mapping** | Not just "does the control exist?" but "are all the attributes used on this control supported by BWFC?" Per-attribute gap analysis. |
+
+### Phase 3 — Copilot Agent Integration (Milestone 15+)
+
+| Capability | Description |
+|------------|-------------|
+| **GitHub Copilot Extension** | Package the analysis engine as a Copilot agent that can be invoked via `@bwfc-migrate analyze` in Copilot Chat. |
+| **VS Code extension** | Provide inline diagnostics in `.aspx` files showing migration status per control. |
+| **Interactive migration** | Agent-guided migration: analyze → review report → approve page → generate scaffolded `.razor` → iterate. |
+| **GitHub Actions integration** | Run migration analysis as a CI step on PRs that modify Web Forms projects. |
+
+---
+
+## 6. Open Questions for Jeff
+
+These need answers before or during implementation:
+
+| # | Question | Impact | Forge's Recommendation |
+|---|----------|--------|----------------------|
+| 1 | **Same repo or separate repo?** | Project structure. | Same repo — the mapping table is tightly coupled to our component list. A separate repo means the mapping drifts. |
+| 2 | **Tool name: `bwfc-migrate` or something else?** | CLI ergonomics. | `bwfc-migrate` is descriptive. Alternative: `blazor-migrate`. |
+| 3 | **Do we have a reference Web Forms app to test against?** | Test data quality. `samples/BeforeWebForms` exists in older branches but may be stale. | Build a synthetic test project in the test folder. Optionally test against `BeforeWebForms` if it's still around. |
+| 4 | **Should the tool detect third-party controls (Telerik, DevExpress, Infragistics)?** | Scope. | Not in PoC. Phase 2 could add a plugin/extension model for third-party control mappings. |
+| 5 | **JSON schema: should we publish a formal JSON Schema for the report?** | Downstream tooling. | Yes in Phase 2. For PoC, just document the structure in a markdown file. |
+| 6 | **Should this be a `dotnet tool` (global/local) or a standalone executable?** | Distribution. | `dotnet tool` — matches the .NET ecosystem, easy install via `dotnet tool install -g Fritz.BlazorWebFormsComponents.MigrationAnalysis`. |
+| 7 | **Copilot agent integration timeline?** | Phase 3 planning. | Not before the PoC proves the analysis engine works. Phase 3 at earliest. |
+
+---
+
+## 7. Summary
+
+| Priority | Work Items | Theme |
+|----------|-----------|-------|
+| P0 | 6 | Core analysis engine (parsing, mapping, scoring) |
+| P1 | 4 | Report generation + CLI wiring |
+| P2 | 3 | Documentation |
+| **Total** | **13** | |
+
+### Agent Assignments
+
+| Agent | Work Items | Load |
+|-------|-----------|------|
+| Cyclops | WI-01 through WI-09 | 9 WIs (S + 4M + S + 2M + M) — heavy load, but sequential pipeline |
+| Rogue | WI-10 | 1 WI (M) — end-to-end test |
+| Beast | WI-11, WI-12, WI-13 | 3 WIs (M + S + XS) — docs |
+
+### Dependencies
+
+```
+WI-01 ──→ WI-02, WI-03, WI-04, WI-05 (project skeleton first)
+WI-02 ──→ WI-06 (extractor before scorer)
+WI-03 ──→ WI-06 (mapping before scorer)
+WI-04 ──→ WI-06 (analyzer before scorer)
+WI-06 ──→ WI-07, WI-08 (scorer before report generators)
+WI-07 ──→ WI-09 (reports before CLI wiring)
+WI-08 ──→ WI-09
+WI-09 ──→ WI-10 (CLI before integration test)
+WI-09 ──→ WI-11 (CLI before docs)
+WI-10 ──→ WI-12 (test data before sample report)
+WI-11 ──→ WI-13 (tool docs before README update)
+```
+
+### Risk Assessment
+
+| Risk | Likelihood | Impact | Mitigation |
+|------|-----------|--------|-----------|
+| Regex-based ASPX parsing misses edge cases | Medium | Low | PoC explicitly scopes to `` patterns only. Phase 2 adds proper parsing. |
+| No real Web Forms test project available | Medium | Medium | Build a synthetic test project with representative patterns. |
+| Control mapping drifts from actual component library | Low | Medium | Mapping lives in same repo. CI could validate mapping against actual component classes. |
+| System.CommandLine learning curve | Low | Low | Well-documented library. Forge has reviewed similar tools. |
+| Scope creep into Roslyn analysis | Medium | High | Hard boundary: PoC is regex-only. Roslyn is Phase 2. Period. |
+
+### Exit Criteria
+
+1. CLI tool builds and runs against a test Web Forms project
+2. Markdown report produced with summary, per-page breakdown, gap list, complexity scores
+3. JSON report produced with matching data
+4. All controls in `status.md` correctly represented in the mapping registry
+5. 10+ unit tests covering the analysis pipeline
+6. 1 end-to-end integration test
+7. Documentation published in `docs/Migration/`
+
+---
+
+## 8. Why Now — Strategic Context
+
+We're at 51/53 components. The component library is mature. The remaining work (Milestones 9-11) is hardening and polish. The highest-value thing we can do for the migration story is not building the 52nd component — it's helping developers actually USE the 51 we already have.
+
+Every Web Forms developer evaluating BlazorWebFormsComponents asks the same question: "Will this work for MY app?" This tool answers that question programmatically. It turns a week of manual analysis into a 5-second CLI invocation.
+
+This is the tool that makes BlazorWebFormsComponents a migration *platform*, not just a component *library*.
+
+— Forge
diff --git a/planning-docs/MILESTONE9-PLAN.md b/planning-docs/MILESTONE9-PLAN.md
new file mode 100644
index 000000000..b5b66ec4f
--- /dev/null
+++ b/planning-docs/MILESTONE9-PLAN.md
@@ -0,0 +1,158 @@
+# Milestone 9 — Migration Fidelity & Hardening
+
+**Created:** 2026-02-25
+**Author:** Forge (Lead / Web Forms Reviewer)
+**Branch:** `milestone9/migration-fidelity`
+**Baseline:** dev (post-v0.14.1, 51/53 components, 1200+ tests)
+
+---
+
+## Goals
+
+Milestones 6–8 brought us from ~45% to 96% component completion (51/53) and shipped 1200+ tests. The remaining work is not about new components — it's about **migration fidelity**: making the existing 51 components behave exactly like their Web Forms originals so that migrating developers can paste markup and have it work.
+
+This milestone targets the highest-leverage remaining gaps: a base class fix that sweeps across 28+ controls, a data-corruption bug, and stale branch cleanup. All changes are low-risk and high-impact.
+
+### Theme: "Paste and It Works"
+
+**Priority targets (by blast radius):**
+
+| Change | Controls Affected | Risk | Impact |
+|--------|-------------------|------|--------|
+| ToolTip → BaseStyledComponent | 28+ controls gain ToolTip | Low | High — single most impactful remaining gap |
+| ValidationSummary comma-split fix | All validation scenarios | Low | High — data corruption |
+| SkinID type fix (bool→string) | All 51 components | Low | Medium — migration correctness |
+| Stale branch cleanup | N/A | None | Housekeeping |
+| Documentation gap closure | 8 controls | None | Medium — migration guides |
+
+### Scoping Rules (carried forward)
+
+- ✅ Substitution and Xml remain intentionally deferred
+- ✅ Chart advanced properties remain deferred (canvas architecture)
+- ✅ DataSourceID / model binding methods remain N/A
+- ✅ Focus() method remains deferred (use `ElementReference.FocusAsync()`)
+- ✅ Docs and samples must ship in the same milestone as the features they cover
+
+---
+
+## Pre-Milestone Verification
+
+Before starting, the following gaps from prior audits were verified against the current `dev` branch:
+
+### ✅ ALREADY FIXED (no action needed)
+
+| Original Gap | Status | Fixed In |
+|-------------|--------|----------|
+| AccessKey on BaseWebFormsComponent | ✅ Done | M6 — on `BaseWebFormsComponent` line 118 |
+| Image → BaseStyledComponent | ✅ Done | M6/M7 — `Image.razor.cs` inherits `BaseStyledComponent` |
+| Label → BaseStyledComponent + AssociatedControlID | ✅ Done | M7 — `Label.razor.cs` inherits `BaseStyledComponent`, `AssociatedControlID` parameter present |
+| Validation Display property | ✅ Done | M6 — `ValidatorDisplay` enum with None/Static/Dynamic on `BaseValidator` |
+| HyperLink NavigateUrl vs NavigationUrl | ✅ Done | M6 — Property is `NavigateUrl`, obsolete `NavigationUrl` alias preserved |
+| DataBoundComponent chain → BaseStyledComponent | ✅ Done | M7 — `BaseDataBoundComponent` inherits `BaseStyledComponent` |
+| bUnit test migration (beta → 2.x) | ✅ Not needed | Tests already use modern bUnit API (no TestComponentBase/Fixture/SnapshotTest) |
+
+### ❌ STILL OPEN (targeted in this milestone)
+
+| Gap | Severity | Details |
+|-----|----------|---------|
+| ToolTip not on BaseStyledComponent | P0 | Scattered across 8 individual components; 28+ styled controls lack it entirely |
+| ValidationSummary comma-split bug | P1 | `AspNetValidationSummary.razor.cs` line 27: `.Split(',')[1]` corrupts messages containing commas |
+| SkinID is `bool` instead of `string` | P2 | `BaseWebFormsComponent.cs` line 101: `public bool SkinID` — Web Forms SkinID is `string` |
+
+---
+
+## Work Items
+
+### P0 — ToolTip Base Class Fix (4 WIs, ~28 gap closures)
+
+This is the single highest-leverage change remaining. Web Forms `WebControl.ToolTip` renders as `title="..."` on the HTML element. Currently 8 components implement ToolTip individually; 28+ styled controls are missing it. Moving ToolTip to `BaseStyledComponent` fixes all of them in one change and removes duplication from the 8 that have it.
+
+| ID | Title | Description | Agent | Dependencies | Size | Priority |
+|----|-------|-------------|-------|-------------|------|----------|
+| WI-01 | **ToolTip → BaseStyledComponent** | Add `[Parameter] public string ToolTip { get; set; }` to `BaseStyledComponent`. Remove the individual `ToolTip` declarations from the 8 components that define it locally (Button, Calendar, DataList, FileUpload, HyperLink, Image, ImageButton, ImageMap). Ensure templates that render `title="@ToolTip"` continue to work. For components that don't yet render ToolTip in their template, add `title="@ToolTip"` to the outermost HTML element. **Note:** ChartSeries.ToolTip, DataPoint.ToolTip, MenuItem.ToolTip, TreeNode.ToolTip, MenuItemBinding.ToolTipField, and TreeNodeBinding.ToolTipField are sub-component properties — do NOT remove these (they are semantically different from the control-level ToolTip). | Cyclops | — | M | P0 |
+| WI-02 | **ToolTip base class tests** | Expand the existing `ToolTipTests.razor` test file. Add tests for controls that newly gain ToolTip: Label, TextBox, CheckBox, RadioButton, Panel, Table, DropDownList, ListBox, LinkButton. Verify `title="myTooltip"` renders in the HTML output. Verify that removing the individual ToolTip from Button/Image/HyperLink etc. doesn't regress (they should now inherit from base). | Rogue | WI-01 | M | P0 |
+| WI-03 | **ToolTip rendering in templates** | Audit all 51 component `.razor` templates. For any component that inherits BaseStyledComponent (directly or via DataBoundComponent chain) but does NOT render `title="@ToolTip"` on its outermost element, add it. Components that render complex HTML (GridView renders ``, Login renders ``) should render ToolTip on the outermost container element, matching Web Forms behavior. | Cyclops | WI-01 | M | P0 |
+| WI-04 | **ToolTip docs update** | Update component documentation to note ToolTip availability on all styled controls. Add a migration note to the main migration guide explaining that `ToolTip` is now universally supported. | Beast | WI-01 | S | P0 |
+
+### P1 — Bug Fixes (3 WIs, 2 bug fixes)
+
+| ID | Title | Description | Agent | Dependencies | Size | Priority |
+|----|-------|-------------|-------|-------------|------|----------|
+| WI-05 | **Fix ValidationSummary comma-split bug** | In `AspNetValidationSummary.razor.cs` line 27, `FilteredMessages.Select(x => x.Split('\x1F')[0].Split(',')[1])` splits on comma to extract the error message. If the error message itself contains a comma (e.g., "Please enter first name, last name"), only the text after the first comma is shown. **Fix:** Use `IndexOf(',')` + `Substring()` instead of `Split(',')[1]` — take everything after the FIRST comma only. Also verify the corresponding storage in `BaseValidator` uses the same delimiter convention. | Cyclops | — | S | P1 |
+| WI-06 | **ValidationSummary comma-split tests** | Add bUnit tests: (1) error message containing one comma renders full message, (2) error message containing multiple commas renders full message, (3) error message with no commas renders correctly, (4) empty error message renders correctly. | Rogue | WI-05 | S | P1 |
+| WI-07 | **Fix SkinID type: bool → string** | In `BaseWebFormsComponent.cs` line 101, change `public bool SkinID { get; set; }` to `public string SkinID { get; set; }`. Web Forms `SkinID` is a string (the name of the skin to apply). Keep the `[Obsolete]` attribute. This is a breaking change for any code that sets `SkinID="true"` — but since the property does nothing, impact is minimal. Update any tests that reference SkinID. | Cyclops | — | XS | P1 |
+
+### P2 — Housekeeping (5 WIs)
+
+| ID | Title | Description | Agent | Dependencies | Size | Priority |
+|----|-------|-------------|-------|-------------|------|----------|
+| WI-08 | **Clean up stale local branches** | Delete merged/stale local branches: `copilot/create-basepage-for-services`, `copilot/create-calendar-component`, `copilot/fix-334-fileupload-component`, `copilot/fix-336-imagemap-component`, `feature-phase2`, `fix-sample-container`, `fix/deployment-workflows`, `sprint-2/editor-login-controls`, `sprint3/detailsview-passwordrecovery`, `v0.13`. Keep `dev`, `main`, `milestone8/release-readiness` (tag reference). Coordinate with repo owner on remote branch cleanup. | Cyclops | — | XS | P2 |
+| WI-09 | **Documentation gap audit** | Audit `docs/` directory against the features added in M6–M8. Identify any controls whose docs don't cover features added in M6–M8 (GridView selection/styles/display, TreeView selection/expand, Menu selection/events, FormView events/styles, DetailsView styles/caption, DataGrid styles/paging, validators ControlToValidate dual-path, PagerSettings). Produce a list of doc pages that need updates. | Beast | — | M | P2 |
+| WI-10 | **Update stale planning-docs audit files** | The individual control audit files in `planning-docs/` (e.g., `GridView.md`, `Menu.md`, `TreeView.md`) are stale — they reflect pre-M6 state. Either update them to reflect the current state post-M8 or mark them as historical with a header noting they are pre-M6 snapshots. | Beast | — | M | P2 |
+| WI-11 | **Integration test coverage for M7 features** | Review integration test coverage for features added in M7 (GridView selection, TreeView expand/collapse, Menu selection, FormView events, DetailsView styles). Identify any gaps and add missing Playwright smoke/interaction tests. | Colossus | — | M | P2 |
+| WI-12 | **Sample site navigation audit** | Verify all sample pages are reachable from the AfterBlazorServerSide navigation. Check that new M7/M8 sample pages (GridView Selection, GridView DisplayProperties, TreeView Selection, TreeView ExpandCollapse, Menu Selection, FormView Events/Styles, DetailsView Styles/Caption, ListView CrudOperations) are linked in NavMenu.razor or the sample index. | Jubilee | — | S | P2 |
+
+---
+
+## Summary
+
+| Priority | Work Items | Estimated Gap Closures | Theme |
+|----------|-----------|----------------------|-------|
+| P0 | 4 | ~28 | ToolTip base class fix |
+| P1 | 3 | 2 bugs | ValidationSummary + SkinID fixes |
+| P2 | 5 | 0 (housekeeping) | Cleanup + docs + test coverage |
+| **Total** | **12** | **~30** | |
+
+### Agent Assignments
+
+| Agent | Work Items | Load |
+|-------|-----------|------|
+| Cyclops | WI-01, WI-03, WI-05, WI-07, WI-08 | 5 WIs (M + M + S + XS + XS) |
+| Rogue | WI-02, WI-06 | 2 WIs (M + S) |
+| Beast | WI-04, WI-09, WI-10 | 3 WIs (S + M + M) |
+| Colossus | WI-11 | 1 WI (M) |
+| Jubilee | WI-12 | 1 WI (S) |
+
+### Dependencies
+
+```
+WI-01 ──→ WI-02 (tests need base class change)
+WI-01 ──→ WI-03 (template audit after base class change)
+WI-01 ──→ WI-04 (docs after implementation)
+WI-05 ──→ WI-06 (tests need bug fix)
+```
+
+All other work items are independent and can proceed in parallel.
+
+### Risk Assessment
+
+| Risk | Likelihood | Impact | Mitigation |
+|------|-----------|--------|-----------|
+| ToolTip base class change breaks existing component behavior | Low | Medium | 8 components already use ToolTip individually — removal is mechanical. Run full test suite before merge. |
+| ValidationSummary fix introduces new parsing bugs | Low | High | Targeted fix (IndexOf vs Split). Test with edge cases. |
+| SkinID type change breaks consumer code | Very Low | Low | Property is `[Obsolete]` and does nothing. |
+| Stale branch deletion removes needed work | Low | Low | Only delete branches whose work is already on dev. |
+
+### Exit Criteria
+
+1. All 1200+ existing tests pass
+2. ToolTip renders on all BaseStyledComponent-derived controls
+3. ValidationSummary correctly handles commas in error messages
+4. SkinID parameter accepts string values
+5. No stale local branches remain
+6. Docs and samples ship with any feature changes
+
+---
+
+## Post-M9 Outlook
+
+After M9, the remaining meaningful gaps are:
+
+1. **DataBoundStyledComponent** — Chart currently inherits BaseStyledComponent directly; a proper intermediate class could benefit future data+style controls. Low priority since only Chart uses it.
+2. **ListView CRUD events** — 16 missing events. Large effort, deferred since M7.
+3. **Menu level styles** — Dynamic/Static menu level style sub-components. Medium effort.
+4. **Panel.BackImageUrl** — Single missing property. XS effort.
+5. **PasswordRecovery → BaseStyledComponent** — Login controls pattern alignment.
+6. **LoginView → BaseStyledComponent** — Currently still on BaseWebFormsComponent.
+
+These can be scoped into a Milestone 10 if the project continues toward 1.0.
diff --git a/planning-docs/Menu.md b/planning-docs/Menu.md
index 00333043b..2c4273842 100644
--- a/planning-docs/Menu.md
+++ b/planning-docs/Menu.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Menu — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.menu?view=netframework-4.8.1
diff --git a/planning-docs/MultiView.md b/planning-docs/MultiView.md
index af7402e78..a28b17e9c 100644
--- a/planning-docs/MultiView.md
+++ b/planning-docs/MultiView.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# MultiView — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.multiview?view=netframework-4.8
diff --git a/planning-docs/Panel.md b/planning-docs/Panel.md
index b5b0158e0..04f8699a9 100644
--- a/planning-docs/Panel.md
+++ b/planning-docs/Panel.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Panel — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.panel?view=netframework-4.8
diff --git a/planning-docs/PasswordRecovery.md b/planning-docs/PasswordRecovery.md
index fbd0e9f32..020f20ff0 100644
--- a/planning-docs/PasswordRecovery.md
+++ b/planning-docs/PasswordRecovery.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# PasswordRecovery — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.passwordrecovery?view=netframework-4.8
diff --git a/planning-docs/PlaceHolder.md b/planning-docs/PlaceHolder.md
index 0bd9039de..543a46c3f 100644
--- a/planning-docs/PlaceHolder.md
+++ b/planning-docs/PlaceHolder.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# PlaceHolder — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.placeholder?view=netframework-4.8
diff --git a/planning-docs/RadioButton.md b/planning-docs/RadioButton.md
index 91d6999f0..129f4d45a 100644
--- a/planning-docs/RadioButton.md
+++ b/planning-docs/RadioButton.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# RadioButton — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.radiobutton?view=netframework-4.8
diff --git a/planning-docs/RadioButtonList.md b/planning-docs/RadioButtonList.md
index 4f4c5d7b2..5901d60bd 100644
--- a/planning-docs/RadioButtonList.md
+++ b/planning-docs/RadioButtonList.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# RadioButtonList — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.radiobuttonlist?view=netframework-4.8
diff --git a/planning-docs/RangeValidator.md b/planning-docs/RangeValidator.md
index 705ab7cbd..e3cfea2a9 100644
--- a/planning-docs/RangeValidator.md
+++ b/planning-docs/RangeValidator.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# RangeValidator — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.rangevalidator?view=netframework-4.8
diff --git a/planning-docs/RegularExpressionValidator.md b/planning-docs/RegularExpressionValidator.md
index fa7f15642..c65eaf424 100644
--- a/planning-docs/RegularExpressionValidator.md
+++ b/planning-docs/RegularExpressionValidator.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# RegularExpressionValidator — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.regularexpressionvalidator?view=netframework-4.8
diff --git a/planning-docs/Repeater.md b/planning-docs/Repeater.md
index d31b7f064..50113478b 100644
--- a/planning-docs/Repeater.md
+++ b/planning-docs/Repeater.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Repeater — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.repeater?view=netframework-4.8.1
diff --git a/planning-docs/RequiredFieldValidator.md b/planning-docs/RequiredFieldValidator.md
index 05a179976..b058cab83 100644
--- a/planning-docs/RequiredFieldValidator.md
+++ b/planning-docs/RequiredFieldValidator.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# RequiredFieldValidator — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.requiredfieldvalidator?view=netframework-4.8
diff --git a/planning-docs/SKINS-THEMES-COMPATIBILITY-REPORT.md b/planning-docs/SKINS-THEMES-COMPATIBILITY-REPORT.md
new file mode 100644
index 000000000..197d9bfa7
--- /dev/null
+++ b/planning-docs/SKINS-THEMES-COMPATIBILITY-REPORT.md
@@ -0,0 +1,318 @@
+# Skins & Themes Compatibility Report
+
+**Author:** Forge (Lead / Web Forms Reviewer)
+**Date:** 2026-02-25
+**Scope:** ASP.NET Web Forms Skins & Themes → Blazor compatibility analysis
+
+---
+
+## 1. Web Forms Skins & Themes — How They Work in .NET 4.8
+
+### 1.1 Theme Files (`.skin` files)
+
+In ASP.NET Web Forms, themes live in `App_Themes/{ThemeName}/` folders under the application root. Each theme folder can contain:
+
+- **`.skin` files** — XML-like declarations that set default property values for server controls
+- **`.css` files** — automatically linked to every page using the theme
+- **Image files** — referenced by skin property values or CSS
+
+#### What's in a .skin file
+
+A `.skin` file contains server control declarations *without* an `ID` attribute. These act as property templates:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Key rules:
+- Each `.skin` file can contain declarations for multiple control types
+- Only **appearance properties** can be set (not behavioral properties like `DataSource`)
+- `runat="server"` is required; `ID` is prohibited (except via `SkinID`)
+- Property values follow standard `.aspx` attribute syntax
+
+#### Default skins vs Named skins
+
+| Aspect | Default Skin | Named Skin |
+|--------|-------------|------------|
+| Declaration | No `SkinID` attribute | Has `SkinID="name"` |
+| Application | Automatic — all instances of that control type | Opt-in — only controls with matching `SkinID` |
+| Limit | One default skin per control type per theme | Unlimited named skins per control type |
+
+#### How themes cascade
+
+Themes are applied at three levels (lowest to highest precedence):
+
+1. **`web.config`** — ` ` or ` `
+2. **Page directive** — `<%@ Page Theme="ThemeName" %>` or `<%@ Page StyleSheetTheme="ThemeName" %>`
+3. **Control-level** — `SkinID="specificSkin"` on individual controls, or `EnableTheming="false"` to opt out
+
+### 1.2 CSS Integration
+
+Any `.css` files placed in the theme folder (`App_Themes/{ThemeName}/`) are **automatically** added as ` ` elements to every page that uses the theme. The developer doesn't need to explicitly reference them.
+
+- Multiple CSS files are included alphabetically
+- CSS files in subfolders of the theme directory are also included
+- This provides a convenient way to bundle visual styles with control property defaults
+
+### 1.3 Runtime Behavior — Page Lifecycle
+
+Skins are applied during the **PreInit** phase of the page lifecycle:
+
+1. `Page.PreInit` fires
+2. ASP.NET resolves the theme (from `web.config`, page directive, or code)
+3. For each control on the page:
+ - If `EnableTheming=false`, skip
+ - If control has a `SkinID`, look for a matching named skin
+ - Otherwise, apply the default skin for that control type
+4. Skin property values are merged onto the control
+5. **Theme** properties override control declarations; **StyleSheetTheme** properties are overridden by control declarations
+
+This happens *before* `Init`, so controls have their themed properties when `Init` fires.
+
+### 1.4 SkinID Property
+
+`SkinID` is a `string` property on `System.Web.UI.Control`:
+
+```csharp
+public virtual string SkinID { get; set; }
+```
+
+- Set declaratively: ` `
+- Set in code: `myButton.SkinID = "goButton";` (must be set in `PreInit`)
+- If set to a name that doesn't exist in the theme, a runtime exception is thrown
+- Empty string or null → use default skin
+
+### 1.5 EnableTheming Property
+
+`EnableTheming` is a `bool` property on `System.Web.UI.Control`:
+
+```csharp
+public virtual bool EnableTheming { get; set; }
+```
+
+- Default is `true`
+- When set to `false`, no skin (default or named) is applied to that control
+- Also prevents theming of child controls when set on a container
+- Can be set at the page level: `<%@ Page EnableTheming="false" %>`
+
+### 1.6 StyleSheetTheme vs Theme — Override Priority
+
+| Mechanism | Priority | Behavior |
+|-----------|----------|----------|
+| `StyleSheetTheme` | Low — acts as defaults | Skin values apply first, then control declarations override them |
+| `Theme` | High — acts as overrides | Control declarations apply first, then skin values override them |
+
+In practice:
+- **`StyleSheetTheme`** = "these are the defaults, but the page can override"
+- **`Theme`** = "these are enforced, regardless of what the page says"
+
+Most real-world apps use `Theme` (enforced overrides) because the goal is visual consistency.
+
+---
+
+## 2. Compatibility Analysis for Blazor
+
+### 2.1 Theme File Structure
+
+| Aspect | Can Emulate? | Notes |
+|--------|-------------|-------|
+| `App_Themes/` folder convention | **Partial** | Blazor has no equivalent folder convention. We'd need to define our own (e.g., `wwwroot/themes/` or embedded resources). |
+| `.skin` file format | **Partial** | `.skin` files are pseudo-ASPX markup. A parser could extract property values, but the format is not standard XML (no root element). We'd likely define a new format (JSON/C#). |
+| Multiple skins per file | **Yes** | Any configuration format can support this. |
+| Automatic CSS bundling | **Partial** | Blazor has CSS isolation (`Component.razor.css`) and can link CSS, but there's no automatic "include all CSS from a folder" mechanism. Would require a build step or explicit references. |
+
+**Closest Blazor equivalent:** A `ThemeProvider` component using `CascadingValue` to pass a theme configuration object down the component tree.
+
+**Compromises:** Developers must convert `.skin` files to a new format (JSON or C# configuration). The automatic CSS bundling would need manual CSS ` ` tags or a build-time generator.
+
+### 2.2 Default Skins vs Named Skins
+
+| Aspect | Can Emulate? | Notes |
+|--------|-------------|-------|
+| Default skin (per control type) | **Yes** | A theme configuration can specify defaults by control type name (e.g., `"Button": { "BackColor": "#FFDEAD" }`). |
+| Named skins (SkinID) | **Yes** | The configuration can have named entries. Components already have a `SkinID` parameter. |
+| Automatic application of defaults | **Partial** | Requires components to actively read from a cascading theme provider during initialization. Not "free" like Web Forms. |
+
+**Closest Blazor equivalent:** `CascadingValue` providing a dictionary of control type → property defaults, with `SkinID` lookups.
+
+**Compromises:** Each component's `OnInitialized` must include theme-reading logic. This is more explicit than Web Forms' automatic application.
+
+### 2.3 Runtime Behavior (Lifecycle Mapping)
+
+| Web Forms Phase | Blazor Equivalent | Feasibility |
+|----------------|-------------------|-------------|
+| `PreInit` — theme resolution | `OnInitialized` / `OnParametersSet` | **Yes** — theme properties can be applied in `OnInitialized` before rendering |
+| Automatic property merging | Manual property reading | **Partial** — components must explicitly read theme values and apply them |
+| Theme override of declarations | Parameter precedence logic | **Yes** — components can check "was this parameter explicitly set?" using `ParameterView` |
+
+**Key difference:** In Web Forms, the framework does the merging automatically. In Blazor, each component must cooperate by checking the theme provider. This means the base class must implement the theme-reading logic.
+
+### 2.4 SkinID Property
+
+| Aspect | Can Emulate? | Notes |
+|--------|-------------|-------|
+| Property exists | **Yes** — already on `BaseWebFormsComponent` | Currently marked `[Obsolete]` and typed as `string` |
+| Named skin lookup | **Yes** | ThemeProvider can resolve `SkinID` to specific property values |
+| Missing SkinID throws | **Configurable** | Could throw or warn — Blazor apps typically prefer graceful degradation |
+
+**Current state:** `SkinID` exists as a `string` parameter on `BaseWebFormsComponent` (line 101) but is marked `[Obsolete("Theming is not available in Blazor")]`. This needs to be un-obsoleted.
+
+**⚠️ Bug found:** In the M9 audit, `SkinID` was flagged as having a `bool→string` type mismatch. Current code shows it's typed `string`, which is correct. The `[Obsolete]` attribute is the actual issue to address.
+
+### 2.5 EnableTheming Property
+
+| Aspect | Can Emulate? | Notes |
+|--------|-------------|-------|
+| Property exists | **Yes** — already on `BaseWebFormsComponent` | Currently marked `[Obsolete]` and typed as `bool` |
+| Opt-out behavior | **Yes** | When `false`, component skips theme-reading logic |
+| Container-level disable | **Partial** | Would need cascading parameter to propagate to children |
+
+**Current state:** `EnableTheming` exists as a `bool` parameter on `BaseWebFormsComponent` (line 95) but is marked `[Obsolete]`. Needs un-obsoleting.
+
+### 2.6 StyleSheetTheme vs Theme Priority
+
+| Aspect | Can Emulate? | Notes |
+|--------|-------------|-------|
+| Two-tier priority model | **Yes** | Components can implement "apply theme before parameters" (StyleSheetTheme behavior) or "apply theme after parameters" (Theme behavior) |
+| Configuration-level toggle | **Yes** | ThemeProvider can expose a `ThemeMode` enum: `Default` (StyleSheetTheme) vs `Override` (Theme) |
+
+**Closest Blazor equivalent:** The theme provider applies property values either before or after component parameter binding, controlled by a mode setting.
+
+---
+
+## 3. Existing Component Readiness
+
+### 3.1 SkinID Support
+
+| Component / Class | Has SkinID? | Notes |
+|-------------------|-------------|-------|
+| `BaseWebFormsComponent` | ✅ Yes | `string SkinID` — marked `[Obsolete]`, needs activation |
+| All 51 components | ✅ Inherited | All inherit from `BaseWebFormsComponent` |
+
+**Verdict:** SkinID parameter exists universally but is dormant (obsolete attribute prevents use without compiler warnings).
+
+### 3.2 EnableTheming Support
+
+| Component / Class | Has EnableTheming? | Notes |
+|-------------------|-------------------|-------|
+| `BaseWebFormsComponent` | ✅ Yes | `bool EnableTheming` — marked `[Obsolete]`, needs activation |
+| All 51 components | ✅ Inherited | All inherit from `BaseWebFormsComponent` |
+
+**Verdict:** Same as SkinID — exists but dormant.
+
+### 3.3 Base Class Support
+
+| Base Class | Style Properties | Theme-Ready? |
+|-----------|-----------------|-------------|
+| `BaseWebFormsComponent` | `SkinID`, `EnableTheming`, `ID`, `Visible`, `Enabled` | Has the hooks, needs theme-reading logic |
+| `BaseStyledComponent` | All of above + `BackColor`, `ForeColor`, `BorderColor`, `BorderStyle`, `BorderWidth`, `Height`, `Width`, `CssClass`, `Font`, `ToolTip` | Best candidate for theme application — has all visual properties |
+| `DataBoundComponent` | Inherits `BaseWebFormsComponent` | Missing style properties (known gap from M7 audit) |
+| `UiStyle` | Style properties via `TableItemStyle`/`Style` | Sub-component styles, not direct theme targets |
+
+### 3.4 Styling Infrastructure
+
+The library has a mature styling pipeline:
+
+- **`IStyle` / `IHasLayoutStyle` / `IFontStyle`** — interfaces defining style properties
+- **`Style.cs` / `TableItemStyle.cs`** — property bags for style values
+- **`HasStyleExtensions.cs`** — converts style objects to CSS via `StyleBuilder`
+- **`WebColor`** — HTML/CSS color wrapper with 140+ named colors
+- **`FontInfo`** — font property collection (Bold, Italic, Size, Names, etc.)
+- **`UiStyle` / `UiTableItemStyle`** — Blazor components for declarative style sub-elements
+
+### 3.5 Gaps to Fill Before Themes Can Work
+
+| Gap | Severity | Description |
+|-----|----------|-------------|
+| **No ThemeProvider** | 🔴 Critical | No mechanism to cascade theme configuration to components |
+| **No theme-reading logic in base classes** | 🔴 Critical | Components don't read from any theme source during initialization |
+| **SkinID/EnableTheming obsolete** | 🟡 Medium | Properties exist but are marked obsolete — needs attribute removal |
+| **No theme configuration format** | 🔴 Critical | No defined format for specifying skin property values |
+| **DataBound chain missing styles** | 🟡 Medium | `DataBoundComponent` doesn't inherit `BaseStyledComponent` — theme can't set visual properties on data controls via base class |
+| **No CSS auto-bundling** | 🟡 Medium | No mechanism to automatically include theme CSS files |
+| **No build-time .skin parser** | 🟡 Medium | If we want to read actual `.skin` files, we need a parser |
+
+---
+
+## 4. Recommendation
+
+### Overall Suitability Rating: **Medium**
+
+The library has good foundations — `SkinID` and `EnableTheming` parameters exist on every component, and the styling infrastructure (`IStyle`, `WebColor`, `FontInfo`, style builders) is mature. However, there is zero runtime theming infrastructure today. The gap is architectural, not component-level.
+
+### Migration Effort Estimate
+
+For a typical Web Forms app with themes:
+
+| Task | Effort |
+|------|--------|
+| Convert `.skin` files to new format (JSON/C#) | 1–2 hours per theme |
+| Add `` wrapper to Blazor layout | 15 minutes |
+| Move CSS files to `wwwroot/themes/` | 30 minutes |
+| Update ` ` references for theme CSS | 30 minutes |
+| Test visual output matches | 2–4 hours |
+| **Total per theme** | **4–7 hours** |
+
+Most Web Forms apps have 1–3 themes. The bulk of the effort is testing visual fidelity, not conversion.
+
+### Recommended Approach
+
+**Architecture: CascadingValue ThemeProvider with C# configuration**
+
+1. A `ThemeProvider` component wraps the app (or a section) and provides a `ThemeConfiguration` via `CascadingValue`
+2. `ThemeConfiguration` is a C# class containing dictionaries: control type → property defaults, with named skin support
+3. `BaseWebFormsComponent.OnInitialized()` reads the cascading theme and applies matching properties
+4. `BaseStyledComponent` applies visual properties (colors, fonts, borders) from the theme
+5. `EnableTheming=false` skips theme application; `SkinID` selects named skins
+6. Theme/StyleSheetTheme distinction is supported via a mode flag
+
+**Rationale:**
+- C# configuration is type-safe, IDE-friendly, and testable
+- CascadingValue is the idiomatic Blazor mechanism for cross-cutting concerns
+- No build-time tooling required — works immediately in any Blazor project
+- A `.skin` → C# converter can be built later as a migration tool (M12 synergy)
+
+---
+
+## Appendix A: Property Categories Eligible for Theming
+
+Based on Web Forms rules, only appearance properties can be themed. The following property categories from `BaseStyledComponent` are theme-eligible:
+
+| Property | Type | Theme-Eligible |
+|----------|------|---------------|
+| `BackColor` | `WebColor` | ✅ |
+| `ForeColor` | `WebColor` | ✅ |
+| `BorderColor` | `WebColor` | ✅ |
+| `BorderStyle` | `BorderStyle` | ✅ |
+| `BorderWidth` | `Unit` | ✅ |
+| `Height` | `Unit` | ✅ |
+| `Width` | `Unit` | ✅ |
+| `CssClass` | `string` | ✅ |
+| `Font` | `FontInfo` | ✅ |
+| `ToolTip` | `string` | ✅ |
+
+Sub-component styles (e.g., `HeaderStyle`, `RowStyle`, `AlternatingRowStyle` on GridView) are also theme-eligible in Web Forms and should be supported.
diff --git a/planning-docs/SKINS-THEMES-POC-PLAN.md b/planning-docs/SKINS-THEMES-POC-PLAN.md
new file mode 100644
index 000000000..680f040ca
--- /dev/null
+++ b/planning-docs/SKINS-THEMES-POC-PLAN.md
@@ -0,0 +1,300 @@
+# Milestone 10 — Skins & Themes PoC Architecture Plan
+
+**Author:** Forge (Lead / Web Forms Reviewer)
+**Date:** 2026-02-25
+**Milestone:** M10 — Sample Nav, Docs & Test Gaps (PoC portion)
+**Full implementation:** Deferred to M11 — Skins and Themes Implementation
+
+---
+
+## 1. PoC Scope
+
+### Question to Answer
+
+> "Can we faithfully emulate `.skin` file behavior in Blazor, and what does the developer experience look like?"
+
+### In Scope (M10 PoC)
+
+- ✅ `ThemeProvider` component using `CascadingValue`
+- ✅ `ThemeConfiguration` class with control-type defaults and named skins
+- ✅ Base class integration — `BaseStyledComponent` reads from theme during init
+- ✅ `SkinID` un-obsoleted and wired to named skin lookup
+- ✅ `EnableTheming` un-obsoleted and wired to skip logic
+- ✅ Demo with 2–3 controls (Button, Label, GridView) showing themed output
+- ✅ Unit tests proving theme application, SkinID lookup, and EnableTheming opt-out
+
+### Deferred to M11
+
+- ❌ `.skin` file parser (reading actual `.skin` files)
+- ❌ StyleSheetTheme vs Theme priority distinction
+- ❌ CSS auto-bundling from theme folders
+- ❌ Sub-component style theming (HeaderStyle, RowStyle, etc.)
+- ❌ Container-level EnableTheming propagation to children
+- ❌ Theme switching at runtime
+- ❌ Build-time code generation from `.skin` files
+- ❌ Migration tooling integration (M12 synergy)
+
+---
+
+## 2. Architecture Options
+
+### Option A: CascadingValue ThemeProvider with C# Configuration
+
+**How it works:**
+- A `ThemeProvider` Blazor component wraps the app layout
+- It holds a `ThemeConfiguration` object and provides it via `CascadingValue`
+- `ThemeConfiguration` contains `Dictionary` keyed by control type name
+- Each `ControlSkin` has default properties and optional named skins
+- `BaseWebFormsComponent.OnInitialized()` receives the cascading value and applies matching properties
+
+```csharp
+// Developer experience
+
+
+ ...
+
+
+
+@code {
+ ThemeConfiguration myTheme = new ThemeConfiguration()
+ .ForControl("Button", skin => skin
+ .Set(s => s.BackColor, WebColor.FromHtml("#FFDEAD"))
+ .Set(s => s.Font.Bold, true))
+ .ForControl("Button", "goButton", skin => skin
+ .Set(s => s.BackColor, WebColor.FromHtml("#006633"))
+ .Set(s => s.Width, new Unit("120px")));
+}
+```
+
+| Pros | Cons |
+|------|------|
+| Type-safe, IDE-friendly | Requires converting `.skin` files to C# |
+| No build tooling required | More verbose than `.skin` files |
+| Testable with bUnit | Doesn't read existing `.skin` files |
+| Idiomatic Blazor pattern | Learning curve for Web Forms developers used to `.skin` syntax |
+| Works in all Blazor hosting models | |
+
+### Option B: .skin File Parser (Read actual `.skin` files)
+
+**How it works:**
+- A parser reads `.skin` files from `wwwroot/themes/{ThemeName}/`
+- Extracts control declarations and property values
+- Builds a `ThemeConfiguration` at startup (or build time)
+- Same `CascadingValue` delivery mechanism as Option A
+
+```csharp
+// Developer experience
+builder.Services.AddWebFormsTheme("CoolTheme", "wwwroot/themes/CoolTheme/");
+// or
+
+ ...
+
+```
+
+| Pros | Cons |
+|------|------|
+| Direct reuse of existing `.skin` files | Complex parser needed (pseudo-ASPX format) |
+| Familiar to Web Forms developers | `.skin` format is not standard XML |
+| Lower migration effort per theme | Runtime file reading may not work in all Blazor modes (WASM) |
+| | Build-time vs runtime parsing decision |
+| | Harder to test and debug |
+
+### Option C: Blazor CSS Isolation + Component Parameters
+
+**How it works:**
+- Themes are pure CSS, leveraging Blazor's CSS isolation (`::deep` selectors)
+- A `ThemeProvider` applies a CSS class to a wrapper ``
+- Component parameters are not set by themes — only CSS classes/styles change
+
+```css
+/* CoolTheme.razor.css */
+::deep .webforms-button {
+ background-color: #FFDEAD;
+ font-weight: bold;
+}
+```
+
+| Pros | Cons |
+|------|------|
+| Standard CSS — no custom infrastructure | Can't set non-CSS properties (Text, ToolTip, etc.) |
+| Works with Blazor CSS isolation | Only visual properties — not behavioral |
+| No special component logic needed | Doesn't match Web Forms skin semantics |
+| | `.skin` files set component *properties*, not CSS rules |
+| | Breaks the "same attributes and properties" principle |
+
+### Option D: JSON Configuration Files
+
+**How it works:**
+- Themes defined in JSON files: `themes/CoolTheme/theme.json`
+- Deserialized into `ThemeConfiguration` at startup
+- Same `CascadingValue` delivery as Option A
+
+```json
+{
+ "controls": {
+ "Button": {
+ "default": {
+ "BackColor": "#FFDEAD",
+ "Font-Bold": true,
+ "BorderStyle": "Solid"
+ },
+ "goButton": {
+ "Text": "Go",
+ "Width": "120px",
+ "BackColor": "#006633"
+ }
+ }
+ }
+}
+```
+
+| Pros | Cons |
+|------|------|
+| Easy to author and read | Not type-safe at authoring time |
+| Closer to `.skin` file feel | Requires custom deserialization for WebColor, Unit, FontInfo |
+| Can be loaded from static assets | No IDE IntelliSense without JSON schema |
+| Works in all hosting models | Property name mapping complexity |
+
+---
+
+## 3. Recommended Approach
+
+**Option A: CascadingValue ThemeProvider with C# Configuration**
+
+### Rationale
+
+1. **Type safety** — Property assignments are checked at compile time. A typo in `BackColor` fails the build, not at runtime. This is critical for a migration library where correctness is the #1 goal.
+
+2. **IDE experience** — IntelliSense, refactoring, and Go to Definition all work. Developers migrating from Web Forms already face a learning curve; the tooling support reduces friction.
+
+3. **Testability** — bUnit tests can create `ThemeConfiguration` objects directly, assert property values, and verify the full theming pipeline without file I/O or parsing.
+
+4. **No build tooling** — Works immediately. No MSBuild targets, source generators, or file watchers needed. The PoC can ship as a pure library change.
+
+5. **Incremental path** — Option B (`.skin` parser) can be layered on top later. The parser's output would be a `ThemeConfiguration` object, so Options A and B are complementary, not competing.
+
+6. **Blazor-idiomatic** — CascadingValue is how Blazor apps share cross-cutting state (see: `CascadingAuthenticationState`, `EditContext`). Web Forms developers learning Blazor should learn this pattern.
+
+Option C (CSS-only) is **rejected** because Web Forms skins set *component properties*, not CSS rules. A skin can set `Text="Go"` on a Button — CSS cannot do that.
+
+Option D (JSON) is a viable future enhancement but adds deserialization complexity without compile-time safety. Better as a migration aid (M11/M12) than the core mechanism.
+
+---
+
+## 4. Work Items (PoC Sprint)
+
+### WI-1: ThemeConfiguration data model
+**Scope:** Create `ThemeConfiguration`, `ControlSkin`, and `SkinPropertyBag` classes in `src/BlazorWebFormsComponents/Theming/`.
+- `ThemeConfiguration`: dictionary of control type → default `ControlSkin` + named skins
+- `ControlSkin`: property bag with typed getters for style properties
+- Fluent builder API: `.ForControl("Button", skin => skin.Set(...))`
+- Unit tests for configuration building and lookup
+
+**Effort:** S (Small)
+**Assignee:** Cyclops
+
+### WI-2: ThemeProvider cascading component
+**Scope:** Create `ThemeProvider.razor` component that wraps content in `CascadingValue`.
+- Accepts `ThemeConfiguration Theme` parameter
+- Renders `@ChildContent` inside cascading value
+- Supports nesting (inner ThemeProvider overrides outer)
+
+**Effort:** S (Small)
+**Assignee:** Cyclops
+
+### WI-3: Base class theme integration
+**Scope:** Modify `BaseWebFormsComponent` and `BaseStyledComponent` to read and apply theme properties.
+- Add `[CascadingParameter] ThemeConfiguration Theme` to `BaseWebFormsComponent`
+- In `OnInitialized`, resolve control skin by type name and `SkinID`
+- Apply skin properties to component parameters (only if not explicitly set)
+- Respect `EnableTheming=false` to skip
+- Remove `[Obsolete]` from `SkinID` and `EnableTheming`
+
+**Effort:** M (Medium)
+**Assignee:** Cyclops
+
+### WI-4: PoC demo and sample page
+**Scope:** Create a sample page demonstrating theming with Button, Label, and GridView.
+- Define a theme configuration in code
+- Show default skins applied automatically
+- Show named skins via SkinID
+- Show EnableTheming=false opt-out
+- Before/after comparison with Web Forms equivalent
+
+**Effort:** S (Small)
+**Assignee:** Jubilee
+
+### WI-5: bUnit tests for theming pipeline
+**Scope:** Write tests proving the theming pipeline works end-to-end.
+- Test: default skin applies to Button
+- Test: named skin applies when SkinID matches
+- Test: EnableTheming=false prevents skin application
+- Test: explicit parameter values are not overridden by theme (StyleSheetTheme behavior)
+- Test: missing SkinID is handled gracefully (no throw, log warning)
+
+**Effort:** S (Small)
+**Assignee:** Rogue
+
+---
+
+## 5. Success Criteria
+
+The PoC is viable if **all** of the following are true:
+
+| # | Criterion | How to Verify |
+|---|-----------|---------------|
+| 1 | A `ThemeConfiguration` can express default and named skins for multiple control types | Unit test: build config, query properties, assert values |
+| 2 | `ThemeProvider` delivers configuration to nested components via `CascadingValue` | bUnit test: render Button inside ThemeProvider, assert themed output |
+| 3 | Default skins apply automatically without SkinID | bUnit test: Button with no SkinID gets theme's BackColor |
+| 4 | Named skins apply when SkinID matches | bUnit test: Button with SkinID="goButton" gets named skin's Width |
+| 5 | `EnableTheming=false` prevents theme application | bUnit test: Button with EnableTheming=false renders without theme |
+| 6 | Explicitly set parameters are not overridden | bUnit test: `` inside theme still shows Red |
+| 7 | Sample page visually demonstrates themed controls | Manual review: page renders controls with theme styling |
+| 8 | All existing tests continue to pass | CI green: no regressions from theme infrastructure |
+
+---
+
+## 6. Open Questions for Jeff
+
+1. **Theme mode default:** Should the default behavior be `StyleSheetTheme` (theme as defaults, page overrides) or `Theme` (theme overrides page)? Web Forms apps use `Theme` more commonly, but `StyleSheetTheme` is safer for migration. **Recommendation:** Default to `StyleSheetTheme` semantics (don't override explicit values).
+
+2. **Namespace/folder:** Is `BlazorWebFormsComponents.Theming` acceptable for the new types, or should they go in the root namespace?
+
+3. **`.skin` parser priority:** Should M11 focus on a `.skin` file parser (to read existing files directly), or is C#/JSON configuration sufficient? This affects whether Web Forms developers can drag-and-drop their `.skin` files.
+
+4. **Error handling:** When a component specifies `SkinID="foo"` but no skin named "foo" exists, should we: (a) throw an exception (Web Forms behavior), (b) log a warning and continue, or (c) silently ignore? **Recommendation:** (b) log a warning.
+
+5. **Sub-component styles scope:** GridView/DetailsView have 8–12 style sub-components each (HeaderStyle, RowStyle, etc.). Should M11 theme these individually, or only theme the top-level control? **Recommendation:** Support sub-component styles — they're the most common use of GridView skins.
+
+---
+
+## 7. GitHub Issues
+
+The following issues have been created for this PoC:
+
+| WI | Issue # | Title | Milestone |
+|----|---------|-------|-----------|
+| WI-1 | [#364](https://github.com/FritzAndFriends/BlazorWebFormsComponents/issues/364) | ThemeConfiguration data model | M10 |
+| WI-2 | [#365](https://github.com/FritzAndFriends/BlazorWebFormsComponents/issues/365) | ThemeProvider cascading component | M10 |
+| WI-3 | [#366](https://github.com/FritzAndFriends/BlazorWebFormsComponents/issues/366) | Base class theme integration | M10 |
+| WI-4 | [#367](https://github.com/FritzAndFriends/BlazorWebFormsComponents/issues/367) | PoC demo and sample page for theming | M10 |
+| WI-5 | [#368](https://github.com/FritzAndFriends/BlazorWebFormsComponents/issues/368) | bUnit tests for theming pipeline | M10 |
+| M11 | [#369](https://github.com/FritzAndFriends/BlazorWebFormsComponents/issues/369) | M11 Planning: Full Skins & Themes implementation | M11 |
+
+*(Issue numbers will be updated after creation)*
+
+---
+
+## Appendix: M11 Scope Preview
+
+Based on this PoC, M11 should cover:
+
+- StyleSheetTheme vs Theme priority mode
+- `.skin` file parser (for direct migration of existing files)
+- Sub-component style theming (HeaderStyle, RowStyle, etc.)
+- CSS file bundling from theme folders
+- Container-level EnableTheming propagation
+- Runtime theme switching
+- JSON theme format as alternative input
+- Migration tooling integration (`.skin` → C# converter in M12 tool)
+- Documentation: migration guide for Web Forms themes
diff --git a/planning-docs/SUMMARY.md b/planning-docs/SUMMARY.md
index ebb3a83ee..05fc5c100 100644
--- a/planning-docs/SUMMARY.md
+++ b/planning-docs/SUMMARY.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Audit Summary — BlazorWebFormsComponents
**Generated:** 2026-02-12
diff --git a/planning-docs/SiteMapPath.md b/planning-docs/SiteMapPath.md
index ee2f78635..5039b9f05 100644
--- a/planning-docs/SiteMapPath.md
+++ b/planning-docs/SiteMapPath.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# SiteMapPath — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.sitemappath?view=netframework-4.8.1
diff --git a/planning-docs/Substitution.md b/planning-docs/Substitution.md
index 82de929a9..d1d6077d1 100644
--- a/planning-docs/Substitution.md
+++ b/planning-docs/Substitution.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Substitution — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.substitution?view=netframework-4.8
diff --git a/planning-docs/Table.md b/planning-docs/Table.md
index 6176c4352..690bad891 100644
--- a/planning-docs/Table.md
+++ b/planning-docs/Table.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Table — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.table?view=netframework-4.8
diff --git a/planning-docs/TextBox.md b/planning-docs/TextBox.md
index 75e9c9d00..4f10a23fb 100644
--- a/planning-docs/TextBox.md
+++ b/planning-docs/TextBox.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# TextBox — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.textbox?view=netframework-4.8
diff --git a/planning-docs/TreeView.md b/planning-docs/TreeView.md
index 0c32c2ea5..201bcbad8 100644
--- a/planning-docs/TreeView.md
+++ b/planning-docs/TreeView.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# TreeView — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.treeview?view=netframework-4.8.1
diff --git a/planning-docs/ValidationSummary.md b/planning-docs/ValidationSummary.md
index 6b36caac1..31d7c2bc8 100644
--- a/planning-docs/ValidationSummary.md
+++ b/planning-docs/ValidationSummary.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# ValidationSummary — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.validationsummary?view=netframework-4.8
diff --git a/planning-docs/View.md b/planning-docs/View.md
index f049439ce..b36d09180 100644
--- a/planning-docs/View.md
+++ b/planning-docs/View.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# View — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.view?view=netframework-4.8
diff --git a/planning-docs/Xml.md b/planning-docs/Xml.md
index e0899247d..40e7e83e0 100644
--- a/planning-docs/Xml.md
+++ b/planning-docs/Xml.md
@@ -1,3 +1,5 @@
+> **Historical Snapshot (Pre-Milestone 6):** This audit was conducted before Milestones 6-8 which closed the majority of gaps listed below. For current status, see `status.md` and `planning-docs/MILESTONE9-PLAN.md`.
+
# Xml — Feature Comparison Audit
**ASP.NET Docs:** https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.xml?view=netframework-4.8
diff --git a/samples/AfterBlazorServerSide/Components/App.razor b/samples/AfterBlazorServerSide/Components/App.razor
index a376eb4d6..7da621dd0 100644
--- a/samples/AfterBlazorServerSide/Components/App.razor
+++ b/samples/AfterBlazorServerSide/Components/App.razor
@@ -10,6 +10,7 @@
+