@atlashub/smartstack-cli 4.76.0 → 4.80.0
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/package.json +1 -1
- package/templates/project/claude-md/root.CLAUDE.md.template +1 -1
- package/templates/skills/ai-prompt/SKILL.md +64 -0
- package/templates/skills/ai-prompt/references/ai-agent-modes.md +89 -0
- package/templates/skills/ai-prompt/references/eval-framework.md +129 -0
- package/templates/skills/apex/references/checks/frontend-checks.sh +97 -11
- package/templates/skills/apex/references/checks/seed-checks.sh +34 -0
- package/templates/skills/apex/references/core-seed-data.md +7 -4
- package/templates/skills/apex/references/domain-events-pattern.md +45 -0
- package/templates/skills/apex/references/entity-hooks-pattern.md +68 -0
- package/templates/skills/apex/references/licensing-enforcement.md +52 -0
- package/templates/skills/apex/references/smartstack-api.md +112 -1
- package/templates/skills/apex-verify/steps/step-01-nav-audit.md +4 -0
- package/templates/skills/application/references/contexts-cheatsheet.md +86 -0
- package/templates/skills/application/references/extensions-system.md +158 -0
- package/templates/skills/application/references/frontend-route-naming.md +7 -5
- package/templates/skills/application/references/frontend-verification.md +7 -5
- package/templates/skills/application/references/provider-template.md +4 -2
- package/templates/skills/application/references/smartstack-provider.md +118 -0
- package/templates/skills/application/references/themes-db-driven.md +484 -0
- package/templates/skills/application/templates-seed.md +4 -2
- package/templates/skills/audit-route/references/routing-pattern.md +3 -1
- package/templates/skills/business-analyse/react/components.md +30 -28
- package/templates/skills/business-analyse/templates-react.md +15 -15
- package/templates/skills/cli-app-sync/SKILL.md +105 -4
- package/templates/skills/cli-app-sync/references/comparison-map.md +13 -0
- package/templates/skills/cli-app-sync/references/diff-entities.md +162 -0
- package/templates/skills/documentation/templates.md +16 -16
- package/templates/skills/migrate/SKILL.md +312 -0
- package/templates/skills/migrate/references/v3.34-to-v3.46.md +289 -0
- package/templates/skills/smoke-generation/SKILL.md +313 -0
- package/templates/skills/ui-components/SKILL.md +10 -0
- package/templates/skills/ui-components/references/component-catalog.md +82 -0
- package/templates/skills/workflow/SKILL.md +70 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Licensing — `[RequiresLicense]` attribute (v3.46+)
|
|
2
|
+
|
|
3
|
+
> Extracted from `smartstack-api.md` for clarity. Loaded only when generating code that needs license gating.
|
|
4
|
+
|
|
5
|
+
Mark a Command or Query as requiring a valid license / feature / write capability.
|
|
6
|
+
|
|
7
|
+
## Attribute definition
|
|
8
|
+
|
|
9
|
+
```csharp
|
|
10
|
+
namespace SmartStack.Application.Common.Licensing;
|
|
11
|
+
|
|
12
|
+
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
|
|
13
|
+
public class RequiresLicenseAttribute : Attribute
|
|
14
|
+
{
|
|
15
|
+
public string? Feature { get; }
|
|
16
|
+
public bool IsWriteOperation { get; }
|
|
17
|
+
|
|
18
|
+
public RequiresLicenseAttribute() { /* just needs valid license */ }
|
|
19
|
+
public RequiresLicenseAttribute(bool isWriteOperation) { /* blocked in read-only mode */ }
|
|
20
|
+
public RequiresLicenseAttribute(string feature, bool isWriteOperation = false) { /* both */ }
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage on a Command
|
|
25
|
+
|
|
26
|
+
```csharp
|
|
27
|
+
[RequiresLicense(LicenseFeatures.AdvancedWorkflows, isWriteOperation: true)]
|
|
28
|
+
public record CreateAdvancedWorkflowCommand(string Name, ...) : IRequest<WorkflowDto>;
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Behavior at runtime
|
|
32
|
+
|
|
33
|
+
- A MediatR pipeline behavior reads the attribute on the request type.
|
|
34
|
+
- `Feature` → checked against the cached `License.Features` (resolved per-tenant from JWT).
|
|
35
|
+
- `IsWriteOperation = true` → blocked when the system is in read-only mode (license expired but in grace period, license downgraded, …).
|
|
36
|
+
- Failures throw a typed exception → 403 Forbidden with a stable error code (`LICENSE_FEATURE_DISABLED`, `LICENSE_READ_ONLY`).
|
|
37
|
+
|
|
38
|
+
## Where to read feature constants
|
|
39
|
+
|
|
40
|
+
`SmartStack.Domain.Licensing.LicenseFeatures` (string consts — `Entra`, `AdvancedWorkflows`, `AiAdvanced`, …).
|
|
41
|
+
|
|
42
|
+
## DO / DON'T
|
|
43
|
+
|
|
44
|
+
- DO put `[RequiresLicense]` on the request (Command/Query), not on the handler
|
|
45
|
+
- DO use `LicenseFeatures.X` constants — never magic strings
|
|
46
|
+
- DON'T add `[RequiresLicense]` on `[AllowAnonymous]` endpoints — they bypass the pipeline
|
|
47
|
+
- DON'T duplicate the check in the handler body — the pipeline already enforced it
|
|
48
|
+
|
|
49
|
+
## Sources
|
|
50
|
+
|
|
51
|
+
- `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Application/Common/Licensing/RequiresLicenseAttribute.cs`
|
|
52
|
+
- `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Domain/Licensing/LicenseFeatures.cs`
|
|
@@ -16,10 +16,46 @@ public abstract class BaseEntity
|
|
|
16
16
|
public Guid Id { get; set; }
|
|
17
17
|
public DateTime CreatedAt { get; set; }
|
|
18
18
|
public DateTime? UpdatedAt { get; set; }
|
|
19
|
+
|
|
20
|
+
// v3.46+ : JSON storage for SDK extension fields. Default "{}".
|
|
21
|
+
public string ExtensionData { get; private set; } = "{}";
|
|
19
22
|
}
|
|
20
23
|
```
|
|
21
24
|
|
|
22
|
-
**
|
|
25
|
+
**4 inherited properties** (v3.46+). No Code, no IsDeleted, no RowVersion, no SoftDelete, no CreatedBy/UpdatedBy. `Code` is a business property — add it on the concrete entity. `CreatedBy`/`UpdatedBy` come from `IAuditableEntity` (separate interface).
|
|
26
|
+
|
|
27
|
+
### ExtensionData — SDK custom fields (v3.46+)
|
|
28
|
+
|
|
29
|
+
`BaseEntity.ExtensionData` is a JSON string allowing SDK clients to attach custom fields to ANY entity without schema migrations. The base class exposes 7 typed methods:
|
|
30
|
+
|
|
31
|
+
```csharp
|
|
32
|
+
// Read a typed value
|
|
33
|
+
var color = entity.GetExtensionValue<string>("color");
|
|
34
|
+
var meta = entity.GetExtensionValue<MyMetadata>("meta");
|
|
35
|
+
|
|
36
|
+
// Write
|
|
37
|
+
entity.SetExtensionValue("color", "blue");
|
|
38
|
+
entity.SetExtensionValue("meta", new MyMetadata { Tag = "vip" });
|
|
39
|
+
|
|
40
|
+
// Inspect
|
|
41
|
+
if (entity.HasExtensionValue("color")) { ... }
|
|
42
|
+
var all = entity.GetAllExtensionData(); // IReadOnlyDictionary<string, JsonElement>
|
|
43
|
+
|
|
44
|
+
// Bulk replace
|
|
45
|
+
entity.SetAllExtensionData(new Dictionary<string, object?> { ["a"] = 1, ["b"] = 2 });
|
|
46
|
+
|
|
47
|
+
// Cleanup
|
|
48
|
+
entity.RemoveExtensionValue("color"); // returns bool
|
|
49
|
+
entity.ClearExtensionData(); // resets to "{}"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Conventions:**
|
|
53
|
+
- Serialization uses `JsonNamingPolicy.CamelCase` — `MyField` is stored as `"myField"`. Frontend consumers see camelCase.
|
|
54
|
+
- Mutating methods automatically update `UpdatedAt`.
|
|
55
|
+
- Backed by SQL Server `nvarchar(max)`. Map with `builder.Property(x => x.ExtensionData).HasColumnType("nvarchar(max)").HasDefaultValue("{}");` in EF config.
|
|
56
|
+
- `ExtensionData` is for **client SDK extensions** — NOT for storing application-controlled data (use real columns for those).
|
|
57
|
+
|
|
58
|
+
> Source : `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Domain/Common/BaseEntity.cs`
|
|
23
59
|
|
|
24
60
|
---
|
|
25
61
|
|
|
@@ -90,6 +126,38 @@ Verify in `Program.cs`: `JsonSerializerOptions.Converters.Add(new JsonStringEnum
|
|
|
90
126
|
|
|
91
127
|
---
|
|
92
128
|
|
|
129
|
+
## File Storage — `StorageType` enum (v3.46+)
|
|
130
|
+
|
|
131
|
+
```csharp
|
|
132
|
+
namespace SmartStack.Domain.Common;
|
|
133
|
+
|
|
134
|
+
public enum StorageType
|
|
135
|
+
{
|
|
136
|
+
Normal = 0, // Standard Azure Blob — deletable any time
|
|
137
|
+
Legal = 1 // Azure Legal Hold — immutable, 10-year retention (Swiss law Art. 958f CO)
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**When to use `Legal`:**
|
|
142
|
+
- Contracts, invoices, accounting documents (Swiss CO art. 958f → 10 years)
|
|
143
|
+
- Audit logs that must survive deletion attempts (compliance, GDPR Art. 30)
|
|
144
|
+
- Anything required by regulation to outlive the user's delete intent
|
|
145
|
+
|
|
146
|
+
**When `Normal` is enough:**
|
|
147
|
+
- User-uploaded avatars, attachments to tickets, message attachments, theme assets
|
|
148
|
+
- Anything the user owns and can legitimately delete
|
|
149
|
+
|
|
150
|
+
**Storage routing (Infrastructure):**
|
|
151
|
+
- `Normal` → standard container (deletable, free tier eligible)
|
|
152
|
+
- `Legal` → container with **Azure Blob immutable storage policy** + retention lock applied at upload
|
|
153
|
+
- The choice is made by the entity author when creating the file — not a runtime toggle
|
|
154
|
+
|
|
155
|
+
**AppSettings:** `appsettings.json` exposes both containers under `AzureStorage.Normal` and `AzureStorage.Legal` (with `EnableLegalHold` and `EnableRetentionPolicy` flags). Local dev uses `FileStorage.Normal/Legal` folders.
|
|
156
|
+
|
|
157
|
+
> Source : `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Domain/Common/StorageType.cs`
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
93
161
|
## Entity Patterns
|
|
94
162
|
|
|
95
163
|
### Tenant-scoped (most common)
|
|
@@ -333,6 +401,49 @@ public class {Name}Controller : ControllerBase
|
|
|
333
401
|
|
|
334
402
|
**Sub-resource completeness:** If a parent page has a navigate() to a sub-resource route, the frontend MUST include a page for that route. Otherwise → dead link → white screen. Prefer separate controllers (with Suffix) over sub-endpoints in parent controller.
|
|
335
403
|
|
|
404
|
+
### Core Controllers Exception (Bootstrap / Navigation / Config)
|
|
405
|
+
|
|
406
|
+
A small set of **bootstrap controllers** uses `[Route("api/[controller]")]` instead of `[NavRoute]`. These are routed BEFORE the navigation registry is built from the DB, so they cannot use NavRoute.
|
|
407
|
+
|
|
408
|
+
| Controller | Route | Reason |
|
|
409
|
+
|---|---|---|
|
|
410
|
+
| `BootstrapController` | `api/bootstrap` | Returns initial config (CSRF token, public flags) |
|
|
411
|
+
| `NavigationController` | `api/navigation` | Returns the navigation menu — must run before NavRoute resolves |
|
|
412
|
+
| `ConfigController` (if present) | `api/config` | Public app config (read-only) |
|
|
413
|
+
| `AuthController` | `api/auth` | Login/refresh — runs before authenticated routes resolve |
|
|
414
|
+
|
|
415
|
+
**Rules for core controllers:**
|
|
416
|
+
- Use the classical `[Route("api/[controller]")]` attribute
|
|
417
|
+
- Still apply `[Authorize]` and `[RequirePermission]` when applicable (most are anonymous-allowed)
|
|
418
|
+
- Do NOT add to navigation seed data
|
|
419
|
+
- POST-CHECK ignores these specific controller names when validating NavRoute usage
|
|
420
|
+
|
|
421
|
+
**Do not extend this list arbitrarily.** Application/business controllers MUST use `[NavRoute]`. If you think a new controller belongs to the bootstrap set, justify it: the controller must run before navigation is loaded.
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Entity Lifecycle Hooks (v3.46+)
|
|
426
|
+
|
|
427
|
+
6 typed hook interfaces + 1 executor in `SmartStack.Application.Common.Interfaces.Hooks` : `IBeforeCreate<T>`, `IAfterCreate<T>`, `IBeforeUpdate<T>`, `IAfterUpdate<T>`, `IBeforeDelete<T>`, `IAfterDelete<T>`, `IHookExecutor`. `IBefore*` may throw to cancel ; `IAfter*` runs post-commit (no rollback). Each has an `Order` property (default 0).
|
|
428
|
+
|
|
429
|
+
**See [entity-hooks-pattern.md](entity-hooks-pattern.md)** for full interface signatures, DI registration, usage example, and DO/DON'T list.
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Domain Events (lightweight pub-sub)
|
|
434
|
+
|
|
435
|
+
Minimal pattern in `SmartStack.Domain.Support.Events` : `IDomainEvent { OccurredAt }` + `DomainEvent` abstract base. Use **events** for business facts independent of CRUD ; use **hooks** for cross-cutting reactions tied to the entity lifecycle. Dispatch is explicit (no auto-publication from `SaveChangesAsync` in v3.46).
|
|
436
|
+
|
|
437
|
+
**See [domain-events-pattern.md](domain-events-pattern.md)** for the full when-to-use table, naming convention, and dispatch example.
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Licensing — `[RequiresLicense]` attribute (v3.46+)
|
|
442
|
+
|
|
443
|
+
Mark a Command or Query class as requiring a valid license, a specific feature (`LicenseFeatures.X`), or write capability (blocked in read-only mode). MediatR pipeline reads the attribute and throws 403 with stable error codes (`LICENSE_FEATURE_DISABLED`, `LICENSE_READ_ONLY`).
|
|
444
|
+
|
|
445
|
+
**See [licensing-enforcement.md](licensing-enforcement.md)** for the attribute definition (3 constructors), usage on Commands, runtime behavior, feature constants location, and DO/DON'T.
|
|
446
|
+
|
|
336
447
|
---
|
|
337
448
|
|
|
338
449
|
## Navigation Seed Data (routes must be full paths)
|
|
@@ -78,6 +78,10 @@ For each issue found, create a finding:
|
|
|
78
78
|
| NAV-003 | BLOCKING | Route contains forbidden segment (/detail/, /create/, /edit/) |
|
|
79
79
|
| NAV-004 | WARNING | Reserved section code with IsActive = false (acceptable but unnecessary) |
|
|
80
80
|
| NAV-005 | WARNING | Section code not in kebab-case |
|
|
81
|
+
| NAV-006 | WARNING (v3.46) | `ApplicationZone` enum referenced in seed data — must use `IsPersonal`/`IsOpen` flags (cf. seed-checks.sh check **C66**, will become BLOCKING in v3.47) |
|
|
82
|
+
| NAV-007 | WARNING (v3.46) | Legacy layouts `AdminLayout`/`BusinessLayout`/`UserLayout`/`HRLayout`/`SalesLayout` referenced — must use `AppLayout` (cf. seed-checks.sh check **C67**, will become BLOCKING in v3.47) |
|
|
83
|
+
|
|
84
|
+
**Note** : NAV-006 and NAV-007 mirror the bash post-checks **C66**/**C67** in `apex/references/checks/seed-checks.sh`. The bash checks emit `WARNING` (no FAIL) during the v3.46 grace period — apex-verify should align with the same severity.
|
|
81
85
|
|
|
82
86
|
---
|
|
83
87
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# SmartStack Contexts — Cheatsheet
|
|
2
|
+
|
|
3
|
+
> **Reference for `application` skill** — quick lookup of the 10 system contexts exposed by `<SmartStackProvider>`.
|
|
4
|
+
|
|
5
|
+
## When This Reference Applies
|
|
6
|
+
|
|
7
|
+
- You generate a component that needs to read auth/tenant/theme state
|
|
8
|
+
- You audit a page that re-implements existing context state
|
|
9
|
+
- The user asks "how do I read the current tenant", "how do I check a permission", "where is the SignalR connection"
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## The 10 contexts (state map)
|
|
14
|
+
|
|
15
|
+
| # | Context | Hook | State exposed | Example use case |
|
|
16
|
+
|---|---|---|---|---|
|
|
17
|
+
| 1 | AuthContext | `useAuth()` | `user`, `roles`, `permissions`, `isAuthenticated`, `login()`, `logout()`, `refresh()`, `hasPermission(path)` | Read current user, check permission gate |
|
|
18
|
+
| 2 | TenantContext | `useTenant()` | `currentTenant`, `userTenants`, `isGlobalView`, `switchTenant(id)` | Display tenant switcher, scope a query |
|
|
19
|
+
| 3 | NavigationContext | `useNavigation()` | `menu` (MenuDto), `reload()`, `currentApp`, `currentModule`, `currentSection`, `isLoading`, `getApplicationsByZone()` (back-compat alias) | Render sidebar, breadcrumb, route resolution |
|
|
20
|
+
| 4 | ThemeContext | `useTheme()` | `mode`, `selectedTheme`, `selectedPreset`, `setMode()`, `setTheme(id)`, `setPreset(id)` | Toggle dark mode, switch theme |
|
|
21
|
+
| 5 | LicenseContext | `useLicense()` | `license`, `isFeatureEnabled(code)`, `hasModule(code)` | Gate a feature behind license |
|
|
22
|
+
| 6 | SignalRContext | `useSignalR()` | `isConnected`, `on(event, handler)`, `off(event, handler)`, `emit(event, payload)` | Real-time notifications |
|
|
23
|
+
| 7 | FavoritesContext | `useFavorites()` | `favorites`, `toggleFavorite(item)`, `isFavorite(id)` | Bookmark a page/entity |
|
|
24
|
+
| 8 | SidebarContext | `useSidebar()` | `isCollapsed`, `toggle()`, `setCollapsed(value)` | Sidebar collapse button |
|
|
25
|
+
| 9 | FeatureConfigContext | `useFeatureConfig()` | `features` (record from `/api/config/public`) | Feature flag, A/B test |
|
|
26
|
+
| 10 | DocPanelContext | `useDocPanel()` | `isOpen`, `toggle()`, `openDoc(slug)` | Contextual help panel |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Common patterns
|
|
31
|
+
|
|
32
|
+
### Permission check — render guard
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
const { hasPermission } = useAuth();
|
|
36
|
+
if (!hasPermission('administration.users.create')) return <Forbidden />;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Tenant-scoped query
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
const { currentTenant } = useTenant();
|
|
43
|
+
const { data } = useQuery(
|
|
44
|
+
['orders', currentTenant?.id],
|
|
45
|
+
() => orderApi.list({ tenantId: currentTenant?.id })
|
|
46
|
+
);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Real-time updates
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
const { on, off } = useSignalR();
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const handler = (notif) => toast(notif.message);
|
|
55
|
+
on('NotificationReceived', handler);
|
|
56
|
+
return () => off('NotificationReceived', handler);
|
|
57
|
+
}, []);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Feature flag
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
const { isFeatureEnabled } = useLicense();
|
|
64
|
+
if (isFeatureEnabled('ai-assistant')) return <AiAssistant />;
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## DO / DON'T
|
|
70
|
+
|
|
71
|
+
| ✅ DO | ❌ DON'T |
|
|
72
|
+
|---|---|
|
|
73
|
+
| Consume contexts via the provided hooks | `useContext(SmartStackContext)` directly |
|
|
74
|
+
| Memoize derived values from context | Re-compute on every render |
|
|
75
|
+
| Subscribe/unsubscribe SignalR handlers in `useEffect` cleanup | Forget to `off()` (memory leak) |
|
|
76
|
+
| Treat `permissions` as opaque strings | Parse permissions client-side |
|
|
77
|
+
| Read tenant from `currentTenant`, not URL | Trust the URL slug as a source of truth |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Reference source files (read-only)
|
|
82
|
+
|
|
83
|
+
- `D:\01 - projets\SmartStack.app\features\IA-Workflow\web\smartstack-web\src\contexts\` (all 10 files)
|
|
84
|
+
- `D:\01 - projets\SmartStack.app\features\IA-Workflow\web\smartstack-web\src\provider\SmartStackProvider.tsx` (wrapping order)
|
|
85
|
+
|
|
86
|
+
See [smartstack-provider.md](smartstack-provider.md) for the wrapping order.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Extension System — Slot/Fill, Page Overrides, Hooks
|
|
2
|
+
|
|
3
|
+
> **Reference for `application` skill** — how a client project enriches a SmartStack page without forking the SDK.
|
|
4
|
+
|
|
5
|
+
## When This Reference Applies
|
|
6
|
+
|
|
7
|
+
- Client wants to inject content into an existing SmartStack page (header action, table column, side panel)
|
|
8
|
+
- Client wants to fully replace a built-in page with a custom one
|
|
9
|
+
- Client needs to react to navigation/auth/tenant lifecycle events
|
|
10
|
+
- The user asks "how do I add a column to UsersList", "how do I override the AdministrationDashboard page", "how do I customize a SmartStack form"
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Three extension mechanisms
|
|
15
|
+
|
|
16
|
+
### 1. Page overrides — `componentRegistry` + `PageRegistry`
|
|
17
|
+
|
|
18
|
+
Override a built-in page by registering your component under the same key :
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
// In your client componentRegistry.generated.ts (or a custom registry.ts)
|
|
22
|
+
import { PageRegistry } from '@atlashub/smartstack';
|
|
23
|
+
import { lazy } from 'react';
|
|
24
|
+
|
|
25
|
+
PageRegistry.register(
|
|
26
|
+
'administration.users', // built-in key — replaces SmartStack page
|
|
27
|
+
lazy(() => import('@/pages/MyCustomUsers')), // your component
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Or via SmartStackConfig.extensions.pages (declarative)
|
|
31
|
+
const config: SmartStackConfig = {
|
|
32
|
+
apiUrl: '...',
|
|
33
|
+
extensions: {
|
|
34
|
+
pages: {
|
|
35
|
+
'administration.users': () => import('@/pages/MyCustomUsers'),
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The `DynamicRouter` calls `PageRegistry.resolve(componentKey)` and your override wins.
|
|
42
|
+
|
|
43
|
+
> **Tip** — for net-new pages on client-defined routes, prefer the MCP tool `mcp__smartstack__scaffold_routes` to keep `componentRegistry.generated.ts` consistent.
|
|
44
|
+
|
|
45
|
+
### 2. Slot / Fill — render-prop injection
|
|
46
|
+
|
|
47
|
+
Inject content into a named slot exposed by a SmartStack page :
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { Fill } from '@atlashub/smartstack';
|
|
51
|
+
|
|
52
|
+
// Inject into the slot 'users.header.actions'
|
|
53
|
+
<Fill slot="users.header.actions">
|
|
54
|
+
<button onClick={...}>My custom action</button>
|
|
55
|
+
</Fill>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Available slot names follow the convention `{section}.{location}.{purpose}` (see `extensions/types.ts` SlotName union for the full list — examples : `users.header.actions`, `users.table.columns.before`, `users.table.row.actions`, `users.form.fields.after`, `users.detail.tabs.after`).
|
|
59
|
+
|
|
60
|
+
You can declare slots once via `SmartStackConfig.extensions.slots` :
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
extensions: {
|
|
64
|
+
slots: {
|
|
65
|
+
'users.header.actions': MyHeaderWidget,
|
|
66
|
+
'dashboard.kpi.before': MyKpi,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 3. Lifecycle hooks
|
|
72
|
+
|
|
73
|
+
Subscribe to platform events from anywhere :
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
extensions: {
|
|
77
|
+
hooks: {
|
|
78
|
+
onAuthSuccess: (user) => analytics.identify(user.id),
|
|
79
|
+
onTenantSwitch: (tenant) => router.navigate('/dashboard'),
|
|
80
|
+
onNavigationChanged: (menu) => console.log('menu refreshed'),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Table columns & form fields extensions
|
|
88
|
+
|
|
89
|
+
Extend the standard `DataTable` or `EntityForm` of any built-in section :
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
extensions: {
|
|
93
|
+
tableColumns: {
|
|
94
|
+
'administration.users.list': [
|
|
95
|
+
{
|
|
96
|
+
key: 'department',
|
|
97
|
+
label: 'Department',
|
|
98
|
+
render: (row) => row.department?.name,
|
|
99
|
+
align: 'left',
|
|
100
|
+
sortable: true,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
formFields: {
|
|
105
|
+
'administration.users.create': [
|
|
106
|
+
{
|
|
107
|
+
name: 'department',
|
|
108
|
+
type: 'select',
|
|
109
|
+
label: 'Department',
|
|
110
|
+
options: () => departmentApi.list(),
|
|
111
|
+
validation: { required: true },
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
These extensions are consumed by the corresponding hooks in the SmartStack page :
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
const extraColumns = useTableColumnExtensions('administration.users.list');
|
|
122
|
+
const extraFields = useFormFieldExtensions('administration.users.create');
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## DO / DON'T
|
|
128
|
+
|
|
129
|
+
| ✅ DO | ❌ DON'T |
|
|
130
|
+
|---|---|
|
|
131
|
+
| Use `PageRegistry.register()` once at startup | Register the same key twice (last wins, but ambiguous) |
|
|
132
|
+
| Use lazy imports for page overrides | Import override pages eagerly (kills code splitting) |
|
|
133
|
+
| Match exact componentKey from the navigation menu | Guess the key — read it from `/api/navigation/menu` response |
|
|
134
|
+
| Use slots for additive content | Use slots to remove content (use page override instead) |
|
|
135
|
+
| Type custom column data via `row` typing | Cast `row as any` |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## MCP delegation
|
|
140
|
+
|
|
141
|
+
For complex extension scaffolds (page override + slots + tests), prefer the MCP tool over hand-written code :
|
|
142
|
+
|
|
143
|
+
| Goal | MCP tool |
|
|
144
|
+
|---|---|
|
|
145
|
+
| Generate a page override + register it | `mcp__smartstack__scaffold_frontend_extension` (type: `page-override`) |
|
|
146
|
+
| Generate a custom Slot component | `mcp__smartstack__scaffold_frontend_extension` (type: `slot`) |
|
|
147
|
+
| Generate a custom column extension | `mcp__smartstack__scaffold_frontend_extension` (type: `table-column`) |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Reference source files (read-only)
|
|
152
|
+
|
|
153
|
+
- `D:\01 - projets\SmartStack.app\features\IA-Workflow\web\smartstack-web\src\extensions\PageRegistry.tsx`
|
|
154
|
+
- `D:\01 - projets\SmartStack.app\features\IA-Workflow\web\smartstack-web\src\extensions\types.ts` (SlotName, ColumnExtension, FormFieldExtension)
|
|
155
|
+
- `D:\01 - projets\SmartStack.app\features\IA-Workflow\web\smartstack-web\src\extensions\ExtensionContext.tsx` (useExtensions, useTableColumnExtensions, useFormFieldExtensions)
|
|
156
|
+
- `D:\01 - projets\SmartStack.app\features\IA-Workflow\web\smartstack-web\src\extensions\SlotFill.tsx` (Slot, Fill, SlotFillProvider)
|
|
157
|
+
|
|
158
|
+
See also [smartstack-provider.md](smartstack-provider.md) for the SmartStackConfig shape that hosts `extensions`.
|
|
@@ -79,19 +79,21 @@ const applicationRoutes: ApplicationRouteExtensions = {
|
|
|
79
79
|
|
|
80
80
|
## Implementation Pattern
|
|
81
81
|
|
|
82
|
+
> **v3.46+ note** — The domain-specific layouts `AdminLayout`, `BusinessLayout`, `UserLayout`, `HRLayout`, `SalesLayout` are REMOVED. `AppLayout` is now the SOLE shell for authenticated application routes. Routes are resolved dynamically by `DynamicRouter` via `componentRegistry.generated.ts` — the JSX `<Route>` blocks below are illustrative for naming convention only, not for manual declaration. See `references/smartstack-provider.md` for the full provider/router setup.
|
|
83
|
+
|
|
82
84
|
When generating routes, apply kebab-case to all multi-word segments:
|
|
83
85
|
|
|
84
86
|
```tsx
|
|
85
|
-
// CORRECT
|
|
86
|
-
<Route path="/human-resources" element={<
|
|
87
|
+
// CORRECT — kebab-case convention (illustrative — actual routes flow through DynamicRouter)
|
|
88
|
+
<Route path="/human-resources" element={<AppLayout />}>
|
|
87
89
|
<Route path="employees" element={<EmployeesPage />} />
|
|
88
90
|
<Route path="time-management" element={<TimeManagementPage />} />
|
|
89
91
|
</Route>
|
|
90
92
|
|
|
91
93
|
// FORBIDDEN
|
|
92
|
-
<Route path="/humanresources" element={<
|
|
93
|
-
<Route path="/HR" element={<
|
|
94
|
-
<Route path="/human_resources" element={<
|
|
94
|
+
<Route path="/humanresources" element={<AppLayout />} />
|
|
95
|
+
<Route path="/HR" element={<AppLayout />} />
|
|
96
|
+
<Route path="/human_resources" element={<AppLayout />} />
|
|
95
97
|
```
|
|
96
98
|
|
|
97
99
|
---
|
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
## Routing Rules
|
|
6
6
|
|
|
7
|
+
> ⚠️ **DEPRECATED PATTERN — v3.46+** : Domain-specific layouts (`SalesLayout`, `AdminLayout`, `BusinessLayout`, `UserLayout`, `HRLayout`) are REMOVED. There are now **only 4 layouts** : `AppLayout` (sole shell for authenticated app routes), `AuthLayout` (login/register), `PublicLayout` (public pages), `DocsLayout` (documentation). The "manual `<Route>` declaration" examples below are LEGACY ; on v3.46+ routes are resolved by `DynamicRouter` via `componentRegistry.generated.ts` (see Section 5b below + `references/smartstack-provider.md`). The examples are kept for projects on v3.6 and earlier.
|
|
8
|
+
|
|
7
9
|
### Rule 1: Routes MUST be inside Layout wrappers
|
|
8
10
|
|
|
9
|
-
**CRITICAL:** All routes MUST be nested inside the appropriate context layout. This is what provides the SmartStack shell (header with AvatarMenu, sidebar, navigation).
|
|
11
|
+
**CRITICAL (legacy v3.6 only):** All routes MUST be nested inside the appropriate context layout. This is what provides the SmartStack shell (header with AvatarMenu, sidebar, navigation). On v3.46+, `AppLayout` is the SOLE shell.
|
|
10
12
|
|
|
11
13
|
```tsx
|
|
12
|
-
// CORRECT - Inside
|
|
13
|
-
<Route path="/sales" element={<
|
|
14
|
+
// CORRECT (v3.46+) - Inside AppLayout (shell renders: header + AvatarMenu + sidebar)
|
|
15
|
+
<Route path="/sales" element={<AppLayout />}>
|
|
14
16
|
<Route path="products" element={<ProductsPage />} />
|
|
15
17
|
</Route>
|
|
16
18
|
|
|
@@ -23,8 +25,8 @@
|
|
|
23
25
|
**CRITICAL:** SmartStack requires NESTED routes within the layout:
|
|
24
26
|
|
|
25
27
|
```tsx
|
|
26
|
-
// CORRECT - Nested routes inside layout
|
|
27
|
-
<Route path="/sales" element={<
|
|
28
|
+
// CORRECT - Nested routes inside layout (v3.46+ uses AppLayout)
|
|
29
|
+
<Route path="/sales" element={<AppLayout />}>
|
|
28
30
|
<Route path="products">
|
|
29
31
|
<Route index element={<Navigate to="products" replace />} />
|
|
30
32
|
<Route path="products" element={<ProductsPage />} />
|
|
@@ -42,10 +42,12 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
|
|
|
42
42
|
}
|
|
43
43
|
else
|
|
44
44
|
{
|
|
45
|
+
// v3.46+ signature : Zone removed. New flags isOpen + isPersonal at the END (optional, default false).
|
|
45
46
|
app = NavigationApplication.Create(
|
|
46
|
-
|
|
47
|
+
appEntry.Code, appEntry.Label,
|
|
47
48
|
appEntry.Description, appEntry.Icon, appEntry.IconType,
|
|
48
|
-
appEntry.Route, appEntry.DisplayOrder
|
|
49
|
+
appEntry.Route, appEntry.DisplayOrder,
|
|
50
|
+
isOpen: appEntry.IsOpen, isPersonal: appEntry.IsPersonal);
|
|
49
51
|
context.NavigationApplications.Add(app);
|
|
50
52
|
await ((DbContext)context).SaveChangesAsync(ct);
|
|
51
53
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# SmartStackProvider — Wrapping the Client App
|
|
2
|
+
|
|
3
|
+
> **Reference for `application` skill** — how to bootstrap a SmartStack client project (npm `@atlashub/smartstack`) using the SDK provider.
|
|
4
|
+
|
|
5
|
+
## When This Reference Applies
|
|
6
|
+
|
|
7
|
+
- The client project consumes `@atlashub/smartstack` as an npm package (v3.7+)
|
|
8
|
+
- You are generating or auditing `App.tsx` / `main.tsx` / the application bootstrap
|
|
9
|
+
- The user asks "how do I wrap my app", "where do I put providers", "how do I configure SmartStack"
|
|
10
|
+
|
|
11
|
+
If the project is the SmartStack platform itself (`SmartStack.app`), the platform's internal `App.tsx` already inlines the providers and does NOT use `<SmartStackProvider>` — only client projects use it.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## SmartStackConfig shape
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
interface SmartStackConfig {
|
|
19
|
+
apiUrl: string; // REQUIRED — backend root URL (e.g. "https://api.client.com")
|
|
20
|
+
defaultLanguage?: 'fr' | 'en' | 'it' | 'de'; // default 'fr'
|
|
21
|
+
debug?: boolean; // verbose logging
|
|
22
|
+
logo?: ReactNode; // override branding logo
|
|
23
|
+
appName?: string; // displayed in title/header fallback
|
|
24
|
+
licenseKey?: string; // license string (validated against /api/license)
|
|
25
|
+
extensions?: ExtensionConfig; // see references/extensions-system.md
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Wrapping order (Outer → Inner)
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
35
|
+
import { SmartStackProvider, DynamicRouter } from '@atlashub/smartstack';
|
|
36
|
+
import '@atlashub/smartstack/theme.css';
|
|
37
|
+
|
|
38
|
+
const config: SmartStackConfig = {
|
|
39
|
+
apiUrl: import.meta.env.VITE_API_URL,
|
|
40
|
+
defaultLanguage: 'fr',
|
|
41
|
+
appName: 'My Client App',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
createRoot(document.getElementById('root')!).render(
|
|
45
|
+
<StrictMode>
|
|
46
|
+
<BrowserRouter>
|
|
47
|
+
<SmartStackProvider config={config}>
|
|
48
|
+
<DynamicRouter />
|
|
49
|
+
</SmartStackProvider>
|
|
50
|
+
</BrowserRouter>
|
|
51
|
+
</StrictMode>,
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`<SmartStackProvider>` internally nests, in this order :
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
SmartStackContext (config)
|
|
59
|
+
└─ GlobalErrorBoundary
|
|
60
|
+
└─ ExtensionProvider
|
|
61
|
+
└─ ThemeProvider
|
|
62
|
+
└─ FeatureConfigProvider
|
|
63
|
+
└─ AuthProvider
|
|
64
|
+
└─ TenantProvider
|
|
65
|
+
└─ LicenseProvider
|
|
66
|
+
└─ FavoritesProvider
|
|
67
|
+
└─ SidebarProvider
|
|
68
|
+
└─ SessionProvider
|
|
69
|
+
└─ ThemeSync
|
|
70
|
+
└─ NavigationProvider
|
|
71
|
+
└─ SignalRProvider
|
|
72
|
+
└─ {children}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Do NOT re-wrap any of these providers manually.** They are exported only for advanced cases (e.g., Storybook).
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Hooks consumable from any descendant
|
|
80
|
+
|
|
81
|
+
| Hook | Context | Returns |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `useSmartStack()` | SmartStackContext | `{ config }` |
|
|
84
|
+
| `useAuth()` | AuthContext | `{ user, login, logout, refresh, hasPermission }` |
|
|
85
|
+
| `useTenant()` | TenantContext | `{ currentTenant, userTenants, switchTenant, isGlobalView }` |
|
|
86
|
+
| `useNavigation()` | NavigationContext | `{ menu, reload, currentApp, currentModule, isLoading }` |
|
|
87
|
+
| `useTheme()` | ThemeContext | `{ mode, selectedTheme, selectedPreset, setTheme, setPreset, toggleMode }` |
|
|
88
|
+
| `useLicense()` | LicenseContext | `{ license, isFeatureEnabled, hasModule }` |
|
|
89
|
+
| `useSignalR()` | SignalRContext | `{ isConnected, on, off, emit }` |
|
|
90
|
+
| `useFavorites()` | FavoritesContext | `{ favorites, toggleFavorite, isFavorite }` |
|
|
91
|
+
| `useSidebar()` | SidebarContext | `{ isCollapsed, toggle, setCollapsed }` |
|
|
92
|
+
| `useFeatureConfig()` | FeatureConfigContext | `{ features }` |
|
|
93
|
+
| `useDocPanel()` | DocPanelContext | `{ isOpen, toggle, openDoc }` |
|
|
94
|
+
|
|
95
|
+
See [contexts-cheatsheet.md](contexts-cheatsheet.md) for usage examples.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## DO / DON'T
|
|
100
|
+
|
|
101
|
+
| ✅ DO | ❌ DON'T |
|
|
102
|
+
|---|---|
|
|
103
|
+
| Place `<SmartStackProvider>` exactly once at root | Wrap the same provider twice |
|
|
104
|
+
| Put `<BrowserRouter>` ABOVE `<SmartStackProvider>` | Put `<SmartStackProvider>` above the router |
|
|
105
|
+
| Read config via `useSmartStack()` from any descendant | Pass config down via props |
|
|
106
|
+
| Import `componentRegistry.generated` in `main.tsx` BEFORE the render | Forget the import (DynamicRouter will not resolve pages) |
|
|
107
|
+
| Wait for `i18nReady` promise before render | Render before i18n loaded (causes FOUC) |
|
|
108
|
+
| Provide `apiUrl` via `import.meta.env.VITE_API_URL` | Hardcode the backend URL |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Reference source files (read-only — DO NOT copy verbatim)
|
|
113
|
+
|
|
114
|
+
- `D:\01 - projets\SmartStack.app\features\IA-Workflow\web\smartstack-web\src\provider\SmartStackProvider.tsx`
|
|
115
|
+
- `D:\01 - projets\SmartStack.app\features\IA-Workflow\web\smartstack-web\src\router\types.ts` (SmartStackConfig)
|
|
116
|
+
- `D:\01 - projets\SmartStack.app\features\IA-Workflow\web\smartstack-web\src\main.tsx` (i18nReady + componentRegistry import)
|
|
117
|
+
|
|
118
|
+
For overrides via `extensions`, see [extensions-system.md](extensions-system.md).
|