@polymorphism-tech/morph-spec 4.10.8 → 4.10.10

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.
Files changed (116) hide show
  1. package/CLAUDE.md +3 -3
  2. package/LICENSE +71 -71
  3. package/README.md +2 -2
  4. package/bin/morph-spec.js +22 -0
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/agents/README.md +14 -14
  9. package/framework/agents/architecture/standards-architect.md +159 -159
  10. package/framework/agents/infrastructure/azure-architect.md +149 -149
  11. package/framework/agents/infrastructure/infra-architect.md +48 -48
  12. package/framework/hooks/README.md +204 -204
  13. package/framework/hooks/shared/phase-utils.js +139 -139
  14. package/framework/rules/csharp-standards.md +13 -13
  15. package/framework/rules/frontend-standards.md +13 -13
  16. package/framework/rules/infrastructure-standards.md +16 -16
  17. package/framework/rules/testing-standards.md +14 -14
  18. package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +254 -254
  19. package/framework/skills/level-0-meta/morph-init/SKILL.md +57 -9
  20. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +1 -1
  21. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +1 -1
  22. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +1 -1
  23. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +38 -0
  24. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +1 -1
  25. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +1 -1
  26. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +1 -1
  27. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +14 -0
  28. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +1 -1
  29. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +49 -16
  30. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
  31. package/framework/standards/STANDARDS.json +2338 -2041
  32. package/framework/standards/frontend/nextjs/nextjs-patterns.md +17 -17
  33. package/framework/standards/infrastructure/supabase/supabase-custom-jwt.md +206 -0
  34. package/framework/templates/.idea/morph-templates.xml +92 -0
  35. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  36. package/framework/templates/README.md +814 -814
  37. package/framework/templates/code/dotnet/contracts/Commands.cs +74 -74
  38. package/framework/templates/code/dotnet/contracts/Entities.cs +25 -25
  39. package/framework/templates/code/dotnet/contracts/Queries.cs +74 -74
  40. package/framework/templates/code/dotnet/contracts/README.md +74 -74
  41. package/framework/templates/code/dotnet/contracts/api-contracts.cs +173 -173
  42. package/framework/templates/code/dotnet/frontend/component.razor +239 -239
  43. package/framework/templates/code/dotnet/jobs/agent.cs +163 -163
  44. package/framework/templates/code/dotnet/jobs/job.cs +171 -171
  45. package/framework/templates/code/dotnet/test.cs +239 -239
  46. package/framework/templates/code/sql/rls-policy.sql +57 -57
  47. package/framework/templates/code/sql/supabase-migration.sql +100 -100
  48. package/framework/templates/code/sql/supabase-migration.template.sql +113 -113
  49. package/framework/templates/code/typescript/contracts.ts +168 -168
  50. package/framework/templates/context/CONTEXT.md +181 -181
  51. package/framework/templates/docs/proposal.md +182 -182
  52. package/framework/templates/docs/user-stories.md +34 -34
  53. package/framework/templates/examples/design-system-examples.md +357 -357
  54. package/framework/templates/examples/spec-examples.md +90 -90
  55. package/framework/templates/feature/decisions.md +187 -187
  56. package/framework/templates/feature/recap.md +146 -146
  57. package/framework/templates/feature/tasks.md +199 -199
  58. package/framework/templates/infrastructure/azure/README.md +286 -286
  59. package/framework/templates/infrastructure/azure/deploy-checklist.md +426 -426
  60. package/framework/templates/infrastructure/azure/parameters.dev.json +29 -29
  61. package/framework/templates/infrastructure/azure/parameters.prod.json +29 -29
  62. package/framework/templates/infrastructure/azure/parameters.staging.json +29 -29
  63. package/framework/templates/infrastructure/docker/docker-compose.template.yml +67 -67
  64. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  65. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  66. package/framework/templates/infrastructure/docker/easypanel.template.json +54 -54
  67. package/package.json +1 -1
  68. package/src/commands/agents/dispatch-agents.js +17 -0
  69. package/src/commands/agents/index.js +13 -13
  70. package/src/commands/agents/project-metrics.js +74 -0
  71. package/src/commands/project/install-plugin-cmd.js +28 -28
  72. package/src/commands/project/setup-infra-cmd.js +12 -12
  73. package/src/commands/standards/context-budget.js +89 -0
  74. package/src/commands/tasks/index.js +4 -4
  75. package/src/commands/templates/index.js +5 -5
  76. package/src/commands/utils/index.js +3 -3
  77. package/src/commands/validation/index.js +4 -4
  78. package/src/core/state/phase-state-machine.js +217 -217
  79. package/src/lib/agents/metrics-aggregator.js +56 -0
  80. package/src/lib/detectors/design-system-detector.js +73 -31
  81. package/src/lib/generators/design-system-generator.js +298 -298
  82. package/src/lib/generators/index.js +10 -10
  83. package/src/lib/generators/metadata-extractor.js +387 -387
  84. package/src/lib/stack-filter.js +65 -65
  85. package/src/lib/standards/digest-builder.js +57 -3
  86. package/src/lib/standards/index.js +7 -7
  87. package/src/lib/standards/standards-context-injector.js +302 -302
  88. package/src/lib/validators/architecture/architecture-validator.js +387 -387
  89. package/src/lib/validators/architecture/index.js +7 -7
  90. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +316 -316
  91. package/src/lib/validators/blazor/blazor-state-validator.js +279 -279
  92. package/src/lib/validators/blazor/blazor-validator.js +369 -369
  93. package/src/lib/validators/blazor/index.js +9 -9
  94. package/src/lib/validators/content/content-validator.js +287 -287
  95. package/src/lib/validators/content/index.js +7 -7
  96. package/src/lib/validators/contracts/index.js +7 -7
  97. package/src/lib/validators/css/css-validator.js +352 -352
  98. package/src/lib/validators/css/index.js +7 -7
  99. package/src/lib/validators/design-system/design-system-validator.js +231 -231
  100. package/src/lib/validators/design-system/index.js +7 -7
  101. package/src/lib/validators/nextjs/index.js +6 -6
  102. package/src/lib/validators/nextjs/next-component-validator.js +183 -183
  103. package/src/lib/validators/packages/index.js +7 -7
  104. package/src/lib/validators/packages/package-validator.js +360 -360
  105. package/src/lib/validators/shared/index.js +12 -12
  106. package/src/lib/validators/shared/issue-counter.js +18 -18
  107. package/src/lib/validators/shared/result-formatter.js +124 -124
  108. package/src/lib/validators/ui/index.js +7 -7
  109. package/src/lib/validators/ui/ui-contrast-validator.js +422 -422
  110. package/src/scripts/global-install.js +58 -58
  111. package/src/scripts/install-plugin.js +126 -126
  112. package/src/utils/claude-md-injector.js +90 -90
  113. package/src/utils/file-copier.js +200 -200
  114. package/src/utils/logger.js +32 -32
  115. package/src/utils/skills-installer.js +178 -178
  116. package/src/utils/version-checker.js +175 -175
