diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b775de0..177b5d5 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,17 +2,14 @@ "permissions": { "allow": [ "Bash(dotnet build:*)", + "Bash(Select-Object -First 50)", + "Bash(ls:*)", "Bash(dotnet test:*)", "Bash(dotnet clean:*)", - "Bash(Remove-Item -Recurse -Force CodeSoupCafe.Mauiobj,CodeSoupCafe.Mauibin -ErrorAction SilentlyContinue)", - "Bash(Remove-Item -Recurse -Force obj,bin -ErrorAction SilentlyContinue)", - "Bash(dir:*)", - "Bash(dotnet pack:*)", - "Bash(dotnet restore:*)", - "Bash(dotnet sln:*)", - "Bash(findstr:*)", - "Bash(Get-ChildItem -Recurse -Filter \"*carousel*\")", - "Bash(Select-Object FullName)" + "Bash(find:*)", + "Bash(powershell -ExecutionPolicy Bypass -File fix-namespaces.ps1)", + "Bash(grep:*)", + "Bash(timeout 90 dotnet test:*)" ] } } diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml index 363be4a..afa17c9 100644 --- a/.github/workflows/dotnet-desktop.yml +++ b/.github/workflows/dotnet-desktop.yml @@ -33,9 +33,6 @@ jobs: restore-keys: | ${{ runner.os }}-nuget- - - name: Remove Local Source for CI - run: dotnet nuget remove source LocalNuGetPackages --configfile nuget.config - - name: Restore NuGet packages run: dotnet restore LunaDraw.csproj --configfile nuget.config diff --git a/.gitignore b/.gitignore index 59f3084..a83aec4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,23 @@ -.vs -packages -bin -obj -binaries -Debug +# .gitignore for LunaDraw (.NET MAUI) +# Build results +[Bb]in/ +[Oo]bj/ +.vs/ +.vscode/ + +# User-specific files +*.user +*.userosscache +*.sln.docstates + +# Test results +TestResults/ + +# NuGet +packages/ +*.nupkg + +# OS generated files .DS_Store -.AppleDouble -.LSOverride -._* -*.psd -*.ipa -*.so -**/*.so -/tbsa-burn-calc/tbsa-burn-calc.Android/logcat.txt -/logcat.txt -legacy \ No newline at end of file +Thumbs.db diff --git a/.lean-spec/config.json b/.lean-spec/config.json new file mode 100644 index 0000000..2dfb426 --- /dev/null +++ b/.lean-spec/config.json @@ -0,0 +1,19 @@ +{ + "template": "README.md", + "specsDir": "specs", + "structure": { + "pattern": "flat", + "prefix": "", + "dateFormat": "YYYYMMDD", + "sequenceDigits": 3, + "defaultFile": "README.md" + }, + "features": { + "aiAgents": true, + "compliance": true, + "approvals": true + }, + "templates": { + "default": "README.md" + } +} \ No newline at end of file diff --git a/.lean-spec/templates/AI-QUICK-REFERENCE.md b/.lean-spec/templates/AI-QUICK-REFERENCE.md new file mode 100644 index 0000000..3becd56 --- /dev/null +++ b/.lean-spec/templates/AI-QUICK-REFERENCE.md @@ -0,0 +1,482 @@ +# AI Agent Quick Reference Card + +> **Purpose**: Fast reference for AI agents executing TDD workflow +> **Print/Save**: Keep this open during implementation phases + +--- + +## 🎯 Your Mission + +Execute a **Test-Driven Development (TDD)** workflow using **Red-Green-Refactor (RGR)** methodology for .NET MAUI development. + +--- + +## πŸ“– Read These First (In Order) + +1. βœ… **README.md** - Context, environment, AI directive +2. βœ… **DESIGN.md** - Architecture, signatures, test scenarios +3. βœ… **PLAN.md** - Phase-by-phase execution plan +4. βœ… **TEST.md** - Test templates and examples + +--- + +## πŸš€ Phase Overview + +| Phase | Goal | Success Criteria | New Chat? | +|-------|------|------------------|-----------| +| **1. Scaffold** | Create empty classes | βœ… `dotnet build` succeeds | YES | +| **2. Test Definition** | Write failing tests | βœ… `dotnet test` shows RED | YES | +| **3. TDD Loop** | Implement one test at a time | βœ… `dotnet test` shows GREEN | YES | +| **4. Integration** | Wire up DI, refactor SOLID | βœ… Production-ready code | YES | + +--- + +## Phase 1: The Scaffold + +### Your Role +`Act as a C# scaffolding expert.` + +### Your Task +1. Read **DESIGN.md** β†’ Section: "Interface & Method Signatures" +2. Create ALL interfaces listed +3. Create ALL classes listed (constructor signatures only) +4. All methods: `throw new NotImplementedException();` +5. Add `[Obsolete("Work in Progress")]` to all classes +6. Run: `dotnet build` + +### You Are Done When +- [ ] Project compiles with ZERO errors +- [ ] All types from DESIGN.md exist in codebase +- [ ] All methods throw `NotImplementedException` + +### Example Output + +```csharp +// ISearchService.cs +public interface ISearchService +{ + Task> SearchAsync(string query); +} + +// SearchService.cs +[Obsolete("Work in Progress")] +public class SearchService : ISearchService +{ + public Task> SearchAsync(string query) + { + throw new NotImplementedException(); + } +} +``` + +### Do NOT +- ❌ Implement any business logic +- ❌ Write tests yet +- ❌ Add features not in DESIGN.md + +--- + +## Phase 2: Narrow Test Definition + +### Your Role +`Act as a TDD expert.` + +### Your Task +1. Read **DESIGN.md** β†’ Section: "Plain English Test Scenarios" +2. For EACH scenario, create a test method +3. Name tests: `Should_{Expected}_{When}_{Condition}` +4. Write assertions FIRST (e.g., `result.Should().NotBeNull();`) +5. Mock all dependencies with Moq +6. Do NOT implement production code to make tests pass +7. Run: `dotnet test` + +### You Are Done When +- [ ] All test files created +- [ ] All tests FAIL (RED state) +- [ ] No implementation code written + +### Example Output + +```csharp +// SearchViewModelTests.cs +public class SearchViewModelTests +{ + [Fact] + public async Task Should_Populate_Results_When_Search_Returns_Items() + { + // Arrange + var mockService = new Mock(); + var testItems = new List + { + new Item { Id = 1, Name = "Test 1" }, + new Item { Id = 2, Name = "Test 2" } + }; + mockService.Setup(s => s.SearchAsync("test")) + .ReturnsAsync(testItems); + + var viewModel = new SearchViewModel(mockService.Object); + + // Act + await viewModel.SearchCommand.Execute("test"); + + // Assert + viewModel.Results.Should().HaveCount(2); + viewModel.Results.Should().BeEquivalentTo(testItems); + } +} +``` + +### Do NOT +- ❌ Implement production code to make tests pass +- ❌ Skip writing tests for any scenario +- ❌ Add test scenarios not in DESIGN.md + +--- + +## Phase 3: The TDD Loop + +### Your Role +`Act as a TDD practitioner.` + +### Your Task (REPEAT for EACH test) +1. **RED**: Run `dotnet test` and identify ONE failing test +2. **GREEN**: Write MINIMAL code to pass ONLY that test +3. **VERIFY**: Run `dotnet test --filter "FullyQualifiedName~TestMethodName"` +4. **REFACTOR**: Clean up code (extract methods, rename variables) +5. **ASK**: Wait for confirmation before proceeding to next test + +### You Are Done When +- [ ] ALL tests pass (`dotnet test` shows 100% success) +- [ ] No `NotImplementedException` remaining +- [ ] Code is clean (SOLID principles) + +### Example Process + +**User**: "Implement the first failing test: Should_Populate_Results_When_Search_Returns_Items" + +**You**: + +```csharp +// SearchViewModel.cs +public class SearchViewModel : ReactiveObject +{ + private readonly ISearchService searchService; + + [Reactive] public ObservableCollection Results { get; set; } + + public ReactiveCommand> SearchCommand { get; } + + public SearchViewModel(ISearchService searchService) + { + this.searchService = searchService; + Results = new ObservableCollection(); + + SearchCommand = ReactiveCommand.CreateFromTask>( + async query => + { + var items = await searchService.SearchAsync(query); + Results.Clear(); + foreach (var item in items) + { + Results.Add(item); + } + return items; + }); + } +} +``` + +**Verify**: Run `dotnet test --filter "Should_Populate_Results_When_Search_Returns_Items"` + +**Result**: βœ… Test passes + +**You**: "Test passes. Ready to proceed to the next test? (Should_Clear_Results_When_Search_Text_Is_Empty)" + +### Do NOT +- ❌ Implement multiple tests at once +- ❌ Add features not covered by tests +- ❌ Skip refactoring step + +--- + +## Phase 4: Integration & Refactoring + +### Your Role +`Act as a C# architect.` + +### Your Task +1. Review code for SOLID violations +2. Register all services in `MauiProgram.cs` DI container +3. Add integration tests (ViewModel β†’ Service β†’ Data) +4. Remove `[Obsolete]` attributes +5. Add XML documentation comments to public APIs +6. Run: `dotnet test` (all tests must pass) + +### You Are Done When +- [ ] All services registered in DI +- [ ] Integration tests pass +- [ ] No SOLID violations +- [ ] Code is production-ready + +### Example Output + +```csharp +// MauiProgram.cs +public static class MauiProgram +{ + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + + // Register services + builder.Services.AddSingleton(); + builder.Services.AddTransient(); + + // ... + + return builder.Build(); + } +} + +// Integration test +[Fact] +public async Task Should_Load_And_Display_Search_Results_End_To_End() +{ + // Arrange: Full dependency chain + var mockRepository = new Mock(); + mockRepository.Setup(r => r.SearchAsync("test")) + .ReturnsAsync(new List { new Item { Id = 1 } }); + + var service = new SearchService(mockRepository.Object); + var viewModel = new SearchViewModel(service); + + // Act + await viewModel.SearchCommand.Execute("test"); + + // Assert + viewModel.Results.Should().HaveCount(1); +} +``` + +### Do NOT +- ❌ Add new features +- ❌ Skip DI registration +- ❌ Leave `[Obsolete]` attributes + +--- + +## πŸ›‘ Critical Rules + +### ALWAYS +- βœ… Read DESIGN.md BEFORE writing code +- βœ… Follow ONLY the current phase in PLAN.md +- βœ… Use NEW chat window for EACH phase +- βœ… Write tests BEFORE implementation (Phase 2 before Phase 3) +- βœ… Implement ONE test at a time (Phase 3) +- βœ… Use compiler errors to self-correct type mismatches +- βœ… Mock all dependencies with Moq +- βœ… Name tests: `Should_{Expected}_{When}_{Condition}` + +### NEVER +- ❌ Perform "Big Bang" implementations +- ❌ Add features not defined in DESIGN.md (check "Non-Goals") +- ❌ Skip tests or implement multiple features at once +- ❌ Write implementation code in Phase 2 (Test Definition) +- ❌ Guess types or method signatures (they're in DESIGN.md) +- ❌ Use underscores, regions, or abbreviations (LunaDraw standards) + +--- + +## πŸ§ͺ Test Template (Copy-Paste) + +```csharp +// TODO: Implement test for "[Scenario name from DESIGN.md]" +// Given: [Precondition] +// When: [Action] +// Then: [Expected result] + +[Fact] +public void Should_{Expected}_{When}_{Condition}() +{ + // Arrange + var mockService = new Mock(); + // Setup mock behavior + mockService.Setup(s => s.MethodName(It.IsAny())) + .ReturnsAsync(expectedResult); + + var viewModel = new ExampleViewModel(mockService.Object); + + // Act + var result = viewModel.Command.Execute(input).Subscribe(); + + // Assert + result.Should().NotBeNull(); + result.PropertyName.Should().Be(expectedValue); + mockService.Verify(s => s.MethodName(It.IsAny()), Times.Once); +} +``` + +--- + +## πŸ”§ Common Commands + +```bash +# Build project +dotnet build LunaDraw.csproj -f net10.0-windows10.0.19041.0 + +# Run all tests +dotnet test + +# Run specific test +dotnet test --filter "FullyQualifiedName~TestMethodName" + +# Check code coverage +dotnet test /p:CollectCoverage=true +``` + +--- + +## πŸ” Self-Check Before Proceeding + +### Phase 1 β†’ Phase 2 +- [ ] Does `dotnet build` succeed? +- [ ] Do all classes have `[Obsolete("Work in Progress")]`? +- [ ] Do all methods throw `NotImplementedException`? + +### Phase 2 β†’ Phase 3 +- [ ] Does `dotnet test` show RED (all tests fail)? +- [ ] Is there a test for EVERY scenario in DESIGN.md? +- [ ] Did I avoid writing any implementation code? + +### Phase 3 β†’ Phase 4 +- [ ] Does `dotnet test` show GREEN (all tests pass)? +- [ ] Is there no `NotImplementedException` remaining? +- [ ] Did I implement tests one at a time? + +### Phase 4 β†’ Complete +- [ ] Are all services registered in `MauiProgram.cs`? +- [ ] Do integration tests pass? +- [ ] Are `[Obsolete]` attributes removed? + +--- + +## 🚨 Red Flags (Stop and Ask User) + +If you encounter ANY of these, STOP and ask the user: + +1. **Missing interface signature in DESIGN.md** + - "I don't see the signature for `IExampleService` in DESIGN.md. Should I create it?" + +2. **Unclear test scenario** + - "The scenario 'User submits form' is ambiguous. What should happen on submit?" + +3. **Feature not in DESIGN.md** + - "Pagination is not mentioned in DESIGN.md Goals. Should I add it?" + +4. **Compilation error after following instructions** + - "The build fails with error X. The signature in DESIGN.md may need updating." + +5. **Test passes immediately (should be RED first)** + - "Test passes without implementation. The test may not be testing the right thing." + +--- + +## πŸ“Š Progress Tracking + +Copy this checklist into your responses to track progress: + +```markdown +### Phase Progress + +**Phase 1: Scaffold** +- [ ] Interfaces created +- [ ] Classes created +- [ ] Project compiles + +**Phase 2: Test Definition** +- [ ] Test files created +- [ ] All scenarios have tests +- [ ] All tests FAIL (RED) + +**Phase 3: TDD Loop** +- [ ] Test 1: [Name] βœ… +- [ ] Test 2: [Name] ⏳ +- [ ] Test 3: [Name] ⏳ + +**Phase 4: Integration** +- [ ] Services registered in DI +- [ ] Integration tests added +- [ ] SOLID refactoring complete +``` + +--- + +## πŸ’‘ Pro Tips + +1. **Use compiler feedback**: If you get a type error, check DESIGN.md for the correct signature +2. **One assertion per line**: Makes test failures easier to diagnose +3. **Prefer `[Theory]` over multiple `[Fact]`**: Test multiple inputs with `[InlineData]` +4. **Mock interfaces, not classes**: Use `Mock`, not `Mock` +5. **Verify mock calls**: Use `mockService.Verify(s => s.Method(...), Times.Once)` + +--- + +## πŸŽ“ Example Chat Flow + +**User**: "Start Phase 1 for the search feature spec" + +**You**: +``` +Acting as a C# scaffolding expert for Phase 1. + +I've read DESIGN.md and identified the following to scaffold: +- ISearchService interface +- SearchService class +- SearchViewModel class + +Creating empty classes now... + +[Create files with NotImplementedException] + +βœ… Phase 1 Complete +- Project compiles +- All methods throw NotImplementedException +- All classes marked [Obsolete("Work in Progress")] + +Run `dotnet build` to verify. + +Ready to proceed to Phase 2 (Test Definition)? +``` + +**User**: "Yes, proceed to Phase 2 in a NEW chat" + +**You**: +``` +Please open a NEW chat window and paste the following: + +--- + +Act as a TDD expert working on the LunaDraw search feature. + +Current Phase: Phase 2 (Narrow Test Definition) +Previous Phase Chat: [Link to this chat] + +Read the spec at: specs/NNN-search-feature/DESIGN.md +Focus on section: "Plain English Test Scenarios" + +Create xUnit tests for each scenario using naming: Should_{Expected}_{When}_{Condition} +Use Moq to mock ISearchService +Do NOT implement production code to make tests pass + +Your goal: RED state (all tests fail) +``` + +--- + +**That's it! You're ready to execute TDD workflows.** πŸš€ + +For detailed explanations, refer to: +- [TDD-WORKFLOW-GUIDE.md](TDD-WORKFLOW-GUIDE.md) - Comprehensive guide +- [DESIGN.md](DESIGN.md) - Architecture blueprint +- [PLAN.md](PLAN.md) - Phase-by-phase plan +- [TEST.md](TEST.md) - Test templates diff --git a/.lean-spec/templates/DESIGN.md b/.lean-spec/templates/DESIGN.md new file mode 100644 index 0000000..54d663a --- /dev/null +++ b/.lean-spec/templates/DESIGN.md @@ -0,0 +1,168 @@ +# Design: {name} + +> Part of [{name}](README.md) + +## Goals & Non-Goals + +### Goals + +- + +### Non-Goals + +- + +## Visual Architecture + + + +```mermaid +graph TD + A[MainPage] --> B[ViewModel] + B --> C[Service/Repository] + C --> D[Data Layer] +``` + +### Component Hierarchy + + + +**Pages/Views**: +- + +**ViewModels**: +- + +**Services/Repositories**: +- + +**Models/Entities**: +- + +## Interface & Method Signatures + + + + +### Interface: IExampleService + +```csharp +public interface IExampleService +{ + /// + /// Retrieves items with pagination support + /// + /// Starting position (0-based) + /// Maximum number of items to return + /// Collection of items + Task> GetItemsAsync(int offset, int limit); +} +``` + +### Class: ExampleViewModel + +```csharp +public class ExampleViewModel : ReactiveObject +{ + // Observable properties + [Reactive] public string SearchText { get; set; } + + // Commands + public ReactiveCommand SubmitCommand { get; } + + // Constructor signature + public ExampleViewModel(IExampleService service, IMessageBus messageBus); +} +``` + +## Plain English Test Scenarios + + + +### Scenario: User submits empty form +- **Given**: The form is displayed +- **When**: User clicks "Submit" with an empty required field +- **Then**: An error message appears and form is not submitted + +### Scenario: Successful data load +- **Given**: The service returns 5 items +- **When**: The ViewModel initializes +- **Then**: The Items collection contains 5 elements + +### Scenario: Navigation on item selection +- **Given**: A list of items is displayed +- **When**: User taps an item +- **Then**: Navigation occurs to the detail page with the selected item + +## Technical Approach + + + +**Framework**: .NET MAUI (net10.0-windows10.0.19041.0) + +**Patterns**: +- MVVM with ReactiveUI +- Repository pattern for data access +- Dependency Injection (Microsoft.Extensions.DependencyInjection) + +**Key Libraries**: +- ReactiveUI (v{version}) +- SkiaSharp (if applicable) +- CommunityToolkit.Maui + +## Design Decisions + + + +### Decision 1: Use ReactiveUI over plain INotifyPropertyChanged + +**Context**: Need observable properties for data binding in MAUI + +**Decision**: Use ReactiveUI's `ReactiveObject` base class and `[Reactive]` attribute + +**Rationale**: +- Reduces boilerplate compared to manual property change notifications +- Provides powerful reactive extensions (WhenAnyValue, Throttle, etc.) +- Aligns with existing LunaDraw architecture + +**Trade-offs**: +- Additional dependency on ReactiveUI NuGet package +- Steeper learning curve for developers unfamiliar with reactive programming + +## Platform-Specific Constraints + + + +**Windows**: +- + +**Android**: +- + +**iOS/MacCatalyst**: +- + +## Dependencies + + + +### System Dependencies +- .NET 10 SDK +- MAUI workload installed +- SkiaSharp (if rendering graphics) + +### Project Dependencies +- LunaDraw.Logic (for shared models/services) +- CodeSoupCafe.Maui (for carousel/gallery controls) + +### External Dependencies +- + +## Security & Compliance + + + +- [ ] Handles sensitive data (PII, credentials, etc.) +- [ ] Input validation implemented for all user inputs +- [ ] Security review completed +- [ ] Compliance requirements addressed (COPPA for children's app) diff --git a/.lean-spec/templates/INDEX.md b/.lean-spec/templates/INDEX.md new file mode 100644 index 0000000..99a6f63 --- /dev/null +++ b/.lean-spec/templates/INDEX.md @@ -0,0 +1,389 @@ +# Lean-Spec TDD Templates - Index + +> **Version**: 1.0 +> **Last Updated**: 2025-12-29 +> **Purpose**: TDD-optimized specification templates for AI-driven .NET MAUI development + +--- + +## πŸ“‹ Quick Navigation + +| Document | Purpose | Audience | +|----------|---------|----------| +| **[README.md](README.md)** | Entry point and route map for AI agents | AI Agents + Humans | +| **[DESIGN.md](DESIGN.md)** | Architectural blueprint with signatures and scenarios | Architects + AI Agents | +| **[PLAN.md](PLAN.md)** | Phase-by-phase TDD execution plan | AI Agents (Implementation) | +| **[TEST.md](TEST.md)** | Test templates and acceptance criteria | AI Agents (Testing) | +| **[TDD-WORKFLOW-GUIDE.md](TDD-WORKFLOW-GUIDE.md)** | Comprehensive workflow guide | Humans (Learning) | +| **[REFACTORING-SUMMARY.md](REFACTORING-SUMMARY.md)** | What changed and why | Humans (Context) | + +--- + +## πŸš€ Getting Started + +### For Humans (Spec Authors) + +1. **Start here**: [REFACTORING-SUMMARY.md](REFACTORING-SUMMARY.md) - Understand the refactoring rationale +2. **Learn the workflow**: [TDD-WORKFLOW-GUIDE.md](TDD-WORKFLOW-GUIDE.md) - Deep dive into the 4-phase process +3. **Create a spec**: Use the templates (README, DESIGN, PLAN, TEST) to define your feature +4. **Hand off to AI**: Copy the AI Agent Directive from [README.md](README.md) into your chat session + +### For AI Agents (Implementation) + +1. **Start here**: [README.md](README.md) - Entry point with AI Development Flow +2. **Read in order**: + - [DESIGN.md](DESIGN.md) - Architecture, signatures, test scenarios + - [PLAN.md](PLAN.md) - Phase-by-phase execution plan + - [TEST.md](TEST.md) - Test structure and templates +3. **Execute phases sequentially**: + - Phase 1: Scaffold (empty classes, compile-only) + - Phase 2: Test Definition (write failing tests, RED state) + - Phase 3: TDD Loop (implement one test at a time, GREEN state) + - Phase 4: Integration & Refactoring (wire up DI, SOLID principles) +4. **Use NEW chat window for EACH phase** to minimize context drift + +--- + +## πŸ“– Document Details + +### Core Templates (Used for Every Spec) + +#### [README.md](README.md) - Route Map + +**Contains**: +- Overview and context +- AI Development Flow (step-by-step guide) +- AI Agent Directive (copy-paste prompt) +- Environment specifications (.NET 10, MAUI, dependencies) +- Project context (LunaDraw patterns, coding standards) + +**Use when**: +- Starting a new spec (fill out overview) +- Beginning a new phase (copy AI Agent Directive) +- Linking chat sessions (update Notes section) + +--- + +#### [DESIGN.md](DESIGN.md) - Architectural Blueprint + +**Contains**: +- Goals & Non-Goals (explicit boundaries) +- Visual Architecture (Mermaid diagrams) +- Component Hierarchy (MVVM structure) +- Interface & Method Signatures (type-safe definitions) +- Plain English Test Scenarios (Given-When-Then) +- Technical Approach (frameworks, patterns) +- Design Decisions (rationale and trade-offs) + +**Use when**: +- Defining system architecture +- Specifying public APIs +- Creating test scenarios +- Preventing scope creep (Non-Goals) + +**Key for AI agents**: Provides type signatures to prevent compilation-driven hallucinations + +--- + +#### [PLAN.md](PLAN.md) - Phase-by-Phase Execution Plan + +**Contains**: +- TDD Development Process overview +- Phase Transition Requirements (gated progression) +- **Phase 1: The Scaffold** - Empty classes, `NotImplementedException` +- **Phase 2: Narrow Test Definition** - Write failing tests (RED) +- **Phase 3: The TDD Loop** - Implement one test at a time (GREEN) +- **Phase 4: Integration & Refactoring** - Wire up DI, SOLID principles +- Rollout Strategy +- Risks & Mitigation + +**Use when**: +- Executing implementation (AI agents follow phase by phase) +- Tracking progress (check off tasks within each phase) +- Gating transitions (verify success criteria before moving to next phase) + +**Key for AI agents**: Contains AI Agent Directives for each phase + +--- + +#### [TEST.md](TEST.md) - Test Templates & Acceptance Criteria + +**Contains**: +- Testing Strategy (test pyramid) +- Unit Tests (scenario-based with TODO comments) +- Test Naming Convention (`Should_{Expected}_{When}_{Condition}`) +- Integration Tests (end-to-end flows) +- Performance Tests (benchmarks) +- Security Tests (input validation) +- Test Data (TestDataBuilder pattern) +- Acceptance Criteria (definition of done) + +**Use when**: +- Writing tests in Phase 2 (translate scenarios to test methods) +- Validating implementation in Phase 3 (run tests) +- Defining acceptance criteria (what makes the feature "done") + +**Key for AI agents**: Provides concrete test templates derived from DESIGN.md scenarios + +--- + +### Documentation (Reference for Humans) + +#### [TDD-WORKFLOW-GUIDE.md](TDD-WORKFLOW-GUIDE.md) - Comprehensive Guide + +**Contains**: +- Overview of TDD philosophy +- Red-Green-Refactor (RGR) explanation +- Document structure breakdown +- Four-phase workflow walkthrough +- Chat session management strategy +- Anti-patterns to avoid (e.g., "vibe coding", "Big Bang" implementation) +- Complete example walkthrough (Search feature) + +**Use when**: +- Learning the TDD workflow +- Onboarding new team members +- Troubleshooting issues (anti-patterns section) +- Creating new specs (example walkthrough) + +--- + +#### [REFACTORING-SUMMARY.md](REFACTORING-SUMMARY.md) - What Changed & Why + +**Contains**: +- What changed (before/after comparison) +- Document-by-document changes +- Key improvements (prevents "vibe coding", enforces test-first, etc.) +- How to use the refactored templates +- Migration guide (updating existing specs) +- Benefits summary + +**Use when**: +- Understanding the rationale for the refactoring +- Migrating existing specs to the new format +- Explaining the new workflow to stakeholders + +--- + +## πŸ”„ Workflow Summary + +### The 4-Phase TDD Process + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHASE 1: SCAFFOLD β”‚ +β”‚ Goal: Empty classes with NotImplementedException β”‚ +β”‚ Success: Project compiles β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHASE 2: TEST DEFINITION β”‚ +β”‚ Goal: Write failing tests (RED state) β”‚ +β”‚ Success: All tests FAIL β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHASE 3: TDD LOOP β”‚ +β”‚ Goal: Implement one test at a time (GREEN state) β”‚ +β”‚ Success: All tests PASS β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHASE 4: INTEGRATION & REFACTOR β”‚ +β”‚ Goal: Wire up DI, refactor for SOLID β”‚ +β”‚ Success: Production-ready code β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## 🎯 AI Agent Quick Start + +### Copy-Paste This Into Your Chat + +``` +Act as a TDD expert working on the LunaDraw .NET MAUI project. + +ENVIRONMENT: +- Framework: .NET 10, MAUI (net10.0-windows10.0.19041.0) +- Testing: xUnit + Moq + FluentAssertions +- Architecture: MVVM with ReactiveUI +- Dependency Injection: Microsoft.Extensions.DependencyInjection + +INSTRUCTIONS: +1. Do NOT perform "Big Bang" implementations +2. Read DESIGN.md and PLAN.md BEFORE writing code +3. Follow ONLY the current phase in PLAN.md +4. For Phase 1: Scaffold empty classes with NotImplementedException +5. For Phase 2: Write failing tests based on DESIGN.md scenarios +6. For Phase 3: Implement MINIMAL code to pass ONE test at a time +7. For Phase 4: Refactor for SOLID principles and wire up DI + +USE COMPILER FEEDBACK to self-correct type mismatches. +DO NOT add features not defined in DESIGN.md. +DO NOT skip tests or implement multiple features at once. + +Current Phase: [Specify Phase 1, 2, 3, or 4] +Previous Phase Chat: [Link to previous chat session] + +Read the spec at: [Path to spec directory] +``` + +--- + +## πŸ“Š File Structure Example + +When you create a new spec using these templates: + +``` +specs/NNN-feature-name/ +β”œβ”€β”€ README.md # Route map (start here for AI agents) +β”œβ”€β”€ DESIGN.md # Architectural blueprint +β”œβ”€β”€ PLAN.md # Phase-by-phase execution plan +β”œβ”€β”€ TEST.md # Test templates +└── [Additional files as needed] +``` + +--- + +## βœ… Phase Checklist + +Use this checklist to track progress through the workflow: + +- [ ] **Spec Definition** + - [ ] Fill out README.md (overview, context) + - [ ] Define Goals & Non-Goals in DESIGN.md + - [ ] Create Mermaid diagram in DESIGN.md + - [ ] Define all interface signatures in DESIGN.md + - [ ] Write Plain English Test Scenarios in DESIGN.md + - [ ] Review PLAN.md phases (customize if needed) + +- [ ] **Phase 1: Scaffold** (New Chat) + - [ ] Copy AI Agent Directive (Phase 1) to chat + - [ ] AI creates empty classes/interfaces + - [ ] Verify: `dotnet build` succeeds + - [ ] Link chat session in README.md Notes + +- [ ] **Phase 2: Test Definition** (New Chat) + - [ ] Copy AI Agent Directive (Phase 2) to chat + - [ ] AI writes failing tests for each scenario + - [ ] Verify: `dotnet test` shows RED state (all tests fail) + - [ ] Link chat session in README.md Notes + +- [ ] **Phase 3: TDD Loop** (New Chat per test or group) + - [ ] Copy AI Agent Directive (Phase 3) to chat + - [ ] For each test: RED β†’ GREEN β†’ REFACTOR + - [ ] Commit after each test passes: `git commit -m "test: pass TestName"` + - [ ] Verify: `dotnet test` shows GREEN state (all tests pass) + - [ ] Link chat session in README.md Notes + +- [ ] **Phase 4: Integration & Refactoring** (New Chat) + - [ ] Copy AI Agent Directive (Phase 4) to chat + - [ ] AI registers services in MauiProgram.cs + - [ ] AI adds integration tests + - [ ] AI refactors for SOLID principles + - [ ] Remove `[Obsolete]` attributes + - [ ] Verify: `dotnet test` passes, code is production-ready + - [ ] Link chat session in README.md Notes + +- [ ] **Completion** + - [ ] Update spec status: `planned` β†’ `in-progress` β†’ `complete` + - [ ] Final code review + - [ ] Merge feature branch + - [ ] Archive spec (if appropriate) + +--- + +## πŸ› οΈ Common Commands + +### Build & Test + +```bash +# Build project +dotnet build LunaDraw.csproj -f net10.0-windows10.0.19041.0 + +# Run all tests +dotnet test tests/LunaDraw.Tests/LunaDraw.Tests.csproj + +# Run specific test +dotnet test --filter "FullyQualifiedName~TestMethodName" + +# Check code coverage +dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover +``` + +### Lean-Spec Commands + +```bash +# Create new spec +lean-spec create "feature-name" + +# List specs +lean-spec list + +# View spec +lean-spec view "feature-name" + +# Update spec status +lean-spec update "feature-name" --status in-progress +``` + +--- + +## 🚨 Anti-Patterns to Avoid + +| Anti-Pattern | Symptom | Fix | +|--------------|---------|-----| +| **Vibe Coding** | AI adds features not in DESIGN.md | Add to Non-Goals section | +| **Big Bang** | AI implements all code at once | Use Phase 3 chat prompt (one test at a time) | +| **Tests After** | AI writes code first, then tests | Enforce Phase 2 before Phase 3 | +| **Skipping Scaffold** | No interfaces defined | Complete Phase 1 before Phase 2 | +| **Context Drift** | AI forgets earlier instructions | Use NEW chat per phase | + +--- + +## πŸ“š Additional Resources + +- **LunaDraw CLAUDE.md**: Project-specific coding standards and architecture +- **Workspace CLAUDE.md**: Multi-project workspace overview +- **.clinerules**: Strict coding rules (NO underscores, NO regions, etc.) + +--- + +## πŸ”— Quick Links + +- [README Template](README.md) - Route map for AI agents +- [DESIGN Template](DESIGN.md) - Architectural blueprint +- [PLAN Template](PLAN.md) - Phase-by-phase plan +- [TEST Template](TEST.md) - Test templates +- [Workflow Guide](TDD-WORKFLOW-GUIDE.md) - Comprehensive guide +- [Refactoring Summary](REFACTORING-SUMMARY.md) - What changed and why + +--- + +## πŸ“ Version History + +| Version | Date | Changes | +|---------|------|---------| +| 1.0 | 2025-12-29 | Initial refactoring for TDD/RGR methodology | + +--- + +## πŸ’‘ Tips for Success + +1. **Always start with DESIGN.md**: Define Goals, Non-Goals, and test scenarios before writing code +2. **Use Mermaid diagrams**: Visualize architecture before implementation +3. **Define interfaces upfront**: Let the compiler guide the AI +4. **New chat per phase**: Prevent context drift +5. **One test at a time**: Resist the urge to implement everything at once +6. **Link chat sessions**: Maintain continuity across phases +7. **Verify gates**: Don't proceed to next phase until success criteria met + +--- + +**Happy TDD-ing!** πŸŽ‰ + +For questions or improvements, update this index and related documents as you refine the process. diff --git a/.lean-spec/templates/PLAN.md b/.lean-spec/templates/PLAN.md new file mode 100644 index 0000000..12841c4 --- /dev/null +++ b/.lean-spec/templates/PLAN.md @@ -0,0 +1,188 @@ +# Plan: {name} + +> Part of [{name}](README.md) + +## TDD Development Process + +This spec follows **Red-Green-Refactor (RGR)** methodology. Each phase must be completed in order, with green state achieved before proceeding. + +### Phase Transition Requirements + +**CRITICAL**: Use a NEW chat window for each phase to minimize context drift. Include a link to the previous phase's chat session. + +- **Phase 1 β†’ Phase 2**: All scaffolding must compile with `NotImplementedException` +- **Phase 2 β†’ Phase 3**: At least one failing test must exist (RED state) +- **Phase 3 β†’ Phase 4**: All tests must pass (GREEN state) +- **Phase 4 β†’ Complete**: Code review and refactoring complete + +## Phase 1: The Scaffold + +**Goal**: Create the structural skeleton WITHOUT any business logic. Ensure the project compiles. + +**AI Agent Directive**: +> "Act as a C# scaffolding expert. Read [DESIGN.md](DESIGN.md) and create ONLY empty classes and interfaces based on the signatures defined. All methods must contain `throw new NotImplementedException();`. Mark all classes with `[Obsolete("Work in Progress")]`. Do NOT implement any logic. Your only goal is a compiling project." + +**Tasks**: +- [ ] Create all interfaces defined in DESIGN.md (Section: Interface & Method Signatures) +- [ ] Create all ViewModel classes with constructor signatures only +- [ ] Create all Service/Repository classes with method stubs +- [ ] Create all Model/Entity classes with properties only +- [ ] Add `[Obsolete("Work in Progress")]` attribute to all classes +- [ ] Verify project compiles: `dotnet build` + +**Success Criteria**: +- [ ] Project builds successfully with zero errors +- [ ] All types from DESIGN.md exist in codebase +- [ ] All methods throw `NotImplementedException` +- [ ] No business logic implemented + +**New Chat Session**: Link to Phase 1 chat: [Chat Session URL] + +--- + +## Phase 2: Narrow Test Definition + +**Goal**: Write failing tests that isolate ViewModels from UI. Define test scenarios WITHOUT implementation. + +**AI Agent Directive**: +> "Act as a TDD expert. Read the 'Plain English Test Scenarios' from [DESIGN.md](DESIGN.md). For each scenario, create a corresponding xUnit test method in the appropriate test file. Use the naming convention `Should_{Expected}_{When}_{Condition}`. Write ONLY the test method signature and assertionsβ€”do NOT implement the code to make it pass. Use Moq to mock all dependencies. Your goal is RED state (failing tests)." + +**Tasks**: +- [ ] Create test project: `tests/{name}.Tests/` +- [ ] Add xUnit, Moq, FluentAssertions NuGet packages +- [ ] Create test file for each ViewModel: `{ViewModelName}Tests.cs` +- [ ] Translate each Plain English Scenario from DESIGN.md into a test method +- [ ] Write assertions FIRST (e.g., `result.Should().NotBeNull();`) +- [ ] Mock all service dependencies using Moq +- [ ] Run tests: `dotnet test` (expect ALL to fail) + +**Example Test Template**: + +```csharp +[Fact] +public void Should_Show_Error_Message_When_Submit_With_Empty_Field() +{ + // Arrange + var mockService = new Mock(); + var viewModel = new ExampleViewModel(mockService.Object); + viewModel.SearchText = string.Empty; + + // Act + viewModel.SubmitCommand.Execute().Subscribe(); + + // Assert + viewModel.ErrorMessage.Should().NotBeNullOrEmpty(); + viewModel.IsFormValid.Should().BeFalse(); +} +``` + +**Success Criteria**: +- [ ] All test files created and scenarios covered +- [ ] All tests FAIL (RED state) +- [ ] No implementation code written yet +- [ ] Test coverage for all scenarios in DESIGN.md + +**New Chat Session**: Link to Phase 2 chat: [Chat Session URL] + +--- + +## Phase 3: The TDD Loop (Red-Green-Refactor) + +**Goal**: Implement minimal code to pass tests one at a time. Achieve GREEN state. + +**AI Agent Directive**: +> "Act as a TDD practitioner. Read [TEST.md](TEST.md) for the first failing test scenario. Write the MINIMAL code required to make ONLY that test pass. Do NOT implement features not covered by tests. After the test passes, ask me to confirm before proceeding to the next test. Use C# compiler errors to self-correct type mismatches." + +**Process** (Repeat for EACH test): + +1. **RED**: Run `dotnet test` and identify ONE failing test +2. **GREEN**: Write minimal implementation to pass that test +3. **VERIFY**: Run `dotnet test --filter "FullyQualifiedName~{TestMethodName}"` +4. **REFACTOR**: Clean up code if needed (extract methods, rename variables) +5. **COMMIT**: Commit with message: `test: pass {TestMethodName}` +6. **REPEAT**: Move to next failing test + +**Tasks**: +- [ ] Test 1: [Scenario name from DESIGN.md] + - [ ] RED: Confirm test fails + - [ ] GREEN: Implement minimal code + - [ ] VERIFY: Test passes + - [ ] REFACTOR: Clean up +- [ ] Test 2: [Scenario name from DESIGN.md] + - [ ] RED: Confirm test fails + - [ ] GREEN: Implement minimal code + - [ ] VERIFY: Test passes + - [ ] REFACTOR: Clean up +- [ ] Test 3: [Scenario name from DESIGN.md] + - [ ] RED: Confirm test fails + - [ ] GREEN: Implement minimal code + - [ ] VERIFY: Test passes + - [ ] REFACTOR: Clean up + +**Success Criteria**: +- [ ] ALL tests pass: `dotnet test` shows 100% success +- [ ] No `NotImplementedException` remaining in production code +- [ ] Code coverage meets minimum threshold (e.g., 80%) +- [ ] GREEN state achieved + +**New Chat Session**: Link to Phase 3 chat: [Chat Session URL] + +--- + +## Phase 4: Integration & Refactoring + +**Goal**: Integrate components, wire up DI, and refactor for SOLID principles. + +**AI Agent Directive**: +> "Act as a C# architect. Review the implemented code for SOLID violations, code smells, and opportunities for refactoring. Ensure all dependencies are registered in `MauiProgram.cs`. Add integration tests that verify end-to-end flows. Do NOT add featuresβ€”only improve existing code structure." + +**Tasks**: +- [ ] Register all services in `MauiProgram.cs` DI container +- [ ] Add integration tests for ViewModel β†’ Service β†’ Data flow +- [ ] Refactor for SOLID principles (SRP, OCP, DIP) +- [ ] Remove `[Obsolete]` attributes +- [ ] Add XML documentation comments to public APIs +- [ ] Run static analysis (if configured) +- [ ] Final test run: `dotnet test` + +**Success Criteria**: +- [ ] All services registered in DI container +- [ ] Integration tests pass +- [ ] No SOLID violations +- [ ] Code is production-ready + +**New Chat Session**: Link to Phase 4 chat: [Chat Session URL] + +--- + +## Rollout Strategy + + + +**Staging**: +- Build and test on local machine +- Test on Windows target device + +**Production**: +- Merge feature branch to main +- Create release build +- Deploy to test devices (Windows/Android/iOS) + +**Monitoring**: +- Check for runtime exceptions in logs +- Validate user flows in production + +**Rollback Plan**: +- Revert commit if critical bugs found +- Disable feature flag (if applicable) + +--- + +## Risks & Mitigation + +| Risk | Impact | Mitigation | +|------|--------|------------| +| AI generates invalid C# syntax | High (compile failure) | Use compiler feedback to self-correct | +| Tests pass but feature is broken | High (false confidence) | Add integration tests in Phase 4 | +| Context drift across chat sessions | Medium (inconsistent code) | Use NEW chat per phase with links | +| Missing dependencies in DI | Medium (runtime crash) | Validate in Phase 4 integration tests | diff --git a/.lean-spec/templates/README.md b/.lean-spec/templates/README.md new file mode 100644 index 0000000..a6e947b --- /dev/null +++ b/.lean-spec/templates/README.md @@ -0,0 +1,173 @@ +--- +status: planned +created: '{date}' +tags: [] +priority: medium +--- + +# {name} + +> **Status**: {status} Β· **Priority**: {priority} Β· **Created**: {date} + +## Overview + + + +--- + +## AI Development Flow (Route Map) + +**CRITICAL**: This spec follows **Test-Driven Development (TDD)** with **Red-Green-Refactor (RGR)** methodology. + +### πŸ“‹ Entry Point for AI Agents + +Before starting implementation, AI agents MUST: + +1. **Read in order**: + - βœ… This README (context and constraints) + - βœ… [DESIGN.md](DESIGN.md) (architecture, signatures, test scenarios) + - βœ… [PLAN.md](PLAN.md) (phase-by-phase execution plan) + - βœ… [TEST.md](TEST.md) (test structure and examples) + +2. **Execute phases sequentially** (see [PLAN.md](PLAN.md)): + - **Phase 1**: Scaffold (empty classes, compile-only) + - **Phase 2**: Test Definition (write failing tests, RED state) + - **Phase 3**: TDD Loop (implement one test at a time, GREEN state) + - **Phase 4**: Integration & Refactoring (wire up DI, SOLID principles) + +3. **Use NEW chat window for EACH phase** to prevent context drift + +### 🎯 AI Agent Directive (Copy to Chat) + +``` +Act as a TDD expert working on the LunaDraw .NET MAUI project. + +ENVIRONMENT: +- Framework: .NET 10, MAUI (net10.0-windows10.0.19041.0) +- Testing: xUnit + Moq + FluentAssertions +- Architecture: MVVM with ReactiveUI +- Dependency Injection: Microsoft.Extensions.DependencyInjection + +INSTRUCTIONS: +1. Do NOT perform "Big Bang" implementations +2. Read DESIGN.md and PLAN.md BEFORE writing code +3. Follow ONLY the current phase in PLAN.md +4. For Phase 1: Scaffold empty classes with NotImplementedException +5. For Phase 2: Write failing tests based on DESIGN.md scenarios +6. For Phase 3: Implement MINIMAL code to pass ONE test at a time +7. For Phase 4: Refactor for SOLID principles and wire up DI + +USE COMPILER FEEDBACK to self-correct type mismatches. +DO NOT add features not defined in DESIGN.md. +DO NOT skip tests or implement multiple features at once. + +Current Phase: [Specify Phase 1, 2, 3, or 4] +``` + +--- + +## Environment Specifications + +### Target Frameworks +- **Primary**: `net10.0-windows10.0.19041.0` +- **Cross-platform**: `net10.0-android36.0`, `net10.0-ios26.0`, `net10.0-maccatalyst26.0` + +### Required Dependencies +```xml + + + + +``` + +### Testing Dependencies +```xml + + + + +``` + +### Build & Test Commands +```bash +# Build project +dotnet build LunaDraw.csproj -f net10.0-windows10.0.19041.0 + +# Run all tests +dotnet test tests/LunaDraw.Tests/LunaDraw.Tests.csproj + +# Run specific test +dotnet test --filter "FullyQualifiedName~TestMethodName" + +# Check code coverage +dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover +``` + +--- + +## Sub-Specs (Technical Documentation) + +For detailed information, see: + +- **[DESIGN.md](DESIGN.md)** - Architecture, interface signatures, and test scenarios +- **[PLAN.md](PLAN.md)** - Phase-by-phase implementation plan with TDD workflow +- **[TEST.md](TEST.md)** - Testing strategy, test templates, and acceptance criteria + +--- + +## Quick Summary + + + + +--- + +## Project Context (for AI Agents) + +### LunaDraw Architecture Patterns + +**MVVM with ReactiveUI**: +- ViewModels inherit from `ReactiveObject` +- Use `[Reactive]` attribute for observable properties +- Commands use `ReactiveCommand` + +**Messaging**: +- Prefer reactive observables over `IMessageBus` +- Use `IMessageBus` ONLY for loosely-coupled broadcast messages +- MessageBus is instance-based (injected via DI), NOT static + +**Dependency Injection**: +- Services registered in `MauiProgram.cs` +- ViewModels typically Singleton or Transient +- Pages typically Transient + +### Coding Standards (from CLAUDE.md) + +- ❌ NO underscores in names +- ❌ NO regions +- ❌ NO abbreviations (use full descriptive names) +- ❌ NO legacy or duplicate code +- βœ… Use SOLID principles (SRP, OCP, LSP, ISP, DIP) +- βœ… Test naming: `Should_{Expected}_{When}_{Condition}` +- βœ… One assertion per line in tests +- βœ… Write test BEFORE fixing bugs (TDD) + +--- + +## Notes + + + +### Context Links +- Previous Phase Chat: [Link to chat session] +- Related Specs: [Links to dependent specs] +- Design Documents: [Links to additional design docs] + +### Open Questions +- [ ] Question 1 +- [ ] Question 2 + +### Constraints +- Must maintain compatibility with existing LunaDraw architecture +- Target audience: Children ages 3-8 (child-friendly UX required) +- Cross-platform: Windows, Android, iOS, MacCatalyst diff --git a/.lean-spec/templates/REFACTORING-SUMMARY.md b/.lean-spec/templates/REFACTORING-SUMMARY.md new file mode 100644 index 0000000..5351633 --- /dev/null +++ b/.lean-spec/templates/REFACTORING-SUMMARY.md @@ -0,0 +1,674 @@ +# Lean-Spec Template Refactoring Summary + +> **Date**: 2025-12-29 +> **Purpose**: Align lean-spec templates with Test-Driven Development (TDD) and Red-Green-Refactor (RGR) methodology for AI-driven development + +--- + +## What Changed? + +The lean-spec templates have been refactored to optimize for **AI agent-driven development** using strict TDD practices. The goal is to eliminate "vibe coding" (hallucinated features) and enforce a disciplined, test-first workflow. + +### Before: Narrative Specs (Human-Oriented) + +**Problem**: Traditional specs assume human developers can "read between the lines" and make reasonable assumptions. AI agents take instructions literally and often: + +- Add features not explicitly defined +- Implement "Big Bang" code without tests +- Mix concerns across multiple phases +- Suffer from context drift in long conversations + +### After: Blueprint Specs (AI-Optimized) + +**Solution**: Refactored specs provide explicit instructions, hard boundaries, and phase-gated workflows: + +- **Goals & Non-Goals**: Prevent scope creep +- **Mermaid Diagrams**: Visualize architecture before coding +- **Interface Signatures**: Define types upfront (compiler-driven) +- **Plain English Scenarios**: Convert directly to test methods +- **Phase-Gated Execution**: Scaffold β†’ Test β†’ Implement β†’ Refactor +- **Chat Session Management**: New window per phase to minimize drift + +--- + +## Document-by-Document Changes + +### 1. DESIGN.md (The Blueprint) + +#### Added Sections + +- **Goals & Non-Goals**: Explicit boundaries (what NOT to build) +- **Visual Architecture**: Mermaid diagrams for component hierarchy +- **Interface & Method Signatures**: Define ALL public APIs with type signatures +- **Plain English Test Scenarios**: Given-When-Then scenarios that become tests +- **Platform-Specific Constraints**: MAUI-specific platform differences + +#### Changed Sections + +- **Technical Approach**: Now includes specific framework versions and patterns +- **Design Decisions**: Added "Trade-offs" subsection for each decision +- **Dependencies**: Split into System, Project, and External dependencies + +#### Example + +**Before**: + +```markdown +## Architecture + +``` + +**After**: + +```markdown +## Goals & Non-Goals + +### Goals +- βœ… Implement pagination (20 items per page) + +### Non-Goals +- ❌ Infinite scroll (use pagination only) + +## Interface & Method Signatures + +```csharp +public interface IExampleService +{ + Task> GetItemsAsync(int offset, int limit); +} +``` + +## Plain English Test Scenarios + +### Scenario: Successful data load +- **Given**: Service returns 5 items +- **When**: ViewModel initializes +- **Then**: Items collection contains 5 elements +``` + +--- + +### 2. PLAN.md (The Assembly Sequence) + +#### Complete Restructure + +**Before**: Generic phase descriptions with tasks + +**After**: Strict 4-phase TDD workflow with gated transitions + +#### New Structure + +1. **TDD Development Process**: Overview of RGR methodology +2. **Phase Transition Requirements**: Explicit gates (e.g., Phase 1 β†’ 2 requires compilation) +3. **Phase 1: The Scaffold**: Empty classes only, `NotImplementedException` +4. **Phase 2: Narrow Test Definition**: Write failing tests (RED state) +5. **Phase 3: The TDD Loop**: Implement one test at a time (GREEN state) +6. **Phase 4: Integration & Refactoring**: Wire up DI, SOLID principles + +#### Added AI Agent Directives + +Each phase includes a copy-paste prompt for AI agents: + +```markdown +**AI Agent Directive**: +> "Act as a C# scaffolding expert. Read DESIGN.md and create ONLY empty classes..." +``` + +#### Example + +**Before**: + +```markdown +### Phase 1: [Name] +**Goal**: +**Tasks**: - [ ] Task 1 +``` + +**After**: + +```markdown +## Phase 1: The Scaffold + +**Goal**: Create the structural skeleton WITHOUT any business logic. + +**AI Agent Directive**: +> "Act as a C# scaffolding expert. Read DESIGN.md and create ONLY empty classes based on signatures. All methods must throw NotImplementedException. Mark classes with [Obsolete('Work in Progress')]. Your ONLY goal is a compiling project." + +**Tasks**: +- [ ] Create all interfaces defined in DESIGN.md +- [ ] Create all ViewModel classes with constructor signatures only +- [ ] Verify project compiles: `dotnet build` + +**Success Criteria**: +- [ ] Project builds successfully with zero errors +- [ ] All methods throw NotImplementedException +``` + +--- + +### 3. TEST.md (The Inspection Criteria) + +#### Complete Restructure + +**Before**: Generic test categories (unit, integration, performance) + +**After**: Scenario-based test templates derived from DESIGN.md + +#### New Structure + +1. **Testing Strategy**: TDD focus with test pyramid +2. **Unit Tests (Red-Green-Refactor)**: Scenario-based test structure with TODO comments +3. **Integration Tests**: End-to-end flow validation +4. **Performance Tests**: Benchmark thresholds +5. **Security Tests**: Input validation examples +6. **Test Data**: TestDataBuilder pattern + +#### Added Concrete Test Examples + +Each scenario from DESIGN.md has a corresponding test template: + +```csharp +// TODO: Implement test for "User submits empty form" scenario +// Given: The form is displayed +// When: User clicks "Submit" with an empty required field +// Then: An error message appears and form is not submitted + +[Fact] +public void Should_Set_Error_Message_When_Submit_With_Empty_Field() +{ + // Arrange + var mockService = new Mock(); + var viewModel = new ExampleViewModel(mockService.Object); + viewModel.SearchText = string.Empty; + + // Act + viewModel.SubmitCommand.Execute().Subscribe(); + + // Assert + viewModel.ErrorMessage.Should().NotBeNullOrEmpty(); +} +``` + +#### Example + +**Before**: + +```markdown +## Unit Tests +**Key test cases**: +- [ ] Test case 1 +- [ ] Test case 2 +``` + +**After**: + +```markdown +## Unit Tests (Red-Green-Refactor) + +### Test Naming Convention +Use LunaDraw standard: `Should_{Expected}_{When}_{Condition}` + +### Scenario-Based Test Structure + +#### Scenario: User submits empty form +[Complete test code example with Arrange-Act-Assert] + +### Test Coverage Requirements +- βœ… Happy path +- βœ… Edge cases +- βœ… Error handling +- βœ… State transitions +``` + +--- + +### 4. README.md (The Route Map) + +#### Added AI-Centric Sections + +**Before**: Generic overview for human readers + +**After**: Entry point for AI agents with explicit instructions + +#### New Sections + +1. **AI Development Flow (Route Map)**: Step-by-step guide for agents +2. **AI Agent Directive**: Copy-paste prompt for chat sessions +3. **Environment Specifications**: Exact frameworks, dependencies, build commands +4. **Project Context (for AI Agents)**: LunaDraw-specific patterns and coding standards + +#### Example + +**Before**: + +```markdown +## Overview + + +## Sub-Specs +- DESIGN.md +- PLAN.md +- TEST.md +``` + +**After**: + +```markdown +## AI Development Flow (Route Map) + +**CRITICAL**: This spec follows TDD with Red-Green-Refactor methodology. + +### πŸ“‹ Entry Point for AI Agents + +Before starting implementation, AI agents MUST: + +1. **Read in order**: + - βœ… README.md (context) + - βœ… DESIGN.md (architecture) + - βœ… PLAN.md (phases) + - βœ… TEST.md (test templates) + +2. **Execute phases sequentially**: + - Phase 1: Scaffold + - Phase 2: Test Definition + - Phase 3: TDD Loop + - Phase 4: Integration + +3. **Use NEW chat window for EACH phase** + +### 🎯 AI Agent Directive (Copy to Chat) + +``` +Act as a TDD expert working on the LunaDraw .NET MAUI project. + +ENVIRONMENT: +- Framework: .NET 10, MAUI +- Testing: xUnit + Moq + FluentAssertions + +INSTRUCTIONS: +1. Do NOT perform "Big Bang" implementations +2. Read DESIGN.md and PLAN.md BEFORE writing code +3. Follow ONLY the current phase in PLAN.md +... + +Current Phase: [Specify Phase 1, 2, 3, or 4] +``` +``` + +--- + +## New Documents Created + +### 5. TDD-WORKFLOW-GUIDE.md + +**Purpose**: Comprehensive guide explaining the refactored workflow + +**Contents**: + +- Overview of TDD philosophy +- Document structure breakdown +- Four-phase workflow walkthrough +- Chat session management strategy +- Anti-patterns to avoid (e.g., "vibe coding") +- Complete example walkthrough (Search feature) + +--- + +## Key Improvements + +### 1. Prevents "Vibe Coding" + +**Before**: AI adds features not explicitly defined + +```csharp +// ❌ AI hallucinated pagination when only search was specified +public async Task> SearchAsync(string query, int page, int pageSize) +``` + +**After**: Non-Goals section prevents scope creep + +```markdown +### Non-Goals +- ❌ Pagination (implement in future spec) +``` + +--- + +### 2. Enforces Test-First Development + +**Before**: AI writes code first, then adds tests + +**After**: Phase 2 (Test Definition) MUST complete before Phase 3 (Implementation) + +``` +Phase 2 β†’ Phase 3 Gate: At least one failing test must exist (RED state) +``` + +--- + +### 3. Compiler-Driven Self-Correction + +**Before**: AI guesses types and parameters + +**After**: Interface signatures defined upfront in DESIGN.md + +```csharp +// Defined in DESIGN.md before any code is written +public interface ISearchService +{ + Task> SearchAsync(string query); +} +``` + +--- + +### 4. Atomic Progress Tracking + +**Before**: Unclear when a phase is "done" + +**After**: Explicit success criteria for each phase + +```markdown +**Success Criteria**: +- [ ] All tests FAIL (RED state) +- [ ] No implementation code written yet +``` + +--- + +### 5. Context Drift Prevention + +**Before**: Single long conversation loses coherence + +**After**: NEW chat window per phase with linked sessions + +```markdown +## Notes +### Context Links +- Phase 1 Chat: [URL] +- Phase 2 Chat: [URL] +``` + +--- + +## How to Use the Refactored Templates + +### Step 1: Create a New Spec + +```bash +lean-spec create "feature-name" +``` + +This generates: + +``` +specs/NNN-feature-name/ +β”œβ”€β”€ README.md # Route map for AI agents +β”œβ”€β”€ DESIGN.md # Architectural blueprint +β”œβ”€β”€ PLAN.md # Phase-by-phase plan +└── TEST.md # Test templates +``` + +--- + +### Step 2: Fill Out DESIGN.md + +1. Define **Goals & Non-Goals** (explicit boundaries) +2. Create **Mermaid diagram** (component hierarchy) +3. Define **Interface Signatures** (all public APIs) +4. Write **Plain English Test Scenarios** (Given-When-Then) +5. Document **Technical Approach** (frameworks, patterns) + +--- + +### Step 3: Execute Phases (One Chat Per Phase) + +#### Phase 1: Scaffold + +**Open Chat β†’ Paste AI Agent Directive from README.md** + +``` +Current Phase: Phase 1 (Scaffold) + +Read DESIGN.md and create empty classes. +All methods throw NotImplementedException. +Verify: dotnet build +``` + +**Exit Criteria**: βœ… Project compiles + +--- + +#### Phase 2: Test Definition + +**Open NEW Chat β†’ Paste AI Agent Directive** + +``` +Current Phase: Phase 2 (Test Definition) +Previous Phase: [Link to Phase 1 chat] + +Read DESIGN.md scenarios. +Create xUnit tests for each scenario. +Do NOT implement code to make tests pass. +Verify: dotnet test (expect failures) +``` + +**Exit Criteria**: βœ… All tests FAIL (RED) + +--- + +#### Phase 3: TDD Loop + +**Open NEW Chat β†’ Paste AI Agent Directive** + +``` +Current Phase: Phase 3 (TDD Loop - Test 1) +Previous Phase: [Link to Phase 2 chat] + +Implement MINIMAL code to pass the FIRST failing test ONLY. +Wait for confirmation before proceeding to next test. +``` + +**Repeat** for each test: + +1. RED: Identify failing test +2. GREEN: Implement minimal code +3. REFACTOR: Clean up +4. COMMIT: `git commit -m "test: pass TestName"` + +**Exit Criteria**: βœ… All tests PASS (GREEN) + +--- + +#### Phase 4: Integration & Refactoring + +**Open NEW Chat β†’ Paste AI Agent Directive** + +``` +Current Phase: Phase 4 (Integration) +Previous Phase: [Link to Phase 3 chat] + +Review code for SOLID violations. +Register services in MauiProgram.cs. +Add integration tests. +Remove [Obsolete] attributes. +``` + +**Exit Criteria**: βœ… Production-ready code + +--- + +## Migration Guide (Existing Specs) + +If you have existing lean-spec documents, here's how to migrate them: + +### 1. Backup Existing Specs + +```bash +cp -r specs/ specs-backup/ +``` + +### 2. Update DESIGN.md + +Add these sections: + +- `## Goals & Non-Goals` +- `## Visual Architecture` (Mermaid diagram) +- `## Interface & Method Signatures` +- `## Plain English Test Scenarios` + +### 3. Restructure PLAN.md + +Replace generic phases with: + +- `## Phase 1: The Scaffold` +- `## Phase 2: Narrow Test Definition` +- `## Phase 3: The TDD Loop` +- `## Phase 4: Integration & Refactoring` + +Add AI Agent Directives to each phase. + +### 4. Expand TEST.md + +Add scenario-based test templates with TODO comments. + +### 5. Enhance README.md + +Add: + +- `## AI Development Flow (Route Map)` +- `## AI Agent Directive` +- `## Environment Specifications` +- `## Project Context (for AI Agents)` + +--- + +## Benefits Summary + +| Aspect | Before (Narrative) | After (Blueprint) | +|--------|-------------------|-------------------| +| **Scope Control** | Vague "high-level design" | Explicit Goals & Non-Goals | +| **Architecture** | Prose descriptions | Mermaid diagrams + signatures | +| **Test Strategy** | Generic test categories | Scenario-based test templates | +| **AI Guidance** | Implicit expectations | Explicit AI Agent Directives | +| **Phase Gating** | Unclear transitions | Explicit success criteria | +| **Context Management** | Single long conversation | New chat per phase | +| **Type Safety** | AI guesses types | Signatures defined upfront | +| **Progress Tracking** | Subjective completion | RED β†’ GREEN β†’ REFACTOR | + +--- + +## Example Comparison + +### Before: Generic Spec + +```markdown +# DESIGN.md + +## Architecture +The system will use MVVM pattern with ViewModels and Services. + +## Technical Approach +We'll use .NET MAUI and ReactiveUI. +``` + +### After: Blueprint Spec + +```markdown +# DESIGN.md + +## Goals & Non-Goals + +### Goals +- βœ… Implement search with 500ms debounce +- βœ… Display results in scrollable list + +### Non-Goals +- ❌ Infinite scroll +- ❌ Advanced filtering + +## Visual Architecture + +```mermaid +graph TD + A[SearchPage] --> B[SearchViewModel] + B --> C[ISearchService] + C --> D[ItemRepository] +``` + +## Interface & Method Signatures + +```csharp +public interface ISearchService +{ + Task> SearchAsync(string query); +} +``` + +## Plain English Test Scenarios + +### Scenario: Search returns results +- **Given**: Service returns 3 items for "test" +- **When**: User types "test" +- **Then**: Results collection has 3 items +``` + +--- + +## FAQs + +### Q: Do I need to use new chat windows for every phase? + +**A**: Yes, this is critical to prevent context drift. AI agents lose coherence in long conversations. + +--- + +### Q: Can I skip Phase 1 (Scaffold) and go straight to implementation? + +**A**: No. The scaffold ensures all types are defined upfront, preventing compilation-driven hallucinations. + +--- + +### Q: What if my feature is too simple for 4 phases? + +**A**: You can combine Phase 1 and Phase 2 for trivial features, but NEVER skip test-first development (Phase 2 before Phase 3). + +--- + +### Q: How do I link previous chat sessions? + +**A**: Add a "Context Links" section in README.md Notes: + +```markdown +### Context Links +- Phase 1 Chat: https://chatgpt.com/c/abc123 +- Phase 2 Chat: https://chatgpt.com/c/def456 +``` + +--- + +## Next Steps + +1. **Review TDD-WORKFLOW-GUIDE.md** for detailed workflow explanation +2. **Create a test spec** using the new templates +3. **Practice the 4-phase workflow** with a simple feature +4. **Update existing specs** (optional) to use new format + +--- + +## Feedback & Iteration + +This refactoring is version 1.0. As you use these templates, please document: + +- Bottlenecks or friction points +- Unclear instructions for AI agents +- Additional anti-patterns discovered +- Successful patterns to amplify + +Update this document and TDD-WORKFLOW-GUIDE.md as you refine the process. + +--- + +**Version**: 1.0 +**Last Updated**: 2025-12-29 +**Author**: AI-assisted refactoring based on TDD best practices diff --git a/.lean-spec/templates/TDD-WORKFLOW-GUIDE.md b/.lean-spec/templates/TDD-WORKFLOW-GUIDE.md new file mode 100644 index 0000000..7588344 --- /dev/null +++ b/.lean-spec/templates/TDD-WORKFLOW-GUIDE.md @@ -0,0 +1,832 @@ +# TDD Workflow Guide for AI-Driven Development + +> **Purpose**: This guide explains how to use the lean-spec templates for Test-Driven Development (TDD) with AI agents following Red-Green-Refactor (RGR) methodology. + +--- + +## Table of Contents + +1. [Overview](#overview) +2. [The TDD Philosophy](#the-tdd-philosophy) +3. [Document Structure](#document-structure) +4. [Workflow: Four Phases](#workflow-four-phases) +5. [Chat Session Management](#chat-session-management) +6. [Anti-Patterns to Avoid](#anti-patterns-to-avoid) +7. [Example Walkthrough](#example-walkthrough) + +--- + +## Overview + +Traditional spec documents are written for human developers who can "read between the lines" and make reasonable assumptions. AI agents, however, require **explicit instructions** to avoid "vibe coding" (hallucinating features or guessing implementations). + +These templates refactor the spec-driven development workflow into a **blueprint-first approach**: + +- **DESIGN.md**: The architectural blueprint (what to build, not how) +- **PLAN.md**: The assembly sequence (phase-by-phase execution) +- **TEST.md**: The inspection criteria (test templates and acceptance) +- **README.md**: The route map (entry point for AI agents) + +### Analogy: Building a Skyscraper + +| Traditional Spec | TDD Spec (Refactored) | +|------------------|------------------------| +| "Build a house" (vague) | Structural blueprints (DESIGN.md) | +| Developer figures it out | Assembly sequence (PLAN.md) | +| Tests written after | Inspection criteria (TEST.md) | +| Unclear starting point | Route map (README.md) | + +--- + +## The TDD Philosophy + +### Red-Green-Refactor (RGR) Cycle + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ RED β”‚ Write a failing test +β”‚ (Test First)β”‚ +β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ GREEN β”‚ Write minimal code to pass +β”‚ (Make it Work)β”‚ +β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ REFACTOR β”‚ Clean up code (SOLID, DRY) +β”‚ (Make it Right)β”‚ +β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + └──────► Repeat for next test +``` + +### Why TDD for AI Agents? + +1. **Prevents "Big Bang" implementations**: AI agents tend to write large chunks of code at once, leading to brittle, untestable code. +2. **Compiler-driven self-correction**: C# is strongly typed. Compiler errors guide the AI to fix type mismatches. +3. **Atomic progress**: Each test is a measurable milestone (RED β†’ GREEN). +4. **Prevents hallucinated features**: Tests define the ONLY features to implement. + +--- + +## Document Structure + +### DESIGN.md (The Blueprint) + +**Purpose**: Define the architecture, interfaces, and test scenarios WITHOUT implementation details. + +**Key Sections**: + +1. **Goals & Non-Goals**: Explicit boundaries (prevents scope creep) +2. **Visual Architecture**: Mermaid diagrams for component hierarchy +3. **Interface & Method Signatures**: Define ALL public APIs upfront +4. **Plain English Test Scenarios**: User stories that become tests +5. **Technical Approach**: Frameworks, patterns, libraries +6. **Design Decisions**: Rationale for key choices (with trade-offs) + +**Example**: + +```markdown +## Goals & Non-Goals + +### Goals +- βœ… Implement search functionality with debounce (500ms) +- βœ… Paginate results (20 items per page) + +### Non-Goals +- ❌ Infinite scroll (use pagination only) +- ❌ Advanced filtering (only basic search) +``` + +--- + +### PLAN.md (The Assembly Sequence) + +**Purpose**: Break implementation into 4 sequential phases, each with gated transitions. + +**Phases**: + +1. **Phase 1: The Scaffold** + - Goal: Create empty classes/interfaces (compile-only) + - Success: Project builds with `NotImplementedException` + +2. **Phase 2: Narrow Test Definition** + - Goal: Write failing tests (RED state) + - Success: All tests fail, no implementation code + +3. **Phase 3: The TDD Loop** + - Goal: Implement one test at a time (GREEN state) + - Success: All tests pass, no `NotImplementedException` + +4. **Phase 4: Integration & Refactoring** + - Goal: Wire up DI, refactor for SOLID + - Success: Integration tests pass, production-ready + +**Phase Transition Gates**: + +- **Phase 1 β†’ 2**: Must compile +- **Phase 2 β†’ 3**: Must have failing tests (RED) +- **Phase 3 β†’ 4**: All tests must pass (GREEN) +- **Phase 4 β†’ Done**: Code review complete + +--- + +### TEST.md (The Inspection Criteria) + +**Purpose**: Provide test templates and scenarios derived from DESIGN.md. + +**Key Sections**: + +1. **Unit Tests**: Scenario-based tests with TODO comments +2. **Integration Tests**: End-to-end flow validation +3. **Performance Tests**: Benchmark thresholds +4. **Security Tests**: Input validation and sanitization +5. **Manual Testing**: UI/UX verification checklist + +**Example Test Template**: + +```csharp +// TODO: Implement test for "User submits empty form" scenario +// Given: The form is displayed +// When: User clicks "Submit" with an empty required field +// Then: An error message appears and form is not submitted + +[Fact] +public void Should_Set_Error_Message_When_Submit_With_Empty_Field() +{ + // Arrange + var mockService = new Mock(); + var viewModel = new ExampleViewModel(mockService.Object); + viewModel.SearchText = string.Empty; + + // Act + viewModel.SubmitCommand.Execute().Subscribe(); + + // Assert + viewModel.ErrorMessage.Should().NotBeNullOrEmpty(); +} +``` + +--- + +### README.md (The Route Map) + +**Purpose**: Serve as the entry point for AI agents with explicit instructions. + +**Key Sections**: + +1. **AI Development Flow**: Step-by-step guide for agents +2. **AI Agent Directive**: Copy-paste prompt for chat sessions +3. **Environment Specifications**: Frameworks, dependencies, build commands +4. **Project Context**: LunaDraw-specific patterns and coding standards + +**Example AI Directive**: + +``` +Act as a TDD expert working on the LunaDraw .NET MAUI project. + +Current Phase: Phase 1 (Scaffold) + +Read DESIGN.md and create ONLY empty classes based on the signatures. +All methods must throw NotImplementedException. +Mark classes with [Obsolete("Work in Progress")]. +Your ONLY goal is a compiling project. +``` + +--- + +## Workflow: Four Phases + +### Phase 1: The Scaffold (Empty Classes) + +**Chat Prompt**: + +``` +You are a C# scaffolding expert. Read DESIGN.md and create empty classes/interfaces. +All methods must throw NotImplementedException. +Add [Obsolete("Work in Progress")] to all classes. +Verify the project compiles: dotnet build +``` + +**Tasks**: + +1. Create all interfaces from DESIGN.md +2. Create all classes with constructor signatures only +3. Stub all methods with `throw new NotImplementedException();` +4. Run `dotnet build` (must succeed) + +**Exit Criteria**: βœ… Project builds with zero errors + +--- + +### Phase 2: Narrow Test Definition (Write Failing Tests) + +**Chat Prompt**: + +``` +You are a TDD expert. Read the "Plain English Test Scenarios" from DESIGN.md. +For each scenario, create a corresponding xUnit test method. +Use naming: Should_{Expected}_{When}_{Condition} +Write ONLY the testβ€”do NOT implement the code to make it pass. +Use Moq to mock dependencies. +Run dotnet test (expect all to FAILβ€”this is RED state). +``` + +**Tasks**: + +1. Create test project: `tests/{name}.Tests/` +2. Add xUnit, Moq, FluentAssertions packages +3. Translate each Plain English Scenario into a test method +4. Write assertions FIRST +5. Run `dotnet test` (expect failures) + +**Exit Criteria**: βœ… All tests FAIL (RED state) + +--- + +### Phase 3: The TDD Loop (Implement One Test at a Time) + +**Chat Prompt**: + +``` +You are a TDD practitioner. Read TEST.md for the first failing test. +Write the MINIMAL code to make ONLY that test pass. +Do NOT implement features not covered by tests. +After the test passes, wait for confirmation before proceeding to the next test. +Use compiler errors to self-correct type mismatches. +``` + +**Process** (Repeat for EACH test): + +1. **RED**: Run `dotnet test`, identify ONE failing test +2. **GREEN**: Write minimal code to pass that test +3. **VERIFY**: Run `dotnet test --filter "FullyQualifiedName~TestMethodName"` +4. **REFACTOR**: Clean up code (extract methods, rename variables) +5. **COMMIT**: `git commit -m "test: pass TestMethodName"` +6. **REPEAT**: Move to next failing test + +**Exit Criteria**: βœ… ALL tests pass (GREEN state) + +--- + +### Phase 4: Integration & Refactoring (Wire Up DI, SOLID) + +**Chat Prompt**: + +``` +You are a C# architect. Review the code for SOLID violations and code smells. +Ensure all dependencies are registered in MauiProgram.cs. +Add integration tests that verify end-to-end flows. +Do NOT add featuresβ€”only improve code structure. +Remove [Obsolete] attributes. +``` + +**Tasks**: + +1. Register all services in `MauiProgram.cs` DI container +2. Add integration tests (ViewModel β†’ Service β†’ Data) +3. Refactor for SOLID principles +4. Add XML documentation comments +5. Run `dotnet test` (all tests must pass) + +**Exit Criteria**: βœ… Production-ready code + +--- + +## Chat Session Management + +### Why New Chat Windows per Phase? + +**Problem**: AI agents suffer from **context drift**β€”as conversations grow longer, the agent may: + +- Forget earlier instructions +- Introduce inconsistencies +- Mix concerns from different phases + +**Solution**: Use a **NEW chat window for EACH phase** to minimize context drift. + +### Phase Transition Checklist + +Before starting a new phase: + +1. βœ… Verify current phase exit criteria met +2. βœ… Open NEW chat window +3. βœ… Copy AI Agent Directive from README.md +4. βœ… Update `Current Phase: [Phase X]` +5. βœ… Link to previous phase's chat session in README.md "Notes" section + +**Example README.md Notes Section**: + +```markdown +## Notes + +### Context Links +- Phase 1 Chat: https://chatgpt.com/c/abc123 +- Phase 2 Chat: https://chatgpt.com/c/def456 +- Phase 3 Chat: https://chatgpt.com/c/ghi789 +``` + +--- + +## Anti-Patterns to Avoid + +### ❌ "Vibe Coding" (Hallucinated Features) + +**Symptom**: AI adds features not defined in DESIGN.md + +**Example**: + +```csharp +// ❌ BAD: AI added pagination when only search was specified +public async Task> SearchAsync(string query, int page, int pageSize) +``` + +**Fix**: Add to DESIGN.md Non-Goals: + +```markdown +### Non-Goals +- ❌ Pagination (implement in future spec) +``` + +--- + +### ❌ "Big Bang" Implementation + +**Symptom**: AI writes all code at once instead of one test at a time + +**Example**: + +``` +// ❌ BAD: AI implements 10 methods in one go +Implemented SearchViewModel: 10 methods, 300 lines of code +``` + +**Fix**: Use Phase 3 chat prompt: + +``` +Write MINIMAL code to pass ONLY the first failing test. +Wait for my confirmation before proceeding. +``` + +--- + +### ❌ Tests Written After Implementation + +**Symptom**: AI writes code first, then adds tests to match + +**Example**: + +``` +1. Implemented SearchViewModel.cs +2. Added tests for SearchViewModel +``` + +**Fix**: Enforce Phase 2 (Test Definition) BEFORE Phase 3 (Implementation): + +``` +Do NOT write ANY implementation code in Phase 2. +Your ONLY goal is failing tests (RED state). +``` + +--- + +### ❌ Skipping Scaffold Phase + +**Symptom**: AI jumps straight to implementation without defining interfaces + +**Example**: + +```csharp +// ❌ BAD: No interface defined, direct implementation +public class SearchService +{ + public async Task Search(string query) { /* code */ } +} +``` + +**Fix**: Phase 1 must create interfaces FIRST: + +```csharp +// βœ… GOOD: Interface defined in Phase 1 +public interface ISearchService +{ + Task> SearchAsync(string query); +} + +[Obsolete("Work in Progress")] +public class SearchService : ISearchService +{ + public Task> SearchAsync(string query) + { + throw new NotImplementedException(); + } +} +``` + +--- + +## Example Walkthrough + +### Scenario: Implement a Search Feature + +#### Step 1: Create Spec Documents + +```bash +# Create new spec +lean-spec create "search-feature" + +# Files created: +# 001-search-feature/README.md +# 001-search-feature/DESIGN.md +# 001-search-feature/PLAN.md +# 001-search-feature/TEST.md +``` + +--- + +#### Step 2: Fill Out DESIGN.md + +```markdown +## Goals & Non-Goals + +### Goals +- βœ… Implement search with debounce (500ms) +- βœ… Display search results in a list + +### Non-Goals +- ❌ Advanced filtering +- ❌ Pagination + +## Interface & Method Signatures + +### Interface: ISearchService + +```csharp +public interface ISearchService +{ + Task> SearchAsync(string query); +} +``` + +### Class: SearchViewModel + +```csharp +public class SearchViewModel : ReactiveObject +{ + [Reactive] public string SearchText { get; set; } + [Reactive] public ObservableCollection Results { get; set; } + + public ReactiveCommand> SearchCommand { get; } + + public SearchViewModel(ISearchService searchService); +} +``` + +## Plain English Test Scenarios + +### Scenario: Search returns results +- **Given**: Service returns 3 items for query "test" +- **When**: User types "test" in search box +- **Then**: Results collection contains 3 items + +### Scenario: Empty search clears results +- **Given**: Results collection has items +- **When**: User clears search text +- **Then**: Results collection is empty +``` + +--- + +#### Step 3: Phase 1 - Scaffold + +**Chat Prompt**: + +``` +Act as a C# scaffolding expert. + +Read the spec at: 001-search-feature/DESIGN.md + +Create ONLY empty classes and interfaces based on the signatures defined. +All methods must throw NotImplementedException. +Add [Obsolete("Work in Progress")] to all classes. + +Current Phase: Phase 1 (Scaffold) +``` + +**AI Output**: + +```csharp +// ISearchService.cs +public interface ISearchService +{ + Task> SearchAsync(string query); +} + +// SearchService.cs +[Obsolete("Work in Progress")] +public class SearchService : ISearchService +{ + public Task> SearchAsync(string query) + { + throw new NotImplementedException(); + } +} + +// SearchViewModel.cs +[Obsolete("Work in Progress")] +public class SearchViewModel : ReactiveObject +{ + [Reactive] public string SearchText { get; set; } + [Reactive] public ObservableCollection Results { get; set; } + + public ReactiveCommand> SearchCommand { get; } + + public SearchViewModel(ISearchService searchService) + { + throw new NotImplementedException(); + } +} +``` + +**Verify**: + +```bash +dotnet build +# Build succeeded. 0 Warning(s). 0 Error(s). +``` + +βœ… **Phase 1 Complete**: Project compiles + +--- + +#### Step 4: Phase 2 - Narrow Test Definition + +**Open NEW Chat Window** + +**Chat Prompt**: + +``` +Act as a TDD expert. + +Read the spec at: 001-search-feature/DESIGN.md +Read the test template at: 001-search-feature/TEST.md + +For each "Plain English Test Scenario", create a corresponding xUnit test. +Use naming: Should_{Expected}_{When}_{Condition} +Write ONLY the testβ€”do NOT implement the code to make it pass. +Use Moq to mock ISearchService. + +Current Phase: Phase 2 (Narrow Test Definition) +Previous Phase: [Link to Phase 1 chat] +``` + +**AI Output**: + +```csharp +// SearchViewModelTests.cs +public class SearchViewModelTests +{ + [Fact] + public async Task Should_Populate_Results_When_Search_Returns_Items() + { + // Arrange + var mockService = new Mock(); + var testItems = new List + { + new Item { Id = 1, Name = "Test 1" }, + new Item { Id = 2, Name = "Test 2" }, + new Item { Id = 3, Name = "Test 3" } + }; + mockService.Setup(s => s.SearchAsync("test")) + .ReturnsAsync(testItems); + + var viewModel = new SearchViewModel(mockService.Object); + + // Act + await viewModel.SearchCommand.Execute("test"); + + // Assert + viewModel.Results.Should().HaveCount(3); + viewModel.Results.Should().BeEquivalentTo(testItems); + } + + [Fact] + public void Should_Clear_Results_When_Search_Text_Is_Empty() + { + // Arrange + var mockService = new Mock(); + var viewModel = new SearchViewModel(mockService.Object); + viewModel.Results.Add(new Item { Id = 1 }); + + // Act + viewModel.SearchText = string.Empty; + + // Assert + viewModel.Results.Should().BeEmpty(); + } +} +``` + +**Verify**: + +```bash +dotnet test +# Test Run Failed. +# Total tests: 2 +# Passed: 0 +# Failed: 2 +``` + +βœ… **Phase 2 Complete**: All tests FAIL (RED state) + +--- + +#### Step 5: Phase 3 - TDD Loop + +**Open NEW Chat Window** + +**Chat Prompt**: + +``` +Act as a TDD practitioner. + +Read the spec at: 001-search-feature/TEST.md + +Implement MINIMAL code to pass the FIRST failing test ONLY: +- Should_Populate_Results_When_Search_Returns_Items + +Do NOT implement the second test yet. +Wait for my confirmation before proceeding. + +Current Phase: Phase 3 (TDD Loop - Test 1) +Previous Phase: [Link to Phase 2 chat] +``` + +**AI Output**: + +```csharp +// SearchViewModel.cs +public class SearchViewModel : ReactiveObject +{ + private readonly ISearchService searchService; + + [Reactive] public string SearchText { get; set; } + [Reactive] public ObservableCollection Results { get; set; } + + public ReactiveCommand> SearchCommand { get; } + + public SearchViewModel(ISearchService searchService) + { + this.searchService = searchService; + Results = new ObservableCollection(); + + SearchCommand = ReactiveCommand.CreateFromTask>( + async query => + { + var items = await searchService.SearchAsync(query); + Results.Clear(); + foreach (var item in items) + { + Results.Add(item); + } + return items; + }); + } +} +``` + +**Verify**: + +```bash +dotnet test --filter "Should_Populate_Results_When_Search_Returns_Items" +# Test Run Successful. +# Total tests: 1 +# Passed: 1 +``` + +βœ… **Test 1 GREEN** + +**Commit**: + +```bash +git add . +git commit -m "test: pass Should_Populate_Results_When_Search_Returns_Items" +``` + +**Continue to Test 2** (repeat RED-GREEN-REFACTOR for remaining tests) + +--- + +#### Step 6: Phase 4 - Integration & Refactoring + +**Open NEW Chat Window** + +**Chat Prompt**: + +``` +Act as a C# architect. + +Review the implemented SearchViewModel for SOLID violations. +Ensure ISearchService is registered in MauiProgram.cs. +Add integration test that verifies end-to-end flow. +Remove [Obsolete] attributes. + +Current Phase: Phase 4 (Integration & Refactoring) +Previous Phase: [Link to Phase 3 chat] +``` + +**AI Output**: + +```csharp +// MauiProgram.cs +public static class MauiProgram +{ + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder.Services.AddSingleton(); + builder.Services.AddTransient(); + // ... + return builder.Build(); + } +} + +// Integration test +[Fact] +public async Task Should_Load_And_Display_Search_Results_End_To_End() +{ + var mockRepository = new Mock(); + mockRepository.Setup(r => r.SearchAsync("test")) + .ReturnsAsync(new List { new Item { Id = 1 } }); + + var service = new SearchService(mockRepository.Object); + var viewModel = new SearchViewModel(service); + + await viewModel.SearchCommand.Execute("test"); + + viewModel.Results.Should().HaveCount(1); +} +``` + +**Verify**: + +```bash +dotnet test +# Test Run Successful. +# Total tests: 3 +# Passed: 3 +``` + +βœ… **Phase 4 Complete**: Production-ready + +--- + +## Summary + +By refactoring the lean-spec templates for TDD: + +1. **DESIGN.md** becomes the architectural blueprint with hard boundaries +2. **PLAN.md** enforces scaffold-first, phase-gated development +3. **TEST.md** provides scenario-based test templates with TODO comments +4. **README.md** serves as the AI-centric route map + +This approach **prevents "vibe coding"** and ensures AI agents follow a disciplined, compiler-driven, test-first workflow. + +--- + +## Quick Reference + +### Phase Checklist + +- [ ] **Phase 1**: Scaffold empty classes β†’ βœ… Compiles +- [ ] **Phase 2**: Write failing tests β†’ βœ… RED state +- [ ] **Phase 3**: Implement one test at a time β†’ βœ… GREEN state +- [ ] **Phase 4**: Refactor & integrate β†’ βœ… Production-ready + +### Chat Session Strategy + +- βœ… Use NEW chat per phase +- βœ… Copy AI Agent Directive from README.md +- βœ… Link previous phase's chat in README.md Notes +- βœ… Verify exit criteria before transitioning + +### AI Agent Directive Template + +``` +Act as a [Role: scaffolding expert / TDD expert / architect]. + +Read: [DESIGN.md / PLAN.md / TEST.md] + +Current Phase: [Phase 1/2/3/4] + +Instructions: +[Phase-specific instructions from PLAN.md] +``` + +--- + +**Last Updated**: 2025-12-29 +**Version**: 1.0 diff --git a/.lean-spec/templates/TEST.md b/.lean-spec/templates/TEST.md new file mode 100644 index 0000000..053b730 --- /dev/null +++ b/.lean-spec/templates/TEST.md @@ -0,0 +1,338 @@ +# Test: {name} + +> Part of [{name}](README.md) + +## Testing Strategy + +This spec follows **Test-Driven Development (TDD)** with atomic, scenario-based test files. Each test scenario from [DESIGN.md](DESIGN.md) becomes a discrete test method. + +### Test Pyramid + +``` + /\ + / \ UI/E2E Tests (Manual) + /____\ + / \ Integration Tests +/________\ Unit Tests (TDD Focus) +``` + +**Primary Focus**: Unit tests for ViewModels and Services using xUnit + Moq + FluentAssertions + +--- + +## Unit Tests (Red-Green-Refactor) + +### Test File Organization + +Create one test file per production class: + +- `{ViewModelName}Tests.cs` - ViewModel behavior tests +- `{ServiceName}Tests.cs` - Service logic tests +- `{ExtensionName}Tests.cs` - Extension method tests + +### Test Naming Convention + +Use LunaDraw standard: `Should_{Expected}_{When}_{Condition}` + +**Examples**: +- `Should_Set_Error_Message_When_Submit_With_Empty_Field` +- `Should_Load_Items_When_Service_Returns_Data` +- `Should_Navigate_To_Detail_When_Item_Selected` + +### Scenario-Based Test Structure + +For EACH scenario in [DESIGN.md](DESIGN.md#plain-english-test-scenarios), create a corresponding test method: + +#### Scenario: User submits empty form + +```csharp +// TODO: Implement test for "User submits empty form" scenario +// Given: The form is displayed +// When: User clicks "Submit" with an empty required field +// Then: An error message appears and form is not submitted + +[Fact] +public void Should_Set_Error_Message_When_Submit_With_Empty_Field() +{ + // Arrange + var mockService = new Mock(); + var viewModel = new ExampleViewModel(mockService.Object); + viewModel.SearchText = string.Empty; // Empty field + + // Act + viewModel.SubmitCommand.Execute().Subscribe(); + + // Assert + viewModel.ErrorMessage.Should().NotBeNullOrEmpty(); + viewModel.IsSubmitting.Should().BeFalse(); + mockService.Verify(s => s.SubmitAsync(It.IsAny()), Times.Never); +} +``` + +#### Scenario: Successful data load + +```csharp +// TODO: Implement test for "Successful data load" scenario +// Given: The service returns 5 items +// When: The ViewModel initializes +// Then: The Items collection contains 5 elements + +[Fact] +public async Task Should_Load_Items_When_Service_Returns_Data() +{ + // Arrange + var mockService = new Mock(); + var testItems = new List + { + new Item { Id = 1, Name = "Item 1" }, + new Item { Id = 2, Name = "Item 2" }, + new Item { Id = 3, Name = "Item 3" }, + new Item { Id = 4, Name = "Item 4" }, + new Item { Id = 5, Name = "Item 5" } + }; + mockService.Setup(s => s.GetItemsAsync(0, 10)) + .ReturnsAsync(testItems); + + var viewModel = new ExampleViewModel(mockService.Object); + + // Act + await viewModel.LoadCommand.Execute(); + + // Assert + viewModel.Items.Should().HaveCount(5); + viewModel.Items.Should().BeEquivalentTo(testItems); + viewModel.IsLoading.Should().BeFalse(); +} +``` + +#### Scenario: Navigation on item selection + +```csharp +// TODO: Implement test for "Navigation on item selection" scenario +// Given: A list of items is displayed +// When: User taps an item +// Then: Navigation occurs to the detail page with the selected item + +[Fact] +public void Should_Navigate_To_Detail_When_Item_Selected() +{ + // Arrange + var mockService = new Mock(); + var mockNavigationService = new Mock(); + var viewModel = new ExampleViewModel(mockService.Object, mockNavigationService.Object); + var selectedItem = new Item { Id = 1, Name = "Test Item" }; + + // Act + viewModel.SelectItemCommand.Execute(selectedItem).Subscribe(); + + // Assert + mockNavigationService.Verify( + n => n.NavigateToAsync("DetailPage", It.Is(i => i.Id == 1)), + Times.Once + ); +} +``` + +### Test Coverage Requirements + +**Minimum Coverage**: 80% for all ViewModels and Services + +**Required Test Types**: +- βœ… Happy path (successful execution) +- βœ… Edge cases (null, empty, boundary values) +- βœ… Error handling (exceptions, network failures) +- βœ… State transitions (loading β†’ loaded, idle β†’ busy) + +--- + +## Integration Tests + +### Scope + +Test the full flow: ViewModel β†’ Service β†’ Repository β†’ Data Layer (mocked) + +### Key Test Cases + +#### Integration Test 1: End-to-End Data Flow + +```csharp +[Fact] +public async Task Should_Load_And_Display_Items_End_To_End() +{ + // Arrange: Set up full dependency chain + var mockRepository = new Mock(); + mockRepository.Setup(r => r.GetAllAsync()) + .ReturnsAsync(new List { new Item { Id = 1 } }); + + var service = new ExampleService(mockRepository.Object); + var viewModel = new ExampleViewModel(service); + + // Act: Execute ViewModel command + await viewModel.LoadCommand.Execute(); + + // Assert: Verify data flows through all layers + viewModel.Items.Should().HaveCount(1); + mockRepository.Verify(r => r.GetAllAsync(), Times.Once); +} +``` + +#### Integration Test 2: Dependency Injection Resolution + +```csharp +[Fact] +public void Should_Resolve_All_Dependencies_From_DI_Container() +{ + // Arrange: Build DI container (as in MauiProgram.cs) + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddTransient(); + var serviceProvider = services.BuildServiceProvider(); + + // Act: Resolve ViewModel + var viewModel = serviceProvider.GetRequiredService(); + + // Assert: ViewModel is not null and dependencies are injected + viewModel.Should().NotBeNull(); +} +``` + +--- + +## Performance Tests + +### Requirements + +- ViewModel initialization: < 100ms +- Data loading (100 items): < 500ms +- UI responsiveness: No blocking on main thread + +### Test Scenarios + +#### Performance Test: Load Large Dataset + +```csharp +[Fact] +public async Task Should_Load_1000_Items_Within_1_Second() +{ + // Arrange + var mockService = new Mock(); + var largeDataset = Enumerable.Range(1, 1000) + .Select(i => new Item { Id = i, Name = $"Item {i}" }) + .ToList(); + mockService.Setup(s => s.GetItemsAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(largeDataset); + + var viewModel = new ExampleViewModel(mockService.Object); + + // Act + var stopwatch = Stopwatch.StartNew(); + await viewModel.LoadCommand.Execute(); + stopwatch.Stop(); + + // Assert + stopwatch.ElapsedMilliseconds.Should().BeLessThan(1000); + viewModel.Items.Should().HaveCount(1000); +} +``` + +--- + +## Security Tests + +### Security Checks + +- [ ] Input validation: Reject SQL injection patterns +- [ ] Authentication: Verify unauthenticated users cannot access protected features +- [ ] Authorization: Verify role-based access control +- [ ] Data sanitization: User input is sanitized before storage + +### Example Security Test + +```csharp +[Theory] +[InlineData("")] +[InlineData("'; DROP TABLE Users; --")] +[InlineData("../../etc/passwd")] +public void Should_Reject_Malicious_Input_When_Validating_Search_Text(string maliciousInput) +{ + // Arrange + var mockService = new Mock(); + var viewModel = new ExampleViewModel(mockService.Object); + + // Act + viewModel.SearchText = maliciousInput; + viewModel.SubmitCommand.Execute().Subscribe(); + + // Assert + viewModel.ErrorMessage.Should().Contain("Invalid input"); + mockService.Verify(s => s.SearchAsync(It.IsAny()), Times.Never); +} +``` + +--- + +## Acceptance Criteria + + + +- [ ] All unit tests pass (100% success rate) +- [ ] Code coverage β‰₯ 80% for ViewModels and Services +- [ ] Integration tests verify end-to-end flows +- [ ] Performance benchmarks met +- [ ] Security tests validate input sanitization +- [ ] Manual testing confirms UI behavior + +--- + +## Test Data + +### Mock Data Setup + +Create a `TestDataBuilder.cs` class for consistent test data: + +```csharp +public static class TestDataBuilder +{ + public static List CreateItems(int count) + { + return Enumerable.Range(1, count) + .Select(i => new Item { Id = i, Name = $"Item {i}" }) + .ToList(); + } + + public static ExampleViewModel CreateViewModelWithMockedService(List items) + { + var mockService = new Mock(); + mockService.Setup(s => s.GetItemsAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(items); + return new ExampleViewModel(mockService.Object); + } +} +``` + +--- + +## Manual Testing + +### UI/UX Validation + +- [ ] **Visual Regression**: Compare screenshots before/after changes +- [ ] **Touch Targets**: Verify buttons are β‰₯ 2cm x 2cm (child-friendly) +- [ ] **Animations**: Confirm smooth transitions (60fps) +- [ ] **Error States**: Verify error messages display correctly +- [ ] **Accessibility**: Test with screen reader (Windows Narrator) + +### Platform-Specific Testing + +- [ ] **Windows**: Test on Windows 11 with touch and mouse input +- [ ] **Android**: Test on emulator and physical device (API 36+) +- [ ] **iOS**: Test on simulator and physical device (iOS 17+) +- [ ] **MacCatalyst**: Verify app runs on macOS + +### Test Execution Checklist + +1. Run unit tests: `dotnet test` +2. Check code coverage: `dotnet test /p:CollectCoverage=true` +3. Run integration tests: `dotnet test --filter Category=Integration` +4. Build release: `dotnet build -c Release` +5. Deploy to test device and perform manual testing diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..4aa419b --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "lean-spec": { + "command": "npx", + "args": ["-y", "leanspec", "mcp", "--project", "${workspaceFolder}"] + } + } +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..22056fb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "cucumberautocomplete.steps": ["LunaDraw.Tests/Steps/*.cs"], + "cucumberautocomplete.syncfeatures": "LunaDraw.Tests/Features/*.feature", + "cucumberautocomplete.strictGherkinCompletion": true, + "cucumberautocomplete.onStepDefinitionNotFound": "recommend" +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0703536 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,97 @@ +# AI Agent Instructions + +## Project: LunaDraw + +## 🚨 CRITICAL: Before ANY Task + +**STOP and check these first:** + +1. **Discover context** β†’ Use `board` tool to see project state +2. **Search for related work** β†’ Use `search` tool before creating new specs +3. **Never create files manually** β†’ Always use `create` tool for new specs + +> **Why?** Skipping discovery creates duplicate work. Manual file creation breaks LeanSpec tooling. + +## πŸ”§ Managing Specs + +### MCP Tools (Preferred) with CLI Fallback + +| Action | MCP Tool | CLI Fallback | +|--------|----------|--------------| +| Project status | `board` | `lean-spec board` | +| List specs | `list` | `lean-spec list` | +| Search specs | `search` | `lean-spec search "query"` | +| View spec | `view` | `lean-spec view ` | +| Create spec | `create` | `lean-spec create ` | +| Update spec | `update` | `lean-spec update --status ` | +| Link specs | `link` | `lean-spec link --depends-on ` | +| Unlink specs | `unlink` | `lean-spec unlink --depends-on ` | +| Dependencies | `deps` | `lean-spec deps ` | +| Token count | `tokens` | `lean-spec tokens ` | + +## ⚠️ Core Rules + +| Rule | Details | +|------|---------| +| **NEVER edit frontmatter manually** | Use `update`, `link`, `unlink` for: `status`, `priority`, `tags`, `assignee`, `transitions`, timestamps, `depends_on` | +| **ALWAYS link spec references** | Content mentions another spec β†’ `lean-spec link --depends-on ` | +| **Track status transitions** | `planned` β†’ `in-progress` (before coding) β†’ `complete` (after done) | +| **No nested code blocks** | Use indentation instead | + +### 🚫 Common Mistakes + +| ❌ Don't | βœ… Do Instead | +|----------|---------------| +| Create spec files manually | Use `create` tool | +| Skip discovery | Run `board` and `search` first | +| Leave status as "planned" | Update to `in-progress` before coding | +| Edit frontmatter manually | Use `update` tool | + +## πŸ“‹ SDD Workflow + +``` +BEFORE: board β†’ search β†’ check existing specs +DURING: update status to in-progress β†’ code β†’ document decisions β†’ link dependencies +AFTER: update status to complete β†’ document learnings +``` + +**Status tracks implementation, NOT spec writing.** + +## Spec Dependencies + +Use `depends_on` to express blocking relationships between specs: +- **`depends_on`** = True blocker, work order matters, directional (A depends on B) + +Link dependencies when one spec builds on another: +```bash +lean-spec link --depends-on +``` + +## When to Use Specs + +| βœ… Write spec | ❌ Skip spec | +|---------------|--------------| +| Multi-part features | Bug fixes | +| Breaking changes | Trivial changes | +| Design decisions | Self-explanatory refactors | + +## Token Thresholds + +| Tokens | Status | +|--------|--------| +| <2,000 | βœ… Optimal | +| 2,000-3,500 | βœ… Good | +| 3,500-5,000 | ⚠️ Consider splitting | +| >5,000 | πŸ”΄ Must split | + +## First Principles (Priority Order) + +1. **Context Economy** - <2,000 tokens optimal, >3,500 needs splitting +2. **Signal-to-Noise** - Every word must inform a decision +3. **Intent Over Implementation** - Capture why, let how emerge +4. **Bridge the Gap** - Both human and AI must understand +5. **Progressive Disclosure** - Add complexity only when pain is felt + +--- + +**Remember:** LeanSpec tracks what you're building. Keep specs in sync with your work! \ No newline at end of file diff --git a/App.xaml b/App.xaml index ae58304..e976035 100644 --- a/App.xaml +++ b/App.xaml @@ -16,6 +16,8 @@ + + diff --git a/App.xaml.cs b/App.xaml.cs index fc4f826..83c54a1 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -21,14 +21,19 @@ * */ -using LunaDraw.Logic.Utils; +using LunaDraw.Logic.Messages; +using ReactiveUI; +using LunaDraw.Logic.Storage; namespace LunaDraw; public partial class App : Application { - public App(IPreferencesFacade preferencesFacade) + private readonly IMessageBus messageBus; + + public App(IPreferencesFacade preferencesFacade, IMessageBus messageBus) { + this.messageBus = messageBus; InitializeComponent(); var theme = preferencesFacade.Get(AppPreference.AppTheme); @@ -44,4 +49,10 @@ protected override Window CreateWindow(IActivationState? activationState) { return new Window(new AppShell()); } + + protected override void OnSleep() + { + base.OnSleep(); + messageBus.SendMessage(new AppSleepingMessage()); + } } \ No newline at end of file diff --git a/AppShell.xaml.cs b/AppShell.xaml.cs index 045a01a..485990f 100644 --- a/AppShell.xaml.cs +++ b/AppShell.xaml.cs @@ -28,5 +28,6 @@ public partial class AppShell : Shell public AppShell() { InitializeComponent(); + Routing.RegisterRoute(nameof(Pages.PlaybackPage), typeof(Pages.PlaybackPage)); } } diff --git a/ClearCache.bat b/ClearCache.bat index 254420c..85b1aba 100644 --- a/ClearCache.bat +++ b/ClearCache.bat @@ -1,3 +1,13 @@ +rem^ ||( + This is the default license template. + + File: ClearCache.bat + Author: iknow + Copyright (c) 2025 iknow + + To edit this license information: Press Ctrl+Shift+P and press 'Create new License Template...'. +) + dotnet nuget locals all --clear dotnet clean for /d /r . %%d in (bin,obj) do @if exist "%%d" rd /s/q "%%d" \ No newline at end of file diff --git a/Components/BrushStrokePreviewControl.cs b/Components/BrushStrokePreviewControl.cs new file mode 100644 index 0000000..e4137b1 --- /dev/null +++ b/Components/BrushStrokePreviewControl.cs @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2025 CodeSoupCafe LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +using LunaDraw.Logic.Models; +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace LunaDraw.Components; + +/// +/// Displays a live preview of the current brush stroke with all effects applied. +/// Shows a predefined S-curve with the current brush shape, color, size, and effects. +/// +public class BrushStrokePreviewControl : SKCanvasView +{ + // Bindable Properties + + public static readonly BindableProperty StrokeColorProperty = + BindableProperty.Create(nameof(StrokeColor), typeof(SKColor), typeof(BrushStrokePreviewControl), SKColors.White, propertyChanged: OnPropertyChanged); + + public SKColor StrokeColor + { + get => (SKColor)GetValue(StrokeColorProperty); + set => SetValue(StrokeColorProperty, value); + } + + public static readonly BindableProperty StrokeWidthProperty = + BindableProperty.Create(nameof(StrokeWidth), typeof(float), typeof(BrushStrokePreviewControl), 10f, propertyChanged: OnPropertyChanged); + + public float StrokeWidth + { + get => (float)GetValue(StrokeWidthProperty); + set => SetValue(StrokeWidthProperty, value); + } + + public static readonly BindableProperty BrushShapeProperty = + BindableProperty.Create(nameof(BrushShape), typeof(BrushShape), typeof(BrushStrokePreviewControl), null, propertyChanged: OnPropertyChanged); + + public BrushShape BrushShape + { + get => (BrushShape)GetValue(BrushShapeProperty); + set => SetValue(BrushShapeProperty, value); + } + + public static readonly BindableProperty IsGlowEnabledProperty = + BindableProperty.Create(nameof(IsGlowEnabled), typeof(bool), typeof(BrushStrokePreviewControl), false, propertyChanged: OnPropertyChanged); + + public bool IsGlowEnabled + { + get => (bool)GetValue(IsGlowEnabledProperty); + set => SetValue(IsGlowEnabledProperty, value); + } + + public static readonly BindableProperty GlowColorProperty = + BindableProperty.Create(nameof(GlowColor), typeof(SKColor), typeof(BrushStrokePreviewControl), SKColors.Yellow, propertyChanged: OnPropertyChanged); + + public SKColor GlowColor + { + get => (SKColor)GetValue(GlowColorProperty); + set => SetValue(GlowColorProperty, value); + } + + public static readonly BindableProperty GlowRadiusProperty = + BindableProperty.Create(nameof(GlowRadius), typeof(float), typeof(BrushStrokePreviewControl), 10f, propertyChanged: OnPropertyChanged); + + public float GlowRadius + { + get => (float)GetValue(GlowRadiusProperty); + set => SetValue(GlowRadiusProperty, value); + } + + public static readonly BindableProperty IsRainbowEnabledProperty = + BindableProperty.Create(nameof(IsRainbowEnabled), typeof(bool), typeof(BrushStrokePreviewControl), false, propertyChanged: OnPropertyChanged); + + public bool IsRainbowEnabled + { + get => (bool)GetValue(IsRainbowEnabledProperty); + set => SetValue(IsRainbowEnabledProperty, value); + } + + public static readonly BindableProperty ScatterRadiusProperty = + BindableProperty.Create(nameof(ScatterRadius), typeof(float), typeof(BrushStrokePreviewControl), 0f, propertyChanged: OnPropertyChanged); + + public float ScatterRadius + { + get => (float)GetValue(ScatterRadiusProperty); + set => SetValue(ScatterRadiusProperty, value); + } + + public static readonly BindableProperty SizeJitterProperty = + BindableProperty.Create(nameof(SizeJitter), typeof(float), typeof(BrushStrokePreviewControl), 0f, propertyChanged: OnPropertyChanged); + + public float SizeJitter + { + get => (float)GetValue(SizeJitterProperty); + set => SetValue(SizeJitterProperty, value); + } + + public static readonly BindableProperty AngleJitterProperty = + BindableProperty.Create(nameof(AngleJitter), typeof(float), typeof(BrushStrokePreviewControl), 0f, propertyChanged: OnPropertyChanged); + + public float AngleJitter + { + get => (float)GetValue(AngleJitterProperty); + set => SetValue(AngleJitterProperty, value); + } + + public static readonly BindableProperty HueJitterProperty = + BindableProperty.Create(nameof(HueJitter), typeof(float), typeof(BrushStrokePreviewControl), 0f, propertyChanged: OnPropertyChanged); + + public float HueJitter + { + get => (float)GetValue(HueJitterProperty); + set => SetValue(HueJitterProperty, value); + } + + private static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is BrushStrokePreviewControl control) + { + control.InvalidateSurface(); + } + } + + protected override void OnPaintSurface(SKPaintSurfaceEventArgs e) + { + base.OnPaintSurface(e); + + var canvas = e.Surface.Canvas; + canvas.Clear(SKColors.Transparent); + + var info = e.Info; + + // Create a smooth S-curve path + using var path = new SKPath(); + + // Start point (left) + float startX = info.Width * 0.1f; + float startY = info.Height * 0.5f; + + // End point (right) + float endX = info.Width * 0.9f; + float endY = info.Height * 0.5f; + + // Control points for smooth S-curve + float cp1X = info.Width * 0.3f; + float cp1Y = info.Height * 0.2f; + float cp2X = info.Width * 0.7f; + float cp2Y = info.Height * 0.8f; + + path.MoveTo(startX, startY); + path.CubicTo(cp1X, cp1Y, cp2X, cp2Y, endX, endY); + + // Draw the stroke with current settings + using var paint = new SKPaint + { + IsAntialias = true, + Style = SKPaintStyle.Stroke, + StrokeWidth = StrokeWidth, + Color = StrokeColor + }; + + // Apply glow effect if enabled + if (IsGlowEnabled && GlowRadius > 0) + { + paint.MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, GlowRadius / 2); + paint.Color = GlowColor.WithAlpha((byte)(GlowColor.Alpha * 0.8f)); + canvas.DrawPath(path, paint); + + // Draw main stroke on top + paint.MaskFilter = null; + paint.Color = StrokeColor; + } + + // Apply brush shape - always use round cap for preview simplicity + paint.StrokeCap = SKStrokeCap.Round; + + canvas.DrawPath(path, paint); + } +} diff --git a/Components/ColorPickerFlyoutPanel.xaml b/Components/ColorPickerFlyoutPanel.xaml new file mode 100644 index 0000000..1e62d1d --- /dev/null +++ b/Components/ColorPickerFlyoutPanel.xaml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/Components/ColorPickerFlyoutPanel.xaml.cs b/Components/ColorPickerFlyoutPanel.xaml.cs new file mode 100644 index 0000000..98ba686 --- /dev/null +++ b/Components/ColorPickerFlyoutPanel.xaml.cs @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 CodeSoupCafe LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +using LunaDraw.Logic.ViewModels; +using Maui.ColorPicker; +using SkiaSharp; + +namespace LunaDraw.Components; + +/// +/// Popover panel for selecting stroke color. +/// +public partial class ColorPickerFlyoutPanel : ContentView +{ + public ColorPickerFlyoutPanel() + { + InitializeComponent(); + } + + protected override void OnBindingContextChanged() + { + base.OnBindingContextChanged(); + + if (BindingContext is ToolbarViewModel viewModel) + { + StrokeColorPicker.PickedColor = SKColorToMauiColor(viewModel.StrokeColor); + } + } + + private void OnStrokeColorChanged(object? sender, EventArgs e) + { + if (sender is ColorPicker colorPicker && BindingContext is ToolbarViewModel viewModel) + { + viewModel.StrokeColor = MauiColorToSKColor(colorPicker.PickedColor); + } + } + + private static SKColor MauiColorToSKColor(Color mauiColor) + { + return new SKColor( + (byte)((mauiColor?.Red ?? 0) * 255), + (byte)((mauiColor?.Green ?? 0) * 255), + (byte)((mauiColor?.Blue ?? 0) * 255), + (byte)((mauiColor?.Alpha ?? 0) * 255)); + } + + private static Color SKColorToMauiColor(SKColor skColor) + { + return Color.FromRgba(skColor.Red, skColor.Green, skColor.Blue, skColor.Alpha); + } +} diff --git a/Components/DrawingGalleryPopup.xaml b/Components/DrawingGalleryPopup.xaml index 48d4513..2eaf248 100644 --- a/Components/DrawingGalleryPopup.xaml +++ b/Components/DrawingGalleryPopup.xaml @@ -48,6 +48,7 @@ + + + + + + diff --git a/Components/EffectsFlyoutPanel.xaml.cs b/Components/EffectsFlyoutPanel.xaml.cs new file mode 100644 index 0000000..68edae7 --- /dev/null +++ b/Components/EffectsFlyoutPanel.xaml.cs @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2025 CodeSoupCafe LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +using LunaDraw.Logic.ViewModels; +using SkiaSharp; + +namespace LunaDraw.Components; + +/// +/// Popover panel for brush effects including scatter, jitter, and magic effects (Rainbow, Glow). +/// +public partial class EffectsFlyoutPanel : ContentView +{ + public EffectsFlyoutPanel() + { + InitializeComponent(); + } + + protected override void OnBindingContextChanged() + { + base.OnBindingContextChanged(); + + if (BindingContext is ToolbarViewModel viewModel) + { + ScatterRadiusSlider.Value = viewModel.ScatterRadius; + SizeJitterSlider.Value = viewModel.SizeJitter; + AngleJitterSlider.Value = viewModel.AngleJitter; + HueJitterSlider.Value = viewModel.HueJitter; + RainbowSwitch.IsToggled = viewModel.IsRainbowEnabled; + GlowSwitch.IsToggled = viewModel.IsGlowEnabled; + GlowRadiusSlider.Value = viewModel.GlowRadius; + } + } + + private void OnRainbowSwitchToggled(object? sender, ToggledEventArgs e) + { + if (BindingContext is ToolbarViewModel viewModel) + { + viewModel.IsRainbowEnabled = e.Value; + } + } + + private void OnGlowSwitchToggled(object? sender, ToggledEventArgs e) + { + if (BindingContext is ToolbarViewModel viewModel) + { + viewModel.IsGlowEnabled = e.Value; + } + } + + private void OnGlowRadiusChanged(object? sender, ValueChangedEventArgs e) + { + if (BindingContext is ToolbarViewModel viewModel) + { + viewModel.GlowRadius = (float)e.NewValue; + } + } + + private void OnGlowColorTapped(object? sender, TappedEventArgs e) + { + if (e.Parameter is string colorHex && BindingContext is ToolbarViewModel viewModel) + { + if (SKColor.TryParse(colorHex, out var skColor)) + { + viewModel.GlowColor = skColor; + } + } + } + + private void OnScatterRadiusChanged(object? sender, ValueChangedEventArgs e) + { + if (BindingContext is ToolbarViewModel viewModel) + { + viewModel.ScatterRadius = (float)e.NewValue; + } + } + + private void OnSizeJitterChanged(object? sender, ValueChangedEventArgs e) + { + if (BindingContext is ToolbarViewModel viewModel) + { + viewModel.SizeJitter = (float)e.NewValue; + } + } + + private void OnAngleJitterChanged(object? sender, ValueChangedEventArgs e) + { + if (BindingContext is ToolbarViewModel viewModel) + { + viewModel.AngleJitter = (float)e.NewValue; + } + } + + private void OnHueJitterChanged(object? sender, ValueChangedEventArgs e) + { + if (BindingContext is ToolbarViewModel viewModel) + { + viewModel.HueJitter = (float)e.NewValue; + } + } +} diff --git a/Components/MiniMapView.xaml.cs b/Components/MiniMapView.xaml.cs index 04e0d1c..fbff880 100644 --- a/Components/MiniMapView.xaml.cs +++ b/Components/MiniMapView.xaml.cs @@ -25,7 +25,7 @@ using LunaDraw.Logic.Extensions; using LunaDraw.Logic.Messages; -using LunaDraw.Logic.Utils; +using LunaDraw.Logic.Storage; using LunaDraw.Logic.ViewModels; using ReactiveUI; diff --git a/Components/PlaybackControls.xaml b/Components/PlaybackControls.xaml new file mode 100644 index 0000000..a90db94 --- /dev/null +++ b/Components/PlaybackControls.xaml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + - diff --git a/Components/TopToolbarView.xaml b/Components/TopToolbarView.xaml new file mode 100644 index 0000000..7dc9bce --- /dev/null +++ b/Components/TopToolbarView.xaml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + +