diff --git a/.squad/agents/book/history.md b/.squad/agents/book/history.md
index 8d8b7699..ddbf664b 100644
--- a/.squad/agents/book/history.md
+++ b/.squad/agents/book/history.md
@@ -1,5 +1,22 @@
# Project Context
+## 2026-04-30 — v0.8 Milestone Role Assignment
+
+**Role:** Plugin Ecosystem Documentation Lead (v0.8.1–0.8.3 Tier 3)
+**Effort:** 4–6 hours
+**Timeline:** v0.8.0 completion → 4–6 weeks (Tier 3)
+
+**Tier 3 (plugin ecosystem):**
+- #339 — Documentation for plugin authors (4-6h) — guides, examples, API reference
+
+**Support:**
+- Coordinate with River (#166 plugin packager) on CLI documentation and authoring workflow
+- Work in parallel with River/Simon on plugin ecosystem features; documentation follows code
+
+**Sync point:** Await Tier 1-2 completion; docs can begin once #166 CLI spec is finalized (early in sprint). Deliver with v0.8.1 release.
+
+# Project Context
+
- **Owner:** Jeffrey T. Fritz
- **Project:** SharpSite — a modern, accessible CMS built with .NET 9 and Blazor
- **Stack:** .NET 9, Blazor (SSR + Interactive Server), ASP.NET Core, Entity Framework Core, PostgreSQL, Docker, Playwright, xUnit, GitHub Actions
@@ -8,3 +25,10 @@
## Learnings
+
+---
+**[SCRIBE UPDATE - 2026-04-30 20:15:51]**
+- Decision inbox merged (15 decisions)
+- v0.8.0 scope locked and confirmed
+- Cross-agent context synchronized
+- Ready for parallel execution
diff --git a/.squad/agents/inara/history.md b/.squad/agents/inara/history.md
index 8d8b7699..4b04dd0f 100644
--- a/.squad/agents/inara/history.md
+++ b/.squad/agents/inara/history.md
@@ -8,3 +8,10 @@
## Learnings
+
+---
+**[SCRIBE UPDATE - 2026-04-30 20:15:51]**
+- Decision inbox merged (15 decisions)
+- v0.8.0 scope locked and confirmed
+- Cross-agent context synchronized
+- Ready for parallel execution
diff --git a/.squad/agents/jayne/history.md b/.squad/agents/jayne/history.md
index dd5e8af3..60414beb 100644
--- a/.squad/agents/jayne/history.md
+++ b/.squad/agents/jayne/history.md
@@ -15,3 +15,19 @@ When moving a commit from one branch to another without rewriting remote history
4. Clean up stale `.git/index.lock` if git operations hang (e.g., from crashed prior processes)
5. After cherry-pick succeeds, push destination branch; source branch remains untouched
**Rationale:** Safer than rebasing for remote-tracked branches — no rewrite risk, clear audit trail. Source commit stays on its branch.
+
+### v0.7 Release: Tag & GitHub Release (2026-04-30)
+Completed end-to-end release process for v0.7:
+1. Verified PR #304 merged to `upstream/main` (commit `c35e5e06c5e17b3e0fbada8fb006d30dadcbf5e6`)
+2. Created annotated tag `v0.7` locally
+3. Pushed tag to `upstream` with explicit `refs/tags/v0.7` (necessary because a branch named `v0.7` existed from earlier fetch)
+4. Created GitHub release with auto-generated changelog (v0.2...v0.7)
+5. Release URL: https://github.com/csharpfritz/SharpSite/releases/tag/v0.7
+**Key Learning:** Explicit tag refspec (`refs/tags/v0.7`) prevents ambiguity when a branch and tag share names. GitHub release auto-notes work well after v0.2 baseline is established.
+
+---
+**[SCRIBE UPDATE - 2026-04-30 20:15:51]**
+- Decision inbox merged (15 decisions)
+- v0.8.0 scope locked and confirmed
+- Cross-agent context synchronized
+- Ready for parallel execution
diff --git a/.squad/agents/kaylee/history.md b/.squad/agents/kaylee/history.md
index d0feb2ab..f2450d41 100644
--- a/.squad/agents/kaylee/history.md
+++ b/.squad/agents/kaylee/history.md
@@ -1,5 +1,45 @@
# Project Context
+## 2026-04-30 — v0.8 Milestone Role Assignment
+
+**Role:** Test Coverage Lead (v0.8.1–0.8.3 Tier 3)
+**Effort:** 5–7 hours
+**Timeline:** v0.8.0 completion → 4–6 weeks (Tier 3)
+
+**Tier 3 (plugin ecosystem):**
+- #341 — Unit tests for PluginPackager (3-4h) — testing infrastructure for #166
+- #319 — E2E test cases (2-3h) — support Wash; cover OOTB experience gaps
+
+**Support:**
+- Support River on plugin ecosystem test cases; Kaylee to suggest test structures for #166, #336, #334, #254
+
+**Sync point:** Await Tier 1 completion (E2E framework stability) before #319 test spike. Coordinate with River on packager test harness design (early in #166 work).
+
+### 2026-04-30 — Simon Admin Asset + River Trash Delete: Test Infrastructure Ready
+
+**Status:** Completed (Simon) + Proposed (River)
+**Issues:** #320, #321, #323, #322 (Simon) | #309, #299 (River delete contract)
+
+**Simon's Asset Component Tests:**
+- `SiteAssetManager` Blazor component now testable
+- Test patterns: upload validation, preview URL generation, removal behavior
+- Reusable across setup wizard (Step 2) and admin maintenance
+- Build: 57/57 tests passing
+
+**River's Trash Delete Contract:**
+- Backend pattern defined: `ITrashableContent` + repository extensions (`GetDeleted*`, `Restore*`, `PermanentlyDelete*`)
+- Normal queries exclude trashed records by default
+- Soft delete (archive + recovery) instead of hard delete
+
+**Impact on Kaylee:**
+- **Asset Component Tests:** Can now write unit tests for `SiteAssetManager` (upload validation, error cases, state updates)
+- **Trash/Restore Tests:** Clear backend contract for test cases (delete → GetDeleted, restore → Restore, purge → PermanentlyDelete)
+- **Test Scope:** Asset tests in Tier 2 support (Simon phase); trash tests in Tier 3 support (River phase)
+
+**Next:** Coordinate with River on trash test harness design as #309/#299 scope activates post-Tier 1.
+
+
+
- **Owner:** Jeffrey T. Fritz
- **Project:** SharpSite — a modern, accessible CMS built with .NET 9 and Blazor
- **Stack:** .NET 9, Blazor (SSR + Interactive Server), ASP.NET Core, Entity Framework Core, PostgreSQL, Docker, Playwright, xUnit, GitHub Actions
@@ -25,3 +65,18 @@ River completed the Security P0 Remote Code Execution vulnerability fix (#346).
- `tests/SharpSite.Tests.Plugins/ConcurrentAccessTests.cs` — Issue #348: 3 tests for concurrent PluginAssemblyManager AddAssembly/RemoveAssembly and read-while-write safety.
- **Lambda discard gotcha**: Don't use `_ =` for discards inside lambdas where `_` is already the lambda parameter (causes CS0029). Use named locals instead.
- **Central Package Management**: Test projects use `` without `Version` attribute; versions are in `Directory.Packages.props`.
+
+### 2026-04-30 — Trash repository unit coverage for pages and posts
+- **Repository harness**: `PgPageRepository` and `PgPostRepository` can be exercised deterministically with EF Core InMemory by registering `PgContext` against a shared `InMemoryDatabaseRoot` in a per-test `ServiceProvider`.
+- **Coverage seam**: Trash tests should assert both persistence state (`IsDeleted`, `DeletedAt`) and read-path behavior (`Get*`, `GetDeleted*`, restore, purge) so soft delete never leaks back into normal queries.
+- **Page add gotcha**: `PgPageRepository.AddPage` saves a cast copy and returns the original `Page`, so the generated database `Id` is not populated on the returned object; tests must re-read the stored page before using its key.
+- **Files added**:
+ - `tests/SharpSite.Tests.Web/Data/Postgres/PgPageRepositoryTrashTests.cs`
+ - `tests/SharpSite.Tests.Web/Data/Postgres/PgPostRepositoryTrashTests.cs`
+
+---
+**[SCRIBE UPDATE - 2026-04-30 20:15:51]**
+- Decision inbox merged (15 decisions)
+- v0.8.0 scope locked and confirmed
+- Cross-agent context synchronized
+- Ready for parallel execution
diff --git a/.squad/agents/mal/history.md b/.squad/agents/mal/history.md
index 071519b3..af53c9bd 100644
--- a/.squad/agents/mal/history.md
+++ b/.squad/agents/mal/history.md
@@ -1,39 +1,53 @@
# Project Context
- **Owner:** Jeffrey T. Fritz
-- **Project:** SharpSite — a modern, accessible CMS built with .NET 9 and Blazor
+- **Project:** SharpSite a modern, accessible CMS built with .NET 9 and Blazor
- **Stack:** .NET 9, Blazor (SSR + Interactive Server), ASP.NET Core, Entity Framework Core, PostgreSQL, Docker, Playwright, xUnit, GitHub Actions
- **Created:** 2026-03-26
-## Learnings
+## Core Context
-
+**v0.8.0 Sprint (CURRENT - Locked & Ready)**
+- **Status:** Locked scope; all clarifications answered; squad ready for execution
+- **8 Active Issues:** 3647h total (Tier 1: ship-blockers; Tier 2: core CMS)
+- **Squad Assignments:**
+ - Simon: #322 (setup), #323 (favicon), #320 (logo), #321 (logo-display), #328 (CSS) 5 issues, 1623h
+ - River: #309 (page-delete), #299 (post-delete) 2 issues, 1216h
+ - Wash: #319 (E2E tests) 1 issue, 810h
+ - Zoe: Supporting (badges) 34h (waiting)
+- **First Batch Ready:** #322, #323, #319, #309, #299 (zero blockers)
+- **Critical Path:** Parallel execution; Tier 1 + Tier 2 concurrent
+- **Execution:** Ready to launch now
-### 2026-03-26 — Issue Triage Results (6 squad-labeled issues)
+**Clarifications Resolved (v0.8 Planning)**
+- #208/#209 (file browser): Both ship in v0.8.0
+- #212 (autosave): Deferred to v0.8.1+ (scope creep)
+- #203/#117/#118 (plugin lifecycle): Deferred to v0.8.1+
+- #313 (CSP header): Deferred to v0.8.1+
+- #297 (home post display): Deferred
-**Routed to squads:**
-- **River** (Backend): #346 (P0 RCE), #347 (ZIP bomb), #348 (thread safety), #349 (plugin signing)
-- **Simon** (Frontend): #350 (forced password reset UI)
-- **Wash** (E2E): #351 (.NET 10 validation)
+**v0.7 Completion & v0.8 Planning (Pre-May 2026)**
+- Triaged 73 open issues; identified 8 core items for v0.8
+- v0.7 blockers resolved: Security P0s (#346 RCE, #347 ZIP bomb, #348 threading) + build stabilization
+- Plugin architecture assessed; three security gaps addressed in v0.7 fixes
+- GitHub Issues re-enabled by owner (was disabled, blocking triage)
+- v0.8 milestone applied to 8 active issues; v0.7 stragglers triaged/deferred
-**Priority ordering:**
-1. #346: Security P0 RCE via TypeNameHandling.Auto → **blocks production**
-2. #347, #349: Security vulnerabilities (plugin extraction, DLL validation) → **blocks production**
-3. #348: Thread-safety bug (PluginManager static state) → **critical correctness**
-4. #350: Security hardening (forced password reset) → **medium priority**
-5. #351: .NET 10 validation → **mostly done, E2E pending**
+## Recent Decisions
-All issues labeled with `squad:{member}` and triage comments added. Labels created: squad:river, squad:simon, squad:wash.
+*All decisions merged to .squad/decisions.md (canonical source)*
-### 2026-03-26 — Plugin System Architecture Review (spike_DatabasePlugin branch)
+**2026-05-03:** v0.8.0 squad assignment locked. 8 issues routed per expertise; sequencing confirmed; no blockers.
-- **Branch health: RED** — Build fails with 60 errors. Root cause is type ambiguity between `SharpSite.Abstractions.Security` types and `Microsoft.AspNetCore.Identity` types in `src/SharpSite.Security.Postgres/`. The security abstraction migration is incomplete.
-- **Plugin system architecture** is convention-based + attribute-driven. Plugins are `.sspkg` ZIP files loaded via collectible `AssemblyLoadContext`. Services registered via `RegisterPluginAttribute` and reflection scanning in `PluginManager.RegisterWithServiceLocator()`.
-- **Critical security finding:** `ApplicationState.cs` uses `TypeNameHandling.Auto` with Newtonsoft.Json — a known RCE deserialization vector (lines 130-134, 212-216). Must be fixed before production.
-- **Critical security finding:** No assembly validation, code signing, or integrity checking on plugin DLLs. Any DLL in the plugins directory runs with full app permissions.
-- **Plugin extension points:** FileStorage, DataStorage (Config, EfContext, PageRepo, PostRepo), Security (SignIn, UserManager, UserRepo, EmailSender). Mapped in `PluginTypeMapping.cs`.
-- **Static mutable state** in `PluginManager` (`_ServiceDescriptors`, `_ServiceProvider`) is a thread safety concern.
-- **`IRunAtStartup` interface** is defined but never invoked by the PluginManager — lifecycle hooks are dead code.
-- **Postgres plugin `manifest.json` is empty** (0 bytes) — would fail validation through standard plugin loading.
-- **Hardcoded default admin** (`admin@localhost` / `Admin123!`) in the Postgres plugin's `RegisterPluginServices.cs`.
-- Full analysis written to `.squad/decisions/inbox/mal-plugin-analysis.md`.
+**2026-05-01:** File browser clarification (#208/#209); plugin lifecycle deferral (#203/#117/#118); autosave deferral (#212).
+
+**2026-04-30:** GitHub issues enabled; milestone triage complete; 10 v0.7 items deferred to backlog.
+
+---
+
+**[SCRIBE UPDATE - 2026-04-30 20:18:02]**
+- Decision inbox merged (15 decisions into decisions.md)
+- v0.8.0 scope confirmed locked
+- Cross-agent context synchronized
+- Ready for parallel execution
+- Agent history files updated across team
diff --git a/.squad/agents/ralph/history.md b/.squad/agents/ralph/history.md
index c79388ac..15402d1c 100644
--- a/.squad/agents/ralph/history.md
+++ b/.squad/agents/ralph/history.md
@@ -12,3 +12,10 @@ Ralph initialized. Team cast from Firefly universe on 2026-03-26.
## Learnings
📌 Team initialized on 2026-03-26 — 9 agents + Scribe + Ralph
+
+---
+**[SCRIBE UPDATE - 2026-04-30 20:15:51]**
+- Decision inbox merged (15 decisions)
+- v0.8.0 scope locked and confirmed
+- Cross-agent context synchronized
+- Ready for parallel execution
diff --git a/.squad/agents/river/history.md b/.squad/agents/river/history.md
index b4c41121..fdf64bc5 100644
--- a/.squad/agents/river/history.md
+++ b/.squad/agents/river/history.md
@@ -1,5 +1,73 @@
# Project Context
+## 2026-04-30 — v0.8 Milestone Role Assignment
+
+**Role:** Plugin Ecosystem Lead (v0.8.0 → v0.8.x)
+**Effort:** 35–40 hours
+**Timeline:** v0.8.0 (6 weeks), v0.8.1–0.8.3 (4–6 weeks)
+
+**Tier 1 (concurrent):**
+- #309 — Delete a page (4-6h) — data layer + API + UI
+- #299 — Delete a post (2-3h) — similar scope
+
+**Tier 3 (after Tier 1-2):**
+- #166 — Plugin packager tool (8-10h) — CLI to build .sspkg files
+- #336 — Upload plugins without install (4-6h) — staging area
+- #334 — Choose DB plugin during install (6-8h) — installer backend + setup
+- #254 — Move Postgres to plugin (6-8h) — refactor + migration tests
+
+**Key risks:** Plugin packager complexity (mitigate: spike week 1), DB plugin refactor data loss risk (dry-run first).
+
+**Sync point:** v0.8.0 ship decision gates all Tier 3 work. Coordinate with Simon (installer UX) and Zoe (CI).
+
+### 2026-05-01 — v0.8 Tier 2 Backend Work Queued: Delete Pages/Posts Clarification Pending
+
+**Status:** QUEUED pending Tier 1 completion + clarification
+**Issues:** #309 (Delete page), #299 (Delete post)
+**Effort:** 6–9 hours combined (Tier 1 Phase 2 work)
+**Critical Gap:** Delete semantics undefined — hard delete or soft (archive with recovery)?
+
+**Impact on River:**
+- Cannot scope #309/#299 schema/data layer changes until delete semantics clarified
+- Hard delete is simpler (2–3h); soft delete with archive complicates schema by 2–3x (4–6h)
+
+**Dependencies:**
+- Tier 1 completion (Wash's #319, #351, #272 + Simon's #350) gates start
+- User clarification on delete semantics required (Q5 in Tier 2 phase)
+
+**Next Action:** User provides delete semantics; Mal updates #309/#299 with acceptance criteria. River can then scope schema/API work post-Tier 1.
+
+### 2026-04-30 — River Trash Delete Backend Contract Documented (Proposed)
+
+**Status:** Proposed
+**Issues:** #309 (Delete page), #299 (Delete post)
+
+**Backend Contract Finalized:**
+- Pattern: Soft delete (archive + recovery) via `ITrashableContent` + repository extensions
+- Methods: `GetDeleted*()`, `Restore*()`, `PermanentlyDelete*()`
+- Normal queries exclude trashed records by default
+- Deterministic behavior: delete → soft archive; restore → un-archive; purge → permanent removal
+
+**Schema Impact:**
+- Soft delete requires `IsDeleted` flag + optional `DeletedAt` timestamp on pages/posts tables
+- Complexity: ~4–6 hours for dual-repository implementation + migration tests
+- Risk mitigation: Dry-run migration on test DB first
+
+**Simon UI Contract:**
+- Delete button: calls `DeletePage(int id)` / `DeletePost(string slug)` (soft delete)
+- Trash UI (future): reads `GetDeletedPages()` / `GetDeletedPosts()`
+- Restore actions: calls `RestorePage(int id)` / `RestorePost(string slug)`
+- Purge actions: calls `PermanentlyDeletePage(int id)` / `PermanentlyDeletePost(string slug)`
+
+**Impact on River:**
+- Ready to spike schema + migration design once Tier 1 completes
+- Test infrastructure (trash/restore patterns) ready; Kaylee can define test cases early
+- Unblocks Simon's future Trash UI work (backend contract clear)
+
+**Next:** Await Tier 1 completion + #309/#299 scope activation; coordinate with Kaylee on test harness design.
+
+
+
- **Owner:** Jeffrey T. Fritz
- **Project:** SharpSite — a modern, accessible CMS built with .NET 9 and Blazor
- **Stack:** .NET 9, Blazor (SSR + Interactive Server), ASP.NET Core, Entity Framework Core, PostgreSQL, Docker, Playwright, xUnit, GitHub Actions
@@ -9,6 +77,12 @@
+### 2026-05-03 — Trash Delete Pattern for Pages and Posts
+
+Pages and posts now share a trash-state contract via `ITrashableContent` plus `TrashableContentExtensions` in `SharpSite.Abstractions`. The delete flow is soft delete only: repository `Delete*` methods mark `IsDeleted = true`, stamp `DeletedAt`, and all normal `GetPage/GetPages/GetPost/GetPosts` queries exclude trashed content by default.
+
+Both `SharpSite.Data.Postgres` and `SharpSite.Plugins.Data.Postgres` expose the same recovery contract: `GetDeleted*`, `Restore*`, and `PermanentlyDelete*`. EF migrations `20260503101500_AddTrashStateToPagesAndPosts` in both projects add the shared `IsDeleted`/`DeletedAt` columns needed for recycle-bin behavior.
+
### 2026-03-26 — Security Context from Mal's Analysis
River should be aware of two critical P0 security issues identified on spike_DatabasePlugin branch:
@@ -49,6 +123,14 @@ Fixed remaining build error in `RegisterPostgresSecurityServices.cs:33`. The `Sh
- Updated test file `WhenFileExists.cs` to use `System.Text.Json`.
- All 47 unit tests pass. Build is clean.
+---
+**[SCRIBE UPDATE - 2026-05-01 00:02:00Z — Kaylee Trash Backend Unit Tests Completed]**
+- Kaylee completed repository-level unit test harness validating River's trash backend contracts (#309/#299)
+- Tests cover: soft delete, restore, permanent purge, trashed-content exclusion
+- All test projects passing (SharpSite.Tests.Web ✅, SharpSite.Tests.Plugins ✅)
+- **Critical correctness note propagated:** `PgPageRepository.AddPage()` returns the caller's original `Page` instance, NOT the tracked `PgPage`. Generated database `Id` is NOT populated on the returned object. Any caller needing the persisted key must re-read after insertion. This applies to repository callers in API endpoints (Simon) and wherever Page objects are materialized.
+- River's trash delete backend (#309/#299) contracts validated; ready for Simon API integration and Wash E2E testing
+
**Pattern to remember:** For any future polymorphic serialization in the plugin system, use the `ConfigurationSectionJsonConverter` pattern: validate the resolved type implements the expected interface before instantiation. Never allow arbitrary type resolution from JSON input.
### 2026-03-31 — Security: ZIP Bomb & Path Traversal Protection in Plugin Extraction
@@ -97,3 +179,10 @@ Fixed remaining build error in `RegisterPostgresSecurityServices.cs:33`. The `Sh
**Pattern to remember:** For plugin integrity, validate at two points: (1) the file hash before loading bytes into memory, and (2) the assembly metadata after loading. Store hashes on first install and verify on every subsequent load. At startup, catch validation failures per-plugin and `continue` to avoid one bad plugin blocking all others.
**Cross-agent coordination:** Simon's #350 forced password reset is independent; no blocking dependencies.
+
+---
+**[SCRIBE UPDATE - 2026-04-30 20:15:51]**
+- Decision inbox merged (15 decisions)
+- v0.8.0 scope locked and confirmed
+- Cross-agent context synchronized
+- Ready for parallel execution
diff --git a/.squad/agents/scribe/history.md b/.squad/agents/scribe/history.md
index fe8e1941..643a5dc6 100644
--- a/.squad/agents/scribe/history.md
+++ b/.squad/agents/scribe/history.md
@@ -12,3 +12,10 @@ Scribe initialized. Team cast from Firefly universe on 2026-03-26.
## Learnings
📌 Team initialized on 2026-03-26 — 9 agents + Scribe + Ralph
+
+---
+**[SCRIBE UPDATE - 2026-04-30 20:15:51]**
+- Decision inbox merged (15 decisions)
+- v0.8.0 scope locked and confirmed
+- Cross-agent context synchronized
+- Ready for parallel execution
diff --git a/.squad/agents/simon/history.md b/.squad/agents/simon/history.md
index a5fdc8f6..d1dfe29e 100644
--- a/.squad/agents/simon/history.md
+++ b/.squad/agents/simon/history.md
@@ -1,5 +1,60 @@
# Project Context
+## 2026-04-30 — v0.8 Milestone Role Assignment
+
+**Role:** Admin UI Lead (v0.8.0 Tier 2)
+**Effort:** 15–20 hours
+**Timeline:** v0.8.0 (6 weeks)
+
+**Tier 2 (after Tier 1 ship-blockers):**
+- #321 — Logo management in Admin UI (3-4h) — upload, preview, admin panel
+- #323 — Set favicon (1-2h) — small UI + metadata
+- #328 — Custom CSS in Admin (2-3h) — text editor in admin panel
+
+**Support:**
+- #334 — Choose DB plugin during install (UX portion) — installer flows
+
+**Sync point:** Coordinate with River on installer UX for #334. Both Tier 1 and Tier 2 must complete before Tier 3 (plugin ecosystem) activates.
+
+### 2026-05-01 — v0.8 Tier 1 Clarification Blocker: #350 Forced Password Reset UX
+
+---
+**[SCRIBE UPDATE - 2026-05-01 00:04:00Z — Simon #319 E2E Blocker: Public Post Route Fix]**
+- **ASSIGNED:** Fix published post public URLs (CreatePostTests.CanCreateSaveAndPublishPost blocker)
+- **Test Context:** E2E test in `/tests/SharpSite.E2E/Tests/CreatePostTests.cs`
+- **Failure Point:** Post creation/save/publish flow works; live-site assertion fails with 404
+- **Root Cause:** Route not mapped or URL generation logic missing for published post public access
+- **Expected Fix:** Add/fix post publication route handler + ensure URL generation in UI resolves correctly
+- **Scope:** Isolated route/URL generation fix; minimal scope, critical path impact
+- **Validation:** Wash will rerun E2E test once route is fixed
+- **Priority:** CRITICAL — unblocks E2E suite acceptance for v0.8.0
+
+---
+
+---
+
+**[SCRIBE UPDATE - 2026-05-01 00:06:00Z — Simon Public Post Route Fix COMPLETED]**
+- ✅ Fixed /post/{slug} public route blocker (#319)
+- **Root Cause:** SSR page treated "not loaded yet" as "not found", triggering premature 404
+- **Solution:** Thin route page + shared rendering component + explicit load-complete gate before PageNotFound
+- **Validation:** Live post URL returns 200 | E2E test CreatePostTests.CanCreateSaveAndPublishPost PASSES
+- **Impact:** Issue #319 first execution wave blocker RESOLVED; v0.8.0 Tier 1 unblocked
+- **Next:** Wash revalidates full E2E suite against fixed route
+
+---
+
+**Status:** BLOCKED pending user clarification
+**Issue:** #350 (Forced password reset after initial admin seed) lacks UX flow definition.
+**Critical Gap:** Should password reset happen immediately on first login, or allow N logins before enforcing?
+
+**Impact on Simon:**
+- Cannot scope #350 UI effort until UX flow is defined (3–4h estimate depends on scope)
+- Cannot begin UI spike until redirect timing and warning placement are clarified
+
+**Next Action:** User provides UX flow requirements; Mal updates #350 with acceptance criteria. Simon can then scope UI implementation.
+
+# Project Context
+
- **Owner:** Jeffrey T. Fritz
- **Project:** SharpSite — a modern, accessible CMS built with .NET 9 and Blazor
- **Stack:** .NET 9, Blazor (SSR + Interactive Server), ASP.NET Core, Entity Framework Core, PostgreSQL, Docker, Playwright, xUnit, GitHub Actions
@@ -9,6 +64,15 @@
+### Public Post SSR Routing (Issue #319 blocker) — 2026-05-01
+- `DisplayPost`-style public SSR pages must not treat `null` model state as "not found" during the first render. With async repository lookups, Blazor can render once before the awaited load finishes, so a premature `PageNotFound` sets HTTP 404 even when the content exists.
+- For SharpSite public content pages, keep an explicit load-complete flag and only render `PageNotFound` after the async fetch has finished. A dedicated `/post/{slug}` route component can stay thin and delegate to a shared page component for lookup/rendering.
+
+### Setup Wizard + Site Assets (Issues #322, #320, #321, #323) — 2026-05-02
+- The startup wizard now has a natural four-step flow: site name → site identity assets → database setup → first admin account. Step transitions are stored in `ApplicationState.StartupStep`, so saving state between steps matters.
+- `ApplicationState` is the durable source of truth for site branding (`HasCustomLogo`, `HasCustomFavicon`), while the actual files live behind `/api/files/{filename}`. UI can stay simple if it only updates the stored filename and lets layout components resolve URLs.
+- A single reusable Blazor asset component works well for both setup and admin maintenance as long as it owns preview/upload/remove behavior and parents own persistence.
+
### Forced Password Reset (Issue #350) — 2026-03-31
- ASP.NET Core Identity user claims (AspNetUserClaims table) are included in the auth cookie automatically by `SignInManager.PasswordSignInAsync`. This makes claims ideal for request-level checks without hitting the DB.
- The `MustChangePassword` feature uses a claim-based approach: add claim on seed → check in Login.razor + middleware → remove claim + RefreshSignInAsync after password change.
@@ -17,3 +81,19 @@
- The admin seed happens in `RegisterPostgresSecurityServices.ConfigureHttpApp()`, called from `StartApi.cs` — not during `Program.cs` startup directly.
- E2E tests (Playwright) require a running Aspire stack and fail locally without it — this is expected.
- **Cross-agent coordination:** River's #346–#349 security fixes are parallel, non-blocking work on the plugin system. No dependencies on this feature.
+
+---
+**[SCRIBE UPDATE - 2026-04-30 20:15:51]**
+- Decision inbox merged (15 decisions)
+- v0.8.0 scope locked and confirmed
+- Cross-agent context synchronized
+- Ready for parallel execution
+
+---
+**[SCRIBE UPDATE - 2026-05-01 00:02:00Z — Kaylee Trash Backend Unit Tests Completed]**
+- Kaylee completed repository-level unit test harness for trash/recycle bin flows (#309/#299)
+- Tests validate: soft delete, restore, permanent purge, trashed-content exclusion
+- All test projects passing (SharpSite.Tests.Web ✅, SharpSite.Tests.Plugins ✅)
+- **CRITICAL FOR API ENDPOINTS:** `PgPageRepository.AddPage()` returns the caller's original `Page` instance, NOT the tracked `PgPage`. Generated database `Id` is NOT populated on the returned object. Any caller needing the persisted key MUST re-read after insertion.
+- **Action for Simon:** When implementing delete/restore API endpoints, apply ID re-read pattern after AddPage calls to ensure generated keys are available for return payloads.
+- River trash backend (#309/#299) contracts ready for integration into API endpoints
diff --git a/.squad/agents/wash/history.md b/.squad/agents/wash/history.md
index 8d8b7699..292d6aef 100644
--- a/.squad/agents/wash/history.md
+++ b/.squad/agents/wash/history.md
@@ -1,5 +1,77 @@
# Project Context
+## 2026-04-30 — v0.8 Milestone Role Assignment
+
+**Role:** E2E Validation Lead (v0.8.0 Tier 1)
+**Effort:** 8–10 hours
+**Timeline:** v0.8.0 (2–3 weeks critical path)
+
+**Tier 1 (ship-blockers):**
+- #351 — .NET 10 + Aspire validation (E2E) (2-3h) — Playwright + AppHost
+- #319 — E2E test for out-of-box experience (4-6h) — prevents "works on my machine"
+- #272 — Fix Playwright badge/reports in CI (2-3h) — unblock test visibility
+
+**Responsibilities:**
+- Add retry logic + diagnostics to #319 (E2E flakiness mitigation)
+- Validate full E2E pipeline in CI post-fix
+- Coordinate with Zoe (CI/badges) and Kaylee (test cases)
+
+**Risk mitigation:** 2-week buffer for E2E flakiness; instrument diagnostics early.
+
+**Sync point:** Tier 1 completion gates Tier 2-3 work. E2E tests must pass CI before v0.8.0 release.
+
+### 2026-04-30 — Simon Completion: Admin Asset UI + Setup Wizard (Tier 2 ✅)
+
+**Status:** COMPLETED
+**Issues:** #320, #321, #323, #322 (setup wizard integration)
+**Deliverables:** Reusable `SiteAssetManager` component + 4-step setup wizard
+
+**Simon's Asset Decision:** One shared Blazor component for both setup and admin asset management. Assets managed through `ApplicationState` flags, resolved via `/api/files/{filename}` with deterministic names (`site-logo.*`, `site-favicon.*`).
+
+**Impact on Wash:**
+- **E2E Test Scenarios Now Available:**
+ - Setup wizard: Verify asset upload in Step 2 (site identity)
+ - Admin panel: Verify logo/favicon upload, preview, removal
+ - Cross-flow: Verify assets persist across setup + admin maintenance
+- **Ready:** Can spike E2E test case coverage for setup + admin asset workflows
+- **Test data:** Use SiteAssetManager test patterns from Simon's implementation (upload validation, preview URLs)
+
+**Build Status:** SharpSite.Web clean ✅ | SharpSite.Tests.Web 57/57 ✅
+
+**Next:** Wash can now integrate asset upload E2E scenarios into #319 test spike.
+
+
+
+---
+**[SCRIBE UPDATE - 2026-05-01 00:03:00Z — Wash #319 E2E v0.8.0 Execution COMPLETE]**
+- ✅ E2E test suite: Login + admin workflow acceptance coverage
+- ✅ Admin workflow helpers: Reusable test utilities for setup wizard, asset management
+- ✅ Data-testid seams: Stable element selectors across Blazor components
+- ✅ Forced password change E2E: Full flow validation pass
+- ✅ Navigation flakiness: Replaced timing waits with condition-based polling
+- ✅ Build status: SharpSite.Web clean, SharpSite.E2E builds clean
+- ⏳ **BLOCKER:** CreatePostTests.CanCreateSaveAndPublishPost fails at live-site assertion
+ - Published post public URLs return 404 (route not mapped or URL generation issue)
+ - Post create/save/publish flow works; issue is at public URL resolution layer
+ - E2E test ready to validate once route is fixed
+- **HANDOFF:** Simon assigned to fix public post route (Issue #319 blocker)
+- **NEXT:** Wash standing by to revalidate E2E test against fixed route
+
+---
+
+**Status:** E2E SUITE REVALIDATION READY
+**Issue:** #351 (.NET 10 + Aspire 13.2 E2E validation) awaiting acceptance criteria
+**Blocker Resolved:** Published post public route fixed by Simon; E2E CreatePostTests.CanCreateSaveAndPublishPost now PASSES
+
+**Current Actions:**
+- Revalidate full E2E test pipeline against Simon's route fix
+- Confirm E2E suite completes successfully end-to-end
+- Document any remaining flakiness or timing issues for v0.8.0 burndown
+
+**Next Steps:** Run E2E suite; document results in session log
+
+# Project Context
+
- **Owner:** Jeffrey T. Fritz
- **Project:** SharpSite — a modern, accessible CMS built with .NET 9 and Blazor
- **Stack:** .NET 9, Blazor (SSR + Interactive Server), ASP.NET Core, Entity Framework Core, PostgreSQL, Docker, Playwright, xUnit, GitHub Actions
@@ -8,3 +80,19 @@
## Learnings
+
+---
+**[SCRIBE UPDATE - 2026-04-30 20:15:51]**
+- Decision inbox merged (15 decisions)
+- v0.8.0 scope locked and confirmed
+- Cross-agent context synchronized
+- Ready for parallel execution
+
+---
+**[SCRIBE UPDATE - 2026-05-01 00:02:00Z — Kaylee Trash Backend Unit Tests Completed]**
+- Kaylee completed repository-level unit test harness for trash/recycle bin flows (#309/#299)
+- Tests validate: soft delete, restore, permanent purge, trashed-content exclusion
+- All test projects passing (SharpSite.Tests.Web ✅, SharpSite.Tests.Plugins ✅)
+- Unit test foundation ready for E2E test scenarios on #319
+- River trash backend contracts validated; ready for Simon API integration
+- **Important:** Repository tests exclude trashed content from normal queries by default — E2E tests should verify this behavior end-to-end
diff --git a/.squad/agents/zoe/history.md b/.squad/agents/zoe/history.md
index 27281634..dc8c7f2e 100644
--- a/.squad/agents/zoe/history.md
+++ b/.squad/agents/zoe/history.md
@@ -1,5 +1,21 @@
# Project Context
+## 2026-04-30 — v0.8 Milestone Role Assignment
+
+**Role:** CI/DevOps & Test Reporting Lead (v0.8.0 Tier 1)
+**Effort:** 3–4 hours
+**Timeline:** v0.8.0 (2–3 weeks)
+
+**Tier 1 (ship-blockers):**
+- #272 — Fix Playwright badge/reports in CI (2-3h) — unblock test visibility in README + CI status page
+
+**Support:**
+- #319 — E2E pipeline setup (1-2h) — coordinate with Wash on CI workflow
+
+**Sync point:** #272 must complete before v0.8.0 release. Coordinate with Wash (E2E) and Kaylee (test results). No production Docker deployment needed for this release.
+
+# Project Context
+
- **Owner:** Jeffrey T. Fritz
- **Project:** SharpSite — a modern, accessible CMS built with .NET 9 and Blazor
- **Stack:** .NET 9, Blazor (SSR + Interactive Server), ASP.NET Core, Entity Framework Core, PostgreSQL, Docker, Playwright, xUnit, GitHub Actions
@@ -16,3 +32,19 @@
- The effective timeout was ~10.5 minutes (90 retries × ~7s per retry due to 5s HTTP timeout + 2s sleep), not the intended 3 minutes
- Fix: Added `WithHttpEndpoint(port: 5020, name: "http")` when `testOnly` is true, reduced HTTP timeout to 2s, added stderr capture and diagnostic logging on failure
- The PR is from a fork (`csharpfritz/SharpSite`), so fork-PR limitations (no secrets access) also apply but are not the root cause here
+
+### v0.7 Release Blocker: OpenTelemetry CVE (2026-03-31)
+- After merging PR #352 (.NET 10 + Aspire 13.2 upgrade), the build fails with NuGet security warnings-as-errors
+- OpenTelemetry.* 1.15.0 has three moderate CVEs (GHSA-g94r-2vxg-569j, GHSA-mr8r-92fq-pj8p, GHSA-q834-8qmm-v933)
+- Directory.Packages.props pins 1.15.0 as the "floor required by Aspire 13.2"
+- TreatWarningsAsErrors is enabled, so security warnings → build errors → CI fails
+- Root cause: Aspire 13.2 dependency chain requires 1.15.0, but that version has known CVEs
+- **Action:** River must decide: (a) upgrade OpenTelemetry to newer 1.x if compatible, (b) suppress NU1902 warnings and document CVE risk, or (c) revert .NET 10 upgrade pending upstream fix
+- **Release Impact:** v0.7 cannot ship until this is resolved
+
+---
+**[SCRIBE UPDATE - 2026-04-30 20:15:51]**
+- Decision inbox merged (15 decisions)
+- v0.8.0 scope locked and confirmed
+- Cross-agent context synchronized
+- Ready for parallel execution
diff --git a/.squad/commit_msg.txt b/.squad/commit_msg.txt
new file mode 100644
index 00000000..554a7345
--- /dev/null
+++ b/.squad/commit_msg.txt
@@ -0,0 +1,19 @@
+Scribe: Log Kaylee trash backend unit-test completion & merge decisions
+
+**Orchestration:**
+- Added 2026-05-01T00-02-Kaylee.md: Unit test harness completion for trash/recycle bin flows (#309/#299)
+- Added 2026-05-01T00-02-Kaylee-trash-tests.md: Session summary for trash-flow test coverage
+
+**Decision Merge:**
+- Merged kaylee-trash-tests.md from decisions/inbox/ to decisions.md
+- Deleted inbox file after merge
+
+**Cross-Agent Updates:**
+- Propagated Kaylee trash-flow test results to Simon history (API endpoint correctness note on AddPage Id)
+- Propagated Kaylee trash-flow test results to Wash history (E2E foundation ready)
+- Propagated Kaylee trash-flow test results to River history (contract validation complete)
+
+**Critical Note:**
+PgPageRepository.AddPage() returns original Page instance (not tracked PgPage), so generated Id must be re-read by callers.
+
+Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
diff --git a/.squad/decisions-archive.md b/.squad/decisions-archive.md
new file mode 100644
index 00000000..9e51b714
--- /dev/null
+++ b/.squad/decisions-archive.md
@@ -0,0 +1,168 @@
+# Squad Decisions Archive
+
+**Archived:** 2026-04-30
+**Policy:** Entries older than 30 days moved from decisions.md
+
+---
+
+### Security P0: Remove TypeNameHandling.Auto RCE Vector (2026-03-26) ✅ COMPLETED
+**Status:** Completed
+**Owner:** River
+**Issue:** `ApplicationState.cs` (lines 130-134, 212-216) uses `TypeNameHandling.Auto` with Newtonsoft.Json — a well-documented Remote Code Execution deserialization vulnerability. Attacker can modify `plugins/applicationState.json` to inject arbitrary type instantiation payloads.
+**Resolution:** Replaced all Newtonsoft.Json usage with System.Text.Json. Implemented ConfigurationSectionJsonConverter for safe polymorphic deserialization.
+**Completion:** 2026-03-31T13:47
+**Verification:** Build clean, 47 tests pass, Newtonsoft.Json fully removed.
+**Blocker Status:** CLEARED — Plugin system production readiness unblocked.
+
+### Security P0: ZIP Bomb Protection for Plugin Extraction (2026-03-31) ✅ COMPLETED
+**Status:** Completed
+**Owner:** River
+**Issue:** `#347` — Plugin ZIP extraction in `PluginManager.cs` had no safety limits. An attacker could upload a ZIP bomb (42KB compressed → petabytes uncompressed) to exhaust server disk. Path traversal via `../` sequences in entry names was also not explicitly blocked.
+**Resolution:** Added two-layer security validation:
+- **Layer 1 (upload-time):** `ValidateArchiveSecurity` validates max total size (100MB), per-file cap (50MB), compression ratio (100:1), and rejects path traversal via `..`
+- **Layer 2 (extraction-time):** Defense-in-depth validation during file writes using `Path.GetFullPath()` containment checks
+**Completion:** 2026-03-31T13:55
+**Verification:** Build clean, 55 tests pass (8 ZIP security tests included)
+**Blocker Status:** CLEARED — Plugin ZIP extraction production ready.
+
+### Security P0: Implement Assembly Validation for Plugin Loading (2026-03-26) ✅ COMPLETED
+**Status:** Completed (Phase 1)
+**Owner:** River
+**Issue:** Plugin DLLs in the plugins directory are loaded and executed with zero integrity verification, code signing, or assembly name validation. A malicious plugin has unrestricted access to database, filesystem, environment variables, network, and all application services.
+**Resolution:** Implemented Phase 1 assembly validation with two checks:
+- **Assembly Name Validation** — After loading a plugin DLL, the assembly's `GetName().Name` is verified against the manifest `Id`. A mismatch causes the plugin to be rejected and unloaded.
+- **SHA-256 Hash Verification** — Before loading a DLL, its SHA-256 hash is computed. On first install, the hash is stored in `plugins/_assembly-hashes.json`. On every subsequent load (including startup), the hash is verified against the stored value. A mismatch indicates tampering.
+**Completion:** 2026-03-31T14:12
+**Verification:** Build clean, 67 tests pass.
+**Blocker Status:** CLEARED — Phase 1 assembly validation production ready. Phase 2 (permission manifests) and Phase 3 (code signing) are future work.
+
+### Build Stabilization: Fix Type Ambiguities in Security.Postgres (2026-03-26)
+**Status:** In Progress
+**Owner:** River
+**Issue:** 60 build errors across `src/SharpSite.Security.Postgres/` due to collision between `SharpSite.Abstractions.Security.*` types and `Microsoft.AspNetCore.Identity.*` types. Secondary: missing type definitions in `src/SharpSite.UI.Security/`.
+**Action:** Resolve ambiguous references via full namespace qualification or selective using imports. Estimated effort: 1-2 days.
+**Dependency:** Must complete before .NET 10 upgrade can be fully validated.
+
+### .NET 10 + Aspire 13.2 Upgrade (2026-03-26)
+**Status:** In Progress
+**Owner:** River
+**Progress:** 39 files upgraded, 51 build errors remain (Blazor BL0008 + C# type errors).
+**Dependency:** Blocked on build stabilization.
+
+### Correctness Bug: Thread-unsafe Static Service Collection in PluginManager (2026-03-31) ✅ COMPLETED
+**Status:** Completed
+**Owner:** River
+**Issue:** `#348` — Static mutable `IServiceCollection` and `IServiceProvider?` fields in `PluginManager`, and plain `Dictionary` in `PluginAssemblyManager`, shared across all instances with no synchronization. Concurrent plugin loads or configuration changes could corrupt collections mid-enumeration.
+**Resolution:** Three-layer fix:
+- **PluginAssemblyManager:** Replaced `Dictionary` with `ConcurrentDictionary`; used atomic `AddOrUpdate` and `TryRemove` operations
+- **PluginManager:** Added static `lock` object guarding all `_ServiceDescriptors` mutations and reads; used `Interlocked.Exchange` for atomic `_ServiceProvider` swaps; restructured async `ConfigurationSectionChanged` handler into snapshot-before-await and mutate-after-await phases
+- **ApplicationState.Plugins:** Changed from `Dictionary` to `ConcurrentDictionary`
+**Completion:** 2026-03-31T14:02
+**Verification:** Build clean (0 errors, 0 warnings), 67 tests pass (including Kaylee's 6 thread-safety tests). E2E 9 failures are pre-existing (require Aspire host).
+**Blocker Status:** CLEARED — Plugin system thread-safety now production-ready.
+
+### Triage Priorities & Routing (2026-03-31)
+**Status:** Completed
+**Owner:** Mal
+**Decision:** 6 squad-labeled GitHub issues routed to team members based on expertise, security criticality, and effort priority.
+
+**Team Routing:**
+- **River (Backend Dev):** #346, #347, #348, #349 — plugin system security + threading (4 issues)
+- **Simon (Frontend Dev):** #350 — forced password reset UX
+- **Wash (E2E Tester):** #351 — .NET 10 + Aspire validation
+
+**Tier 1: Production Blockers (Security P0)**
+- **#346** → River: RCE via `TypeNameHandling.Auto` in `ApplicationState.cs` (Effort: 2-4 hours)
+ - Action: Replace Newtonsoft.Json polymorphic deserialization with System.Text.Json or strict `ISerializationBinder` whitelist
+ - Blocker: Plugin system production readiness
+ - **Status:** ✅ COMPLETED
+
+- **#349** → River: Implement assembly validation & signing for plugin loading (Effort: 4-6 hours Phase 1)
+ - Phase 1: Assembly name validation + SHA-256 hash verification
+ - Phase 2: Permission manifest sandboxing
+ - Phase 3: Code signing certificate chain
+ - Blocker: Plugin system production readiness
+
+**Tier 2: Critical Security Issues**
+- **#347** → River: ZIP bomb vulnerability in plugin extraction (Effort: 2-3 hours)
+ - Action: Add max total size (100MB), per-file cap (50MB), compression ratio check (100:1), path normalization
+ - **Status:** ✅ COMPLETED
+
+**Tier 3: Correctness Bugs**
+- **#348** → River: Thread-unsafe static service collection in `PluginManager` (Effort: 3-4 hours)
+ - Action: Add `lock` or `ReaderWriterLockSlim` around mutations; use `ConcurrentDictionary`
+ - **Status:** ✅ COMPLETED
+
+**Tier 4: Security Hardening**
+- **#350** → Simon: Forced password reset after initial admin seed (Effort: 3-4 hours)
+ - Scope: Add `MustChangePassword` flag, redirect to reset on first login, warn in Production if default creds active
+ - Note: Keep `Admin123!` as dev default; mandatory reset in production
+
+**Tier 5: Validation Work**
+- **#351** → Wash: .NET 10 + Aspire 13.2 upgrade validation (Effort: 2-3 hours)
+ - Status: Build complete (0 errors), unit tests pass (47/47)
+ - Pending: E2E/Playwright, Aspire AppHost, CI SDK updates
+ - Tracking: PR #352
+
+### Security Testing Framework: Anticipatory Tests for #346-#348 (2026-03-31) ✅ COMPLETED
+**Status:** Completed
+**Owner:** Kaylee (Tester)
+**Decision:** Wrote 21 anticipatory unit test cases across 4 test files to accelerate River's security fixes for Issues #346, #347, #348. Tests are written now; some verify the *fixed* behavior and will pass once implementations land.
+
+**Test Structure:**
+- **RCE Serialization (#346):** 7 tests validating System.Text.Json safe deserialization behavior
+ - Location: `tests/SharpSite.Tests.Web/ApplicationState/Security/`
+- **ZIP Bomb (#347):** 8 tests validating compression ratio limits, max sizes, path traversal rejection
+ - Location: `tests/SharpSite.Tests.Web/PluginManager/Security/`
+ - **Status:** Already passing with River's implementation
+- **Thread Safety (#348):** 6 tests verifying concurrent access patterns
+ - Location: Split across `Tests.Plugins` (PluginAssemblyManager) and `Tests.Web` (PluginManager, ApplicationState)
+
+**Completion:** 2026-03-31T13:55
+**Verification:** Build clean, 55 tests pass (ZIP bomb tests already validated)
+**Cross-Agent Impact:** Tests remain pending River's completion of #346 and #348 fixes.
+
+
+
+### User Directive: Admin Seed Credentials (2026-03-26T15:37Z)
+**By:** Jeffrey T. Fritz (via Copilot)
+**Decision:** Keep `Admin123!` as the default developer experience. Add mechanism to force password reset after initial install in production mode. Do NOT remove seed credentials — they are a feature, not a bug.
+
+### .NET 10 + Aspire 13.2 Package Versions (2026-03-26)
+**Status:** Completed
+**Author:** River
+**Context:** Upgraded the entire SharpSite solution from .NET 9 / Aspire 9.1 to .NET 10 / Aspire 13.2.
+**Decisions:**
+| Package Family | Version | Rationale |
+|---|---|---|
+| Aspire.Hosting.*, Aspire.Npgsql.* | 13.2.0 | Target Aspire version |
+| Aspire.AppHost.Sdk | 13.2.0 | Must match Aspire packages |
+| Microsoft.EntityFrameworkCore.* | 10.0.5 | Floor required by Aspire 13.2 transitives |
+| Microsoft.Extensions.Hosting.* | 10.0.5 | Floor required by Aspire 13.2 transitives |
+| Microsoft.Extensions.Caching.Memory | 10.0.5 | Floor required by Aspire 13.2 transitives |
+| Microsoft.Extensions.ServiceDiscovery | 10.4.0 | NOT an Aspire package; 13.2.0 doesn't exist |
+| Microsoft.Extensions.Http.Resilience | 10.4.0 | Latest stable for .NET 10 |
+| OpenTelemetry.* | 1.15.0 | Fixes CVE in 1.11.x; floor required by Aspire 13.2 |
+| Newtonsoft.Json | 13.0.4 | ~~Floor required by Aspire 13.2~~ REMOVED in #346 |
+| System.Text.Json | 10.0.0 | Kept in central management, removed from csproj (pruned) |
+| ASP.NET Core packages | 10.0.0 | Standard .NET 10 RTM |
+
+**TargetFramework:** Moved `net10.0` to `Directory.Build.props`. Future upgrades require one-line change.
+
+**Packages Pruned (removed from csproj):**
+- `Microsoft.Extensions.Localization` — part of .NET 10 shared framework
+- `Microsoft.Extensions.Caching.Memory` — part of .NET 10 shared framework
+- `System.Text.Json` — part of .NET 10 shared framework
+
+### Pin Aspire Port for E2E Test Mode (2026-03-26)
+**Status:** Completed
+**Author:** Zoe (CI/DevOps)
+**PR:** #352
+**Context:** Aspire 13.2 assigns dynamic ports; CI hangs waiting for port 5020.
+**Decision:**
+- Add `WithHttpEndpoint(port: 5020, name: "http")` in AppHost when `testOnly` is true
+- Improve `build-and-test.ps1` with stderr capture, progress logging, diagnostics
+- Reduce HTTP health-check timeout from 5s to 2s
+**Impact:** CI no longer hangs; failures produce diagnostic output; no production impact
+
+### v0.8 Milestone Plan (2026-04-30) 🎯 PROPOSED
diff --git a/.squad/decisions.md b/.squad/decisions.md
index e8bca830..f82b6003 100644
--- a/.squad/decisions.md
+++ b/.squad/decisions.md
@@ -1,170 +1,1116 @@
# Squad Decisions
-
## Active Decisions
+## Governance
+## Merged Decisions (2026-04-30)
+# v0.8.0 Locked Scope & Squad Work Assignment
+## Executive Summary
+## Active v0.8.0 Issue Set (8 Issues)
+## Deferred to v0.8.1+ (Out of Active Scope)
+## Squad Work Assignment
+## Immediate First Work Batch (Launch Now)
+## Risks & Mitigations
+## GitHub Labels Applied
+## Checklist
+## Next Actions
+# v0.8.0 Scope Decision Notes
+## Decision Summary
+## Impact
+## Next Steps
+# Decision: README Badges Milestone Assignment (#273)
+## Decision
+## Impact on Interview Queue
+## Next Steps
+## Reference
+# Decision: #212 Autosave Deferral
+## What Changed
+## Next Clarification Queue
+# Decision: File Browsers Ship in v0.8.0
+## Decision
+## Rationale
+## Changes Made
+## Next Clarifications Needed
+# Decision: Issue #297 — Home Page Post Display Mode Deferred to v0.8.1+
+## Decision
+## User Input
+## Rationale
+## GitHub Actions Executed
+## Cross-Team Impact
+## Next Phase
+## Reference
+# Decision: CSP Header Work Deferred to v0.8.1+
+## Decision
+## Next Steps
+# Decision: Soft Delete via Trash/Recycle Bin for Pages & Posts (#309, #299)
+## Decision
+## Rationale
+## GitHub Updates Applied
+## Cross-Team Impact
+## Next Action
+# Decision: Trash Backend Unit-Test Harness Completed (Kaylee, 2026-05-01)
+## What Completed
+Repository-level unit test coverage for page/post trash flows in `tests/SharpSite.Tests.Web/Data/Postgres/`. Standardized on EF Core InMemory with shared `InMemoryDatabaseRoot` per test service provider. All test projects passing.
-### Security P0: Remove TypeNameHandling.Auto RCE Vector (2026-03-26) ✅ COMPLETED
-**Status:** Completed
-**Owner:** River
-**Issue:** `ApplicationState.cs` (lines 130-134, 212-216) uses `TypeNameHandling.Auto` with Newtonsoft.Json — a well-documented Remote Code Execution deserialization vulnerability. Attacker can modify `plugins/applicationState.json` to inject arbitrary type instantiation payloads.
-**Resolution:** Replaced all Newtonsoft.Json usage with System.Text.Json. Implemented ConfigurationSectionJsonConverter for safe polymorphic deserialization.
-**Completion:** 2026-03-31T13:47
-**Verification:** Build clean, 47 tests pass, Newtonsoft.Json fully removed.
-**Blocker Status:** CLEARED — Plugin system production readiness unblocked.
-
-### Security P0: ZIP Bomb Protection for Plugin Extraction (2026-03-31) ✅ COMPLETED
-**Status:** Completed
-**Owner:** River
-**Issue:** `#347` — Plugin ZIP extraction in `PluginManager.cs` had no safety limits. An attacker could upload a ZIP bomb (42KB compressed → petabytes uncompressed) to exhaust server disk. Path traversal via `../` sequences in entry names was also not explicitly blocked.
-**Resolution:** Added two-layer security validation:
-- **Layer 1 (upload-time):** `ValidateArchiveSecurity` validates max total size (100MB), per-file cap (50MB), compression ratio (100:1), and rejects path traversal via `..`
-- **Layer 2 (extraction-time):** Defense-in-depth validation during file writes using `Path.GetFullPath()` containment checks
-**Completion:** 2026-03-31T13:55
-**Verification:** Build clean, 55 tests pass (8 ZIP security tests included)
-**Blocker Status:** CLEARED — Plugin ZIP extraction production ready.
-
-### Security P0: Implement Assembly Validation for Plugin Loading (2026-03-26) ✅ COMPLETED
-**Status:** Completed (Phase 1)
-**Owner:** River
-**Issue:** Plugin DLLs in the plugins directory are loaded and executed with zero integrity verification, code signing, or assembly name validation. A malicious plugin has unrestricted access to database, filesystem, environment variables, network, and all application services.
-**Resolution:** Implemented Phase 1 assembly validation with two checks:
-- **Assembly Name Validation** — After loading a plugin DLL, the assembly's `GetName().Name` is verified against the manifest `Id`. A mismatch causes the plugin to be rejected and unloaded.
-- **SHA-256 Hash Verification** — Before loading a DLL, its SHA-256 hash is computed. On first install, the hash is stored in `plugins/_assembly-hashes.json`. On every subsequent load (including startup), the hash is verified against the stored value. A mismatch indicates tampering.
-**Completion:** 2026-03-31T14:12
-**Verification:** Build clean, 67 tests pass.
-**Blocker Status:** CLEARED — Phase 1 assembly validation production ready. Phase 2 (permission manifests) and Phase 3 (code signing) are future work.
-
-### Build Stabilization: Fix Type Ambiguities in Security.Postgres (2026-03-26)
-**Status:** In Progress
-**Owner:** River
-**Issue:** 60 build errors across `src/SharpSite.Security.Postgres/` due to collision between `SharpSite.Abstractions.Security.*` types and `Microsoft.AspNetCore.Identity.*` types. Secondary: missing type definitions in `src/SharpSite.UI.Security/`.
-**Action:** Resolve ambiguous references via full namespace qualification or selective using imports. Estimated effort: 1-2 days.
-**Dependency:** Must complete before .NET 10 upgrade can be fully validated.
-
-### .NET 10 + Aspire 13.2 Upgrade (2026-03-26)
-**Status:** In Progress
-**Owner:** River
-**Progress:** 39 files upgraded, 51 build errors remain (Blazor BL0008 + C# type errors).
-**Dependency:** Blocked on build stabilization.
-
-### Correctness Bug: Thread-unsafe Static Service Collection in PluginManager (2026-03-31) ✅ COMPLETED
-**Status:** Completed
-**Owner:** River
-**Issue:** `#348` — Static mutable `IServiceCollection` and `IServiceProvider?` fields in `PluginManager`, and plain `Dictionary` in `PluginAssemblyManager`, shared across all instances with no synchronization. Concurrent plugin loads or configuration changes could corrupt collections mid-enumeration.
-**Resolution:** Three-layer fix:
-- **PluginAssemblyManager:** Replaced `Dictionary` with `ConcurrentDictionary`; used atomic `AddOrUpdate` and `TryRemove` operations
-- **PluginManager:** Added static `lock` object guarding all `_ServiceDescriptors` mutations and reads; used `Interlocked.Exchange` for atomic `_ServiceProvider` swaps; restructured async `ConfigurationSectionChanged` handler into snapshot-before-await and mutate-after-await phases
-- **ApplicationState.Plugins:** Changed from `Dictionary` to `ConcurrentDictionary`
-**Completion:** 2026-03-31T14:02
-**Verification:** Build clean (0 errors, 0 warnings), 67 tests pass (including Kaylee's 6 thread-safety tests). E2E 9 failures are pre-existing (require Aspire host).
-**Blocker Status:** CLEARED — Plugin system thread-safety now production-ready.
-
-### Triage Priorities & Routing (2026-03-31)
-**Status:** Completed
+## Critical Correctness Note
+**AddPage ID Population Issue:**
+- `PgPageRepository.AddPage()` returns the caller's original `Page` instance, not the tracked `PgPage`
+- Generated database `Id` is NOT automatically populated on the returned object
+- Callers must explicitly re-read to use the persisted key
+- **Action:** Simon (API integration) and Wash (E2E tests) must handle ID re-read pattern
+
+## Test Coverage
+- Soft delete, restore, permanent purge scenarios
+- Trashed-content exclusion from normal queries
+- Test status: ✅ SharpSite.Tests.Web.csproj PASSED, ✅ SharpSite.Tests.Plugins.csproj PASSED
+
+## Cross-Team Impact
+- **River:** Unit test harness validates backend contracts
+- **Simon:** Tests provide validation reference for API endpoints
+- **Wash:** Repository tests serve as foundation for E2E workflows (#319)
+
+## Next Action
+Simon: Implement API endpoints; Wash: Build E2E tests on validated repository layer
+
+# Decision Note: Unified Image Management UI (#320, #321)
+## Date
+## Decision
+## Rationale
+## Changes Made
+## Next Steps
+## Related
+# Decision: #323 Favicon Scope Clarified
+## User Input
+## Decision Summary
+## GitHub Update
+## Next Blocker
+# Upstream Issue #272: Playwright Badge Metric Decision
+## User Decision
+## Actions Executed
+## Implementation Context
+## Cross-Team Impact
+## Phase 1 Tier 1 Completion
+# Update #328 Custom CSS Scope & Sanitization (2026-05-02)
+## Decision Recorded
+## User Input
+## Actions Executed
+## Impact
+## Cross-Team Status
+## Decision Reference
+# Mal — Update #322 Upstream (2026-05-02)
+## Decision Captured
+## GitHub Actions Executed
+## Impact
+## Crosslinks
+# Decision: Issue #319 E2E Test Acceptance Criteria — CAPTURED
+## What Changed on #319
+## Next Clarification Question (Priority 1)
+## Cross-Team Status
+## Next Action
+# E2E Test Acceptance Criteria Clarified (2026-05-02)
+## User-Provided E2E Acceptance Criteria
+## Next Clarification Question
+## Cross-Team Unblocking
+## Syncing Note
+
+
+### v0.8 Milestone Plan (2026-04-30) 🎯 PROPOSED
+**Status:** Proposed
**Owner:** Mal
-**Decision:** 6 squad-labeled GitHub issues routed to team members based on expertise, security criticality, and effort priority.
-
-**Team Routing:**
-- **River (Backend Dev):** #346, #347, #348, #349 — plugin system security + threading (4 issues)
-- **Simon (Frontend Dev):** #350 — forced password reset UX
-- **Wash (E2E Tester):** #351 — .NET 10 + Aspire validation
-
-**Tier 1: Production Blockers (Security P0)**
-- **#346** → River: RCE via `TypeNameHandling.Auto` in `ApplicationState.cs` (Effort: 2-4 hours)
- - Action: Replace Newtonsoft.Json polymorphic deserialization with System.Text.Json or strict `ISerializationBinder` whitelist
- - Blocker: Plugin system production readiness
- - **Status:** ✅ COMPLETED
-
-- **#349** → River: Implement assembly validation & signing for plugin loading (Effort: 4-6 hours Phase 1)
- - Phase 1: Assembly name validation + SHA-256 hash verification
- - Phase 2: Permission manifest sandboxing
- - Phase 3: Code signing certificate chain
- - Blocker: Plugin system production readiness
-
-**Tier 2: Critical Security Issues**
-- **#347** → River: ZIP bomb vulnerability in plugin extraction (Effort: 2-3 hours)
- - Action: Add max total size (100MB), per-file cap (50MB), compression ratio check (100:1), path normalization
- - **Status:** ✅ COMPLETED
-
-**Tier 3: Correctness Bugs**
-- **#348** → River: Thread-unsafe static service collection in `PluginManager` (Effort: 3-4 hours)
- - Action: Add `lock` or `ReaderWriterLockSlim` around mutations; use `ConcurrentDictionary`
- - **Status:** ✅ COMPLETED
-
-**Tier 4: Security Hardening**
-- **#350** → Simon: Forced password reset after initial admin seed (Effort: 3-4 hours)
- - Scope: Add `MustChangePassword` flag, redirect to reset on first login, warn in Production if default creds active
- - Note: Keep `Admin123!` as dev default; mandatory reset in production
-
-**Tier 5: Validation Work**
-- **#351** → Wash: .NET 10 + Aspire 13.2 upgrade validation (Effort: 2-3 hours)
- - Status: Build complete (0 errors), unit tests pass (47/47)
- - Pending: E2E/Playwright, Aspire AppHost, CI SDK updates
- - Tracking: PR #352
-
-### Security Testing Framework: Anticipatory Tests for #346-#348 (2026-03-31) ✅ COMPLETED
-**Status:** Completed
-**Owner:** Kaylee (Tester)
-**Decision:** Wrote 21 anticipatory unit test cases across 4 test files to accelerate River's security fixes for Issues #346, #347, #348. Tests are written now; some verify the *fixed* behavior and will pass once implementations land.
+**Decision:** After analyzing 73 open GitHub issues, recommended a 2-phase v0.8 release strategy:
+- **Phase 1 (v0.8.0):** 6 weeks — Tier 1 (ship-blockers: #350, #351, #319, #272) + Tier 2 (core CMS: #309, #299, #321, #323, #328) = ~50–60 hours
+- **Phase 2 (v0.8.1–0.8.3):** 4–6 weeks — Tier 3 (plugin ecosystem: #166, #336, #334, #254, #341, #339) = ~30–40 hours
+- **Defer to v0.9+:** Accessibility (#133, #171, #267), performance (#62, #217, #218), future plugins, storefront
-**Test Structure:**
-- **RCE Serialization (#346):** 7 tests validating System.Text.Json safe deserialization behavior
- - Location: `tests/SharpSite.Tests.Web/ApplicationState/Security/`
-- **ZIP Bomb (#347):** 8 tests validating compression ratio limits, max sizes, path traversal rejection
- - Location: `tests/SharpSite.Tests.Web/PluginManager/Security/`
- - **Status:** Already passing with River's implementation
-- **Thread Safety (#348):** 6 tests verifying concurrent access patterns
- - Location: Split across `Tests.Plugins` (PluginAssemblyManager) and `Tests.Web` (PluginManager, ApplicationState)
+**Squad Assignments:**
+- **River (Backend):** Plugin ecosystem lead (#166, #336, #334, #254) + core deletes (#309, #299) = 35–40h
+- **Simon (Frontend):** Admin UI lead (#321, #323, #328) + installer UX = 15–20h
+- **Wash (E2E):** Validation lead (#319, #351, #272) = 8–10h
+- **Kaylee (Tester):** Plugin test coverage (#341) + test cases = 5–7h
+- **Zoe (CI/DevOps):** Test reporting (#272) = 3–4h
+- **Book (Docs):** Plugin author docs (#339) = 4–6h
-**Completion:** 2026-03-31T13:55
-**Verification:** Build clean, 55 tests pass (ZIP bomb tests already validated)
-**Cross-Agent Impact:** Tests remain pending River's completion of #346 and #348 fixes.
+**Identified Stale Issues (10):** #331, #301, #300, #298, #289, #283, #203, #161, #122, #121 — require triage/clarification before work.
+**Key Risks & Mitigations:**
+- Plugin packager complexity (#166): River to spike week 1; clarify format early
+- DB plugin refactor (#254): Comprehensive migration tests; dry-run first
+- E2E flakiness (#319): 2-week buffer; Wash to add retry logic
+- Scope creep: Close Tier 4 issues; Mal approval required for exceptions
+**Full Plan:** `.squad/decisions/inbox/mal-v08-milestone.md`
-### User Directive: Admin Seed Credentials (2026-03-26T15:37Z)
-**By:** Jeffrey T. Fritz (via Copilot)
-**Decision:** Keep `Admin123!` as the default developer experience. Add mechanism to force password reset after initial install in production mode. Do NOT remove seed credentials — they are a feature, not a bug.
+### User Directive: Central Package Management (2026-04-30)
+**By:** Copilot
+**Decision:** Package version updates should be made in `Directory.Packages.props` (central package management), not in individual project files.
-### .NET 10 + Aspire 13.2 Package Versions (2026-03-26)
-**Status:** Completed
-**Author:** River
-**Context:** Upgraded the entire SharpSite solution from .NET 9 / Aspire 9.1 to .NET 10 / Aspire 13.2.
-**Decisions:**
-| Package Family | Version | Rationale |
-|---|---|---|
-| Aspire.Hosting.*, Aspire.Npgsql.* | 13.2.0 | Target Aspire version |
-| Aspire.AppHost.Sdk | 13.2.0 | Must match Aspire packages |
-| Microsoft.EntityFrameworkCore.* | 10.0.5 | Floor required by Aspire 13.2 transitives |
-| Microsoft.Extensions.Hosting.* | 10.0.5 | Floor required by Aspire 13.2 transitives |
-| Microsoft.Extensions.Caching.Memory | 10.0.5 | Floor required by Aspire 13.2 transitives |
-| Microsoft.Extensions.ServiceDiscovery | 10.4.0 | NOT an Aspire package; 13.2.0 doesn't exist |
-| Microsoft.Extensions.Http.Resilience | 10.4.0 | Latest stable for .NET 10 |
-| OpenTelemetry.* | 1.15.0 | Fixes CVE in 1.11.x; floor required by Aspire 13.2 |
-| Newtonsoft.Json | 13.0.4 | ~~Floor required by Aspire 13.2~~ REMOVED in #346 |
-| System.Text.Json | 10.0.0 | Kept in central management, removed from csproj (pruned) |
-| ASP.NET Core packages | 10.0.0 | Standard .NET 10 RTM |
-
-**TargetFramework:** Moved `net10.0` to `Directory.Build.props`. Future upgrades require one-line change.
-
-**Packages Pruned (removed from csproj):**
-- `Microsoft.Extensions.Localization` — part of .NET 10 shared framework
-- `Microsoft.Extensions.Caching.Memory` — part of .NET 10 shared framework
-- `System.Text.Json` — part of .NET 10 shared framework
-
-### Pin Aspire Port for E2E Test Mode (2026-03-26)
+### User Directive: Use gh CLI for GitHub Operations (2026-04-30)
+**By:** Copilot
+**Decision:** Use the `gh` command line tool for GitHub operations in squad workflows.
+
+### v0.7 Release Completion (2026-04-30) ✅ COMPLETED
+**By:** Jayne (CI/DevOps)
+**Decision:** v0.7 release executed end-to-end:
+- Commit: `c35e5e06c5e17b3e0fbada8fb006d30dadcbf5e6` (PR #304 merge to main)
+- Tag: Annotated `v0.7` created and pushed
+- Release: Public on GitHub Releases with auto-generated changelog (v0.2...v0.7)
+- **Key detail:** Used explicit `git push upstream refs/tags/v0.7` (not `git push upstream v0.7`) to avoid ambiguity with branch names
+
+**Notes:** No production Docker deployment yet (code-only release); tag is public and discoverable.
+
+### Commit Movement: Squad GitHub Actions Workflow Deletion (2026-04-30)
+**By:** Jayne (CI/DevOps)
+**Decision:** Moved commit `683b828` ("Remove Squad GitHub Actions workflows") from `issue-331-content-root-paths` to `fix/issue-331-ootb-startup` via cherry-pick.
+- **Source:** Remains on `issue-331-content-root-paths`
+- **Destination:** New commit `eba624e` on `fix/issue-331-ootb-startup`, pushed to origin
+- **Files deleted:** 4 Squad workflow files (squad-heartbeat.yml, squad-issue-assign.yml, squad-triage.yml, sync-squad-labels.yml)
+- **Method:** Safe cherry-pick preserves remote history
+
+### River — PR #354 Package Upgrades (2026-04-30)
+**By:** River (Backend Dev)
+**Branch:** `issue-331-content-root-paths`
+**Decision:** Upgrade OpenTelemetry packages to resolve NuGet vulnerability findings in PR #354:
+- `OpenTelemetry.Api` → 1.15.3 (explicit transitive pin; fixes GHSA-g94r-2vxg-569j)
+- `OpenTelemetry.Exporter.OpenTelemetryProtocol` → 1.15.3 (fixes GHSA-mr8r-92fq-pj8p, GHSA-q834-8qmm-v933)
+- `OpenTelemetry.Extensions.Hosting` → 1.15.3
+- `OpenTelemetry.Instrumentation.AspNetCore` → 1.15.2 (1.15.3 not available on NuGet)
+- `OpenTelemetry.Instrumentation.Http` → 1.15.1 (1.15.2+ not available)
+- `OpenTelemetry.Instrumentation.Runtime` → 1.15.1 (1.15.2+ not available)
+
+**Rationale:** Satisfies patched vulnerability floors while respecting actual NuGet availability; keeps change surgical.
+
+**Validation:** Solution builds clean; unit tests pass; vulnerability audit passes; respects Aspire 13.2/.NET 10 alignment.
+
+### Zoe — PR #354 CI Findings (2026-04-30)
+**By:** Zoe (CI/DevOps)
+**PR:** #354
+**Decision:** Diagnosed CI failures in `.NET Build` and `Playwright Tests` jobs:
+- **Root cause:** NuGet vulnerability warnings (OpenTelemetry 1.15.0 CVEs) promoted to errors via `TreatWarningsAsErrors=true` in Directory.Packages.props
+- **Failing advisories:**
+ - `GHSA-g94r-2vxg-569j` (OpenTelemetry.Api 1.15.0)
+ - `GHSA-mr8r-92fq-pj8p` (OpenTelemetry.Exporter.OpenTelemetryProtocol 1.15.0)
+ - `GHSA-q834-8qmm-v933` (OpenTelemetry.Exporter.OpenTelemetryProtocol 1.15.0)
+
+**Fix:** Upgrade packages per River's decision (above). No CI workflow changes required.
+
+**Note:** Do **not** set all OpenTelemetry packages to 1.15.3; NuGet lacks 1.15.3 for Instrumentation.* packages. The validated safe set (1.15.3 for Api/Exporter, 1.15.2 for AspNetCore, 1.15.1 for Http/Runtime) clears all vulnerabilities.
+
+### v0.8 GitHub Milestone Assignment (2026-04-30) ✅ COMPLETED
**Status:** Completed
-**Author:** Zoe (CI/DevOps)
-**PR:** #352
-**Context:** Aspire 13.2 assigns dynamic ports; CI hangs waiting for port 5020.
-**Decision:**
-- Add `WithHttpEndpoint(port: 5020, name: "http")` in AppHost when `testOnly` is true
-- Improve `build-and-test.ps1` with stderr capture, progress logging, diagnostics
-- Reduce HTTP health-check timeout from 5s to 2s
-**Impact:** CI no longer hangs; failures produce diagnostic output; no production impact
+**Owner:** Mal
+**Decision:** Triaged v0.7 milestone on GitHub and applied v0.8 scope per strategic roadmap using `gh` CLI.
+
+**Actions:**
+- **Closed:** #331, #348 (marked as completed in v0.7)
+- **Removed from v0.7:** 7 stale issues deferred to backlog (#334, #312, #298, #292, #289, #218, #161)
+- **Assigned to v0.8:**
+ - **Tier 1 (Ship-blockers):** #350, #351, #319, #272
+ - **Tier 2 (Core CMS):** #309, #299, #321, #323, #328
+
+**Final State:**
+- v0.7 Milestone: 2 issues (both closed: #293, #167)
+- v0.8 Milestone: 21 issues (9 Tier 1–2 committed + 12 Tier 3+ ecosystem already present)
+
+**Verification:**
+```
+gh issue list --repo csharpfritz/SharpSite --milestone "0.7 - Installation and Startup" --state all
+→ 2 issues total (both CLOSED)
+
+gh issue list --repo csharpfritz/SharpSite --milestone "0.8 - The Version after this" --state all
+→ 21 issues total
+```
+
+**Reference:** `.squad/decisions/inbox/mal-v08-milestone-github.md` (archived to history)
+
+### v0.8 Milestone Clarification Plan (2026-05-01)
+**Status:** Proposed (awaiting user answers)
+**Owner:** Mal
+**Decision:** Analyzed v0.8 milestone (21 issues, 3 tiers) and identified implementation detail gaps blocking sprint work. Created 7-question clarification framework prioritized by shipping impact.
+
+**Tier 1 Ship-Blockers (4 questions, ~12–16h):**
+1. **#351 E2E Validation** — Which test scenarios = "done"? (AppHost health + page load + workflows?)
+2. **#350 Forced Password Reset** — Trigger on first login or after N warnings?
+3. **#319 Flaky Tests** — Can you name 1–2 consistently failing Playwright tests?
+4. **#272 Badge Metric** — Show test count, pass %, or coverage %?
+
+**Tier 2 Core CMS (3 questions, ~15–19h):**
+5. **#309, #299 Delete Semantics** — Hard delete or soft (archive with recovery)?
+6. **#321, #323 Assets** — Display location, size limits, serving strategy (local vs. CDN)?
+7. **#328 Custom CSS** — Site-wide or page-scoped? Sanitization rules?
+
+**Tier 3+ (12 issues):** Deferred to v0.8.1–0.8.3 patches; spike #166 (plugin packager) first.
+
+**Cross-Agent Blockers:**
+- Wash: Blocked on #351 test checklist (#4 in Tier 1)
+- Simon: Blocked on #350 UX flow (#2 in Tier 1)
+- River: Tier 2 backend (#309, #299) queued after Tier 1 clarity
+- All: v0.8.0 sprint planning ready once Phase 1 (Tier 1) answered
+
+**Deliverable:** `.squad/decisions/inbox/mal-v08-clarification-plan.md`
+**Next Phase:** User provides answers; Mal updates GitHub issues per recommendations table
+
+### User Directive: Upstream Repository as Source of Truth (2026-04-30T15:00:16Z)
+**By:** User (via Copilot)
+**Decision:** Use the upstream repository milestone 0.8 (FritzAndFriends/SharpSite) as the canonical source of truth for v0.8 release planning.
+**Impact:** All clarification questions and scope decisions reference upstream issues, not local planning documents.
+
+### Upstream v0.8 Milestone Review (2026-05-02) ✅ COMPLETED
+**Status:** Completed analysis; clarification queue prioritized
+**Owner:** Mal
+**Directive:** User requested full review of upstream FritzAndFriends/SharpSite v0.8 milestone using upstream as source of truth.
+
+**Upstream v0.8 Milestone State:**
+- **Total open issues:** 18 (vs. 9 anticipated in local docs)
+- **Tier 1 (Ship-blockers):** 5 issues — #319 (E2E OOB testing), #272 (Playwright badge), #322 (first admin user), #323 (favicon), #328 (custom CSS)
+- **Tier 2 (Core CMS):** 5 issues — #309 (delete page), #299 (delete post), #320 (logo upload), #321 (logo display), #297 (post summary on home)
+- **Tier 3+ (Ecosystem):** 8 issues — #313 (CSP header), #208 (admin file browser), #209 (editor file browser), #212 (autosave), #203 (plugin navlinks), #118 (remove plugin), #117 (upgrade plugin), #273 (README badges)
+
+**Scope Mismatch Discovery:**
+- **Local plan:** 9 issues (Tier 1: #350, #351 CLOSED upstream; Tier 2: 5 issues)
+- **Upstream v0.8:** 18 open issues (all Tier 1–2 present + 11 additional ecosystem items)
+- **Impact:** Scope discussion needed to confirm which of 11 new items belong in v0.8.0 (ship) vs v0.8.1+ (patch)
+
+**Critical Gaps Identified (by Tier):**
+| Tier | Issue | Gap | Impact |
+|------|-------|-----|--------|
+| 1 | #319 | No acceptance criteria defined | Wash blocked; unclear pass criteria |
+| 1 | #272 | Metric type undefined (count, %, coverage?) | Zoe blocked; CI reporting depends on choice |
+| 1 | #322 | Scope unclear (installer vs seed logic?) | Simon blocked; password reset UX depends on answer |
+| 1 | #328 | CSS scope & sanitization rules undefined | Simon blocked; security review depends on scope |
+| 2 | #309, #299 | Delete semantics undefined (hard vs soft delete?) | River/Simon blocked; schema complexity 2–3x difference |
+| 2 | #320, #321 | Logo feature scope unclear (image validation, formats, serving?) | Simon blocked; asset handling complexity |
+
+**Clarification Queue (7 Questions by Priority):**
+
+**PHASE 1 — Tier 1 Blockers (4 questions, unblocks E2E + UI sprint):**
+
+**Q1: E2E Testing Acceptance Criteria (#319)**
+- What must E2E validation achieve to be "done"?
+ - AppHost health only?
+ - AppHost + page load + key workflows (post create, save, publish)?
+ - Login + admin UI + content workflows?
+- **Impact:** Blocks Wash's sprint; unclear pass criteria
+- **GitHub Action:** Update #319 body with acceptance criteria; assign to Wash
+
+**Q2: Playwright Badge Metric (#272)**
+- Which metric should the badge display?
+ - Test count (e.g., "247 tests")?
+ - Pass percentage (e.g., "94% passing")?
+ - Coverage percentage?
+ - All three?
+- **Impact:** Blocks Zoe's CI work; changes reporting logic
+- **GitHub Action:** Update #272 with metric choice; assign to Zoe
+
+**Q3: First Admin User Creation (#322)**
+- How should the first admin user be created?
+ - Installer wizard step?
+ - Seed logic with forced password reset on first login?
+ - Both?
+- **Impact:** Blocks Simon's UI work; affects installer flow
+- **GitHub Action:** Clarify #322 scope; link to #350 if dependency
+
+**Q4: Custom CSS Scope & Sanitization (#328)**
+- Which CSS scope is needed for v0.8.0?
+ - Site-wide only (global stylesheet)?
+ - Per-page customization?
+ - Both?
+ - Sanitization: Allow all CSS or restrict XSS-prone properties?
+- **Impact:** Blocks Simon's UI work; affects security review
+- **GitHub Action:** Update #328 with scope; assign to Simon
+
+**PHASE 2 — Tier 2 Core CMS (3 questions, depends on Phase 1):**
+
+**Q5: Delete Semantics (#309, #299)**
+- For delete page/post, which strategy?
+ - Hard delete (purge immediately)?
+ - Soft delete (archive, with recovery)?
+ - Both (admin can choose)?
+- **Impact:** Schema complexity; River backend design depends on answer
+- **GitHub Action:** Update #309 + #299 bodies with semantics choice
+
+**Q6: Logo Upload & Display (#320, #321)**
+- Logo feature scope for v0.8.0?
+ - Upload existing images only?
+ - Or include image validation + serving strategy?
+ - Supported formats (JPG, PNG, SVG)?
+- **Impact:** Image handling complexity; affects #320 + #321 effort
+- **GitHub Action:** Update #320 + #321 with feature boundary
+
+**Q7: Tier 3 Deferral Decision**
+- For v0.8.0, which belong in scope?
+ - #313 (CSP): Ship-blocker or v0.8.1?
+ - #208, #209 (File browser): v0.8.0 or v0.8.1?
+ - #212 (Autosave): v0.8.0 or v0.8.1?
+ - #203, #117, #118 (Plugin lifecycle): v0.8.0 or post-v0.8.0?
+ - #273 (README badges): v0.8.0 or separate release?
+- **Impact:** Determines v0.8.0 vs v0.8.1+ split; resourcing
+- **GitHub Action:** Update each issue with milestone assignment (v0.8.0 vs v0.8.1)
+
+**Cross-Team Blockers:**
+| Role | Issue | Blocked Until | Impact |
+|------|-------|---------------|--------|
+| Wash | #319 | Q1 answer | Cannot write acceptance criteria or test plan |
+| Simon | #322, #328 | Q2, Q4 answers | Cannot scope admin UI sprint (est. 3–4 issues) |
+| River | #309, #299 | Q5 answer | Cannot design delete schema; mid-tier effort shift |
+| Zoe | #272 | Q3 answer | Cannot implement badge + reporting |
+| All | Tier 2–3 | Phase 1 complete | Cannot plan v0.8.0 → v0.8.1 split |
+
+**Deliverables:**
+- `.squad/decisions/inbox/mal-upstream-v08-question-queue.md` (clarification queue, 7 questions)
+- `.squad/decisions/inbox/mal-upstream-v08-question.md` (scope mismatch analysis)
+
+**Next Actions:**
+1. Present Q1 (E2E acceptance criteria) to user now
+2. Upon user answer, update #319 on GitHub + unblock Wash
+3. Move through Phase 1 sequentially (Q2, Q3, Q4)
+4. After Phase 1 complete, ask Phase 2 questions (#5–#7)
+5. After all 7 answered, Mal applies milestone tags and team begins sprint planning
-## Governance
- All meaningful changes require team consensus
- Document architectural decisions here
- Keep history focused on work, decisions focused on direction
+
+
+
+
+**Date:** 2026-05-03
+**Owner:** Mal (Lead)
+**Status:** ✅ READY FOR EXECUTION
+
+
+v0.8.0 scope is **fully locked** with all clarifications answered. 8 active issues routed to squad members with clear dependencies and immediate first batch identified. No blockers remain; sprint can launch now.
+
+---
+
+
+| # | Title | Tier | Squad | Effort | Status |
+|---|-------|------|-------|--------|--------|
+| #319 | E2E test suite (login, dashboard, post workflows) | 1 | Wash | 8–10h | Ready |
+| #322 | First admin user setup wizard | 1 | Simon | 4–6h | Ready |
+| #323 | Favicon upload to Admin UI | 1 | Simon | 2–3h | Ready |
+| #328 | Site-wide + per-page custom CSS editor (sanitized) | 1 | Simon | 5–7h | Ready |
+| #309 | Delete page (soft delete → trash/recycle bin) | 2 | River | 6–8h | Ready |
+| #299 | Delete post (soft delete → trash/recycle bin) | 2 | River | 6–8h | Ready |
+| #320 | Logo upload (image formats, validation) | 2 | Simon | 3–4h | Ready |
+| #321 | Logo display/maintenance in Admin UI | 2 | Simon | 2–3h | Ready |
+
+**Total Effort:** ~36–47 hours
+**Critical Path:** Tier 1 → Tier 2 (Tier 1 blocks no other work; both can proceed in parallel after clarifications)
+
+---
+
+
+| # | Title | Rationale |
+|---|-------|-----------|
+| #313 | CSP header (security enhancement) | Not a ship-blocker; deferred for patch cycle |
+| #297 | Home page post display toggle | v0.8.1+ feature; lower priority than core content editing |
+
+---
+
+
+### Simon (Frontend/Admin UI Lead) — 5 Active Issues
+
+**Issues:** #322, #323, #320, #321, #328
+
+**Delivery:** Unified image-management UI component + setup wizard + CSS editor
+**Effort:** ~16–23h
+**Dependencies:** None (all clarifications locked)
+
+**Sequence (recommended):**
+1. **#322** (First admin user setup wizard) — foundational, other features depend on first-user context
+2. **#323** (Favicon upload) — leverage unified image UI pattern
+3. **#320** (Logo upload) — part of same unified image UI
+4. **#321** (Logo display) — consumes uploaded logo; depends on #320
+5. **#328** (Custom CSS editor) — distinct component; can parallel with images if bandwidth available
+
+**Batch 1 (Start now):**
+- #322, #323 — Setup wizard + favicon upload (ship-blockers)
+
+**Batch 2 (After Batch 1 or parallel):**
+- #320, #321 — Unified image UI for logo
+- #328 — CSS editor (can parallel)
+
+---
+
+### River (Backend Lead) — 2 Active Issues
+
+**Issues:** #309, #299
+
+**Delivery:** Soft-delete schema + trash/recovery queries + cleanup policies
+**Effort:** ~12–16h
+**Dependencies:** None (delete semantics clarified: soft delete → trash/recycle)
+
+**Sequence:**
+1. Extend Post/Page models with `IsDeleted` flag + `DeletedAt` timestamp
+2. Add trash view queries (retrieves deleted items within retention period)
+3. Implement recovery endpoint (un-delete with restore)
+4. Implement permanent cleanup (admin can purge old trash)
+
+**Batch 1 (Start now):**
+- **#309 + #299** — Both can start immediately; implement together as paired schema work
+
+---
+
+### Wash (E2E Tester) — 1 Active Issue
+
+**Issue:** #319
+
+**Delivery:** E2E test suite covering full workflows
+**Effort:** ~8–10h
+**Dependencies:** None (acceptance criteria locked)
+
+**Test Scenarios (per clarification):**
+1. Login authentication to admin area
+2. Admin dashboard navigation and load
+3. Post creation (new)
+4. Post save as draft
+5. Post publish to live site
+
+**Batch 1 (Start now):**
+- **#319** — Playwright test suite; CI integration for badge metric
+
+---
+
+### Zoe (CI/DevOps) — Supporting Role
+
+**Dependency:** Wash completes #319 E2E tests
+
+**Delivery:** Badge metric aggregation + GitHub Actions workflow
+**Effort:** ~3–4h
+
+**Blocked until:** Wash provides test count data from #319
+**Note:** #272 metric already clarified (total test count, not %)
+
+---
+
+
+**Parallel Tier 1 (Ship-Blockers):**
+
+1. **Simon:** #322 (First admin user setup) + #323 (Favicon)
+ - ~6–9h combined
+ - Unblocked; can start immediately
+ - Creates foundation for site identity (logo, CSS, initial auth)
+
+2. **Wash:** #319 (E2E test suite)
+ - ~8–10h
+ - Unblocked; can start immediately
+ - Test scenarios fully defined
+ - Provides data to Zoe for badge metric
+
+**Parallel Tier 2 (Can start after OR immediately):**
+
+3. **River:** #309 + #299 (Delete operations)
+ - ~12–16h combined
+ - No blockers; proceed in parallel with Tier 1
+ - Design soft-delete schema while Simon/Wash execute Tier 1
+
+**Critical Path Optimization:**
+- Tier 1 + Tier 2 can overlap; no blocking dependencies between teams
+- Simon finishes Tier 1 → can start #320, #321 (logo) + #328 (CSS)
+- All issues can progress without gating on each other
+
+**Recommended Sprint Cadence:**
+- **Week 1:** Tier 1 foundation (#322, #323, #319)
+- **Week 1–2:** Tier 2 design + initial implementation (River)
+- **Week 2–3:** Tier 2 completion + Tier 1 polish + integration testing
+
+---
+
+
+| Risk | Likelihood | Mitigation |
+|------|------------|-----------|
+| Unified image UI complexity (Simon) | Low | Start with MVP (single input), extend if time permits |
+| E2E test environment setup (Wash) | Medium | Coordinate with River on AppHost health checks |
+| Trash cleanup retention policy (River) | Low | Default 30-day retention; make configurable later |
+| CSS sanitizer XSS vectors (Simon) | Medium | Use well-vetted library (e.g., HtmlSanitizer); avoid regex parsing |
+
+---
+
+
+✅ All squad labels already correct as of this review:
+- `squad:simon` → #322, #323, #320, #321, #328
+- `squad:river` → #309, #299
+- `squad:wash` → #319
+- `squad:mal` → #313 (deferred, monitoring)
+
+---
+
+
+- [x] v0.8.0 scope locked (no outstanding clarifications)
+- [x] All 8 active issues routed to squad members
+- [x] Dependencies identified and sequenced
+- [x] First batch identified and ready
+- [x] Effort estimates aligned with team expertise
+- [x] Deferred items marked and rationale documented
+- [x] GitHub labels verified
+- [x] No blockers remain
+
+---
+
+
+1. **Mal:** Present this assignment to squad + obtain team thumbs-up
+2. **Simon, River, Wash:** Begin assigned work per batch recommendations
+3. **All:** Daily standups on progress; escalate blockers immediately
+4. **Scribe:** Merge inbox decision to squad decisions.md
+5. **Ralph (Monitor):** Track v0.8.0 milestone closure on GitHub issues board
+
+---
+
+**Decision Delivered:** `.squad/decisions/inbox/mal-assign-v080-work.md`
+**Scope:** FritzAndFriends/SharpSite v0.8.0 milestone (GitHub source of truth)
+**Team Ready:** ✅ Yes — all clarifications answered, no outstanding questions
+
+
+
+**Date:** 2026-05-01
+**Owner:** Mal (Lead)
+**Status:** Committed to upstream GitHub issues
+
+
+Two issue groups triaged and decisions recorded on GitHub:
+
+### 1. Plugin Lifecycle Cluster — DEFERRED to v0.8.1+
+
+**Issues:** #203, #117, #118
+**Decision:** Defer plugin ecosystem enhancements (navigation links, upgrade, remove) to v0.8.1+
+
+**Rationale:**
+- v0.8.0 scope is core CMS delivery: file browser, content management, autosave
+- Plugin lifecycle features are post-v0.8.0 enhancements; no user stories block v0.8.0 ship
+- Ecosystem work planned for patch releases after v0.8.0 stabilizes
+- Clears capacity for Tier 1 & 2 blockers
+
+**GitHub Actions:**
+- #203 comment posted: Navigation links deferred
+- #117 comment posted: Upgrade functionality deferred
+- #118 comment posted: Remove functionality deferred
+- No label changes (ecosystem already identified in backlog)
+
+### 2. README Badges — SHIP in v0.8.0
+
+**Issue:** #273
+**Decision:** Include README status badges in v0.8.0 release
+
+**Rationale:**
+- Quick-win polish item; ~2-3 hours effort max
+- No dependencies on other features
+- Improves project visibility + CI status transparency at launch
+- Low risk of blockers
+
+**GitHub Actions:**
+- #273 comment posted: Badges ship in v0.8.0
+- Label routing: None applied (already identified in Tier 3 ecosystem)
+
+
+**v0.8.0 Scope Unchanged:**
+- Tier 1 (4 ship-blockers): #350, #351, #319, #272
+- Tier 2 (5 core CMS): #309, #299, #321, #323, #328
+- Tier 3 quick-wins: #273 (badges) + others
+- Deferred: #203, #117, #118 → v0.8.1+ backlog
+
+**Team Bandwidth:** Reinforces v0.8.0 focus on core CMS, clears plugin ecosystem work for patches.
+
+
+1. GitHub decisions synced upstream ✅
+2. Await squad confirmation on Tier 1 & 2 progress
+3. Track v0.8.1+ roadmap (plugin lifecycle, CSP headers, editor enhancements)
+
+
+
+**Date:** 2025 (current session)
+**Clarification Question:** Q7 — Which Tier 3+ issues belong in v0.8.0 vs later releases?
+**User Answer:** README badges ship in **v0.8.0**
+
+- README badges (#273) are a **v0.8.0 deliverable** alongside core functionality
+- Rationale: Customer-facing documentation, ships with initial release
+- Label routing: Not required (already scoped as documentation, up-for-grabs)
+
+- Q7 (Tier 3 deferral) now has its first binding decision
+- Remaining Tier 3+ issues (#313, #208, #209, #212, #203, #118, #117) still pending deferral decisions
+- Interview queue **NOT complete** — 6 more Tier 3 items need clarification
+
+1. Review remaining Tier 3+ deferral decisions (CSP, file browsers, autosave, plugin lifecycle)
+2. Determine if all 8 should ship in v0.8.0 or some defer to v0.8.1+
+
+- Upstream: FritzAndFriends/SharpSite#273
+- Clarification plan: `.squad/decisions/inbox/mal-v08-clarification-plan.md`
+
+
+
+**Date:** 2025-01-12
+**Issue:** #212 (Enable autosaving of content when editing in TextEditor)
+**Decision:** Defer to v0.8.1+
+**Rationale:** Implementation scope requires unique ID binding + form submission integration. Not a blocking requirement for v0.8.0 MVP.
+
+- Added upstream comment explaining deferral rationale
+- No label changes (already marked `enhancement`, `up-for-grabs`)
+- Milestone remains `0.8 - The Version after this` (no milestone change)
+
+Remaining unresolved items after #212:
+1. **Plugin lifecycle cluster** (#203, #117, #118): v0.8.0 or post-v0.8.0?
+2. **README badges** (#273): v0.8.0 or separate release?
+
+**Next Question:** Should plugin navlinks (#203), plugin removal (#118), and plugin upgrade (#117) ship in v0.8.0, or defer to v0.8.1+?
+
+
+
+**Date:** 2026-03-31
+**Owner:** Mal
+**Issues:** #208, #209
+
+Both the admin file browser (#208) and editor file browser (#209) will ship in **v0.8.0**.
+
+- These are foundational for the content management workflow (uploading and selecting files for posts/pages)
+- Admin browser provides site-wide file management
+- Editor browser enables inline file selection during content editing
+- Both features are closely coupled in user intent
+
+- Added decision comment to #208 and #209
+- Both issues remain in v0.8.0 milestone
+
+- #212 (Autosave): v0.8.0 or v0.8.1?
+- #203, #117, #118 (Plugin ecosystem): scope for v0.8.0?
+- #273 (README badges): v0.8.0 or separate?
+
+
+
+**Date:** 2026-05-02
+**Owner:** Mal (Lead)
+**Status:** ✅ Recorded
+**Issue:** #297 "Add an option to display summary or full post on home page"
+
+
+Home page post display mode toggle is **NOT a v0.8.0 ship-blocker** and can defer to v0.8.1+ patch cycle.
+
+
+> "home page post display mode toggle can DEFER to v0.8.1+"
+
+
+- Feature is aesthetic/UX enhancement, not core CMS functionality required for v0.8.0 release
+- Deferral unblocks v0.8.0 scope to focus on critical ship-blockers (Tier 1) and core CMS features (Tier 2)
+- Can be implemented as a straightforward Site Appearance checkbox in follow-up patch
+
+
+1. **Comment added to #297** — Decision recorded upstream
+ - URL: https://github.com/FritzAndFriends/SharpSite/issues/297#issuecomment-4355729722
+ - Routing: v0.8.1+ patch cycle (no squad label needed at this stage)
+
+
+- **Simon:** Unlocked; no admin UI work required for v0.8.0 on this feature
+- **River:** Unlocked; no backend API required for v0.8.0 on this feature
+- **All:** Tier 2 scope now finalized; all 5 core CMS issues (#309, #299, #320, #321, #297) are routed or deferred
+
+
+All Tier 1 (ship-blocker) and most Tier 2 clarifications now complete. Remaining unresolved:
+- #309, #299: Delete semantics (hard vs soft delete)
+- #208, #209: File browser features (admin + editor)
+
+**Next target:** Clarify v0.8.0 vs v0.8.1+ split for remaining Tier 3+ ecosystem issues.
+
+
+- Issue: FritzAndFriends/SharpSite#297
+- Milestone: 0.8 - The Version after this
+- Related: .squad/decisions/inbox/mal-update-313-upstream.md (CSP deferral pattern)
+
+
+
+**Issue:** #313 — Add CSP to base/default theme to enhance security on site
+**Decision Date:** 2026-05-01
+**Owner:** Mal (Lead)
+**Status:** RECORDED on upstream issue
+
+
+CSP (Content Security Policy) header implementation is **NOT** a v0.8.0 ship blocker. Work deferred to v0.8.1+ patch release.
+
+### Rationale
+- Security enhancement with no impact on core CMS functionality
+- Adds defense depth but does not unblock other v0.8.0 deliverables
+- Can be implemented post-release without breaking changes
+- Aligns with v0.8.0 scope: admin UI, page/post management, plugin ecosystem
+
+### Action Taken
+- Commented on #313 with decision rationale
+- Applied `squad:mal` routing label for tracking
+- No GitHub milestone change needed (remains in 0.8 for future decision point)
+
+- Monitor for any security escalations that would promote this to ship-blocker status
+- Schedule CSP design/implementation for v0.8.1 planning cycle
+
+
+
+**Date:** 2026-05-02
+**Owner:** Mal
+**Status:** Applied
+**Issues:** #309 (delete page), #299 (delete post)
+
+
+When a user deletes a page or post, the item should be **moved to a trash/recycle bin** for deferred deletion, not immediately purged. Permanent deletion occurs only when the user explicitly empties the trash.
+
+**Design Implication:** Soft delete with recovery path.
+
+
+- **User experience:** Prevents accidental permanent data loss
+- **Admin capability:** Recoverable deletion provides safety net
+- **Schema impact:** Moderate (add `IsDeleted` flag + trash view; River backend effort: 4–6 hours)
+
+
+- **#309 comment:** Decision recorded with relation to #299
+- **#299 comment:** Decision recorded with relation to #309
+
+
+- **River (Backend):** Unblocked; can design soft-delete schema & queries
+- **Simon (Admin UI):** Delete buttons + trash UI component added to scope
+- **Effort shift:** Schema complexity moderate; recoverable within Tier 2 CMS estimate
+
+
+Q6 (Logo feature scope) or Q7 (Tier 3 deferral) to be clarified next per queue priority.
+
+
+
+2025-01-XX
+
+Logo and favicon upload will share **ONE UNIFIED image-management UI** rather than separate upload flows.
+
+- **Consistency:** Single image handling pattern across site configuration
+- **Scope Reduction:** Reduces frontend complexity vs. separate flows (Simon estimate: 2–3h unified vs 3–5h separate)
+- **UX:** Admin users get consistent experience for all image uploads
+- **Maintainability:** Fewer edge cases; simpler validation/storage logic
+
+# Decision: Public Post Route Publishing (#319 — 2026-05-01)
+## Date
+2026-05-01
+
+## Decision
+Implement published post public URL access using a dedicated `/post/{slug}` route component that delegates to a shared post page rendering component, with an explicit load-complete gate to prevent premature HTTP 404 responses.
+
+## Why
+The published-post lookup path reached the repository successfully, but the shared page component rendered before the async repository fetch completed. The initial render with `Post == null` was immediately treated as "not found", causing Blazor SSR to set HTTP 404 status before the real content loaded.
+
+## Technical Solution
+- **Route component:** Thin `/post/{slug}` page that accepts slug parameter and manages load lifecycle
+- **Shared rendering component:** Delegated post display logic reusable across public and admin contexts
+- **Load-complete gate:** Explicit flag prevents `PageNotFound` rendering until async fetch finishes
+- **Rendering stages:** Show loading indicator or content; never render 404 before async work completes
+
+## Impact
+- ✅ Published posts are now accessible at `/post/{slug}` on live site
+- ✅ E2E test `CreatePostTests.CanCreateSaveAndPublishPost` passes end-to-end
+- ✅ HTTP 200 returned for published posts; 404 only when content truly not found
+- ✅ Future SSR content pages should adopt the same "distinguish loading from not found" pattern
+
+## Cross-Team Impact
+- **Wash:** E2E suite unblocked; full v0.8.0 Tier 1 test pipeline can now validate
+- **Kaylee:** Test harness foundation validated against working public route
+- **River:** Backend contracts confirmed working with public layer
+- **v0.8.0 Tier 1:** Critical path blocker resolved
+
+## Validation
+- Live URL test: `/post/{published-slug}` returns 200 + renders post ✅
+- E2E test: Full post creation/save/publish/view workflow passes ✅
+- Build: No regressions; clean build verified ✅
+
+
+- ✅ Comment added to #320 with decision recorded
+- ✅ Comment added to #321 with decision recorded
+- ✅ Label `squad:simon` added to both issues
+- ✅ Both issues now scoped as Simon frontend work
+
+- Simon can now estimate and scope admin UI sprint for #320, #321, #323, #328
+- Unified image-upload component serves both logo and favicon (#323) requirements
+- Awaiting clarification on **delete semantics** (#309, #299) for Tier 2 scope finalization
+
+- Tier 2 Clarifications: #319 ✅, #272 ✅, #322 ✅, #323 ✅, #328 ✅
+- Next Clarification: Delete behavior (#309, #299) — soft vs hard delete semantics
+
+
+
+**Status:** CAPTURED + ROUTED
+**Issue:** #323 (Set a favicon for the site)
+**Owner:** Mal
+**Date:** 2026-05-02
+
+
+> "the site favicon should be a USER-EDITABLE UPLOAD via Admin UI"
+
+
+**Scope:** Favicon is user-editable via Admin UI file upload, not theme-configured or static.
+
+**Implementation Details:**
+- Admin UI provides file upload component for favicon (.ico, .png, .svg, etc.)
+- Favicon stored in site assets/media (not committed to code)
+- Served from Admin UI asset management or dedicated favicon endpoint
+- Replaces default favicon on all pages
+
+**Impact:**
+- **Simon:** Design favicon upload UI component; integrate with asset management
+- **Effort:** 2-3 hours (depends on asset management refactoring)
+- **Blocker:** None; can proceed in parallel with #320, #321 (logo/image management)
+
+
+**Issue:** #323
+**Actions:**
+1. Add comment with decision and routing
+2. Apply `squad:simon` label (UI component dev)
+3. Reference related issues: #320 (logo image types), #321 (logo admin UI)
+
+
+**Issue:** #320, #321 (Logo Management Scope)
+
+**Question:** Should logo and favicon upload use the **same unified image management UI**, or separate upload flows?
+- **Impact:** Simon scope estimation (2-3h single UI vs 3-5h dual UIs)
+- **Cross-team:** Affects asset management architecture decision
+
+---
+
+**Tier 1 Progress:** 4/5 issues clarified (#319 ✅, #272 ✅, #322 ✅, #328 ✅)
+**Next blocker:** #320, #321 (logo management) OR #309, #299 (delete semantics)
+
+
+
+**Status:** Captured and routed
+**Owner:** Mal
+**Issue:** #272 — Playwright badge and reports not updating properly
+**Blocker:** Zoe (CI/Badge implementation)
+
+
+> "The Playwright badge should display the TOTAL TEST COUNT when E2E tests complete."
+
+
+1. **Added comment to #272** documenting decision:
+ - Link: https://github.com/FritzAndFriends/SharpSite/issues/272#issuecomment-4355597657
+ - Metric: Total test count (not percentage or coverage)
+ - Impact: GitHub Actions workflow aggregates all E2E test runs into single count
+
+2. **Applied `squad:zoe` label** — unblocks Zoe to implement badge update logic
+
+
+- **Metric:** Display absolute count (e.g., "272 tests passed")
+- **Trigger:** On E2E test completion in GitHub Actions
+- **Aggregation:** Merge multiple test runs (if parallel jobs) into single count via publish-unit-test-result-action
+- **Badge source:** Shields.io or similar dynamic badge service
+
+
+- **Wash:** E2E test suite provides input data (test count)
+- **Zoe:** Can now implement badge update workflow
+- **CI/Badge:** Unblocked for v0.8.0 sprint
+
+
+All 4 original Tier 1 blockers now answered:
+- Q1: #319 E2E acceptance criteria ✅
+- Q2: #272 Badge metric ✅
+- Q3: #322 First admin user ✅
+- Q4: #328 Custom CSS ✅
+- **Remaining Tier 1:** #323 (favicon) — needs scope clarification
+
+
+
+**Status:** Scope clarified; upstream #328 updated
+**Owner:** Mal
+**Issue:** #328 (Allow administrators to submit some custom CSS)
+
+> "v0.8.0 should support BOTH site-wide and per-page custom CSS, and sanitization should restrict unsafe/XSS-prone rules"
+
+1. **Added comprehensive comment to #328** documenting:
+ - **Scope:** Site-wide CSS + per-page CSS overrides/extensions
+ - **Sanitization:** Blocks script injection, XSS-prone pseudo-selectors, @-rules; allows basic selectors, colors, spacing, typography
+ - **Routing:** `squad:simon` label applied
+
+2. **Applied `squad:simon` label** — unblocks Simon (UI dev) to design CSS editor form + sanitizer integration
+
+- **Simon Unblocked:** Can now scope CSS editor component, input validation, and sanitizer library integration
+- **Tier 1 Progress:** 3 of 4 Tier 1 blockers now answered (#319 ✅, #322 ✅, #328 ✅)
+- **Next Blocker:** #272 (Playwright badge metric) — Q2 in phase 1 clarification queue
+
+- **Wash:** Awaiting no further Tier 1 input (E2E acceptance criteria ✅)
+- **Simon:** Unblocked on CSS; still needs clarification on logo display scope (#320, #321) and favicon (#323)
+- **River:** Awaiting Q5 on delete semantics (#309, #299)
+- **Zoe:** Blocked on Q2 (badge metric #272)
+- **All:** Phase 1 Tier 1 (60–70% complete); Phase 2 Tier 2 can begin once Q1–Q4 answered
+
+**Link:** https://github.com/FritzAndFriends/SharpSite/issues/328#issuecomment-4355512012
+
+
+
+**Status:** Completed
+**Owner:** Mal
+**Issue:** #322 (First Admin User Creation)
+
+
+**User Input:**
+> First admin user should be created via an interactive setup wizard.
+
+
+1. **Comment added to #322**
+ - Link: https://github.com/FritzAndFriends/SharpSite/issues/322#issuecomment-4355501208
+ - Content: Decision summary + squad routing
+
+2. **Label applied: `squad:simon`**
+ - Unblocks Simon (UI dev) to scope installer wizard
+
+
+- **Simon:** Can now design setup wizard flow (password entry, confirmation, validation)
+- **Tier 1 Clarity:** 3 of 4 Tier 1 blockers answered (#319 E2E ✅, #322 First User ✅, #272 Badge pending, #328 CSS pending)
+- **Next Blocker:** #328 (Custom CSS scope & sanitization) — Q4 in clarification queue
+
+
+- Relates to: #350 (forced password reset)
+- Tier 1 sprint: E2E (#319) + Installer (#322) + Badges (#272) + CSS (#328)
+
+
+
+**Date:** 2026-05-02
+**Owner:** Mal
+**Status:** Acceptance criteria documented; next clarification queued
+
+
+**Comment Added:** E2E test acceptance criteria captured with 5 required test scenarios:
+1. Login authentication to admin area
+2. Admin dashboard navigation and load
+3. Post creation (new post)
+4. Post save as draft
+5. Post publish to live site
+
+**Label Added:** `squad:wash` — unblocks Wash (E2E tester) to scope test buildout
+
+**Impact:**
+- Wash: Can now define Playwright test cases and CI integration
+- v0.8 Tier 1: E2E test scope is now actionable
+
+
+**Issue:** #322 (Create first user with Admin access)
+**Gap:** Scope unclear — is first user created during installation/setup (interactive UI), or via database seeding (code-based)?
+
+**Question to User:**
+> "When SharpSite is deployed for the first time, how should the first admin user be created? Should the system provide an interactive setup wizard (e.g., form-based admin credentials entry), or is it code-based database seeding with hardcoded defaults?"
+
+**Blockers if unclear:**
+- Simon (UI) cannot design admin setup flow without knowing the pattern
+- Installation documentation depends on user creation method
+- Tier 1 timeline at risk if scope expands
+
+**Expected Answers:**
+- **(A) Interactive Setup:** SharpSite shows first-run form → user creates admin via UI → redirects to dashboard
+- **(B) Code Seeding:** Default admin user (e.g., `admin@localhost` / `Admin123!`) created at deploy-time; user must change on first login
+- **(C) Hybrid:** Setup wizard available; pre-seeded default also available as fallback
+
+**Impact if A:** +4–6h UI work (form, validation, auth integration)
+**Impact if B:** +1h seeding logic; no UI needed
+**Impact if C:** +5–7h (both paths required)
+
+
+- **Wash:** ✅ Unblocked on #319 (E2E acceptance criteria)
+- **Simon:** ⏸️ Awaiting Q1 answer (#322 first user creation)
+- **River:** ⏸️ Awaiting Q2 answer (#309/#299 delete semantics — hard/soft/archive)
+- **All:** v0.8 Tier 1 sprint planning continues after Q1 answered
+
+
+Ask user: Q1 on #322 (first user creation: setup wizard vs code seeding vs hybrid?)
+
+Once answered → update #322 + unblock Simon → decide Tier 1 timeline feasibility
+
+
+
+**Status:** Accepted — User-provided criteria captured
+**Issue Reference:** #319 (planned in local decisions.md as Tier 1 E2E work)
+**Upstream Status:** Issue #319 does not exist in upstream GitHub; v0.8 milestone absent
+
+
+> "E2E testing should validate full workflows: login, admin UI, and post create/save/publish."
+
+### Acceptance Criteria Breakdown
+1. **Login flow:** Successful authentication to admin area
+2. **Admin UI navigation:** Verify admin dashboard loads and is accessible
+3. **Post creation:** Create a new post (step 1 of workflow)
+4. **Post save:** Save post as draft (step 2 of workflow)
+5. **Post publish:** Publish post to live site (step 3 of workflow)
+
+### Test Venues
+- **Location:** `tests/SharpSite.Tests.E2E/` (Playwright + .NET Aspire host)
+- **Coverage:** Full end-to-end workflow in single session
+- **Flakiness flag:** #319 item noted as having flaky tests; investigate common timeouts/race conditions
+
+
+After E2E acceptance criteria, the single **best next question** for upstream open items is:
+
+**Issue #4 (Forced Password Reset) — Implementation Detail:**
+> "On password reset completion in step 4 of the forced-change flow, should the system:
+> - A) Auto-redirect to admin dashboard?
+> - B) Show confirmation page with manual "Go to Admin" link?
+> - C) Force re-login with new password?"
+
+**Impact:** Blocks Simon (UI component) from determining redirect logic and error handling.
+
+
+- **Wash:** E2E criteria now clear → can scope test cases and prioritize flaky test fixes
+- **Simon:** Waiting on #4 password reset UX detail (A/B/C above) to start component build
+- **Mal:** Will update GitHub once upstream issues are created and synced to local roadmap
+
+
+**Critical:** v0.8 roadmap lives in local `decisions.md` (21 issues planned) but upstream GitHub is out of sync (only #4 exists). Before assigning Tier 1 work to Wash, River, Simon, need to clarify:
+- Should all 21 planned issues be created in upstream GitHub?
+- Or is upstream a separate, simpler track?
+
+Owner (Jeffrey T. Fritz) should confirm source of truth.
+
+
+
+# Simon — Admin Asset UI Implementation (2026-04-30) ✅ COMPLETED
+## Status: Completed
+## Owner: Simon
+
+### Context
+Working #320, #321, and #323 alongside the #322 setup-wizard flow exposed a duplication risk: setup and admin both needed logo/favicon upload, preview, and removal behavior.
+
+### Decision
+Use one reusable Blazor asset-management component for both startup and admin maintenance:
+
+- `SiteAssetManager` handles preview, upload, validation, and removal
+- parents own persistence by updating `ApplicationState.HasCustomLogo` or `ApplicationState.HasCustomFavicon`
+- assets resolve through `/api/files/{filename}` using deterministic names (`site-logo.*`, `site-favicon.*`)
+
+### Why
+This keeps the unified image-management decision concrete in code, reduces divergent UX between setup and admin, and makes future asset work (alt text, cropping, theme variants) easier to extend in one place.
+
+### Implementation Status
+- ✅ Setup wizard: 4-step flow (site name → assets → database → first admin)
+- ✅ Admin UI: Asset management panel in admin dashboard
+- ✅ Security: Wizard now seeds first admin (cleaner than separate DB seed)
+- ✅ Build: SharpSite.Web passes clean
+- ✅ Tests: SharpSite.Tests.Web 57/57 passing
+
+### Impact
+- **Wash:** Can now test E2E asset upload scenarios in setup/admin flows
+- **River:** Asset API contract stable; trash delete patterns documented for future Trash UI
+- **Kaylee:** Test infrastructure ready for asset component coverage
+
+---
+
+# River — Trash Delete Backend Contract (2026-05-03)
+## Status: Proposed
+## Owner: River
+
+### Decision
+Implement page/post delete as a shared trash pattern, not a one-off flag per repository.
+
+### Implementation
+- Added `ITrashableContent` + `TrashableContentExtensions` in `SharpSite.Abstractions`
+- Extended both page/post repository contracts with:
+ - `GetDeleted*()`
+ - `Restore*()`
+ - `PermanentlyDelete*()`
+- Normal read queries exclude trashed records by default
+- Permanent deletion is a separate explicit action, matching the recycle-bin decision
+
+### Why
+This keeps delete semantics consistent across pages and posts, and gives Simon a clear backend contract for a future Trash UI without putting business rules in Razor components.
+
+### Simon UI Contract
+- Delete button continues calling existing `DeletePage(int id)` / `DeletePost(string slug)` methods
+- Trash UI should read from `GetDeletedPages()` / `GetDeletedPosts()`
+- Restore actions call `RestorePage(int id)` / `RestorePost(string slug)`
+- Empty-trash or per-item purge calls `PermanentlyDeletePage(int id)` / `PermanentlyDeletePost(string slug)`
+
+### Cross-Impact
+- **Simon:** Unblocks Trash UI implementation when #309/#299 scope is active
+- **Kaylee:** Test infrastructure ready for trash/restore test cases
+
diff --git a/.squad/skills/site-asset-management/SKILL.md b/.squad/skills/site-asset-management/SKILL.md
new file mode 100644
index 00000000..3edafb1b
--- /dev/null
+++ b/.squad/skills/site-asset-management/SKILL.md
@@ -0,0 +1,39 @@
+# Site Asset Management
+
+> Reuse one Blazor component for logo/favicon maintenance across setup and admin.
+
+## SCOPE
+
+✅ THIS SKILL PRODUCES:
+- A shared upload/preview/remove component for site identity assets
+- Deterministic filenames for durable branding assets
+- Layout bindings that resolve custom assets through the file API
+
+❌ THIS SKILL DOES NOT PRODUCE:
+- Image cropping, resizing, or optimization pipelines
+- Media-library browsing
+- Theme-specific asset variants
+
+## Pattern
+
+When SharpSite needs editable site branding:
+
+1. Store only the selected filename in `ApplicationState`
+2. Keep the binary in file storage, served from `/api/files/{filename}`
+3. Use a shared interactive component to:
+ - preview the current asset
+ - validate allowed extensions for the asset kind
+ - upload a replacement using a deterministic filename
+ - remove the current custom asset
+4. Let parent components persist the chosen filename so the same component works in setup and admin
+
+## Current Conventions
+
+- Logo filenames: `site-logo.{ext}`
+- Favicon filenames: `site-favicon.{ext}`
+- Logo types: PNG, JPG, JPEG, WebP, GIF, SVG
+- Favicon types: PNG, ICO, SVG
+
+## Why This Exists
+
+Logo and favicon management were explicitly unified for v0.8. Reusing the component keeps the UX aligned between the setup wizard and the admin appearance screen while minimizing render and maintenance cost.
diff --git a/.squad/skills/ssr-async-not-found/SKILL.md b/.squad/skills/ssr-async-not-found/SKILL.md
new file mode 100644
index 00000000..3c55b8a2
--- /dev/null
+++ b/.squad/skills/ssr-async-not-found/SKILL.md
@@ -0,0 +1,35 @@
+---
+name: ssr-async-not-found
+description: Prevent premature 404s on SSR Razor components that load content asynchronously
+domain: frontend, blazor
+confidence: high
+source: earned (public post route blocker)
+---
+
+## Context
+
+In SharpSite SSR pages, a component can render once before an awaited repository call completes. If the page treats `null` model state as "not found" immediately, the first render can set HTTP 404 even though the content exists and arrives moments later.
+
+## Pattern
+
+- Separate route-only components from shared render/load components when a page needs multiple public routes.
+- Track load completion explicitly with a flag such as `_IsLoaded`.
+- Render a neutral loading state while the async fetch is in flight.
+- Only render `PageNotFound` after the fetch completes and the model is still null.
+- Keep the actual content rendering path unchanged once the model is present.
+
+## Example Shape
+
+1. Route component maps `/post/{slug}` and passes parameters into a shared page component.
+2. Shared page component:
+ - sets `_IsLoaded = false`
+ - awaits repository lookup
+ - sets `_IsLoaded = true`
+ - renders content when found
+ - renders `PageNotFound` only when `_IsLoaded` is true and the model is null
+
+## Anti-Patterns
+
+- Using `model is null` as both "loading" and "missing"
+- Setting 404 response codes from the first render of an async SSR page
+- Duplicating the full page markup just to support an alternate route when a shared content component will do
diff --git a/.squad/skills/trash-soft-delete/SKILL.md b/.squad/skills/trash-soft-delete/SKILL.md
new file mode 100644
index 00000000..2db2a988
--- /dev/null
+++ b/.squad/skills/trash-soft-delete/SKILL.md
@@ -0,0 +1,45 @@
+---
+name: trash-soft-delete
+description: Use a shared trash contract for recoverable content deletion
+domain: backend, data
+confidence: high
+source: earned (issues #309 and #299)
+---
+
+## Context
+
+SharpSite's content delete semantics for pages and posts are recycle-bin based. "Delete" should hide content from normal reads immediately, preserve enough metadata for recovery, and keep permanent removal as a separate explicit action.
+
+## Patterns
+
+- Add a shared contract in abstractions (`ITrashableContent`) instead of duplicating `IsDeleted` / `DeletedAt` logic per model.
+- Put state transitions in small shared helpers (`MoveToTrash`, `RestoreFromTrash`) so repositories only handle persistence concerns.
+- Keep repository semantics explicit:
+ - `Delete*` => move to trash
+ - `GetDeleted*` => list trash
+ - `Restore*` => recover from trash
+ - `PermanentlyDelete*` => hard delete
+- Make all normal read paths exclude trashed content by default so UI components don't need to remember the filter.
+- Mirror schema and repository changes in both `src\SharpSite.Data.Postgres` and `plugins\SharpSite.Plugins.Data.Postgres` while both data stacks exist.
+
+## Examples
+
+- `PgPostRepository.DeletePost(slug)` marks `IsDeleted` and `DeletedAt`, then saves.
+- `PgPageRepository.GetPages()` returns only `!IsDeleted` rows and invalidates the cached active-page list on trash/restore/purge.
+- EF migrations add nullable `DeletedAt` plus non-null `IsDeleted` with a default of `false`.
+
+## Testing Notes
+
+- Repository tests can use EF Core InMemory safely when `PgContext` is registered with a shared `InMemoryDatabaseRoot`; otherwise separate scopes may not observe the same data.
+- Validate the contract end-to-end for each content type:
+ 1. `Delete*` marks the entity deleted and removes it from normal reads
+ 2. `GetDeleted*` exposes the trashed entity
+ 3. `Restore*` clears trash metadata and restores normal visibility
+ 4. `PermanentlyDelete*` removes the row entirely
+- For pages, re-read the saved entity before asserting by `Id`, because `PgPageRepository.AddPage` currently returns the original abstraction object rather than the tracked database instance.
+
+## Anti-Patterns
+
+- Treating soft delete like a UI concern and filtering only in Razor components
+- Reusing `Delete*` for permanent removal
+- Adding trash fields to one data stack but not the mirrored plugin-backed data stack