@@ -1,239 +1,239 @@
1
- @* ============================================================
2
- BLAZOR COMPONENT TEMPLATE
3
- Generated by MORPH Framework
4
- ============================================================ *@
5
-
6
- @page "/{{kebabCase FEATURE_NAME}}/{{kebabCase FEATURE_NAME}}s"
7
- @attribute [Authorize(Policy = "CanView{{pascalCase FEATURE_NAME}}")]
8
- @inject I{{pascalCase FEATURE_NAME}}Service {{pascalCase FEATURE_NAME}}Service
9
- @inject ILogger<{{pascalCase FEATURE_NAME}}List> Logger
10
- @inject NavigationManager Navigation
11
-
12
- <PageTitle>{{pascalCase FEATURE_NAME}}s</PageTitle>
13
-
14
- <div class="container-fluid">
15
- <div class="row mb-4">
16
- <div class="col">
17
- <h1>{{pascalCase FEATURE_NAME}}s</h1>
18
- </div>
19
- <div class="col-auto">
20
- <AuthorizeView Policy="CanManage{{pascalCase FEATURE_NAME}}">
21
- <button class="btn btn-primary" @onclick="ShowCreateModal">
22
- <i class="bi bi-plus-lg"></i> New {{pascalCase FEATURE_NAME}}
23
- </button>
24
- </AuthorizeView>
25
- </div>
26
- </div>
27
-
28
- @if (_isLoading)
29
- {
30
- <div class="d-flex justify-content-center py-5">
31
- <div class="spinner-border text-primary" role="status">
32
- <span class="visually-hidden">Loading...</span>
33
- </div>
34
- </div>
35
- }
36
- else if (_error is not null)
37
- {
38
- <div class="alert alert-danger" role="alert">
39
- <i class="bi bi-exclamation-triangle"></i>
40
- @_error
41
- <button class="btn btn-link" @onclick="LoadDataAsync">Retry</button>
42
- </div>
43
- }
44
- else if (_items is null || !_items.Any())
45
- {
46
- <div class="text-center py-5">
47
- <i class="bi bi-inbox display-1 text-muted"></i>
48
- <p class="lead mt-3">No {{kebabCase FEATURE_NAME}}s found</p>
49
- <AuthorizeView Policy="CanManage{{pascalCase FEATURE_NAME}}">
50
- <button class="btn btn-primary" @onclick="ShowCreateModal">
51
- Create your first {{kebabCase FEATURE_NAME}}
52
- </button>
53
- </AuthorizeView>
54
- </div>
55
- }
56
- else
57
- {
58
- <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
59
- @foreach (var item in _items)
60
- {
61
- <div class="col">
62
- <div class="card h-100">
63
- <div class="card-body">
64
- <h5 class="card-title">@item.Name</h5>
65
- <p class="card-text">
66
- <span class="badge @GetStatusBadgeClass(item.Status)">
67
- @item.Status
68
- </span>
69
- </p>
70
- </div>
71
- <div class="card-footer bg-transparent">
72
- <small class="text-muted">
73
- Created @item.CreatedAt.ToString("d")
74
- </small>
75
- <AuthorizeView Policy="CanManage{{pascalCase FEATURE_NAME}}">
76
- <div class="float-end">
77
- <button class="btn btn-sm btn-outline-primary"
78
- @onclick="() => ShowEditModal(item)">
79
- Edit
80
- </button>
81
- </div>
82
- </AuthorizeView>
83
- </div>
84
- </div>
85
- </div>
86
- }
87
- </div>
88
- }
89
- </div>
90
-
91
- @* Modal for Create/Edit *@
92
- @if (_showModal)
93
- {
94
- <div class="modal fade show d-block" tabindex="-1" style="background: rgba(0,0,0,0.5)">
95
- <div class="modal-dialog">
96
- <div class="modal-content">
97
- <div class="modal-header">
98
- <h5 class="modal-title">
99
- @(_editingItem is null ? "New {{pascalCase FEATURE_NAME}}" : "Edit {{pascalCase FEATURE_NAME}}")
100
- </h5>
101
- <button type="button" class="btn-close" @onclick="CloseModal"></button>
102
- </div>
103
- <EditForm Model="_formModel" OnValidSubmit="HandleSubmitAsync">
104
- <DataAnnotationsValidator />
105
- <div class="modal-body">
106
- <div class="mb-3">
107
- <label class="form-label">Name</label>
108
- <InputText class="form-control" @bind-Value="_formModel.Name" />
109
- <ValidationMessage For="() => _formModel.Name" />
110
- </div>
111
- @* Add more fields as needed *@
112
- </div>
113
- <div class="modal-footer">
114
- <button type="button" class="btn btn-secondary" @onclick="CloseModal">
115
- Cancel
116
- </button>
117
- <button type="submit" class="btn btn-primary" disabled="@_isSaving">
118
- @if (_isSaving)
119
- {
120
- <span class="spinner-border spinner-border-sm"></span>
121
- }
122
- Save
123
- </button>
124
- </div>
125
- </EditForm>
126
- </div>
127
- </div>
128
- </div>
129
- }
130
-
131
- @code {
132
- private List<{{pascalCase FEATURE_NAME}}Dto>? _items;
133
- private bool _isLoading = true;
134
- private bool _showModal;
135
- private bool _isSaving;
136
- private string? _error;
137
- private {{pascalCase FEATURE_NAME}}Dto? _editingItem;
138
- private {{pascalCase FEATURE_NAME}}FormModel _formModel = new();
139
-
140
- protected override async Task OnInitializedAsync()
141
- {
142
- await LoadDataAsync();
143
- }
144
-
145
- private async Task LoadDataAsync()
146
- {
147
- _isLoading = true;
148
- _error = null;
149
-
150
- try
151
- {
152
- _items = await {{pascalCase FEATURE_NAME}}Service.GetAllAsync();
153
- }
154
- catch (Exception ex)
155
- {
156
- Logger.LogError(ex, "Failed to load {{kebabCase FEATURE_NAME}}s");
157
- _error = "Failed to load data. Please try again.";
158
- }
159
- finally
160
- {
161
- _isLoading = false;
162
- }
163
- }
164
-
165
- private void ShowCreateModal()
166
- {
167
- _editingItem = null;
168
- _formModel = new {{pascalCase FEATURE_NAME}}FormModel();
169
- _showModal = true;
170
- }
171
-
172
- private void ShowEditModal({{pascalCase FEATURE_NAME}}Dto item)
173
- {
174
- _editingItem = item;
175
- _formModel = new {{pascalCase FEATURE_NAME}}FormModel
176
- {
177
- Name = item.Name
178
- // Map other fields
179
- };
180
- _showModal = true;
181
- }
182
-
183
- private void CloseModal()
184
- {
185
- _showModal = false;
186
- _formModel = new();
187
- }
188
-
189
- private async Task HandleSubmitAsync()
190
- {
191
- _isSaving = true;
192
-
193
- try
194
- {
195
- if (_editingItem is null)
196
- {
197
- // Create
198
- var request = new Create{{pascalCase FEATURE_NAME}}Request(_formModel.Name);
199
- await {{pascalCase FEATURE_NAME}}Service.CreateAsync(request);
200
- }
201
- else
202
- {
203
- // Update
204
- var request = new Update{{pascalCase FEATURE_NAME}}Request(_formModel.Name);
205
- await {{pascalCase FEATURE_NAME}}Service.UpdateAsync(_editingItem.Id, request);
206
- }
207
-
208
- CloseModal();
209
- await LoadDataAsync();
210
- }
211
- catch (Exception ex)
212
- {
213
- Logger.LogError(ex, "Failed to save {{kebabCase FEATURE_NAME}}");
214
- // Show error to user
215
- }
216
- finally
217
- {
218
- _isSaving = false;
219
- }
220
- }
221
-
222
- private static string GetStatusBadgeClass({{pascalCase FEATURE_NAME}}Status status) => status switch
223
- {
224
- {{pascalCase FEATURE_NAME}}Status.Active => "bg-success",
225
- {{pascalCase FEATURE_NAME}}Status.Pending => "bg-warning text-dark",
226
- {{pascalCase FEATURE_NAME}}Status.Completed => "bg-info",
227
- {{pascalCase FEATURE_NAME}}Status.Failed => "bg-danger",
228
- _ => "bg-secondary"
229
- };
230
-
231
- private class {{pascalCase FEATURE_NAME}}FormModel
232
- {
233
- [Required]
234
- [StringLength(200, MinimumLength = 3)]
235
- public string Name { get; set; } = string.Empty;
236
-
237
- // Add more fields as needed
238
- }
239
- }
1
+ @* ============================================================
2
+ BLAZOR COMPONENT TEMPLATE
3
+ Generated by MORPH Framework
4
+ ============================================================ *@
5
+
6
+ @page "/{{kebabCase FEATURE_NAME}}/{{kebabCase FEATURE_NAME}}s"
7
+ @attribute [Authorize(Policy = "CanView{{pascalCase FEATURE_NAME}}")]
8
+ @inject I{{pascalCase FEATURE_NAME}}Service {{pascalCase FEATURE_NAME}}Service
9
+ @inject ILogger<{{pascalCase FEATURE_NAME}}List> Logger
10
+ @inject NavigationManager Navigation
11
+
12
+ <PageTitle>{{pascalCase FEATURE_NAME}}s</PageTitle>
13
+
14
+ <div class="container-fluid">
15
+ <div class="row mb-4">
16
+ <div class="col">
17
+ <h1>{{pascalCase FEATURE_NAME}}s</h1>
18
+ </div>
19
+ <div class="col-auto">
20
+ <AuthorizeView Policy="CanManage{{pascalCase FEATURE_NAME}}">
21
+ <button class="btn btn-primary" @onclick="ShowCreateModal">
22
+ <i class="bi bi-plus-lg"></i> New {{pascalCase FEATURE_NAME}}
23
+ </button>
24
+ </AuthorizeView>
25
+ </div>
26
+ </div>
27
+
28
+ @if (_isLoading)
29
+ {
30
+ <div class="d-flex justify-content-center py-5">
31
+ <div class="spinner-border text-primary" role="status">
32
+ <span class="visually-hidden">Loading...</span>
33
+ </div>
34
+ </div>
35
+ }
36
+ else if (_error is not null)
37
+ {
38
+ <div class="alert alert-danger" role="alert">
39
+ <i class="bi bi-exclamation-triangle"></i>
40
+ @_error
41
+ <button class="btn btn-link" @onclick="LoadDataAsync">Retry</button>
42
+ </div>
43
+ }
44
+ else if (_items is null || !_items.Any())
45
+ {
46
+ <div class="text-center py-5">
47
+ <i class="bi bi-inbox display-1 text-muted"></i>
48
+ <p class="lead mt-3">No {{kebabCase FEATURE_NAME}}s found</p>
49
+ <AuthorizeView Policy="CanManage{{pascalCase FEATURE_NAME}}">
50
+ <button class="btn btn-primary" @onclick="ShowCreateModal">
51
+ Create your first {{kebabCase FEATURE_NAME}}
52
+ </button>
53
+ </AuthorizeView>
54
+ </div>
55
+ }
56
+ else
57
+ {
58
+ <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
59
+ @foreach (var item in _items)
60
+ {
61
+ <div class="col">
62
+ <div class="card h-100">
63
+ <div class="card-body">
64
+ <h5 class="card-title">@item.Name</h5>
65
+ <p class="card-text">
66
+ <span class="badge @GetStatusBadgeClass(item.Status)">
67
+ @item.Status
68
+ </span>
69
+ </p>
70
+ </div>
71
+ <div class="card-footer bg-transparent">
72
+ <small class="text-muted">
73
+ Created @item.CreatedAt.ToString("d")
74
+ </small>
75
+ <AuthorizeView Policy="CanManage{{pascalCase FEATURE_NAME}}">
76
+ <div class="float-end">
77
+ <button class="btn btn-sm btn-outline-primary"
78
+ @onclick="() => ShowEditModal(item)">
79
+ Edit
80
+ </button>
81
+ </div>
82
+ </AuthorizeView>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ }
87
+ </div>
88
+ }
89
+ </div>
90
+
91
+ @* Modal for Create/Edit *@
92
+ @if (_showModal)
93
+ {
94
+ <div class="modal fade show d-block" tabindex="-1" style="background: rgba(0,0,0,0.5)">
95
+ <div class="modal-dialog">
96
+ <div class="modal-content">
97
+ <div class="modal-header">
98
+ <h5 class="modal-title">
99
+ @(_editingItem is null ? "New {{pascalCase FEATURE_NAME}}" : "Edit {{pascalCase FEATURE_NAME}}")
100
+ </h5>
101
+ <button type="button" class="btn-close" @onclick="CloseModal"></button>
102
+ </div>
103
+ <EditForm Model="_formModel" OnValidSubmit="HandleSubmitAsync">
104
+ <DataAnnotationsValidator />
105
+ <div class="modal-body">
106
+ <div class="mb-3">
107
+ <label class="form-label">Name</label>
108
+ <InputText class="form-control" @bind-Value="_formModel.Name" />
109
+ <ValidationMessage For="() => _formModel.Name" />
110
+ </div>
111
+ @* Add more fields as needed *@
112
+ </div>
113
+ <div class="modal-footer">
114
+ <button type="button" class="btn btn-secondary" @onclick="CloseModal">
115
+ Cancel
116
+ </button>
117
+ <button type="submit" class="btn btn-primary" disabled="@_isSaving">
118
+ @if (_isSaving)
119
+ {
120
+ <span class="spinner-border spinner-border-sm"></span>
121
+ }
122
+ Save
123
+ </button>
124
+ </div>
125
+ </EditForm>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ }
130
+
131
+ @code {
132
+ private List<{{pascalCase FEATURE_NAME}}Dto>? _items;
133
+ private bool _isLoading = true;
134
+ private bool _showModal;
135
+ private bool _isSaving;
136
+ private string? _error;
137
+ private {{pascalCase FEATURE_NAME}}Dto? _editingItem;
138
+ private {{pascalCase FEATURE_NAME}}FormModel _formModel = new();
139
+
140
+ protected override async Task OnInitializedAsync()
141
+ {
142
+ await LoadDataAsync();
143
+ }
144
+
145
+ private async Task LoadDataAsync()
146
+ {
147
+ _isLoading = true;
148
+ _error = null;
149
+
150
+ try
151
+ {
152
+ _items = await {{pascalCase FEATURE_NAME}}Service.GetAllAsync();
153
+ }
154
+ catch (Exception ex)
155
+ {
156
+ Logger.LogError(ex, "Failed to load {{kebabCase FEATURE_NAME}}s");
157
+ _error = "Failed to load data. Please try again.";
158
+ }
159
+ finally
160
+ {
161
+ _isLoading = false;
162
+ }
163
+ }
164
+
165
+ private void ShowCreateModal()
166
+ {
167
+ _editingItem = null;
168
+ _formModel = new {{pascalCase FEATURE_NAME}}FormModel();
169
+ _showModal = true;
170
+ }
171
+
172
+ private void ShowEditModal({{pascalCase FEATURE_NAME}}Dto item)
173
+ {
174
+ _editingItem = item;
175
+ _formModel = new {{pascalCase FEATURE_NAME}}FormModel
176
+ {
177
+ Name = item.Name
178
+ // Map other fields
179
+ };
180
+ _showModal = true;
181
+ }
182
+
183
+ private void CloseModal()
184
+ {
185
+ _showModal = false;
186
+ _formModel = new();
187
+ }
188
+
189
+ private async Task HandleSubmitAsync()
190
+ {
191
+ _isSaving = true;
192
+
193
+ try
194
+ {
195
+ if (_editingItem is null)
196
+ {
197
+ // Create
198
+ var request = new Create{{pascalCase FEATURE_NAME}}Request(_formModel.Name);
199
+ await {{pascalCase FEATURE_NAME}}Service.CreateAsync(request);
200
+ }
201
+ else
202
+ {
203
+ // Update
204
+ var request = new Update{{pascalCase FEATURE_NAME}}Request(_formModel.Name);
205
+ await {{pascalCase FEATURE_NAME}}Service.UpdateAsync(_editingItem.Id, request);
206
+ }
207
+
208
+ CloseModal();
209
+ await LoadDataAsync();
210
+ }
211
+ catch (Exception ex)
212
+ {
213
+ Logger.LogError(ex, "Failed to save {{kebabCase FEATURE_NAME}}");
214
+ // Show error to user
215
+ }
216
+ finally
217
+ {
218
+ _isSaving = false;
219
+ }
220
+ }
221
+
222
+ private static string GetStatusBadgeClass({{pascalCase FEATURE_NAME}}Status status) => status switch
223
+ {
224
+ {{pascalCase FEATURE_NAME}}Status.Active => "bg-success",
225
+ {{pascalCase FEATURE_NAME}}Status.Pending => "bg-warning text-dark",
226
+ {{pascalCase FEATURE_NAME}}Status.Completed => "bg-info",
227
+ {{pascalCase FEATURE_NAME}}Status.Failed => "bg-danger",
228
+ _ => "bg-secondary"
229
+ };
230
+
231
+ private class {{pascalCase FEATURE_NAME}}FormModel
232
+ {
233
+ [Required]
234
+ [StringLength(200, MinimumLength = 3)]
235
+ public string Name { get; set; } = string.Empty;
236
+
237
+ // Add more fields as needed
238
+ }
239
+ }