Introduction
This document defines source and destination requirements for refactoring PostPageGenerator from side-effect orchestrator to PostPageFolder virtual IFolder pattern. Refactor enables Multi-Page scenario implementation by introducing composable virtual structure while maintaining backward compatibility with existing Single-Page usage.
Scope: Single-Page (Post/Page) refactor only. Multi-Page implementation deliberately excluded from this planning document.
Purpose: Establish clear what/where requirements for review before implementation execution. Transformation "how" deliberately omitted per planning phase separation.
Context: PostPageGenerator currently operational (October 25-26, 2025 implementation). Design investigation completed November 1-3, 2025, confirming virtual IFolder pattern viability across all three scenarios and invalidating "perfect reuse" assumption.
Table of Contents
- Introduction
- Source Requirements: PostPageGenerator Current State
- Destination Requirements
- Backward Compatibility Requirements
- Success Criteria
- Explicit Exclusion: Transformation "How"
- References
Source Requirements: PostPageGenerator Current State
Architecture
Pattern: Side-effect orchestrator with immediate write operations
Structure:
- Stateless instance-based transformer
- Thread-safe, no shared state
- 3 partials: Main, Markdown, Template
- Source location:
V:\WindowsAppCommunity.CommandLine\src\Blog\PostPage\PostPageGenerator*.cs
Design Characteristics:
- Imperative orchestration
- Direct file system operations
- Consumer provides output destination
- Single responsibility: Transform one markdown file to one folderized HTML page
Behavior
GenerateAsync() Current Operation:
- Creates output folders/files immediately during method call
- Eager materialization (writes happen within GenerateAsync scope)
- No virtual representation of output structure
- Output destination controlled by caller via parameters
- Returns upon completion of file system operations
Processing Flow:
- Resolve template (IFile or IFolder)
- Extract YAML front-matter from markdown
- Transform markdown body to HTML (Markdig)
- Apply Scriban template with data model
- Create output folder structure
- Write index.html
- Copy template assets (if folder template)
Components
PostPageGenerator (Core Transformer):
- 3 partials: Main (orchestration), Markdown (transformation), Template (resolution/application)
- Handles markdown → HTML transformation
- Manages template rendering
- Coordinates output folder creation
- Location:
V:\WindowsAppCommunity.CommandLine\src\Blog\PostPage\PostPageGenerator*.cs
PostPageDataModel (Scriban Data Contract):
- Properties: body, frontmatter, filename, created, modified
- Passed to Scriban template for rendering
- Location:
V:\WindowsAppCommunity.CommandLine\src\Blog\PostPage\PostPageDataModel.cs
PostPageCommand (CLI Interface):
- Wires PostPageGenerator to command-line interface
- Handles parameter parsing and validation
- Provides user-facing Single-Page transformation command
- Location:
V:\WindowsAppCommunity.CommandLine\src\Commands\Blog\PostPage\PostPageCommand.cs
WacsdkBlogCommands (Command Aggregator):
- Registers PostPageCommand with CLI system
- Location:
V:\WindowsAppCommunity.CommandLine\src\Commands\Blog\WacsdkBlogCommands.cs
Destination Requirements
Virtual IFolder Implementation
Pattern: PostPageFolder implements IFolder interface representing single-page output structure
Characteristics:
- Implements IFolder fully (all methods, properties, events per interface contract)
- Represents output structure:
{filename}/index.html+ template assets - No immediate file system operations during construction
- Virtual representation of what output would contain if materialized
Scope:
- Single-page output structure only
- One PostPageFolder instance = one folderized HTML page
- Template assets included in virtual structure
Three Component Types (from Gap 1 investigation):
PostPageFolder (Main virtual folder):
- Implements
IFolderinterface - Wraps source markdown
IFile - Yields
IndexHtmlFile+ template assets viaGetItemsAsync - Constructor:
PostPageFolder(IFile markdownSource, IStorable templateSource, string? templateFileName = null) - Identity: Derived from source markdown IFile.Id, sanitized filename for Name
- Implements
PostPageAssetFolder (Recursive template asset wrapper):
- Implements
IChildFolderinterface (includesGetParentAsync) - Recursively mirrors template folder structure
- Stores
Parentproperty for proper folder hierarchy - Constructor:
PostPageAssetFolder(IFolder wrappedFolder, IFolder parent, IFile? templateFileToExclude) - Identity: Passthrough from wrapped template folder (Id, Name)
- Implements
IndexHtmlFile (Virtual markdown→HTML file):
- Implements
IFileinterface - Renders markdown→HTML only when
OpenStreamAsynccalled - Constructor:
IndexHtmlFile(string id, IFile markdownSource, IStorable templateSource, string? templateFileName) - Identity: Parent-derived ID (format TBD), constant "index.html" name
- Implements
Wrapper Architecture:
- PostPageFolder wraps source markdown IFile and template IStorable
- Template subfolders recursively wrapped as PostPageAssetFolder instances
- Template files (non-markdown) passed through directly without wrapping
- Rationale for file passthrough: Preserves type identity for fastpath extension method type checks (avoids interfering with API optimization across implementations)
Lazy Generation
Requirement: Content created on access, not during construction
Trigger Points:
- GetFileAsync() called → Generate file content on-demand
- CopyToAsync() called → Materialize complete structure to destination
- GetItemsAsync() called → Return virtual file/folder listing
Not Triggered By:
- PostPageFolder construction (no side effects)
- Property access (Id, Name, etc.)
- Interface method calls that don't require content (CanDelete, CanMove, etc.)
Rationale: Enables composition scenarios where PostPageFolder instances created but not materialized until consumer explicitly requests output.
Generation Timing Decision (from Gap 2 investigation):
- Option A: Generate on every OpenStreamAsync call (SELECTED)
- Decision rationale: "The direct pass-through is a selling point. Semaphore not needed."
- No caching layer - direct transformation on each access
- Selling point: Immediate reflection of source changes, simple architecture
- IndexHtmlFile.OpenStreamAsync triggers markdown→HTML transformation, returns stream with HTML content (stream implementation TBD)
- FileAccess.Read only (throw exception for write modes, exception type TBD)
State Storage:
- PostPageFolder stores:
markdownSource(IFile),templateSource(IStorable),templateFileName(string?) - IndexHtmlFile receives same inputs via constructor
- All transformation inputs available at OpenStreamAsync time
- No caching, no synchronization primitives required
Error Handling Timing:
- Exceptions thrown during OpenStreamAsync execution (on access, not construction)
- YamlException, MarkdigException, ScribanException bubble naturally
- Consistent with lazy generation pattern (user confirmation: "On access, of course.")
Composition Enablement
Requirement: PostPageFolder instances composable by higher-level orchestrators
Target Scenario: PagesFolder (Multi-Page) composes multiple PostPageFolder instances to create multi-page output
Composition Characteristics:
- PostPageFolder construction cheap (no I/O)
- Multiple instances created without filesystem impact
- Parent orchestrator controls materialization timing
- Virtual structure enables folder hierarchy preservation
Architectural Validation: PostPageFolder ready for PagesFolder composition (implementation of PagesFolder deliberately excluded from this planning document)
Consumer Control
Requirement: Consumer determines when and where output materializes
Current Behavior Preserved:
- Consumer provides output destination
- Consumer triggers materialization explicitly
- Consumer handles errors during materialization
New Capability Added:
- Consumer can create PostPageFolder without triggering materialization
- Consumer can inspect virtual structure before materialization
- Consumer controls materialization via CopyToAsync() call
IFolder Method Implementation Scope
Requirement: Specify which IFolder/IChildFolder/IFile methods required vs omitted
Interface Analysis (from Gap 4 investigation):
- IFolder extends IStorable:
GetItemsAsync(StorableType, CancellationToken)method only - IStorable:
Idproperty,Nameproperty - IChildFolder extends IFolder + IStorableChild: Adds
GetParentAsync(CancellationToken)method - IFile extends IStorable:
OpenStreamAsync(FileAccess, CancellationToken)method only - No write operations in base interfaces - read-only by design
PostPageFolder Method Scope (implements IFolder):
- ✅
GetItemsAsync(StorableType, CancellationToken): Enumerate virtual children (IndexHtmlFile + PostPageAssetFolder wrappers + template file passthrough) - ✅
string Id { get; }: Derived from source markdown IFile.Id - ✅
string Name { get; }: Sanitized markdown filename - ❌ No write operations required (IFolder contains no mutation methods)
PostPageAssetFolder Method Scope (implements IChildFolder):
- ✅
GetItemsAsync(StorableType, CancellationToken): Recursively wrap subfolders, passthrough files - ✅
GetParentAsync(CancellationToken): Return stored parent reference - ✅
string Id { get; },Name { get; }: Passthrough from wrapped template folder - ✅
IFolder Parent { get; }: Store parent for hierarchy (property, not interface requirement)
IndexHtmlFile Method Scope (implements IFile):
- ✅
OpenStreamAsync(FileAccess, CancellationToken): Trigger transformation, return stream with HTML content (Read mode only, throw exception for write modes, stream implementation TBD) - ✅
string Id { get; }: Parent-derived identifier (format TBD) - ✅
string Name { get; }: Constant "index.html"
GetItemsAsync Enumeration Patterns:
- Filter by StorableType: All (both), File (files only), Folder (folders only)
- PostPageFolder: Yield IndexHtmlFile, PostPageAssetFolder wrappers for template subfolders, passthrough template files, exclude template HTML file (already rendered as index.html, exclusion mechanism TBD)
- PostPageAssetFolder: Recursively wrap subfolders with
thisas parent, passthrough files directly (preserves type identity), propagate template file exclusion down hierarchy
Capabilities Interfaces: None required - base interfaces sufficient for read-only virtual structure
Component Decomposition
Requirement: Pure transformation logic extractable and reusable across implementation patterns
Investigation Status (from Gap 3): Deferred to implementation phase - extraction boundaries emerge naturally during refactoring, October implementation provides working reference (3 partials: Markdown, Template, Main)
Components to Extract:
Markdown Processing:
- YAML front-matter extraction (handles presence and absence)
- Markdown → HTML transformation (Markdig Advanced Extensions)
- Pure text transformation, no I/O dependencies
- Reusable by both PostPageFolder and future PagesFolder
Template Processing:
- Template source resolution (IFile vs IFolder detection)
- Scriban template rendering (model → HTML)
- Template file location via convention (template.html default, override support)
- Pure transformation with OwlCore.Storage abstractions
Asset Management:
- Recursive asset enumeration (DepthFirstRecursiveFolder)
- Template file exclusion (ID comparison)
- Path-based asset copying preserving folder structure
- Reusable for any virtual IFolder implementation requiring asset inclusion
Rationale: Extracting pure transformation logic:
- Enables reuse across PostPageFolder (Single-Page) and PagesFolder (Multi-Page)
- Supports unit testing without file system dependencies
- Reduces duplication between scenarios
- Maintains separation of concerns (transformation vs orchestration)
Asset Virtual Representation (from Gap 5 investigation):
- Template files: Passed through directly without wrapping (preserves type identity for fastpath extension method optimization)
- Template subfolders: Wrapped as PostPageAssetFolder instances
- Recursive wrapping: PostPageAssetFolder.GetItemsAsync wraps subfolders with
thisas parent, yields files directly - Template HTML file exclusion: Already rendered as index.html (ID prop value TBD)
- Loading timing: Eager discovery via GetItemsAsync enumeration, no caching (aligns with direct passthrough architecture)
- Filter logic: StorableType parameter (All/File/Folder) determines what children yielded
External Dependencies
Requirement: External libraries and configurations preserved across refactor
Required Libraries:
Markdig (Markdown → HTML transformation):
- Configuration: Advanced Extensions pipeline
- Enables: Tables, strikethrough, task lists, autolinks
- Usage: Standard for blog generators (CommonMark + GFM subset)
- Balances features vs complexity/security
Scriban (Template rendering engine):
- Template parsing and rendering
- TemplateContext for model data passing
- Supports arbitrary front-matter access via dot notation
YamlDotNet (YAML front-matter parsing):
- Deserializes YAML to Dictionary<string, object>
- Supports arbitrary key-value pairs without schema enforcement
- Error handling: YamlException wrapping with descriptive messages
OwlCore.Storage Extensions:
- DepthFirstRecursiveFolder for recursive asset enumeration
- CreateCopyOfAsync for file copying with path preservation
- SystemFile/SystemFolder for local filesystem access
- Universal storage abstraction enabling virtual implementations
Rationale: Dependencies unchanged by refactor. Virtual IFolder pattern works with same libraries, just changes when/where transformations execute (lazy vs eager).
Configuration Preservation
Requirement: Configuration decisions from October implementation maintained
Markdig Configuration:
- Pipeline:
new MarkdownPipelineBuilder().UseAdvancedExtensions().Build() - Features enabled: Tables, strikethrough, task lists, autolinks
- Rationale: Proven configuration, standard for blog generators
Template File Convention:
- Default:
template.htmlwhen template source is IFolder - Override: Optional parameter allows custom template filename
- Error: InvalidOperationException if template file not found
- Rationale: Convention reduces user burden, parameter provides flexibility
Folder Name Sanitization:
- Algorithm:
Path.GetInvalidFileNameChars(), replace with underscore - Source: Markdown filename without extension
- Rationale: Ensures valid folder names across platforms
Overwrite Behavior:
- Pattern: Silent overwrite via
CreateFolderAsync(overwrite: true) - Rationale: Standard blog generator behavior (Jekyll, Hugo, Eleventy), regeneration workflow friendly
YAML Front-Matter Handling:
- Arbitrary key-value pairs, no filtering, no schema enforcement
- Optional: Empty string when front-matter absent (parses to empty dictionary)
- Generator provides all keys, template controls HTML generation
- Rationale: Maximum flexibility for users, separation of concerns
Rationale: These configurations proven operational in October implementation. Virtual IFolder pattern preserves same decisions, changes only orchestration timing.
Error Handling
Requirement: Exception patterns and bubbling behavior preserved
YamlException Wrapping:
- Pattern:
try-catchwith YamlException, wrap with InvalidOperationException - Message:
"Failed to parse YAML front-matter: {original message}" - Rationale: Provides context, maintains inner exception for debugging
Template File Not Found:
- Pattern: InvalidOperationException when template file missing in folder
- Message:
"Template file '{fileName}' not found in folder." - Rationale: Clear user-facing error with specific filename
Path Resolution Failures:
- Pattern: Exceptions bubble from SystemFile/SystemFolder constructors
- Validation: Constructors validate path existence automatically
- Rationale: No extra File.Exists checks needed, constructors handle validation
Framework Integration:
- Pattern: No try-catch in generator, exceptions bubble to System.CommandLine framework
- Global handler: AppDomain.CurrentDomain.UnhandledException in Program.cs
- Rationale: Project convention, framework provides graceful error handling
Rationale: Virtual IFolder pattern maintains same exception patterns. Errors during lazy generation bubble same way as errors during eager generation.
Capabilities Preservation
Requirement: All 9 features from October implementation must continue working post-refactor
Feature Checklist:
Markdown → HTML Body Conversion
- Markdig Advanced Extensions (tables, strikethrough, task lists, autolinks)
- Pure text transformation
- Populates PostPageDataModel.Body
YAML Front-Matter Extraction
- Parse arbitrary key-value pairs
- Handle absence gracefully (empty string → empty dictionary)
- Populates PostPageDataModel.Frontmatter
Front-Matter → HTML Meta Tags
- Generator provides frontmatter dictionary with all keys
- Template generates
<meta>tags from dictionary - No special handling of any keys (template responsibility)
Scriban Template Application
- Template receives PostPageDataModel
- Template generates all HTML including meta tags
- Generator invokes, template transforms
Template Source Resolution
- Type detection: IFile vs IFolder (pattern matching)
- Convention: template.html default, parameter override
- Error: InvalidOperationException if not found
Template Folder Asset Copying
- DepthFirstRecursiveFolder for recursive enumeration
- Path-based copying preserves folder structure
- Template file exclusion via ID comparison
Output Folderization
- Sanitized folder name from markdown filename
- Structure:
{sanitized-name}/index.html+ assets - Silent overwrite for regeneration workflows
Open Graph Meta Tag Support
- Generator includes og:* keys in frontmatter (no filtering)
- Template generates
<meta property="og:*">tags - Template controls which keys become meta tags
Template-Defined HTML Structure
- Template receives model, generates complete HTML
- Generator does not generate any HTML structure
- Clear boundary: Generator = data, Template = HTML
Validation: PostPageCommand tests must pass unchanged (backward compatibility requirement enforces feature preservation)
Rationale: Refactor changes orchestration pattern (eager → lazy), not feature set. All capabilities remain functional through virtual IFolder pattern.
Backward Compatibility Requirements
Direct Integration (No Facade)
Decision: PostPageGenerator removed, PostPageFolder integrated directly into PostPageCommand
Integration Strategy:
- PostPageCommand modified to use PostPageFolder instead of PostPageGenerator
- PostPageFolder.CopyToAsync() called with output destination
- PostPageGenerator class removed after refactor validated
- Command signature unchanged (backward compatibility at CLI level)
Preserved API:
- PostPageCommand CLI signature unchanged
- Parameter meanings unchanged
- Output structure unchanged
- Exception types unchanged (timing shifted to access per lazy generation)
Rationale: Facade adds unnecessary complexity when direct integration achieves same backward compatibility goals. PostPageCommand is the user-facing API, internal implementation can be replaced cleanly.
Validation Criteria
Requirement: Output matches baseline from community planning.md generation
Validation Approach:
- Manual Baseline: Community planning.md generation run (established baseline for correctness)
- Automated Tests: PostPageCommand tests unchanged (validate CLI interface)
- Output Validation: Byte-for-byte comparison with baseline output
Validation Targets:
- Same output structure (filename/index.html + assets)
- Same file contents (identical HTML to baseline)
- Same error messages and exception types (timing shifted to access phase)
- PostPageCommand tests pass unchanged
Error Timing Note: Errors thrown on access (GetFileAsync/CopyToAsync) per lazy generation pattern, not during PostPageFolder construction. Exception types preserved, timing shifted.
Success Criteria
Refactor complete when ALL criteria met:
Gap analysis complete
- All critical gaps resolved (Gap 1: Virtual structure, Gap 2: Lazy generation, Gap 4: Method scope)
- High-priority gaps resolved or acceptably deferred (Gap 5: Asset representation resolved, Gap 3: Component decomposition deferred)
- Architectural certainty established (HIGH certainty on source, destination, transformation patterns)
PostPageFolder implements IFolder fully
- GetItemsAsync method implemented (enumerate IndexHtmlFile + PostPageAssetFolder wrappers + template file passthrough) ✅
- Id and Name properties implemented (derived from source markdown IFile) ✅
- Virtual structure correctly represents output (3 component types: PostPageFolder, PostPageAssetFolder, IndexHtmlFile) ✅
PostPageAssetFolder implements IChildFolder
- GetItemsAsync method implemented (recursive wrapping, file passthrough) ✅
- GetParentAsync method implemented (return stored parent reference) ✅
- Parent property stored for hierarchy (PostPageFolder or PostPageAssetFolder) ✅
IndexHtmlFile implements IFile
- OpenStreamAsync method implemented (trigger transformation, return stream with HTML content, Read mode only, throw exception for write modes) ✅
- Id and Name properties implemented (parent-derived ID format, constant "index.html") ✅
- No caching, direct generation on every access (Option A per user decision) ✅
Lazy generation working
- Construction has no side effects (no file system operations) ✅ Verified via code inspection 2025-11-09
- Content generated on access (IndexHtmlFile.OpenStreamAsync) ✅ Verified via manual testing 2025-11-09
- Multiple instances creatable without filesystem impact ✅
- No synchronization primitives needed (per direct passthrough architecture) ✅
Backward compatibility validated
- PostPageCommand using PostPageFolder directly (PostPageGenerator removed) ✅
- Output matches baseline (community planning.md generation) ✅ Manual validation complete 2025-11-09
- PostPageCommand tests pass unchanged (deferred - no automated tests exist)
- Error types preserved (timing shifted to access phase per lazy generation) ✅
Composition-ready architecture
- PostPageFolder instances composable by parent orchestrators ✅ Construction has no side effects
- Virtual structure enables hierarchy preservation ✅ IFolder/IChildFolder hierarchy implemented
- Ready for PagesFolder integration ✅ Architectural validation via construction cost verification
Code quality maintained
- No regressions in error handling ✅ All 9 capabilities verified working
- Thread safety preserved (no synchronization needed per Option A) ✅
- Stateless instance pattern maintained ✅ No mutable state in components
- Documentation updated ✅ Execution tracking complete
Explicit Exclusion: Transformation "How"
Deliberately Excluded from this Planning Document:
- Implementation approach for PostPageFolder
- Extraction strategy for pure transformation logic (deferred to implementation phase per Gap 3)
- Internal architecture details (resolved through gap analysis - see Gap 1, 2, 4, 5 resolutions)
- Specific refactoring steps or sequence
- Code structure decisions (classes determined, method implementations deferred)
Rationale: Planning phase establishes direction and requirements (what/where). Implementation phase determines approach and execution (how). Premature specification of transformation approach risks over-constraint and reduces implementation flexibility.
Gap Analysis Complete (2025-11-08):
- 8 gap categories identified in transformation domain
- 3 gaps resolved during initial review (Gap 6: Direct integration, Gap 7: Errors on access, Gap 8: Manual validation baseline)
- 3 critical gaps resolved through investigation (Gap 1: Virtual structure, Gap 2: Lazy generation, Gap 4: Method scope)
- 1 high-priority gap partially resolved (Gap 5: Asset representation - core pattern established, edge cases deferred)
- 1 high-priority gap deferred to implementation (Gap 3: Component decomposition - acceptable deferral)
- Architectural certainty: HIGH on source, destination, transformation patterns
- See planning,post-page,refactor,gaps.md for complete investigation findings
Implementation Readiness: All critical gaps resolved, sufficient architectural certainty established. Ready to proceed with implementation execution.
Next Phase: Implementation execution following established patterns from gap analysis investigation.
References
Primary Planning Context:
- Blog Generator Planning - Complete source state documentation, Multi-Page requirements, blocker identification
Historical Context:
- Planning Inventory - Timeline of prior work (October-November 2025)
Requirements Documentation:
- Post/Page Requirements (Oct 12) - Original Single-Page scenario requirements
- Pages Requirements (Nov 1) - Multi-Page scenario requirements establishing refactor need
Investigation Records:
- Design Investigation Complete - Virtual IFolder pattern analysis (Nov 1-3, 2025)
Session Records:
- 2025-11-01: Pages Planning
- 2025-11-01: Gap Analysis
- 2025-11-03: Design Investigation Complete
- 2025-11-08: Planning Restructure
- 2025-11-08: ToC & Status Corrections
Methodology:
- PROCEDURES.md v2.10 - Semantic engine applied throughout planning (Why→What/Where→How recursion, D3+E3 loop, O3 checkpointing)
October Implementation Reference:
- Planning Source (Oct 25) - Complete implementation details, gap analysis, component structure (source for capabilities preservation requirements)
Gap Analysis:
- Refactor Gaps - Gap analysis investigation complete (2025-11-08): 3 critical gaps resolved (Gap 1, 2, 4), 1 high-priority gap partially resolved (Gap 5), 1 high-priority gap acceptably deferred (Gap 3). Architectural certainty HIGH, implementation-ready.
Document Status: Planning complete with comprehensive gap analysis integrated (November 8, 2025). Source requirements (PostPageGenerator current architecture) and destination requirements (PostPageFolder virtual IFolder pattern with implementation preservation) fully documented with HIGH architectural certainty. All critical gaps resolved through systematic investigation: virtual structure pattern (3 component types), lazy generation mechanics (Option A direct passthrough), IFolder method scope, and asset representation patterns established. Implementation-ready - sufficient certainty achieved for execution phase.