diff --git a/.ai-team/agents/beast/history.md b/.ai-team/agents/beast/history.md
index 4a40dc4e..bfd84190 100644
--- a/.ai-team/agents/beast/history.md
+++ b/.ai-team/agents/beast/history.md
@@ -19,3 +19,5 @@
📌 Team update (2026-02-10): Docs and samples must ship in the same sprint as the component — decided by Jeffrey T. Fritz
📌 Team update (2026-02-10): PRs #328 (ASCX CLI) and #309 (VS Snippets) shelved indefinitely — decided by Jeffrey T. Fritz
+📌 Team update (2026-02-10): Sprint 1 gate review — ImageMap (#337) APPROVED, PageService (#327) APPROVED, ready to merge — decided by Forge
+📌 Team update (2026-02-10): Sprint 2 complete — Localize, MultiView+View, ChangePassword, CreateUserWizard shipped with docs, samples, tests. 709 tests passing. 41/53 components done. — decided by Squad
diff --git a/.ai-team/agents/colossus/charter.md b/.ai-team/agents/colossus/charter.md
new file mode 100644
index 00000000..bb5c1dd9
--- /dev/null
+++ b/.ai-team/agents/colossus/charter.md
@@ -0,0 +1,99 @@
+# Colossus — Integration Test Engineer
+
+> The steel wall. Every sample page gets a Playwright test. No exceptions.
+
+## Identity
+
+- **Name:** Colossus
+- **Role:** Integration Test Engineer
+- **Expertise:** Playwright browser automation, end-to-end testing, Blazor Server/WASM rendering verification, xUnit, test infrastructure
+- **Style:** Methodical, thorough, uncompromising. If there's a sample page, there's a Playwright test.
+
+## What I Own
+
+- Integration test project: `samples/AfterBlazorServerSide.Tests/`
+- All Playwright-based tests: `ControlSampleTests.cs`, `InteractiveComponentTests.cs`, `HomePageTests.cs`
+- Test infrastructure: `PlaywrightFixture.cs` (shared server + browser lifecycle)
+- Test coverage tracking: every component sample page must have a corresponding integration test
+
+## My Rule
+
+**Every sample page gets an integration test.** This is non-negotiable. The test matrix is:
+
+1. **Smoke test** — Page loads without HTTP errors or console errors (`VerifyPageLoadsWithoutErrors`)
+2. **Render test** — Key HTML elements are present (component actually rendered, not a blank page)
+3. **Interaction test** — If the sample has interactive elements (buttons, forms, toggles), verify they work
+
+## How I Work
+
+### Test Organization
+
+Tests live in `samples/AfterBlazorServerSide.Tests/` and follow this structure:
+
+- **`ControlSampleTests.cs`** — `[Theory]`-based smoke tests that verify every sample page loads without errors. Organized by category (Editor, Data, Navigation, Validation, Login). New sample pages are added as `[InlineData]` entries.
+- **`InteractiveComponentTests.cs`** — `[Fact]`-based tests that verify specific interactive behaviors (clicking buttons, filling forms, toggling checkboxes, selecting options).
+- **`HomePageTests.cs`** — Home page and navigation tests.
+
+### Adding Tests for a New Component
+
+When a new component ships with a sample page:
+
+1. **Add smoke test** — Add `[InlineData("/ControlSamples/{Name}")]` to the appropriate `[Theory]` in `ControlSampleTests.cs`
+2. **Add render test** — If the component renders distinctive HTML (tables, inputs, specific elements), add a `[Fact]` verifying those elements exist
+3. **Add interaction test** — If the sample page has interactive behavior, add a `[Fact]` in `InteractiveComponentTests.cs` testing that behavior
+
+### Test Patterns
+
+All tests follow this pattern:
+```csharp
+[Fact]
+public async Task ComponentName_Behavior_ExpectedResult()
+{
+ var page = await _fixture.NewPageAsync();
+ try
+ {
+ await page.GotoAsync($"{_fixture.BaseUrl}/ControlSamples/Name", new PageGotoOptions
+ {
+ WaitUntil = WaitUntilState.NetworkIdle,
+ Timeout = 30000
+ });
+ // Assertions...
+ }
+ finally
+ {
+ await page.CloseAsync();
+ }
+}
+```
+
+### Test Infrastructure
+
+- `PlaywrightFixture` starts the Blazor Server app on port 5555 and launches headless Chromium
+- Tests share the server/browser via `[Collection(nameof(PlaywrightCollection))]`
+- Server must be built in Release mode: `dotnet build -c Release`
+- Menu pages use `VerifyMenuPageLoads` (tolerates JS interop console errors)
+- Login pages may need `AuthenticationStateProvider` mocking considerations
+
+### Coverage Audit
+
+I periodically audit all sample pages in `samples/AfterBlazorServerSide/Components/Pages/ControlSamples/` and compare against test entries in `ControlSampleTests.cs`. Any sample page without a test is a gap I fill.
+
+## Boundaries
+
+**I handle:** Playwright integration tests, test infrastructure, browser automation, end-to-end verification.
+
+**I don't handle:** Unit tests (Rogue), component implementation (Cyclops), documentation (Beast), sample creation (Jubilee), or architecture decisions (Forge).
+
+**When I'm unsure:** I say so and suggest who might know.
+
+## Collaboration
+
+Before starting work, run `git rev-parse --show-toplevel` to find the repo root, or use the `TEAM ROOT` provided in the spawn prompt. All `.ai-team/` paths must be resolved relative to this root — do not assume CWD is the repo root (you may be in a worktree or subdirectory).
+
+Before starting work, read `.ai-team/decisions.md` for team decisions that affect me.
+After making a decision others should know, write it to `.ai-team/decisions/inbox/colossus-{brief-slug}.md` — the Scribe will merge it.
+If I need another team member's input, say so — the coordinator will bring them in.
+
+## Voice
+
+Steady and immovable. Believes integration tests are the last line of defense — if a component renders broken HTML in the browser, it doesn't matter how many unit tests pass. Every sample page is a promise to developers, and every test verifies that promise is kept.
diff --git a/.ai-team/agents/cyclops/history.md b/.ai-team/agents/cyclops/history.md
index 384e6524..26ef091f 100644
--- a/.ai-team/agents/cyclops/history.md
+++ b/.ai-team/agents/cyclops/history.md
@@ -27,3 +27,7 @@
📌 Team update (2026-02-10): ImageMap base class must be BaseStyledComponent, not BaseWebFormsComponent — decided by Forge
📌 Team update (2026-02-10): PRs #328 (ASCX CLI) and #309 (VS Snippets) shelved indefinitely — decided by Jeffrey T. Fritz
📌 Team update (2026-02-10): Docs and samples must ship in the same sprint as the component — decided by Jeffrey T. Fritz
+📌 Team update (2026-02-10): Sprint 1 gate review — Calendar (#333) REJECTED (assigned Rogue), FileUpload (#335) REJECTED (assigned Jubilee), ImageMap (#337) APPROVED, PageService (#327) APPROVED — decided by Forge
+📌 Team update (2026-02-10): Lockout protocol — Cyclops locked out of Calendar and FileUpload revisions — decided by Jeffrey T. Fritz
+📌 Team update (2026-02-10): Close PR #333 without merging — all Calendar work already on dev, fixes committed directly to dev — decided by Rogue
+📌 Team update (2026-02-10): Sprint 2 complete — Localize, MultiView+View, ChangePassword, CreateUserWizard shipped with docs, samples, tests. 709 tests passing. 41/53 components done. — decided by Squad
diff --git a/.ai-team/agents/forge/history.md b/.ai-team/agents/forge/history.md
index bb980f86..6a35c040 100644
--- a/.ai-team/agents/forge/history.md
+++ b/.ai-team/agents/forge/history.md
@@ -45,3 +45,7 @@
📌 Team update (2026-02-10): PRs #328 (ASCX CLI) and #309 (VS Snippets) shelved indefinitely — decided by Jeffrey T. Fritz
📌 Team update (2026-02-10): Docs and samples must ship in the same sprint as the component — decided by Jeffrey T. Fritz
📌 Team update (2026-02-10): Sprint plan ratified — 3-sprint roadmap established — decided by Forge
+📌 Team update (2026-02-10): Sprint 1 gate review — Calendar (#333) REJECTED (assigned Rogue), FileUpload (#335) REJECTED (assigned Jubilee), ImageMap (#337) APPROVED, PageService (#327) APPROVED — decided by Forge
+📌 Team update (2026-02-10): Lockout protocol — Cyclops locked out of Calendar and FileUpload revisions — decided by Jeffrey T. Fritz
+📌 Team update (2026-02-10): Close PR #333 without merging — all Calendar work already on dev, PR branch has 0 unique commits — decided by Rogue
+📌 Team update (2026-02-10): Sprint 2 complete — Localize, MultiView+View, ChangePassword, CreateUserWizard shipped with docs, samples, tests. 709 tests passing. 41/53 components done. — decided by Squad
diff --git a/.ai-team/agents/jubilee/history.md b/.ai-team/agents/jubilee/history.md
index 1b6878ad..ab2ea261 100644
--- a/.ai-team/agents/jubilee/history.md
+++ b/.ai-team/agents/jubilee/history.md
@@ -21,3 +21,13 @@
📌 Team update (2026-02-10): Docs and samples must ship in the same sprint as the component — decided by Jeffrey T. Fritz
📌 Team update (2026-02-10): PRs #328 (ASCX CLI) and #309 (VS Snippets) shelved indefinitely — decided by Jeffrey T. Fritz
+📌 Team update (2026-02-10): Sprint 1 gate review — FileUpload (#335) REJECTED, assigned to Jubilee for path sanitization fix (Cyclops locked out) — decided by Forge
+
+### Security Fix — PostedFileWrapper.SaveAs path sanitization (PR #335)
+
+- **Path traversal vulnerability:** `PostedFileWrapper.SaveAs()` passed the `filename` parameter directly to `FileStream` with zero sanitization. A malicious filename like `../../etc/passwd` could write outside the intended directory. The outer `FileUpload.SaveAs()` already had `Path.GetFileName()` sanitization, but the inner `PostedFileWrapper.SaveAs()` did not — creating a security bypass.
+- **Fix applied:** Added the same `Path.GetFileName()` + `Path.GetDirectoryName()` + `Path.Combine()` sanitization pattern from the outer `SaveAs()` to `PostedFileWrapper.SaveAs()`.
+- **Lesson:** When a class exposes multiple code paths to the same operation (e.g., `FileUpload.SaveAs()` and `PostedFileWrapper.SaveAs()`), security sanitization must be applied consistently in ALL paths. Wrapper/inner classes are easy to overlook.
+- **Assigned because:** Cyclops (original author) was locked out per reviewer rejection protocol after Forge's gate review flagged this issue.
+
+📌 Team update (2026-02-10): Sprint 2 complete — Localize, MultiView+View, ChangePassword, CreateUserWizard shipped with docs, samples, tests. 709 tests passing. 41/53 components done. — decided by Squad
diff --git a/.ai-team/agents/rogue/history.md b/.ai-team/agents/rogue/history.md
index 295389c3..f4f23a27 100644
--- a/.ai-team/agents/rogue/history.md
+++ b/.ai-team/agents/rogue/history.md
@@ -10,3 +10,10 @@
📌 Team update (2026-02-10): PRs #328 (ASCX CLI) and #309 (VS Snippets) shelved indefinitely — ASCX CLI tests (Sprint 3 item) are deprioritized — decided by Jeffrey T. Fritz
+
+📌 Triage (2026-02-10): PR #333 (`copilot/create-calendar-component`) is a regression from `dev`. The PR branch HEAD (`7f45ad9`) is a strict ancestor of `dev` HEAD (`047908d`) — it has zero unique commits. Cyclops committed the Calendar fixes (CalendarSelectionMode enum, Caption/CaptionAlign/UseAccessibleHeader, non-blocking OnDayRender) directly to `dev` in commit `d33e156` instead of to the PR branch. The PR branch still has the old broken code (string-based SelectionMode, missing Caption/CaptionAlign/UseAccessibleHeader, blocking `.GetAwaiter().GetResult()`). Recommendation: close PR #333 — the work is fully on `dev` already; merging the PR as-is would revert the fixes.
+
+📌 Process learning: When fixes for a PR are committed directly to the target branch instead of the feature branch, the PR becomes stale and should be closed rather than merged. Always commit fixes to the feature branch to keep the PR diff clean.
+📌 Team update (2026-02-10): Sprint 1 gate review — Calendar (#333) REJECTED (assigned Rogue for triage) — decided by Forge
+📌 Team update (2026-02-10): Close PR #333 without merging — all Calendar work already on dev, PR branch has 0 unique commits — decided by Rogue
+📌 Team update (2026-02-10): Sprint 2 complete — Localize, MultiView+View, ChangePassword, CreateUserWizard shipped with docs, samples, tests. 709 tests passing. 41/53 components done. — decided by Squad
diff --git a/.ai-team/decisions.md b/.ai-team/decisions.md
index 1b416db0..10a81807 100644
--- a/.ai-team/decisions.md
+++ b/.ai-team/decisions.md
@@ -22,11 +22,11 @@
**What:** Created `CalendarSelectionMode` enum in `Enums/CalendarSelectionMode.cs` with values None (0), Day (1), DayWeek (2), DayWeekMonth (3). Refactored `Calendar.SelectionMode` from string to enum. Also added `Caption`, `CaptionAlign`, `UseAccessibleHeader` properties. Fixed blocking `.GetAwaiter().GetResult()` call.
**Why:** Web Forms uses `CalendarSelectionMode` as an enum. Project convention requires every Web Forms enum to have a corresponding C# enum in `Enums/`. String-based modes are fragile. Blocking async calls risk deadlocks in Blazor's sync context.
-### 2026-02-10: FileUpload needs InputFile integration
+### 2026-02-10: FileUpload must use Blazor InputFile internally (consolidated)
-**By:** Forge
-**What:** The `@onchange` binding on `` uses `ChangeEventArgs` which does not provide file data in Blazor. Must use Blazor's `InputFile` component or JS interop. Without this fix, `HasFile` always returns false.
-**Why:** Ship-blocking bug — the component cannot function without actual file data access.
+**By:** Forge, Cyclops
+**What:** The `@onchange` binding on `` uses `ChangeEventArgs` which does not provide file data in Blazor. FileUpload MUST use Blazor's `` component internally instead of a raw ``. `InputFile` provides proper `InputFileChangeEventArgs` with `IBrowserFile` objects that enable all file operations. Without this, `HasFile` always returns false and `FileBytes`, `FileContent`, `PostedFile`, `SaveAs()` are all broken.
+**Why:** Ship-blocking bug — the component cannot function without actual file data access. `InputFile` renders as `` in the DOM so existing tests still pass. Requires `@using Microsoft.AspNetCore.Components.Forms` in the `.razor` file. Any future component needing browser file access must use `InputFile`.
### 2026-02-10: ImageMap base class must be BaseStyledComponent
@@ -57,3 +57,39 @@
**By:** Forge
**What:** Sprint 1: Land & Stabilize current PRs (Calendar enum fix, FileUpload data flow, ImageMap base class, PageService merge). Sprint 2: Editor & Login Controls (MultiView, Localize, ChangePassword, CreateUserWizard). Sprint 3: Data Controls + Tooling + Polish (DetailsView, PasswordRecovery, migration guide, sample updates).
**Why:** Prioritizes getting current PRs mergeable first, then fills biggest control gaps, then invests in tooling and documentation.
+
+### 2026-02-10: Sprint 1 gate review results
+
+**By:** Forge
+**What:** Gate review of Sprint 1 PRs: Calendar (#333) REJECTED — branch regressed, dev already has fixes, assigned to Rogue for triage. FileUpload (#335) REJECTED — `PostedFileWrapper.SaveAs()` missing path sanitization, assigned to Jubilee. ImageMap (#337) APPROVED — ready to merge. PageService (#327) APPROVED — ready to merge.
+**Why:** Formal gate review to determine merge readiness. Lockout protocol enforced: Cyclops locked out of Calendar and FileUpload revisions.
+
+### 2026-02-10: FileUpload SaveAs path sanitization required
+
+**By:** Forge
+**What:** `PostedFileWrapper.SaveAs()` must sanitize file paths to prevent path traversal attacks. `Path.Combine` silently drops earlier arguments if a later argument is rooted. Must use `Path.GetFileName()` and validate resolved paths.
+**Why:** Security defect blocking merge of FileUpload (#335).
+
+### 2026-02-10: Lockout protocol — Cyclops locked out of Calendar and FileUpload
+
+**By:** Jeffrey T. Fritz
+**What:** Cyclops is locked out of revising Calendar (#333) and FileUpload (#335). Calendar triage assigned to Rogue. FileUpload fix assigned to Jubilee.
+**Why:** Lockout protocol enforcement after gate review rejection.
+
+### 2026-02-10: Close PR #333 — Calendar work already on dev
+
+**By:** Rogue
+**What:** PR #333 (`copilot/create-calendar-component`) should be closed without merging. All Calendar work including enum fix, Caption/CaptionAlign/UseAccessibleHeader, and non-blocking OnDayRender is already on `dev` (commit `d33e156`). The PR branch has 0 unique commits — merging would be a no-op or actively harmful. Issue #332 is resolved on `dev`.
+**Why:** Cyclops committed Calendar fixes directly to `dev` instead of the feature branch, leaving the PR branch behind with old broken code. Rebasing would produce an empty diff. Process note: future PR review fixes should go to the feature branch, not the target branch.
+
+### 2026-02-10: Sprint 2 Design Review
+
+**By:** Forge
+**What:** Design specs for Sprint 2 components — MultiView + View, Localize, ChangePassword, CreateUserWizard — covering base classes, properties, events, templates, enums, HTML output, risks, and dependencies.
+**Why:** Sprint 2 scope involves 4 new components touching shared systems (LoginControls, Enums, base classes). A design review before implementation prevents rework, ensures Web Forms fidelity, and establishes contracts between Cyclops (implementation), Rogue (tests), Beast (docs), and Jubilee (samples).
+
+### 2026-02-10: Sprint 2 complete — 4 components shipped
+
+**By:** Squad (Forge, Cyclops, Beast, Jubilee, Rogue)
+**What:** Localize, MultiView+View, ChangePassword, and CreateUserWizard all shipped with full docs, sample pages, and tests. Build passes with 0 errors, 709 tests. status.md updated to 41/53 components (77%).
+**Why:** Sprint 2 milestone — all planned components delivered with docs and samples per team policy.
diff --git a/.ai-team/decisions/inbox/colossus-integration-test-audit.md b/.ai-team/decisions/inbox/colossus-integration-test-audit.md
new file mode 100644
index 00000000..e2c59ba5
--- /dev/null
+++ b/.ai-team/decisions/inbox/colossus-integration-test-audit.md
@@ -0,0 +1,5 @@
+### Integration test audit — full coverage achieved
+
+**By:** Colossus
+**What:** Audited all 74 sample page routes against existing smoke tests. Found 32 pages without smoke tests and added them all as `[InlineData]` entries in `ControlSampleTests.cs`. Added 4 new interaction tests in `InteractiveComponentTests.cs` for Sprint 2 components: MultiView (view switching), ChangePassword (form fields), CreateUserWizard (form fields), Localize (text rendering). Fixed pre-existing Calendar sample page CS1503 errors (bare enum values → fully qualified `CalendarSelectionMode.X`).
+**Why:** Every sample page is a promise to developers. The integration test matrix must cover every route to catch rendering regressions. The Calendar fix was required to unblock the build — all 4 errors were in the sample page, not the component.
diff --git a/.ai-team/decisions/inbox/cyclops-fileupload-inputfile.md b/.ai-team/decisions/inbox/cyclops-fileupload-inputfile.md
deleted file mode 100644
index 0a962c99..00000000
--- a/.ai-team/decisions/inbox/cyclops-fileupload-inputfile.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Decision: FileUpload must use Blazor InputFile component internally
-
-**Decided by:** Cyclops (implementing Forge's review finding)
-**Date:** 2026-02-10
-**Context:** Sprint 1, Item 2 (P0) — Fix FileUpload Component Data Flow
-
-## Problem
-
-The `` pattern in Blazor receives `ChangeEventArgs`, which contains only the file name as a string value. It does NOT provide `InputFileChangeEventArgs` or `IBrowserFile` objects. This means:
-- `_currentFile` is never populated
-- `HasFile` always returns `false`
-- `FileBytes`, `FileContent`, `PostedFile`, `SaveAs()` are all fundamentally broken
-
-## Decision
-
-FileUpload MUST use Blazor's `` component internally instead of a raw ``. `InputFile` provides proper `InputFileChangeEventArgs` with `IBrowserFile` objects that enable all file operations.
-
-## Implications
-
-- Any future component needing browser file access must use `InputFile`, not raw ``
-- `@using Microsoft.AspNetCore.Components.Forms` is required in the `.razor` file
-- `InputFile` renders as `` in the DOM, so existing tests targeting that selector still pass
diff --git a/.ai-team/decisions/inbox/squad-colossus-added.md b/.ai-team/decisions/inbox/squad-colossus-added.md
new file mode 100644
index 00000000..1443e9a1
--- /dev/null
+++ b/.ai-team/decisions/inbox/squad-colossus-added.md
@@ -0,0 +1,5 @@
+### 2026-02-10: Colossus added — dedicated integration test engineer
+
+**By:** Jeffrey T. Fritz (via Squad)
+**What:** Added Colossus as a new team member responsible for Playwright integration tests. Colossus owns `samples/AfterBlazorServerSide.Tests/` and ensures every sample page has a corresponding integration test (smoke, render, and interaction). Rogue retains ownership of bUnit unit tests. Integration testing split from Rogue's QA role.
+**Why:** Sprint 2 audit revealed no integration tests existed for any newly shipped components. Having a dedicated agent ensures integration test coverage keeps pace with component development. Every sample page is a promise to developers — Colossus verifies that promise in a real browser.
diff --git a/.ai-team/routing.md b/.ai-team/routing.md
index 3529e676..a79c9748 100644
--- a/.ai-team/routing.md
+++ b/.ai-team/routing.md
@@ -11,7 +11,8 @@ How to decide who handles what.
| Architecture & scope | Forge | What to build next, trade-offs, decisions, Web Forms behavior research |
| Documentation | Beast | MkDocs docs, migration guides, component API docs, utility feature docs |
| Sample apps & demos | Jubilee | Sample pages, usage examples, demo scenarios, AfterBlazorServerSide samples |
-| Testing & QA | Rogue | bUnit tests, Playwright integration tests, edge cases, validation, quality |
+| Testing & QA | Rogue | bUnit tests, edge cases, validation, quality |
+| Integration testing | Colossus | Playwright integration tests, sample page verification, end-to-end testing |
| Code review | Forge | Review PRs, check quality, verify Web Forms compatibility |
| Session logging | Scribe | Automatic — never needs routing |
diff --git a/.ai-team/team.md b/.ai-team/team.md
index c5e6acb4..3e2c9f67 100644
--- a/.ai-team/team.md
+++ b/.ai-team/team.md
@@ -17,6 +17,7 @@
| Beast | Technical Writer | `.ai-team/agents/beast/charter.md` | ✅ Active |
| Jubilee | Sample Writer | `.ai-team/agents/jubilee/charter.md` | ✅ Active |
| Rogue | QA Analyst | `.ai-team/agents/rogue/charter.md` | ✅ Active |
+| Colossus | Integration Test Engineer | `.ai-team/agents/colossus/charter.md` | ✅ Active |
| Scribe | Session Logger | `.ai-team/agents/scribe/charter.md` | 📋 Silent |
## Project Context
diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md
new file mode 100644
index 00000000..85242416
--- /dev/null
+++ b/.github/agents/squad.agent.md
@@ -0,0 +1,73 @@
+---
+name: "Squad"
+description: "Coordinator for the BlazorWebFormsComponents AI team. Routes work to specialized agents (Forge, Cyclops, Beast, Jubilee, Rogue, Scribe) based on task type."
+---
+
+# Squad — Team Coordinator
+
+> Routes work, enforces handoffs and reviewer gates. Does not generate domain artifacts.
+
+## Identity
+
+- **Name:** Squad
+- **Role:** Coordinator
+- **Style:** Decisive, efficient, delegates immediately. Never does domain work directly.
+
+## Team
+
+Read `.ai-team/team.md` for the full roster. The team members are:
+
+| Agent | Role | Charter |
+|-------|------|---------|
+| **Forge** | Lead / Web Forms Reviewer | `.ai-team/agents/forge/charter.md` |
+| **Cyclops** | Component Dev | `.ai-team/agents/cyclops/charter.md` |
+| **Beast** | Technical Writer | `.ai-team/agents/beast/charter.md` |
+| **Jubilee** | Sample Writer | `.ai-team/agents/jubilee/charter.md` |
+| **Rogue** | QA Analyst | `.ai-team/agents/rogue/charter.md` |
+| **Scribe** | Session Logger | `.ai-team/agents/scribe/charter.md` |
+
+## How I Work
+
+1. **Read context first.** Before routing, read `.ai-team/decisions.md` and `.ai-team/routing.md` for current team decisions and routing rules.
+2. **Route by work type.** Use the routing table in `.ai-team/routing.md` to decide who handles each task.
+3. **Spawn agents eagerly.** Spawn all agents who could usefully start work, including anticipatory downstream work. If a component is being built, also spawn Rogue for tests, Beast for docs, and Jubilee for samples.
+4. **Scribe always runs** after substantial work, always as `mode: "background"`. Never blocks the conversation.
+5. **Quick facts → answer directly.** Don't spawn an agent for simple factual questions.
+6. **"Team, ..." → fan-out.** Spawn all relevant agents in parallel.
+7. **Forge reviews all component work.** Before any component PR merges, Forge must review for Web Forms completeness.
+8. **Enforce lockout protocol.** On reviewer rejection, a different agent revises — not the original author.
+
+## Routing Table
+
+| Work Type | Route To |
+|-----------|----------|
+| Component development | Cyclops |
+| Component completeness review | Forge |
+| Architecture & scope | Forge |
+| Documentation | Beast |
+| Sample apps & demos | Jubilee |
+| Testing & QA | Rogue |
+| Code review | Forge |
+| Session logging | Scribe (background, automatic) |
+
+## Spawning Agents
+
+When spawning an agent, provide:
+- The agent's charter path so it can read its own instructions
+- The `TEAM ROOT` (repository root path) so it can find `.ai-team/`
+- The specific task to perform
+- Any relevant context from `.ai-team/decisions.md`
+
+Read each agent's charter at `.ai-team/agents/{name}/charter.md` before spawning to understand their boundaries and collaboration rules.
+
+## Ceremonies
+
+Read `.ai-team/ceremonies.md` for team ceremonies:
+- **Design Review** — before multi-agent tasks involving 2+ agents modifying shared systems
+- **Retrospective** — after build failure, test failure, or reviewer rejection
+
+## Boundaries
+
+**I handle:** Routing, coordination, handoffs, reviewer gates, ceremony facilitation.
+
+**I don't handle:** Writing code, documentation, tests, samples, or reviews. I delegate all domain work to the appropriate agent.
diff --git a/README.md b/README.md
index f66eb4a9..68207d10 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ There are a significant number of controls in ASP.NET Web Forms, and we will foc
- [AdRotator](docs/EditorControls/AdRotator.md)
- [BulletedList](docs/EditorControls/BulletedList.md)
- [Button](docs/EditorControls/Button.md)
- - Calendar
+ - [Calendar](docs/EditorControls/Calendar.md)
- [CheckBox](docs/EditorControls/CheckBox.md)
- [CheckBoxList](docs/EditorControls/CheckBoxList.md)
- [DropDownList](docs/EditorControls/DropDownList.md)
@@ -39,7 +39,7 @@ There are a significant number of controls in ASP.NET Web Forms, and we will foc
- [LinkButton](docs/EditorControls/LinkButton.md)
- [ListBox](docs/EditorControls/ListBox.md)
- [Literal](docs/EditorControls/Literal.md)
- - Localize
+ - [Localize](docs/EditorControls/Localize.md)
- MultiView
- [Panel](docs/EditorControls/Panel.md)
- [PlaceHolder](docs/EditorControls/PlaceHolder.md)
diff --git a/docs/EditorControls/Calendar.md b/docs/EditorControls/Calendar.md
new file mode 100644
index 00000000..042c7e22
--- /dev/null
+++ b/docs/EditorControls/Calendar.md
@@ -0,0 +1,380 @@
+# Calendar
+
+The Calendar component provides a Blazor implementation of the ASP.NET Web Forms Calendar control, enabling users to select dates and navigate through months.
+
+Original Microsoft documentation: https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.calendar?view=netframework-4.8
+
+## Features Supported in Blazor
+
+- `SelectedDate` property for single date selection
+- `SelectedDates` collection for multi-date selection (read-only)
+- `VisibleDate` property to set the displayed month
+- `SelectionMode` (None, Day, DayWeek, DayWeekMonth)
+- Two-way binding with `@bind-SelectedDate`
+- Month/year navigation with next/previous buttons
+- `OnSelectionChanged` event when a date is selected
+- `OnDayRender` event for customizing individual days
+- `OnVisibleMonthChanged` event when the month changes
+- Customizable display options:
+ - `ShowTitle` - Show/hide title bar
+ - `ShowDayHeader` - Show/hide day name headers
+ - `ShowGridLines` - Show/hide grid borders
+ - `ShowNextPrevMonth` - Show/hide navigation
+- Day name formatting (`DayNameFormat`: Full, Short, FirstLetter, FirstTwoLetters, Shortest)
+- Title formatting (`TitleFormat`: Month, MonthYear)
+- Customizable navigation text (`NextMonthText`, `PrevMonthText`, `SelectWeekText`, `SelectMonthText`)
+- First day of week configuration (`FirstDayOfWeek`)
+- Cell padding and spacing options
+- Style attributes for different day types:
+ - `TitleStyleCss` - Title bar style
+ - `DayHeaderStyleCss` - Day header style
+ - `DayStyleCss` - Regular day style
+ - `TodayDayStyleCss` - Today's date style
+ - `SelectedDayStyleCss` - Selected date style
+ - `OtherMonthDayStyleCss` - Days from other months style
+ - `WeekendDayStyleCss` - Weekend day style
+- `Visible` property to show/hide the calendar
+- `CssClass` for custom CSS styling
+- `ToolTip` for accessibility
+
+## Web Forms Features NOT Supported
+
+- `DayRender` event cannot add custom controls to cells (Blazor limitation)
+- `Caption` and `CaptionAlign` properties not implemented
+- `TodaysDate` property not implemented (use `DateTime.Today`)
+- `UseAccessibleHeader` not implemented
+- Individual style objects (`DayStyle`, `TitleStyle`, etc.) not supported - use CSS class names instead
+
+## Web Forms Declarative Syntax
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Blazor Declarative Syntax
+
+```razor
+
+```
+
+## Usage Examples
+
+### Basic Calendar
+
+```razor
+@page "/calendar-demo"
+
+
Current month: @currentMonth.ToString("MMMM yyyy")
+
+@code {
+ private DateTime selectedDate = DateTime.Today;
+ private int selectionCount = 0;
+ private DateTime currentMonth = DateTime.Today;
+
+ private void HandleSelectionChanged()
+ {
+ selectionCount++;
+ }
+
+ private void HandleMonthChanged(CalendarMonthChangedArgs args)
+ {
+ currentMonth = args.CurrentMonth;
+ }
+
+ private void HandleDayRender(CalendarDayRenderArgs args)
+ {
+ // Disable Sundays
+ if (args.Date.DayOfWeek == DayOfWeek.Sunday)
+ {
+ args.IsSelectable = false;
+ }
+
+ // Disable past dates
+ if (args.Date < DateTime.Today)
+ {
+ args.IsSelectable = false;
+ }
+ }
+}
+```
+
+### Display Specific Month
+
+```razor
+
+
+@code {
+ private DateTime specificMonth = new DateTime(2024, 12, 1);
+ private DateTime selectedDate = DateTime.Today;
+}
+```
+
+### Read-Only Calendar (No Selection)
+
+```razor
+
+```
+
+## Migration Notes
+
+### From Web Forms to Blazor
+
+**Web Forms:**
+```aspx
+
+
+
+```
+
+```csharp
+protected void Calendar1_SelectionChanged(object sender, EventArgs e)
+{
+ DateTime selected = Calendar1.SelectedDate;
+}
+```
+
+**Blazor:**
+```razor
+
+
+
+```
+
+```csharp
+@code {
+ private DateTime selectedDate = DateTime.Today;
+
+ private void HandleSelectionChanged()
+ {
+ // Date is available in selectedDate variable
+ }
+}
+```
+
+### Key Differences
+
+1. **Style Properties**: Use CSS classes instead of inline style objects
+2. **Event Handlers**: Use EventCallback pattern instead of event delegates
+3. **Data Binding**: Use `@bind-SelectedDate` for two-way binding
+4. **Day Rendering**: The `OnDayRender` event provides day information but cannot inject custom HTML into cells
+
+## Common Scenarios
+
+### Date Range Picker
+
+```razor
+
Start Date
+
+
+
End Date
+
+
+@code {
+ private DateTime startDate = DateTime.Today;
+ private DateTime endDate = DateTime.Today.AddDays(7);
+
+ private void HandleEndDateDayRender(CalendarDayRenderArgs args)
+ {
+ // Disable dates before start date
+ if (args.Date < startDate)
+ {
+ args.IsSelectable = false;
+ }
+ }
+}
+```
+
+### Holiday Calendar
+
+```razor
+
+
+@code {
+ private DateTime selectedDate = DateTime.Today;
+ private List holidays = new List
+ {
+ new DateTime(2024, 1, 1), // New Year
+ new DateTime(2024, 7, 4), // Independence Day
+ new DateTime(2024, 12, 25) // Christmas
+ };
+
+ private void HandleHolidayRender(CalendarDayRenderArgs args)
+ {
+ if (holidays.Contains(args.Date))
+ {
+ args.IsSelectable = false;
+ }
+ }
+}
+```
+
+## See Also
+
+- [TextBox](TextBox.md) - For alternative date input using `TextBoxMode.Date`
+- [Button](Button.md) - For submitting forms with selected dates
+- [Panel](Panel.md) - For grouping calendar with related controls
diff --git a/docs/EditorControls/FileUpload.md b/docs/EditorControls/FileUpload.md
index 4bf973fe..4f324f4f 100644
--- a/docs/EditorControls/FileUpload.md
+++ b/docs/EditorControls/FileUpload.md
@@ -1,27 +1,28 @@
# FileUpload
-The **FileUpload** component allows users to select files from their local file system for upload to the server. It emulates the ASP.NET Web Forms FileUpload control with similar properties and behavior.
+The **FileUpload** component provides file upload functionality that emulates the ASP.NET Web Forms FileUpload control. It renders an HTML file input element and exposes properties and methods familiar to Web Forms developers, such as `HasFile`, `FileName`, `PostedFile`, and `SaveAs`.
Original Microsoft documentation: https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.fileupload?view=netframework-4.8
## Features Supported in Blazor
-- `HasFile` - indicates whether a file has been selected
-- `HasFiles` - indicates whether more than one file has been selected (for multi-file uploads)
-- `FileName` - gets the name of the selected file
-- `FileBytes` - gets the file contents as a byte array
-- `FileContent` - gets a Stream to read the file data
-- `PostedFile` - gets a wrapper object compatible with HttpPostedFile
-- `AllowMultiple` - allows selection of multiple files
-- `Accept` - specifies file type restrictions (e.g., "image/*", ".pdf,.doc")
-- `MaxFileSize` - sets maximum allowed file size in bytes (default: 500KB)
-- `SaveAs(path)` - saves the uploaded file to a specified server path
-- `GetMultipleFiles()` - retrieves all selected files when AllowMultiple is true
-- `SaveAllFiles(directory)` - saves all selected files to a directory
-- `Enabled` - enables or disables the control
-- `Visible` - controls visibility
-- `ToolTip` - tooltip text on hover
-- All style properties (`BackColor`, `ForeColor`, `BorderColor`, `BorderStyle`, `BorderWidth`, `CssClass`, `Width`, `Height`, `Font`)
+- `HasFile` — indicates whether a file has been selected
+- `FileName` — the name of the selected file
+- `FileBytes` — the file content as a byte array (synchronous)
+- `FileContent` — a `Stream` pointing to the uploaded file
+- `PostedFile` — a `PostedFileWrapper` providing `ContentLength`, `ContentType`, `FileName`, `InputStream`, and `SaveAs` (compatible with Web Forms `HttpPostedFile` patterns)
+- `AllowMultiple` — enables multi-file selection (default: `false`)
+- `Accept` — restricts file types via the HTML `accept` attribute (e.g., `".jpg,.png"` or `"image/*"`)
+- `MaxFileSize` — maximum file size in bytes (default: `512000` / ~500 KiB)
+- `ToolTip` — tooltip text displayed on hover
+- `OnFileSelected` — event raised when a file is selected
+- `SaveAs(filename)` — saves the uploaded file to a specified server path
+- `GetFileBytesAsync()` — async method to get file content as a byte array
+- `GetMultipleFiles()` — returns all selected files when `AllowMultiple` is enabled
+- `SaveAllFiles(directory)` — saves all uploaded files to a directory with sanitized filenames
+- `Enabled` — enables or disables the file input
+- `Visible` — controls visibility
+- All base style properties (`CssClass`, `Style`, `BackColor`, `ForeColor`, `BorderColor`, `BorderStyle`, `BorderWidth`, `Width`, `Height`, `Font`)
### Blazor Notes
@@ -33,6 +34,10 @@ Original Microsoft documentation: https://docs.microsoft.com/en-us/dotnet/api/sy
## Web Forms Features NOT Supported
+- **PostedFile.SaveAs with HttpContext** — Blazor's `SaveAs` works directly with `IBrowserFile` streams; there is no `HttpContext`-based file handling
+- **Server.MapPath** — Use absolute paths or `IWebHostEnvironment.WebRootPath` in Blazor
+- **Request.Files collection** — Use the component's `GetMultipleFiles()` method instead
+- **Lifecycle events** (`OnDataBinding`, `OnInit`, etc.) — Use Blazor lifecycle methods instead
- Direct postback behavior - use event handlers instead
- Automatic form submission - implement form handling in Blazor
- Server-side file system access in WebAssembly - must send to API endpoint
@@ -41,106 +46,162 @@ Original Microsoft documentation: https://docs.microsoft.com/en-us/dotnet/api/sy
```html
```
-## Blazor Syntax
+## Blazor Razor Syntax
-```razor
-
+### Basic File Upload
-
+```razor
+
@code {
- FileUpload myFileUpload;
-
- async Task HandleUpload()
+ void HandleFileSelected(InputFileChangeEventArgs args)
{
- if (myFileUpload.HasFile)
- {
- string fileName = myFileUpload.FileName;
- await myFileUpload.SaveAs($"uploads/{fileName}");
- }
+ // File has been selected
}
}
```
-## Common Usage Patterns
+### File Upload with Type Restriction
+
+```razor
+
+```
+
+### Multiple File Upload
-### Single File Upload
+```razor
+
+```
+
+### File Upload with Increased Size Limit
```razor
-
-
+
@code {
- FileUpload fileControl;
+ async Task HandleLargeFile(InputFileChangeEventArgs args)
+ {
+ // MaxFileSize is set to 10 MB
+ }
+}
+```
+
+### Saving an Uploaded File
+
+```razor
+@inject IWebHostEnvironment Environment
+
+
+
- async Task UploadPdf()
+@code {
+ private FileUpload uploader;
+
+ void HandleFile(InputFileChangeEventArgs args)
{
- if (fileControl.HasFile)
+ // File selected, ready to save
+ }
+
+ async Task SaveFile()
+ {
+ if (uploader.HasFile)
{
- await fileControl.SaveAs($"documents/{fileControl.FileName}");
+ var path = Path.Combine(Environment.WebRootPath, "uploads", uploader.FileName);
+ await uploader.SaveAs(path);
}
}
}
```
-### Multiple File Upload
+### Using PostedFile (Web Forms Pattern)
```razor
-
-
+
@code {
- FileUpload fileControl;
+ private FileUpload uploader;
- async Task UploadAll()
+ void HandleFile(InputFileChangeEventArgs args)
{
- if (fileControl.HasFile)
+ if (uploader.HasFile)
{
- var savedPaths = await fileControl.SaveAllFiles("uploads");
- // Process savedPaths list
+ var postedFile = uploader.PostedFile;
+ var fileName = postedFile.FileName;
+ var contentType = postedFile.ContentType;
+ var size = postedFile.ContentLength;
}
}
}
```
-### Image Upload with Preview
+### Multiple File Save
```razor
-
+
+
@code {
- FileUpload imageUpload;
+ private FileUpload uploader;
+
+ void HandleFiles(InputFileChangeEventArgs args) { }
- void HandleImageSelected()
+ async Task SaveAllFiles()
{
- if (imageUpload.HasFile)
+ if (uploader.HasFile)
{
- byte[] imageData = imageUpload.FileBytes;
- // Process image data
+ var savedPaths = await uploader.SaveAllFiles("C:\\uploads");
+ // savedPaths contains the full path of each saved file
}
}
}
```
+## HTML Output
+
+**Blazor Input:**
+```razor
+
+```
+
+**Rendered HTML:**
+```html
+
+```
+
+## PostedFileWrapper Reference
+
+The `PostedFile` property returns a `PostedFileWrapper` object that mirrors the Web Forms `HttpPostedFile` API:
+
+| Property/Method | Type | Description |
+|-----------------|------|-------------|
+| `ContentLength` | `long` | Size of the uploaded file in bytes |
+| `ContentType` | `string` | MIME content type of the file |
+| `FileName` | `string` | Name of the uploaded file |
+| `InputStream` | `Stream` | Stream pointing to the file content |
+| `SaveAs(filename)` | `Task` | Saves the file to the specified path |
+
## Security Considerations
- Always validate file types on the server side, not just through the `Accept` attribute
@@ -150,28 +211,34 @@ Original Microsoft documentation: https://docs.microsoft.com/en-us/dotnet/api/sy
- Store uploaded files outside of the web root when possible
- Implement authentication and authorization for file upload endpoints
-## Migration from Web Forms
+## Migration Notes
-When migrating from Web Forms FileUpload:
+When migrating from Web Forms to Blazor:
-1. Replace `` with `` (remove `asp:` prefix and `runat="server"`)
-2. Replace `FileUpload1.HasFile` with direct property access (no change needed)
-3. Replace `FileUpload1.SaveAs(Server.MapPath("~/path"))` with `await FileUpload1.SaveAs(path)`
-4. Handle file uploads asynchronously using `async/await` pattern
-5. Use `@ref` instead of `ID` to reference the component in code
+1. **Remove `asp:` prefix** — Change `` to ``
+2. **Remove `runat="server"`** — Not needed in Blazor
+3. **Remove `ID` attribute** — Use `@ref` to get a component reference
+4. **Replace `PostBack` button pattern** — In Web Forms, a separate Button triggered the upload via PostBack. In Blazor, use the `OnFileSelected` event or a Button with `@ref` to call `SaveAs`
+5. **Replace `Server.MapPath`** — Use `IWebHostEnvironment.WebRootPath` or `ContentRootPath` for server paths
+6. **Use async methods** — Prefer `GetFileBytesAsync()` over `FileBytes` for better performance
### Before (Web Forms)
-```aspx
-
-
+```html
+
+
+
+```
-// Code-behind
-protected void UploadButton_Click(object sender, EventArgs e)
+```csharp
+protected void btnUpload_Click(object sender, EventArgs e)
{
- if (FileUpload1.HasFile)
+ if (fileUpload1.HasFile)
{
- FileUpload1.SaveAs(Server.MapPath("~/uploads/" + FileUpload1.FileName));
+ string fileName = fileUpload1.FileName;
+ string savePath = Server.MapPath("~/uploads/") + fileName;
+ fileUpload1.SaveAs(savePath);
+ lblStatus.Text = "File uploaded: " + fileName;
}
}
```
@@ -179,18 +246,38 @@ protected void UploadButton_Click(object sender, EventArgs e)
### After (Blazor)
```razor
-
-
+@inject IWebHostEnvironment Environment
+
+
+
+
@code {
- FileUpload FileUpload1;
+ private FileUpload fileUpload;
+ private string statusMessage = "";
+
+ void HandleFile(InputFileChangeEventArgs args) { }
- async Task UploadButton_Click()
+ async Task Upload()
{
- if (FileUpload1.HasFile)
+ if (fileUpload.HasFile)
{
- await FileUpload1.SaveAs($"uploads/{FileUpload1.FileName}");
+ var fileName = fileUpload.FileName;
+ var savePath = Path.Combine(Environment.WebRootPath, "uploads", fileName);
+ await fileUpload.SaveAs(savePath);
+ statusMessage = "File uploaded: " + fileName;
}
}
}
```
+
+!!! warning "Security Consideration"
+ Always validate uploaded files on the server side. The `Accept` attribute only provides client-side filtering and can be bypassed. Validate file extensions, content types, and file sizes before saving. The `SaveAllFiles` method sanitizes filenames using `Path.GetFileName` to prevent directory traversal attacks, but additional validation is recommended.
+
+!!! note "File Size Limits"
+ The default `MaxFileSize` is 512,000 bytes (~500 KiB). For larger files, increase this value. Be mindful that large files consume memory, especially when using `FileBytes` or `GetFileBytesAsync()`. For very large files, work with the `FileContent` stream directly.
+
+## See Also
+
+- [Microsoft Docs: FileUpload Control](https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.fileupload?view=netframework-4.8)
+- [Blazor File Uploads](https://docs.microsoft.com/en-us/aspnet/core/blazor/file-uploads)
diff --git a/docs/EditorControls/Localize.md b/docs/EditorControls/Localize.md
new file mode 100644
index 00000000..c63cc87f
--- /dev/null
+++ b/docs/EditorControls/Localize.md
@@ -0,0 +1,138 @@
+# Localize
+
+The **Localize** component emulates the ASP.NET Web Forms `asp:Localize` control. In Web Forms, `Localize` inherits directly from `Literal` and is functionally identical to it — the only difference is semantic. `Localize` marks text as localizable for design-time tooling and resource expression support (`<%$ Resources:... %>`). In Blazor, there is no design-time localization distinction, so this component exists purely for markup compatibility during migration.
+
+Original Microsoft documentation: https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.localize?view=netframework-4.8
+
+## Features Supported in Blazor
+
+- `Text` - the text content to display (pass localized strings from `IStringLocalizer`)
+- `Mode` - specifies how the content is rendered (`Encode`, `PassThrough`, or `Transform`)
+- `Visible` - controls whether the text is rendered
+
+### Blazor Notes
+
+- `Localize` inherits from `Literal` and behaves identically. All properties, rendering, and modes are inherited.
+- No wrapper HTML element is rendered — the text is output directly into the DOM.
+- When `Mode` is `Encode` (the default), text is HTML-encoded. When `Mode` is `PassThrough`, text is rendered as raw markup.
+
+!!! warning "Localization in Blazor"
+ In Web Forms, `Localize` enabled design-time localization via resource expressions like `<%$ Resources:MyResource, MyKey %>`. Blazor does not have resource expressions. Instead, inject `IStringLocalizer` and pass localized strings to the `Text` property. See [Microsoft's Blazor globalization documentation](https://learn.microsoft.com/en-us/aspnet/core/blazor/globalization-localization) for details.
+
+## Web Forms Features NOT Supported
+
+- **Resource Expressions** (`<%$ Resources:... %>`) - Not supported in Blazor; use `IStringLocalizer` instead
+- **Design-time localization tooling** - No Blazor equivalent; localization is handled at runtime via .NET localization APIs
+- **EnableTheming / SkinID** - Theming is not supported in Blazor
+
+## Web Forms Declarative Syntax
+
+```html
+
+```
+
+## Blazor Syntax
+
+### Basic Usage
+
+```razor
+
+```
+
+### With Localized Text
+
+```razor
+@inject IStringLocalizer Localizer
+
+
+```
+
+### With Mode
+
+```razor
+
+
+```
+
+## HTML Output
+
+`Localize` renders no wrapper HTML element — text is output directly into the DOM, identical to `Literal`.
+
+**Blazor Input:**
+```razor
+
+```
+
+**Rendered HTML:**
+```html
+Hello, World!
+```
+
+**With Mode=PassThrough:**
+```razor
+
+```
+
+**Rendered HTML:**
+```html
+emphasized
+```
+
+**With Mode=Encode (default):**
+```razor
+
+```
+
+**Rendered HTML:**
+```html
+<em>encoded</em>
+```
+
+## Migration Notes
+
+When migrating from Web Forms to Blazor:
+
+1. **Remove `asp:` prefix** - Change `` to ``
+2. **Remove `runat="server"`** - Not needed in Blazor
+3. **Replace resource expressions** - Replace `<%$ Resources:MyResource, MyKey %>` with `IStringLocalizer` injection
+4. **Static text** - If the `Text` was hardcoded, no additional changes are needed
+
+### Before (Web Forms)
+
+```html
+
+```
+
+### After (Blazor)
+
+```razor
+@inject IStringLocalizer Localizer
+
+
+```
+
+!!! tip "Consider Using Literal"
+ Since `Localize` is identical to `Literal` in Blazor, you can use either component interchangeably. If you are writing new Blazor code (not migrating), `Literal` is the conventional choice. Use `Localize` when migrating existing markup that already uses `` to minimize changes.
+
+## See Also
+
+- [Literal](Literal.md) - Identical component; Localize inherits from Literal
+- [Blazor Globalization and Localization](https://learn.microsoft.com/en-us/aspnet/core/blazor/globalization-localization) - Microsoft's guide to localization in Blazor
diff --git a/docs/EditorControls/MultiView.md b/docs/EditorControls/MultiView.md
new file mode 100644
index 00000000..39225e6b
--- /dev/null
+++ b/docs/EditorControls/MultiView.md
@@ -0,0 +1,124 @@
+# MultiView / View
+
+The **MultiView** and **View** components emulate the ASP.NET Web Forms `asp:MultiView` and `asp:View` controls. MultiView is a container that holds multiple View controls, displaying only one at a time based on the `ActiveViewIndex` property.
+
+Original Microsoft documentation:
+
+- MultiView: https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.multiview?view=netframework-4.8
+- View: https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.view?view=netframework-4.8
+
+## Blazor Features Supported
+
+### MultiView
+- `ActiveViewIndex` — index of the currently visible View (-1 = none)
+- `OnActiveViewChanged` — event fired when the active view changes
+- `GetActiveView()` — returns the currently active View
+- `SetActiveView(View)` — sets the active view by reference
+- Command name constants: `NextViewCommandName`, `PreviousViewCommandName`, `SwitchViewByIDCommandName`, `SwitchViewByIndexCommandName`
+- Child `View` components auto-register via `CascadingParameter`
+
+### View
+- `ChildContent` — arbitrary child content rendered when active
+- `OnActivate` — fired when this View becomes active
+- `OnDeactivate` — fired when this View is deactivated
+- `Visible` — controlled by parent MultiView
+
+## Web Forms Features NOT Supported
+
+- Command-based navigation via `BubbleEvent` (use `ActiveViewIndex` binding instead)
+- `EnableTheming` / `SkinID` — theming not supported in Blazor
+
+## Web Forms Declarative Syntax
+
+```html
+
+
+
This is View 1
+
+
+
This is View 2
+
+
+```
+
+## Blazor Syntax
+
+```razor
+
+
+
This is View 1
+
+
+
+
This is View 2
+
+
+
+
+@code {
+ private int activeIndex = 0;
+
+ private void ViewChanged(EventArgs e)
+ {
+ // Handle view change
+ }
+}
+```
+
+## HTML Output
+
+MultiView and View render **no wrapper HTML elements**. Only the active View's child content is rendered directly into the DOM.
+
+**Blazor Input:**
+```razor
+
+
Hello
+
World
+
+```
+
+**Rendered HTML:**
+```html
+
Hello
+```
+
+## Migration Notes
+
+1. **Remove `asp:` prefix** — Change `` to `` and `` to ``
+2. **Remove `runat="server"`** — Not needed in Blazor
+3. **Bind ActiveViewIndex** — Use `@activeIndex` binding instead of code-behind manipulation
+4. **Replace command navigation** — Web Forms uses `NextView`/`PrevView` command names with Button controls. In Blazor, bind button clicks to change `ActiveViewIndex` directly.
+
+### Before (Web Forms)
+
+```html
+
+
+
+
+
+
+
+
+```
+
+### After (Blazor)
+
+```razor
+
+
+
+
+
+
+
+
+
+@code {
+ private int activeIndex = 0;
+}
+```
+
+## See Also
+
+- [Panel](../EditorControls/Panel.md) — Another container component
diff --git a/docs/LoginControls/ChangePassword.md b/docs/LoginControls/ChangePassword.md
new file mode 100644
index 00000000..8b8c0299
--- /dev/null
+++ b/docs/LoginControls/ChangePassword.md
@@ -0,0 +1,150 @@
+# ChangePassword
+
+The **ChangePassword** component emulates the ASP.NET Web Forms `asp:ChangePassword` control. It provides a complete user interface for changing a user's password, including current password, new password, and confirmation fields with a two-view layout (Change Password view and Success view).
+
+Original Microsoft documentation: https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.changepassword?view=netframework-4.8
+
+## Blazor Features Supported
+
+- Current password, new password, and confirm new password input fields
+- Configurable labels and error messages for all fields
+- Two-view state: Change Password view and Success view
+- `DisplayUserName` — optionally show a username field
+- Cancel, Change Password, and Continue buttons with configurable text and type
+- Navigation via `CancelDestinationPageUrl`, `ContinueDestinationPageUrl`, `SuccessPageUrl`
+- Create User, Password Recovery, Help Page, and Edit Profile links
+- `InstructionText` and `PasswordHintText` display
+- Failure text display on error
+- `OnChangingPassword` — before password change (cancellable)
+- `OnChangedPassword` — after successful password change
+- `OnChangePasswordError` — on error
+- `OnCancelButtonClick`, `OnContinueButtonClick` events
+- Custom templates via `ChangePasswordTemplate` and `SuccessTemplate`
+- Styling through cascading style components (follows Login control pattern)
+- Table-based layout matching Web Forms HTML output
+
+## Web Forms Features NOT Supported
+
+- `MembershipProvider` — marked `[Obsolete]`; use `OnChangingPassword` event to integrate with ASP.NET Identity
+- `MailDefinition` — email sending not supported in Blazor components
+- `OnSendingMail` / `OnSendMailError` events — not applicable
+
+!!! warning "Authentication Integration"
+ The ChangePassword component does NOT perform password changes directly. You must handle the `OnChangingPassword` event and use ASP.NET Identity's `UserManager.ChangePasswordAsync()` or your own authentication service to perform the actual password change.
+
+## Web Forms Declarative Syntax
+
+```html
+
+```
+
+## Blazor Syntax
+
+```razor
+
+
+@code {
+ private async Task HandleChangingPassword(LoginCancelEventArgs e)
+ {
+ // Call your identity service to change the password
+ // var result = await UserManager.ChangePasswordAsync(user, currentPassword, newPassword);
+ // if (!result.Succeeded) { e.Cancel = true; }
+ }
+
+ private void HandleChangedPassword(EventArgs e)
+ {
+ // Password changed successfully
+ }
+}
+```
+
+## HTML Output
+
+The component renders a table-based layout matching the Web Forms ChangePassword control:
+
+```html
+
+
+
+
+
+
+
Change Your Password
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Migration Notes
+
+1. **Remove `asp:` prefix** — Change `` to ``
+2. **Remove `runat="server"`** — Not needed in Blazor
+3. **Replace MembershipProvider** — Handle `OnChangingPassword` event instead
+4. **Remove MailDefinition** — Handle email notifications in your own service
+5. **Event handler signatures** — `OnChangingPassword` uses `LoginCancelEventArgs` (set `Cancel = true` to abort)
+
+### Before (Web Forms)
+
+```html
+
+```
+
+### After (Blazor)
+
+```razor
+
+
+@code {
+ private async Task HandleChanging(LoginCancelEventArgs e)
+ {
+ // Use UserManager to change password
+ }
+
+ private void HandleChanged(EventArgs e)
+ {
+ // Success handling
+ }
+}
+```
+
+## See Also
+
+- [Login](Login.md) — Related login control with similar table layout
+- [CreateUserWizard](CreateUserWizard.md) — User registration wizard
diff --git a/docs/LoginControls/CreateUserWizard.md b/docs/LoginControls/CreateUserWizard.md
new file mode 100644
index 00000000..af593055
--- /dev/null
+++ b/docs/LoginControls/CreateUserWizard.md
@@ -0,0 +1,178 @@
+# CreateUserWizard
+
+The **CreateUserWizard** component emulates the ASP.NET Web Forms `asp:CreateUserWizard` control. It provides a complete user registration interface with a two-step wizard: a registration form and a success confirmation page.
+
+Original Microsoft documentation: https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.createuserwizard?view=netframework-4.8
+
+## Blazor Features Supported
+
+- User registration form with UserName, Password, Confirm Password, Email fields
+- Optional Security Question and Answer fields
+- Two-step wizard: Create User step and Complete step
+- `AutoGeneratePassword` — hides password fields when true
+- `RequireEmail` — controls email field visibility
+- `DisplaySideBar` — optional sidebar with step navigation
+- `DisplayCancelButton` — optional cancel button
+- Configurable labels, error messages, and button text for all fields
+- `OnCreatingUser` — before user creation (cancellable via `LoginCancelEventArgs`)
+- `OnCreatedUser` — after successful creation
+- `OnCreateUserError` — on error (provides `CreateUserErrorEventArgs`)
+- Navigation events: `OnCancelButtonClick`, `OnContinueButtonClick`, `OnActiveStepChanged`
+- Custom templates: `CreateUserStep`, `CompleteStep`, `SideBarTemplate`, `HeaderTemplate`
+- Styling through cascading style components
+- Table-based layout matching Web Forms HTML output
+
+## Web Forms Features NOT Supported
+
+- `MembershipProvider` — marked `[Obsolete]`; use `OnCreatingUser` event to integrate with ASP.NET Identity
+- `MailDefinition` — email sending not supported in Blazor components
+- Custom `WizardStep` child components (v1 supports only the two built-in steps)
+- `LoginCreatedUser` auto-login — the component fires `OnCreatedUser`; handle login in your callback
+- `DisableCreatedUser` — handle in your `OnCreatingUser` callback
+
+!!! warning "Authentication Integration"
+ The CreateUserWizard does NOT create users directly. You must handle the `OnCreatingUser` event and use ASP.NET Identity's `UserManager.CreateAsync()` or your own registration service to create the user account.
+
+## Web Forms Declarative Syntax
+
+```html
+
+```
+
+## Blazor Syntax
+
+```razor
+
+
+@code {
+ private async Task HandleCreatingUser(LoginCancelEventArgs e)
+ {
+ // Use UserManager to create the user
+ // var result = await UserManager.CreateAsync(new AppUser { UserName = ... }, password);
+ // if (!result.Succeeded) { e.Cancel = true; }
+ }
+
+ private void HandleCreatedUser(EventArgs e)
+ {
+ // User created successfully — handle login, redirect, etc.
+ }
+
+ private void HandleError(CreateUserErrorEventArgs e)
+ {
+ // Display error: e.ErrorMessage
+ }
+}
+```
+
+## HTML Output
+
+### Create User Step
+
+```html
+
+
+
+
+
+
+
Sign Up for Your New Account
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Complete Step
+
+```html
+
+
+
+
+
+
+
Complete
+
Your account has been successfully created.
+
+
+
+
+
+
+
+```
+
+## Migration Notes
+
+1. **Remove `asp:` prefix** — Change `` to ``
+2. **Remove `runat="server"`** — Not needed in Blazor
+3. **Replace MembershipProvider** — Handle `OnCreatingUser` event with ASP.NET Identity
+4. **Remove MailDefinition** — Handle email in your own service
+5. **Event handler signatures** — `OnCreatingUser` uses `LoginCancelEventArgs`; `OnCreateUserError` uses `CreateUserErrorEventArgs`
+
+### Before (Web Forms)
+
+```html
+
+```
+
+### After (Blazor)
+
+```razor
+
+
+@code {
+ private async Task HandleCreating(LoginCancelEventArgs e)
+ {
+ // Create user via UserManager
+ }
+
+ private void HandleCreated(EventArgs e)
+ {
+ // Handle success
+ }
+}
+```
+
+## See Also
+
+- [Login](Login.md) — Related login control
+- [ChangePassword](ChangePassword.md) — Password change control
diff --git a/docs/NavigationControls/ImageMap.md b/docs/NavigationControls/ImageMap.md
new file mode 100644
index 00000000..90cec8a0
--- /dev/null
+++ b/docs/NavigationControls/ImageMap.md
@@ -0,0 +1,346 @@
+# ImageMap
+
+The **ImageMap** component displays an image with defined clickable hot spot regions. Each hot spot can navigate to a URL or trigger a server-side event when clicked, emulating the ASP.NET Web Forms ImageMap control. This is useful for interactive images like site maps, diagrams, or region selectors.
+
+Original Microsoft documentation: https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.imagemap?view=netframework-4.8
+
+## Features Supported in Blazor
+
+- `ImageUrl` — URL of the image to display
+- `AlternateText` — alternate text when the image is unavailable
+- `DescriptionUrl` — URL to a detailed description for accessibility
+- `ImageAlign` — alignment of the image relative to other elements (`NotSet`, `Left`, `Right`, `Baseline`, `Top`, `Middle`, `Bottom`, `AbsBottom`, `AbsMiddle`, `TextTop`)
+- `ToolTip` — tooltip text displayed on hover
+- `GenerateEmptyAlternateText` — generates an empty `alt=""` for decorative images
+- `HotSpotMode` — default behavior for hot spots: `Navigate`, `PostBack`, `Inactive`, or `NotSet`
+- `Target` — default target window/frame for navigation links
+- `HotSpots` — collection of `HotSpot` objects defining clickable regions
+- `OnClick` — event raised when a hot spot in `PostBack` mode is clicked, providing `ImageMapEventArgs`
+- `Visible` — controls visibility
+
+### Hot Spot Types
+
+| Type | Shape | Properties | Coordinates Format |
+|------|-------|------------|-------------------|
+| `RectangleHotSpot` | `rect` | `Left`, `Top`, `Right`, `Bottom` | `left,top,right,bottom` |
+| `CircleHotSpot` | `circle` | `X`, `Y`, `Radius` | `x,y,radius` |
+| `PolygonHotSpot` | `poly` | `Coordinates` (string) | `x1,y1,x2,y2,...` |
+
+### HotSpot Base Properties
+
+All hot spot types share these properties:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `AlternateText` | `string` | Alt text for the hot spot area |
+| `HotSpotMode` | `HotSpotMode` | Behavior override (`NotSet` inherits from parent `ImageMap`) |
+| `NavigateUrl` | `string` | URL to navigate to (when mode is `Navigate`) |
+| `PostBackValue` | `string` | Value passed in event args (when mode is `PostBack`) |
+| `Target` | `string` | Target window/frame for navigation |
+| `TabIndex` | `short` | Tab order of the hot spot |
+| `AccessKey` | `string` | Keyboard shortcut for the hot spot |
+
+## Web Forms Features NOT Supported
+
+- **Style properties** (`BackColor`, `ForeColor`, etc.) — Not applicable to the `` and `