diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..46f691c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,25 @@ +## Description + + + +## Changes + + + +- +- + +## How to Test + +1. Run the Blazor Web App: `dotnet run --project Fixiy.Web/Fixiy.Web.csproj` +2. + +## Checklist + +- [ ] New interfaces defined in `Fixiy.Shared/Interfaces/` (not in platform projects) +- [ ] Platform-specific services implemented in both `Fixiy.Maui/Services/` and `Fixiy.Web/Services/` +- [ ] Both DI roots updated (`MauiProgram.cs` and `Fixiy.Web/Program.cs`) +- [ ] Render modes use `InteractiveRenderSettings.*` properties, not `RenderMode.*` constants +- [ ] No MAUI-specific APIs (`DeviceInfo`, `FileSystem`, etc.) introduced in `Fixiy.Shared` +- [ ] `_Imports.razor` updated if new shared namespaces are needed +- [ ] `InteractiveRenderSettings.ConfigureBlazorHybridRenderModes()` updated if new render mode properties added diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..cc632cd --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,67 @@ +# GitHub Copilot Instructions — Fixiy + +## Language and Style + +- **C# 12+** with `enable` and `enable` throughout. Never disable these per-file without strong justification. +- Use **file-scoped namespaces** (`namespace Fixiy.Shared.Services;`) not block-scoped. +- Prefer `record` types for immutable DTOs. +- Use `async`/`await` consistently — never `.Result` or `.Wait()` on Tasks. +- Remove unused `using` directives. + +## Blazor Component Conventions + +- All shared components live in `Fixiy.Shared/Components/`. Do not create components in `Fixiy.Maui` or `Fixiy.Web`. +- Pages go in `Fixiy.Shared/Components/Pages/`, layout in `Layout/`, reusable elements in `SingleElements/`. +- Scoped CSS goes in a matching `.razor.css` file alongside the component. +- Inject services via `@inject` in `.razor` files — Blazor components do not support constructor injection. +- Prefer `[Parameter]` for component inputs; avoid cascading parameters unless genuinely needed across deep trees. + +## Render Mode Rules + +- **Always use `InteractiveRenderSettings.*` properties** (`@InteractiveServer`, `@InteractiveAuto`, `@InteractiveWebAssembly`) in shared components — never hard-coded `RenderMode.*` constants. +- `Fixiy.Maui/MauiProgram.cs` calls `ConfigureBlazorHybridRenderModes()` which sets all three properties to `null`. This is intentional — MAUI Blazor Hybrid runs components statically. +- Do not work around the null render modes in shared code. Components that need interactivity must tolerate `null` render mode in MAUI context. + +## IFormFactor Platform Abstraction + +- Platform-specific device info must be accessed via `IFormFactor` injection (`Fixiy.Shared/Interfaces/IFormFactor.cs`). +- **Never call `DeviceInfo`, `Connectivity`, `FileSystem`, or any `Microsoft.Maui.*` API from `Fixiy.Shared`.** +- When adding new platform capabilities: define interface in `Fixiy.Shared/Interfaces/` → implement in both `Fixiy.Maui/Services/` and `Fixiy.Web/Services/` → register in both DI roots. + +## IntegryApiClient Registration + +- Register with the same `appToken` and `useLoginAzienda: true` in both hosts. +- MAUI: `.UseIntegry(appToken, useLoginAzienda: true)` on the `MauiAppBuilder` chain. +- Web: `builder.Services.UseIntegry(appToken, useLoginAzienda: true)` in `Program.cs`. +- One registration per DI root — do not call it twice. + +## Service Registration Lifetimes + +- `Fixiy.Maui` registers platform services as **`Singleton`** — the MAUI app has a single long-lived process. +- `Fixiy.Web` registers platform services as **`Scoped`** — ASP.NET Core server-side per-request scope. +- Interfaces must not assume a specific lifetime; implementations may. + +## Maintenance Matrix + +| Change | Cascades to | +|--------|-------------| +| Add/modify `Fixiy.Shared/Interfaces/*.cs` | Both `Fixiy.Maui/Services/` and `Fixiy.Web/Services/` implementations + DI registration in both hosts | +| Modify `InteractiveRenderSettings.cs` | `MauiProgram.ConfigureBlazorHybridRenderModes()` — must null every new property | +| Add a new shared page | `Fixiy.Shared/Components/Pages/` → nav link in `NavMenu.razor` if user-accessible | +| Add a NuGet package | Target `.csproj` file; check private NuGet feed has it | +| Change IntegryApiClient version | All three `.csproj` files (`Fixiy.Maui`, `Fixiy.Shared`, `Fixiy.Web`) | +| Modify `_Imports.razor` | Verify no MAUI-only or web-only namespaces are introduced | +| Update app token | `Fixiy.Maui/MauiProgram.cs` `AppToken` const + `Fixiy.Web/Program.cs` `appToken` const | + +## Test Conventions + +- No test projects are currently configured. When adding tests, use **xUnit** with **bUnit** for Razor component testing. +- Test projects should reference `Fixiy.Shared` and provide mock implementations of `IFormFactor`. +- Use `Moq` or `NSubstitute` for mocking; do not create manual stubs unless the interface is trivial. + +## Code Style Notes + +- Omit the `private` modifier on fields — it is the default in C#. +- Constants: `PascalCase` for class-level `const`, no Hungarian notation. +- One type per file; file name matches type name. +- Keep `@code` blocks in `.razor` files focused — extract complex logic to a `*.razor.cs` code-behind or a separate service class. diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 0000000..e7c1df6 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,33 @@ +name: Copilot Setup Steps + +on: workflow_dispatch + +jobs: + copilot-setup-steps: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Configure private NuGet source credentials + # NuGet.Config points to the private Integry feed; CI secrets provide auth. + # Set NUGET_USERNAME and NUGET_PASSWORD in repository secrets. + run: | + dotnet nuget update source integry \ + --username "${{ secrets.NUGET_USERNAME }}" \ + --password "${{ secrets.NUGET_PASSWORD }}" \ + --store-password-in-clear-text + + - name: Restore dependencies + run: dotnet restore Fixiy.sln + + - name: Build Fixiy.Web + # MAUI builds require platform-specific workloads not available on ubuntu-latest. + # Fixiy.Web covers the shared Blazor component tree and validates the full build. + run: dotnet build Fixiy.Web/Fixiy.Web.csproj --no-restore -c Debug diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0ff52dd --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,132 @@ +# Fixiy — Agent Guide + +## Project Overview + +Fixiy is a **.NET MAUI Blazor Hybrid** app that shares its entire UI with a **Blazor Server Web App** via a Razor Class Library (RCL). All pages and components live in `Fixiy.Shared` and are consumed by both host projects without modification. + +- **App ID:** `it.integry.fixiy` +- **Solution:** `Fixiy.sln` +- **SDK:** .NET 10 (`global.json` — `rollForward: latestMajor`) +- **NuGet source:** Private feed — see `NuGet.Config` (`https://nuget.studioml.it/repository/nuget-group/index.json`) +- **Platforms:** Android 26+, iOS 15+, Web (Blazor Server) + +## Repository Structure + +``` +Fixiy.sln +├── Fixiy.Maui/ # .NET MAUI Blazor Hybrid host (Android + iOS) +│ ├── MauiProgram.cs # DI root — registers MAUI services + IntegryApiClient +│ ├── Services/FormFactor.cs # MAUI implementation of IFormFactor (uses DeviceInfo) +│ ├── Platforms/ # Platform-specific entry points (Android, iOS, …) +│ └── Resources/ # App icons, splash, fonts, images +├── Fixiy.Shared/ # Razor Class Library — all shared UI +│ ├── Components/ +│ │ ├── Layout/ # MainLayout.razor, NavBar.razor, NavMenu.razor +│ │ ├── Pages/ # Routable pages (e.g., Home.razor) +│ │ └── SingleElements/ # Reusable components (e.g., NoDataAvailable.razor) +│ ├── Interfaces/IFormFactor.cs # Platform abstraction interface +│ ├── InteractiveRenderSettings.cs # Shared render mode constants (nulled in MAUI) +│ └── _Imports.razor # Global using directives for shared components +└── Fixiy.Web/ # Blazor Server Web App host + ├── Program.cs # DI root — registers web services + IntegryApiClient + └── Services/FormFactor.cs # Web implementation of IFormFactor +``` + +## Tech Stack + +| Layer | Technology | +|-------|-----------| +| Mobile host | .NET MAUI Blazor Hybrid — `net10.0-android`, `net10.0-ios` | +| Web host | Blazor Server (ASP.NET Core, `net10.0`) | +| Shared UI | Razor Class Library (`Fixiy.Shared`, `net10.0`) | +| Client SDK | `IntegryApiClient` — MAUI / Core / Blazor variants (private NuGet) | +| Fonts | OpenSans via MAUI font registration | + +## Build & Run + +**Prerequisites:** .NET SDK 10, MAUI workload (`dotnet workload install maui`) for mobile builds. + +```bash +# Restore (requires private NuGet credentials — see NuGet.Config) +dotnet restore Fixiy.sln + +# Run Blazor Web App (no platform SDK required) +dotnet run --project Fixiy.Web/Fixiy.Web.csproj + +# Build MAUI for Android (requires MAUI workload) +dotnet build Fixiy.Maui/Fixiy.Maui.csproj -f net10.0-android + +# Build MAUI for iOS (requires macOS + Xcode) +dotnet build Fixiy.Maui/Fixiy.Maui.csproj -f net10.0-ios +``` + +## Key Patterns and Conventions + +### 1. InteractiveRenderSettings pattern + +`Fixiy.Shared/InteractiveRenderSettings.cs` exposes `static IComponentRenderMode?` properties (`InteractiveServer`, `InteractiveAuto`, `InteractiveWebAssembly`). In `Fixiy.Web` these remain set to real render modes. In `Fixiy.Maui`, `MauiProgram.ConfigureBlazorHybridRenderModes()` sets all three to `null` — MAUI Blazor Hybrid does not support server or WASM render modes. + +**Always use the property, not the constant, in shared components:** + +```razor +@* Wrong — hard-coded constant, crashes in MAUI *@ +@rendermode="RenderMode.InteractiveServer" + +@* Correct — resolves to null in MAUI, InteractiveServer in Web *@ +@rendermode="@InteractiveServer" +``` + +### 2. IFormFactor platform abstraction + +`IFormFactor` (in `Fixiy.Shared/Interfaces/`) provides `GetFormFactor()` and `GetPlatform()`. Each host project provides its own implementation registered in DI. Inject `IFormFactor` in shared components; **never call `DeviceInfo` or other MAUI APIs from `Fixiy.Shared`**. + +### 3. IntegryApiClient registration + +Both DI roots register IntegryApiClient with the same `appToken` and `useLoginAzienda: true`. + +- MAUI: `.UseIntegry(appToken, useLoginAzienda: true)` on `MauiAppBuilder` +- Web: `builder.Services.UseIntegry(appToken, useLoginAzienda: true)` + +The app token must match across both registrations. It is currently declared as a `const` in `MauiProgram.cs` and a `const string` in `Fixiy.Web/Program.cs`. + +### 4. Adding a new page + +1. Create `Fixiy.Shared/Components/Pages/YourPage.razor` with `@page "/your-route"` +2. No extra registration required — `Routes.razor` discovers pages via assembly scanning +3. Add a navigation entry to `Fixiy.Shared/Components/Layout/NavMenu.razor` if user-accessible + +### 5. Adding a platform-specific service + +1. Define the interface in `Fixiy.Shared/Interfaces/IYourService.cs` +2. Implement in `Fixiy.Maui/Services/YourService.cs` (may use MAUI APIs) +3. Implement in `Fixiy.Web/Services/YourService.cs` (BCL only) +4. Register in `Fixiy.Maui/MauiProgram.cs` as `AddSingleton()` +5. Register in `Fixiy.Web/Program.cs` as `AddScoped()` + +## Maintenance Matrix + +| When you change… | Also update… | +|------------------|-------------| +| `Fixiy.Shared/Interfaces/IFormFactor.cs` | `Fixiy.Maui/Services/FormFactor.cs` and `Fixiy.Web/Services/FormFactor.cs` | +| Any interface in `Fixiy.Shared/Interfaces/` | Both platform `Services/` implementations + both DI roots | +| `InteractiveRenderSettings.cs` (add/remove property) | `MauiProgram.ConfigureBlazorHybridRenderModes()` — must null all new properties | +| `Fixiy.Shared/_Imports.razor` | Verify imports are valid in both MAUI and Web (no platform-only namespaces) | +| IntegryApiClient package version | Update in all three `.csproj` files consistently | +| App token | Update in both `Fixiy.Maui/MauiProgram.cs` and `Fixiy.Web/Program.cs` | +| `Fixiy.Shared/Components/Layout/NavMenu.razor` | Review navigation works on both platforms | + +## CI/CD + +No CI pipeline is currently configured. The project uses a private NuGet feed (`NuGet.Config`). For CI builds: + +- Set `NUGET_USERNAME` and `NUGET_PASSWORD` (or PAT) as CI secrets +- Build only `Fixiy.Web` in standard CI; MAUI builds require platform-specific agents with MAUI workloads +- See `.github/workflows/copilot-setup-steps.yml` for environment setup reference + +## Common Pitfalls + +- **Render mode constants in shared code:** Use `@InteractiveServer` (property from `InteractiveRenderSettings`) never `RenderMode.InteractiveServer`. The MAUI host nulls these via `ConfigureBlazorHybridRenderModes()`. +- **MAUI APIs in Fixiy.Shared:** `DeviceInfo`, `FileSystem`, `Connectivity`, and other `Microsoft.Maui.*` APIs are not available in the RCL. Define a new `IFormFactor`-style interface instead. +- **Private NuGet feed:** All `dotnet restore` calls require credentials. In CI, add the source credentials via environment secrets — the `NuGet.Config` `` directive removes all default sources. +- **MAUI workload for builds:** `dotnet workload install maui` is required before building `Fixiy.Maui`. Standard CI runners (Ubuntu) don't have it by default. +- **Service lifetimes:** MAUI registers platform services as `Singleton` (long-lived app); Web registers them as `Scoped` (per request). Interfaces must not assume a specific lifetime. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4e05021 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). + +## [Unreleased] + +## [1.0.0] + +### Added + +- .NET MAUI Blazor Hybrid host targeting Android 26+ and iOS 15+ +- Blazor Server Web App host sharing UI via `Fixiy.Shared` Razor Class Library +- `IFormFactor` platform abstraction for device/platform detection +- `InteractiveRenderSettings` pattern for sharing render modes between MAUI and Web +- IntegryApiClient integration for both MAUI and Web entry points +- `NoDataAvailable` reusable component +- `NavBar` and `NavMenu` layout components diff --git a/README.md b/README.md index 43aac9e..b2790e9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![AI Ready](https://img.shields.io/badge/AI--Ready-yes-brightgreen?style=flat)](https://github.com/johnpapa/ai-ready) + # .NET MAUI Blazor Hybrid app with a Blazor Web App sample app This sample app demonstrates a .NET MAUI Blazor Hybrid app with a Blazor Web App that uses a shared user interface via a Razor class library (RCL). @@ -8,4 +10,12 @@ For more information, see [Build a .NET MAUI Blazor Hybrid app with a Blazor Web 1. Clone this repository or download a ZIP archive of the repository. For more information, see [How to download a sample](https://learn.microsoft.com/aspnet/core/introduction-to-aspnet-core#how-to-download-a-sample). -1. Run the `MauiBlazorWeb.Maui` project to run the .NET MAUI Blazor Hybrid app, or run the `MauiBlazorWeb.Web` project to run the Blazor Web App. +1. Run the `Fixiy.Maui` project to run the .NET MAUI Blazor Hybrid app, or run the `Fixiy.Web` project to run the Blazor Web App. + +## Contributing + +1. Fork the repository and create a branch from `master`. +2. Restore dependencies: `dotnet restore Fixiy.sln` (requires credentials for the private NuGet feed — see `NuGet.Config`). +3. Run the web app to verify changes: `dotnet run --project Fixiy.Web/Fixiy.Web.csproj`. +4. Open a pull request — see [`.github/PULL_REQUEST_TEMPLATE.md`](.github/PULL_REQUEST_TEMPLATE.md) for the checklist. +5. See [`AGENTS.md`](AGENTS.md) for architecture details and the maintenance matrix.