From 114ff2a8515960d6c90efb9580708c58dfcc1ab9 Mon Sep 17 00:00:00 2001
From: guneyunus <92909421+guneyunus@users.noreply.github.com>
Date: Thu, 4 Jun 2026 22:39:50 +0300
Subject: [PATCH] merge (#1)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* build(migrator): chiseled Dockerfile + APP_UID + csproj container hardening
* build(deploy): docker-compose .env.example with all knobs documented
* feat(docs): redesign landing — conversion-focused with modern code window
Replaces the placeholder hero/feature-grid with a six-section landing
designed for developer conversion:
- Hero: editorial display headline with gradient-text inflection, single
strong subhead, dual CTA, immediate install command (copyable) below
the fold. Brand-shadow blur + subtle dot-field for atmosphere.
- CodeFirst: split section with editorial copy left, custom-framed code
window right showing real RegisterUser feature folder (endpoint /
handler / validator tabs). Mac-style traffic lights, green-accent
active tab underline, hover gradient border, status footer.
- WhatsIncluded: 12-item refined checklist (not boxy cards). Scannable.
- ModuleShowcase: 3 deep cards (Identity / Multitenancy / Auditing) —
tagline, capability bullets, real code snippet, deep-dive link each.
Retains magnetic-shimmer hover from blog token system.
- TechStack: 12-tech compact grid with vertical border accent on hover.
- FinalCta: editorial close with repeated install command + CTAs +
'≈5 minutes to localhost' time-to-running indicator.
Codeblocks use Expressive Code's programmatic component with
frame=none so the custom window chrome (traffic lights, tabs, status
bar) wraps clean EC output. EC config externalized to ec.config.mjs as
required by the programmatic path.
Every color/surface uses semantic tokens — no hardcoded colors except
mac traffic light hexes (intentional, vendor-recognizable signal).
Cleanly inverts between light and dark.
* build(deploy): postgres init SQL — required extensions
* build(deploy): production docker-compose.yml — full stack on one host
* docs(deploy): five-minute docker-compose deploy guide
* fix(docs): theme toggle, nav active, alignment, code panel polish
Batch of bug fixes from initial review pass:
- ThemeToggle: switched script to is:inline plain JS (was hoisted module
with TS annotations). Dropped View Transitions wrapper — was the
silent failure mode. Listener now attaches deterministically.
- Header nav active state: previous startsWith() matched /docs/ for any
/docs/* path, lighting up both 'Docs' and the actual section. New
logic picks the longest matching prefix as the single active item.
- Container width: header + footer were max-w-6xl while docs grid was
max-w-7xl, causing visible left/right misalignment on docs pages.
Unified all surfaces to max-w-7xl.
- prose.css ul bullets: referenced ../icons/svgs/star.svg from the blog
that we never copied → runtime 404. Replaced with a green dot using
--primary token (consistent with our design).
- Dropped @astrojs/react integration: zero React components remain
after ThemeToggle was converted to Astro. Eliminated the Vite 7
rolldown 'Missing field moduleType' dev errors from react-refresh.
- Code panel rewrite (code-window.css): now targets the real DOM
(.expressive-code .frame, not the outer div). Soft elevation, brand
hover border, language badge floats above pre on hover, copy button
positioned cleanly inside header. Mac traffic lights (already from
EC) sit naturally in the gradient title bar.
* fix(docs): use blog's exact MDX codeblock style (drop custom code-window.css)
The blog's MDX codeblock treatment lives in prose.css and is minimal:
rounded-xl pre, near-invisible white-with-low-opacity border, brand-tinted
border on hover, copy button scales 1.05 with brand-tinted bg on hover,
plus a gradient line-highlight indicator. My custom code-window.css was
overdesigning a problem the blog had already solved.
- Deleted docs/src/styles/code-window.css
- Removed its import from globals.css
- prose.css already inherited from the blog verbatim; updated the two
blog-purple references: rgb(118 89 236, ...) → rgb(22 163 74, ...)
(#16a34a, our --primary-soft mid-green) and #4bf3c8 → #4ade80 (our
dark-mode --primary-soft) in the line-highlight gradient.
* fix(deploy): resolve 3 issues found during e2e smoke test
- docker-compose migrator: add --seed flag so IdentityDbInitializer
runs SeedAdminUserAsync and admin@root.com is seeded on first boot
- docker-compose api: add AllowedHosts=* env var to override the
appsettings.Production.json restriction (was api.example.com,
which Kestrel rejected for localhost requests)
- docker-compose api: add HangfireOptions__UserName/Password env vars
(required by ValidateOnStart() in Jobs/Extensions.cs); document in
.env.example with generation instructions
- appsettings.Production.json: set AllowedHosts to * (host filtering
belongs at the reverse proxy layer, not Kestrel)
All surfaces verified: health/live, health/ready (all 14 checks
Healthy), admin config.json, dashboard config.json, both 200 OK,
admin@root.com login returns JWT, profile returns correct email.
Volume durability: migrator re-runs idempotently on second boot.
* test(deploy): end-to-end docker-compose smoke verified locally
All 7 steps passed: stack up, migrator exit 0, health Healthy,
admin/dashboard config.json correct, login + profile verified,
volume durability confirmed, stack torn down and .env removed.
* docs: point README at deploy/docker for the production deploy story
* feat(docs): redesign CodeFirst as VS Code-style editor with file tree
Shows the full VSA feature-folder structure as a real-feeling IDE pane:
- Title bar: mac traffic lights, breadcrumb path (Modules.Identity /
Features / v1 / Users / RegisterUser / .cs), C# lang badge
- Left column (file tree): the Users/ folder with RegisterUser/
expanded showing its three files, and five other collapsed feature
folders (LoginUser, AssignUserRole, ChangePassword, DeleteUser,
RefreshToken) — visually demonstrates that each feature is its own
folder with its own files
- Active file: green left-border accent + tinted background + primary
text color. Inactive: muted with hover row tint
- Right column: code panel with EC frame=none (chrome stripped, just
the syntax-highlighted body)
- Status footer: project name, encoding, line count updates with selection
- Breadcrumb's filename and line count both swap on click
- Hover gradient ring behind the editor (mask-composite trick)
- Mobile: tree stacks above code with bottom border instead of right
- Indentation guide line (1px vertical) inside the RegisterUser folder
- Chevrons + folder icons via inline SVGs (lucide-shaped) — no astro-icon
dep needed for the section
* docs(spec): editorial alignment of docs site with codewithmukesh/blog
Spec for the polish pass that ports the blog's brand/* primitive system
(Button, Card, Callout, Pill, Kbd) into the docs site, adds the eyebrow
numbering pattern, wires magnetic-cards + animated-IDE patterns, and
aligns ExpressiveCode theme. Green stays as the FSH brand color.
Co-Authored-By: Claude Opus 4.7 (1M context)
* fix(container): use numeric UID 1654 in
MSBuild does not expand shell-style variables in , so
the literal string "$APP_UID" was written into the OCI image config's
User field by `dotnet publish /t:PublishContainer`. At `docker run`
time the daemon could not resolve that "user" and exited with
`unable to find user $APP_UID: no matching entries in passwd file`,
breaking the DbMigrator Container Smoke job in CI.
The hand-written Dockerfiles keep `USER $APP_UID` because Docker's
parser does expand ENV vars inside the USER directive at build time.
Co-Authored-By: Claude Opus 4.7 (1M context)
* feat(docs): editorial alignment with codewithmukesh/blog
Ports the blog's brand primitive system (Button, Card, Callout, Pill,
Kbd) into the docs site and adopts the magazine-style numbered eyebrow
rhythm across the six landing sections. Green stays as the FSH brand
color; primitives flow through var(--primary) so the swap is automatic.
- brand/ primitives ported verbatim with var(--primary) substitutions
in Card.astro (color-mix on hover glows/borders).
- New SectionEyebrow primitive: mono uppercase tabular-nums "01 · …"
pattern with a primary-tinted accent rule.
- New AnimatedIde wrapper: pointer-tracked 3D tilt + cursor-following
radial spotlight via --mouse-x / --mouse-y custom properties; wraps
the CodeFirst editor. No-op on touch / reduced-motion.
- Magnetic-cards script ported into docs/src/scripts/ and wired from
BaseLayout. Handles both [data-magnetic-card] and [data-magnetic-ide].
- ExpressiveCode config extracted into ec.config.mjs (function-valued
options aren't JSON-serializable when inlined). Houston dark theme +
green-tinted frame/marker accents.
- Hero CTAs + FinalCta CTAs + Header GitHub button now use the Button
primitive (ink-on-paper primary). Header Ctrl·K hint uses .
- Hero gains a tabular-nums proof strip (".NET 10 · 3 modules · 598
tests · 5 min to localhost") + mono "$ get running locally" prompt.
- Prose list marker swaps from a dot to the star SVG used by the blog.
Build green; 10 pages built in 2.13s.
* feat(docs): redesign Hero with animated terminal + stat cards
Replaces the install-and-CTA-focused hero with a magazine-style
conversion surface modeled on the dotnet-claude-kit hero from
codewithmukesh/blog.
What changed
- Magazine eyebrow rule across the top: "Open source · .NET 10 starter
kit ─── v1 · MIT · production-ready" (tabular-nums mono, primary
on the left, muted on the right, hairline rule between).
- Display headline now splits font-light lead-in ("The .NET starter
kit") from font-bold payoff ("that actually ships.") with a green
gradient underline strip beneath the bold phrase.
- Subtitle bolds the three pillars (multitenancy, identity,
observability) and lands on a feature-work-not-scaffolding payoff.
- Trust strip below the CTAs: v1 · MIT licensed · .NET 10 / C# 14 ·
PostgreSQL · Redis · React.
- Animated terminal preview: typewriter for the dotnet run command,
then staggered reveal of an Aspire orchestration boot sequence
(PostgreSQL, Redis, API, Dashboard, Hangfire) ending in
"Ready in 4.2s · 5 services orchestrated · multitenancy enforced".
The blinking emerald cursor animates while the command types out.
- "What ships in v1" stat-card grid (4 metric cards): 3 modules · 12+
building blocks · 598 tests · 5 min to localhost. Each card has a
large tabular-nums display number with a tinted unit superscript, a
hover corner glow, and an EQ-tick bar that "dances" on hover.
Motion and accessibility
- All animations gated on prefers-reduced-motion: the typewriter
jumps to its full text immediately, line reveals fire without delay,
and the cursor blink + tick dance are disabled.
- Terminal cursor + tick dance are CSS keyframe animations, GPU-
composited via transform/opacity only.
- Script is idempotent (data-hero-init guard) so it survives
astro:after-swap from Astro view transitions.
Build green; 10 pages built in 1.90s.
* feat(docs): header nav — add Home, drop Reference
Order is now Home · Docs · Modules · Recipes (down from 4 sections
that put Docs first and included a stubbed Reference link). The
liquid hover indicator and longest-prefix active-state logic both
work unchanged.
* feat(docs): wire FullStackHero logo + full favicon set
Header brand link now leads with the 512×512 FSH logo (rendered at
28×28, rounded corners) ahead of the lowercase "fullstackhero" mark
and the "/docs" mono suffix.
BaseLayout serves the full favicon ladder so every browser and OS gets
a sensible icon:
- favicon.ico (broad fallback, 48×48 multi-res)
- favicon.svg (modern vector)
- favicon-32x32.png + favicon-16x16.png (older PNG ladder)
- logo-fullstackhero.png as the apple-touch-icon
fullstackhero.svg is left in public/ for ad-hoc use even though it's
not wired by default.
* feat(docs): drop "/docs" suffix from header brand link
Logo + lowercase fullstackhero wordmark only now — the /docs mono
suffix made sense before there was an icon, but the visual rhythm is
cleaner without it.
* feat(docs): unify all landing sections under Hero's design language
Brings CodeFirst, WhatsIncluded, ModuleShowcase, TechStack, and FinalCta
into the same magazine-style chrome as the Hero — magazine eyebrow rule,
light/bold split headlines with a green gradient underline strip, inline-
bold subtitles, and small editorial flourishes per section.
What landed
- SectionEyebrow refactored from inline pill → full-width magazine rule
(left primary mono label, hairline rule, optional right meta tag).
- All section containers aligned to max-w-7xl with px-4 sm:px-6 lg:px-8
(matches the Header so brand logo and section content land on the same
vertical lines).
Section-by-section
- 01 CodeFirst: light/bold split "One feature. / One folder." Mini-stat
micro-readout (4 files / feature · 0 project jumps · 1 endpoint / slice)
ahead of the bullet list, which now uses the star marker SVG. VS-Code
editor + AnimatedIde wrapper retained.
- 02 WhatsIncluded: "Everything you'd build anyway" with a 3-col grid of
12 building-block cards. Each card: icon chip + mono category tag
(AUTH, ISOLATION, DATA, …) + title + body, with a corner-glow hover
matching the Hero stat cards.
- 03 ModuleShowcase: "Opinionated where it matters." Three large cards
with icon chip + "MODULE" mono label, tagline, star-marker bullets, and
a small dark terminal-style code panel (matching the Hero terminal
aesthetic — bg #0d1117, three traffic-light dots, mono code).
- 04 TechStack: 4-col grid of compact tech cells with icon chip, name,
version + sub. Hover adds a brand-coloured left accent that slides in.
- 05 FinalCta: "Start where you'd finish." Dotted backdrop, centred
headline, mono "$ clone the starter kit" prompt over the CopyCommand,
primary + secondary CTAs, trust strip footer.
All animations are pointer + prefers-reduced-motion aware; magnetic-card
tilt still binds idempotently after astro:after-swap.
Build green; 10 pages built in 2.02s.
* feat(docs): SEO/GEO foundation + fact-checked accuracy pass
Comprehensive SEO/GEO pass aimed at making the docs site the canonical
result for ".NET 10 starter kit" type intents, plus a ground-up
fact-check against the actual repository. Every claim on the landing
now matches what's in the codebase.
ACCURACY FIXES (fact-check against repo)
- Hero stat cards: 3 modules → 10 (Identity, Multitenancy, Auditing +
Files, Chat, Notifications, Webhooks, Billing, Catalog, Tickets);
598 tests → 900+ (actual [Fact]+[Theory] count is ~906); "5 min to
localhost" → "1 command to run" with `dotnet run` boots the stack.
- Hero animated terminal: rewritten to mirror AppHost.cs exactly —
7 services (postgres, redis, minio, db-migrator, fsh-api, fsh-admin,
fsh-dashboard). Removed fictional "Ready in 4.2s" timing claim.
- TechStack: Aspire 10.0 → 13.3, EF Core → 10.0.8, Postgres → 17,
Mediator → 3.0, FluentValidation → 12.1, Scalar → 2.14, Redis
StackExchange 2.11, OTel 1.15, Finbuckle 10.0, React TS 5.7 — all
pinned to src/Directory.Packages.props.
- ModuleShowcase Identity: removed "Per-tenant SSO hooks" (not
shipped, only JWT bearer auth is in the repo) → replaced with
"Rate-limited login, register & password reset" which is real
(auth rate-limit policy applied across those endpoints).
- ModuleShowcase: section eyebrow "3 bounded contexts" → "10 modules
ship in v1"; headline reframed as "Three pillars, seven more in the
box"; added a four-column aux-module strip below the three pillar
cards showing Files, Chat, Notifications, Webhooks, Billing,
Catalog, Tickets with one-line summaries.
NEW SECTIONS (fact-checked content only)
- 05 · WhoItsFor — two-column "Built for / Probably not for" panel.
Built for: SaaS-on-.NET teams, VSA + sane-defaults teams, indie
devs, founders who want to own their code. Not for: tiny CRUD,
Clean-Architecture purists, microservices-day-one, paid-support
needs. Reduces wrong-fit bounce + builds trust.
- 06 · FAQ — 8-question disclosure list answering the questions
developers actually ask (free?, prod-ready?, vs ABP?, vs Clean
Architecture templates?, do I have to use all 10 modules?, SaaS?,
deployment?, update path?). All answers fact-checked.
SEO/GEO INFRASTRUCTURE
- public/robots.txt — explicit Allow for every major AI/search crawler
(GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, anthropic-ai,
PerplexityBot, Perplexity-User, CCBot, cohere-ai, Bytespider,
Google-Extended, Applebot-Extended, Bingbot, plus the usual social
unfurlers). Points at the sitemap.
- public/llms.txt — fact-dense kit summary (architecture, 10 modules,
12 building blocks, tech stack with versions, how to run, docs map,
positioning vs ABP / Clean Architecture / commercial competitors).
Designed to be the single source AI crawlers cite when summarizing
what the kit is.
- JSON-LD structured data wired through BaseLayout.astro using
schema.org's @graph: Organization (with logo + sameAs to GitHub +
codewithmukesh), WebSite, and SoftwareSourceCode (programmingLanguage,
runtimePlatform, codeRepository, MIT license, author).
- FAQPage JSON-LD attached to the FAQ section so AI engines can
extract the Q&A pairs directly without scraping the disclosure
markup.
- theme-color meta tags (light: brand green #15803d, dark: surface
#0d0e11) for mobile browser chrome.
- og-default.png was 404'ing — pointed ogImage at logo-fullstackhero.png
so social shares get a real image until a dedicated 1200×630 OG
card is designed.
H2 + EYEBROW KEYWORD TIGHTENING
- Hero H1: "The .NET starter kit" → "The .NET 10 starter kit"; subtitle
rewritten as a self-contained citable paragraph that opens "FullStackHero
is a free, MIT-licensed, production-ready .NET 10 modular monolith…".
- Section eyebrows tightened with literal target-keyword right-meta:
"Modular Monolith + VSA", "12 shared building blocks", "10 modules
ship in v1", ".NET 10 · EF Core 10 · Aspire 13".
- WhatsIncluded label: "Everything pre-wired" → "Everything a
production .NET 10 API needs".
META + DOCUMENT
- siteConfig.title: "FullStackHero — .NET Starter Kit" → "FullStackHero
— Free .NET 10 Starter Kit" (adds the keyword that gets searched).
- siteConfig.description rewritten to be the same citable paragraph as
the hero subtitle, with ".NET 10", "free", "MIT-licensed", "ten
modules" front-loaded.
Build green; 10 pages built in 2.18s. robots.txt + llms.txt served
correctly on the dev server.
* feat(docs): copy rewrite — developer-centric + conversion-focused
Tightens copy across every landing section for three audiences (devs,
tech leads, CTOs), weaves target search phrases naturally, and pushes
the voice closer to "real codebase, not marketing." All claims still
match the repo per the fact-check from the previous commit.
Hero
- H1 bold: "that actually ships." → "you'd actually inherit." Speaks
to devs ("I won't fight this"), TLs ("team can take it over"), and
CTOs ("low risk, no lock-in") in three words.
- Subtitle rewritten as a citable definition paragraph:
"FullStackHero is the free, MIT-licensed .NET 10 starter kit for
teams shipping production SaaS. Ten modules — identity, multitenancy,
auditing, files, chat, notifications, webhooks, billing, catalog,
tickets — wired through a modular monolith with Vertical Slice
Architecture. A real codebase, not a tutorial. No vendor framework.
No lock-in." High keyword density, AI-quotable as a single block.
- Stat-card 4 label: "Command to run" → "Command"; note now ends in
"Aspire 13" instead of "Aspire" for the version anchor.
CodeFirst (01)
- Subtitle: leans harder on the workflow promise — "Add a feature in
one PR; ship in one merge." Adds tests to the file list. Calls out
"No jumping between five projects" in bold.
WhatsIncluded (02)
- Subtitle: lists the unglamorous parts by name ("Authentication.
Authorization. Migrations. Caching. Background jobs. Structured
logging. Distributed tracing. Idempotency. Webhooks.") and lands
on "You'd build all of it before your first real feature." More
specific, less abstract.
ModuleShowcase (03)
- Subtitle: tightened with bold "three questions every B2B SaaS has
to answer" and "what makes your product yours — not chat, not
billing, not file uploads." Frames the seven aux modules as time
saved.
TechStack (04)
- Added a real H2 + subtitle ahead of the grid:
"First-party Microsoft. / Best-in-class OSS." with the no-lock-in
pitch ("No proprietary framework. No DSL. No magic. The same
.NET 10, EF Core 10, Aspire 13, and OSS libraries your team
already knows — chosen carefully and wired together so they
actually compose."). Targets the CTO/TL "what are we adopting?"
audit.
FAQ (06)
- Added two questions that close common gaps:
- "How does it scale as my product grows?" — frames the modular
monolith → extract-a-service path honestly.
- "Can I deploy it to Azure, AWS, or Kubernetes?" — confirms each
service is a normal Docker image and lists target platforms.
FinalCta (07)
- Subtitle: "scaffolding" → "plumbing"; adds "run one command" to
reinforce the Aspire single-command boot story.
Build green; 13 pages built in 2.68s.
* feat(docs): redesign sidebar + TOC as numbered chapter cards
Sidebar: warm-paper card per category with single-expand accordion,
monospace 01–13 indices, primary-tinted rail + pill on active item,
auto-expand for the active chapter. Hidden scrollbar with edge-fade
affordance.
TOC: matching card chrome, vertical hairline rail with station-marker
dots on h2, scrollspy via rAF-throttled scroll listener, soft halo on
active dot.
Co-Authored-By: Claude Opus 4.7 (1M context)
* feat(docs): restructure sections + add full Getting Started flow
Sections now: Getting Started, Architecture, Modules, Building Blocks,
Cross-Cutting Concerns, Security, Frontend, CLI, Testing, Guides,
Deployment, Contributing, Changelog. Drop concepts/reference (folded
into Architecture and Changelog), rename recipes → guides.
Getting Started contains Introduction (moved from /docs/), Prerequisites,
Quick Start, Install. /docs and /docs/getting-started redirect to the
introduction. Header, footer, and landing CTAs rewired to the new paths.
Co-Authored-By: Claude Opus 4.7 (1M context)
* feat(docs): tighten typography to docs-standard scale
Page H1 32px/600 (was 36px/700), description 15.5px/lh1.55 (was 18px).
Prose H2 22px/600 (was 40px/300), H3 17px/600, H4 15px/600. Body 15px
with lh 1.65 (was 18px/1.75). Matches Stripe/Linear/Anthropic norms;
weight 600 throughout instead of 700.
Co-Authored-By: Claude Opus 4.7 (1M context)
* fix(docs): switch wrangler to Workers Static Assets
Replace deprecated pages_build_output_dir with [assets] block so
wrangler deploy works (Pages is being unified under Workers).
Rename worker to 'fullstackhero'. Single-quote style to match
Cloudflare's auto-PR generator.
Co-Authored-By: Claude Opus 4.7 (1M context)
* feat(docs): lowercase fullstackhero brand + GitHub stars on landing
Brand:
- New title: 'fullstackhero - .NET 10 Starter Kit with React UI'.
- Wordmark lowercased everywhere except NuGet package IDs
(FullStackHero.CLI, FullStackHero.NET.StarterKit must stay PascalCase).
- JSON-LD Organization + SoftwareSourceCode updated.
Social proof:
- helpers/github-stars.ts: build-time fetch of stargazers_count with a
6500 fallback for offline / rate-limited builds. Memoized per build.
- Header GitHub button: split-pill '[gh] GitHub | ★ 6.5k'.
- Hero chip leads with a tinted star-count pill.
- Hero secondary CTA: 'Star on GitHub | ★ 6.5k' split-pill.
- Primary CTA points to /docs/getting-started/quick-start/.
Co-Authored-By: Claude Opus 4.7 (1M context)
* feat(docs): mobile docs nav with slide-up sheet
Hidden on lg+, but on phones/tablets the docs main column now has a
trigger pill at the top showing the current section. Tapping opens a
bottom sheet (Stripe/Vercel/Linear pattern) that slides up from the
viewport edge with the full Sidebar inside — same numbered chapter
cards and single-expand accordion.
Affordances:
- Drag handle, close button, tap-scrim to close, ESC to close.
- Tap any link inside → close (route change feels native).
- Active page link is scrollIntoView({block: 'center'}) on open.
- Body scroll lock, inert when closed, focus returns to trigger.
Sidebar now takes a `prefix` prop (default 'fsh', mobile passes
'fsh-m') so aria-controls / labelledby IDs stay unique across the
two simultaneous renderings.
Co-Authored-By: Claude Opus 4.7 (1M context)
* fix(docs): drop placeholder favicon.svg — use brand PNGs/ICO
The favicon.svg in public/ was the old 'FSH on green square'
placeholder. Removing it and dropping its link from BaseLayout so
browsers resolve favicon.ico → 32x32.png → 16x16.png (already the
new brand triangle from the earlier favicon commit).
Co-Authored-By: Claude Opus 4.7 (1M context)
* feat(docs): tier-1 SEO + GEO code wins
Six concrete improvements aimed at unlocking AI citability and search
ranking on top of the editorial landing already in place.
1) Per-page SEO meta (content.config.ts + DocsLayout)
Extends the docs content schema with an optional seo block:
{ title, description, keywords, ogImage, noindex }. Each field
falls through to the frontmatter / site defaults so existing pages
keep working. The [...slug] route now plumbs seoTitle and
additionalSchemas through DocsLayout → BaseLayout so individual
docs pages can target their own keyword-rich independent
of the on-page H1.
2) SoftwareApplication schema upgrade (BaseLayout)
Swapped the homepage's SoftwareSourceCode entity for the more
AI-citable SoftwareApplication, with applicationCategory
"DeveloperApplication", operatingSystem, softwareVersion 10.0.0-rc.1,
softwareRequirements (.NET 10 SDK, PostgreSQL 17, Redis 7, Node 20+),
downloadUrl, and crucially:
offers: { price: "0", priceCurrency: "USD", availability: InStock }
That's the marker Google uses to label software listings as "Free"
in rich results.
3) BreadcrumbList JSON-LD on every docs page (helpers/breadcrumb.ts)
New buildBreadcrumbItems + buildBreadcrumbSchema helpers derive
breadcrumbs from the URL pathname, title-casing each segment, and
override the final crumb name with the page's actual title.
Emitted via additionalSchemas alongside a TechArticle schema
carrying headline, description, dateModified / datePublished, image,
author, publisher, and inLanguage — generic enough to cover
guides, references, concepts, and recipes.
4) llms-full.txt generated at build time (pages/llms-full.txt.ts)
New static endpoint that concatenates every docs collection entry
as plain text — title, blockquote description, canonical URL, raw
markdown body. Per-page sections separated by `---` rules so AI
crawlers can ingest the entire docs corpus in a single fetch.
Builds 429-line file at /llms-full.txt covering 16 pages.
5) Sitemap priorities + changefreq (astro.config.mjs)
serialize() callback on @astrojs/sitemap sets per-URL priority +
changefreq:
/ → 1.0 / weekly
/docs/getting-started/* → 0.9 / monthly
/docs/modules|building-blocks|architecture/* → 0.8 / monthly
/docs/guides/* → 0.75 / monthly
/docs/security|deployment|frontend|testing|cli/* → 0.7 / monthly
/docs/changelog/* → 0.5 / weekly (freshness signal)
/docs/contributing/* → 0.4 / yearly
/docs/* → 0.6 / monthly (fallback)
6) Cross-link landing → docs (WhatsIncluded + FAQ)
- Each WhatsIncluded card now click-throughs to the most relevant
/docs/ page (12 new internal links from a high-PR landing page).
Cards reveal a "Read the docs →" affordance on hover.
- Section footer adds "Browse the full building-blocks reference →"
CTA pointing at /docs/building-blocks/.
- Each FAQ answer gets an optional link object rendered as a
"Read the X →" link below the prose. Adds 9 more internal links
to deeper docs (testing, architecture, modules, deployment,
changelog, cross-cutting-concerns).
Build green; 18 pages built in 2.93s. Verified: docs pages emit
3 application/ld+json blocks (site graph + BreadcrumbList + TechArticle),
homepage emits site graph + FAQPage, sitemap carries priority + changefreq
per URL, /llms-full.txt is 429 lines covering all 16 docs pages.
* feat(docs): nav Docs link points straight to introduction
Header / Footer / Breadcrumbs all link the "Docs" entry directly at
/docs/getting-started/introduction/ (no redirect hop). Header keeps
the previous active-state semantics via a new `match` field on the
nav item — Docs still highlights for any /docs/* path even though
its link target is deeper.
Co-Authored-By: Claude Opus 4.7 (1M context)
* feat(docs): section index pages with auto-generated card grids
Each category now has a real index page that lists its child pages as
a card grid (2-col / 1-col mobile). Cards show a monospace 01/02/…
index, the page title, its description, and a chevron that nudges on
hover. Empty-state placeholder card renders for sections that don't
have child pages yet.
- New SectionIndex.astro: discovers pages in src/content/docs/{section},
sorts by sidebar.order, excludes the index itself + hidden pages.
- Registered as a global MDX component (no per-file imports needed).
- Re-created getting-started/index.mdx + dropped the
/docs/getting-started/ redirect so the new index is reachable.
- Updated 10 other section index pages to use SectionIndex.
contributing + changelog kept as single-page sections.
Co-Authored-By: Claude Opus 4.7 (1M context)
* feat(docs): track-1 content — comparison pages + getting-started SEO
The first content writing pass. Adds three full comparison pages that
rank for "X alternative" queries, wires them into the sidebar under
a new Compare section, and adds per-page SEO frontmatter to the four
Getting Started pages.
Comparison pages (~1100-1400 words each, fact-checked against repos)
- /docs/compare/fsh-vs-abp/ — biggest direct competitor. Honest
side-by-side (license, code ownership, DI, mediator, multitenancy
stack, stars). "When to choose ABP / when to choose fullstackhero"
with fair trade-offs. Migration notes (modules map, repositories
become DbContexts, app services become Mediator handlers).
- /docs/compare/fsh-vs-clean-architecture/ — Jason Taylor (~20.1k★)
and Ardalis (~18.2k★) templates. Frames the difference as "they
ship an architecture; fullstackhero ships ten production modules
on top of an architecture." Acknowledges where CA templates are
the better classroom + the better fit for small focused services.
- /docs/compare/fsh-vs-blazorplate/ — paid closed-source ($499-$999)
vs free open-source MIT. React vs Blazor, webhooks + OpenTelemetry
deltas, hybrid-use guidance.
Each comparison page ships honest disclaimers ("this page is
maintained by us, open an issue if anything is unfair") and links to
the competitor's canonical site/repo. All Q→A claims are checked
against either upstream repos or our Directory.Packages.props.
Compare section
- New /docs/compare/index.mdx — section landing with a quick decision
tree pointing the reader at the right comparison.
- _sections.ts gains a 'compare' entry at order 12, between Deployment
and Contributing — sidebar wiring automatic via the section list.
- astro.config.mjs sitemap rule adds /docs/compare/* → priority 0.8 /
monthly. These are high-conversion + rank for head-intent "X
alternative" queries; deserve the bump.
Getting Started SEO frontmatter
- Each of the four existing getting-started pages (introduction,
prerequisites, install, quick-start) gains a seo block with
keyword-targeted title, description, and keywords. The on-page H1
stays editorial; the tag and meta description target
long-tail searches like "fsh CLI", ".NET Aspire workload",
"FullStackHero.NET.StarterKit dotnet new template".
Verified
- Build green; 24 pages built in 3.17s (up from 18 last pass).
- Sitemap carries all four compare URLs at priority 0.8.
- llms-full.txt picked up the new content automatically (no code
change needed — the static endpoint enumerates the docs collection).
- FSH CLI claims verified: `FullStackHero.CLI` package + `fsh` tool
command in src/Tools/CLI/FSH.CLI.csproj, `fsh new` + `fsh doctor`
commands in Commands/.
* feat(docs): /docs/ overview hub with chapter-plate category cards
/docs/ is now a real overview page that lists every category as a card
grid. Each card carries a lucide icon, a huge ghosted watermark numeral
(editorial accent), title, description, live page count, and an
orchestrated hover sequence (lift + border + icon tint + watermark
glow + arrow nudge on a single 320ms curve).
- CategoryIndex.astro: discovers sections from _sections.ts, pulls
descriptions from each section's index.mdx, counts child pages.
- Registered as a global MDX component.
- docs/index.mdx (new) renders the hub.
- Header / Footer / Breadcrumbs re-point the "Docs" entry at /docs/.
- Typography: body-font title, sentence-case page-count meta (no more
mono-caps eyebrow / slug).
Co-Authored-By: Claude Opus 4.7 (1M context)
* fix(docs): wire .not-prose utility so card grids escape prose styles
The card grids inside MDX pages were inheriting prose-context styles —
the star li::before marker (rendering as little green +/sparkle marks
around each card) and the :where(a) underline. The .not-prose class
on the grids was a no-op because there was no actual rule wiring it.
Added a real .not-prose rule in globals.css that resets list-marker,
list padding, and anchor text-decoration for any descendant subtree
marked not-prose. SectionIndex picks up a belt-and-braces inline
text-decoration:none to defeat any stray :where(a) cascade.
Co-Authored-By: Claude Opus 4.7 (1M context)
* docs(modules): 10 module deep-dive pages — full first drafts
One MDX per module under /docs/modules/. All claims fact-checked
against the runtime source — every endpoint table, version pin,
domain-model excerpt, and configuration block was verified before
landing in prose. Each page targets a specific long-tail search
phrase via its seo.title and runs 1,000-1,800 words.
The ten pages
- identity.mdx (order 1) — JWT + refresh + permissions + groups +
impersonation + 2FA + sessions + password policy. ~1,800 words.
48 endpoints tabulated. References IdentityModule.cs + Domain/
FshUser.cs + Contracts/v1/Tokens/.
- multitenancy.mdx (order 2) — Finbuckle strategy chain + cache store +
EFCoreStore + per-tenant connection strings + provisioning state
machine + tenant themes. ~1,400 words. 9 endpoints. Documents the
root-operator header override + claim-strategy-pre-auth gotcha.
- auditing.mdx (order 3) — SaveChanges interceptor + HTTP
middleware + fluent Audit builder + JSON masking + channel
publisher + SQL/DLQ sinks + retention job. ~1,500 words. All
seven read endpoints + the AuditHttpOptions / AuditRetentionOptions
configuration blocks documented verbatim.
- files.mdx (order 4) — presigned URL flow + per-OwnerType
IFileAccessPolicy + finalize state machine + Files Categories
config + IFileScanner hook + orphan/retention purge jobs +
FileFinalizedIntegrationEvent. ~1,300 words.
- chat.mdx (order 5) — three channel kinds + idempotent
DirectKey for DMs + tombstone soft-delete + threads + reactions
+ pinning + mention parser + AppHub realtime + Redis backplane
+ 3s typing throttle. ~1,500 words. 22 endpoints. Documents the
module-order quirk (Notifications 750 before Chat 800).
- notifications.mdx (order 6) — denormalized inbox row + integration
event bridge + SignalR push to user:{id} + idempotent mark-read +
bulk ExecuteUpdateAsync mark-all-read. ~1,000 words. 4 endpoints.
Includes step-by-step "add a new notification type from another
module" extension recipe.
- webhooks.mdx (order 7) — tenant-scoped subscriptions + open-
generic WebhookFanoutHandler + HMAC-SHA256 signing +
Hangfire AutomaticRetry with 30s/2m/10m/1h backoff + delivery log.
~1,400 words. 5 endpoints. Documents the "restore tenant context
manually" gotcha + ships a complete C# signature-verification
recipe for subscribers.
- billing.mdx (order 8) — already shipped in prior commit
- catalog.mdx (order 9) — already shipped in prior commit
- tickets.mdx (order 10) — already shipped in prior commit
Across all ten pages we now document
- 234 endpoints (verified from MapEndpoint extensions)
- 30+ aggregate / entity types (with sealed-class + invariant excerpts)
- 100+ commands / queries from the Contracts assemblies
- Every IOptions config block with appsettings keys
- Every Hangfire recurring job by cron expression
- Every IGlobalEntity / tenant-context quirk worth knowing
Build green; 34 pages built in 4.75s (up from 27 last pass).
* docs(building-blocks): 12 building-block reference pages
One MDX per FSH.Framework block under /docs/building-blocks/. Each
page documents the block's public extension methods, interfaces, base
types, options, and consuming-module examples — fact-checked against
the BuildingBlocks/ source.
The twelve pages
- core.mdx (1) BaseEntity, AggregateRoot, DomainEvent,
ICurrentUser, CustomException hierarchy
- persistence.mdx (2) BaseDbContext + auto-applied filters,
Specification, audit + domain-event
interceptors
- shared.mdx (3) AppTenantInfo, PermissionConstants,
FshPermission, claim/action/resource
constants, audit attributes
- web.mdx (4) AddHeroPlatform / UseHeroPlatform,
ModuleLoader, GlobalExceptionHandler,
ValidationBehavior, CurrentUserMiddleware
- caching.mdx (5) HybridCache + ObservableHybridCache
decorator with OTel; stampede protection
- eventing-abstractions.mdx (6) IIntegrationEvent, IIntegrationEventHandler,
IEventBus, IEventSerializer (dep-free)
- eventing.mdx (7) InMemoryEventBus, RabbitMqEventBus,
EfCoreOutboxStore, EfCoreInboxStore,
OutboxDispatcher
- jobs.mdx (8) Hangfire wiring + FshJobActivator +
FshJobFilter (tenant context) + dashboard
basic auth + stale-lock cleanup
- mailing.mdx (9) IMailService + SMTP (MailKit) and
SendGrid implementations
- storage.mdx (10) IStorageService + LocalStorageService
and S3StorageService + presigned URLs
+ QuotaMeteredStorageService decorator
- quota.mdx (11) IQuotaService (Redis/InMemory/Noop) +
plan resolution + gauge providers +
enforcement middleware
- blazor-ui.mdx (12) Placeholder — v1 ships React frontends;
block reserved for future Blazor lib
Per page: ~700-1,200 words covering purpose, public surface, options,
how modules consume it, extensibility recipes, and gotchas. Every
extension method, interface, and option type listed has a file path
in the appendix.
Cross-link density between blocks and modules is dense — Storage links
to Files, Quota links to Identity's user gauge + Billing's plan keys,
Eventing links to Notifications + Webhooks, etc.
Build green; 46 pages built in 5.73s (up from 34).
* docs(architecture): 4 architecture pillar pages
Synthesizes patterns documented across the module and building-block
pages into four conceptual deep-dives at /docs/architecture/.
- modular-monolith.mdx (order 2) — the outer shape: ten modules
with one-way Contracts boundaries enforced by NetArchTest, load
order, three patterns for cross-module talk (service interfaces,
domain events, integration events), and the extract-a-service
migration path when scaling demands it. ~1,400 words.
- vertical-slice.mdx (order 3) — the inner shape: one feature
per folder with endpoint + command + handler + validator + tests.
Complete RegisterUser end-to-end excerpt. Conventions (sealed,
ValueTask, ConfigureAwait(false), one endpoint per slice).
Trade-offs vs Clean Architecture. ~1,200 words.
- multitenancy-deep-dive.mdx (order 4) — tenancy as default. Three
layers (HTTP/data/jobs), Finbuckle strategy chain + claim-pre-auth
gotcha, BaseDbContext.ApplyTenantIsolationByDefault(), IGlobalEntity
opt-out, named SoftDelete filter, cross-tenant query pattern
(IgnoreQueryFilters + explicit re-filter), per-tenant connection
strings, tag-based cache eviction. ~1,400 words.
- dependency-injection.mdx (order 5) — composition root: 4-line
Program.cs, the IModule contract, Mediator 3 source-gen MSG0007
gotcha, FluentValidation auto-registration, three lifetimes, the
four wire points for a new module (with the Mediator-list silent-
failure flag), middleware pipeline order including CORS-before-
HTTPS-redirect and CurrentUserMiddleware-last rules. ~1,300 words.
Build green; 50 pages built in 6.11s (up from 46).
* feat(docs): section cards match chapter-plate aesthetic + fix not-prose
SectionIndex cards now use the same modern design language as the
CategoryIndex hub: huge ghosted watermark numeral as the editorial
accent, body-font title, line-clamped description, pagination eyebrow
(01 / 04), and a unified hover sequence on a single 320ms curve.
Also fixes the .not-prose escape hatch. Previous selectors required
.not-prose to be an *ancestor* of the
, but the class lives ON
the
itself — so prose's star li::before marker kept leaking
into both card grids. Rewrote with :where(.not-prose) selectors that
match either placement; `!important` plus :where()'s 0-specificity
cleanly defeats the prose cascade.
Co-Authored-By: Claude Opus 4.7 (1M context)
* docs(sections): testing + security + cross-cutting-concerns full pages
Replaces three stub section indexes with substantial fact-checked
content. Each page now stands as a complete reference; the
wiring is removed since these are no longer
just landing pages for child docs.
testing/index.mdx (~1,800 words)
- Three-layer model: unit (xUnit + Shouldly + NSubstitute + AutoFixture),
integration (WebApplicationFactory + Testcontainers Postgres/Redis/
MinIO), architecture (NetArchTest).
- The test stack pinned in Directory.Packages.props, the test project
layout under src/Tests/, the naming convention "Method_Should_X_When_Y".
- Real RegisterUserCommandHandlerTests + ProductsEndpointTests
excerpts as templates.
- All 9 NetArchTest rule families enumerated (ModuleBoundary,
ContractsPurity, BuildingBlocksIndependence, CircularReference,
HandlerValidatorPairing, DomainEntity, ApiVersioning, FeatureFolder,
HostArchitecture). 48 architecture tests total.
- "How to add a new test" recipes for each layer.
- Honest disclaimers: no load tests, no visual regression, no chaos.
security/index.mdx (~2,000 words)
- The eight pillars: authentication, authorization, tenant isolation,
audit, rate limiting, webhook integrity, secret protection, data
masking.
- JWT bearer + rotating refresh, 5-attempt lockout, password policy
(12-char min, 5-history, 90-day expiry), email confirmation,
optional 2FA TOTP.
- Permission-based authorization via .RequirePermission(); roles as
groupings, permissions as the gate; the RequiredPermissionAttribute
silent-no-op gotcha flagged inline.
- Tenant isolation default; IgnoreQueryFilters discipline; named
SoftDelete filter; IGlobalEntity opt-out.
- Impersonation grant lifecycle + revocation list.
- Rate-limit auth policy on 6 endpoints enumerated.
- HMAC-SHA256 webhook signing + full subscriber-side verifier excerpt.
- Redis-backed Data Protection key persistence.
- Audit + JSON masking + retention.
- CORS-before-HTTPS-redirect + SignalR credentialed CORS gotcha.
- A 10-item day-one production security checklist.
cross-cutting-concerns/index.mdx (~1,800 words)
- The 11 platform features in a single table + per-feature deep dives.
- HybridCache stampede protection + tag-based eviction + shared
multiplexer rationale.
- Hangfire FshJobFilter (tenant context), FshJobActivator (scoped DI),
HangfireTelemetryFilter, plus the 5 recurring jobs the kit ships.
- OpenTelemetry: traces (incl. MediatorTracingBehavior), metrics,
logs over OTLP; Serilog enrichers.
- Idempotency-Key replay via HybridCache.
- Feature flags with TenantFeatureFilter for per-tenant overrides.
- Rate limiting (vs Quota — different concerns).
- Health checks: db, redis, MinIO, tenant migrations.
- SSE + SignalR + Redis backplane + AppHub group conventions.
- HTTP resilience pipeline (Polly v8 via Microsoft.Extensions.Http.Resilience).
- ProblemDetails (RFC 9457) global handler + Mediator pipeline
behaviors (ValidationBehavior, MediatorTracingBehavior).
All claims fact-checked against the source — versions pinned from
Directory.Packages.props, recurring-job crons verified from each
module's WebhooksModule.cs / BillingModule.cs / FilesModule.cs / etc.,
NetArchTest rule families verified from src/Tests/Architecture.Tests/.
Build green; 50 pages (unchanged — these replaced existing stubs).
* docs(cross-cutting): split into 11 per-topic pages
Replaces the single-page cross-cutting-concerns index with one MDX
per concern under /docs/cross-cutting-concerns/. Each page is now
rank-able for its own long-tail search query and the section index
auto-lists children via .
The eleven new pages
- caching.mdx (1) HybridCache L1+L2 + stampede + tag eviction
+ shared multiplexer
- background-jobs.mdx (2) Hangfire + FshJobFilter (tenant context) +
FshJobActivator (scoped DI) + the 5 cron
jobs the kit ships + retry attributes
- observability.mdx (3) Serilog enrichers + OpenTelemetry 1.15 +
instrumentation matrix + MediatorTracing
- idempotency.mdx (4) Idempotency-Key header + HybridCache replay
+ retention window + content-dedupe disclaimer
- feature-flags.mdx (5) Microsoft.FeatureManagement + TenantFeatureFilter
+ staged rollout / kill switch / per-env patterns
- rate-limiting.mdx (6) auth policy on 6 endpoints + AuthRateLimitWiringTests
+ adding custom policies + monitoring 429s
- health-checks.mdx (7) /health and /health/ready + per-DbContext +
Redis + MinIO + TenantMigrationsHealthCheck
+ k8s liveness/readiness probe wiring
- server-sent-events.mdx (8) SSE primitive + browser EventSource client +
?access_token= auth + heartbeat config
- realtime.mdx (9) SignalR over Redis backplane + AppHub group
conventions (user:{id} + channel:{id}) +
Context.User vs ICurrentUser gotcha +
TestServer-no-WebSocket integration testing
- http-resilience.mdx (10) Polly v8 standard pipeline + HTTP-layer vs
job-layer retry split + circuit breaker +
hedging + non-idempotent retry pitfall
- error-handling.mdx (11) GlobalExceptionHandler + RFC 9457
ProblemDetails + the 4-exception hierarchy +
FluentValidation field errors + throw-from-
domain idiom
Index page is now slim — short intro + auto-listing.
Build green; 61 pages built in 8.19s (up from 50).
* docs(security): split into 8 per-topic pages
Replaces the single-page security index with one MDX per concern.
The eight new pages
- authentication.mdx (1) JWT bearer + rotating refresh + lockout +
password policy + email confirmation flow
- authorization.mdx (2) Permission-based gates, role+group aggregation,
and the silent-no-op gotcha for duplicate
RequiredPermissionAttribute declarations
- impersonation.mdx (3) Time-bound grants, IGlobalEntity persistence,
jti revocation list, cross-tenant root override,
full audit trail
- two-factor.mdx (4) TOTP enrol/verify/disable; recovery-codes
gap flagged as recommended add-on
- webhook-signing.mdx (5) HMAC-SHA256 signing scheme + both C# and
Node/Express subscriber-side verification
recipes + replay protection via delivery id
- data-protection.mdx (6) Redis-backed key persistence so cookies +
antiforgery + email tokens survive rolling deploys
- cors-and-headers.mdx (7) CORS-before-HTTPS-redirect ordering, the
SignalR-credentialed-CORS gotcha, full CSP +
HSTS + X-Frame-Options reference
- production-checklist.mdx (8) The 10-item release gate — JWT rotation,
password policy, CORS, headers, rate limits,
HTTPS, debug-endpoint lockdown, Hangfire auth,
Data Protection persistence, audit retention
Index page is now a slim landing with auto-listing.
Build green; 69 pages built in 7.27s (up from 61).
* docs(testing): split into 6 per-topic pages
Replaces the single-page testing index with one MDX per layer plus
meta pages (fixtures, running, writing).
The six new pages
- unit-tests.mdx (1) xUnit + Shouldly + NSubstitute +
AutoFixture; Method_Should_X_When_Y naming;
three ground rules (Shouldly, direct SUT,
no infrastructure); aggregate-invariant +
validator + mock-verification patterns
- integration-tests.mdx (2) WebApplicationFactory + Testcontainers
Postgres/Redis/MinIO; FshWebApplicationFactory
+ Seed.* + CreateAuthenticatedClient helpers;
full ImpersonationTests example
- architecture-tests.mdx (3) 9 rule families + 48 tests; module-boundary
+ endpoint-convention + feature-folder
examples; "specification in XML doc + assertion
in code" pattern; what arch tests don't cover
- fixtures-and-seed-data.mdx (4) DatabaseFixture + RedisFixture + MinioFixture
composition; FshWebApplicationFactory wiring;
Seed.* static accessor; insert-don't-mutate
discipline; common mistakes
- running-tests.mdx (5) Commands (everything, single project, filter,
verbose, coverage); Docker requirement; full
GitHub Actions workflow; CI sharding pattern;
container cleanup
- writing-new-tests.mdx (6) Three step-by-step recipes: unit (with full
handler example), integration (with full
endpoint example), architecture rule (with
IL-walker disclaimer)
Index page is now a slim landing with .
Build green; 75 pages built in 8.60s (up from 69).
* docs(frontend + guides): impersonation walkthrough + frontend split + screenshot placeholders
Adds a comprehensive operator impersonation guide and splits the
frontend section into per-app pages. New MDX component
renders styled placeholder boxes when the image asset doesn't exist
yet — owner can drop screenshots into docs/public/screenshots/ and
the placeholders flip to real images automatically.
New MDX component
- components/docs/Screenshot.astro — file-exists check at build time;
styled dashed-border placeholder when missing, real + caption
when present. Wired into mdxComponents export.
New guides page
- guides/operator-impersonation.mdx (~1,700 words) — end-to-end UI
walkthrough: open admin → find user → start grant (with mandatory
reason) → act as user in new tab → end or revoke → audit trail
query. 9 screenshot placeholders covering every step. Plus a
cross-tenant section explaining the root-operator override, audit
query examples (Identity.Impersonation events), operational policies
(mandate reason, short duration, alert on patterns, train operators),
and 4 common-question answers (notified? chained? self? lost device?).
Frontend section — 5 sub-pages + slim index
- admin.mdx (~1,500 words) — operator-facing app: 11 page
areas, JWT auth flow with refresh, permission-gated UI, TanStack
Query patterns, SignalR notifications, the impersonation modal +
flow + audit, theme editor for tenants. 8 screenshot placeholders.
- dashboard.mdx (~1,400 words) — tenant-facing app: catalog,
chat (the densest realtime page), files presigned upload, tickets,
identity (profile + sessions + 2FA). 12 screenshot placeholders
covering every major page.
- architecture.mdx (~1,500 words) — shared patterns across both
apps: folder layout, provider stack order, apiClient interceptors
(auth + refresh + ProblemDetails), TanStack Query conventions,
RouteGuard, RealtimeProvider, env handling.
- theming.mdx (~1,200 words) — per-tenant theming via CSS
custom properties, the 9 palette roles (light + dark), brand assets,
typography, layout. Theme editor UI walkthrough with 4 screenshot
placeholders. Loading-flash mitigation pattern + dark mode story.
- development.mdx (~1,400 words) — Vite npm scripts, Aspire-
orchestrated vs npm-direct dev, production build, runtime config via
envsubst (full Dockerfile + entrypoint.sh), Nginx SPA config,
Vercel / Netlify / S3+CloudFront / Cloudflare Pages deployment shapes,
Vitest setup, common issues.
Total: ~33 screenshot placeholders embedded across the new pages.
Each Screenshot has a target path under public/screenshots/ — drop
images there and they replace the placeholders automatically.
Build green; 81 pages built in 9.40s (up from 75).
* fix(docs): hide screenshot placeholders in production builds
The placeholder box now renders only in dev (\`astro dev\`)
where the author needs to see what's still missing. Production builds
(\`astro build\`) emit nothing when the asset doesn't exist — so missing
screenshots leave no trace in shipped docs.
The "image exists" path is unchanged; once a screenshot lands at the
expected path under docs/public/screenshots/, the real renders
in any environment.
Toggle uses import.meta.env.DEV, which Astro/Vite sets to true under
the dev server and false during production builds.
Verified: 0 .screenshot-placeholder elements in the production build
across operator-impersonation, admin, dashboard, theming pages.
* feat(dashboard+backend): v1 UI polish, file visibility/sharing, permission catalog
Dashboard: revert warm-paper-hue chassis to untinted neutrals; polish list
primitives (combobox, density-toggle, empty-state, hero, pagination, sort,
stat) and add entity-detail/entity-shell/tone-icon-tile; tighten auth shell,
command palette, file pickers, notifications, sidebar/topbar, theme toggle;
page-level passes across activity, audits, auth, catalog, chat, files, login,
settings, system, tickets.
Files: add ChangeFileVisibility command + ListSharedFiles query, surface
visibility on FileAssetDto.
Identity: add GetPermissionCatalog query/endpoint; RoleService surfaces
catalog data.
Tests: add PermissionCatalogTests; add adminPassword to
TenantCreation/TenantActivation payloads; update RoleManagementTests.
Co-Authored-By: Claude Opus 4.7 (1M context)
* fix(tests): update role helpers to deserialize PagedResponse
GET /roles now returns PagedResponse (paged) instead of a flat
RoleDto[]; three integration test helpers still expected the array
shape, blowing up on the first byte of every response and failing 8
tests across the Roles and Users suites.
Co-Authored-By: Claude Opus 4.7 (1M context)
* feat(dashboard): A+ rollup — deps + a11y + perf + tokens + UX
Closes the P0 + most P1 items from the 2026-05-21 dashboard audit
(docs/superpowers/audits/2026-05-21-dashboard-world-class-audit.md).
Bundle: main JS 197.5 KB -> 178.0 KB gzip (-10%). New lazy chunks:
command-palette-dialog 8.2 KB, SignalR 14.5 KB. Chat-specific CSS
extracted to a 3.9 KB per-route chunk. Main CSS 127.5 -> 121.4 KB.
Build + lint + Playwright (37/37) all green. Zero npm advisories.
Dependency hygiene
- Wave-1 bumps: react 19.2.6, react-dom 19.2.6, @tanstack/react-query
5.100.11, @tanstack/react-virtual 3.13.25, react-router-dom 7.15.1,
tailwindcss 4.3.0, @tailwindcss/vite 4.3.0, tailwind-merge 3.6,
vite 7.3.3, @vitejs/plugin-react 4.7, typescript 5.9.3,
typescript-eslint 8.59.4, eslint 9.39.4, plus @types/react,
@types/react-dom, @types/node patches.
- Removed unused deps: recharts (declared, zero imports),
react-hook-form, @hookform/resolvers, zod, @types/lodash,
autoprefixer (Tailwind v4 handles prefixing natively).
Dead code
- Deleted 10 orphan files (zero cross-grep consumers):
file-gallery, sse/live-feed, sse/sse-status-badge, theme-toggle,
list/density-toggle, list/empty-state, list/list-hero,
list/pagination, list/sort-chips, list/stat. components/sse/
directory removed.
- Pruned components/list/index.ts barrel. Removed unused API
exports: getAuditsByTrace, reorderProductImages, discoverChannels,
restoreChannel, getRoleById. Dropped unused export keywords
(BRAND_LADDER).
Tokens + visual cohesion
- Dropped backdrop-grayscale from the Dialog scrim (it was
desaturating the entire page behind every modal).
- Light-mode elevation restored: --surface-3 now differs from
--card / --background so cards float again.
- Muted-foreground bumped (dark L 0.680 -> 0.730; light 0.575 ->
0.500) so placeholders + chevrons clear WCAG 2.2 AA 4.5:1.
- Added canonical type scale: --text-display-page/section/card/stat
(Tailwind v4 auto-generates the text-display-* utilities).
- Defined missing fsh-sheet-in/out keyframes for the mobile drawer
(previously referenced but undefined).
- Replaced 19 shadow-[0_1px_2px_oklch(...0.04)] literals across
12 files with the shadow-xs token. Fixed clipped oklch lightness
in overview hover-borders (now uses --color-border-strong).
- Removed dead .chat-empty-hero rule.
Type scale migration
- Migrated h1s in EntityPageHeader, PageHero, OverviewPage,
NotFoundPage to the new tokens. Chat channel header demoted
h1 -> h2 so each route has a single h1.
Fonts
- index.html eager Google Fonts trimmed from 12 families to 3
(Figtree + Outfit + JetBrains Mono). The other 9 are lazy-injected
by ensureLazyFontsLoaded() when Appearance settings mounts, or
synchronously at boot when the user has a non-default body font
stored in localStorage. ~200-400 KB cold-load savings.
Accessibility (WCAG 2.2 AA)
- Programmatic-label fixes: aria-label on cmdk Command.Input + chat
composer textarea; aria-expanded={!collapsed} on sidebar collapse
button.
- FilePreviewDialog no longer renders a whitespace-only
DialogDescription (SR-read as empty description).
- Field primitive: required indicator gets an sr-only "required"
sibling alongside the visual dot.
- RouteError stack trace gated behind import.meta.env.DEV — prod
users see a recoverable card with no internals exposed.
- RealtimeStatusPill: new `announce` prop, single mount (bell footer)
now owns the live region — the chat-rail instance stops
duplicate-announcing reconnect events.
- role="log" aria-live="polite" on chat MessageList scroll
container + Activity feed (mobile cards + desktop table).
- role="status" aria-live="polite" on typing-indicator.
- Touch targets bumped from h-5 w-5 (20px) to h-6 w-6 (24px) on
composer reply-quote/attachment/upload clears, channel-rail
filter clear + section-action plus, login eye-toggle, with
focus-visible rings added where missing.
Forms
- aria-invalid + aria-describedby wired on login + forgot-password
+ reset-password forms so SR users hear errors linked to the
fields they apply to. Sibling error blocks got stable ids.
Performance
- @microsoft/signalr is now dynamic-imported inside
realtime-context's connect flow (37 KB gzip off the main shell).
HubConnectionState comparisons rewritten as string-literal so the
shutdown / invoke paths don't pull SignalR back in.
- CommandPaletteDialog extracted to its own file and React.lazy
imported by CommandPaletteRoot — cmdk + the full action graph
load on first Cmd+K, not on cold start.
- SseContext split into useSseStatus (status + eventCount) and
useSseEvents (events array). Topbar SSE dot + bell pill stop
re-rendering on every SSE event; only LiveFeed / Activity /
Overview's livefeed-body subscribe to the events stream.
- Avatar gained loading="lazy" + decoding="async" + explicit
width/height (matches sizeClass), eliminating CLS during avatar
swap-in.
UX
- Command palette expanded: Identity (users/roles/groups), Catalog
(products/brands/categories), Tickets, Files, Chat, Trash,
Sessions, Appearance — every leaf route is now navigable from
Cmd+K. New Create group with 9 entries (user/role/group/product/
brand/category/ticket/channel/file-upload).
- Tickets list + detail render assignee/reporter via useUserDisplay
(real names + avatars) instead of authorUserId.slice(0,8) + "...".
- Chat-specific CSS rules (typing dots, unread divider, day rule,
reaction chip, jump pill, mention pill) moved to
pages/chat/chat.css — Vite extracts them into the chat-page CSS
chunk so other pages stop shipping ~3 KB of selectors they don't
use. .chat-status-pill stays in globals.css because the global
notification bell consumes it.
ESLint
- jsx-a11y/no-noninteractive-element-interactions promoted from
warn -> error (configured to exclude onError so legitimate image
fallback handlers don't trip). Added no-noninteractive-element-
to-interactive-role / no-aria-hidden-on-focusable / anchor-has-
content as error. Fixed the one violation in user-picker.tsx
(ul role=listbox -> div role=listbox).
Tests
- Refreshed two Playwright auth specs (forgot-password,
reset-password) that were asserting the old "// 02.FORGOT-
PASSWORD" / "// 03.RESET-PASSWORD" eyebrow chrome and chip
rendering that the v1 AuthShell redesign (commit 519d664d)
retired. Tests now target the current headline + body text.
Docs
- Added docs/superpowers/audits/2026-05-21-dashboard-world-class-
audit.md (the original six-agent composite audit, updated with a
Status preamble noting what landed in this rollup).
Co-Authored-By: Claude Opus 4.7 (1M context)
* feat(docs): page view counter (D1) + frontmatter dates + hero copy
Add a Cloudflare D1-backed page view counter to the docs site:
- worker.ts handles POST/GET /api/views with KV per-(IP+UA) dedup (1h TTL)
and delegates all non-API paths to the static assets binding
- migrations/0001_init.sql defines the views table (+ count index)
- client beacon (views-counter.ts) posts the slug and renders the count
into a PageHeader chip with an eased count-up reveal; dev shows a sample,
prod stays hidden until the beacon resolves
- wrangler.toml wires the worker entry, assets, D1, and KV bindings
Move page dates into MDX frontmatter (lastUpdated), backfilled across all
79 docs from git history, and remove the broken remark-modified-time plugin
(Astro 6's glob loader discards remark frontmatter mutations, so the value
never reached entry.data).
Reword the hero headline to "built to ship, not to demo."
Co-Authored-By: Claude Opus 4.7 (1M context)
* refactor(identity,multitenancy): propagate CancellationToken through service layer + endpoints
Full-sweep CancellationToken audit (Roslyn navigator AP009). Adds
`CancellationToken cancellationToken = default` to the Identity service
interfaces and their implementations, propagating the token into every
real EF/IO sink (SaveChangesAsync, FirstOrDefaultAsync, storage upload/
remove, downstream expiry/history services). Wires the request token
through the affected command handlers and the Multitenancy CreateTenant /
ChangeTenantActivation endpoints (mediator.Send).
Methods deliberately left unchanged: private helpers that only call
UserManager/RoleManager (no cancellable sink — ASP.NET Identity exposes
no CT overloads) and framework-fixed signatures.
Logging audit (AP006): no changes required — the codebase already uses
structured logging throughout; the 2 flagged OutboxDispatcher hits are
false positives (arithmetic args to proper message templates).
Also fixes the DeleteUserCommandHandler test assertion to verify the
request token is forwarded (was asserting the implicit default).
Co-Authored-By: Claude Opus 4.7 (1M context)
* docs(test): master test plan — risk-tiered coverage matrix + gap backlog
Audits the ~390 existing integration tests into a feature x scenario x tier
matrix, defines the scenario taxonomy, and produces a dedupe-aware gap backlog
(P0-P3). Foundation artifact for the test-hardening workstream (sub-project A
of 3: plan / gap-fill+E2E / CI ratchet).
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(architecture): add validators for the 3 handlers flagged by HandlerValidatorPairingTests
Adds the missing FluentValidation validators so every command handler and
paginated query handler has a paired validator:
- ChangeFileVisibilityCommandValidator — FileAssetId not empty, Visibility
restricted to Public(0)/Private(1) (mirrors the handler's guard).
- ListSharedFilesQueryValidator — Page >= 1, PageSize in [1,100]
(matches ListMyFilesQueryValidator).
- GetRolesQueryValidator — PageNumber/PageSize >= 1
(matches GetTenantSessionsValidator for the IPagedQuery shape).
Architecture.Tests now 49/49 green.
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(identity): forgot/reset + change-password integration tests
Fills two total gaps from the master test plan (P0 #2, #3): the password
reset and change flows had zero functional coverage. Each proves the change
end-to-end (new password authenticates, old one is rejected) plus the
unhappy paths (invalid/expired token, wrong current password, 401).
6 tests, all green against Testcontainers Postgres.
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(files): cross-tenant isolation + visibility/sharing
Closes the two Files-module test gaps from the master plan (P2 #10):
cross-TENANT file access and the visibility/sharing feature.
FileTenantIsolationTests proves a user in tenant B cannot read metadata
or mint a download URL for a file owned in tenant A (404, no existence
leak) — isolation comes from the schema-per-tenant BaseDbContext.
FileVisibilityAndSharingTests covers the actual model: a Public/Private
bit enforced by DefaultUploaderOnlyPolicy. Private files are owner-only;
Public files are readable by any same-tenant user and appear in /shared;
flipping visibility (the "share" action) is uploader-only (403 otherwise).
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(chat): cross-tenant channel/message isolation
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(billing): cross-tenant fetch-by-id isolation
Adds BillingTenantIsolationTests proving direct fetch-by-id does not leak
across tenants: a different tenant fetching another tenant's invoice id gets
404, and passing another tenant's id to the subscription query returns no
subscription — while the owning tenant fetches the same id successfully.
The tests surfaced two real isolation bugs (BillingDbContext extends raw
DbContext, so there is no global tenant query filter — each handler must scope
by the caller's tenant explicitly):
- GetInvoiceById had no tenant filter at all (fetched by id only) -> leak.
- GetSubscription trusted the caller-supplied tenantId param -> any tenant
could read another tenant's subscription by passing its id.
Fixes both handlers to scope by the authenticated tenant (mirrors
GetMyInvoices). Subscription still allows the root operator to pass an
arbitrary tenantId for admin cross-tenant reads. All 28 existing
BillingEndpointTests remain green.
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(webhooks): HMAC signature correctness
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(catalog/tickets/groups): cross-tenant isolation
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(multitenancy): provisioning failure path + activation block
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(identity): permission-cache invalidation + role tenant isolation
Co-Authored-By: Claude Opus 4.7 (1M context)
* docs(test): mark wave 1+2 gap-fill complete; note Billing P1 leak fix
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(webhooks): deliveries/dispatch/fanout/test-send coverage
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(multitenancy): theme/upgrade/retry/migrations coverage
Add integration coverage for the lowest-covered Multitenancy targets:
- TenantThemeTests: get/update/reset end-to-end, validation, cross-tenant isolation
- UpgradeTenantTests: plan upgrade happy path, validation, root-only authz
- RetryTenantProvisioningTests: retry of a failed (unreachable conn string) tenant
- TenantMigrationsTests: per-tenant migration-status query + authz
- ChangeTenantActivationValidationTests: mismatch/notfound/root/double/reactivate
Fix: wire the previously-unreachable GetTenantMigrations endpoint into
MultitenancyModule.MapEndpoints (handler/endpoint existed but were never mapped).
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(auditing): by-id/correlation/trace/exception query coverage
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(identity): sessions/users/forgot-password coverage
Add integration tests for previously-uncovered Identity handlers:
- Sessions: GetUserSessions, GetTenantSessions, AdminRevokeSession,
AdminRevokeAllSessions, RevokeAllSessions (+ cross-tenant isolation)
- Users: UpdateUser (PUT /profile), SetProfileImage, GetUserRoles,
GetUserGroups, SearchUsers (filter/sort/page)
- ForgotPassword request flow (POST /forgot-password)
Fix a real bug surfaced by the profile tests: UserProfileService.UpdateAsync
dereferenced image.Data on a null FileUploadRequest, so any text-only profile
update returned 500. Guard the null and handle delete-only image clears.
Co-Authored-By: Claude Opus 4.7 (1M context)
* fix(identity): close forgot-password user-enumeration leak
ForgotPasswordAsync returned 404 for unknown emails but 200 for real ones,
letting an anonymous caller enumerate registered accounts. Now returns a
uniform 200 regardless (real users still get the reset email). Un-skips the
ForgotPasswordRequestTests enumeration test.
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(catalog): product-image remove/reorder + access-policy + update coverage
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(tickets): comments + mappings + event-handler + search coverage
Fix TicketComment.Id missing ValueGeneratedNever() — EF tracked the
nav-collection child as Modified (UPDATE 0 rows) instead of Added,
raising DbUpdateConcurrencyException on AddComment. Un-skips the existing
AddComment endpoint test and adds comment, search-filter, lifecycle, and
validation coverage.
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(files): purge jobs + access-policy + storage flow coverage
Co-Authored-By: Claude Opus 4.7 (1M context)
* fix(auditing): cast jsonb PayloadJson to text for ILIKE filters + tests
EF.Functions.ILike was applied directly to the PayloadJson column, which
maps to PostgreSQL jsonb. PostgreSQL has no like_escape(jsonb, unknown),
so every payload-backed filter crashed at execution with HTTP 500
(GetSecurityAudits action, GetExceptionAudits area/exceptionType/
routeOrLocation, and the GetAudits search whose jsonb OR-leg poisoned the
whole predicate).
Fix: introduce AuditJsonbFunctions.AsText, mapped via HasDbFunction +
HasTranslation to a SQL CAST(x AS text) in AuditDbContext, and wrap the
jsonb column with it before ILIKE so valid SQL is generated. Also align
the key-match patterns with PostgreSQL jsonb canonical text form, which
emits a space after each colon ({"area": "Value"}); the previous compact
patterns ("area":"Value") never matched the casted text.
No column mapping change and no migration: the jsonb mapping and GIN index
in AuditRecordConfiguration are untouched. Changes are Auditing-module-local.
Adds AuditPayloadFilterTests (7 tests) seeding known payloads and asserting
each filter returns the correct row and does not 500, plus an end-to-end
test driving search over a real async-written login audit.
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(billing): usage-snapshots query + monthly-invoice job + domain coverage
Co-Authored-By: Claude Opus 4.7 (1M context)
* feat(dashboard,admin): dentalOS-style login + demo picker, FSH logo lockup
- Rebuild the dashboard login to the dentalOS card layout (Welcome back / tenant+email+password / dashed demo button) and add a "Step into any role" demo-account picker (components/auth/demo-accounts-dialog.tsx) that signs in instantly, each account carrying its own tenant.
- AuthShell brand lockup now uses the FSH logo (logo-fullstackhero.png) + "fullstackhero" wordmark + ".NET 10 Starter Kit" caption; admin BrandMarkXL leads with the logo while keeping its Console aesthetic.
- Gate the demo button on a runtime config.json `demoMode` flag (env.ts) so one build serves staging (on) and prod (off); remove the old login.demo-panel.tsx.
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(e2e): Playwright suites for dashboard + admin (214 tests)
Full route-mocked, JWT-seeded Playwright coverage of every page in both React apps — no live backend required.
- Dashboard (121): login/demo picker, overview/activity/invoices, catalog, identity, tickets, chat, files, settings, system.
- Admin (93): login/dashboard, tenants, users/roles, billing, audits/impersonation/webhooks/notifications/health, settings.
- Shared harness in tests/helpers/shell-mocks.ts mocks all global AppShell calls (notifications/chat/profile/permissions; aborts SSE/realtime). All green, eslint clean.
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(auditing): un-skip the 6 jsonb-filter tests now that the cast fix is merged
* test(catalog): unit tests for product/money/category domain
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(billing): unit tests for domain transitions + services
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(webhooks): unit tests for subscription matching + delivery + signer
Co-Authored-By: Claude Opus 4.7 (1M context)
* build(tests): wire Billing/Catalog/Webhooks unit-test projects into solution
* build(coverage): exclude *HostedService background loops from coverage denominator
* test(identity): unit tests for device/current-user/password services
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(framework): unit tests for storage/core/web/persistence/eventing helpers
Co-Authored-By: Claude Opus 4.7 (1M context)
* build(tests): wire Framework.Tests into solution
* ci: add Billing/Catalog/Chat/Files/Framework/Webhooks to test matrix + coverage-gate job
Fixes the stale test matrix (Chat.Tests/Files.Tests were never run in CI) and adds
a Coverage Gate job that runs the whole solution with Coverlet and fails below an
80%-ish line floor (ratchet — bump MIN_LINE as coverage rises).
* ci: raise coverage floor to 80% (meaningful coverage now 83.3%)
* test(middleware): real-wiring tests for global exception handler + rate limiting + security headers
Co-Authored-By: Claude Opus 4.7 (1M context)
* Revert "test(middleware): real-wiring tests for global exception handler + rate limiting + security headers"
This reverts commit 3d9955a5547b67b009072f5f88cc7a3fa31c912a, reversing
changes made to 679e6e3a63d6da3ddda3e741d677e1a48e027c55.
* test(middleware): real-wiring tests in isolated assembly (global exception handler + rate limiting + security headers)
Co-Authored-By: Claude Opus 4.7 (1M context)
* build(ci): wire Integration.Middleware.Tests into solution + integration-test job
* chore: gitignore generated coverage-report/ and TestResults/ dirs
* refactor: harden concurrency hotspots + cleanup from Roslyn-navigator audit
Findings and fixes from a race-condition / code-smell audit (Roslyn navigator
detect_antipatterns, find_dead_code, detect_circular_dependencies + targeted
concurrency sweeps). Concurrency hygiene was already strong — no async void,
no sync-over-async, no fire-and-forget, thread-safe singletons, zero circular
deps. The genuine items:
- Audit (static): swap the enricher list for an atomically-replaced immutable
array read once per WriteAsync. Kills the latent "collection modified" race
if Configure() ever runs concurrently with enrichment, and removes a
process-global mutable-List test-isolation hazard.
- QuotaEnforcementMiddleware: inject TimeProvider instead of DateTimeOffset.UtcNow
for Retry-After, matching the rest of the Quota subsystem (deterministic in tests).
- PresenceTracker.Connect: also report the offline->online transition when the
AddOrUpdate update factory resurrects a count==0 key (Disconnect set 0 but
hadn't removed it yet) — previously a reconnect in that window missed the
presence broadcast.
- ChannelMember: remove dead SetMuted (zero callers); IsMuted is now get-only
(still persisted + exposed via DTO, populated by EF via backing field).
Investigated but intentionally NOT changed (false positives confirmed):
- catch(Exception) in hosted services already filter OperationCanceledException.
- Flagged EF "missing AsNoTracking" queries all read-then-mutate-then-save
(tracking required); the read-only paths already use AsNoTracking.
- find_dead_code hits for ChannelAuthorization / EntityEntryExtensions are
used extension methods; EF model snapshots are tooling artifacts.
All unit (779) + integration (665) + middleware (5) tests green.
Co-Authored-By: Claude Opus 4.7 (1M context)
* docs(agents): AGENTS.md guide + .agents/ rules, skills, and workflows (accurate to current codebase)
Establish AGENTS.md as the canonical, tool-neutral AI guide (CLAUDE.md/GEMINI.md
are thin @import bridges) and build out .agents/ guidance verified against the
current code via Roslyn navigation + real-file extraction.
Rules (.agents/rules/) — lean, on-demand, indexed from AGENTS.md:
- cross-cutting: architecture, api-conventions, database, eventing, caching, jobs,
resilience, storage, security, realtime, logging, testing, integration-testing
- per-module: identity, multitenancy, chat, files, webhooks, auditing, billing,
catalog, tickets, notifications
- frontend: shared + admin + dashboard (the two React apps' real divergences)
- replaces the old flat modules.md/persistence.md/testing-rules.md
Skills (.agents/skills/) — audited the 6 existing against the live codebase and
rewrote them (they were stale: IRepository, PagedList, Moq/FluentAssertions,
Guid.NewGuid, class-level [FshModule(Order=n)], DbContext vs BaseDbContext, only 2
of the 4 module-registration sites). Added 5: add-react-page, add-full-slice,
create-migration, add-integration-event, add-permission. Skills hold the recipe.
Workflows (.agents/workflows/) — reconciled so they orchestrate and delegate to
skills (no duplicated/contradictory templates); fixed stale facts (migrations run
via DbMigrator, not on startup; TenantDbContext not "MultitenancyDbContext").
Also corrects the [FshModule] attribute (assembly-level positional, not class-level)
in architecture.md and the AGENTS.md pointer.
Docs only; no source/build impact.
Co-Authored-By: Claude Opus 4.7 (1M context)
* chore: remove NSwag OpenAPI client-gen tooling + obsolete scripts/docs
- Drop nswag.consolecore from dotnet-tools and delete scripts/openapi/*
(NSwag-based C# client generation is no longer used).
- Remove scripts/test-cli.ps1 and the requirements/frontend-and-platform.md
planning doc.
Co-Authored-By: Claude Opus 4.7 (1M context)
* docs(site): AI-Driven Development section, sponsorship surfaces, homepage redesign, v10
- AI-Driven Development docs section (overview, AGENTS.md & .agents/, skills &
workflows, developing with Claude Code) + section registration.
- Sponsorship: sticky sponsor card atop the docs ToC, and a "Back the build"
home section (Open Collective), placed right under the architecture section.
- Homepage: new "Stack at a glance" bento opener; the architecture section
rebuilt as a modular-monolith + vertical-slice diagram (two balanced boards);
FSH brand mark in the hero; "Who it's for" redesigned as icon-led cards;
module-card code panels removed; competitor product references removed.
- Version: product version aligned to v10 (.NET 10) across the homepage and
docs copy. API version paths (/api/v1, Features/v1, Contracts.v1) untouched.
- Fix: type the header nav `match` field — astro check now 0 errors / 0 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context)
* docs(home): testing section, View Transitions theme toggle, mobile polish
- Add "Tested to production standard" landing section (Testing.astro): a
four-pillar scoreboard (unit / integration / E2E / architecture) over a
green test-run terminal, leading with real-PostgreSQL-via-Testcontainers.
Wire in as section 05; renumber WhoItsFor/FAQ/FinalCta to 06/07/08. Sync
stale test counts in Hero (900+ -> 1,400+) and the FAQ answer.
- Theme toggle: replace the muddy CSS token cross-fade with a View Transitions
API snapshot cross-fade (GPU-composited, text stays crisp, no frame drops);
clean instant fallback for reduced-motion / unsupported browsers.
- Mobile responsiveness pass on the homepage: drop heading bases (sections
30px, hero 34px) so they stop dominating phones; hero stats, TechStack, and
the testing scoreboard go single-column on mobile; hero terminal scrolls
instead of clipping; FAQ answers reclaim full width on mobile.
Co-Authored-By: Claude Opus 4.7 (1M context)
* feat(infra): modernize Terraform — TF 1.15.4 / AWS 6.46, collapse root, React SPA hosting
- Pin Terraform >= 1.15.4 and AWS provider ~> 6.46 in the roots; permissive
floors (>= 1.15 / >= 6.0) in child modules. Commit a multi-platform
.terraform.lock.hcl locked to aws 6.46.0.
- Collapse the shared/ -> app_stack/ wrapper into a single root (provider +
backend now live in app_stack/), removing ~500 lines of duplicated vars and
outputs plus a passthrough bug that silently dropped ~30 tunables. Dedupe
tags via provider default_tags.
- Add reusable modules/static_site (private S3 + OAC CloudFront, SPA
403/404->index.html fallback, default-on managed security headers,
Terraform-owned config.json) and host the two React SPAs (admin, dashboard)
that replaced the removed server-rendered Blazor service. SPA CloudFront
origins are auto-added to the API CORS allow-list. No Route53/domain
resources (custom aliases optional).
- Drop the unnecessary api_task_secrets IAM policy (least privilege).
- Add one-command deploy.sh / deploy.ps1: terraform apply -> optional API
image build/push -> build + s3 sync + CloudFront invalidate for both SPAs.
- Refresh README; terraform fmt + validate clean against aws 6.46.0.
Co-Authored-By: Claude Opus 4.7 (1M context)
* chore: sweep in-flight distribution-template WIP + gitignore local .claude
Pre-existing working-tree changes outside the Terraform audit, committed so the
branch is clean:
- templates/ NuGet pack csproj, .template.config/template.json, README-template
- CLI NewCommand updates; AppHost + solution (slnx) wiring
- CI: add template-smoke workflow, ci.yml tweak
- docs: site.ts, breadcrumb helper, llms.txt/robots.txt, Cloudflare _headers/_redirects, error-handling page
- .gitignore: exclude local .claude worktrees, scheduled_tasks.lock, last30days.env
Co-Authored-By: Claude Opus 4.7 (1M context)
* fix(docs): render section Overview first, dedup breadcrumbs, name index pages "Overview"
- sidebar: detect the Astro 6 glob-loader bare-dir index id ('frontend' as well
as 'frontend/index') so every section's overview is pulled out and rendered
first, instead of leaking into the page list and sorting by order.
- breadcrumbs: a section overview now renders 'Docs / ' (section is the
current page) instead of duplicating it ('Docs / Modules / Modules'); fixed in
both the visible component and the JSON-LD schema.
- name all 15 category index pages 'Overview' (with descriptive per-page seo.title).
Co-Authored-By: Claude Opus 4.7 (1M context)
* docs(deployment): add Aspire, DbMigrator, AWS Terraform, CI/CD guides; rewrite install paths
- deployment/aspire: services that spin up locally + the design decisions.
- deployment/database-migrations: the DbMigrator (commands, multitenancy, prod usage).
- deployment/aws-terraform: end-to-end AWS deploy from prerequisites to one command.
- deployment/ci-cd: the GitHub Actions pipeline + template smoke test.
- getting-started/install: four install paths with screenshot placeholders.
Co-Authored-By: Claude Opus 4.7 (1M context)
* docs: remove Blazor references (the kit no longer ships Blazor)
Delete the Blazor.UI building-block page and drop Blazor.UI / FSH.Starter.Blazor
from the project trees, the building-blocks list (llms.txt), and the CORS CSP
note. Competitor comparisons (BlazorPlate, ABP) and legacy blazor-* redirects
are kept intentionally.
Co-Authored-By: Claude Opus 4.7 (1M context)
* docs(architecture): expand tenant isolation deep-dive + fix cross-links
- deep-dive: add the subclass 'base.OnModelCreating LAST' rule, the documented
Billing isolation exception (plain DbContext + manual TenantId), per-tenant
migration ordering, and the AsyncLocal test gotcha.
- fix multitenancy module links that pointed at the section index instead of
the deep-dive; cross-link Billing to the documented exception.
Co-Authored-By: Claude Opus 4.7 (1M context)
* fix: http(s) scheme validation for webhook URLs + cross-platform BuildingBlocks tests (#1244)
* fix(tests): make BuildingBlocks dependency checks cross-platform
ProjectReference Include paths are authored with Windows separators
(..\Core\Core.csproj). Path.GetFileNameWithoutExtension only treats
"\" as a separator on Windows, so on Linux CI it returned the full
path minus extension (..\Core\Core) instead of the bare name (Core).
This caused BuildingBlocks_Should_Follow_Layered_Dependencies to report
12 false-positive violations on Linux (passed locally on Windows), and
silently disabled the module-reference enforcement in
BuildingBlocks_Projects_Should_Not_Reference_Modules_Directly.
Normalize "\" to "/" before extracting the project name so both checks
work on every platform.
Co-Authored-By: Claude Opus 4.7 (1M context)
* fix(webhooks): require http(s) scheme for subscription URL
Uri.TryCreate(UriKind.Absolute) accepts a leading-/ path as an implicit
file:// URI on Unix (but not Windows), so "/relative/path" passed
validation on the Linux CI runner while the test passed locally on
Windows. A webhook target must be an HTTP(S) endpoint regardless of
platform, so assert the scheme explicitly. Fixes the Linux-only failure
of Create_Should_Fail_When_Url_Relative.
Co-Authored-By: Claude Opus 4.7 (1M context)
---------
Co-authored-by: Claude Opus 4.7 (1M context)
* docs: add Google Analytics (gtag.js) to BaseLayout
Loads gtag.js site-wide via the base layout head. Both script tags
use is:inline so Astro emits them verbatim instead of bundling.
Co-Authored-By: Claude Opus 4.7 (1M context)
* @
chore: extract docs site to a separate repo (fullstackhero/docs)
The Astro docs site now lives at github.com/fullstackhero/docs. Remove
docs/ from this repo and repoint the references that pointed into it.
- Move docs/superpowers/ (audits, specs, plans -- whole-project notes,
not docs-site content) to repo root superpowers/, preserving history
- Remove the docs/ row from the AGENTS.md repo map
- Drop docs/{node_modules,dist,.astro} entries from .gitignore
- Repoint the README "deeper story" link to the new repo
- Drop "docs" from the code-reviewer workflow change-area grouping
Co-Authored-By: Claude Opus 4.7 (1M context)
@
* @
fix(docker): create MinIO bucket in prod compose + add migrator to root compose
deploy/docker (prod): nothing created the S3 bucket the Files module
writes to (S3StorageService just PutObjects into Storage:S3:Bucket and
never creates it), so the first upload failed with NoSuchBucket. Add a
minio-init one-shot (mc mb local/fsh) and gate api on its completion. No
anonymous policy -- objects are served via the API / presigned URLs.
docker-compose.yml (root/dev): had no migrator, so the API ran against an
empty schema (the DB is not migrated at API startup). Add a migrator
(apply --seed) + matching JWT/seed env so the seeded admin can log in,
gate api on it, and add a header clarifying this is the dev compose
(Aspire and deploy/docker are the other paths). DEV-ONLY secrets.
Co-Authored-By: Claude Opus 4.7 (1M context)
@
* feat(infra): switch Redis to Valkey + add RedisInsight cache browser
Valkey 8 (BSD-3, the Linux Foundation Redis fork) replaces the
source-available Redis image across Aspire and both compose files. Drop-in:
the kit uses only StackExchange.Redis over RESP (cache, data protection,
SignalR backplane, quota) with no Redis Stack modules, and Hangfire is on
Postgres. Resource/service name stays "redis" so connection strings and
config keys do not churn.
- AppHost: AddRedis(...).WithImage("valkey/valkey","8") + .WithRedisInsight()
(auto-wired cache browser; SSPL but dev-only). The valkey image ships
redis-* symlinks and supports Aspire's TLS command, so the Redis
integration works unmodified.
- docker-compose.yml (dev): valkey/valkey:8-alpine.
- deploy/docker (prod): valkey/valkey:8-alpine + valkey-server/valkey-cli.
Verified by booting Aspire: Valkey starts clean (tcp+tls listeners),
authenticated PING returns PONG, RedisInsight comes up auto-connected.
Co-Authored-By: Claude Opus 4.7 (1M context)
* test(integration): run the HybridCache distributed-cache test against Valkey
Switch the Testcontainers image from redis:7-alpine to valkey/valkey:8-alpine
so the suite proves the cache round-trip works on the new engine. Verified
locally: 4 passed.
Co-Authored-By: Claude Opus 4.7 (1M context)
* docs(agents): docs repo + changelog must travel with each change
Add golden rule #10 — a user-facing change (feature, endpoint, config, infra,
breaking change) isn't done until the separate docs repo
(github.com/fullstackhero/docs) is updated to match and a changelog entry is
added. Also fix the stale "Redis 6379" ports line to "Valkey 6379".
Co-Authored-By: Claude Opus 4.7 (1M context)
* @ (#1247)
fix(web): return 400 (not 500) for malformed requests / missing required params
Anonymous, tenant-scoped endpoints (forgot-password, reset-password,
self-register) bind a required `tenant` header. When it is missing,
ASP.NET Core throws BadHttpRequestException (StatusCode 400) during
parameter binding. GlobalExceptionHandler did not recognise that type
and rendered it as a generic 500.
Map BadHttpRequestException to its own StatusCode so missing required
header/route/query params (and unreadable/oversized bodies) surface as a
proper 400 (or 413, etc.) with a ProblemDetails body. The fix applies to
every endpoint with a required bound parameter, not just identity.
Mirror the mapping in the test-only DetailedTestExceptionHandler, add
integration regression tests for the three identity endpoints, and add
GlobalExceptionHandler unit tests.
Closes #1245
@
Co-authored-by: Claude Opus 4.7 (1M context)
* Remove obsolete MCP config and superpowers audit/spec/plan files
* @
chore(docker): remove redundant root docker-compose.yml
Local dev is covered by Aspire (FSH.Starter.AppHost) and production by
deploy/docker/docker-compose.yml. The root quick-run compose duplicated
infra config with no unique role; nothing references it.
Co-Authored-By: Claude Opus 4.7 (1M context)
@
* fix(dashboard): UX/a11y/perf/correctness pass (#1248)
Audit-driven fixes across the tenant dashboard. No backend or
BuildingBlocks changes; all verified (tsc clean, eslint 22->12 warnings,
build OK, Playwright 121/121).
Functional bugs:
- realtime-context: wire 4 subscribed-but-unregistered hub events
(PresenceChanged, ChatMessagePinned/Unpinned, ChatChannelMemberRead) so
live presence/pin/read-receipt updates actually arrive.
- chat-search: drop a stateful /g RegExp.test() whose lastIndex drifted
and mis-highlighted matches.
- user-detail: key the staged-roles reset on userId (not the data array)
so a background refetch can no longer wipe unsaved edits.
Accessibility:
- Combobox: replace invalid role="option" in a menu / role="combobox"
without aria-expanded with valid menuitemradio; lift the nested
interactive clear button out to a sibling.
- accessible names for reaction chips, quick-react emoji, EntitySearch,
collapsed sidebar nav items.
- role="status"/sr-only on skeletons, role="progressbar" on upload bars,
DialogTitle on the mobile nav Sheet, anchor-as-menuitem for link items,
h1->h2 heading hierarchy, status rows hidden from the chat log region.
- raise placeholder contrast to AA (drop sub-AA alpha multipliers).
UX/correctness:
- retire hardcoded text-white/bg-white/bg-black via a new
--color-overlay-foreground/--color-overlay token + semantic foregrounds.
- profile form: surface load errors, gate inputs while loading, seed once.
- wire the previously no-op "Reduce motion" toggle through the theme
provider; fix the notifications "open bell" selector.
Cleanup:
- remove 3 `void X` dead-import hacks + a dead effect, swap a full-page
window.location reload for router navigate(), and resolve all 10
exhaustive-deps lint warnings via useMemo.
Tests: update 3 identity empty-state heading-level assertions to match the
corrected h2 hierarchy.
Co-authored-by: Claude Opus 4.7 (1M context)
* fix(admin): UX/a11y/perf/correctness pass
Audit-driven fixes across the operator console. No backend or
BuildingBlocks changes; all verified (tsc clean, eslint 2 errors -> 0,
build OK, Playwright 93/93).
Build-red + real bugs:
- fix 2 lint errors: drop the unused `grant` prop on RowActions
(active-grants-card) and the dangling `react/no-danger` disable
directive that referenced an uninstalled rule (security).
- App.tsx: wrap RouterProvider in a top-level Suspense so the public
lazy routes (login, password reset, confirm-email) have a boundary on
cold chunk fetch instead of throwing.
Accessibility (admin had no eslint-plugin-jsx-a11y — now added):
- Field primitive now threads aria-describedby + aria-invalid to its
control, so every RHF form announces hints/errors (one fix, all forms).
- notification-bell: drop the focusable aria-hidden click-away
(tabIndex=-1), remove the invalid role="menu", add Escape-to-close,
and stop redefining a component inside render.
- skip-to-content link + landmark in AppShell.
- accessible names on unlabelled search/filter inputs (users, audits x3,
impersonate) and the icon-only webhook delete button.
- Segmented toggle gets role="group" + aria-pressed; impersonation
details disclosure gets aria-expanded.
- role="alert" on login/users/tenants inline errors; role="status" on
loaders; reduced-motion now also stops Tailwind's animate-spin.
Performance:
- impersonation list: collapse two overlapping 5s take:200 polls into one
fetch + client-side filtering/counts.
- notification-bell: coalesce the per-event invalidation burst.
- security: lazy-import the ~50KB qrcode lib only at 2FA enrollment.
Correctness:
- sessions revoke: track in-flight ids in a Set so concurrent revokes
don't clear each other's busy state (user-sessions-card + settings).
- invoices row: real