From e588c67e3ab9cccd87e6e48ef0e032ff8e96e6dc Mon Sep 17 00:00:00 2001 From: "Robert J. Good" Date: Thu, 23 Jul 2026 23:39:13 -0700 Subject: [PATCH 01/15] mobile desktop layout --- .github/copilot-instructions.md | 4 + .../Features/Chat/ChatPage.razor | 132 ++++++++++-------- .../Chat/Components/ChatSessionList.razor | 5 +- .../Chat/Components/ChatSessionStrip.razor | 14 +- 4 files changed, 84 insertions(+), 71 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d2865a5..a0961c7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -33,6 +33,10 @@ - **RBAC & Security:** Enforced in API layer, see `ConfigureServicesAuth.cs`. - **Auth-Triggered User Provisioning:** Keep the flow UI-tied (not middleware/pipeline) because OBO token acquisition requires user context on the main UI thread/circuit. +## UI Design Guidelines +- **ChatPage Layout:** Implement distinct desktop and mobile layouts with a strict row/column structure on mobile. +- **Input Visibility:** Ensure persistent visible input under a capped message list height. + ## References - [README.md](../README.md): Project overview and getting started diff --git a/src/Presentation.Web/Features/Chat/ChatPage.razor b/src/Presentation.Web/Features/Chat/ChatPage.razor index a1ad029..25b5533 100644 --- a/src/Presentation.Web/Features/Chat/ChatPage.razor +++ b/src/Presentation.Web/Features/Chat/ChatPage.razor @@ -15,57 +15,80 @@ Chat Session - - - - - - @if (sessionListOrientation == Orientation.Vertical) - { - + @if (isMobileLayout) + { + + + + + + + + + + What can I help you with? + + + + + + + + + + + - } - else - { - - } - - - - - What can I help you with? - - - @if (sessionListOrientation == Orientation.Vertical) - { - - } - else - { - - - } - - + OnSessionCreated="HandleSessionCreated" + OnMessageSubmitted="HandleMessageSubmitted" /> + + } + else + { + + + + + + + What can I help you with? + + + + + + + + + + + + + + + + + + + + + + } @code { private ChatSessionsModel chatSessions = new ChatSessionsModel(); private ChatSessionList? chatSessionListRef; - private ChatSessionStrip? ChatSessionStripRef; - private Orientation sessionListOrientation = Orientation.Vertical; - private int sessionListVerticalGap; - private int sessionListHorizontalGap; - private bool wrapSessions = false; - private string chatContentStyle = "max-width:50vw;"; + private ChatSessionStrip? chatSessionStripRef; + private bool isMobileLayout; protected override async Task OnInitializedAsync() { @@ -83,7 +106,7 @@ private void HandleNewSessionPressed() { chatSessionListRef?.ClearSelection(); - ChatSessionStripRef?.ClearSelection(); + chatSessionStripRef?.ClearSelection(); chatSessions.ClearActive(); StateHasChanged(); } @@ -110,21 +133,8 @@ private void OnBreakpointEnterHandler(GridItemSize size) { - wrapSessions = size == GridItemSize.Xs ? true : false; - if (size == GridItemSize.Xs || size == GridItemSize.Sm) - { - sessionListOrientation = Orientation.Horizontal; - sessionListVerticalGap = 0; - sessionListHorizontalGap = 8; - chatContentStyle = ""; - } - else - { - sessionListOrientation = Orientation.Vertical; - sessionListVerticalGap = 8; - sessionListHorizontalGap = 0; - chatContentStyle = "max-width:50vw;"; - } + isMobileLayout = size == GridItemSize.Xs || size == GridItemSize.Sm; StateHasChanged(); } + } \ No newline at end of file diff --git a/src/Presentation.Web/Features/Chat/Components/ChatSessionList.razor b/src/Presentation.Web/Features/Chat/Components/ChatSessionList.razor index c4b348a..4fa553e 100644 --- a/src/Presentation.Web/Features/Chat/Components/ChatSessionList.razor +++ b/src/Presentation.Web/Features/Chat/Components/ChatSessionList.razor @@ -8,7 +8,8 @@ { + SelectedItem="@selectedTreeItem" + Style="width:100%; min-width:30%;"> @{ var sessionItem = (ChatSessionTreeViewItem)item; @@ -34,7 +35,7 @@ } else - { + { @sessionItem.Text } diff --git a/src/Presentation.Web/Features/Chat/Components/ChatSessionStrip.razor b/src/Presentation.Web/Features/Chat/Components/ChatSessionStrip.razor index dd96e94..98009f1 100644 --- a/src/Presentation.Web/Features/Chat/Components/ChatSessionStrip.razor +++ b/src/Presentation.Web/Features/Chat/Components/ChatSessionStrip.razor @@ -9,7 +9,7 @@ @foreach (var session in Sessions) { - + @if (editingSessionId == session.Id) { - + + @session.Title - @if (selectedSession?.Id == session.Id) - { - - } + } From 94023ffd148bedda32a7542c6b24806ef68aa47d Mon Sep 17 00:00:00 2001 From: "Robert J. Good" Date: Thu, 23 Jul 2026 23:48:58 -0700 Subject: [PATCH 02/15] icon button --- src/Presentation.Web/Features/Chat/ChatPage.razor | 5 +++-- .../Features/Chat/Components/ChatSessionEditForm.razor | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Presentation.Web/Features/Chat/ChatPage.razor b/src/Presentation.Web/Features/Chat/ChatPage.razor index 25b5533..5102f98 100644 --- a/src/Presentation.Web/Features/Chat/ChatPage.razor +++ b/src/Presentation.Web/Features/Chat/ChatPage.razor @@ -34,12 +34,13 @@ - + + - + diff --git a/src/Presentation.Web/Features/Chat/Components/ChatSessionEditForm.razor b/src/Presentation.Web/Features/Chat/Components/ChatSessionEditForm.razor index 556ac23..de10765 100644 --- a/src/Presentation.Web/Features/Chat/Components/ChatSessionEditForm.razor +++ b/src/Presentation.Web/Features/Chat/Components/ChatSessionEditForm.razor @@ -7,7 +7,9 @@ Label="" Required="true" Autofocus="true" /> - + + + From 4220082d4686ba1641c9a92495a874626606f46d Mon Sep 17 00:00:00 2001 From: "Robert J. Good" Date: Thu, 23 Jul 2026 23:52:41 -0700 Subject: [PATCH 03/15] elipse always shows --- .../Features/Chat/Components/ChatSessionList.razor | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Presentation.Web/Features/Chat/Components/ChatSessionList.razor b/src/Presentation.Web/Features/Chat/Components/ChatSessionList.razor index 4fa553e..5d81c62 100644 --- a/src/Presentation.Web/Features/Chat/Components/ChatSessionList.razor +++ b/src/Presentation.Web/Features/Chat/Components/ChatSessionList.razor @@ -26,11 +26,12 @@ } else if (isSelected) { - - + + @sessionItem.Text } From 573e635e94383d46f4931da7886cac01e4b376ef Mon Sep 17 00:00:00 2001 From: "Robert J. Good" Date: Thu, 23 Jul 2026 23:58:25 -0700 Subject: [PATCH 04/15] width property --- .../Features/Chat/Components/ChatMessageList.razor | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Presentation.Web/Features/Chat/Components/ChatMessageList.razor b/src/Presentation.Web/Features/Chat/Components/ChatMessageList.razor index aa14b52..a379896 100644 --- a/src/Presentation.Web/Features/Chat/Components/ChatMessageList.razor +++ b/src/Presentation.Web/Features/Chat/Components/ChatMessageList.razor @@ -1,10 +1,9 @@ @using Goodtocode.AgentFramework.Presentation.Web.Features.Chat.Models -@using Microsoft.AspNetCore.Components.Web @using Microsoft.JSInterop @inject IJSRuntime JSRuntime - + @foreach (var message in Messages ?? Enumerable.Empty()) { var isUser = IsUserMessage(message); @@ -22,6 +21,9 @@ [Parameter] public IEnumerable Messages { get; set; } = new List(); + [Parameter] + public string Width { get; set; } = "100%"; + private bool IsUserMessage(ChatMessageModel message) => message?.Role?.ToLowerInvariant() == "user"; } \ No newline at end of file From 0cc8f6503b9f2f39cbd0eb56495d3ada8d50c515 Mon Sep 17 00:00:00 2001 From: "Robert J. Good" Date: Fri, 24 Jul 2026 09:35:31 -0700 Subject: [PATCH 05/15] Reset scripts --- Reset-EfDatabase.ps1 | 182 +++++++++++++++++++++++++++++++++++++++++++ Reset-EfNswag.ps1 | 85 ++++++++++++-------- 2 files changed, 233 insertions(+), 34 deletions(-) create mode 100644 Reset-EfDatabase.ps1 diff --git a/Reset-EfDatabase.ps1 b/Reset-EfDatabase.ps1 new file mode 100644 index 0000000..4dda5a6 --- /dev/null +++ b/Reset-EfDatabase.ps1 @@ -0,0 +1,182 @@ +<# +===================================================================== +EF database reset script (drop + re-migrate) +Example usage: + 1. Open PowerShell in solution root + 2. Run: ./Reset-EfDatabase.ps1 + or: ./Reset-EfDatabase.ps1 -Products $customProducts + For each product: + 3. Script will drop the database + 4. Script will apply existing EF migrations to recreate schema + +Notes: + - This script does NOT create new migrations. + - This script does NOT run NSwag client generation. +===================================================================== +#> + +param ( + [Parameter(Mandatory = $false)] + [array]$Products = @( + @{ Name = "AgentFramework"; Root = ".\src"; Database = "AgentFramework"; ApiProject = "Presentation.Api" } + ), + [switch]$DropDatabase, + [switch]$DropTables, + [string]$DropTablesPath = ".\data\Admin\Drop Tables.sql" +) + +Push-Location +try { + function Invoke-DiagnosticCommand($cmd) { + Write-Host "[DIAG] Running: $cmd" -ForegroundColor Yellow + try { + Invoke-Expression $cmd + } + catch { + Write-Error "[ERROR] Command failed: $cmd" + Write-Error $_ + throw + } + } + + function Install-SqlCmd { + $sqlcmd = Get-Command sqlcmd -ErrorAction SilentlyContinue + if (-not $sqlcmd) { + Write-Host "sqlcmd not found. Installing via winget..." -ForegroundColor Yellow + winget install --id Microsoft.SQLServerCommandLineTools -e --silent + $sqlcmd = Get-Command sqlcmd -ErrorAction SilentlyContinue + if (-not $sqlcmd) { + throw "sqlcmd installation failed. Please install manually." + } + Write-Host "sqlcmd installed." -ForegroundColor Green + } + else { + Write-Host "sqlcmd is already installed." -ForegroundColor Green + } + } + + function Test-DatabaseExists { + param ( + [string]$DatabaseName + ) + + $query = "IF DB_ID('$DatabaseName') IS NOT NULL SELECT 1 ELSE SELECT 0" + $result = sqlcmd -S "(localdb)\MSSQLLocalDB" -Q $query -h -1 -W 2>$null + return ($result -eq '1') + } + + function Reset-DatabaseState { + param ( + [string]$Name, + [string]$Database, + [string]$Context, + [string]$Connection, + [string]$InfraProjAbs, + [string]$WebApiProjAbs + ) + + if ($DropDatabase -and $DropTables) { + throw "Choose either -DropDatabase or -DropTables, not both." + } + + $useDropDatabase = $DropDatabase + $useDropTables = $DropTables + if (-not $DropDatabase -and -not $DropTables) { + # Preserve existing behavior for this script: drop and recreate database. + $useDropDatabase = $true + } + + if ($useDropTables) { + Install-SqlCmd + if (-not (Test-Path $DropTablesPath)) { + throw "Drop tables script not found at $DropTablesPath" + } + if (Test-DatabaseExists -DatabaseName $Database) { + Write-Host "[STEP] Dropping all tables for $Name via $DropTablesPath..." -ForegroundColor Magenta + $dropResult = sqlcmd -S "(localdb)\MSSQLLocalDB" -d $Database -b -i $DropTablesPath 2>&1 + if ($LASTEXITCODE -ne 0) { + throw "Drop tables script failed for ${Name}: $dropResult" + } + Write-Host "[SUCCESS] All tables dropped for $Name." -ForegroundColor Green + } + else { + Write-Host "[WARN] Database '$Database' does not exist. Skipping drop tables step for $Name." -ForegroundColor Yellow + } + } + + if ($useDropDatabase) { + Write-Host "[STEP] Dropping database for $Name..." -ForegroundColor Magenta + Invoke-DiagnosticCommand "dotnet ef database drop --project `"$InfraProjAbs`" --startup-project `"$WebApiProjAbs`" --context $Context --connection '$Connection' --force --verbose" + } + } + + # Ensure dotnet-ef tool is available (idempotent). + $srcPath = Join-Path $PSScriptRoot 'src' + $toolsManifest = Join-Path $srcPath 'dotnet-tools.json' + Push-Location $srcPath + try { + if (!(Test-Path -Path $toolsManifest)) { + Write-Host "[STEP] Creating dotnet tool manifest in src/..." -ForegroundColor Magenta + Invoke-DiagnosticCommand "dotnet new tool-manifest --force" + } + + $toolList = & dotnet tool list --local | Out-String + if ($toolList -notmatch 'dotnet-ef') { + Write-Host "[STEP] Installing dotnet-ef as a local tool in src/..." -ForegroundColor Magenta + Invoke-DiagnosticCommand "dotnet tool install dotnet-ef --local" + } + else { + Write-Host "[STEP] dotnet-ef already installed as a local tool." -ForegroundColor Green + } + + Write-Host "[STEP] Restoring local dotnet tools..." -ForegroundColor Magenta + Invoke-DiagnosticCommand "dotnet tool restore" + } + finally { + Pop-Location + } + + foreach ($product in $Products) { + $name = $product.Name + $root = $product.Root + $database = $product.Database + $apiProject = $product.ApiProject + + $infraProj = "$root\Infrastructure.SqlServer\Infrastructure.SqlServer.csproj" + $webApiProj = "$root\$apiProject\$apiProject.csproj" + $context = "${name}Context" + $connection = "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=$database;Min Pool Size=3;MultipleActiveResultSets=True;Trusted_Connection=Yes;TrustServerCertificate=True;Encrypt=True;Connection Timeout=30;" + + Write-Host "[STEP] Product - $name" -ForegroundColor Cyan + Write-Host "[STEP] Context - $context" -ForegroundColor Cyan + Write-Host "[STEP] Infra project - $infraProj" -ForegroundColor Cyan + Write-Host "[STEP] WebApi project - $webApiProj" -ForegroundColor Cyan + + Invoke-DiagnosticCommand "dotnet clean $infraProj" + Invoke-DiagnosticCommand "dotnet restore $infraProj" + Invoke-DiagnosticCommand "dotnet build $infraProj --no-restore" + Invoke-DiagnosticCommand "dotnet clean $webApiProj" + Invoke-DiagnosticCommand "dotnet restore $webApiProj" + Invoke-DiagnosticCommand "dotnet build $webApiProj --no-restore" + + # Build absolute paths from $PSScriptRoot to avoid CWD-related path resolution issues. + $infraProjAbs = Join-Path $PSScriptRoot ($infraProj -replace '^\.[\\/]', '') + $webApiProjAbs = Join-Path $PSScriptRoot ($webApiProj -replace '^\.[\\/]', '') + + Push-Location $srcPath + try { + Reset-DatabaseState -Name $name -Database $database -Context $context -Connection $connection -InfraProjAbs $infraProjAbs -WebApiProjAbs $webApiProjAbs + + Write-Host "[STEP] Applying existing migrations for $name..." -ForegroundColor Magenta + Invoke-DiagnosticCommand "dotnet ef database update --project `"$infraProjAbs`" --startup-project `"$webApiProjAbs`" --context $context --connection '$connection' --verbose" + } + finally { + Pop-Location + } + } + + Write-Host "[DONE] Database reset and migration update complete." -ForegroundColor Green +} +finally { + Pop-Location +} diff --git a/Reset-EfNswag.ps1 b/Reset-EfNswag.ps1 index b4daf76..6c50725 100644 --- a/Reset-EfNswag.ps1 +++ b/Reset-EfNswag.ps1 @@ -18,10 +18,12 @@ param ( [array]$Products = @( @{ Name = "AgentFramework"; Root = ".\src"; Database = "AgentFramework"; ApiProject = "Presentation.Api" } ), - [string] $dropTablesPath = ".\data\Admin\Drop Tables.sql" + [switch]$DropDatabase, + [switch]$DropTables, + [string]$dropTablesPath = ".\data\Admin\Drop Tables.sql" ) -function Ensure-SqlCmd { +function Install-SqlCmd { $sqlcmd = Get-Command sqlcmd -ErrorAction SilentlyContinue if (-not $sqlcmd) { Write-Host "sqlcmd not found. Installing via winget..." -ForegroundColor Yellow @@ -42,9 +44,8 @@ function Ensure-SqlCmd { } } -Ensure-SqlCmd +Install-SqlCmd -# STEP 1: Drop all tables using the provided SQL script, but only if the database exists function Test-DatabaseExists { param ( [string]$DatabaseName @@ -54,29 +55,21 @@ function Test-DatabaseExists { return ($result -eq '1') } -if (Test-Path $dropTablesPath) { - if ($Products -and $Products.Count -gt 0) { - $dbName = $Products[0].Database - } - if (Test-DatabaseExists -DatabaseName $dbName) { - Write-Host "[STEP 1] Dropping all tables via $dropTablesPath on database '$dbName'..." -ForegroundColor Magenta - $dropResult = sqlcmd -S "(localdb)\MSSQLLocalDB" -d $dbName -b -i $dropTablesPath 2>&1 - if ($LASTEXITCODE -ne 0) { - Write-Host "[ERROR] Drop tables script failed: $dropResult" -ForegroundColor Red - throw "[FAIL-FAST] Database drop failed. Stopping script." - } - Write-Host "[SUCCESS] All tables dropped." -ForegroundColor Green - } else { - Write-Host "[WARN] Database does not exist. Skipping drop tables step." -ForegroundColor Yellow - } -} else { - Write-Host "[WARN] Drop script not found at $dropTablesPath — skipping database reset. Ensure the database is clean before continuing." -ForegroundColor Yellow +if ($DropDatabase -and $DropTables) { + throw "Choose either -DropDatabase or -DropTables, not both." +} + +# Preserve existing behavior for this script: drop tables unless explicitly overridden. +$useDropTables = $DropTables +$useDropDatabase = $DropDatabase +if (-not $DropDatabase -and -not $DropTables) { + $useDropTables = $true } Push-Location try { function Write-Diag($msg) { Write-Host "[DIAG] $msg" -ForegroundColor Yellow } - function Run-Verbose($cmd) { + function Invoke-DiagnosticCommand($cmd) { Write-Host "[DIAG] Running: " + $cmd -ForegroundColor Yellow try { Invoke-Expression $cmd @@ -94,17 +87,17 @@ try { try { if (!(Test-Path -Path $toolsManifest)) { Write-Host "[STEP] Creating dotnet tool manifest in src/..." -ForegroundColor Magenta - Run-Verbose "dotnet new tool-manifest --force" + Invoke-DiagnosticCommand "dotnet new tool-manifest --force" } $toolList = & dotnet tool list --local | Out-String if ($toolList -notmatch 'dotnet-ef') { Write-Host "[STEP] Installing dotnet-ef as a local tool in src/..." -ForegroundColor Magenta - Run-Verbose "dotnet tool install dotnet-ef --local" + Invoke-DiagnosticCommand "dotnet tool install dotnet-ef --local" } else { Write-Host "[STEP] dotnet-ef already installed as a local tool." -ForegroundColor Green } Write-Host "[STEP] Restoring local dotnet tools (dotnet-ef required) in src/..." -ForegroundColor Magenta - Run-Verbose "dotnet tool restore" + Invoke-DiagnosticCommand "dotnet tool restore" } finally { Pop-Location } @@ -128,12 +121,12 @@ try { Write-Host "[STEP] WebApi project - " + $webApiProj -ForegroundColor Cyan # Restore and build before migrations - Run-Verbose "dotnet clean $infraProj" - Run-Verbose "dotnet restore $infraProj" - Run-Verbose "dotnet build $infraProj --no-restore" - Run-Verbose "dotnet clean $webApiProj" - Run-Verbose "dotnet restore $webApiProj" - Run-Verbose "dotnet build $webApiProj --no-restore" + Invoke-DiagnosticCommand "dotnet clean $infraProj" + Invoke-DiagnosticCommand "dotnet restore $infraProj" + Invoke-DiagnosticCommand "dotnet build $infraProj --no-restore" + Invoke-DiagnosticCommand "dotnet clean $webApiProj" + Invoke-DiagnosticCommand "dotnet restore $webApiProj" + Invoke-DiagnosticCommand "dotnet build $webApiProj --no-restore" Write-Host "Removing migration files" Remove-Item $infraPath -ErrorAction SilentlyContinue @@ -142,10 +135,34 @@ try { # resolves against [Environment]::CurrentDirectory, not $PWD, causing wrong paths after Push-Location). $infraProjAbs = Join-Path $PSScriptRoot ($infraProj -replace '^\.[\\/]', '') $webApiProjAbs = Join-Path $PSScriptRoot ($webApiProj -replace '^\.[\\/]', '') + + if ($useDropTables) { + if (Test-Path $dropTablesPath) { + if (Test-DatabaseExists -DatabaseName $database) { + Write-Host "[STEP] Dropping all tables for $name via $dropTablesPath..." -ForegroundColor Magenta + $dropResult = sqlcmd -S "(localdb)\MSSQLLocalDB" -d $database -b -i $dropTablesPath 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host "[ERROR] Drop tables script failed for ${name}: $dropResult" -ForegroundColor Red + throw "[FAIL-FAST] Table drop failed for $name. Stopping script." + } + Write-Host "[SUCCESS] All tables dropped for $name." -ForegroundColor Green + } + else { + Write-Host "[WARN] Database '$database' does not exist. Skipping drop tables step for $name." -ForegroundColor Yellow + } + } + else { + throw "Drop script not found at $dropTablesPath" + } + } Push-Location $srcPath try { - Run-Verbose "dotnet ef migrations add InitialCreate-$context --project `"$infraProjAbs`" --startup-project `"$webApiProjAbs`" --context $context --verbose" - Run-Verbose "dotnet ef database update --project `"$infraProjAbs`" --startup-project `"$webApiProjAbs`" --context $context --connection '$connection' --verbose" + if ($useDropDatabase) { + Write-Host "[STEP] Dropping database for $name..." -ForegroundColor Magenta + Invoke-DiagnosticCommand "dotnet ef database drop --project `"$infraProjAbs`" --startup-project `"$webApiProjAbs`" --context $context --connection '$connection' --force --verbose" + } + Invoke-DiagnosticCommand "dotnet ef migrations add InitialCreate-$context --project `"$infraProjAbs`" --startup-project `"$webApiProjAbs`" --context $context --verbose" + Invoke-DiagnosticCommand "dotnet ef database update --project `"$infraProjAbs`" --startup-project `"$webApiProjAbs`" --context $context --connection '$connection' --verbose" } finally { Pop-Location } @@ -154,7 +171,7 @@ try { if (Test-Path $nswagScript) { Push-Location "$root\$apiProject" try { - Run-Verbose ".\Generate-NswagClientCode.ps1 -SkipBuildRestore" + Invoke-DiagnosticCommand ".\Generate-NswagClientCode.ps1 -SkipBuildRestore" } finally { Pop-Location } From 26084451d93067089cf39c36cc660c48c524adb6 Mon Sep 17 00:00:00 2001 From: "Robert J. Good" Date: Fri, 24 Jul 2026 09:49:06 -0700 Subject: [PATCH 06/15] removed html --- .github/copilot-instructions.md | 1 + .../Library/Analytics/AnalyticsConsentBanner.razor | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a0961c7..33f0afd 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -36,6 +36,7 @@ ## UI Design Guidelines - **ChatPage Layout:** Implement distinct desktop and mobile layouts with a strict row/column structure on mobile. - **Input Visibility:** Ensure persistent visible input under a capped message list height. +- **Component Usage:** Use only components in `.razor` files (no raw HTML elements), except in `App.razor` where raw HTML is allowed. ## References - [README.md](../README.md): Project overview and getting started diff --git a/src/Presentation.Web/Library/Analytics/AnalyticsConsentBanner.razor b/src/Presentation.Web/Library/Analytics/AnalyticsConsentBanner.razor index 9fda29a..7249278 100644 --- a/src/Presentation.Web/Library/Analytics/AnalyticsConsentBanner.razor +++ b/src/Presentation.Web/Library/Analytics/AnalyticsConsentBanner.razor @@ -5,7 +5,7 @@ @if (_isPreferenceLoaded && _showBanner) { - + } @code { From f19df4f8dda11202de0145f5e2479766570b363d Mon Sep 17 00:00:00 2001 From: "Robert J. Good" Date: Fri, 24 Jul 2026 10:07:19 -0700 Subject: [PATCH 07/15] padding and alignment --- src/Presentation.Web/Features/Chat/ChatPage.razor | 3 +-- .../Features/Chat/Components/ChatMessageList.razor | 2 +- .../Features/Chat/Components/NewChatMessageInput.razor | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Presentation.Web/Features/Chat/ChatPage.razor b/src/Presentation.Web/Features/Chat/ChatPage.razor index 5102f98..db9df5d 100644 --- a/src/Presentation.Web/Features/Chat/ChatPage.razor +++ b/src/Presentation.Web/Features/Chat/ChatPage.razor @@ -34,13 +34,12 @@ - + - ()) { var isUser = IsUserMessage(message); - + @message.Content diff --git a/src/Presentation.Web/Features/Chat/Components/NewChatMessageInput.razor b/src/Presentation.Web/Features/Chat/Components/NewChatMessageInput.razor index 0a5c923..138cf9b 100644 --- a/src/Presentation.Web/Features/Chat/Components/NewChatMessageInput.razor +++ b/src/Presentation.Web/Features/Chat/Components/NewChatMessageInput.razor @@ -4,7 +4,7 @@ @inject IChatService chatService - + Date: Fri, 24 Jul 2026 12:32:33 -0700 Subject: [PATCH 08/15] ensured mobile horizontal scroll works --- .../Features/Chat/ChatPage.razor | 228 +++++++++--------- .../Chat/Components/ChatSessionStrip.razor | 54 +++-- 2 files changed, 144 insertions(+), 138 deletions(-) diff --git a/src/Presentation.Web/Features/Chat/ChatPage.razor b/src/Presentation.Web/Features/Chat/ChatPage.razor index db9df5d..2743360 100644 --- a/src/Presentation.Web/Features/Chat/ChatPage.razor +++ b/src/Presentation.Web/Features/Chat/ChatPage.razor @@ -16,125 +16,127 @@ Chat Session - @if (isMobileLayout) - { - - - - - - - - - - What can I help you with? - - - - + @if (isMobileLayout) + { + + + + + + + + + + + + What can I help you with? + + + + - - - - - - - - } - else - { - - - - - - - What can I help you with? - - - - - - - - - - - - - - - - - - - - - - } + + + + + + + + } + else + { + + + + + + + What can I help you with? + + + + + + + + + + + + + + + + + + + + + + } @code { - private ChatSessionsModel chatSessions = new ChatSessionsModel(); - private ChatSessionList? chatSessionListRef; - private ChatSessionStrip? chatSessionStripRef; - private bool isMobileLayout; - - protected override async Task OnInitializedAsync() - { - var authState = await AuthStateProvider.GetAuthenticationStateAsync(); - if (authState.User.Identity?.IsAuthenticated == true) - { - await UserSyncService.SyncUserAsync(authState.User); - } - - chatSessions = new ChatSessionsModel(); - chatSessions.AddRange(await chatService.GetChatSessionsAsync()); - StateHasChanged(); - } - - private void HandleNewSessionPressed() - { - chatSessionListRef?.ClearSelection(); - chatSessionStripRef?.ClearSelection(); - chatSessions.ClearActive(); - StateHasChanged(); - } - - private void HandleSessionCreated(ChatSessionModel chatSession) - { - chatSessions.Add(chatSession); - chatSessions.SetActive(chatSession); - StateHasChanged(); - } - - private void HandleSessionSelected(ChatSessionModel chatSession) + private ChatSessionsModel chatSessions = new ChatSessionsModel(); + private ChatSessionList? chatSessionListRef; + private ChatSessionStrip? chatSessionStripRef; + private bool isMobileLayout; + + protected override async Task OnInitializedAsync() + { + var authState = await AuthStateProvider.GetAuthenticationStateAsync(); + if (authState.User.Identity?.IsAuthenticated == true) { - chatSessions.ClearActive(); - chatSessions.SetActive(chatSession); - StateHasChanged(); + await UserSyncService.SyncUserAsync(authState.User); } - private async Task HandleMessageSubmitted() - { - chatSessions.RefreshItem(await chatService.GetChatSessionAsync(chatSessions?.ActiveSession?.Id ?? Guid.Empty)); - StateHasChanged(); - } - - private void OnBreakpointEnterHandler(GridItemSize size) - { - isMobileLayout = size == GridItemSize.Xs || size == GridItemSize.Sm; - StateHasChanged(); - } + chatSessions = new ChatSessionsModel(); + chatSessions.AddRange(await chatService.GetChatSessionsAsync()); + StateHasChanged(); + } + + private void HandleNewSessionPressed() + { + chatSessionListRef?.ClearSelection(); + chatSessionStripRef?.ClearSelection(); + chatSessions.ClearActive(); + StateHasChanged(); + } + + private void HandleSessionCreated(ChatSessionModel chatSession) + { + chatSessions.Add(chatSession); + chatSessions.SetActive(chatSession); + StateHasChanged(); + } + + private void HandleSessionSelected(ChatSessionModel chatSession) + { + chatSessions.ClearActive(); + chatSessions.SetActive(chatSession); + StateHasChanged(); + } + + private async Task HandleMessageSubmitted() + { + chatSessions.RefreshItem(await chatService.GetChatSessionAsync(chatSessions?.ActiveSession?.Id ?? Guid.Empty)); + StateHasChanged(); + } + + private void OnBreakpointEnterHandler(GridItemSize size) + { + isMobileLayout = size == GridItemSize.Xs || size == GridItemSize.Sm; + StateHasChanged(); + } } \ No newline at end of file diff --git a/src/Presentation.Web/Features/Chat/Components/ChatSessionStrip.razor b/src/Presentation.Web/Features/Chat/Components/ChatSessionStrip.razor index 98009f1..b4e900d 100644 --- a/src/Presentation.Web/Features/Chat/Components/ChatSessionStrip.razor +++ b/src/Presentation.Web/Features/Chat/Components/ChatSessionStrip.razor @@ -5,31 +5,33 @@ @inject IChatService chatService @if (Sessions?.Any() == true) -{ - - @foreach (var session in Sessions) - { - - @if (editingSessionId == session.Id) - { - - } - else - { - - - @session.Title - - - - } - - } - +{ + + + @foreach (var session in SessionItems) + { + + @if (editingSessionId == session.Id) + { + + } + else + { + + + @session.Title + + + + } + + } + + } else { @@ -46,6 +48,8 @@ else private EditContext? editContext; private ChatSessionModel? selectedSession; + private IReadOnlyList SessionItems => Sessions?.ToList() ?? []; + private async Task SelectSession(ChatSessionModel session) { selectedSession = session; From c556a09ed058fcad3eacdd1f80273576ccc04b4d Mon Sep 17 00:00:00 2001 From: "Robert J. Good" Date: Fri, 24 Jul 2026 12:36:48 -0700 Subject: [PATCH 09/15] home page max width --- src/Presentation.Web/Features/HomePage.razor | 2 +- .../Shell/Layout/MainLayout.razor | 172 +++++++++--------- 2 files changed, 87 insertions(+), 87 deletions(-) diff --git a/src/Presentation.Web/Features/HomePage.razor b/src/Presentation.Web/Features/HomePage.razor index e76559f..3560b77 100644 --- a/src/Presentation.Web/Features/HomePage.razor +++ b/src/Presentation.Web/Features/HomePage.razor @@ -7,7 +7,7 @@ Goodtocode Quick-start for Microsoft Agent Framework - + diff --git a/src/Presentation.Web/Shell/Layout/MainLayout.razor b/src/Presentation.Web/Shell/Layout/MainLayout.razor index 11e4d80..e81229a 100644 --- a/src/Presentation.Web/Shell/Layout/MainLayout.razor +++ b/src/Presentation.Web/Shell/Layout/MainLayout.razor @@ -13,104 +13,104 @@ @inject IUserSyncService UserSyncService -
- - - GoodToCode - - - Goodtocode Quick-start for Microsoft Agent Framework - - - -
- - - - - @if (ActivePlatformNavigation is not null) - { - - - } - @Body - - - - - - - - Oops! Something went wrong. - - - @ex.Message - - - - Go Home - - - - - - - - - - Home - - Chat Session - - +
+ + + GoodToCode + + + Goodtocode Quick-start for Microsoft Agent Framework + + + +
+ + + + + @if (ActivePlatformNavigation is not null) + { + + + } + @Body + + + + + + + + Oops! Something went wrong. + + + @ex.Message + + + + Go Home + + + + + + + + + + Home + + Chat Session + +
@code { - private ErrorBoundary? errorBoundaryRef; + private ErrorBoundary? errorBoundaryRef; - private void OnRecoverAndNavigate(Exception ex) - { - errorBoundaryRef?.Recover(); - Navigation.NavigateTo("/", forceLoad: true); - } + private void OnRecoverAndNavigate(Exception ex) + { + errorBoundaryRef?.Recover(); + Navigation.NavigateTo("/", forceLoad: true); + } - private bool _synced = false; + private bool _synced = false; - private PlatformNavigationModel? ActivePlatformNavigation + private PlatformNavigationModel? ActivePlatformNavigation + { + get { - get - { - var route = Navigation.ToBaseRelativePath(Navigation.Uri).Trim('/'); - if (string.IsNullOrWhiteSpace(route)) - { - return null; - } + var route = Navigation.ToBaseRelativePath(Navigation.Uri).Trim('/'); + if (string.IsNullOrWhiteSpace(route)) + { + return null; + } - var segment = route.Split('/', StringSplitOptions.RemoveEmptyEntries)[0]; - return PlatformNavigationModel.All.FirstOrDefault(m => - m.Prefix.Equals(segment, StringComparison.OrdinalIgnoreCase)); - } + var segment = route.Split('/', StringSplitOptions.RemoveEmptyEntries)[0]; + return PlatformNavigationModel.All.FirstOrDefault(m => + m.Prefix.Equals(segment, StringComparison.OrdinalIgnoreCase)); } + } - protected override async Task OnInitializedAsync() + protected override async Task OnInitializedAsync() + { + try { - try - { - if (_synced) - return; + if (_synced) + return; - var authState = await AuthStateProvider.GetAuthenticationStateAsync(); - var user = authState.User; + var authState = await AuthStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; - if (user.Identity?.IsAuthenticated == true) - { - await UserSyncService.SyncUserAsync(user); - _synced = true; - } - } - catch (MicrosoftIdentityWebChallengeUserException) - { - Navigation.NavigateTo(RouteConstants.SignIn, forceLoad: true); - } + if (user.Identity?.IsAuthenticated == true) + { + await UserSyncService.SyncUserAsync(user); + _synced = true; + } + } + catch (MicrosoftIdentityWebChallengeUserException) + { + Navigation.NavigateTo(RouteConstants.SignIn, forceLoad: true); } + } } From a4ad93b0324cbd26032aa929261fb08a4b13d8d9 Mon Sep 17 00:00:00 2001 From: "Robert J. Good" Date: Fri, 24 Jul 2026 12:50:35 -0700 Subject: [PATCH 10/15] reduced verbose branding --- src/Presentation.Api/ConfigureServices.cs | 4 ++-- src/Presentation.Web/Features/HomePage.razor | 4 ++-- .../Features/Legal/AnalyticsNoticePage.razor | 4 ++-- .../Features/Legal/PrivacyPolicyPage.razor | 10 +++++----- .../Features/Legal/TermsOfUsePage.razor | 14 +++++++------- src/Presentation.Web/Shell/App.razor | 10 +++++----- src/Presentation.Web/Shell/Layout/MainLayout.razor | 2 +- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Presentation.Api/ConfigureServices.cs b/src/Presentation.Api/ConfigureServices.cs index 3cee019..8faacf5 100644 --- a/src/Presentation.Api/ConfigureServices.cs +++ b/src/Presentation.Api/ConfigureServices.cs @@ -113,9 +113,9 @@ private static OpenApiInfo CreateVersionInfo(ApiVersionDescription description) { var info = new OpenApiInfo { - Title = $"GoodToCode AgentFramework ({Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")})", + Title = $"GoodToCode Application API ({Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")})", Version = description.ApiVersion.ToString(), - Description = $"An API to interact with the AgentFramework", + Description = "An API to interact with this application", Contact = new OpenApiContact { Email = "developers@goodtocode.com", diff --git a/src/Presentation.Web/Features/HomePage.razor b/src/Presentation.Web/Features/HomePage.razor index 3560b77..673884b 100644 --- a/src/Presentation.Web/Features/HomePage.razor +++ b/src/Presentation.Web/Features/HomePage.razor @@ -4,7 +4,7 @@ @attribute [AllowAnonymous] -Goodtocode Quick-start for Microsoft Agent Framework +GoodToCode Application @@ -13,7 +13,7 @@ - Goodtocode Quick-start for Microsoft Agent Framework + GoodToCode Application diff --git a/src/Presentation.Web/Features/Legal/AnalyticsNoticePage.razor b/src/Presentation.Web/Features/Legal/AnalyticsNoticePage.razor index 669edf1..2c57757 100644 --- a/src/Presentation.Web/Features/Legal/AnalyticsNoticePage.razor +++ b/src/Presentation.Web/Features/Legal/AnalyticsNoticePage.razor @@ -9,7 +9,7 @@ Analytics Notice | GoodToCode - + @@ -26,7 +26,7 @@
Provider -

This site uses Microsoft Clarity for analytics and usability insights.

+

This application uses Microsoft Clarity for analytics and usability insights.

diff --git a/src/Presentation.Web/Features/Legal/PrivacyPolicyPage.razor b/src/Presentation.Web/Features/Legal/PrivacyPolicyPage.razor index 3d1b3f1..b6e698d 100644 --- a/src/Presentation.Web/Features/Legal/PrivacyPolicyPage.razor +++ b/src/Presentation.Web/Features/Legal/PrivacyPolicyPage.razor @@ -6,10 +6,10 @@ Privacy Policy | GoodToCode - + - + @@ -23,12 +23,12 @@
What we collect -

We may collect usage, device, and interaction data needed to operate, secure, and improve this website experience.

+

We may collect usage, device, and interaction data needed to operate, secure, and improve this application experience.

How we use data -

Data is used for site operation, performance monitoring, security, and product improvement.

+

Data is used for application operation, performance monitoring, security, and product improvement.

@@ -43,7 +43,7 @@
-

This policy is a template baseline and should be reviewed by legal counsel before production use.

+

This policy is baseline guidance and should be reviewed by legal counsel before production use.

diff --git a/src/Presentation.Web/Features/Legal/TermsOfUsePage.razor b/src/Presentation.Web/Features/Legal/TermsOfUsePage.razor index 3b41db1..9ff9f02 100644 --- a/src/Presentation.Web/Features/Legal/TermsOfUsePage.razor +++ b/src/Presentation.Web/Features/Legal/TermsOfUsePage.razor @@ -6,10 +6,10 @@ Terms of Use | GoodToCode - + - + @@ -22,23 +22,23 @@
- Use of website -

You agree to use this website lawfully and not attempt to disrupt service availability, integrity, or security.

+ Use of application +

You agree to use this application lawfully and not attempt to disrupt service availability, integrity, or security.

Content and ownership -

All template content, branding, and code samples remain subject to applicable ownership and licensing terms.

+

All content, branding, and code samples remain subject to applicable ownership and licensing terms.

No warranty -

This template is provided as-is without warranties of availability, fitness, or uninterrupted operation.

+

This application is provided as-is without warranties of availability, fitness, or uninterrupted operation.

Limitation of liability -

To the maximum extent allowed by law, the website operator is not liable for indirect or consequential damages from site use.

+

To the maximum extent allowed by law, the application operator is not liable for indirect or consequential damages from application use.