From 94148a8e2132d6e34d787c2c68abb0bd86e7bd66 Mon Sep 17 00:00:00 2001
From: Manpreet Singh <166604315+Code-311@users.noreply.github.com>
Date: Fri, 6 Mar 2026 20:03:37 +0530
Subject: [PATCH] Fix remaining CI build/test issues in MVC, tests, and
internals visibility
---
.github/workflows/ci.yml | 28 +++
Code311.sln | 171 +++++++++++++++
Directory.Build.props | 25 +++
Directory.Build.targets | 9 +
Directory.Packages.props | 19 ++
README.md | 91 ++++++++
docs/adr/ADR-0001-phase0-baseline.md | 13 ++
docs/adr/ADR-TEMPLATE.md | 13 ++
docs/adr/README.md | 3 +
docs/architecture-proposal.md | 179 ++++++++++++++++
global.json | 7 +
src/Code311.Host/.gitkeep | 1 +
src/Code311.Host/Code311.Host.csproj | 28 +++
.../Controllers/ArchitectureController.cs | 8 +
.../Controllers/ComponentsDemoController.cs | 13 ++
.../Controllers/DashboardController.cs | 15 ++
.../Controllers/HomeController.cs | 8 +
.../Controllers/PreferencesController.cs | 25 +++
.../Controllers/WidgetsController.cs | 50 +++++
src/Code311.Host/Models/HostViewModels.cs | 12 ++
.../Pages/Diagnostics/Licensing.cshtml | 17 ++
.../Pages/Diagnostics/Licensing.cshtml.cs | 19 ++
src/Code311.Host/Pages/_ViewImports.cshtml | 3 +
src/Code311.Host/Pages/_ViewStart.cshtml | 3 +
src/Code311.Host/Program.cs | 93 ++++++++
src/Code311.Host/Services/DemoUserContext.cs | 13 ++
.../Services/HostStartupLicenseException.cs | 6 +
.../Services/PreferenceOrchestrator.cs | 48 +++++
.../Views/Architecture/About.cshtml | 7 +
.../Views/ComponentsDemo/Data.cshtml | 2 +
.../Views/ComponentsDemo/Feedback.cshtml | 2 +
.../Views/ComponentsDemo/Forms.cshtml | 11 +
.../Views/ComponentsDemo/Layout.cshtml | 5 +
.../Views/ComponentsDemo/Media.cshtml | 2 +
.../Views/ComponentsDemo/Navigation.cshtml | 2 +
src/Code311.Host/Views/Dashboard/Index.cshtml | 15 ++
src/Code311.Host/Views/Home/Index.cshtml | 12 ++
.../Views/Preferences/Index.cshtml | 24 +++
src/Code311.Host/Views/Shared/_Layout.cshtml | 25 +++
.../Views/Widgets/DataTables.cshtml | 8 +
.../Views/Widgets/WidgetPage.cshtml | 9 +
src/Code311.Host/Views/_ViewImports.cshtml | 5 +
src/Code311.Host/Views/_ViewStart.cshtml | 3 +
src/Code311.Host/appsettings.json | 5 +
src/Code311.Licensing/.gitkeep | 1 +
.../Code311.Licensing.csproj | 13 ++
.../ServiceCollectionExtensions.cs | 48 +++++
.../Diagnostics/LicensingDiagnostics.cs | 29 +++
src/Code311.Licensing/Models/LicenseModels.cs | 49 +++++
.../Models/LicenseStatusModels.cs | 35 ++++
.../Sources/LicenseSources.cs | 41 ++++
.../Validation/LicenseValidationServices.cs | 129 ++++++++++++
src/Code311.Persistence.EFCore/.gitkeep | 1 +
.../Code311.Persistence.EFCore.csproj | 19 ++
.../Code311PreferenceDbContext.cs | 19 ++
.../ServiceCollectionExtensions.cs | 31 +++
.../Entities/UserUiPreferenceEntity.cs | 19 ++
.../Extensions/ModelBuilderExtensions.cs | 20 ++
.../UserUiPreferenceEntityConfiguration.cs | 46 ++++
.../Stores/EfCoreUserUiPreferenceStore.cs | 83 ++++++++
src/Code311.Tabler.Components/.gitkeep | 1 +
.../Code311.Tabler.Components.csproj | 17 ++
.../Common/ComponentModels.cs | 33 +++
.../Common/TagHelperUtility.cs | 38 ++++
.../Data/DataComponents.cs | 98 +++++++++
.../ServiceCollectionExtensions.cs | 26 +++
.../Feedback/FeedbackComponents.cs | 103 +++++++++
.../Forms/FormTagHelpers.cs | 154 ++++++++++++++
.../Forms/FormViewComponents.cs | 39 ++++
.../Layout/LayoutComponents.cs | 94 +++++++++
.../Media/MediaComponents.cs | 70 +++++++
.../Navigation/NavigationComponents.cs | 84 ++++++++
src/Code311.Tabler.Core/.gitkeep | 1 +
.../Assets/TablerAssets.cs | 60 ++++++
.../Code311.Tabler.Core.csproj | 16 ++
.../ServiceCollectionExtensions.cs | 43 ++++
.../Mapping/TablerSemanticClassMapper.cs | 198 ++++++++++++++++++
.../Theming/TablerThemeMapper.cs | 111 ++++++++++
.../Widgets/WidgetSlotContracts.cs | 26 +++
src/Code311.Tabler.Dashboard/.gitkeep | 1 +
.../Code311.Tabler.Dashboard.csproj | 18 ++
.../DashboardCompositionContracts.cs | 47 +++++
.../ServiceCollectionExtensions.cs | 35 ++++
.../Kpi/KpiViewComponents.cs | 21 ++
.../Layout/DashboardShellViewComponent.cs | 41 ++++
.../Models/DashboardModels.cs | 60 ++++++
.../Panels/DashboardPanelViewComponents.cs | 44 ++++
.../Properties/AssemblyInfo.cs | 3 +
src/Code311.Tabler.Mvc/.gitkeep | 1 +
src/Code311.Tabler.Mvc/Assets/AssetModels.cs | 57 +++++
.../WidgetAssetRequestStoreExtensions.cs | 25 +++
.../Code311.Tabler.Mvc.csproj | 17 ++
.../ServiceCollectionExtensions.cs | 69 ++++++
.../Feedback/RequestFeedback.cs | 37 ++++
.../Filters/Code311MvcFilters.cs | 88 ++++++++
.../Helpers/Code311ControllerBase.cs | 27 +++
.../Helpers/HtmlHelperExtensions.cs | 67 ++++++
.../Properties/AssemblyInfo.cs | 3 +
.../Theming/ThemeRequestContext.cs | 22 ++
src/Code311.Tabler.Razor/.gitkeep | 1 +
.../Assets/AssetModels.cs | 37 ++++
.../WidgetAssetRequestStoreExtensions.cs | 25 +++
.../Code311.Tabler.Razor.csproj | 17 ++
.../ServiceCollectionExtensions.cs | 60 ++++++
.../Feedback/RequestFeedback.cs | 25 +++
.../Filters/Code311RazorFilters.cs | 76 +++++++
.../Helpers/Code311PageModelBase.cs | 22 ++
.../Helpers/PageModelExtensions.cs | 46 ++++
.../Properties/AssemblyInfo.cs | 3 +
.../Theming/ThemeRequestContext.cs | 16 ++
src/Code311.Tabler.Widgets.Calendar/.gitkeep | 1 +
.../Assets/CalendarWidgetAssets.cs | 17 ++
.../Code311.Tabler.Widgets.Calendar.csproj | 13 ++
.../ServiceCollectionExtensions.cs | 15 ++
.../Options/CalendarWidgetOptions.cs | 31 +++
.../Widgets/CalendarWidgetSlot.cs | 26 +++
src/Code311.Tabler.Widgets.Charts/.gitkeep | 1 +
.../Assets/ChartWidgetAssets.cs | 17 ++
.../Code311.Tabler.Widgets.Charts.csproj | 13 ++
.../ServiceCollectionExtensions.cs | 15 ++
.../Options/ChartWidgetOptions.cs | 31 +++
.../Widgets/ChartWidgetSlot.cs | 26 +++
.../.gitkeep | 1 +
.../Assets/DataTableWidgetAssets.cs | 17 ++
.../Code311.Tabler.Widgets.DataTables.csproj | 13 ++
.../ServiceCollectionExtensions.cs | 15 ++
.../Options/DataTableWidgetOptions.cs | 51 +++++
.../Widgets/DataTableWidgetSlot.cs | 28 +++
.../Code311.Ui.Abstractions.csproj | 9 +
.../Contracts/InternalContractCatalog.cs | 50 +++++
.../Options/UiFrameworkOptions.cs | 36 ++++
.../Preferences/PreferenceContracts.cs | 33 +++
.../Preferences/UserUiPreference.cs | 84 ++++++++
src/Code311.Ui.Abstractions/README.md | 3 +
.../Semantics/UiSemantics.cs | 122 +++++++++++
.../Theming/ThemeContracts.cs | 21 ++
.../Theming/ThemeModels.cs | 87 ++++++++
.../Widgets/WidgetContracts.cs | 136 ++++++++++++
src/Code311.Ui.Core/.gitkeep | 1 +
src/Code311.Ui.Core/Code311.Ui.Core.csproj | 15 ++
.../ServiceCollectionExtensions.cs | 47 +++++
.../Feedback/FeedbackChannel.cs | 73 +++++++
.../Loading/LoadingOrchestration.cs | 161 ++++++++++++++
src/Code311.Ui.Core/Theming/ThemeRegistry.cs | 134 ++++++++++++
.../Code311.Tests.Host.csproj | 18 ++
.../PreferenceOrchestratorTests.cs | 50 +++++
tests/Code311.Tests.Integration.Mvc/.gitkeep | 1 +
.../Code311.Tests.Integration.Mvc.csproj | 16 ++
.../MvcIntegrationTests.cs | 112 ++++++++++
.../Code311.Tests.Integration.Razor/.gitkeep | 1 +
.../Code311.Tests.Integration.Razor.csproj | 16 ++
.../RazorIntegrationTests.cs | 118 +++++++++++
tests/Code311.Tests.Licensing/.gitkeep | 1 +
.../Code311.Tests.Licensing.csproj | 18 ++
.../LicensingServicesTests.cs | 146 +++++++++++++
.../Code311.Tests.Persistence.EFCore/.gitkeep | 1 +
.../Code311.Tests.Persistence.EFCore.csproj | 20 ++
.../EfCoreUserUiPreferenceStoreTests.cs | 98 +++++++++
.../ModelBuilderExtensionsTests.cs | 32 +++
.../Code311.Tests.Tabler.Components/.gitkeep | 1 +
.../Code311.Tests.Tabler.Components.csproj | 19 ++
.../ComponentRenderingTests.cs | 140 +++++++++++++
tests/Code311.Tests.Tabler.Core/.gitkeep | 1 +
.../Code311.Tests.Tabler.Core.csproj | 17 ++
.../TablerCoreTests.cs | 78 +++++++
tests/Code311.Tests.Tabler.Dashboard/.gitkeep | 1 +
.../Code311.Tests.Tabler.Dashboard.csproj | 18 ++
.../DashboardCompositionTests.cs | 95 +++++++++
.../CalendarWidgetTests.cs | 36 ++++
...de311.Tests.Tabler.Widgets.Calendar.csproj | 17 ++
.../ChartWidgetTests.cs | 36 ++++
...Code311.Tests.Tabler.Widgets.Charts.csproj | 17 ++
...311.Tests.Tabler.Widgets.DataTables.csproj | 17 ++
.../DataTableWidgetTests.cs | 37 ++++
.../Code311.Tests.Ui.Abstractions.csproj | 17 ++
.../ThemeAndPreferenceTests.cs | 59 ++++++
.../WidgetContractTests.cs | 45 ++++
tests/Code311.Tests.Ui.Core/.gitkeep | 1 +
.../Code311.Tests.Ui.Core.csproj | 17 ++
.../ThemeAndOrchestrationTests.cs | 121 +++++++++++
tests/Code311.Tests.Widgets/.gitkeep | 1 +
181 files changed, 6645 insertions(+)
create mode 100644 .github/workflows/ci.yml
create mode 100644 Code311.sln
create mode 100644 Directory.Build.props
create mode 100644 Directory.Build.targets
create mode 100644 Directory.Packages.props
create mode 100644 README.md
create mode 100644 docs/adr/ADR-0001-phase0-baseline.md
create mode 100644 docs/adr/ADR-TEMPLATE.md
create mode 100644 docs/adr/README.md
create mode 100644 docs/architecture-proposal.md
create mode 100644 global.json
create mode 100644 src/Code311.Host/.gitkeep
create mode 100644 src/Code311.Host/Code311.Host.csproj
create mode 100644 src/Code311.Host/Controllers/ArchitectureController.cs
create mode 100644 src/Code311.Host/Controllers/ComponentsDemoController.cs
create mode 100644 src/Code311.Host/Controllers/DashboardController.cs
create mode 100644 src/Code311.Host/Controllers/HomeController.cs
create mode 100644 src/Code311.Host/Controllers/PreferencesController.cs
create mode 100644 src/Code311.Host/Controllers/WidgetsController.cs
create mode 100644 src/Code311.Host/Models/HostViewModels.cs
create mode 100644 src/Code311.Host/Pages/Diagnostics/Licensing.cshtml
create mode 100644 src/Code311.Host/Pages/Diagnostics/Licensing.cshtml.cs
create mode 100644 src/Code311.Host/Pages/_ViewImports.cshtml
create mode 100644 src/Code311.Host/Pages/_ViewStart.cshtml
create mode 100644 src/Code311.Host/Program.cs
create mode 100644 src/Code311.Host/Services/DemoUserContext.cs
create mode 100644 src/Code311.Host/Services/HostStartupLicenseException.cs
create mode 100644 src/Code311.Host/Services/PreferenceOrchestrator.cs
create mode 100644 src/Code311.Host/Views/Architecture/About.cshtml
create mode 100644 src/Code311.Host/Views/ComponentsDemo/Data.cshtml
create mode 100644 src/Code311.Host/Views/ComponentsDemo/Feedback.cshtml
create mode 100644 src/Code311.Host/Views/ComponentsDemo/Forms.cshtml
create mode 100644 src/Code311.Host/Views/ComponentsDemo/Layout.cshtml
create mode 100644 src/Code311.Host/Views/ComponentsDemo/Media.cshtml
create mode 100644 src/Code311.Host/Views/ComponentsDemo/Navigation.cshtml
create mode 100644 src/Code311.Host/Views/Dashboard/Index.cshtml
create mode 100644 src/Code311.Host/Views/Home/Index.cshtml
create mode 100644 src/Code311.Host/Views/Preferences/Index.cshtml
create mode 100644 src/Code311.Host/Views/Shared/_Layout.cshtml
create mode 100644 src/Code311.Host/Views/Widgets/DataTables.cshtml
create mode 100644 src/Code311.Host/Views/Widgets/WidgetPage.cshtml
create mode 100644 src/Code311.Host/Views/_ViewImports.cshtml
create mode 100644 src/Code311.Host/Views/_ViewStart.cshtml
create mode 100644 src/Code311.Host/appsettings.json
create mode 100644 src/Code311.Licensing/.gitkeep
create mode 100644 src/Code311.Licensing/Code311.Licensing.csproj
create mode 100644 src/Code311.Licensing/DependencyInjection/ServiceCollectionExtensions.cs
create mode 100644 src/Code311.Licensing/Diagnostics/LicensingDiagnostics.cs
create mode 100644 src/Code311.Licensing/Models/LicenseModels.cs
create mode 100644 src/Code311.Licensing/Models/LicenseStatusModels.cs
create mode 100644 src/Code311.Licensing/Sources/LicenseSources.cs
create mode 100644 src/Code311.Licensing/Validation/LicenseValidationServices.cs
create mode 100644 src/Code311.Persistence.EFCore/.gitkeep
create mode 100644 src/Code311.Persistence.EFCore/Code311.Persistence.EFCore.csproj
create mode 100644 src/Code311.Persistence.EFCore/Code311PreferenceDbContext.cs
create mode 100644 src/Code311.Persistence.EFCore/DependencyInjection/ServiceCollectionExtensions.cs
create mode 100644 src/Code311.Persistence.EFCore/Entities/UserUiPreferenceEntity.cs
create mode 100644 src/Code311.Persistence.EFCore/Extensions/ModelBuilderExtensions.cs
create mode 100644 src/Code311.Persistence.EFCore/Mapping/UserUiPreferenceEntityConfiguration.cs
create mode 100644 src/Code311.Persistence.EFCore/Stores/EfCoreUserUiPreferenceStore.cs
create mode 100644 src/Code311.Tabler.Components/.gitkeep
create mode 100644 src/Code311.Tabler.Components/Code311.Tabler.Components.csproj
create mode 100644 src/Code311.Tabler.Components/Common/ComponentModels.cs
create mode 100644 src/Code311.Tabler.Components/Common/TagHelperUtility.cs
create mode 100644 src/Code311.Tabler.Components/Data/DataComponents.cs
create mode 100644 src/Code311.Tabler.Components/DependencyInjection/ServiceCollectionExtensions.cs
create mode 100644 src/Code311.Tabler.Components/Feedback/FeedbackComponents.cs
create mode 100644 src/Code311.Tabler.Components/Forms/FormTagHelpers.cs
create mode 100644 src/Code311.Tabler.Components/Forms/FormViewComponents.cs
create mode 100644 src/Code311.Tabler.Components/Layout/LayoutComponents.cs
create mode 100644 src/Code311.Tabler.Components/Media/MediaComponents.cs
create mode 100644 src/Code311.Tabler.Components/Navigation/NavigationComponents.cs
create mode 100644 src/Code311.Tabler.Core/.gitkeep
create mode 100644 src/Code311.Tabler.Core/Assets/TablerAssets.cs
create mode 100644 src/Code311.Tabler.Core/Code311.Tabler.Core.csproj
create mode 100644 src/Code311.Tabler.Core/DependencyInjection/ServiceCollectionExtensions.cs
create mode 100644 src/Code311.Tabler.Core/Mapping/TablerSemanticClassMapper.cs
create mode 100644 src/Code311.Tabler.Core/Theming/TablerThemeMapper.cs
create mode 100644 src/Code311.Tabler.Core/Widgets/WidgetSlotContracts.cs
create mode 100644 src/Code311.Tabler.Dashboard/.gitkeep
create mode 100644 src/Code311.Tabler.Dashboard/Code311.Tabler.Dashboard.csproj
create mode 100644 src/Code311.Tabler.Dashboard/Composition/DashboardCompositionContracts.cs
create mode 100644 src/Code311.Tabler.Dashboard/DependencyInjection/ServiceCollectionExtensions.cs
create mode 100644 src/Code311.Tabler.Dashboard/Kpi/KpiViewComponents.cs
create mode 100644 src/Code311.Tabler.Dashboard/Layout/DashboardShellViewComponent.cs
create mode 100644 src/Code311.Tabler.Dashboard/Models/DashboardModels.cs
create mode 100644 src/Code311.Tabler.Dashboard/Panels/DashboardPanelViewComponents.cs
create mode 100644 src/Code311.Tabler.Dashboard/Properties/AssemblyInfo.cs
create mode 100644 src/Code311.Tabler.Mvc/.gitkeep
create mode 100644 src/Code311.Tabler.Mvc/Assets/AssetModels.cs
create mode 100644 src/Code311.Tabler.Mvc/Assets/WidgetAssetRequestStoreExtensions.cs
create mode 100644 src/Code311.Tabler.Mvc/Code311.Tabler.Mvc.csproj
create mode 100644 src/Code311.Tabler.Mvc/DependencyInjection/ServiceCollectionExtensions.cs
create mode 100644 src/Code311.Tabler.Mvc/Feedback/RequestFeedback.cs
create mode 100644 src/Code311.Tabler.Mvc/Filters/Code311MvcFilters.cs
create mode 100644 src/Code311.Tabler.Mvc/Helpers/Code311ControllerBase.cs
create mode 100644 src/Code311.Tabler.Mvc/Helpers/HtmlHelperExtensions.cs
create mode 100644 src/Code311.Tabler.Mvc/Properties/AssemblyInfo.cs
create mode 100644 src/Code311.Tabler.Mvc/Theming/ThemeRequestContext.cs
create mode 100644 src/Code311.Tabler.Razor/.gitkeep
create mode 100644 src/Code311.Tabler.Razor/Assets/AssetModels.cs
create mode 100644 src/Code311.Tabler.Razor/Assets/WidgetAssetRequestStoreExtensions.cs
create mode 100644 src/Code311.Tabler.Razor/Code311.Tabler.Razor.csproj
create mode 100644 src/Code311.Tabler.Razor/DependencyInjection/ServiceCollectionExtensions.cs
create mode 100644 src/Code311.Tabler.Razor/Feedback/RequestFeedback.cs
create mode 100644 src/Code311.Tabler.Razor/Filters/Code311RazorFilters.cs
create mode 100644 src/Code311.Tabler.Razor/Helpers/Code311PageModelBase.cs
create mode 100644 src/Code311.Tabler.Razor/Helpers/PageModelExtensions.cs
create mode 100644 src/Code311.Tabler.Razor/Properties/AssemblyInfo.cs
create mode 100644 src/Code311.Tabler.Razor/Theming/ThemeRequestContext.cs
create mode 100644 src/Code311.Tabler.Widgets.Calendar/.gitkeep
create mode 100644 src/Code311.Tabler.Widgets.Calendar/Assets/CalendarWidgetAssets.cs
create mode 100644 src/Code311.Tabler.Widgets.Calendar/Code311.Tabler.Widgets.Calendar.csproj
create mode 100644 src/Code311.Tabler.Widgets.Calendar/DependencyInjection/ServiceCollectionExtensions.cs
create mode 100644 src/Code311.Tabler.Widgets.Calendar/Options/CalendarWidgetOptions.cs
create mode 100644 src/Code311.Tabler.Widgets.Calendar/Widgets/CalendarWidgetSlot.cs
create mode 100644 src/Code311.Tabler.Widgets.Charts/.gitkeep
create mode 100644 src/Code311.Tabler.Widgets.Charts/Assets/ChartWidgetAssets.cs
create mode 100644 src/Code311.Tabler.Widgets.Charts/Code311.Tabler.Widgets.Charts.csproj
create mode 100644 src/Code311.Tabler.Widgets.Charts/DependencyInjection/ServiceCollectionExtensions.cs
create mode 100644 src/Code311.Tabler.Widgets.Charts/Options/ChartWidgetOptions.cs
create mode 100644 src/Code311.Tabler.Widgets.Charts/Widgets/ChartWidgetSlot.cs
create mode 100644 src/Code311.Tabler.Widgets.DataTables/.gitkeep
create mode 100644 src/Code311.Tabler.Widgets.DataTables/Assets/DataTableWidgetAssets.cs
create mode 100644 src/Code311.Tabler.Widgets.DataTables/Code311.Tabler.Widgets.DataTables.csproj
create mode 100644 src/Code311.Tabler.Widgets.DataTables/DependencyInjection/ServiceCollectionExtensions.cs
create mode 100644 src/Code311.Tabler.Widgets.DataTables/Options/DataTableWidgetOptions.cs
create mode 100644 src/Code311.Tabler.Widgets.DataTables/Widgets/DataTableWidgetSlot.cs
create mode 100644 src/Code311.Ui.Abstractions/Code311.Ui.Abstractions.csproj
create mode 100644 src/Code311.Ui.Abstractions/Internal/Contracts/InternalContractCatalog.cs
create mode 100644 src/Code311.Ui.Abstractions/Options/UiFrameworkOptions.cs
create mode 100644 src/Code311.Ui.Abstractions/Preferences/PreferenceContracts.cs
create mode 100644 src/Code311.Ui.Abstractions/Preferences/UserUiPreference.cs
create mode 100644 src/Code311.Ui.Abstractions/README.md
create mode 100644 src/Code311.Ui.Abstractions/Semantics/UiSemantics.cs
create mode 100644 src/Code311.Ui.Abstractions/Theming/ThemeContracts.cs
create mode 100644 src/Code311.Ui.Abstractions/Theming/ThemeModels.cs
create mode 100644 src/Code311.Ui.Abstractions/Widgets/WidgetContracts.cs
create mode 100644 src/Code311.Ui.Core/.gitkeep
create mode 100644 src/Code311.Ui.Core/Code311.Ui.Core.csproj
create mode 100644 src/Code311.Ui.Core/DependencyInjection/ServiceCollectionExtensions.cs
create mode 100644 src/Code311.Ui.Core/Feedback/FeedbackChannel.cs
create mode 100644 src/Code311.Ui.Core/Loading/LoadingOrchestration.cs
create mode 100644 src/Code311.Ui.Core/Theming/ThemeRegistry.cs
create mode 100644 tests/Code311.Tests.Host/Code311.Tests.Host.csproj
create mode 100644 tests/Code311.Tests.Host/PreferenceOrchestratorTests.cs
create mode 100644 tests/Code311.Tests.Integration.Mvc/.gitkeep
create mode 100644 tests/Code311.Tests.Integration.Mvc/Code311.Tests.Integration.Mvc.csproj
create mode 100644 tests/Code311.Tests.Integration.Mvc/MvcIntegrationTests.cs
create mode 100644 tests/Code311.Tests.Integration.Razor/.gitkeep
create mode 100644 tests/Code311.Tests.Integration.Razor/Code311.Tests.Integration.Razor.csproj
create mode 100644 tests/Code311.Tests.Integration.Razor/RazorIntegrationTests.cs
create mode 100644 tests/Code311.Tests.Licensing/.gitkeep
create mode 100644 tests/Code311.Tests.Licensing/Code311.Tests.Licensing.csproj
create mode 100644 tests/Code311.Tests.Licensing/LicensingServicesTests.cs
create mode 100644 tests/Code311.Tests.Persistence.EFCore/.gitkeep
create mode 100644 tests/Code311.Tests.Persistence.EFCore/Code311.Tests.Persistence.EFCore.csproj
create mode 100644 tests/Code311.Tests.Persistence.EFCore/EfCoreUserUiPreferenceStoreTests.cs
create mode 100644 tests/Code311.Tests.Persistence.EFCore/ModelBuilderExtensionsTests.cs
create mode 100644 tests/Code311.Tests.Tabler.Components/.gitkeep
create mode 100644 tests/Code311.Tests.Tabler.Components/Code311.Tests.Tabler.Components.csproj
create mode 100644 tests/Code311.Tests.Tabler.Components/ComponentRenderingTests.cs
create mode 100644 tests/Code311.Tests.Tabler.Core/.gitkeep
create mode 100644 tests/Code311.Tests.Tabler.Core/Code311.Tests.Tabler.Core.csproj
create mode 100644 tests/Code311.Tests.Tabler.Core/TablerCoreTests.cs
create mode 100644 tests/Code311.Tests.Tabler.Dashboard/.gitkeep
create mode 100644 tests/Code311.Tests.Tabler.Dashboard/Code311.Tests.Tabler.Dashboard.csproj
create mode 100644 tests/Code311.Tests.Tabler.Dashboard/DashboardCompositionTests.cs
create mode 100644 tests/Code311.Tests.Tabler.Widgets.Calendar/CalendarWidgetTests.cs
create mode 100644 tests/Code311.Tests.Tabler.Widgets.Calendar/Code311.Tests.Tabler.Widgets.Calendar.csproj
create mode 100644 tests/Code311.Tests.Tabler.Widgets.Charts/ChartWidgetTests.cs
create mode 100644 tests/Code311.Tests.Tabler.Widgets.Charts/Code311.Tests.Tabler.Widgets.Charts.csproj
create mode 100644 tests/Code311.Tests.Tabler.Widgets.DataTables/Code311.Tests.Tabler.Widgets.DataTables.csproj
create mode 100644 tests/Code311.Tests.Tabler.Widgets.DataTables/DataTableWidgetTests.cs
create mode 100644 tests/Code311.Tests.Ui.Abstractions/Code311.Tests.Ui.Abstractions.csproj
create mode 100644 tests/Code311.Tests.Ui.Abstractions/ThemeAndPreferenceTests.cs
create mode 100644 tests/Code311.Tests.Ui.Abstractions/WidgetContractTests.cs
create mode 100644 tests/Code311.Tests.Ui.Core/.gitkeep
create mode 100644 tests/Code311.Tests.Ui.Core/Code311.Tests.Ui.Core.csproj
create mode 100644 tests/Code311.Tests.Ui.Core/ThemeAndOrchestrationTests.cs
create mode 100644 tests/Code311.Tests.Widgets/.gitkeep
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..ea91da6
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,28 @@
+name: ci
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+
+jobs:
+ build-and-test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ global-json-file: global.json
+
+ - name: Restore
+ run: dotnet restore Code311.sln
+
+ - name: Build
+ run: dotnet build Code311.sln --configuration Release --no-restore
+
+ - name: Test
+ run: dotnet test Code311.sln --configuration Release --no-build --verbosity normal
diff --git a/Code311.sln b/Code311.sln
new file mode 100644
index 0000000..ee253ba
--- /dev/null
+++ b/Code311.sln
@@ -0,0 +1,171 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Ui.Abstractions", "src\Code311.Ui.Abstractions\Code311.Ui.Abstractions.csproj", "{A1111111-1111-1111-1111-111111111111}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Ui.Core", "src\Code311.Ui.Core\Code311.Ui.Core.csproj", "{A1111111-1111-1111-1111-111111111112}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tabler.Core", "src\Code311.Tabler.Core\Code311.Tabler.Core.csproj", "{A1111111-1111-1111-1111-111111111113}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tabler.Components", "src\Code311.Tabler.Components\Code311.Tabler.Components.csproj", "{A1111111-1111-1111-1111-111111111114}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tabler.Mvc", "src\Code311.Tabler.Mvc\Code311.Tabler.Mvc.csproj", "{A1111111-1111-1111-1111-111111111115}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tabler.Razor", "src\Code311.Tabler.Razor\Code311.Tabler.Razor.csproj", "{A1111111-1111-1111-1111-111111111116}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tabler.Dashboard", "src\Code311.Tabler.Dashboard\Code311.Tabler.Dashboard.csproj", "{A1111111-1111-1111-1111-111111111117}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tests.Ui.Abstractions", "tests\Code311.Tests.Ui.Abstractions\Code311.Tests.Ui.Abstractions.csproj", "{B2222222-2222-2222-2222-222222222222}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tests.Ui.Core", "tests\Code311.Tests.Ui.Core\Code311.Tests.Ui.Core.csproj", "{B2222222-2222-2222-2222-222222222223}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tests.Tabler.Core", "tests\Code311.Tests.Tabler.Core\Code311.Tests.Tabler.Core.csproj", "{B2222222-2222-2222-2222-222222222224}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tests.Tabler.Components", "tests\Code311.Tests.Tabler.Components\Code311.Tests.Tabler.Components.csproj", "{B2222222-2222-2222-2222-222222222225}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tests.Integration.Mvc", "tests\Code311.Tests.Integration.Mvc\Code311.Tests.Integration.Mvc.csproj", "{B2222222-2222-2222-2222-222222222226}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tests.Integration.Razor", "tests\Code311.Tests.Integration.Razor\Code311.Tests.Integration.Razor.csproj", "{B2222222-2222-2222-2222-222222222227}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tests.Tabler.Dashboard", "tests\Code311.Tests.Tabler.Dashboard\Code311.Tests.Tabler.Dashboard.csproj", "{B2222222-2222-2222-2222-222222222228}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tabler.Widgets.DataTables", "src\Code311.Tabler.Widgets.DataTables\Code311.Tabler.Widgets.DataTables.csproj", "{A1111111-1111-1111-1111-111111111118}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tabler.Widgets.Calendar", "src\Code311.Tabler.Widgets.Calendar\Code311.Tabler.Widgets.Calendar.csproj", "{A1111111-1111-1111-1111-111111111119}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tabler.Widgets.Charts", "src\Code311.Tabler.Widgets.Charts\Code311.Tabler.Widgets.Charts.csproj", "{A1111111-1111-1111-1111-111111111120}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tests.Tabler.Widgets.DataTables", "tests\Code311.Tests.Tabler.Widgets.DataTables\Code311.Tests.Tabler.Widgets.DataTables.csproj", "{B2222222-2222-2222-2222-222222222229}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tests.Tabler.Widgets.Calendar", "tests\Code311.Tests.Tabler.Widgets.Calendar\Code311.Tests.Tabler.Widgets.Calendar.csproj", "{B2222222-2222-2222-2222-222222222230}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tests.Tabler.Widgets.Charts", "tests\Code311.Tests.Tabler.Widgets.Charts\Code311.Tests.Tabler.Widgets.Charts.csproj", "{B2222222-2222-2222-2222-222222222231}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Persistence.EFCore", "src\Code311.Persistence.EFCore\Code311.Persistence.EFCore.csproj", "{A1111111-1111-1111-1111-111111111121}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tests.Persistence.EFCore", "tests\Code311.Tests.Persistence.EFCore\Code311.Tests.Persistence.EFCore.csproj", "{B2222222-2222-2222-2222-222222222232}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Licensing", "src\Code311.Licensing\Code311.Licensing.csproj", "{A1111111-1111-1111-1111-111111111122}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tests.Licensing", "tests\Code311.Tests.Licensing\Code311.Tests.Licensing.csproj", "{B2222222-2222-2222-2222-222222222233}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Host", "src\Code311.Host\Code311.Host.csproj", "{A1111111-1111-1111-1111-111111111123}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Code311.Tests.Host", "tests\Code311.Tests.Host\Code311.Tests.Host.csproj", "{B2222222-2222-2222-2222-222222222234}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A1111111-1111-1111-1111-111111111111}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111111}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111111}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111111}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111112}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111112}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111112}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111112}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111113}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111113}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111113}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111113}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111114}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111114}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111114}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111114}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111115}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111115}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111115}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111115}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111116}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111116}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111116}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111116}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111117}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111117}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111117}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111117}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222222}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222222}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222222}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222223}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222223}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222223}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222224}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222224}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222224}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222224}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222225}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222225}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222225}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222225}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222226}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222226}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222226}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222226}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222227}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222227}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222227}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222227}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222228}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222228}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222228}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111118}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111118}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111118}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111118}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111119}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111119}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111119}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111120}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111120}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111120}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222229}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222229}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222229}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222229}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222230}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222230}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222230}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222230}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222231}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222231}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222231}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222231}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111121}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111121}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111121}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111121}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222232}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222232}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222232}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222232}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111122}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111122}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111122}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111122}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222233}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222233}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222233}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222233}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111123}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1111111-1111-1111-1111-111111111123}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1111111-1111-1111-1111-111111111123}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222234}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222234}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2222222-2222-2222-2222-222222222234}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2222222-2222-2222-2222-222222222234}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..6246ab5
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,25 @@
+
+
+ enable
+ enable
+ preview
+ latest
+ true
+ true
+ true
+ $(NoWarn);1591
+ true
+
+
+
+ false
+
+
+
+ Code311
+ Code311
+ true
+ https://example.com/Code311
+ README.md
+
+
diff --git a/Directory.Build.targets b/Directory.Build.targets
new file mode 100644
index 0000000..d84f792
--- /dev/null
+++ b/Directory.Build.targets
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 0000000..4c5b154
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,19 @@
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ab49142
--- /dev/null
+++ b/README.md
@@ -0,0 +1,91 @@
+# Code311
+
+Code311 is a modular, design-system-neutral UI framework ecosystem for ASP.NET Core (.NET 10), with a Tabler-backed implementation and an official hybrid MVC + Razor demo host.
+
+## Repository status
+
+This repository now includes:
+
+- **Foundation and governance**: solution layout, SDK pinning, centralized package versions, analyzer/nullability/doc enforcement.
+- **Framework-neutral contracts and core orchestration**:
+ - `Code311.Ui.Abstractions`
+ - `Code311.Ui.Core`
+- **Tabler implementation packages**:
+ - `Code311.Tabler.Core`
+ - `Code311.Tabler.Components`
+ - `Code311.Tabler.Dashboard`
+ - `Code311.Tabler.Widgets.DataTables`
+ - `Code311.Tabler.Widgets.Calendar`
+ - `Code311.Tabler.Widgets.Charts`
+ - `Code311.Tabler.Mvc`
+ - `Code311.Tabler.Razor`
+- **Infrastructure packages**:
+ - `Code311.Persistence.EFCore`
+ - `Code311.Licensing`
+- **Hybrid demo/integration portal**:
+ - `Code311.Host`
+
+## High-level architecture
+
+Code311 is intentionally layered:
+
+1. **Abstractions layer** (`Ui.Abstractions`) defines semantics/contracts with no design-system coupling.
+2. **Core layer** (`Ui.Core`) provides neutral orchestration services (theme/feedback/loading).
+3. **Design-system layer** (`Tabler.*`) maps abstractions into concrete UI behavior and rendering for MVC/Razor, dashboard, and widgets.
+4. **Cross-cutting infrastructure** (`Persistence.EFCore`, `Licensing`) remains independent from `Ui.Core` and Tabler component primitives.
+5. **Host layer** (`Code311.Host`) composes the ecosystem and demonstrates real integration flows without pushing host concerns down into framework packages.
+
+## Run the host demo (`Code311.Host`)
+
+> Prerequisites
+>
+> - .NET SDK 10 (preview)
+
+From repository root:
+
+```bash
+dotnet restore Code311.sln
+dotnet build Code311.sln
+dotnet run --project src/Code311.Host/Code311.Host.csproj
+```
+
+Default host behavior:
+
+- Uses **SQLite** demo persistence at `src/Code311.Host/App_Data/code311-host-demo.db`.
+- Runs explicit startup licensing validation (demo in-memory license configured in host startup).
+- Hosts both MVC and Razor Pages routes.
+
+## Key demo sections/routes
+
+Base URL assumes local launch profile default (`https://localhost:5001` or similar):
+
+- Home: `/`
+- Components:
+ - Forms: `/ComponentsDemo/Forms`
+ - Navigation: `/ComponentsDemo/Navigation`
+ - Layout: `/ComponentsDemo/Layout`
+ - Feedback: `/ComponentsDemo/Feedback`
+ - Data: `/ComponentsDemo/Data`
+ - Media: `/ComponentsDemo/Media`
+- Dashboard: `/Dashboard`
+- Widgets:
+ - DataTables: `/Widgets/DataTables`
+ - Calendar: `/Widgets/Calendar`
+ - Charts: `/Widgets/Charts`
+- Preferences / Theme: `/Preferences`
+- Licensing Diagnostics (Razor Page): `/Diagnostics/Licensing`
+- Architecture / About: `/Architecture/About`
+
+## Test and validation
+
+Run the full test matrix:
+
+```bash
+dotnet test Code311.sln
+```
+
+## Repository layout
+
+- `src/` — product/framework/host packages
+- `tests/` — unit and integration test projects
+- `docs/` — ADRs and architecture proposal
diff --git a/docs/adr/ADR-0001-phase0-baseline.md b/docs/adr/ADR-0001-phase0-baseline.md
new file mode 100644
index 0000000..6ad9d10
--- /dev/null
+++ b/docs/adr/ADR-0001-phase0-baseline.md
@@ -0,0 +1,13 @@
+# ADR-0001: Phase 0 governance baseline
+
+## Status
+Accepted
+
+## Context
+Phase 0 requires repository-level governance and build defaults for a commercial multi-package ecosystem.
+
+## Decision
+Adopt central package management, repository-wide nullable + XML docs + analyzer defaults, SDK pinning via global.json, and deterministic build settings.
+
+## Consequences
+All projects inherit consistent quality gates and package governance from repository root.
diff --git a/docs/adr/ADR-TEMPLATE.md b/docs/adr/ADR-TEMPLATE.md
new file mode 100644
index 0000000..5fe60b6
--- /dev/null
+++ b/docs/adr/ADR-TEMPLATE.md
@@ -0,0 +1,13 @@
+# ADR-XXXX:
+
+## Status
+Proposed | Accepted | Superseded
+
+## Context
+Describe the problem and constraints.
+
+## Decision
+Describe the chosen option.
+
+## Consequences
+Describe positive and negative outcomes.
diff --git a/docs/adr/README.md b/docs/adr/README.md
new file mode 100644
index 0000000..f497cfd
--- /dev/null
+++ b/docs/adr/README.md
@@ -0,0 +1,3 @@
+# Architecture Decision Records
+
+This folder stores ADRs for Code311.
diff --git a/docs/architecture-proposal.md b/docs/architecture-proposal.md
new file mode 100644
index 0000000..4994d2d
--- /dev/null
+++ b/docs/architecture-proposal.md
@@ -0,0 +1,179 @@
+# Code311 Architecture Proposal (Final Pre-Implementation Refinement)
+
+## Scope
+Architecture-only artifact. No implementation code is included.
+
+---
+
+## Confirmed Foundations
+
+### Strict dependency graph
+
+```text
+Code311.Ui.Abstractions
+ ↓
+Code311.Ui.Core
+ ↓
+Code311.Tabler.Core
+ ↓
+Code311.Tabler.Components
+ ↓
+Code311.Tabler.Dashboard + Code311.Tabler.Widgets.*
+ ↓
+Code311.Tabler.Mvc + Code311.Tabler.Razor
+ ↓
+Code311.Host
+```
+
+### Dependency rules
+- `Code311.Ui.Abstractions`
+ - No dependencies.
+- `Code311.Ui.Core`
+ - Depends only on `Code311.Ui.Abstractions`.
+- `Code311.Tabler.*`
+ - May depend on `Code311.Ui.*`; reverse forbidden.
+- `Code311.Persistence.EFCore`
+ - Depends primarily on `Code311.Ui.Abstractions`.
+ - Provider-agnostic EF Core design; no SQL Server lock-in.
+- `Code311.Licensing`
+ - Independent package.
+ - Startup validation required; runtime checks only at defined integration points.
+- `Code311.Host`
+ - Terminal application; never referenced by framework packages.
+ - Hybrid by default (MVC + Razor Pages), demonstrative only.
+
+### Tooling decision
+- Bundler v1: **esbuild** (predictable, low-overhead, deterministic static asset output for RCL/NuGet distribution).
+
+---
+
+## Responsibility Separation (Locked)
+
+| Area | `Code311.Tabler.Components` | `Code311.Tabler.Dashboard` | `Code311.Tabler.Widgets.*` |
+|---|---|---|---|
+| Core purpose | Reusable primitives + common composites | Dashboard shell and layout orchestration | Specialized widget integrations |
+| Base domain rendering | Yes | No | No |
+| Dashboard orchestration | No | Yes | No |
+| Widget lifecycle | No | Placement coordination | Yes |
+| Third-party JS adapters | Minimal/shared only | None/minimal | Primary ownership |
+| Public API style | Semantic neutral parameters | Dashboard composition semantics | Widget-specific semantic options/builders |
+
+---
+
+## 1) Expanded Component Coverage Matrix (`Code311.Tabler.Components`)
+
+> Parameter vocabulary is normalized to semantic terms (`tone`, `appearance`, `density`, `size`, `state`, `placement`, `layout`, `pinned`) and avoids CSS-framework tokens.
+
+| Component | Domain | Implementation Type | Primary Semantic Parameters | Internal Dependency Requirements | Phase | Classification |
+|---|---|---|---|---|---|---|
+| `Cd311Input` | Forms | TagHelper | `field`, `label`, `state`, `size`, `density`, `appearance`, `hint` | `IFieldMetadataProvider`, `IValidationStateResolver`, `ITablerFormClassMapper` | Phase 2 | Primitive |
+| `Cd311TextArea` | Forms | TagHelper | `field`, `state`, `size`, `density`, `appearance`, `hint`, `rows` | `IFieldMetadataProvider`, `IValidationStateResolver`, `ITablerFormClassMapper` | Phase 2 | Primitive |
+| `Cd311Select` | Forms | TagHelper | `field`, `state`, `size`, `density`, `appearance`, `placement`, `options` | `ISelectOptionSource`, `IValidationStateResolver`, `ITablerFormClassMapper` | Phase 2 | Primitive |
+| `Cd311Checkbox` | Forms | TagHelper | `field`, `state`, `size`, `appearance`, `label` | `IFieldMetadataProvider`, `IValidationStateResolver`, `ITablerFormClassMapper` | Phase 2 | Primitive |
+| `Cd311RadioGroup` | Forms | TagHelper | `field`, `state`, `size`, `density`, `layout`, `options` | `IChoiceOptionSource`, `IValidationStateResolver`, `ITablerFormClassMapper` | Phase 2 | Primitive |
+| `Cd311Switch` | Forms | TagHelper | `field`, `state`, `size`, `tone`, `appearance`, `label` | `IFieldMetadataProvider`, `IValidationStateResolver`, `ITablerFormClassMapper` | Phase 2 | Primitive |
+| `Cd311InputGroup` | Forms | TagHelper | `field`, `appearance`, `size`, `density`, `placement`, `state` | `IFieldMetadataProvider`, `IInputAdornmentResolver`, `ITablerFormClassMapper` | Phase 2 | Composite |
+| `Cd311DateInput` | Forms | TagHelper | `field`, `state`, `size`, `density`, `appearance`, `placement` | `IDateFormatProvider`, `IValidationStateResolver`, `ITablerFormClassMapper` | Phase 2 | Primitive |
+| `Cd311FileUpload` | Forms | ViewComponent | `field`, `state`, `size`, `appearance`, `layout`, `placement` | `IFileUploadPolicy`, `IValidationStateResolver`, `ITablerMediaClassMapper` | Phase 3 | Composite |
+| `Cd311SearchBox` | Forms | TagHelper | `field`, `state`, `size`, `appearance`, `placement`, `pinned` | `ISearchQueryBinder`, `IValidationStateResolver`, `ITablerFormClassMapper` | Phase 2 | Pattern helper |
+| `Cd311FieldGroup` | Forms | ViewComponent | `layout`, `density`, `appearance`, `state`, `placement`, `legend` | `IFormLayoutPolicy`, `IValidationSummaryComposer`, `ITablerLayoutClassMapper` | Phase 2 | Composite |
+| `Cd311ValidationSummary` | Forms | TagHelper | `tone`, `appearance`, `density`, `state`, `placement`, `scope` | `IValidationSummaryComposer`, `IValidationStateResolver`, `ITablerFeedbackClassMapper` | Phase 3 | Pattern helper |
+| `Cd311FormActionsBar` | Forms | ViewComponent | `layout`, `density`, `appearance`, `placement`, `pinned`, `state` | `IFormActionModelBuilder`, `ICommandPolicyResolver`, `ITablerLayoutClassMapper` | Phase 2 | Pattern helper |
+| `Cd311TopNav` | Navigation | ViewComponent | `layout`, `appearance`, `density`, `state`, `pinned`, `placement` | `INavigationModelProvider`, `INavStateResolver`, `ITablerNavigationClassMapper` | Phase 2 | Composite |
+| `Cd311SidebarNav` | Navigation | ViewComponent | `layout`, `appearance`, `density`, `state`, `placement`, `pinned` | `INavigationModelProvider`, `INavStateResolver`, `ITablerNavigationClassMapper` | Phase 2 | Composite |
+| `Cd311Breadcrumb` | Navigation | TagHelper | `appearance`, `density`, `size`, `placement`, `state` | `IBreadcrumbProvider`, `IRouteContextAccessor`, `ITablerNavigationClassMapper` | Phase 2 | Primitive |
+| `Cd311Pagination` | Navigation | TagHelper | `size`, `appearance`, `density`, `state`, `placement`, `layout` | `IPagingModelFactory`, `IPagingStateResolver`, `ITablerDataClassMapper` | Phase 2 | Primitive |
+| `Cd311Tabs` | Navigation | TagHelper | `layout`, `appearance`, `density`, `state`, `placement`, `pinned` | `ITabSetModelProvider`, `ITabStateResolver`, `ITablerNavigationClassMapper` | Phase 2 | Composite |
+| `Cd311DropdownMenu` | Navigation | ViewComponent | `placement`, `appearance`, `size`, `state`, `layout`, `pinned` | `IMenuModelProvider`, `ICommandPolicyResolver`, `ITablerNavigationClassMapper` | Phase 2 | Composite |
+| `Cd311CommandBar` | Navigation | ViewComponent | `layout`, `density`, `appearance`, `placement`, `pinned`, `state` | `ICommandModelProvider`, `ICommandPolicyResolver`, `ITablerNavigationClassMapper` | Phase 3 | Pattern helper |
+| `Cd311MenuGroup` | Navigation | TagHelper | `layout`, `appearance`, `density`, `state`, `placement` | `IMenuModelProvider`, `INavStateResolver`, `ITablerNavigationClassMapper` | Phase 2 | Composite |
+| `Cd311Container` | Layout | TagHelper | `layout`, `density`, `appearance`, `size`, `placement`, `pinned` | `ILayoutProfileResolver`, `IThemeProfileResolver`, `ITablerLayoutClassMapper` | Phase 2 | Primitive |
+| `Cd311Section` | Layout | TagHelper | `layout`, `density`, `appearance`, `state`, `placement`, `pinned` | `ILayoutProfileResolver`, `IThemeProfileResolver`, `ITablerLayoutClassMapper` | Phase 2 | Primitive |
+| `Cd311PageHeader` | Layout | ViewComponent | `layout`, `appearance`, `density`, `state`, `placement`, `pinned` | `IPageHeaderModelProvider`, `ICommandPolicyResolver`, `ITablerLayoutClassMapper` | Phase 2 | Composite |
+| `Cd311Card` | Layout | TagHelper | `tone`, `appearance`, `density`, `size`, `state`, `layout` | `ICardModelPolicy`, `IThemeProfileResolver`, `ITablerLayoutClassMapper` | Phase 2 | Primitive |
+| `Cd311Grid` | Layout | TagHelper | `layout`, `density`, `appearance`, `state`, `placement`, `size` | `IGridLayoutPolicy`, `ILayoutProfileResolver`, `ITablerLayoutClassMapper` | Phase 2 | Primitive |
+| `Cd311Stack` | Layout | TagHelper | `layout`, `density`, `appearance`, `state`, `placement`, `size` | `IStackLayoutPolicy`, `ILayoutProfileResolver`, `ITablerLayoutClassMapper` | Phase 2 | Primitive |
+| `Cd311Accordion` | Layout | ViewComponent | `layout`, `appearance`, `density`, `state`, `placement`, `pinned` | `IAccordionStateResolver`, `ILayoutProfileResolver`, `ITablerLayoutClassMapper` | Phase 2 | Composite |
+| `Cd311Drawer` | Layout | ViewComponent | `placement`, `layout`, `appearance`, `density`, `state`, `pinned` | `IDrawerStateCoordinator`, `ICommandPolicyResolver`, `ITablerLayoutClassMapper` | Phase 3 | Composite |
+| `Cd311TabsContainer` | Layout | TagHelper | `layout`, `appearance`, `density`, `state`, `placement`, `pinned` | `ITabSetModelProvider`, `ILayoutProfileResolver`, `ITablerLayoutClassMapper` | Phase 3 | Pattern helper |
+| `Cd311PanelLayout` | Layout | Service + render pattern | `layout`, `density`, `appearance`, `state`, `placement`, `pinned` | `IPanelLayoutComposer`, `ILayoutProfileResolver`, `ITablerLayoutClassMapper` | Phase 3 | Pattern helper |
+| `Cd311Alert` | Feedback | TagHelper | `tone`, `appearance`, `density`, `size`, `state`, `placement` | `IAlertQueue`, `IFeedbackSerializer`, `ITablerFeedbackClassMapper` | Phase 2 | Primitive |
+| `Cd311ToastHost` | Feedback | ViewComponent | `placement`, `appearance`, `density`, `state`, `size`, `pinned` | `IToastQueue`, `IToastRenderModelFactory`, `ITablerFeedbackClassMapper` | Phase 3 | Composite |
+| `Cd311Modal` | Feedback | ViewComponent | `placement`, `appearance`, `size`, `state`, `layout`, `pinned` | `IDialogOrchestrator`, `IDialogStateResolver`, `ITablerFeedbackClassMapper` | Phase 3 | Composite |
+| `Cd311ConfirmDialog` | Feedback | ViewComponent | `tone`, `appearance`, `size`, `state`, `placement`, `layout` | `IConfirmInteractionService`, `IDialogOrchestrator`, `ITablerFeedbackClassMapper` | Phase 3 | Pattern helper |
+| `Cd311PageAlertHost` | Feedback | Service + render pattern | `placement`, `appearance`, `density`, `state`, `layout`, `pinned` | `IPageAlertPipeline`, `IFeedbackSerializer`, `ITablerFeedbackClassMapper` | Phase 3 | Pattern helper |
+| `Cd311BusyOverlay` | Feedback | TagHelper | `appearance`, `density`, `state`, `placement`, `layout`, `pinned` | `IBusyStateService`, `ILoaderRenderModelFactory`, `ITablerFeedbackClassMapper` | Phase 3 | Pattern helper |
+| `Cd311BusyButton` | Feedback | TagHelper | `tone`, `appearance`, `size`, `state`, `placement`, `layout` | `IButtonStateCoordinator`, `IBusyStateService`, `ITablerFeedbackClassMapper` | Phase 3 | Pattern helper |
+| `Cd311LockScreen` | Feedback | ViewComponent | `layout`, `appearance`, `density`, `state`, `placement`, `pinned` | `ILockScreenPolicy`, `IThemeProfileResolver`, `ITablerLayoutClassMapper` | Phase 4 | Composite |
+| `Cd311PagePreloader` | Feedback | Service + render pattern | `appearance`, `density`, `state`, `placement`, `layout`, `pinned` | `IPreloadOrchestrator`, `ILoaderRenderModelFactory`, `ITablerFeedbackClassMapper` | Phase 3 | Pattern helper |
+| `Cd311Progress` | Feedback | TagHelper | `tone`, `appearance`, `size`, `state`, `placement`, `density` | `IProgressModelFactory`, `IFeedbackSerializer`, `ITablerFeedbackClassMapper` | Phase 2 | Primitive |
+| `Cd311Table` | Data | TagHelper | `layout`, `density`, `appearance`, `state`, `size`, `placement` | `ITableModelPolicy`, `ISortStateResolver`, `ITablerDataClassMapper` | Phase 2 | Primitive |
+| `Cd311DataGridShell` | Data | ViewComponent | `layout`, `density`, `appearance`, `state`, `placement`, `pinned` | `IDataGridModelBuilder`, `IFilterStateResolver`, `ITablerDataClassMapper` | Phase 3 | Composite |
+| `Cd311FilterToolbar` | Data | ViewComponent | `layout`, `density`, `appearance`, `state`, `placement`, `pinned` | `IFilterModelProvider`, `IQueryStateBinder`, `ITablerDataClassMapper` | Phase 3 | Pattern helper |
+| `Cd311Badge` | Data | TagHelper | `tone`, `appearance`, `size`, `state`, `placement`, `density` | `IBadgeStylePolicy`, `IThemeProfileResolver`, `ITablerDataClassMapper` | Phase 2 | Primitive |
+| `Cd311StatKpi` | Data | ViewComponent | `tone`, `appearance`, `density`, `size`, `state`, `layout` | `IStatModelProvider`, `ITrendIndicatorResolver`, `ITablerDataClassMapper` | Phase 2 | Composite |
+| `Cd311ListGroup` | Data | TagHelper | `layout`, `density`, `appearance`, `state`, `placement`, `size` | `IListModelProvider`, `ISelectionStateResolver`, `ITablerDataClassMapper` | Phase 2 | Primitive |
+| `Cd311KeyValueList` | Data | TagHelper | `layout`, `density`, `appearance`, `state`, `size`, `placement` | `IMetadataListFormatter`, `ILayoutProfileResolver`, `ITablerDataClassMapper` | Phase 2 | Primitive |
+| `Cd311EmptyState` | Data | TagHelper | `tone`, `appearance`, `density`, `state`, `placement`, `layout` | `IEmptyStatePolicy`, `ICommandPolicyResolver`, `ITablerDataClassMapper` | Phase 2 | Pattern helper |
+| `Cd311Avatar` | Media | TagHelper | `appearance`, `size`, `state`, `placement`, `density`, `tone` | `IAvatarModelPolicy`, `IMediaUrlResolver`, `ITablerMediaClassMapper` | Phase 2 | Primitive |
+| `Cd311Icon` | Media | TagHelper | `appearance`, `size`, `state`, `placement`, `tone`, `density` | `IIconRegistry`, `IIconResolver`, `ITablerMediaClassMapper` | Phase 2 | Primitive |
+| `Cd311Image` | Media | TagHelper | `appearance`, `size`, `state`, `placement`, `layout`, `density` | `IMediaUrlResolver`, `IMediaOptimizationPolicy`, `ITablerMediaClassMapper` | Phase 2 | Primitive |
+| `Cd311FilePreview` | Media | ViewComponent | `layout`, `appearance`, `size`, `state`, `placement`, `density` | `IFilePreviewModelBuilder`, `IMediaTypeClassifier`, `ITablerMediaClassMapper` | Phase 3 | Composite |
+| `Cd311ProfileBlock` | Media | ViewComponent | `layout`, `appearance`, `density`, `state`, `size`, `placement` | `IProfileSummaryProvider`, `IAvatarModelPolicy`, `ITablerMediaClassMapper` | Phase 3 | Composite |
+| `Cd311Banner` | Media | ViewComponent | `tone`, `appearance`, `density`, `state`, `placement`, `pinned` | `IBannerContentProvider`, `IThemeProfileResolver`, `ITablerMediaClassMapper` | Phase 3 | Pattern helper |
+
+---
+
+## 2) Host Integration Matrix
+
+### 2.1 Package matrix
+
+| Aspect | Shared Responsibilities (`Code311.Tabler.Mvc` + `Code311.Tabler.Razor`) | MVC-only (`Code311.Tabler.Mvc`) | Razor-only (`Code311.Tabler.Razor`) |
+|---|---|---|---|
+| Core registration | Register `Ui.Core`, `Tabler.Core`, `Tabler.Components`, `Tabler.Dashboard`, selected `Tabler.Widgets.*` | Same shared registration for MVC apps | Same shared registration for Razor Pages apps |
+| Feedback services integration | Wire alert/toast/dialog/busy pipelines to request lifecycle | Action-result feedback adapters and MVC model-state bridge | Handler-result feedback adapters and Razor handler-state bridge |
+| Loader/preloader integration | Register loader orchestration and preloader policy services | MVC filter hooks for action-start/action-complete loader transitions | Razor handler filters for page-handler loader transitions |
+| Theme system integration | Request-time theme resolution and preference application | Controller/view-context theme helpers | PageModel/view-data theme helpers |
+| Asset pipeline integration | Asset contributor registration, ordered manifests, deduplication | MVC view helper hooks for scoped asset injection | Razor layout/page helper hooks for scoped asset injection |
+| Licensing integration | Startup validation + bounded runtime checks at integration points | MVC filter/convention integration points | Razor filter/convention integration points |
+| Design neutrality guard | Keep semantic API surface; no Tabler token exposure outside internals | Same | Same |
+
+### 2.2 DI/service registration extensions
+
+- `AddCode311TablerMvc(...)`
+- `AddCode311TablerRazor(...)`
+- `AddCode311TablerDashboard(...)`
+- `AddCode311TablerWidgetsDataTables(...)`
+- `AddCode311TablerWidgetsCalendar(...)`
+- `AddCode311TablerWidgetsCharts(...)`
+- `AddCode311PersistenceEfCore(...)` (provider-agnostic configuration surface)
+- `AddCode311Licensing(...)`
+
+### 2.3 Filters / conventions / helpers / base types
+
+| Package | Filters | Conventions | Helpers | Base Types |
+|---|---|---|---|---|
+| `Code311.Tabler.Mvc` | `Code311FeedbackActionFilter`, `Code311BusyTransitionFilter`, `Code311ThemeContextFilter` | Controller CRUD UX convention set, model-state to feedback convention, view asset convention | `IHtmlHelper` semantic component helpers, feedback helper facade, theme helper facade | `Code311ControllerBase`, `Code311ViewModelBase` |
+| `Code311.Tabler.Razor` | `Code311PageFeedbackFilter`, `Code311BusyTransitionPageFilter`, `Code311ThemePageFilter` | Page folder UX conventions, handler feedback convention, page asset convention | `PageModel` semantic helpers, feedback helper facade, theme helper facade | `Code311PageModelBase`, `Code311RazorViewModelBase` |
+
+---
+
+## 3) New / Updated Architecture Decisions
+
+| Decision | Context | Chosen Option | Reasoning | Consequence |
+|---|---|---|---|---|
+| DR-0010: Semantic vocabulary normalization | Matrix drift risk from inconsistent parameter names | Standardize component vocabulary around `tone`, `appearance`, `density`, `size`, `state`, `placement`, `layout`, `pinned` | Improves API predictability and cross-design-system portability | API review checklist must enforce vocabulary |
+| DR-0011: Explicit internal dependency contracts | Prior matrix used vague dependency labels | Use named internal contracts/services per component line item | Clarifies package responsibilities and test seams | Requires maintaining contract catalog documentation |
+| DR-0012: Explicit implementation type taxonomy | Ambiguous type descriptions reduced precision | Restrict to: TagHelper, ViewComponent, HTML helper extension, Service + render pattern | Better implementation planning and ownership clarity | Architecture reviews must reject vague type labels |
+| DR-0013: Host adapter parity as governance rule | Risk of capability drift between MVC and Razor adapters | Define parity matrix for shared behaviors and stack-specific deltas | Preserves consistent developer experience | Requires parity tests and release gate checks |
+
+---
+
+## 4) Approval Gate
+
+Architecture deliverables finalized for this phase:
+1. Expanded Component Coverage Matrix
+2. Host Integration Matrix
+3. New architecture decisions
+
+Awaiting approval before any implementation work.
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..82dabbb
--- /dev/null
+++ b/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "10.0.100",
+ "rollForward": "latestFeature",
+ "allowPrerelease": true
+ }
+}
diff --git a/src/Code311.Host/.gitkeep b/src/Code311.Host/.gitkeep
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/Code311.Host/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/src/Code311.Host/Code311.Host.csproj b/src/Code311.Host/Code311.Host.csproj
new file mode 100644
index 0000000..4e118e2
--- /dev/null
+++ b/src/Code311.Host/Code311.Host.csproj
@@ -0,0 +1,28 @@
+
+
+ net10.0
+ Code311.Host
+ Code311.Host
+ Hybrid MVC + Razor demo host for Code311 framework packages.
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Code311.Host/Controllers/ArchitectureController.cs b/src/Code311.Host/Controllers/ArchitectureController.cs
new file mode 100644
index 0000000..4914859
--- /dev/null
+++ b/src/Code311.Host/Controllers/ArchitectureController.cs
@@ -0,0 +1,8 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace Code311.Host.Controllers;
+
+public sealed class ArchitectureController : Controller
+{
+ public IActionResult About() => View();
+}
diff --git a/src/Code311.Host/Controllers/ComponentsDemoController.cs b/src/Code311.Host/Controllers/ComponentsDemoController.cs
new file mode 100644
index 0000000..97633ff
--- /dev/null
+++ b/src/Code311.Host/Controllers/ComponentsDemoController.cs
@@ -0,0 +1,13 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace Code311.Host.Controllers;
+
+public sealed class ComponentsDemoController : Controller
+{
+ public IActionResult Forms() => View();
+ public IActionResult Navigation() => View();
+ public IActionResult Layout() => View();
+ public IActionResult Feedback() => View();
+ public IActionResult Data() => View();
+ public IActionResult Media() => View();
+}
diff --git a/src/Code311.Host/Controllers/DashboardController.cs b/src/Code311.Host/Controllers/DashboardController.cs
new file mode 100644
index 0000000..8a66fce
--- /dev/null
+++ b/src/Code311.Host/Controllers/DashboardController.cs
@@ -0,0 +1,15 @@
+using Code311.Host.Models;
+using Code311.Licensing.Validation;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Code311.Host.Controllers;
+
+public sealed class DashboardController(ILicenseFeatureGate featureGate) : Controller
+{
+ public async Task Index(CancellationToken cancellationToken)
+ {
+ var check = await featureGate.CheckFeatureAsync("dashboard.advanced", cancellationToken);
+ var model = new DashboardDemoViewModel(check.IsAllowed, check.Reason);
+ return View(model);
+ }
+}
diff --git a/src/Code311.Host/Controllers/HomeController.cs b/src/Code311.Host/Controllers/HomeController.cs
new file mode 100644
index 0000000..4fabf17
--- /dev/null
+++ b/src/Code311.Host/Controllers/HomeController.cs
@@ -0,0 +1,8 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace Code311.Host.Controllers;
+
+public sealed class HomeController : Controller
+{
+ public IActionResult Index() => View();
+}
diff --git a/src/Code311.Host/Controllers/PreferencesController.cs b/src/Code311.Host/Controllers/PreferencesController.cs
new file mode 100644
index 0000000..5b36c4e
--- /dev/null
+++ b/src/Code311.Host/Controllers/PreferencesController.cs
@@ -0,0 +1,25 @@
+using Code311.Host.Models;
+using Code311.Host.Services;
+using Code311.Ui.Abstractions.Semantics;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Code311.Host.Controllers;
+
+public sealed class PreferencesController(IPreferenceOrchestrator orchestrator) : Controller
+{
+ [HttpGet]
+ public async Task Index(CancellationToken cancellationToken)
+ {
+ var current = await orchestrator.GetCurrentAsync(cancellationToken);
+ return View(new PreferencesPageViewModel(current, string.Empty));
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Save(string theme, UiDensity density, int defaultPageSize, bool sidebarCollapsed, CancellationToken cancellationToken)
+ {
+ await orchestrator.SaveAsync(new PreferenceInputModel(theme, density, defaultPageSize, sidebarCollapsed), cancellationToken);
+ var current = await orchestrator.GetCurrentAsync(cancellationToken);
+ return View("Index", new PreferencesPageViewModel(current, "Preferences saved for demo-tenant/demo-user."));
+ }
+}
diff --git a/src/Code311.Host/Controllers/WidgetsController.cs b/src/Code311.Host/Controllers/WidgetsController.cs
new file mode 100644
index 0000000..52aed68
--- /dev/null
+++ b/src/Code311.Host/Controllers/WidgetsController.cs
@@ -0,0 +1,50 @@
+using Code311.Host.Models;
+using Code311.Tabler.Mvc.Assets;
+using Code311.Tabler.Widgets.Calendar.Options;
+using Code311.Tabler.Widgets.Calendar.Widgets;
+using Code311.Tabler.Widgets.Charts.Options;
+using Code311.Tabler.Widgets.Charts.Widgets;
+using Code311.Tabler.Widgets.DataTables.Options;
+using Code311.Tabler.Widgets.DataTables.Widgets;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Code311.Host.Controllers;
+
+public sealed class WidgetsController(ICode311AssetRequestStore assets) : Controller
+{
+ public IActionResult DataTables()
+ {
+ var widget = new DataTableWidgetSlot(
+ "widget.datatables.incidents",
+ "panel-body",
+ new DataTableWidgetOptionsBuilder().WithPageLength(25).EnableSearch(true).Build());
+
+ assets.AddWidgetAssets(widget);
+ var init = widget.CreateInitialization("datatable-demo");
+ return View(new WidgetDemoViewModel("DataTables", init.ElementId, init.OptionsJson));
+ }
+
+ public IActionResult Calendar()
+ {
+ var widget = new CalendarWidgetSlot(
+ "widget.calendar.operations",
+ "panel-body",
+ new CalendarWidgetOptionsBuilder().WithInitialView("dayGridMonth").ShowWeekends(true).Build());
+
+ assets.AddWidgetAssets(widget);
+ var init = widget.CreateInitialization("calendar-demo");
+ return View("WidgetPage", new WidgetDemoViewModel("Calendar", init.ElementId, init.OptionsJson));
+ }
+
+ public IActionResult Charts()
+ {
+ var widget = new ChartWidgetSlot(
+ "widget.charts.kpi",
+ "panel-body",
+ new ChartWidgetOptionsBuilder().WithType("line").ShowLegend(true).Build());
+
+ assets.AddWidgetAssets(widget);
+ var init = widget.CreateInitialization("charts-demo");
+ return View("WidgetPage", new WidgetDemoViewModel("Charts", init.ElementId, init.OptionsJson));
+ }
+}
diff --git a/src/Code311.Host/Models/HostViewModels.cs b/src/Code311.Host/Models/HostViewModels.cs
new file mode 100644
index 0000000..55c1b71
--- /dev/null
+++ b/src/Code311.Host/Models/HostViewModels.cs
@@ -0,0 +1,12 @@
+using Code311.Licensing.Models;
+using Code311.Ui.Abstractions.Preferences;
+
+namespace Code311.Host.Models;
+
+public sealed record DashboardDemoViewModel(bool AdvancedEnabled, string FeatureReason);
+
+public sealed record WidgetDemoViewModel(string WidgetName, string ElementId, string InitializationJson);
+
+public sealed record LicensingDiagnosticsViewModel(LicenseRuntimeStatus? Current, IReadOnlyList History);
+
+public sealed record PreferencesPageViewModel(UserUiPreference Current, string Message);
diff --git a/src/Code311.Host/Pages/Diagnostics/Licensing.cshtml b/src/Code311.Host/Pages/Diagnostics/Licensing.cshtml
new file mode 100644
index 0000000..753b262
--- /dev/null
+++ b/src/Code311.Host/Pages/Diagnostics/Licensing.cshtml
@@ -0,0 +1,17 @@
+@page
+@model Code311.Host.Pages.Diagnostics.LicensingModel
+
+
+
+Current status: @Model.Diagnostics.Current?.Level - @Model.Diagnostics.Current?.Message
+
+Status History
+
+@foreach (var item in Model.Diagnostics.History)
+{
+ - @item.OccurredAtUtc.ToString("u") | @item.Stage | @item.Level | @item.Code | @item.Message
+}
+
+
+Runtime Feature Probe
+dashboard.advanced => @Model.FeatureCheck.Feature allowed: @Model.FeatureCheck.IsAllowed (@Model.FeatureCheck.Reason)
diff --git a/src/Code311.Host/Pages/Diagnostics/Licensing.cshtml.cs b/src/Code311.Host/Pages/Diagnostics/Licensing.cshtml.cs
new file mode 100644
index 0000000..19afa18
--- /dev/null
+++ b/src/Code311.Host/Pages/Diagnostics/Licensing.cshtml.cs
@@ -0,0 +1,19 @@
+using Code311.Host.Models;
+using Code311.Licensing.Diagnostics;
+using Code311.Licensing.Validation;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace Code311.Host.Pages.Diagnostics;
+
+public sealed class LicensingModel(ILicensingStatusReporter reporter, ILicenseFeatureGate featureGate) : PageModel
+{
+ public LicensingDiagnosticsViewModel Diagnostics { get; private set; } = new(null, []);
+ public Code311.Licensing.Models.LicenseFeatureCheckResult FeatureCheck { get; private set; } =
+ new(false, "dashboard.advanced", Code311.Licensing.Models.LicenseStatusLevel.Error, "Not evaluated", null);
+
+ public async Task OnGetAsync(CancellationToken cancellationToken)
+ {
+ FeatureCheck = await featureGate.CheckFeatureAsync("dashboard.advanced", cancellationToken);
+ Diagnostics = new LicensingDiagnosticsViewModel(reporter.Current, reporter.GetHistory());
+ }
+}
diff --git a/src/Code311.Host/Pages/_ViewImports.cshtml b/src/Code311.Host/Pages/_ViewImports.cshtml
new file mode 100644
index 0000000..e8e2908
--- /dev/null
+++ b/src/Code311.Host/Pages/_ViewImports.cshtml
@@ -0,0 +1,3 @@
+@namespace Code311.Host.Pages
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
+@addTagHelper *, Code311.Tabler.Components
diff --git a/src/Code311.Host/Pages/_ViewStart.cshtml b/src/Code311.Host/Pages/_ViewStart.cshtml
new file mode 100644
index 0000000..a0f2b94
--- /dev/null
+++ b/src/Code311.Host/Pages/_ViewStart.cshtml
@@ -0,0 +1,3 @@
+@{
+ Layout = "/Views/Shared/_Layout.cshtml";
+}
diff --git a/src/Code311.Host/Program.cs b/src/Code311.Host/Program.cs
new file mode 100644
index 0000000..87c52a6
--- /dev/null
+++ b/src/Code311.Host/Program.cs
@@ -0,0 +1,93 @@
+using Code311.Host.Services;
+using Code311.Licensing.DependencyInjection;
+using Code311.Licensing.Models;
+using Code311.Licensing.Validation;
+using Code311.Persistence.EFCore;
+using Code311.Persistence.EFCore.DependencyInjection;
+using Code311.Tabler.Dashboard.DependencyInjection;
+using Code311.Tabler.Mvc.DependencyInjection;
+using Code311.Tabler.Razor.DependencyInjection;
+using Code311.Tabler.Widgets.Calendar.DependencyInjection;
+using Code311.Tabler.Widgets.Charts.DependencyInjection;
+using Code311.Tabler.Widgets.DataTables.DependencyInjection;
+using Code311.Ui.Abstractions.Preferences;
+using Microsoft.EntityFrameworkCore;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddControllersWithViews();
+builder.Services.AddRazorPages();
+
+// Explicit framework package registration for demonstration clarity.
+builder.Services.AddCode311TablerMvc();
+builder.Services.AddCode311TablerRazor();
+builder.Services.AddCode311TablerDashboard();
+builder.Services.AddCode311TablerDataTablesWidgets();
+builder.Services.AddCode311TablerCalendarWidgets();
+builder.Services.AddCode311TablerChartWidgets();
+
+// Host-local provider choice: SQLite for practical demo persistence.
+var connectionString = builder.Configuration.GetConnectionString("Code311Demo") ?? "Data Source=App_Data/code311-host-demo.db";
+Directory.CreateDirectory("App_Data");
+builder.Services.AddCode311PersistenceEfCore(options => options.UseSqlite(connectionString));
+
+builder.Services.AddCode311Licensing(options =>
+{
+ options.RequireValidLicenseAtStartup = true;
+ options.ExpiryWarningWindowDays = 30;
+});
+builder.Services.AddCode311InMemoryLicenseSource(new Code311License
+{
+ LicenseId = "demo-license-001",
+ CustomerName = "Code311 Demo Host",
+ Plan = "demo",
+ NotBeforeUtc = DateTimeOffset.UtcNow.AddDays(-1),
+ ExpiresUtc = DateTimeOffset.UtcNow.AddDays(90),
+ Features = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ "dashboard.basic",
+ "dashboard.advanced",
+ "widgets.datatables",
+ "widgets.calendar",
+ "widgets.charts"
+ }
+});
+
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+
+var app = builder.Build();
+
+using (var scope = app.Services.CreateScope())
+{
+ var db = scope.ServiceProvider.GetRequiredService();
+ await db.Database.EnsureCreatedAsync();
+
+ var startupValidator = scope.ServiceProvider.GetRequiredService();
+ try
+ {
+ await startupValidator.ValidateAtStartupAsync();
+ }
+ catch (InvalidOperationException ex)
+ {
+ throw new HostStartupLicenseException("Code311.Host startup license validation failed.", ex);
+ }
+}
+
+if (!app.Environment.IsDevelopment())
+{
+ app.UseExceptionHandler("/Home/Error");
+}
+
+app.UseStaticFiles();
+app.UseRouting();
+
+app.MapControllerRoute(
+ name: "default",
+ pattern: "{controller=Home}/{action=Index}/{id?}");
+
+app.MapRazorPages();
+
+await app.RunAsync();
+
+public partial class Program;
diff --git a/src/Code311.Host/Services/DemoUserContext.cs b/src/Code311.Host/Services/DemoUserContext.cs
new file mode 100644
index 0000000..50db2c8
--- /dev/null
+++ b/src/Code311.Host/Services/DemoUserContext.cs
@@ -0,0 +1,13 @@
+namespace Code311.Host.Services;
+
+public interface IDemoUserContext
+{
+ string TenantId { get; }
+ string UserId { get; }
+}
+
+public sealed class DemoUserContext : IDemoUserContext
+{
+ public string TenantId => "demo-tenant";
+ public string UserId => "demo-user";
+}
diff --git a/src/Code311.Host/Services/HostStartupLicenseException.cs b/src/Code311.Host/Services/HostStartupLicenseException.cs
new file mode 100644
index 0000000..f9da558
--- /dev/null
+++ b/src/Code311.Host/Services/HostStartupLicenseException.cs
@@ -0,0 +1,6 @@
+namespace Code311.Host.Services;
+
+///
+/// Represents host-local startup failure due to licensing validation.
+///
+public sealed class HostStartupLicenseException(string message, Exception innerException) : Exception(message, innerException);
diff --git a/src/Code311.Host/Services/PreferenceOrchestrator.cs b/src/Code311.Host/Services/PreferenceOrchestrator.cs
new file mode 100644
index 0000000..f554b54
--- /dev/null
+++ b/src/Code311.Host/Services/PreferenceOrchestrator.cs
@@ -0,0 +1,48 @@
+using Code311.Ui.Abstractions.Preferences;
+using Code311.Ui.Abstractions.Semantics;
+
+namespace Code311.Host.Services;
+
+public sealed record PreferenceInputModel(string Theme, UiDensity Density, int DefaultPageSize, bool SidebarCollapsed);
+
+public interface IPreferenceOrchestrator
+{
+ Task GetCurrentAsync(CancellationToken cancellationToken = default);
+ Task SaveAsync(PreferenceInputModel input, CancellationToken cancellationToken = default);
+}
+
+public sealed class PreferenceOrchestrator(IUserUiPreferenceStore store, IDemoUserContext userContext) : IPreferenceOrchestrator
+{
+ public async Task GetCurrentAsync(CancellationToken cancellationToken = default)
+ {
+ var existing = await store.GetAsync(userContext.TenantId, userContext.UserId, cancellationToken);
+ return existing ?? new UserUiPreference
+ {
+ TenantId = userContext.TenantId,
+ UserId = userContext.UserId,
+ Theme = "default",
+ Density = UiDensity.Comfortable,
+ DefaultPageSize = 25,
+ SidebarCollapsed = false,
+ Language = "en-US",
+ TimeZone = "UTC"
+ };
+ }
+
+ public async Task SaveAsync(PreferenceInputModel input, CancellationToken cancellationToken = default)
+ {
+ var model = new UserUiPreference
+ {
+ TenantId = userContext.TenantId,
+ UserId = userContext.UserId,
+ Theme = input.Theme,
+ Density = input.Density,
+ DefaultPageSize = input.DefaultPageSize,
+ SidebarCollapsed = input.SidebarCollapsed,
+ Language = "en-US",
+ TimeZone = "UTC"
+ };
+
+ await store.UpsertAsync(model, cancellationToken);
+ }
+}
diff --git a/src/Code311.Host/Views/Architecture/About.cshtml b/src/Code311.Host/Views/Architecture/About.cshtml
new file mode 100644
index 0000000..c9201a6
--- /dev/null
+++ b/src/Code311.Host/Views/Architecture/About.cshtml
@@ -0,0 +1,7 @@
+
+Code311.Host is the official hybrid MVC + Razor integration portal for framework showcase and documentation alignment.
+
+ - Framework packages remain isolated from host concerns.
+ - Host owns provider binding (SQLite demo persistence).
+ - Host owns startup licensing invocation and diagnostics exposure.
+
diff --git a/src/Code311.Host/Views/ComponentsDemo/Data.cshtml b/src/Code311.Host/Views/ComponentsDemo/Data.cshtml
new file mode 100644
index 0000000..8daedb0
--- /dev/null
+++ b/src/Code311.Host/Views/ComponentsDemo/Data.cshtml
@@ -0,0 +1,2 @@
+
+Data primitives are showcased in Widgets/DataTables page with runtime widget bootstrapping.
diff --git a/src/Code311.Host/Views/ComponentsDemo/Feedback.cshtml b/src/Code311.Host/Views/ComponentsDemo/Feedback.cshtml
new file mode 100644
index 0000000..ed20767
--- /dev/null
+++ b/src/Code311.Host/Views/ComponentsDemo/Feedback.cshtml
@@ -0,0 +1,2 @@
+
+Feedback channel and request stores are wired by Tabler MVC adapter integration.
diff --git a/src/Code311.Host/Views/ComponentsDemo/Forms.cshtml b/src/Code311.Host/Views/ComponentsDemo/Forms.cshtml
new file mode 100644
index 0000000..b00d6b4
--- /dev/null
+++ b/src/Code311.Host/Views/ComponentsDemo/Forms.cshtml
@@ -0,0 +1,11 @@
+
+
+
+
+
+ Navigation |
+ Layout |
+ Feedback |
+ Data |
+ Media
+
diff --git a/src/Code311.Host/Views/ComponentsDemo/Layout.cshtml b/src/Code311.Host/Views/ComponentsDemo/Layout.cshtml
new file mode 100644
index 0000000..1bd0273
--- /dev/null
+++ b/src/Code311.Host/Views/ComponentsDemo/Layout.cshtml
@@ -0,0 +1,5 @@
+
+
+ Layout card A
+ Layout card B
+
diff --git a/src/Code311.Host/Views/ComponentsDemo/Media.cshtml b/src/Code311.Host/Views/ComponentsDemo/Media.cshtml
new file mode 100644
index 0000000..13056b0
--- /dev/null
+++ b/src/Code311.Host/Views/ComponentsDemo/Media.cshtml
@@ -0,0 +1,2 @@
+
+Media components can be introduced similarly; this page exists for section completeness.
diff --git a/src/Code311.Host/Views/ComponentsDemo/Navigation.cshtml b/src/Code311.Host/Views/ComponentsDemo/Navigation.cshtml
new file mode 100644
index 0000000..e605878
--- /dev/null
+++ b/src/Code311.Host/Views/ComponentsDemo/Navigation.cshtml
@@ -0,0 +1,2 @@
+
+Navigation semantics are demonstrated via host menu and component package registration.
diff --git a/src/Code311.Host/Views/Dashboard/Index.cshtml b/src/Code311.Host/Views/Dashboard/Index.cshtml
new file mode 100644
index 0000000..94b824e
--- /dev/null
+++ b/src/Code311.Host/Views/Dashboard/Index.cshtml
@@ -0,0 +1,15 @@
+@model DashboardDemoViewModel
+@using Code311.Tabler.Dashboard.Models
+@using Code311.Ui.Abstractions.Semantics
+
+
+@{
+ var panel = new DashboardPanelModel(
+ PanelKey: "panel-demo",
+ Title: "Operational KPI",
+ BodyHtml: Model.AdvancedEnabled ? "Advanced dashboard feature is licensed." : "Advanced feature unavailable.",
+ Tone: Model.AdvancedEnabled ? UiTone.Success : UiTone.Warning);
+}
+
+@await Component.InvokeAsync("Cd311MetricCard", panel)
+Feature gate result: @Model.FeatureReason
diff --git a/src/Code311.Host/Views/Home/Index.cshtml b/src/Code311.Host/Views/Home/Index.cshtml
new file mode 100644
index 0000000..48d465f
--- /dev/null
+++ b/src/Code311.Host/Views/Home/Index.cshtml
@@ -0,0 +1,12 @@
+@{
+ ViewData["Title"] = "Home";
+}
+
+
+This is the official hybrid MVC + Razor demo host for Code311.
+
+ - MVC controllers and views for Components, Dashboard, Widgets, Preferences, Architecture.
+ - Razor Page for Licensing Diagnostics.
+ - SQLite-backed persisted UI preferences for demo tenant/user.
+ - Explicit startup license validation and runtime feature gate checks.
+
diff --git a/src/Code311.Host/Views/Preferences/Index.cshtml b/src/Code311.Host/Views/Preferences/Index.cshtml
new file mode 100644
index 0000000..3a8dde2
--- /dev/null
+++ b/src/Code311.Host/Views/Preferences/Index.cshtml
@@ -0,0 +1,24 @@
+@model PreferencesPageViewModel
+
+
+@if (!string.IsNullOrWhiteSpace(Model.Message))
+{
+ @Model.Message
+}
+
+
+
+Current persisted scope: tenant @Model.Current.TenantId, user @Model.Current.UserId.
diff --git a/src/Code311.Host/Views/Shared/_Layout.cshtml b/src/Code311.Host/Views/Shared/_Layout.cshtml
new file mode 100644
index 0000000..52b6d90
--- /dev/null
+++ b/src/Code311.Host/Views/Shared/_Layout.cshtml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Code311.Host Demo
+
+
+
+
+
+ @RenderBody()
+
+
+
+
+
diff --git a/src/Code311.Host/Views/Widgets/DataTables.cshtml b/src/Code311.Host/Views/Widgets/DataTables.cshtml
new file mode 100644
index 0000000..b82451f
--- /dev/null
+++ b/src/Code311.Host/Views/Widgets/DataTables.cshtml
@@ -0,0 +1,8 @@
+@model WidgetDemoViewModel
+
+DataTables widget mount point
+@Model.InitializationJson
+
+ Calendar |
+ Charts
+
diff --git a/src/Code311.Host/Views/Widgets/WidgetPage.cshtml b/src/Code311.Host/Views/Widgets/WidgetPage.cshtml
new file mode 100644
index 0000000..15de5a8
--- /dev/null
+++ b/src/Code311.Host/Views/Widgets/WidgetPage.cshtml
@@ -0,0 +1,9 @@
+@model WidgetDemoViewModel
+
+@Model.WidgetName widget mount point
+@Model.InitializationJson
+
+ DataTables |
+ Calendar |
+ Charts
+
diff --git a/src/Code311.Host/Views/_ViewImports.cshtml b/src/Code311.Host/Views/_ViewImports.cshtml
new file mode 100644
index 0000000..77add13
--- /dev/null
+++ b/src/Code311.Host/Views/_ViewImports.cshtml
@@ -0,0 +1,5 @@
+@using Code311.Host
+@using Code311.Host.Models
+@using Code311.Ui.Abstractions.Semantics
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
+@addTagHelper *, Code311.Tabler.Components
diff --git a/src/Code311.Host/Views/_ViewStart.cshtml b/src/Code311.Host/Views/_ViewStart.cshtml
new file mode 100644
index 0000000..820a2f6
--- /dev/null
+++ b/src/Code311.Host/Views/_ViewStart.cshtml
@@ -0,0 +1,3 @@
+@{
+ Layout = "_Layout";
+}
diff --git a/src/Code311.Host/appsettings.json b/src/Code311.Host/appsettings.json
new file mode 100644
index 0000000..94f8d3f
--- /dev/null
+++ b/src/Code311.Host/appsettings.json
@@ -0,0 +1,5 @@
+{
+ "ConnectionStrings": {
+ "Code311Demo": "Data Source=App_Data/code311-host-demo.db"
+ }
+}
diff --git a/src/Code311.Licensing/.gitkeep b/src/Code311.Licensing/.gitkeep
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/Code311.Licensing/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/src/Code311.Licensing/Code311.Licensing.csproj b/src/Code311.Licensing/Code311.Licensing.csproj
new file mode 100644
index 0000000..dca7175
--- /dev/null
+++ b/src/Code311.Licensing/Code311.Licensing.csproj
@@ -0,0 +1,13 @@
+
+
+ net10.0
+ Code311.Licensing
+ Code311.Licensing
+ Hybrid licensing runtime for startup validation and bounded feature checks.
+ Code311.Licensing
+
+
+
+
+
+
diff --git a/src/Code311.Licensing/DependencyInjection/ServiceCollectionExtensions.cs b/src/Code311.Licensing/DependencyInjection/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..c900629
--- /dev/null
+++ b/src/Code311.Licensing/DependencyInjection/ServiceCollectionExtensions.cs
@@ -0,0 +1,48 @@
+using Code311.Licensing.Diagnostics;
+using Code311.Licensing.Models;
+using Code311.Licensing.Sources;
+using Code311.Licensing.Validation;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Code311.Licensing.DependencyInjection;
+
+///
+/// Provides registration helpers for Code311 licensing services.
+///
+public static class ServiceCollectionExtensions
+{
+ ///
+ /// Registers Code311 licensing services and an environment-variable-based source.
+ ///
+ public static IServiceCollection AddCode311Licensing(
+ this IServiceCollection services,
+ Action? configure = null,
+ string environmentVariableName = "CODE311_LICENSE_JSON")
+ {
+ ArgumentNullException.ThrowIfNull(services);
+
+ var options = new LicensingOptions();
+ configure?.Invoke(options);
+
+ services.TryAddSingleton(options);
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.TryAddSingleton(_ => new EnvironmentVariableLicenseSource(environmentVariableName));
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+
+ return services;
+ }
+
+ ///
+ /// Replaces the registered source with an in-memory payload.
+ ///
+ public static IServiceCollection AddCode311InMemoryLicenseSource(this IServiceCollection services, Code311License? license)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+
+ services.Replace(ServiceDescriptor.Singleton(_ => new InMemoryLicenseSource(license)));
+ return services;
+ }
+}
diff --git a/src/Code311.Licensing/Diagnostics/LicensingDiagnostics.cs b/src/Code311.Licensing/Diagnostics/LicensingDiagnostics.cs
new file mode 100644
index 0000000..7a7a0e1
--- /dev/null
+++ b/src/Code311.Licensing/Diagnostics/LicensingDiagnostics.cs
@@ -0,0 +1,29 @@
+using Code311.Licensing.Models;
+
+namespace Code311.Licensing.Diagnostics;
+
+///
+/// Reports and exposes licensing runtime status.
+///
+public interface ILicensingStatusReporter
+{
+ void Report(LicenseRuntimeStatus status);
+ LicenseRuntimeStatus? Current { get; }
+ IReadOnlyList GetHistory();
+}
+
+public sealed class InMemoryLicensingStatusReporter : ILicensingStatusReporter
+{
+ private readonly List _history = [];
+
+ public LicenseRuntimeStatus? Current { get; private set; }
+
+ public void Report(LicenseRuntimeStatus status)
+ {
+ ArgumentNullException.ThrowIfNull(status);
+ Current = status;
+ _history.Add(status);
+ }
+
+ public IReadOnlyList GetHistory() => _history;
+}
diff --git a/src/Code311.Licensing/Models/LicenseModels.cs b/src/Code311.Licensing/Models/LicenseModels.cs
new file mode 100644
index 0000000..a8a70d4
--- /dev/null
+++ b/src/Code311.Licensing/Models/LicenseModels.cs
@@ -0,0 +1,49 @@
+namespace Code311.Licensing.Models;
+
+///
+/// Represents a parsed Code311 license payload.
+///
+public sealed record Code311License
+{
+ public required string LicenseId { get; init; }
+ public required string CustomerName { get; init; }
+ public string Plan { get; init; } = "standard";
+ public DateTimeOffset? NotBeforeUtc { get; init; }
+ public DateTimeOffset? ExpiresUtc { get; init; }
+ public IReadOnlySet Features { get; init; } = new HashSet(StringComparer.OrdinalIgnoreCase);
+}
+
+///
+/// Configuration options for licensing behavior.
+///
+public sealed class LicensingOptions
+{
+ ///
+ /// Indicates whether startup validation is mandatory.
+ ///
+ public bool RequireValidLicenseAtStartup { get; set; } = true;
+
+ ///
+ /// Number of days before expiry where validation emits warning status.
+ ///
+ public int ExpiryWarningWindowDays { get; set; } = 14;
+}
+
+///
+/// Indicates licensing status severity at runtime.
+///
+public enum LicenseStatusLevel
+{
+ Valid,
+ Warning,
+ Error
+}
+
+///
+/// Describes lifecycle stage where a license status was observed.
+///
+public enum LicenseCheckStage
+{
+ Startup,
+ RuntimeFeature
+}
diff --git a/src/Code311.Licensing/Models/LicenseStatusModels.cs b/src/Code311.Licensing/Models/LicenseStatusModels.cs
new file mode 100644
index 0000000..8f1e2c9
--- /dev/null
+++ b/src/Code311.Licensing/Models/LicenseStatusModels.cs
@@ -0,0 +1,35 @@
+namespace Code311.Licensing.Models;
+
+///
+/// Represents a machine-readable status item produced by licensing operations.
+///
+public sealed record LicenseStatusItem(LicenseStatusLevel Level, string Code, string Message);
+
+///
+/// Represents the result of license validation.
+///
+public sealed record LicenseValidationResult(
+ bool IsValid,
+ LicenseStatusLevel OverallLevel,
+ IReadOnlyList Items,
+ Code311License? License);
+
+///
+/// Represents a bounded runtime feature check result.
+///
+public sealed record LicenseFeatureCheckResult(
+ bool IsAllowed,
+ string Feature,
+ LicenseStatusLevel Level,
+ string Reason,
+ Code311License? License);
+
+///
+/// Represents reported runtime status suitable for host diagnostics surfaces.
+///
+public sealed record LicenseRuntimeStatus(
+ DateTimeOffset OccurredAtUtc,
+ LicenseCheckStage Stage,
+ LicenseStatusLevel Level,
+ string Code,
+ string Message);
diff --git a/src/Code311.Licensing/Sources/LicenseSources.cs b/src/Code311.Licensing/Sources/LicenseSources.cs
new file mode 100644
index 0000000..0921bed
--- /dev/null
+++ b/src/Code311.Licensing/Sources/LicenseSources.cs
@@ -0,0 +1,41 @@
+using System.Text.Json;
+using Code311.Licensing.Models;
+
+namespace Code311.Licensing.Sources;
+
+///
+/// Resolves a license payload from a configured source.
+///
+public interface ILicenseSource
+{
+ Task GetLicenseAsync(CancellationToken cancellationToken = default);
+}
+
+///
+/// In-memory source suitable for tests and deterministic bootstrapping.
+///
+public sealed class InMemoryLicenseSource(Code311License? license) : ILicenseSource
+{
+ public Task GetLicenseAsync(CancellationToken cancellationToken = default)
+ => Task.FromResult(license);
+}
+
+///
+/// Reads a license payload from an environment variable containing JSON.
+///
+public sealed class EnvironmentVariableLicenseSource(string variableName) : ILicenseSource
+{
+ public Task GetLicenseAsync(CancellationToken cancellationToken = default)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(variableName);
+
+ var payload = Environment.GetEnvironmentVariable(variableName);
+ if (string.IsNullOrWhiteSpace(payload))
+ {
+ return Task.FromResult(null);
+ }
+
+ var model = JsonSerializer.Deserialize(payload);
+ return Task.FromResult(model);
+ }
+}
diff --git a/src/Code311.Licensing/Validation/LicenseValidationServices.cs b/src/Code311.Licensing/Validation/LicenseValidationServices.cs
new file mode 100644
index 0000000..2a3f626
--- /dev/null
+++ b/src/Code311.Licensing/Validation/LicenseValidationServices.cs
@@ -0,0 +1,129 @@
+using Code311.Licensing.Diagnostics;
+using Code311.Licensing.Models;
+using Code311.Licensing.Sources;
+
+namespace Code311.Licensing.Validation;
+
+///
+/// Validates license payload semantics.
+///
+public interface ILicenseValidator
+{
+ LicenseValidationResult Validate(Code311License? license, DateTimeOffset nowUtc, LicensingOptions options);
+}
+
+///
+/// Provides explicit startup validation flow.
+///
+public interface IStartupLicenseValidator
+{
+ Task ValidateAtStartupAsync(CancellationToken cancellationToken = default);
+}
+
+///
+/// Provides bounded runtime feature-level checks for integration points.
+///
+public interface ILicenseFeatureGate
+{
+ Task CheckFeatureAsync(string feature, CancellationToken cancellationToken = default);
+}
+
+public sealed class DefaultLicenseValidator : ILicenseValidator
+{
+ public LicenseValidationResult Validate(Code311License? license, DateTimeOffset nowUtc, LicensingOptions options)
+ {
+ var items = new List();
+
+ if (license is null)
+ {
+ items.Add(new LicenseStatusItem(LicenseStatusLevel.Error, "license.missing", "No license payload could be resolved."));
+ return new LicenseValidationResult(false, LicenseStatusLevel.Error, items, null);
+ }
+
+ if (license.NotBeforeUtc.HasValue && nowUtc < license.NotBeforeUtc.Value)
+ {
+ items.Add(new LicenseStatusItem(LicenseStatusLevel.Error, "license.not_before", "License is not active yet."));
+ }
+
+ if (license.ExpiresUtc.HasValue)
+ {
+ if (nowUtc >= license.ExpiresUtc.Value)
+ {
+ items.Add(new LicenseStatusItem(LicenseStatusLevel.Error, "license.expired", "License has expired."));
+ }
+ else if (nowUtc >= license.ExpiresUtc.Value.AddDays(-Math.Abs(options.ExpiryWarningWindowDays)))
+ {
+ items.Add(new LicenseStatusItem(LicenseStatusLevel.Warning, "license.expiring_soon", "License is approaching expiry."));
+ }
+ }
+
+ if (items.All(x => x.Level != LicenseStatusLevel.Error))
+ {
+ items.Add(new LicenseStatusItem(LicenseStatusLevel.Valid, "license.valid", "License is valid."));
+ }
+
+ var overall = items.Any(x => x.Level == LicenseStatusLevel.Error)
+ ? LicenseStatusLevel.Error
+ : items.Any(x => x.Level == LicenseStatusLevel.Warning)
+ ? LicenseStatusLevel.Warning
+ : LicenseStatusLevel.Valid;
+
+ return new LicenseValidationResult(overall != LicenseStatusLevel.Error, overall, items, license);
+ }
+}
+
+public sealed class StartupLicenseValidator(
+ ILicenseSource source,
+ ILicenseValidator validator,
+ ILicensingStatusReporter reporter,
+ LicensingOptions options) : IStartupLicenseValidator
+{
+ public async Task ValidateAtStartupAsync(CancellationToken cancellationToken = default)
+ {
+ var license = await source.GetLicenseAsync(cancellationToken).ConfigureAwait(false);
+ var result = validator.Validate(license, DateTimeOffset.UtcNow, options);
+
+ var top = result.Items.FirstOrDefault() ?? new LicenseStatusItem(result.OverallLevel, "license.status", "License status generated.");
+ reporter.Report(new LicenseRuntimeStatus(DateTimeOffset.UtcNow, LicenseCheckStage.Startup, result.OverallLevel, top.Code, top.Message));
+
+ if (options.RequireValidLicenseAtStartup && !result.IsValid)
+ {
+ throw new InvalidOperationException("Code311 startup license validation failed.");
+ }
+
+ return result;
+ }
+}
+
+public sealed class LicenseFeatureGate(
+ ILicenseSource source,
+ ILicenseValidator validator,
+ ILicensingStatusReporter reporter,
+ LicensingOptions options) : ILicenseFeatureGate
+{
+ public async Task CheckFeatureAsync(string feature, CancellationToken cancellationToken = default)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(feature);
+
+ var license = await source.GetLicenseAsync(cancellationToken).ConfigureAwait(false);
+ var validation = validator.Validate(license, DateTimeOffset.UtcNow, options);
+
+ if (!validation.IsValid || validation.License is null)
+ {
+ var denied = new LicenseFeatureCheckResult(false, feature, LicenseStatusLevel.Error, "License invalid for feature check.", license);
+ reporter.Report(new LicenseRuntimeStatus(DateTimeOffset.UtcNow, LicenseCheckStage.RuntimeFeature, denied.Level, "feature.denied.invalid_license", denied.Reason));
+ return denied;
+ }
+
+ if (!validation.License.Features.Contains(feature))
+ {
+ var denied = new LicenseFeatureCheckResult(false, feature, LicenseStatusLevel.Warning, "Feature not covered by current license.", validation.License);
+ reporter.Report(new LicenseRuntimeStatus(DateTimeOffset.UtcNow, LicenseCheckStage.RuntimeFeature, denied.Level, "feature.denied.not_licensed", denied.Reason));
+ return denied;
+ }
+
+ var allowed = new LicenseFeatureCheckResult(true, feature, validation.OverallLevel, "Feature is licensed.", validation.License);
+ reporter.Report(new LicenseRuntimeStatus(DateTimeOffset.UtcNow, LicenseCheckStage.RuntimeFeature, allowed.Level, "feature.allowed", allowed.Reason));
+ return allowed;
+ }
+}
diff --git a/src/Code311.Persistence.EFCore/.gitkeep b/src/Code311.Persistence.EFCore/.gitkeep
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/Code311.Persistence.EFCore/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/src/Code311.Persistence.EFCore/Code311.Persistence.EFCore.csproj b/src/Code311.Persistence.EFCore/Code311.Persistence.EFCore.csproj
new file mode 100644
index 0000000..dab7ebc
--- /dev/null
+++ b/src/Code311.Persistence.EFCore/Code311.Persistence.EFCore.csproj
@@ -0,0 +1,19 @@
+
+
+ net10.0
+ Code311.Persistence.EFCore
+ Code311.Persistence.EFCore
+ Provider-agnostic EF Core persistence for Code311 UI preference storage.
+ Code311.Persistence.EFCore
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Code311.Persistence.EFCore/Code311PreferenceDbContext.cs b/src/Code311.Persistence.EFCore/Code311PreferenceDbContext.cs
new file mode 100644
index 0000000..1ada373
--- /dev/null
+++ b/src/Code311.Persistence.EFCore/Code311PreferenceDbContext.cs
@@ -0,0 +1,19 @@
+using Code311.Persistence.EFCore.Entities;
+using Code311.Persistence.EFCore.Extensions;
+using Microsoft.EntityFrameworkCore;
+
+namespace Code311.Persistence.EFCore;
+
+///
+/// Default EF Core DbContext for Code311 persistence features.
+///
+public sealed class Code311PreferenceDbContext(DbContextOptions options) : DbContext(options)
+{
+ public DbSet UserUiPreferences => Set();
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.ApplyCode311PreferenceStorage();
+ base.OnModelCreating(modelBuilder);
+ }
+}
diff --git a/src/Code311.Persistence.EFCore/DependencyInjection/ServiceCollectionExtensions.cs b/src/Code311.Persistence.EFCore/DependencyInjection/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..d45cbe5
--- /dev/null
+++ b/src/Code311.Persistence.EFCore/DependencyInjection/ServiceCollectionExtensions.cs
@@ -0,0 +1,31 @@
+using Code311.Persistence.EFCore.Stores;
+using Code311.Ui.Abstractions.Preferences;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Code311.Persistence.EFCore.DependencyInjection;
+
+///
+/// Provides provider-agnostic service registration for Code311 EF Core persistence.
+///
+public static class ServiceCollectionExtensions
+{
+ ///
+ /// Registers the default Code311 preference DbContext and EF-based preference store.
+ ///
+ /// The service collection.
+ /// Provider-specific DbContext options configuration.
+ public static IServiceCollection AddCode311PersistenceEfCore(
+ this IServiceCollection services,
+ Action configureDbContext)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(configureDbContext);
+
+ services.AddDbContext(configureDbContext);
+ services.TryAddScoped();
+
+ return services;
+ }
+}
diff --git a/src/Code311.Persistence.EFCore/Entities/UserUiPreferenceEntity.cs b/src/Code311.Persistence.EFCore/Entities/UserUiPreferenceEntity.cs
new file mode 100644
index 0000000..123a32e
--- /dev/null
+++ b/src/Code311.Persistence.EFCore/Entities/UserUiPreferenceEntity.cs
@@ -0,0 +1,19 @@
+using Code311.Ui.Abstractions.Semantics;
+
+namespace Code311.Persistence.EFCore.Entities;
+
+///
+/// EF Core persistence model for a tenant-scoped user UI preference record.
+///
+public sealed class UserUiPreferenceEntity
+{
+ public required string TenantId { get; set; }
+ public required string UserId { get; set; }
+ public required string Theme { get; set; }
+ public UiDensity Density { get; set; }
+ public bool SidebarCollapsed { get; set; }
+ public int DefaultPageSize { get; set; }
+ public string Language { get; set; } = "en";
+ public string TimeZone { get; set; } = "UTC";
+ public DateTimeOffset UpdatedAt { get; set; }
+}
diff --git a/src/Code311.Persistence.EFCore/Extensions/ModelBuilderExtensions.cs b/src/Code311.Persistence.EFCore/Extensions/ModelBuilderExtensions.cs
new file mode 100644
index 0000000..2139c74
--- /dev/null
+++ b/src/Code311.Persistence.EFCore/Extensions/ModelBuilderExtensions.cs
@@ -0,0 +1,20 @@
+using Code311.Persistence.EFCore.Mapping;
+using Microsoft.EntityFrameworkCore;
+
+namespace Code311.Persistence.EFCore.Extensions;
+
+///
+/// Provides provider-agnostic model configuration helpers for Code311 preference entities.
+///
+public static class ModelBuilderExtensions
+{
+ ///
+ /// Applies Code311 UI preference persistence mappings to a model builder.
+ ///
+ public static ModelBuilder ApplyCode311PreferenceStorage(this ModelBuilder modelBuilder)
+ {
+ ArgumentNullException.ThrowIfNull(modelBuilder);
+ modelBuilder.ApplyConfiguration(new UserUiPreferenceEntityConfiguration());
+ return modelBuilder;
+ }
+}
diff --git a/src/Code311.Persistence.EFCore/Mapping/UserUiPreferenceEntityConfiguration.cs b/src/Code311.Persistence.EFCore/Mapping/UserUiPreferenceEntityConfiguration.cs
new file mode 100644
index 0000000..5a56d89
--- /dev/null
+++ b/src/Code311.Persistence.EFCore/Mapping/UserUiPreferenceEntityConfiguration.cs
@@ -0,0 +1,46 @@
+using Code311.Persistence.EFCore.Entities;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace Code311.Persistence.EFCore.Mapping;
+
+internal sealed class UserUiPreferenceEntityConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("UserUiPreferences");
+
+ builder.HasKey(x => new { x.TenantId, x.UserId });
+
+ builder.Property(x => x.TenantId)
+ .HasMaxLength(128)
+ .IsRequired();
+
+ builder.Property(x => x.UserId)
+ .HasMaxLength(256)
+ .IsRequired();
+
+ builder.Property(x => x.Theme)
+ .HasMaxLength(128)
+ .IsRequired();
+
+ builder.Property(x => x.Density)
+ .HasConversion()
+ .HasMaxLength(32)
+ .IsRequired();
+
+ builder.Property(x => x.DefaultPageSize)
+ .IsRequired();
+
+ builder.Property(x => x.Language)
+ .HasMaxLength(32)
+ .IsRequired();
+
+ builder.Property(x => x.TimeZone)
+ .HasMaxLength(128)
+ .IsRequired();
+
+ builder.Property(x => x.UpdatedAt)
+ .IsRequired();
+ }
+}
diff --git a/src/Code311.Persistence.EFCore/Stores/EfCoreUserUiPreferenceStore.cs b/src/Code311.Persistence.EFCore/Stores/EfCoreUserUiPreferenceStore.cs
new file mode 100644
index 0000000..2d43378
--- /dev/null
+++ b/src/Code311.Persistence.EFCore/Stores/EfCoreUserUiPreferenceStore.cs
@@ -0,0 +1,83 @@
+using Code311.Persistence.EFCore.Entities;
+using Code311.Ui.Abstractions.Preferences;
+using Microsoft.EntityFrameworkCore;
+
+namespace Code311.Persistence.EFCore.Stores;
+
+///
+/// Provider-agnostic EF Core implementation of .
+///
+public sealed class EfCoreUserUiPreferenceStore(Code311PreferenceDbContext dbContext) : IUserUiPreferenceStore
+{
+ public async Task GetAsync(string tenantId, string userId, CancellationToken cancellationToken = default)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
+ ArgumentException.ThrowIfNullOrWhiteSpace(userId);
+
+ var entity = await dbContext.UserUiPreferences
+ .AsNoTracking()
+ .SingleOrDefaultAsync(x => x.TenantId == tenantId && x.UserId == userId, cancellationToken)
+ .ConfigureAwait(false);
+
+ return entity is null ? null : ToModel(entity);
+ }
+
+ public async Task UpsertAsync(UserUiPreference preference, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(preference);
+ ArgumentException.ThrowIfNullOrWhiteSpace(preference.TenantId);
+ ArgumentException.ThrowIfNullOrWhiteSpace(preference.UserId);
+ ArgumentException.ThrowIfNullOrWhiteSpace(preference.Theme);
+
+ var existing = await dbContext.UserUiPreferences
+ .SingleOrDefaultAsync(x => x.TenantId == preference.TenantId && x.UserId == preference.UserId, cancellationToken)
+ .ConfigureAwait(false);
+
+ var utcNow = DateTimeOffset.UtcNow;
+
+ if (existing is null)
+ {
+ dbContext.UserUiPreferences.Add(ToEntity(preference, utcNow));
+ }
+ else
+ {
+ existing.Theme = preference.Theme;
+ existing.Density = preference.Density;
+ existing.SidebarCollapsed = preference.SidebarCollapsed;
+ existing.DefaultPageSize = preference.DefaultPageSize;
+ existing.Language = preference.Language;
+ existing.TimeZone = preference.TimeZone;
+ existing.UpdatedAt = utcNow;
+ }
+
+ await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ private static UserUiPreference ToModel(UserUiPreferenceEntity entity)
+ => new()
+ {
+ TenantId = entity.TenantId,
+ UserId = entity.UserId,
+ Theme = entity.Theme,
+ Density = entity.Density,
+ SidebarCollapsed = entity.SidebarCollapsed,
+ DefaultPageSize = entity.DefaultPageSize,
+ Language = entity.Language,
+ TimeZone = entity.TimeZone,
+ UpdatedAt = entity.UpdatedAt
+ };
+
+ private static UserUiPreferenceEntity ToEntity(UserUiPreference model, DateTimeOffset utcNow)
+ => new()
+ {
+ TenantId = model.TenantId,
+ UserId = model.UserId,
+ Theme = model.Theme,
+ Density = model.Density,
+ SidebarCollapsed = model.SidebarCollapsed,
+ DefaultPageSize = model.DefaultPageSize,
+ Language = model.Language,
+ TimeZone = model.TimeZone,
+ UpdatedAt = utcNow
+ };
+}
diff --git a/src/Code311.Tabler.Components/.gitkeep b/src/Code311.Tabler.Components/.gitkeep
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/Code311.Tabler.Components/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/src/Code311.Tabler.Components/Code311.Tabler.Components.csproj b/src/Code311.Tabler.Components/Code311.Tabler.Components.csproj
new file mode 100644
index 0000000..8a748e5
--- /dev/null
+++ b/src/Code311.Tabler.Components/Code311.Tabler.Components.csproj
@@ -0,0 +1,17 @@
+
+
+ net10.0
+ Code311.Tabler.Components
+ Code311.Tabler.Components
+ Tabler component primitives for Code311 semantic UI APIs.
+ Code311.Tabler.Components
+ true
+
+
+
+
+
+
+
+
+
diff --git a/src/Code311.Tabler.Components/Common/ComponentModels.cs b/src/Code311.Tabler.Components/Common/ComponentModels.cs
new file mode 100644
index 0000000..2dcec00
--- /dev/null
+++ b/src/Code311.Tabler.Components/Common/ComponentModels.cs
@@ -0,0 +1,33 @@
+namespace Code311.Tabler.Components.Common;
+
+///
+/// Represents a semantic text/value option item.
+///
+///
+/// This item is used by form and navigation components without exposing framework-specific option models.
+///
+public sealed record Cd311OptionItem(string Value, string Text, bool Selected = false, bool Disabled = false);
+
+///
+/// Represents a semantic navigation item.
+///
+///
+/// Navigation components consume this model to produce menu and tab structures.
+///
+public sealed record Cd311NavItem(string Text, string? Url = null, bool Active = false, bool Disabled = false);
+
+///
+/// Represents a semantic action item.
+///
+///
+/// Action items provide consistent semantics across action bars, dropdowns, and modal actions.
+///
+public sealed record Cd311ActionItem(string Text, string? Command = null, bool Primary = false, bool Disabled = false);
+
+///
+/// Represents a key/value semantic pair.
+///
+///
+/// Data components use this model for metadata and summary displays.
+///
+public sealed record Cd311KeyValueItem(string Key, string Value);
diff --git a/src/Code311.Tabler.Components/Common/TagHelperUtility.cs b/src/Code311.Tabler.Components/Common/TagHelperUtility.cs
new file mode 100644
index 0000000..092f4f2
--- /dev/null
+++ b/src/Code311.Tabler.Components/Common/TagHelperUtility.cs
@@ -0,0 +1,38 @@
+using Code311.Tabler.Core.Mapping;
+using Microsoft.AspNetCore.Razor.TagHelpers;
+
+namespace Code311.Tabler.Components.Common;
+
+internal static class TagHelperUtility
+{
+ public static TagHelperContext CreateContext() =>
+ new(
+ new TagHelperAttributeList(),
+ new Dictionary