Milestone 4: Chart Component (Chart.js + Blazor JS Interop)#4
Milestone 4: Chart Component (Chart.js + Blazor JS Interop)#4csharpfritz wants to merge 38 commits into
Conversation
Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
…ends#333) - Create CalendarSelectionMode enum (None, Day, DayWeek, DayWeekMonth) - Refactor Calendar.SelectionMode from string to CalendarSelectionMode enum - Remove .GetAwaiter().GetResult() blocking call in CreateDayRenderArgs - Add Caption, CaptionAlign, UseAccessibleHeader properties - Update tests and samples to use enum values
- Calendar: date selection, selection modes, styling, day/title formats, events - FileUpload: basic upload, file type filtering, multiple files, disabled, styled - ImageMap: navigate/postback/mixed hot spot modes, rectangle/circle/polygon shapes - Updated NavMenu and ComponentList with links to all three new components
…ents into dev # Conflicts: # docs/EditorControls/FileUpload.md
…Friends#338 merge The FileUpload PR (FritzAndFriends#338) inadvertently reverted Sprint 1 gate review entries from agent histories (beast, cyclops, forge, jubilee, rogue) and downgraded the FileUpload InputFile decision in decisions.md. Restored from commit f85aa42 (docs(ai-team): Sprint 1 gate review results).
Creates .agent.md files for all 6 team agents (Beast, Cyclops, Forge, Jubilee, Rogue, Scribe) so they appear in GitHub Copilot's agent picker. Content sourced from existing .ai-team/agents/*/charter.md files.
Squad is the single Copilot agent that delegates to the specialized agents defined in .ai-team/agents/. Individual agent files were incorrectly created the correct pattern is one coordinator agent (squad.agent.md) that routes work to Forge, Cyclops, Beast, Jubilee, Rogue, and Scribe based on task type.
Session: 2026-02-10-sprint2-complete Requested by: Jeffrey T. Fritz Changes: - Logged Sprint 2 session (4 components shipped with docs, samples, tests) - Merged Sprint 2 design review decision from inbox - Removed duplicate FileUpload InputFile decision from inbox (already consolidated) - Appended Sprint 2 completion decision to decisions.md - Propagated cross-agent updates to all 5 agent histories
…ents into dev # Conflicts: # docs/UtilityFeatures/PageService.md # samples/AfterBlazorServerSide/Components/Pages/ControlSamples/Calendar/Index.razor
Session: 2026-02-11-sprint3-planning Requested by: Jeffrey T. Fritz Changes: - Logged session to .ai-team/log/2026-02-11-sprint3-planning.md - Merged 3 decisions from inbox into decisions.md - Updated status.md to reflect 48/53 components complete - Sprint 3 scope: DetailsView + PasswordRecovery - Propagated cross-agent updates to all agent history files
Session: 2026-02-12-sprint3-execution Requested by: Jeffrey T. Fritz Changes: - Logged Sprint 3 execution session - Merged 7 decisions from inbox into decisions.md - Sprint 3 gate review: DetailsView + PasswordRecovery APPROVED - Propagated cross-agent updates to Beast, Colossus, Cyclops, Rogue, Jubilee - status.md updated to 50/53 (94%)
Session: 2026-02-12-milestone4-planning Requested by: Jeffrey T. Fritz Changes: - Logged session to .ai-team/log/2026-02-12-milestone4-planning.md - Merged decisions from inbox (Chart.js evaluation, milestone plan, milestones directive) - Propagated milestone 4 updates to 5 agent history files
Session: 2026-02-12-milestone4-planning Requested by: Scribe (automatic) Changes: - Summarized Forge history.md (exceeded ~12KB threshold) - Preserved all team updates and key patterns
- Add Chart, ChartSeries, ChartArea, ChartLegend, ChartTitle components - Add JS interop infrastructure (chart-interop.js, ChartJsInterop.cs) - Add ChartConfigBuilder for component state -> Chart.js JSON config - Add SeriesChartType, ChartPalette, Docking, ChartDashStyle enums - Add DataPoint and Axis POCOs - Add 140 bUnit tests for Chart components (866 total, all passing) - Add 8 Chart sample pages (Column, Line, Bar, Pie, Area, Doughnut, Scatter, StackedColumn) - Add 19 Chart integration tests - Add Chart documentation and update mkdocs.yml, README, status.md - Fix integration test console error filter for ASP.NET structured logs - Fix integration test filter for external resource loading errors - Fix duplicate FileUpload route (delete old Pages/ControlSamples/FileUpload/Default.razor) - Fix ChangePassword and CreateUserWizard test assertions for EditForm rendering - All tests green: 866 bUnit + 124 integration tests
| foreach (var s in seriesList) | ||
| { | ||
| if (s.Points != null && s.Points.Count > 0) | ||
| { | ||
| foreach (var p in s.Points) | ||
| { | ||
| if (!string.IsNullOrEmpty(p.Label)) | ||
| labels.Add(p.Label); | ||
| else if (p.XValue != null) | ||
| labels.Add(p.XValue); | ||
| } | ||
| break; | ||
| } | ||
| } |
Check notice
Code scanning / CodeQL
Missed opportunity to use Where Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix this kind of issue, we should replace a foreach loop that scans a sequence and conditionally processes only elements that satisfy a predicate with an explicit LINQ filter such as .Where(predicate) (and possibly .FirstOrDefault(), .Select(...), etc., as appropriate). This makes the code’s intent—“work with the first series that has points”—explicit and reduces nesting.
Here, the loop from lines 101–114 finds the first ChartSeriesConfig whose Points collection is non‑null and non‑empty, then builds labels from that series’ points and breaks. We can preserve this behaviour by:
- Selecting that series explicitly using LINQ:
var firstSeriesWithPoints = seriesList .FirstOrDefault(s => s.Points != null && s.Points.Count > 0);
- If such a series exists, building the labels from
firstSeriesWithPoints.Pointsin a simpleforeachwithout extra conditionals orbreak.
Concretely in BuildDataSection within src/BlazorWebFormsComponents/ChartConfigBuilder.cs, replace the foreach (var s in seriesList) block that builds labels with:
- A
var firstSeriesWithPoints = ...FirstOrDefault(...)statement. - An
if (firstSeriesWithPoints != null)block iterating over itsPointsand adding their labels/XValues to thelabelslist.
No new imports are needed: System.Linq is already imported, and FirstOrDefault is a standard LINQ method. The rest of the method, including the use of labels and datasets, remains unchanged.
| @@ -98,18 +98,21 @@ | ||
|
|
||
| // Collect labels from the first series that has them | ||
| var labels = new List<object>(); | ||
| foreach (var s in seriesList) | ||
| var firstSeriesWithPoints = seriesList | ||
| .FirstOrDefault(s => s.Points != null && s.Points.Count > 0); | ||
|
|
||
| if (firstSeriesWithPoints != null) | ||
| { | ||
| if (s.Points != null && s.Points.Count > 0) | ||
| foreach (var p in firstSeriesWithPoints.Points) | ||
| { | ||
| foreach (var p in s.Points) | ||
| if (!string.IsNullOrEmpty(p.Label)) | ||
| { | ||
| if (!string.IsNullOrEmpty(p.Label)) | ||
| labels.Add(p.Label); | ||
| else if (p.XValue != null) | ||
| labels.Add(p.XValue); | ||
| labels.Add(p.Label); | ||
| } | ||
| break; | ||
| else if (p.XValue != null) | ||
| { | ||
| labels.Add(p.XValue); | ||
| } | ||
| } | ||
| } | ||
|
|
| if (s.ChartType == SeriesChartType.Point) | ||
| { | ||
| dataset["data"] = s.Points.Select(p => new Dictionary<string, object> | ||
| { | ||
| ["x"] = p.XValue ?? 0, | ||
| ["y"] = p.YValues?.Length > 0 ? p.YValues[0] : 0 | ||
| }).ToList(); | ||
| } | ||
| else | ||
| { | ||
| dataset["data"] = s.Points | ||
| .Select(p => p.YValues?.Length > 0 ? p.YValues[0] : 0.0) | ||
| .ToList(); | ||
| } |
Check notice
Code scanning / CodeQL
Missed ternary opportunity Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix this pattern you keep a single assignment and replace the if/else with a conditional (? :) that chooses between the two values that were previously assigned in each branch. This keeps the behavior identical while making it clearer that the only difference is which value is used.
For this specific case in src/BlazorWebFormsComponents/ChartConfigBuilder.cs, lines 135–148, we should replace:
- The entire
if (s.ChartType == SeriesChartType.Point) { ... } else { ... }block
with a single assignment:
dataset["data"] = s.ChartType == SeriesChartType.Point ? <point-expression> : <other-expression>;
The two existing expressions (s.Points.Select(...dictionary...) and s.Points.Select(...double...)) will become the true/false branches of the ternary. We must preserve the .ToList() calls and the existing dictionary and selection logic exactly, only restructuring into a conditional expression. No new imports or helper methods are needed; everything can be expressed inline where the if/else currently sits.
| @@ -132,20 +132,15 @@ | ||
| // Data values | ||
| if (s.Points != null && s.Points.Count > 0) | ||
| { | ||
| if (s.ChartType == SeriesChartType.Point) | ||
| { | ||
| dataset["data"] = s.Points.Select(p => new Dictionary<string, object> | ||
| dataset["data"] = s.ChartType == SeriesChartType.Point | ||
| ? s.Points.Select(p => new Dictionary<string, object> | ||
| { | ||
| ["x"] = p.XValue ?? 0, | ||
| ["y"] = p.YValues?.Length > 0 ? p.YValues[0] : 0 | ||
| }).ToList(); | ||
| } | ||
| else | ||
| { | ||
| dataset["data"] = s.Points | ||
| }).ToList() | ||
| : s.Points | ||
| .Select(p => p.YValues?.Length > 0 ? p.YValues[0] : 0.0) | ||
| .ToList(); | ||
| } | ||
| } | ||
|
|
||
| // Color from series or palette |
| if (area.AxisX != null) | ||
| { | ||
| var xAxis = BuildAxisConfig(area.AxisX); | ||
| if (scales.ContainsKey("x")) |
Check notice
Code scanning / CodeQL
Inefficient use of ContainsKey Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix this issue you should replace patterns of if (dict.ContainsKey(key)) { var v = dict[key]; ... } with if (dict.TryGetValue(key, out var v)) { ... }, which combines the existence check and retrieval into a single dictionary operation.
For this specific code in src/BlazorWebFormsComponents/ChartConfigBuilder.cs, update both the "x" and "y" scale-handling blocks to use TryGetValue on the scales dictionary. Instead of checking scales.ContainsKey("x") and then accessing scales["x"], call scales.TryGetValue("x", out var existingObj), verify that existingObj is a Dictionary<string, object>, and then merge the axis configuration into that dictionary. If the key does not exist (or does not hold the expected type), fall back to assigning scales["x"] = xAxis as before. Do the same for the "y" axis. This preserves existing behavior, but ensures only one dictionary lookup in the common path. No new imports or types are required; you only need to introduce local variables for the out values and type checks.
| @@ -227,9 +227,8 @@ | ||
| if (area.AxisX != null) | ||
| { | ||
| var xAxis = BuildAxisConfig(area.AxisX); | ||
| if (scales.ContainsKey("x")) | ||
| if (scales.TryGetValue("x", out var existingObj) && existingObj is Dictionary<string, object> existing) | ||
| { | ||
| var existing = (Dictionary<string, object>)scales["x"]; | ||
| foreach (var kvp in xAxis) | ||
| existing[kvp.Key] = kvp.Value; | ||
| } | ||
| @@ -242,9 +240,8 @@ | ||
| if (area.AxisY != null) | ||
| { | ||
| var yAxis = BuildAxisConfig(area.AxisY); | ||
| if (scales.ContainsKey("y")) | ||
| if (scales.TryGetValue("y", out var existingObj) && existingObj is Dictionary<string, object> existing) | ||
| { | ||
| var existing = (Dictionary<string, object>)scales["y"]; | ||
| foreach (var kvp in yAxis) | ||
| existing[kvp.Key] = kvp.Value; | ||
| } |
| if (area.AxisY != null) | ||
| { | ||
| var yAxis = BuildAxisConfig(area.AxisY); | ||
| if (scales.ContainsKey("y")) |
Check notice
Code scanning / CodeQL
Inefficient use of ContainsKey Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix this issue you replace the sequence “check with ContainsKey, then read with indexer” with a single call to TryGetValue, which both checks for existence and retrieves the value in one dictionary lookup. You then operate on the retrieved value if the call succeeds, or handle the absence of the key in the else branch.
In this file, there are two such patterns: one for the "x" key (lines 230–235) and one for the "y" key (lines 245–249). For each, we can change:
if (scales.ContainsKey("x")) { var existing = (Dictionary<string, object>)scales["x"]; ... }if (scales.ContainsKey("y")) { var existing = (Dictionary<string, object>)scales["y"]; ... }
to use TryGetValue:
if (scales.TryGetValue("x", out var xScaleObj)) { var existing = (Dictionary<string, object>)xScaleObj; ... }if (scales.TryGetValue("y", out var yScaleObj)) { var existing = (Dictionary<string, object>)yScaleObj; ... }
The cast to Dictionary<string, object> still happens (and will still throw if the stored value is of the wrong type), preserving existing behavior. No new methods, imports, or additional definitions are required; we only change the conditional and how we fetch the existing scale dictionary. All changes are localized to src/BlazorWebFormsComponents/ChartConfigBuilder.cs in the shown region.
| @@ -227,9 +227,9 @@ | ||
| if (area.AxisX != null) | ||
| { | ||
| var xAxis = BuildAxisConfig(area.AxisX); | ||
| if (scales.ContainsKey("x")) | ||
| if (scales.TryGetValue("x", out var xScaleObj)) | ||
| { | ||
| var existing = (Dictionary<string, object>)scales["x"]; | ||
| var existing = (Dictionary<string, object>)xScaleObj; | ||
| foreach (var kvp in xAxis) | ||
| existing[kvp.Key] = kvp.Value; | ||
| } | ||
| @@ -242,9 +241,9 @@ | ||
| if (area.AxisY != null) | ||
| { | ||
| var yAxis = BuildAxisConfig(area.AxisY); | ||
| if (scales.ContainsKey("y")) | ||
| if (scales.TryGetValue("y", out var yScaleObj)) | ||
| { | ||
| var existing = (Dictionary<string, object>)scales["y"]; | ||
| var existing = (Dictionary<string, object>)yScaleObj; | ||
| foreach (var kvp in yAxis) | ||
| existing[kvp.Key] = kvp.Value; | ||
| } |
Session: 2026-02-14-chart-implementation Requested by: Jeffrey T. Fritz Changes: - Logged session: Chart data binding and Playwright tests shipped - Merged 7 decision files from inbox (Chart visual tests, data binding, implementation, samples) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix ChartSeries.ToConfig() to support Items/XValueMember/YValueMembers data binding - Add 12 data binding unit tests (152 Chart tests total) - Create 4 new sample pages: DataBinding, MultiSeries, Styling, ChartAreas - Add 38 Playwright integration tests for Chart appearance verification - All 143 integration tests and 152 Chart unit tests pass Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- ChartAreas, DataBinding, MultiSeries, Styling routes now tested - All 42 Chart integration tests pass - Completes Forge's gate review requirements Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…randing - Upgraded Bootstrap 4.3.1 to 5.3.3 - Fixed pl-4 -> ps-4 utility class in NavMenu.razor - Created ComponentCatalog.cs with 36 components across 6 categories - Added GetByCategory, GetByRoute, Search helper methods - Created logo.svg with migration concept (Web Forms -> Blazor) - Added brand-colors.css with custom properties - Updated App.razor with favicon and brand stylesheet Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Redesigned MainLayout.razor with modern Bootstrap 5 navbar - Fixed-top navbar with logo, search placeholder, external links - Collapsible sidebar with hamburger menu on mobile - Footer with GitHub link and MIT License - data-bs-theme attribute for future dark mode support - Refactored NavMenu.razor to use ComponentCatalog - Data-driven rendering with collapsible category sections - Tooltips on component links with descriptions - Current page highlighting and auto-expand categories - Badge showing component count per category - Created SamplePageTemplate.razor with card-based layout - Title, Description, DemoContent, CodeExample sections - Consistent styling using brand colors - Fixed Menu_Renders_WithItems test (ul/li vs table) All 147 integration tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Implemented client-side search with Fuse.js fuzzy matching - SearchBox.razor with debounced search (200ms) - Keyboard navigation (up/down/enter/escape) - Match highlighting in results - Category badges and truncated descriptions - Redesigned homepage with hero section and card-based catalog - Gradient hero with CTA buttons (Get Started, View on GitHub) - 6 category cards with component counts and links - Responsive grid (3/2/1 columns for desktop/tablet/mobile) - Fixed sidebar layout issues - Removed redundant header text - Categories expanded by default on desktop - Mobile nav collapse on link click All 147 integration tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Added 4 screenshots showcasing the sample site: - Homepage with component catalog - GridView with interactive data binding - Chart component with Chart.js - Fuzzy search feature Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixed route mismatches between ComponentCatalog and actual page routes: - MasterPage: /ControlSamples/MasterPage -> /control-samples/masterpage - FormView: /ControlSamples/FormView -> /ControlSamples/FormView/Simple - DataList: SimpleFlow subpage -> Flow (matches actual route) - Validation controls: Removed /Validations/ prefix from routes - Login controls: Removed /LoginControls/ prefix from routes - LoginStatus: Removed NotAuthenticated subpage (has separate route) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The global footer in App.razor was spanning full width and appearing behind the fixed-position sidebar. Added inline styles in head to ensure the footer respects sidebar width with responsive handling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| if (yValue != null) | ||
| { | ||
| point.YValues = new[] { Convert.ToDouble(yValue) }; | ||
| } | ||
| else | ||
| { | ||
| point.YValues = Array.Empty<double>(); | ||
| } |
Check notice
Code scanning / CodeQL
Missed ternary opportunity Note test
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix this kind of issue you replace an if/else where both branches assign to the same variable with a single assignment whose right-hand side is a ternary expression. That keeps the logic equivalent but shorter and more expressive.
Here, we focus only on the block:
if (yValue != null)
{
point.YValues = new[] { Convert.ToDouble(yValue) };
}
else
{
point.YValues = Array.Empty<double>();
}The best fix is to assign point.YValues once, using a ternary on yValue != null to pick between the two alternatives, while ensuring Convert.ToDouble(yValue) is still evaluated only when yValue is non-null. The resulting line:
point.YValues = yValue != null ? new[] { Convert.ToDouble(yValue) } : Array.Empty<double>();keeps behavior identical: if yValue is non-null, it converts it to double and wraps it in a single-element array; otherwise, it sets YValues to an empty array. No new methods, imports, or definitions are required, and only the shown lines inside src/BlazorWebFormsComponents.Test/ChartTests.cs need to be changed.
| @@ -1325,14 +1325,9 @@ | ||
| { | ||
| var yProp = item.GetType().GetProperty(yValueMembers); | ||
| var yValue = yProp?.GetValue(item); | ||
| if (yValue != null) | ||
| { | ||
| point.YValues = new[] { Convert.ToDouble(yValue) }; | ||
| } | ||
| else | ||
| { | ||
| point.YValues = Array.Empty<double>(); | ||
| } | ||
| point.YValues = yValue != null | ||
| ? new[] { Convert.ToDouble(yValue) } | ||
| : Array.Empty<double>(); | ||
| } | ||
| else | ||
| { |
| if (Items != null && !string.IsNullOrEmpty(YValueMembers)) | ||
| { | ||
| config.Points = ExtractDataPointsFromItems(); | ||
| } | ||
| else | ||
| { | ||
| // Fall back to manually-specified Points | ||
| config.Points = Points; | ||
| } |
Check notice
Code scanning / CodeQL
Missed ternary opportunity Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix this issue you replace an if/else statement where both branches only assign or return a value with a single assignment or return that uses the ternary (? :) operator. This expresses the conditional choice more directly and concisely.
Here, the if/else sets config.Points to either ExtractDataPointsFromItems() or Points. We should replace the whole if/else block with one assignment:
config.Points = (Items != null && !string.IsNullOrEmpty(YValueMembers))
? ExtractDataPointsFromItems()
: Points;This preserves the existing condition and behavior. To retain the intent of the “Fall back to manually-specified Points” comment, we can move that comment onto the ternary’s false branch or just above the assignment. No new methods, imports, or other definitions are required. The change is localized to the ToConfig method in src/BlazorWebFormsComponents/ChartSeries.razor.cs around lines 112–121.
| @@ -109,16 +109,10 @@ | ||
| ChartArea = ChartArea | ||
| }; | ||
|
|
||
| // If Items is provided, extract data points from it | ||
| if (Items != null && !string.IsNullOrEmpty(YValueMembers)) | ||
| { | ||
| config.Points = ExtractDataPointsFromItems(); | ||
| } | ||
| else | ||
| { | ||
| // Fall back to manually-specified Points | ||
| config.Points = Points; | ||
| } | ||
| // If Items is provided, extract data points from it; otherwise fall back to manually-specified Points | ||
| config.Points = (Items != null && !string.IsNullOrEmpty(YValueMembers)) | ||
| ? ExtractDataPointsFromItems() | ||
| : Points; | ||
|
|
||
| return config; | ||
| } |
| catch | ||
| { | ||
| return false; | ||
| } |
Check notice
Code scanning / CodeQL
Generic catch clause Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
In general, to fix generic catch clauses, you should replace them with one or more catch blocks that name the specific exception types you intend to handle, and allow unexpected exceptions to propagate. This avoids accidentally hiding serious problems while still handling the failure modes you care about.
For this specific method, we only want to treat conversion failures as “return false.” According to .NET documentation, Convert.ToDouble(object) can throw InvalidCastException, FormatException, and OverflowException for non-null inputs that cannot be converted. We should explicitly catch these three and return false in each case. Any other exception (e.g., OutOfMemoryException) should not be swallowed and will bubble up, which is safer.
Concretely, in src/BlazorWebFormsComponents/ChartSeries.razor.cs, in the TryConvertToDouble method’s default case, replace the bare catch block starting at line 233 with three specific catch blocks: one each for InvalidCastException, FormatException, and OverflowException, all returning false. No new using directives are needed because these exception types are already in System, which is imported at the top. This preserves the current behavior for ordinary conversion problems while complying with the CodeQL recommendation.
| @@ -230,10 +230,18 @@ | ||
| result = Convert.ToDouble(value); | ||
| return true; | ||
| } | ||
| catch | ||
| catch (InvalidCastException) | ||
| { | ||
| return false; | ||
| } | ||
| catch (FormatException) | ||
| { | ||
| return false; | ||
| } | ||
| catch (OverflowException) | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| } |
…eenshots - Fix per-segment color assignment for pie/doughnut charts in ChartConfigBuilder - Replace chart.min.js placeholder with real Chart.js v4.4.8 (201KB) - Add Playwright-generated screenshots for all 8 chart types to docs/images/chart/ - Add Chart Type Gallery section to Chart.md documentation - Remove palette limitation warnings (bug is now fixed) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Evaluate 5 Blazor-native approaches to replicate Web Forms Themes and Skins functionality: CSS Custom Properties, CascadingValue ThemeProvider, Generated CSS Isolation, DI-based Configuration, and Hybrid. Recommend CascadingValue ThemeProvider as the primary approach due to full Web Forms fidelity (Theme/StyleSheetTheme semantics, SkinID support, all property types). Document is exploratory per Jeff's request. - Create docs/Migration/ThemesAndSkins.md with full analysis - Add to mkdocs.yml nav under Migration section - Record decision in .ai-team/decisions/inbox/ - Update forge history with learnings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Session: 2026-02-23-feature-audit Requested by: Jeffrey T. Fritz Changes: - Logged session to .ai-team/log/2026-02-23-feature-audit.md - Merged 8 decisions from inbox into decisions.md - Consolidated overlapping decisions (AccessKey/ToolTip, Chart architecture, DataBoundComponent gap) - Propagated updates to 6 agent history files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…trategy - Created 53 audit documents in planning-docs/ comparing every ASP.NET Web Forms control against its Blazor component implementation - Created planning-docs/SUMMARY.md with aggregate gap analysis (66.3% feature match) - Created docs/Migration/ThemesAndSkins.md with 5 evaluated migration approaches (recommended: CascadingValue ThemeProvider pattern) - Updated mkdocs.yml nav with ThemesAndSkins entry - Key findings: AccessKey/ToolTip universally missing, GridView weakest at 20.7%, DetailsView+PasswordRecovery stranded on unmerged sprint3 branch, 7 base-class fixes would close ~180 gaps across the library Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Brings in sprint3/detailsview-passwordrecovery (FritzAndFriends#340): - DetailsView component with paging, edit mode, command rows - PasswordRecovery component with 3-step flow - DataBinder/ViewState utility samples - Local SVG placeholder images replacing external URLs - bUnit and Playwright integration tests Conflict resolution: - DeferredControls.md: Kept Chart as Partially Implemented (HEAD) with dev's richer Substitution/Xml documentation - ControlSampleTests.cs: Combined Chart tests (HEAD) + Utility tests (dev), kept comprehensive error filtering - InteractiveComponentTests.cs: Took dev's thorough field-level assertions + all new DetailsView/PasswordRecovery tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Integrates remote work: Phase 1-3 UI overhaul (Bootstrap 5, ComponentCatalog, sidebar), chart data binding samples, and Playwright integration tests for new sample pages. Conflict: NavMenu.razor took remote's ComponentCatalog-driven dynamic nav (UI overhaul) over local's static TreeNode list. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The UI overhaul added ComponentCatalog-driven NavMenu and SearchBox components to AfterBlazorServerSide. Since AfterBlazorClientSide links Layout files via content references, it needs: - ComponentCatalog.cs linked as a compile item (NavMenu depends on it) - @using AfterBlazorServerSide added to client _Imports.razor - @using AfterBlazorServerSide.Components.Shared moved from MainLayout.razor to server-side _Imports.razor (avoids namespace resolution failure in client project where Shared components are not linked) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
97eedc5 to
1219f33
Compare
Milestone 4: Chart Component
Summary
Implements the Web Forms
Chartcontrol as a Blazor component using Chart.js v4.x via JS interop. This is the project's first JavaScript interop component. Brings the component count from 50/53 (94%) to 51/53 (96%).What's New
Chart Component Hierarchy
Chart.razor— Main component inheritingBaseStyledComponent. Renders<canvas>with JS interop to Chart.js.ChartSeries.razor— Child component for data series withChartType,XValueMember,YValueMembersparameters.ChartArea.razor— Chart area configuration withAxisX,AxisYparameters.ChartLegend.razor— Legend configuration withDocking,Enabledparameters.ChartTitle.razor— Title configuration withText,Dockingparameters.Axis.cs/DataPoint.cs— Supporting POCOs.ChartConfigBuilder.cs— Pure static class mapping component state → Chart.js JSON config (highly testable).ChartJsInterop.cs— C#IJSRuntimewrapper with lazy module loading.JS Interop Infrastructure
chart-interop.js— ES module bridge:createChart,updateChart,destroyChart.chart.min.js— Chart.js v4.4.8 placeholder stub (needs real bundle for production).Supported Chart Types (Phase 1)
Column, Line, Bar, Pie, Area, Doughnut, Scatter, StackedColumn — covering 90%+ of real-world usage.
Enums
SeriesChartType(35 values for API fidelity),ChartPalette(12 palettes),Docking,ChartDashStyle.HTML Output Exception
Web Forms Chart renders
<img src=\"/ChartImg.axd?...\">. Blazor Chart renders<canvas>. Justified: server-side image generation is architecturally impossible in Blazor; migration value is in the API, not the HTML.Testing
Bug Fixes (Boy Scout Rule)
Pages/ControlSamples/FileUpload/Default.razor)Documentation
docs/DataControls/Chart.md— Full component documentation with migration notesdocs/Migration/DeferredControls.md— Updated Chart statusmkdocs.yml,README.md,status.mdSample Pages
8 sample pages under
ControlSamples/Chart/:Index (Column), Line, Bar, Pie, Area, Doughnut, Scatter, StackedColumn
Design Decisions
BaseStyledComponentbase class (Web Forms Chart inherits WebControl)IAsyncDisposablefor JS interop cleanup