@polymorphism-tech/morph-spec 4.3.4 → 4.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.morph/.morphversion +5 -0
- package/.morph/config/agents.json +948 -0
- package/.morph/config/config.json +9 -9
- package/.morph/project/context/README.md +17 -0
- package/.morph/project/context/detection-log.md +16 -0
- package/.morph/project/standards/inferred.md +59 -0
- package/.morph/standards/ai-agents/blazor-ui.md +364 -0
- package/.morph/standards/ai-agents/production.md +415 -0
- package/.morph/standards/ai-agents/setup.md +418 -0
- package/.morph/standards/ai-agents/team-orchestration.md +479 -0
- package/.morph/standards/ai-agents/workflows.md +354 -0
- package/.morph/standards/architecture/ddd/aggregates.md +120 -0
- package/.morph/standards/architecture/ddd/entities.md +99 -0
- package/.morph/standards/architecture/ddd/value-objects.md +124 -0
- package/.morph/standards/backend/api/minimal-api.md +494 -0
- package/.morph/standards/backend/api/rest.md +492 -0
- package/.morph/standards/backend/api/validation.md +88 -0
- package/.morph/standards/backend/authentication/passkeys.md +428 -0
- package/.morph/standards/backend/database/ef-core.md +199 -0
- package/.morph/standards/backend/database/migrations.md +393 -0
- package/.morph/standards/backend/database/postgresql/database.md +352 -0
- package/.morph/standards/backend/database/repository-patterns.md +528 -0
- package/.morph/standards/backend/database/vector-search-rag.md +541 -0
- package/.morph/standards/backend/dotnet/async.md +366 -0
- package/.morph/standards/backend/dotnet/core.md +117 -0
- package/.morph/standards/backend/dotnet/di.md +439 -0
- package/.morph/standards/backend/dotnet/program-cs-checklist.md +92 -0
- package/.morph/standards/backend/integrations/asaas/asaas-api.md +216 -0
- package/.morph/standards/backend/integrations/clerk/clerk-auth.md +290 -0
- package/.morph/standards/backend/integrations/hangfire/hangfire-jobs.md +350 -0
- package/.morph/standards/backend/integrations/resend/resend-email.md +385 -0
- package/.morph/standards/context/analytics.md +96 -0
- package/.morph/standards/context/bundles.md +110 -0
- package/.morph/standards/context/priming.md +78 -0
- package/.morph/standards/core/architecture.md +185 -0
- package/.morph/standards/core/coding.md +214 -0
- package/.morph/standards/core/git-branching-strategy.md +403 -0
- package/.morph/standards/core/git.md +185 -0
- package/.morph/standards/core/testing.md +295 -0
- package/.morph/standards/data/nosql/blob-storage.md +102 -0
- package/.morph/standards/data/nosql/cache/redis.md +97 -0
- package/.morph/standards/data/nosql/cosmos-db.md +118 -0
- package/.morph/standards/data/vector-search/azure-ai-search.md +121 -0
- package/.morph/standards/data/vector-search/rag-chunking.md +104 -0
- package/.morph/standards/frontend/blazor/design-checklist.md +222 -0
- package/.morph/standards/frontend/blazor/fluent-ui-setup.md +595 -0
- package/.morph/standards/frontend/blazor/fluent-ui.md +137 -0
- package/.morph/standards/frontend/blazor/html-conversion.md +184 -0
- package/.morph/standards/frontend/blazor/lifecycle.md +195 -0
- package/.morph/standards/frontend/blazor/pitfalls.md +198 -0
- package/.morph/standards/frontend/blazor/state.md +191 -0
- package/.morph/standards/frontend/design-system/animations.md +151 -0
- package/.morph/standards/frontend/design-system/naming.md +64 -0
- package/.morph/standards/frontend/nextjs/nextjs-patterns.md +198 -0
- package/.morph/standards/infrastructure/azure/azure.md +624 -0
- package/.morph/standards/infrastructure/azure/bicep/bicep-patterns.md +422 -0
- package/.morph/standards/infrastructure/azure/devops/azure-devops-setup.md +516 -0
- package/.morph/standards/infrastructure/azure/devops/local-development.md +520 -0
- package/.morph/standards/infrastructure/azure/services/functions.md +486 -0
- package/.morph/standards/infrastructure/azure/services/service-bus.md +459 -0
- package/.morph/standards/infrastructure/azure/services/storage.md +407 -0
- package/.morph/standards/infrastructure/docker/easypanel-deploy.md +196 -0
- package/.morph/standards/infrastructure/supabase/mcp-setup.md +252 -0
- package/.morph/standards/infrastructure/supabase/supabase-auth.md +176 -0
- package/.morph/standards/infrastructure/supabase/supabase-pgvector.md +169 -0
- package/.morph/standards/infrastructure/supabase/supabase-rls.md +184 -0
- package/.morph/standards/infrastructure/supabase/supabase-storage.md +153 -0
- package/.morph/standards/integration/api/graphql.md +91 -0
- package/.morph/standards/integration/api/grpc.md +114 -0
- package/.morph/standards/integration/api/rest-design.md +95 -0
- package/.morph/standards/integration/event-driven/cqrs.md +101 -0
- package/.morph/standards/integration/event-driven/event-sourcing.md +124 -0
- package/.morph/standards/integration/event-driven/service-bus.md +95 -0
- package/.morph/standards/observability/logging.md +131 -0
- package/.morph/standards/observability/metrics.md +121 -0
- package/.morph/standards/observability/monitoring.md +114 -0
- package/.morph/standards/observability/tracing.md +132 -0
- package/.morph/standards/workflows/parallel-execution.md +112 -0
- package/.morph/standards/workflows/thread-management.md +113 -0
- package/.morph/templates/.idea/morph-templates.xml +92 -0
- package/.morph/templates/.vscode/morph-templates.code-snippets +186 -0
- package/.morph/templates/IDE-SNIPPETS.md +266 -0
- package/.morph/templates/README.md +814 -0
- package/.morph/templates/REGISTRY.json +1677 -0
- package/.morph/templates/code/dotnet/backend/repository.cs +141 -0
- package/.morph/templates/code/dotnet/backend/service.cs +139 -0
- package/.morph/templates/code/dotnet/contracts/Commands.cs +74 -0
- package/.morph/templates/code/dotnet/contracts/Entities.cs +25 -0
- package/.morph/templates/code/dotnet/contracts/Queries.cs +74 -0
- package/.morph/templates/code/dotnet/contracts/README.md +74 -0
- package/.morph/templates/code/dotnet/contracts/api-contracts.cs +173 -0
- package/.morph/templates/code/dotnet/contracts/contracts.cs +217 -0
- package/.morph/templates/code/dotnet/database/migration.cs +83 -0
- package/.morph/templates/code/dotnet/frontend/component.razor +239 -0
- package/.morph/templates/code/dotnet/jobs/agent.cs +163 -0
- package/.morph/templates/code/dotnet/jobs/job.cs +171 -0
- package/.morph/templates/code/dotnet/test.cs +239 -0
- package/.morph/templates/code/sql/rls-policy.sql +57 -0
- package/.morph/templates/code/sql/supabase-migration.sql +100 -0
- package/.morph/templates/code/sql/supabase-migration.template.sql +113 -0
- package/.morph/templates/code/typescript/contracts.ts +168 -0
- package/.morph/templates/context/CONTEXT-FEATURE.md +276 -0
- package/.morph/templates/context/CONTEXT.md +181 -0
- package/.morph/templates/docs/proposal.md +182 -0
- package/.morph/templates/docs/spec.md +149 -0
- package/.morph/templates/examples/design-system-examples.md +357 -0
- package/.morph/templates/examples/spec-examples.md +90 -0
- package/.morph/templates/feature/decisions.md +187 -0
- package/.morph/templates/feature/recap.md +146 -0
- package/.morph/templates/feature/tasks.md +199 -0
- package/.morph/templates/infrastructure/azure/Dockerfile.example +82 -0
- package/.morph/templates/infrastructure/azure/README.md +286 -0
- package/.morph/templates/infrastructure/azure/app-insights.bicep +63 -0
- package/.morph/templates/infrastructure/azure/app-service.bicep +164 -0
- package/.morph/templates/infrastructure/azure/container-app-env.bicep +49 -0
- package/.morph/templates/infrastructure/azure/container-app.bicep +156 -0
- package/.morph/templates/infrastructure/azure/deploy-checklist.md +426 -0
- package/.morph/templates/infrastructure/azure/deploy.ps1 +229 -0
- package/.morph/templates/infrastructure/azure/deploy.sh +208 -0
- package/.morph/templates/infrastructure/azure/key-vault.bicep +91 -0
- package/.morph/templates/infrastructure/azure/main.bicep +189 -0
- package/.morph/templates/infrastructure/azure/parameters.dev.json +29 -0
- package/.morph/templates/infrastructure/azure/parameters.prod.json +29 -0
- package/.morph/templates/infrastructure/azure/parameters.staging.json +29 -0
- package/.morph/templates/infrastructure/azure/sql-database.bicep +103 -0
- package/.morph/templates/infrastructure/azure/storage.bicep +106 -0
- package/.morph/templates/infrastructure/docker/Dockerfile.template +58 -0
- package/.morph/templates/infrastructure/docker/docker-compose.template.yml +67 -0
- package/.morph/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -0
- package/.morph/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -0
- package/.morph/templates/infrastructure/docker/easypanel.template.json +54 -0
- package/.morph/templates/infrastructure/github/README.md +593 -0
- package/.morph/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +22 -0
- package/.morph/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -0
- package/.morph/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -0
- package/.morph/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +61 -0
- package/.morph/templates/infrastructure/github/workflows/deploy-easypanel.yml.hbs +31 -0
- package/.morph/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -0
- package/.morph/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -0
- package/.morph/templates/integrations/asaas-client.cs +387 -0
- package/.morph/templates/integrations/asaas-webhook.cs +351 -0
- package/.morph/templates/integrations/azure-identity-config.cs +288 -0
- package/.morph/templates/integrations/clerk-config.cs +258 -0
- package/.morph/templates/meta-prompts/fusion/fusion-agent.md +76 -0
- package/.morph/templates/meta-prompts/fusion/fusion-aggregator.md +100 -0
- package/.morph/templates/meta-prompts/hops/hop-retry.md +78 -0
- package/.morph/templates/meta-prompts/hops/hop-validation.md +97 -0
- package/.morph/templates/meta-prompts/hops/hop-wrapper.md +36 -0
- package/.morph/templates/meta-prompts/parallel-workers/parallel-coordinator.md +113 -0
- package/.morph/templates/meta-prompts/parallel-workers/parallel-worker.md +80 -0
- package/.morph/templates/meta-prompts/squad-leaders/backend-squad.md +90 -0
- package/.morph/templates/meta-prompts/squad-leaders/frontend-squad.md +126 -0
- package/.morph/templates/meta-prompts/squad-leaders/squad-leader.md +43 -0
- package/.morph/templates/meta-prompts/validators/checkpoint-validator.md +107 -0
- package/.morph/templates/meta-prompts/validators/pre-commit-validator.md +95 -0
- package/.morph/templates/saas/subscription.cs +347 -0
- package/.morph/templates/saas/tenant.cs +338 -0
- package/.morph/templates/state.template.json +17 -0
- package/.morph/templates/ui/FluentDesignTheme.cs +149 -0
- package/.morph/templates/ui/MudTheme.cs +281 -0
- package/.morph/templates/ui/design-system.css +226 -0
- package/bin/morph-spec.js +1 -1
- package/package.json +1 -1
- package/src/commands/project/update.js +185 -46
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Fluent UI Blazor - Reference Guide
|
|
2
|
+
|
|
3
|
+
> **Scope:** blazor-azure
|
|
4
|
+
> **Layer:** 2 (on keyword)
|
|
5
|
+
> **Keywords:** blazor, fluent, fluentui, components, datagrid, dialog
|
|
6
|
+
> **Load When:** fluent ui keywords detected
|
|
7
|
+
|
|
8
|
+
## Quick Reference
|
|
9
|
+
|
|
10
|
+
| Topic | Correct | Incorrect |
|
|
11
|
+
|-------|---------|-----------|
|
|
12
|
+
| **Icon Sizes** | Size20, Size24, Size28, Size32, Size48 | Size12, Size14, Size16 (don't exist) |
|
|
13
|
+
| **Icon Namespace** | `@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons` | `@using ...Icons` (without alias) |
|
|
14
|
+
| **Toast Methods** | `ToastService.ShowSuccess("msg")` | `await ToastService.ShowSuccessAsync("msg")` |
|
|
15
|
+
| **Dialog Open** | `_dialog.Show()` | `await _dialog.ShowAsync()` |
|
|
16
|
+
| **Dialog Close** | `await _dialog.CloseAsync()` | `await _dialog.HideAsync()` |
|
|
17
|
+
| **Dialog Visibility** | `Hidden="true"` (default hidden) | Without Hidden (visible on load!) |
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
```razor
|
|
22
|
+
@* _Imports.razor *@
|
|
23
|
+
@using Microsoft.FluentUI.AspNetCore.Components
|
|
24
|
+
@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```csharp
|
|
28
|
+
// Program.cs
|
|
29
|
+
builder.Services.AddFluentUIComponents();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```razor
|
|
33
|
+
@* MainLayout.razor *@
|
|
34
|
+
<FluentToastProvider />
|
|
35
|
+
<FluentDialogProvider />
|
|
36
|
+
<main>@Body</main>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Icons
|
|
40
|
+
|
|
41
|
+
| Size | Use For |
|
|
42
|
+
|------|---------|
|
|
43
|
+
| Size20 | Inputs, badges, inline |
|
|
44
|
+
| Size24 | Buttons, lists, menus |
|
|
45
|
+
| Size28 | Medium-large elements |
|
|
46
|
+
| Size32 | Headers, cards |
|
|
47
|
+
| Size48 | Illustrations, empty states |
|
|
48
|
+
|
|
49
|
+
```razor
|
|
50
|
+
<FluentIcon Value="@(new Icons.Regular.Size24.Home())" />
|
|
51
|
+
<FluentIcon Value="@(new Icons.Filled.Size20.CheckmarkCircle())" Color="Color.Accent" />
|
|
52
|
+
|
|
53
|
+
<!-- For sizes < 20, use CSS -->
|
|
54
|
+
<FluentIcon Value="@(new Icons.Filled.Size20.Star())" Style="width: 14px; height: 14px;" />
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Missing Icons — Alternatives:**
|
|
58
|
+
|
|
59
|
+
| Missing | Use Instead |
|
|
60
|
+
|---------|-------------|
|
|
61
|
+
| Compass | CompassNorthwest |
|
|
62
|
+
| Instagram | Camera |
|
|
63
|
+
| WhatsApp | Chat |
|
|
64
|
+
| Facebook/Twitter/LinkedIn | Share / Person |
|
|
65
|
+
|
|
66
|
+
## Toast Service (Synchronous!)
|
|
67
|
+
|
|
68
|
+
```csharp
|
|
69
|
+
@inject IToastService ToastService
|
|
70
|
+
|
|
71
|
+
// Methods are SYNC, not async
|
|
72
|
+
ToastService.ShowSuccess("Saved!");
|
|
73
|
+
ToastService.ShowError("Error: " + ex.Message);
|
|
74
|
+
ToastService.ShowWarning("Warning");
|
|
75
|
+
ToastService.ShowInfo("Processing...");
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## FluentDialog
|
|
79
|
+
|
|
80
|
+
```razor
|
|
81
|
+
<FluentButton OnClick="() => _dialog.Show()">Open</FluentButton>
|
|
82
|
+
|
|
83
|
+
<FluentDialog @ref="_dialog" Hidden="true" Modal="true" PreventDismissOnOverlayClick="true">
|
|
84
|
+
<FluentDialogHeader>
|
|
85
|
+
<FluentLabel Typo="Typography.H4">Confirm</FluentLabel>
|
|
86
|
+
</FluentDialogHeader>
|
|
87
|
+
<FluentDialogBody><p>Continue?</p></FluentDialogBody>
|
|
88
|
+
<FluentDialogFooter>
|
|
89
|
+
<FluentButton Appearance="Appearance.Neutral" OnClick="() => _dialog.CloseAsync()">Cancel</FluentButton>
|
|
90
|
+
<FluentButton Appearance="Appearance.Accent" OnClick="Confirm">OK</FluentButton>
|
|
91
|
+
</FluentDialogFooter>
|
|
92
|
+
</FluentDialog>
|
|
93
|
+
|
|
94
|
+
@code {
|
|
95
|
+
private FluentDialog _dialog = default!;
|
|
96
|
+
private async Task Confirm() { /* logic */ await _dialog.CloseAsync(); }
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
> **Custom modals:** See `html-to-blazor.md` for custom modal pattern when design doesn't follow Fluent.
|
|
101
|
+
|
|
102
|
+
## FluentDataGrid
|
|
103
|
+
|
|
104
|
+
```razor
|
|
105
|
+
<FluentDataGrid Items="@_orders" Virtualize="true" ItemSize="50">
|
|
106
|
+
<PropertyColumn Property="@(o => o.OrderNumber)" Title="Order" Sortable="true" />
|
|
107
|
+
<PropertyColumn Property="@(o => o.Total)" Title="Total" Format="C" />
|
|
108
|
+
<TemplateColumn Title="Status">
|
|
109
|
+
<FluentBadge Appearance="@GetBadge(context.Status)">@context.Status</FluentBadge>
|
|
110
|
+
</TemplateColumn>
|
|
111
|
+
<TemplateColumn Title="Actions">
|
|
112
|
+
<FluentButton Appearance="Appearance.Lightweight" OnClick="@(() => View(context))">View</FluentButton>
|
|
113
|
+
</TemplateColumn>
|
|
114
|
+
</FluentDataGrid>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Common Errors
|
|
118
|
+
|
|
119
|
+
| Error | Cause | Fix |
|
|
120
|
+
|-------|-------|-----|
|
|
121
|
+
| "Size16 does not exist" | Invalid icon size | Use Size20, 24, 28, 32, 48 |
|
|
122
|
+
| "'Regular' not in context" | Missing icon alias | `@using Icons = ...Icons` |
|
|
123
|
+
| Dialog visible on page load | Missing `Hidden="true"` | Add `Hidden="true"` |
|
|
124
|
+
| "ShowSuccessAsync not found" | Async method doesn't exist | Use sync `ShowSuccess()` |
|
|
125
|
+
|
|
126
|
+
## Checklist
|
|
127
|
+
|
|
128
|
+
- [ ] `AddFluentUIComponents()` in Program.cs
|
|
129
|
+
- [ ] `FluentToastProvider` + `FluentDialogProvider` in layout
|
|
130
|
+
- [ ] Icons use alias `Icons =` and valid sizes (20-48)
|
|
131
|
+
- [ ] Toast methods are synchronous
|
|
132
|
+
- [ ] Dialogs have `Hidden="true"`
|
|
133
|
+
- [ ] DataGrid uses `Virtualize` for large lists
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Guia de Conversao HTML para Blazor
|
|
2
|
+
|
|
3
|
+
> **Scope:** blazor-azure
|
|
4
|
+
> **Layer:** 2 (on keyword)
|
|
5
|
+
> **Keywords:** blazor, html, conversion, prototype, migrate
|
|
6
|
+
> **Load When:** html conversion keywords detected
|
|
7
|
+
|
|
8
|
+
## Element Mapping
|
|
9
|
+
|
|
10
|
+
### Basic Elements
|
|
11
|
+
|
|
12
|
+
| HTML | Blazor (Fluent UI) |
|
|
13
|
+
|------|---------------------|
|
|
14
|
+
| `<button class="btn-primary">` | `<FluentButton Appearance="Appearance.Accent">` |
|
|
15
|
+
| `<button class="btn-secondary">` | `<FluentButton Appearance="Appearance.Neutral">` |
|
|
16
|
+
| `<input type="text">` | `<FluentTextField @bind-Value="Model.Field">` |
|
|
17
|
+
| `<input type="email">` | `<FluentTextField Type="TextFieldType.Email">` |
|
|
18
|
+
| `<input type="password">` | `<FluentTextField Type="TextFieldType.Password">` |
|
|
19
|
+
| `<textarea>` | `<FluentTextArea @bind-Value="Model.Field">` |
|
|
20
|
+
| `<select>` | `<FluentSelect @bind-Value="Model.Field">` |
|
|
21
|
+
| `<input type="checkbox">` | `<FluentCheckbox @bind-Value="Model.Flag">` |
|
|
22
|
+
| `<input type="radio">` | `<FluentRadioGroup @bind-Value="Model.Option">` |
|
|
23
|
+
|
|
24
|
+
### Containers & Layout
|
|
25
|
+
|
|
26
|
+
| HTML | Blazor |
|
|
27
|
+
|------|--------|
|
|
28
|
+
| `<div class="card">` | `<FluentCard>` |
|
|
29
|
+
| `<div class="flex">` | `<FluentStack Orientation="Horizontal">` |
|
|
30
|
+
| `<div class="flex-col">` | `<FluentStack Orientation="Vertical">` |
|
|
31
|
+
| `<div class="grid">` | `<FluentGrid>` |
|
|
32
|
+
| `<div class="modal">` | Custom HTML or `<FluentDialog>` |
|
|
33
|
+
|
|
34
|
+
### Feedback & Data
|
|
35
|
+
|
|
36
|
+
| HTML | Blazor |
|
|
37
|
+
|------|--------|
|
|
38
|
+
| `<div class="alert-success">` | `<FluentMessageBar Intent="MessageIntent.Success">` |
|
|
39
|
+
| `<div class="alert-error">` | `<FluentMessageBar Intent="MessageIntent.Error">` |
|
|
40
|
+
| `<div class="toast">` | `ToastService.ShowSuccess("msg")` |
|
|
41
|
+
| `<div class="spinner">` | `<FluentProgress />` |
|
|
42
|
+
| `<table>` | `<FluentDataGrid Items="@items">` |
|
|
43
|
+
| `<th>` | `<PropertyColumn Property="@(x => x.Prop)">` |
|
|
44
|
+
| `<td>` with actions | `<TemplateColumn>` |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Page Structure
|
|
49
|
+
|
|
50
|
+
```razor
|
|
51
|
+
@page "/pagename"
|
|
52
|
+
@rendermode InteractiveServer
|
|
53
|
+
@inject IMyService _service
|
|
54
|
+
|
|
55
|
+
<PageTitle>App - Pagina</PageTitle>
|
|
56
|
+
|
|
57
|
+
<div class="page-layout">
|
|
58
|
+
<header class="page-header-nav"><!-- Nav --></header>
|
|
59
|
+
<div class="page-content container">
|
|
60
|
+
<h1>Titulo</h1>
|
|
61
|
+
<!-- Content -->
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
@code {
|
|
66
|
+
private bool _isLoading;
|
|
67
|
+
private string? _error;
|
|
68
|
+
protected override async Task OnInitializedAsync() => await LoadDataAsync();
|
|
69
|
+
private async Task HandleClick() { }
|
|
70
|
+
private string FormatValue(decimal value) => value.ToString("C");
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Event Conversion
|
|
77
|
+
|
|
78
|
+
| HTML Event | Blazor Pattern |
|
|
79
|
+
|------------|---------------|
|
|
80
|
+
| `onclick="fn()"` | `@onclick="HandleClick"` (sync) or `@onclick="HandleClickAsync"` (async) |
|
|
81
|
+
| `onchange="fn(val)"` | `@bind-Value="_val"` (two-way) or `@onchange="HandleChange"` (one-way) |
|
|
82
|
+
| `<form onsubmit>` | `<EditForm Model="@_model" OnValidSubmit="HandleSubmit">` with `<DataAnnotationsValidator />` |
|
|
83
|
+
|
|
84
|
+
### Form Example
|
|
85
|
+
|
|
86
|
+
```razor
|
|
87
|
+
<EditForm Model="@_model" OnValidSubmit="HandleSubmit">
|
|
88
|
+
<DataAnnotationsValidator />
|
|
89
|
+
<FluentTextField @bind-Value="_model.Email" Label="Email" Required />
|
|
90
|
+
<FluentButton Type="ButtonType.Submit" Appearance="Appearance.Accent">Enviar</FluentButton>
|
|
91
|
+
</EditForm>
|
|
92
|
+
|
|
93
|
+
@code {
|
|
94
|
+
private MyModel _model = new();
|
|
95
|
+
private async Task HandleSubmit() => await _service.SaveAsync(_model);
|
|
96
|
+
public class MyModel { [Required][EmailAddress] public string Email { get; set; } = ""; }
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Modal Conversion
|
|
103
|
+
|
|
104
|
+
**Use custom HTML** when: custom design, backdrop blur, complex animations.
|
|
105
|
+
**Use `<FluentDialog>`** for simple confirmations.
|
|
106
|
+
|
|
107
|
+
### Custom Modal
|
|
108
|
+
|
|
109
|
+
```razor
|
|
110
|
+
@if (!_modalHidden)
|
|
111
|
+
{
|
|
112
|
+
<div class="modal-overlay" @onclick="CloseModal">
|
|
113
|
+
<div class="modal-container animate-scaleIn" @onclick:stopPropagation="true">
|
|
114
|
+
<div class="modal-header">
|
|
115
|
+
<h3>Titulo</h3>
|
|
116
|
+
<button class="modal-close" @onclick="CloseModal">
|
|
117
|
+
<FluentIcon Value="@(new Icons.Regular.Size20.Dismiss())" />
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="modal-body">Conteudo...</div>
|
|
121
|
+
<div class="modal-footer">
|
|
122
|
+
<FluentButton Appearance="Appearance.Neutral" @onclick="CloseModal">Cancelar</FluentButton>
|
|
123
|
+
<FluentButton Appearance="Appearance.Accent" @onclick="Confirm">Confirmar</FluentButton>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
}
|
|
128
|
+
@code {
|
|
129
|
+
private bool _modalHidden = true;
|
|
130
|
+
private void OpenModal() => _modalHidden = false;
|
|
131
|
+
private void CloseModal() => _modalHidden = true;
|
|
132
|
+
private async Task Confirm() { /* logic */ CloseModal(); }
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Style Interpolation
|
|
139
|
+
|
|
140
|
+
```razor
|
|
141
|
+
<!-- Static: same as HTML -->
|
|
142
|
+
<div style="color: red; margin-top: 20px;">
|
|
143
|
+
|
|
144
|
+
<!-- Dynamic: ALWAYS use @($"...") -->
|
|
145
|
+
<div style="@($"color: {_color}; margin-top: {_margin}px;")">
|
|
146
|
+
|
|
147
|
+
<!-- WRONG: causes RZ9986 -->
|
|
148
|
+
<FluentStack Style="animation-delay: @(0.05 * index)s;">
|
|
149
|
+
<!-- CORRECT -->
|
|
150
|
+
<FluentStack Style="@($"animation-delay: {0.05 * index}s;")">
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Loops & Conditionals
|
|
154
|
+
|
|
155
|
+
```razor
|
|
156
|
+
<!-- Loop -->
|
|
157
|
+
@foreach (var item in _items) { <li>@item.Name</li> }
|
|
158
|
+
|
|
159
|
+
<!-- Loop with stagger animation -->
|
|
160
|
+
@for (int i = 0; i < _items.Count; i++)
|
|
161
|
+
{
|
|
162
|
+
var index = i; // capture for closure
|
|
163
|
+
<li class="animate-slideInUp" style="@($"animation-delay: {0.05 * index}s;")">@_items[index].Name</li>
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
<!-- Conditional rendering (replaces display:none toggle) -->
|
|
167
|
+
@if (_isLoading) { <FluentProgress /> }
|
|
168
|
+
else if (_error != null) { <FluentMessageBar Intent="MessageIntent.Error">@_error</FluentMessageBar> }
|
|
169
|
+
else { <FluentDataGrid Items="@_items"><!-- columns --></FluentDataGrid> }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Checklist
|
|
173
|
+
|
|
174
|
+
- [ ] Page structure: `@page`, `@rendermode`, `@inject`
|
|
175
|
+
- [ ] HTML elements mapped to Fluent UI components
|
|
176
|
+
- [ ] Events: `onclick` → `@onclick`, forms → `EditForm`
|
|
177
|
+
- [ ] Modals: custom vs `FluentDialog` decided
|
|
178
|
+
- [ ] Style interpolation uses `@($"...")`
|
|
179
|
+
- [ ] Loops use `var index = i` for closures
|
|
180
|
+
- [ ] CSS classes validated with `morph-spec validate-css`
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Blazor Lifecycle: Pitfalls & Solutions
|
|
2
|
+
|
|
3
|
+
> **Scope:** blazor-azure
|
|
4
|
+
> **Layer:** 2 (on keyword)
|
|
5
|
+
> **Keywords:** blazor, lifecycle, oninit, onafter, onparametersset, dispose
|
|
6
|
+
> **Load When:** blazor work
|
|
7
|
+
|
|
8
|
+
## Lifecycle Flow
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
1. SetParametersAsync
|
|
12
|
+
2. OnInitialized(Async) ← No JS available (prerendering)
|
|
13
|
+
3. OnParametersSet(Async)
|
|
14
|
+
4. BuildRenderTree (Render)
|
|
15
|
+
5. OnAfterRender(Async) ← JS available (firstRender=true)
|
|
16
|
+
6. [User Interaction] → 3→4→5 (firstRender=false)
|
|
17
|
+
7. Dispose (component removed)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Decision Matrix
|
|
21
|
+
|
|
22
|
+
| Need to... | Use Method | Note |
|
|
23
|
+
|------------|------------|------|
|
|
24
|
+
| Load data from API/DB | `OnInitializedAsync` | No JS available |
|
|
25
|
+
| Call JavaScript / DOM | `OnAfterRenderAsync(firstRender)` | firstRender guard |
|
|
26
|
+
| React to parameter changes | `OnParametersSetAsync` | Called on every change |
|
|
27
|
+
| Clean up resources | `Dispose` | Timers, subscriptions |
|
|
28
|
+
| Update UI after async op | `StateHasChanged()` | Manual re-render |
|
|
29
|
+
|
|
30
|
+
## Common Pitfalls
|
|
31
|
+
|
|
32
|
+
| Problem | Error | Solution |
|
|
33
|
+
|---------|-------|----------|
|
|
34
|
+
| JSRuntime in OnInitialized | "statically rendered" | Move to `OnAfterRenderAsync(firstRender)` |
|
|
35
|
+
| Layout with @rendermode | "Cannot pass Body with rendermode" | Remove `@rendermode` from layouts |
|
|
36
|
+
| HttpClient no BaseAddress | "Invalid request URI" | Use `NavigationManager.BaseUri` |
|
|
37
|
+
| UI not updating after await | Stale UI | Call `StateHasChanged()` after async |
|
|
38
|
+
| File upload > 512KB | "exceeds maximum size" | `OpenReadStream(maxAllowedSize: size)` |
|
|
39
|
+
| Dispose not called | Memory leaks | Implement `IDisposable` |
|
|
40
|
+
| Cascading param null | NullRef during init | Check null in `OnParametersSet()` |
|
|
41
|
+
| Infinite render loop | Browser freezes | Never `StateHasChanged()` in property setters |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Essential Patterns
|
|
46
|
+
|
|
47
|
+
### JSRuntime (OnAfterRender only)
|
|
48
|
+
|
|
49
|
+
```csharp
|
|
50
|
+
@inject IJSRuntime JSRuntime
|
|
51
|
+
@code {
|
|
52
|
+
private bool _initialized;
|
|
53
|
+
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
54
|
+
{
|
|
55
|
+
if (firstRender && !_initialized)
|
|
56
|
+
{
|
|
57
|
+
await JSRuntime.InvokeVoidAsync("initializeChart", "myChart");
|
|
58
|
+
_initialized = true;
|
|
59
|
+
StateHasChanged();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Data Loading
|
|
66
|
+
|
|
67
|
+
```csharp
|
|
68
|
+
@code {
|
|
69
|
+
private List<Item> Items { get; set; } = new();
|
|
70
|
+
private bool Loading = true;
|
|
71
|
+
private string? Error;
|
|
72
|
+
|
|
73
|
+
protected override async Task OnInitializedAsync()
|
|
74
|
+
{
|
|
75
|
+
try { Items = await HttpClient.GetFromJsonAsync<List<Item>>("/api/items"); }
|
|
76
|
+
catch (Exception ex) { Error = ex.Message; }
|
|
77
|
+
finally { Loading = false; }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Parameter Change
|
|
83
|
+
|
|
84
|
+
```csharp
|
|
85
|
+
@code {
|
|
86
|
+
[Parameter] public int ProductId { get; set; }
|
|
87
|
+
private Product? Product;
|
|
88
|
+
|
|
89
|
+
protected override async Task OnParametersSetAsync()
|
|
90
|
+
{
|
|
91
|
+
if (ProductId > 0)
|
|
92
|
+
Product = await HttpClient.GetFromJsonAsync<Product>($"/api/products/{ProductId}");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Dispose Pattern
|
|
98
|
+
|
|
99
|
+
```csharp
|
|
100
|
+
@implements IDisposable
|
|
101
|
+
@code {
|
|
102
|
+
private System.Timers.Timer? _timer;
|
|
103
|
+
protected override void OnInitialized()
|
|
104
|
+
{
|
|
105
|
+
_timer = new System.Timers.Timer(1000);
|
|
106
|
+
_timer.Elapsed += (s, e) => InvokeAsync(StateHasChanged);
|
|
107
|
+
_timer.Start();
|
|
108
|
+
}
|
|
109
|
+
public void Dispose() { _timer?.Stop(); _timer?.Dispose(); }
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### File Upload
|
|
114
|
+
|
|
115
|
+
```csharp
|
|
116
|
+
@code {
|
|
117
|
+
private const long MaxFileSize = 10 * 1024 * 1024;
|
|
118
|
+
private async Task HandleFile(InputFileChangeEventArgs e)
|
|
119
|
+
{
|
|
120
|
+
if (e.File.Size > MaxFileSize) { Error = "File > 10MB"; return; }
|
|
121
|
+
using var stream = e.File.OpenReadStream(maxAllowedSize: MaxFileSize);
|
|
122
|
+
var buffer = new byte[e.File.Size];
|
|
123
|
+
await stream.ReadAsync(buffer);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Infinite Render Loop
|
|
129
|
+
|
|
130
|
+
```csharp
|
|
131
|
+
// WRONG - StateHasChanged in property setter = infinite loop
|
|
132
|
+
private int Counter { get => _c; set { _c = value; StateHasChanged(); } }
|
|
133
|
+
|
|
134
|
+
// CORRECT - StateHasChanged only in event handlers
|
|
135
|
+
private int _c;
|
|
136
|
+
private void Increment() { _c++; StateHasChanged(); }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Performance
|
|
142
|
+
|
|
143
|
+
| Pattern | Code |
|
|
144
|
+
|---------|------|
|
|
145
|
+
| Prevent unnecessary renders | `protected override bool ShouldRender() => _dataChanged;` |
|
|
146
|
+
| List items with key | `@foreach (var item in Items) { <ItemComponent @key="item.Id" Item="@item" /> }` |
|
|
147
|
+
| Debounce input | `_debounce = new Timer(500); _debounce.Elapsed += async (s, a) => await Search(value);` |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Checklist
|
|
152
|
+
|
|
153
|
+
- [ ] No JSRuntime in `OnInitializedAsync`
|
|
154
|
+
- [ ] `StateHasChanged()` after async operations
|
|
155
|
+
- [ ] File uploads specify `maxAllowedSize`
|
|
156
|
+
- [ ] Dispose pattern for subscriptions/timers
|
|
157
|
+
- [ ] No `@rendermode` on layouts
|
|
158
|
+
- [ ] Cascading parameters null-checked
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Code Review Checklist
|
|
167
|
+
|
|
168
|
+
Use this checklist during code review for Blazor component lifecycle:
|
|
169
|
+
|
|
170
|
+
### OnInitializedAsync vs OnAfterRenderAsync
|
|
171
|
+
|
|
172
|
+
- [ ] **JSRuntime calls are in `OnAfterRenderAsync`, not `OnInitializedAsync`?**
|
|
173
|
+
- [ ] **Database/API calls are in `OnInitializedAsync`, not `OnAfterRenderAsync`?**
|
|
174
|
+
- [ ] **`firstRender` check is used to prevent duplicate JS calls?**
|
|
175
|
+
|
|
176
|
+
### StateHasChanged
|
|
177
|
+
|
|
178
|
+
- [ ] **`StateHasChanged()` called after async operations that update UI?**
|
|
179
|
+
- [ ] **No excessive `StateHasChanged()` calls in loops?**
|
|
180
|
+
|
|
181
|
+
### IDisposable / IAsyncDisposable
|
|
182
|
+
|
|
183
|
+
- [ ] **Component implements `IDisposable` if it has timers, event handlers, or CTS?**
|
|
184
|
+
- [ ] **`CancellationTokenSource` is cancelled and disposed?**
|
|
185
|
+
- [ ] **Event handlers are unsubscribed in `Dispose`?**
|
|
186
|
+
- [ ] **Timers are stopped and disposed?**
|
|
187
|
+
|
|
188
|
+
### RenderMode
|
|
189
|
+
|
|
190
|
+
- [ ] **`@rendermode InteractiveServer` is on pages, NOT on layouts?**
|
|
191
|
+
- [ ] **Layout components have NO `@rendermode` directive?**
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
*MORPH-SPEC by Polymorphism Tech*
|