You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This document outlines the architectural approach for enhancing TechieBlog with a full stack modernization to .NET 10 LTS, PostgreSQL, and Microsoft Fluent UI Blazor. Its primary goal is to serve as the guiding architectural blueprint for AI-driven development of new features while ensuring seamless integration with the existing system.
Relationship to Existing Architecture:
This document supplements existing project architecture by defining how new components will integrate with current systems. The migration from MySQL/Blazorise/.NET 9 to PostgreSQL/Fluent UI/.NET 10 represents a significant modernization effort that preserves business logic while updating infrastructure and presentation layers.
1.1 Existing Project Analysis
Current Project State
Primary Purpose: Blazor-native blogging engine template for .NET developers
Strategy: Remove REST API layer entirely. BlogUI components call BlogEngine services directly via dependency injection.
Aspect
Approach
Authentication
JWT tokens validated via Blazor AuthenticationStateProvider
Authorization
[Authorize] attributes on pages, role-based policies
Versioning
N/A - internal service calls, no external API
6.2 Service Interface Patterns
All services follow this pattern for direct UI integration:
publicinterfaceIBlogService{// Async methods for UI responsivenessTask<BlogPost>GetPostByIdAsync(longpostId);Task<BlogPost>GetPostBySlugAsync(stringslug);Task<IEnumerable<BlogPost>>GetPublishedPostsAsync(intpage,intpageSize);Task<long>CreatePostAsync(BlogPostpost);TaskUpdatePostAsync(BlogPostpost);TaskDeletePostAsync(longpostId);// Sync methods where async provides no benefitintGetTotalPostCount();}
6.3 Removed API Endpoints
The following BlogSvc controllers are being removed:
/// <summary>/// Configures resilience policies for database operations./// Circuit breaker prevents cascade failures when database is unavailable./// </summary>/// <remarks>/// <para><b>Pattern:</b> Circuit Breaker with Retry</para>/// <para><b>Behavior:</b></para>/// <list type="number">/// <item>Retry failed operations up to 3 times with exponential backoff</item>/// <item>After 5 consecutive failures, circuit opens for 30 seconds</item>/// <item>During open state, fail fast without attempting database call</item>/// <item>After 30 seconds, allow one test request (half-open state)</item>/// <item>If test succeeds, close circuit; if fails, reopen for another 30 seconds</item>/// </list>/// </remarks>publicstaticclassResiliencePolicies{/// <summary>/// Creates a retry policy for transient database failures./// Retries 3 times with exponential backoff (1s, 2s, 4s)./// </summary>publicstaticAsyncRetryPolicyCreateRetryPolicy(){returnPolicy.Handle<NpgsqlException>().Or<TimeoutException>().WaitAndRetryAsync(retryCount:3,sleepDurationProvider: attempt =>TimeSpan.FromSeconds(Math.Pow(2,attempt-1)),onRetry:(exception,timeSpan,retryCount,context)=>{Log.Warning("Retry {RetryCount} after {Delay}ms due to {ExceptionType}: {Message}",retryCount,timeSpan.TotalMilliseconds,exception.GetType().Name,exception.Message);});}/// <summary>/// Creates a circuit breaker for database operations./// Opens after 5 failures, stays open for 30 seconds./// </summary>publicstaticAsyncCircuitBreakerPolicyCreateCircuitBreakerPolicy(){returnPolicy.Handle<NpgsqlException>().Or<TimeoutException>().CircuitBreakerAsync(exceptionsAllowedBeforeBreaking:5,durationOfBreak:TimeSpan.FromSeconds(30),onBreak:(exception,duration)=>{Log.Error("Circuit OPEN for {Duration}s due to: {Message}",duration.TotalSeconds,exception.Message);},onReset:()=>Log.Information("Circuit CLOSED - normal operation resumed"),onHalfOpen:()=>Log.Information("Circuit HALF-OPEN - testing..."));}}
Service-Level Resilience
Service
Retry Policy
Circuit Breaker
Fallback
Database (all repos)
3 retries, exponential backoff
5 failures → 30s open
Return cached data or error
Email (SMTP)
2 retries, 5s delay
3 failures → 60s open
Queue for later, notify admin
File Storage
2 retries, 2s delay
5 failures → 30s open
Return placeholder image
Graceful Degradation Strategy
Component
Degraded Behavior
User Impact
Analytics Service
Disable view tracking
None visible - analytics stops
Comment System
Show cached comments, disable new
Read-only comments
Rating System
Show cached ratings, disable voting
Read-only ratings
Newsletter
Queue emails, retry later
Delayed delivery
Search
Fall back to basic title search
Reduced search quality
8.5.2 Monitoring & Observability
Health Check Endpoints
/// <summary>/// Configures health checks for all critical dependencies./// Endpoint: /health (detailed) and /health/ready (load balancer)/// </summary>publicstaticvoidConfigureHealthChecks(IServiceCollectionservices,IConfigurationconfig){services.AddHealthChecks()// Database connectivity check.AddNpgSql(config.GetConnectionString("DefaultConnection"),name:"postgresql",failureStatus:HealthStatus.Unhealthy,tags:new[]{"db","critical"})// SMTP connectivity check (if configured).AddSmtpHealthCheck(options =>{options.Host=config["Smtp:Host"];options.Port=config.GetValue<int>("Smtp:Port");},name:"smtp",failureStatus:HealthStatus.Degraded,tags:new[]{"email"})// Memory check.AddProcessAllocatedMemoryHealthCheck(maximumMegabytesAllocated:500,name:"memory",tags:new[]{"memory"});}
Key Metrics to Track
Metric
Type
Alert Threshold
Description
http_request_duration_seconds
Histogram
p95 > 2s
Page load time
http_requests_total
Counter
N/A
Request volume
http_requests_failed_total
Counter
> 10/min
Error rate
db_connection_pool_size
Gauge
> 80% capacity
Connection exhaustion
circuit_breaker_state
Gauge
state = open
Service availability
active_users
Gauge
N/A
Concurrent sessions
post_views_total
Counter
N/A
Content engagement
Structured Logging Standards
/// <summary>/// Standard logging pattern for all service methods./// Includes correlation ID for request tracing across components./// </summary>publicasyncTask<BlogPost>GetPostByIdAsync(longpostId){usingvaractivity=ActivitySource.StartActivity("GetPostById");activity?.SetTag("postId",postId);logger.LogInformation("Fetching post {PostId} for user {UserId}",postId,currentUser?.UserId??0);try{varpost=awaitrepository.GetSingleAsync(postId);logger.LogInformation("Successfully retrieved post {PostId}: {Title}",postId,post?.Title??"NOT FOUND");returnpost;}catch(Exceptionex){logger.LogError(ex,"Failed to fetch post {PostId}. CircuitState: {CircuitState}",postId,circuitBreaker.CircuitState);throw;}}
Alerting Rules
Alert
Condition
Severity
Action
HighErrorRate
Error rate > 5% for 5 minutes
Critical
Page on-call
SlowResponses
p95 latency > 3s for 10 minutes
Warning
Investigate
DatabaseDown
Health check failing for 1 minute
Critical
Page on-call
CircuitOpen
Any circuit breaker open
Warning
Investigate
HighMemory
Memory > 80% for 5 minutes
Warning
Scale or investigate
EmailQueueBacklog
Queue > 100 emails for 30 minutes
Warning
Check SMTP
8.5.3 Caching Strategy
Cache Layers
Layer
Technology
TTL
Use Case
In-Memory
IMemoryCache
5-60 min
Site settings, categories, tags
Output Cache
Response caching
5 min
Public post listings, RSS feed
Distributed
Optional Redis
1-24 hr
Session data (if scaled)
Cache Implementation Pattern
/// <summary>/// Caching service for frequently accessed, rarely changed data./// Uses memory cache with automatic invalidation on updates./// </summary>/// <remarks>/// <para><b>Cache Strategy:</b></para>/// <list type="bullet">/// <item>Site settings: 60 minutes (invalidate on admin save)</item>/// <item>Categories/Tags: 30 minutes (invalidate on CRUD)</item>/// <item>Published posts list: 5 minutes (short for freshness)</item>/// <item>Individual posts: 10 minutes (invalidate on edit)</item>/// </list>/// </remarks>publicclassCachingService:ICachingService{privatereadonlyIMemoryCachecache;privatereadonlyILogger<CachingService>logger;/// <summary>/// Retrieves site settings from cache or database./// Cache key: "SiteSettings"/// TTL: 60 minutes/// </summary>publicasyncTask<SiteSettings>GetSiteSettingsAsync(){returnawaitcache.GetOrCreateAsync("SiteSettings",async entry =>{entry.AbsoluteExpirationRelativeToNow=TimeSpan.FromMinutes(60);entry.Priority=CacheItemPriority.High;logger.LogDebug("Cache miss for SiteSettings - fetching from database");returnawaitsettingsRepo.GetSettingsAsync();});}/// <summary>/// Invalidates all caches related to a specific post./// Called after post create, update, or delete./// </summary>publicvoidInvalidatePostCaches(longpostId){cache.Remove($"Post:{postId}");cache.Remove("PublishedPosts");cache.Remove("RecentPosts");cache.Remove("PopularPosts");logger.LogInformation("Invalidated caches for post {PostId}",postId);}}
CRITICAL RULE: No underscores (_) in any identifier names. Use PascalCase or camelCase exclusively.
9.1.1 C# Code Naming
Element
Convention
Correct
Incorrect
Classes
PascalCase
BlogPostService
Blog_Post_Service
Interfaces
PascalCase with I prefix
IBlogPostRepo
I_Blog_Post_Repo
Methods
PascalCase
GetAllPosts()
Get_All_Posts()
Properties
PascalCase
PostTitle
Post_Title
Local Variables
camelCase
blogPost
blog_post
Parameters
camelCase
postId
post_id
Private Fields
camelCase (no underscore prefix)
connectionString
_connectionString
Constants
PascalCase
MaxPageSize
MAX_PAGE_SIZE
Enums
PascalCase
TokenStatus.ValidToken
Token_Status.Valid_Token
9.1.2 Database Object Naming
Element
Convention
Correct
Incorrect
Tables
PascalCase
BlogPost, UserFavorite
blog_post, user_favorite
Columns
PascalCase
PostId, CreatedOn
post_id, created_on
Stored Procedures/Functions
PascalCase
GetPostById, InsertBlogPost
get_post_by_id, sp_InsertBlogPost
Indexes
PascalCase with Idx prefix
IdxPostSlug
idx_post_slug
Foreign Keys
PascalCase with Fk prefix
FkPostUserId
fk_post_user_id
Primary Keys
PascalCase with Pk prefix
PkBlogPost
pk_blog_post
9.1.3 File and Folder Naming
Element
Convention
Correct
Incorrect
C# Files
PascalCase
BlogPostRepo.cs
blog_post_repo.cs
Razor Files
PascalCase
PostEditor.razor
post_editor.razor
CSS Files
kebab-case
fluent-modern.css
fluent_modern.css
SQL Scripts
Number prefix + PascalCase
001-CreateTables.sql
001_create_tables.sql
Folders
PascalCase
BlogPages, DbAccess
Blog_Pages, Db_Access
9.2 Data Access Standards (Dapper ORM)
MANDATORY: Continue using Dapper as the micro-ORM for all data access.
9.2.1 Repository Pattern with Dapper
/// <summary>/// Repository for managing blog post data access operations./// Implements the generic repository pattern using Dapper for PostgreSQL./// Used by BlogSvc to perform CRUD operations on the Post table./// </summary>publicclassBlogPostRepo:GenericRepository<BlogPost>,IBlogPostRepo{/// <summary>/// Initializes a new instance of BlogPostRepo with database connection./// The connection string is injected via DI from appsettings.json./// </summary>/// <param name="connectionString">PostgreSQL connection string from configuration.</param>publicBlogPostRepo(stringconnectionString):base(connectionString){}/// <summary>/// Retrieves a single blog post by its unique identifier./// Calls the GetPostById stored function in PostgreSQL./// Returns null if no post exists with the given ID./// </summary>/// <param name="postId">The unique identifier of the blog post.</param>/// <returns>BlogPost entity or null if not found.</returns>publicoverrideBlogPostGetSingle(longpostId){usingvarconnection=GetOpenConnection();varparameters=newDynamicParameters();parameters.Add("postId",postId);returnconnection.Query<BlogPost>("SELECT * FROM GetPostById(@postId)",parameters).FirstOrDefault();}}
9.2.2 Dapper Best Practices
Practice
Implementation
Connection Management
Use using statements for automatic disposal
Parameters
Always use DynamicParameters - never concatenate SQL
Stored Functions
Prefer PostgreSQL functions over inline SQL
Async Operations
Use QueryAsync, ExecuteAsync for all DB calls
Result Mapping
Leverage Dapper's automatic mapping to POCOs
9.3 XML Documentation Standards (MANDATORY)
CRITICAL: Every class and method MUST have XML documentation comments.
9.3.1 Class Documentation Template
/// <summary>/// [Brief one-line description of what this class does]/// </summary>/// <remarks>/// <para><b>Purpose:</b> [Explain the role of this class in the solution]</para>/// <para><b>Code Flow:</b> [Describe how this class fits into the overall flow]</para>/// <para><b>Dependencies:</b> [List key dependencies and why they're needed]</para>/// <para><b>Usage:</b> [Explain where/how this class is used]</para>/// </remarks>/// <example>/// <code>/// // Example usage of this class/// var service = new ExampleService(dependency);/// var result = service.DoSomething();/// </code>/// </example>publicclassExampleClass{// ...}
9.3.2 Method Documentation Template
/// <summary>/// [Brief one-line description of what this method does]/// </summary>/// <remarks>/// <para><b>Business Logic:</b> [Explain any business rules applied]</para>/// <para><b>Flow:</b> [Step-by-step description for complex methods]</para>/// <para><b>Side Effects:</b> [Describe any state changes or external calls]</para>/// </remarks>/// <param name="paramName">[Description of parameter and valid values]</param>/// <returns>[Description of return value and possible states]</returns>/// <exception cref="ExceptionType">[When this exception is thrown]</exception>/// <example>/// <code>/// var result = MyMethod("input");/// </code>/// </example>publicReturnTypeMethodName(ParamTypeparamName){// ...}
9.3.3 Documentation Examples for TechieBlog
Service Class Example:
/// <summary>/// Provides authentication and authorization services for the TechieBlog application./// </summary>/// <remarks>/// <para><b>Purpose:</b> Handles user login, signup, JWT token generation, and token validation./// This is the central authentication service used by both the UI layer and any future API endpoints.</para>////// <para><b>Code Flow:</b></para>/// <list type="number">/// <item>UI calls AppLogin() with encrypted credentials</item>/// <item>Credentials are decrypted using AppEncrypt utility</item>/// <item>Password is hashed and validated against BlogUserRepo</item>/// <item>On success, JWT token is generated with user claims</item>/// <item>Token is stored in UserLogin table for tracking</item>/// <item>Encrypted user data and token returned to UI</item>/// </list>////// <para><b>Dependencies:</b></para>/// <list type="bullet">/// <item>IBlogUserRepo - User data access</item>/// <item>IUserLoginRepository - Token tracking</item>/// <item>AppEncrypt - Credential encryption/decryption</item>/// </list>////// <para><b>Security Note:</b> All sensitive data is encrypted in transit using AppEncrypt./// Passwords are hashed using SHA256 before database comparison.</para>/// </remarks>publicclassAuthSvc{/// <summary>/// Authenticates a user with email and password credentials./// </summary>/// <remarks>/// <para><b>Business Logic:</b></para>/// <list type="number">/// <item>Decrypt email and password from SvcData wrapper</item>/// <item>Hash the password using AppEncrypt.CreateHash()</item>/// <item>Query BlogUserRepo for matching credentials</item>/// <item>Generate 15-day JWT token with user claims (ID, Name, Email, Role)</item>/// <item>Store login record in UserLogin table</item>/// <item>Return encrypted user data with tokens</item>/// </list>////// <para><b>Token Claims:</b> PrimarySid (UserId), Name, Email, Role</para>/// </remarks>/// <param name="loginData">Encrypted login credentials containing LoginEmail and LoginPass.</param>/// <returns>/// SvcData containing encrypted user profile and JWT token on success./// Returns null if credentials are invalid or user not found./// </returns>/// <exception cref="Exception">Logged and rethrown on database or encryption errors.</exception>publicSvcDataAppLogin(SvcDataloginData){// Implementation...}}
Repository Class Example:
/// <summary>/// Data access repository for BlogPost entities using Dapper ORM./// </summary>/// <remarks>/// <para><b>Purpose:</b> Provides CRUD operations for the Post table in PostgreSQL./// Extends GenericRepository to inherit common data access patterns.</para>////// <para><b>Code Flow:</b> Called by BlogSvc service layer. Each method opens a new/// database connection, executes a stored function, and returns mapped entities.</para>////// <para><b>Database Objects Used:</b></para>/// <list type="bullet">/// <item>GetPostById - Retrieve single post</item>/// <item>GetPagedBlogList - Paginated post listing</item>/// <item>InsertPost - Create new post</item>/// <item>UpdatePost - Modify existing post</item>/// </list>/// </remarks>publicclassBlogPostRepo:GenericRepository<BlogPost>,IBlogPostRepo{// ...}
CRITICAL: All SQL scripts must include detailed comments explaining purpose and logic.
9.4.1 Table Creation Script Template
-- ============================================================================-- Script: 001-CreateTables.sql-- Purpose: Creates all core tables for TechieBlog PostgreSQL database-- Author: [Developer Name]-- Created: [Date]-- Modified: [Date] - [Description of changes]-- ============================================================================-- ============================================================================-- TABLE: BlogPost-- Purpose: Stores all blog post content and metadata---- Relationships:-- - BlogUser (UserId) - Author of the post-- - Category (via PostCategory junction) - Post categorization-- - Series (SeriesId) - Optional series grouping---- Business Rules:-- - Slug must be unique for SEO-friendly URLs-- - Published = false indicates draft status-- - ScheduledFor enables future publishing---- Indexes:-- - PkBlogPost: Primary key on PostId-- - IdxPostSlug: Unique index for URL lookups-- - IdxPostUserId: Foreign key index for author queries-- ============================================================================CREATETABLEBlogPost (
-- Primary identifier, auto-generated
PostId BIGSERIALPRIMARY KEY,
-- Post title displayed in UI and used for SEO
Title VARCHAR(550) NOT NULL,
-- URL-friendly identifier, auto-generated from title-- Must be unique across all posts for clean URLs
Slug VARCHAR(550) UNIQUE,
-- Short summary shown in post listings and meta description
Abstract VARCHAR(550),
-- Full post content in Markdown format-- Rendered to HTML on display
PostContent TEXTNOT NULL,
-- Timestamp when post was first created (draft or published)
CreatedOn TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- Timestamp of last modification
UpdatedOn TIMESTAMP,
-- Foreign key to BlogUser - the post author-- Required - every post must have an author
UserId BIGINTNOT NULLREFERENCES BlogUser(UserId),
-- Comma-separated tag names for quick display-- Normalized tags stored in PostTag junction table
Tags VARCHAR(550),
-- Path to featured/hero image for post
FeaturedImage VARCHAR(550),
-- Publication status: false = draft, true = published
Published BOOLEANNOT NULL DEFAULT FALSE,
-- Future publish date for scheduled posts-- NULL means immediate publish when Published = true
ScheduledFor TIMESTAMP,
-- SEO: Custom title for search engines (overrides Title if set)
SeoTitle VARCHAR(255),
-- SEO: Meta description for search results
SeoDescription VARCHAR(500),
-- Optional series grouping for multi-part content
SeriesId BIGINTREFERENCES Series(SeriesId),
-- Order within series (1, 2, 3, etc.)
SeriesOrder INT
);
-- Index for fast slug lookups (used in URL routing)CREATEUNIQUE INDEXIdxPostSlugON BlogPost(Slug);
-- Index for author's post queriesCREATEINDEXIdxPostUserIdON BlogPost(UserId);
-- Index for published posts sorted by date (common query pattern)CREATEINDEXIdxPostPublishedON BlogPost(Published, CreatedOn DESC);
9.4.2 Stored Function Documentation Template
-- ============================================================================-- FUNCTION: GetPostById-- Purpose: Retrieves a single blog post with author information---- Parameters:-- @postId (BIGINT) - The unique identifier of the post to retrieve---- Returns: Single row with post data and author name, or empty if not found---- Business Logic:-- 1. Joins BlogPost with BlogUser to get author's full name-- 2. Returns all post fields needed for display-- 3. Does NOT check Published status - caller must filter if needed---- Called By:-- - BlogPostRepo.GetSingle() - Single post retrieval-- - BlogSvc.GetPostForEdit() - Admin post editing---- Performance Notes:-- - Uses primary key lookup - O(1) performance-- - Consider caching results for frequently accessed posts---- Example Usage:-- SELECT * FROM GetPostById(123);-- ============================================================================CREATE OR REPLACEFUNCTIONGetPostById(postId BIGINT)
RETURNS TABLE (
PostId BIGINT,
Title VARCHAR(550),
Slug VARCHAR(550),
Abstract VARCHAR(550),
PostContent TEXT,
CreatedOn TIMESTAMP,
UpdatedOn TIMESTAMP,
UserId BIGINT,
BlogWriter VARCHAR(201), -- FirstName + ' ' + LastName
Tags VARCHAR(550),
FeaturedImage VARCHAR(550),
Published BOOLEAN,
ScheduledFor TIMESTAMP
) AS $$
BEGIN-- Return post with joined author name-- BlogWriter is computed from user's first and last name
RETURN QUERY
SELECTp.PostId,
p.Title,
p.Slug,
p.Abstract,
p.PostContent,
p.CreatedOn,
p.UpdatedOn,
p.UserId,
CONCAT(u.FirstName, '', u.LastName)::VARCHAR(201) AS BlogWriter,
p.Tags,
p.FeaturedImage,
p.Published,
p.ScheduledForFROM BlogPost p
INNER JOIN BlogUser u ONp.UserId=u.UserIdWHEREp.PostId= postId;
END;
$$ LANGUAGE plpgsql;
Enable <Nullable>enable</Nullable> in all projects
Async/Await
All database and I/O operations must be async
Dependency Injection
Constructor injection only, no service locator pattern
Fluent UI Components
Use Fluent UI components exclusively, no mixing with Blazorise
CSS Variables
All colors, fonts, spacing via CSS custom properties only
9.5.2 Error Handling Standards
/// <summary>/// Standard error handling pattern for service methods./// </summary>/// <remarks>/// All exceptions are logged with full context before rethrowing./// Never swallow exceptions silently - always log or handle explicitly./// </remarks>publicasyncTask<Result<T>>ServiceMethodAsync(){try{// Business logic herereturnResult<T>.Success(data);}catch(Exceptionex){// Log with structured data for debugginglogger.LogError(ex,"Failed to execute {Method} for {EntityId}. Context: {Context}",nameof(ServiceMethodAsync),entityId,additionalContext);// Rethrow or return failure resultreturnResult<T>.Failure(ex.Message);}}
9.6 Critical Integration Rules
Rule
Implementation
Existing API Compatibility
N/A - BlogSvc removed, service interfaces updated
Database Integration
All queries via Dapper, stored functions preferred
Error Handling
Use Result pattern or exceptions with logging
Logging Consistency
Serilog with structured logging, correlation IDs
10. Testing Strategy
10.1 Integration with Existing Tests
Existing Test Framework: None
Test Organization: New test project required
Coverage Requirements: Target 80% for BlogEngine services
10.2 New Testing Requirements
Unit Tests for New Components
Aspect
Specification
Framework
xUnit
Location
TechieBlog.Tests/ (new project)
Coverage Target
80% for BlogEngine, 60% for BlogUI components
Integration with Existing
Test project references BlogEngine, BlogUI
Integration Tests
Aspect
Specification
Scope
Repository layer with test database
Existing System Verification
Verify migration doesn't break data access
New Feature Testing
All new services tested against PostgreSQL
Test Database
PostgreSQL in Docker or test containers
Regression Testing
Aspect
Specification
Existing Feature Verification
Login, post CRUD, comments working after migration
Automated Regression Suite
CI pipeline runs all tests on every PR
Manual Testing Requirements
Visual verification of all 28 UI mockups
11. Security Integration
11.1 Existing Security Measures
Measure
Current Implementation
Authentication
JWT tokens with custom claims
Authorization
Role claim in JWT (single role per user)
Data Protection
Custom encryption (AppEncrypt) for sensitive data
Security Tools
None
11.2 Enhancement Security Requirements
Requirement
Implementation
New Security Measures
Rate limiting on auth endpoints, password reset token expiry
Integration Points
AuthSvc for all auth, middleware for rate limiting
Manual review post-MVP, no automated pentest tooling
11.4 Accessibility Architecture (MANDATORY)
Compliance Target: WCAG 2.1 Level AA
11.4.1 Semantic HTML Standards
All Blazor components MUST use semantic HTML elements:
Purpose
Required Element
Avoid
Page Title
<h1> (one per page)
Multiple h1s, div with large font
Sections
<section>, <article>, <aside>
Generic divs
Navigation
<nav> with aria-label
Div with links
Lists
<ul>, <ol>, <dl>
Divs with line breaks
Buttons
<button> or <FluentButton>
Clickable divs/spans
Links
<a href> for navigation
Buttons for navigation
Forms
<form> with <label> associations
Inputs without labels
Tables
<table> with <th scope>
Divs styled as tables
11.4.2 ARIA Implementation Guidelines
@* Component: PostCard.razor Accessibility: Implements article landmark with descriptive aria-label*@
<articlearia-labelledby="post-title-@PostId"class="post-card">
<h2id="post-title-@PostId">@Title</h2>
@* Rating component with accessible label *@
<divrole="img"aria-label="Rating: @Rating out of 5 stars">
<RatingStarsValue="@Rating"ReadOnly="true" />
</div>
@* Read more link with descriptive text for screen readers *@
<ahref="/post/@Slug"aria-describedby="post-title-@PostId">
Read more<spanclass="visually-hidden"> about @Title</span>
</a>
</article>
/// <summary>/// JavaScript interop for managing focus in Blazor components./// Required for modal dialogs, dropdown menus, and dynamic content./// </summary>publicclassFocusManager{privatereadonlyIJSRuntimejsRuntime;/// <summary>/// Traps focus within a modal dialog./// Focus cycles through focusable elements within the container./// </summary>/// <param name="containerId">ID of the modal container element.</param>publicasyncTaskTrapFocusAsync(stringcontainerId){awaitjsRuntime.InvokeVoidAsync("accessibilityHelpers.trapFocus",containerId);}/// <summary>/// Restores focus to the element that triggered a modal./// Called when modal is closed./// </summary>publicasyncTaskRestoreFocusAsync(stringtriggerElementId){awaitjsRuntime.InvokeVoidAsync("accessibilityHelpers.restoreFocus",triggerElementId);}}
Keyboard Shortcuts
Action
Keyboard Shortcut
Context
Skip to main content
Tab → Enter on skip link
All pages
Toggle theme
Alt + T
Global
Open search
/ or Ctrl + K
When not in text input
Close modal
Escape
Any open modal
Navigate menu
Arrow keys
Dropdown menus
Submit form
Enter
Form inputs
Cancel action
Escape
Forms, modals
Focus Indicator Standards
/* * Focus indicators must be visible and have sufficient contrast. * Minimum 3:1 contrast ratio against adjacent colors. * Must not rely on color alone. *//* Default focus style for all interactive elements */:focus-visible {
outline:2px solid var(--focus-ring-color,#0078d4);
outline-offset:2px;
}
/* Never remove focus indicators */:focus {
outline: none; /* Only if custom focus style is applied */
}
/* High contrast mode support */@media (forced-colors: active) {
:focus-visible {
outline:3px solid CanvasText;
}
}
11.4.4 Screen Reader Compatibility
Visually Hidden Text Utility
/* * Text that is hidden visually but available to screen readers. * Used for additional context that sighted users don't need. */
.visually-hidden {
position: absolute;
width:1px;
height:1px;
padding:0;
margin:-1px;
overflow: hidden;
clip:rect(0,0,0,0);
white-space: nowrap;
border:0;
}
/* Show on focus for skip links */
.visually-hidden-focusable:focus {
position: static;
width: auto;
height: auto;
margin:0;
overflow: visible;
clip: auto;
white-space: normal;
}
Announcements for Dynamic Content
@* LiveRegion component for announcing dynamic changes. Use aria-live="polite" for non-urgent updates. Use aria-live="assertive" for errors or critical alerts.*@
<divaria-live="polite"aria-atomic="true"class="visually-hidden">
@AnnouncementText
</div>
@code{
[Parameter]
publicstringAnnouncementText{get; set; }/// <summary>/// Announces a message to screen readers./// Message is automatically cleared after announcement./// </summary>publicasyncTaskAnnounceAsync(stringmessage)
{AnnouncementText=message;
StateHasChanged();
awaitTask.Delay(100); // Allow screen reader to pick upAnnouncementText="";
StateHasChanged();
}}
11.4.5 Accessibility Testing Requirements
Test Type
Tool
Frequency
Pass Criteria
Automated Scan
axe DevTools
Every PR
No critical/serious violations
Keyboard Testing
Manual
Every new component
All functions keyboard accessible
Screen Reader
NVDA / VoiceOver
Major features
Content fully navigable and understandable
Color Contrast
WebAIM Contrast Checker
Theme changes
4.5:1 for text, 3:1 for large text
Zoom Testing
Browser zoom 200%
Major layouts
No horizontal scroll, content readable
Accessibility Checklist per Component
Before any Blazor component is considered complete:
Semantic HTML elements used appropriately
All interactive elements are keyboard accessible
Focus order is logical (follows visual order)
Focus indicator is visible (2px solid outline minimum)
ARIA labels provided where native labels insufficient
Color is not sole means of conveying information
Text contrast meets WCAG AA (4.5:1 normal, 3:1 large)
Tested with screen reader (at least one)
No keyboard traps (focus can always escape)
Dynamic content changes are announced
12. Checklist Results Report
Validation Date: December 16, 2025
Checklist: architect-checklist.md
Overall Readiness: MEDIUM (60% → 85% after updates)
Summary by Section
Section
Original
After Updates
Status
Requirements Alignment
67%
67%
✅ Acceptable
Architecture Fundamentals
90%
90%
✅ Strong
Technical Stack
70%
70%
✅ Acceptable
Frontend Design
48%
60%
⚠️ Improved
Resilience & Operations
20%
85%
✅ Fixed
Security & Compliance
35%
55%
⚠️ Improved
Implementation Guidance
48%
65%
⚠️ Improved
Dependency Management
53%
53%
⚠️ Acceptable
AI Agent Suitability
80%
85%
✅ Strong
Accessibility
0%
80%
✅ Fixed
Critical Items Addressed
Item
Status
Section Added
Circuit breakers and retry policies
✅ Added
8.5.1 Resilience Patterns
Graceful degradation strategy
✅ Added
8.5.1 Graceful Degradation
Monitoring and alerting
✅ Added
8.5.2 Monitoring & Observability
Caching strategy
✅ Added
8.5.3 Caching Strategy
Accessibility architecture
✅ Added
11.4 Accessibility Architecture
ARIA implementation guidelines
✅ Added
11.4.2 ARIA Implementation
Keyboard navigation requirements
✅ Added
11.4.3 Keyboard Navigation
Remaining Recommendations
Priority
Item
Notes
Should-Fix
Frontend state management patterns
Document beyond auth state
Should-Fix
Development environment setup guide
Add Docker Compose for PostgreSQL
Nice-to-Have
Architecture Decision Records
Document why alternatives rejected
Nice-to-Have
Performance/load testing approach
k6 or similar for 100 user target
Approval Status
Approved for Development: YES (Conditional)
Development may proceed with all Epics. The following items should be addressed during implementation:
Document state management patterns when implementing complex forms (Story 3.x)
Create development setup guide during Epic 1 foundation work
Add ADRs as significant decisions are made
13. Next Steps
13.1 Story Manager Handoff
For Story Manager (Sarah):
This architecture document provides the technical blueprint for TechieBlog 2.0 brownfield enhancement. Key points for story planning: