Conversation
- Updated class names for clarity and consistency (improves code readability). - Refactored DownloadManager to use new class names. - Enhanced copilot instructions for better architectural guidance.
…completed), update to new Workflows
- Changed the deserializer and serializer to use YamlDotNet's types directly (improves clarity and reduces namespace usage).
…structure - Changed ISlide and IImageShape to inherit from ISlideObject (ensures a unified interface structure). - Updated namespaces in various files to reflect the new structure (improves clarity and organization). - Renamed methods in ISpecializable to Flatten for better semantic meaning (enhances code readability).
…de, tasks - Updated activity constructors to accept identifiers instead of raw strings (improves type safety). - Refined error handling for invalid inputs in several activities (enhances robustness). - Introduced a new FileSystem class for file operations (promotes single responsibility). - Normalized file naming conventions across the application (ensures consistency).
- Moved various activity classes to the new Generation namespace for better organization and clarity (improves maintainability). - Updated constructors in several activities to use required properties instead of nullable ones (enhances type safety). - Updated CleanupResources activity to manage multi temporary resources during workflow execution (promotes resource management).
- Updated namespaces for clarity and consistency (e.g., SlideGenerator.Domain.Slide.Models to SlideGenerator.Domain.Slide.Models.Previews). - Modified method signatures to remove default parameters for better readability (e.g., GetOrOpen method). - Introduced new interfaces and classes for better abstraction (e.g., IImageReplacer, ITextReplacer).
- Changed IRegistry interface to an abstract class Registry for better resource management. - Implemented reference counting for resources to prevent premature disposal. - Introduced RegistryLease for managing resource leases. - Updated GenerationWorkflow and ScanningService to use the new Registry system. - Removed obsolete IPC-related files and endpoints. - Added XML documentation for public APIs in the new registry classes.
…ion tasks - Removed the RegistryLease class from Common and moved it to Resources. - Introduced a new Lease base class for better resource management. - Implemented a Registry class for shared, reference-counted resources. - Added AcquireActivitySlot, AcquireEditingSlot, and AcquirePreparingSlot activities for managing concurrency. - Created ActivityGate and ActivityLease classes to control and represent activity execution permits. - Updated GenerationWorkflow to utilize new concurrency management features. - Replaced usages of the old RegistryLease with the new implementation across various activities. - Enhanced JobSetting to include separate max concurrent flows for preparing and editing.
- Renamed FaceDetectorModelKey to FaceDetectorModel for clarity. - Updated RoiType enum values for better understanding (RuleOfThirds to Center and vice versa). - Introduced new classes for CenterOption and RuleOfThirdsOption to encapsulate ROI strategies. - Refactored FaceDetector and related classes to improve maintainability and readability.
…tingWorkflow This commit introduces a major refactoring of the GeneratingWorkflow to improve concurrency control, modularity, and clarity. The previous implementation used rigid, monolithic activities for batch processing, which has been replaced with a more flexible, granular, and robust system. Key Changes: - **Introduced `IAsyncKeyedLocker<SlotType>`:** Replaced hardcoded concurrency limits with a generic, keyed locking mechanism. This allows defining flexible, process-wide concurrency gates (e.g., for downloads, image edits, slide edits) that are managed centrally. - **Generic `AcquireSlot` and `ReleaseSlot` Activities:** The numerous specific slot activities (e.g., `AcquirePreparingSlot`, `AcquireEditingSlot`) have been removed and replaced by a single, reusable pair of `AcquireSlot` and `ReleaseSlot` activities. These now operate on the `IAsyncKeyedLocker` by specifying a `SlotType`. - **Parallel-Per-Item Processing:** The monolithic `DownloadImages` and `EditImages` activities have been replaced with a `ParallelForEach` pattern. The workflow now iterates over a collection of items (URLs or paths), and each item is processed individually within its own `AcquireSlot`/`ReleaseSlot` block. This provides fine-grained control over the degree of parallelism. - **Renamed Activities for Clarity:** - `SummarizeDownloadedImagePaths` is now `ReadDownloadedImagePathStore`. - `SummarizeEditedImagePaths` is now `ReadEditedImagePathStore`. This better reflects their function of reading results from the transient workflow store. - **Code Cleanup:** Removed numerous obsolete activity files and updated dependencies across the workflow and related services to support the new architecture.
…gine - Introduced a custom workflow DSL (`Activity`, `Sequence`, `ForEach`, `ParallelForEach`, `Inline`, `SlotGated`) and state management (`Variable`, `IExecutionContext`) in the Application layer. - Added `IActivityFactory` to abstract workflow activity creation. - Migrated all generation workflow activities to use the new Application-layer DSL, removing direct Elsa dependencies (e.g., `Input<T>`, `Output<T>`, `ActivityExecutionContext`). - Refactored `GeneratingWorkflow` to construct the execution tree using the new `IActivityFactory` and strongly-typed context variables. - Introduced `WorksheetContextRules` to standardize workflow variable keys for the generation process. - Implemented Elsa-specific adapters (`ElsaSequence`, `ElsaInline`, `ElsaForEach`, `ElsaParallelForEach`, `ElsaSlotGated`, `ElsaVariable`, `ElsaExecutionContext`) and `ElsaActivityFactory` in the Infrastructure layer. - Reorganized the project structure by moving generating and scanning components to the `Services` namespace. - Updated `CLAUDE.md` to document the architectural constraint that Elsa dependencies must reside exclusively in the Infrastructure layer.
- Added extensive XML documentation to interfaces, classes, properties, and methods across the Domain, Application, and Infrastructure layers to improve maintainability. - Relocated IClientService to the Cloud.Abstractions namespace for better logical organization. - Enhanced resource management by adding GC.SuppressFinalize(this) to the Dispose methods of Mat, StreamTextFile, and YuNet. - Improved thread safety in YuNet.DeInitAsync by ensuring the semaphore is released within a try-finally block. - Removed the obsolete ImageReplacer class in favor of using PictureReplacer and BlipFillReplacer directly. - Corrected an exception message typo in StreamTextFile.Write to accurately reflect read-only file states.
…ttening - Introduced the `InstructionResolver` service to evaluate general instructions against row content and produce specialized instructions with actual values. - Updated the `ISpecializable.Flatten` method signature to require row data, shifting value resolution logic to the execution phase. - Modified `SpecializedInstruction` models for both images and texts to store resolved values (`Uri` and `string`) rather than column source definitions. - Refactored the `SpecializeInstructions` activity to filter and store general instructions in the context, removing premature flattening. - Adapted `ResolveImageUrls`, `DownloadImage`, `ResolveImagePathsFromDisk`, and `ReplaceSlideContents` activities to process the new instruction structure and per-row resolved values. - Added a `Utilities.NormalizeUri` helper to standardize URI formatting during image resolution.
- Introduced a custom Workflow-as-Code DSL (e.g., `SequenceNode`, `ForEachNode`, `TryNode`, `SlotGatedNode`) in the Application layer. - Implemented strongly-typed, stateless `Variable<T>` and `IActivityContext` for lexical scoping and isolated state management across concurrent branches. - Integrated `WorkflowCore` in the Infrastructure layer to interpret and execute the custom DSL via a single `WcInterpreterStep`. - Refactored `GeneratingWorkflow` and all related activities to implement `ILeafActivity<WorkflowTask>`, completely removing dependencies on Elsa. - Added hierarchical state monitoring (`ExecutionState`, `WorkflowState`) and thread-safe, in-memory logging (`Logger`, `LogEntry`). - Replaced `ScanningService` with modular `ScanWorkbook` and `ScanPresentation` leaf activities. - Redesigned temporary output paths to use deterministic, hash-based folder structures for downloaded and edited images. - Renamed replacer abstractions to composers (e.g., `ITextReplacer` to `ITextComposer`, `IImageReplacer` to `IImageComposer`). - Extended the `IImage` interface and its `Mat` implementation with `Crop`, `Resize`, and `SaveAsync` capabilities. - Updated `INSTRUCTIONS.MD` and `CLAUDE.md` to document the new DSL architecture, variable scoping rules, and strict dependency constraints.
- Relocated multiple root application directories (Cloud, Download, Images, Resources, Settings, Slides, Systems, Workflows) into a new `Modules` directory to improve project organization. - Updated namespaces and using directives across the Application and Infrastructure layers to align with the new `Modules` path structure. - Moved WorkflowCore-specific infrastructure implementations (e.g., `WcWorkflowService`, `WcInterpreterStep`, `WcExecutionContext`) into a dedicated `Adapters` subfolder within `SlideGenerator.Infrastructure/Workflows`. - Deleted the obsolete `AssemblyInfo.cs` file from the Application project. - Updated the Application `.csproj` file to replace the old `Cloud` folder inclusion with the new `Modules` directory.
…bust resource locking - Replaced `ExecutionState` with `ExecutionSnapshot`, splitting transient context (`IExecutionContext`) from persisted variables (`IExecutionPayload`). - Eliminated mutable state objects (e.g., `SheetTask`) in favor of strict, scope-based `Variable<T>` persistence for workflow execution. - Refactored `Registry` implementations to support shared-read/exclusive-write locking using `FileLocker`. - Introduced `GateLocker`, `GateNode`, and `GateType`, replacing `SlotGatedNode` and `SlotType` for granular concurrency throttling. - Extracted custom DSL interpretation logic from `WcInterpreterStep` into a dedicated `WorkflowInterpreter` service. - Updated generation and scanning activities to manage data, instructions, and open file leases exclusively through scope variables. - Introduced `Lease<T>` and `ILock` abstractions for reliable, strongly-typed resource lifecycle management. - Updated documentation (INSTRUCTIONS.MD, CLAUDE.md) to reflect new variable scope rules, registry patterns, and strict architectural constraints.
…e and sheet processing - Replaced OpenXML and ClosedXML implementations with Syncfusion equivalents (e.g., SfPresentation, SfReadOnlyWorkbook, SfImageComposer, SfTextComposer). - Moved sheet identifier models (e.g., WorkbookIdentifier, WorksheetIdentifier) to the Models.Identifiers namespace for better organization. - Introduced GetPreview methods in IReadOnlyWorksheet, IReadOnlySlide, and IReadOnlyShape to generate lightweight visual and data previews. - Simplified SlidePreview to only retain the index and image, promoting Id and Name metadata to SlideSummary. - Updated ScanWorkbook and ScanPresentation activities to adopt the new preview rendering approaches. - Removed the Scan method from IImageComposer as preview extraction is now handled directly by shapes. - Cleaned up obsolete XML and ClosedXML adapters, services, and the GeneratingService. - Added Syncfusion packages to the infrastructure project dependencies.
… wrapper - Removed `WorkflowTask` class and updated generating activities and nodes to consume `GeneratingRequest` directly. - Introduced `ScanningWorkflow` and `ScanningRequest` to encapsulate and separate scanning logic from generation. - Created `ScanningVariables` to define specific variables for scanning, aliasing them within `VariablesDeclaration`. - Updated `GeneratingWorkflow` to execute the new `ScanningWorkflow` during its initial parallel scanning phase. - Modified `ScanWorkbook` and `ScanPresentation` activities to implement `ILeafActivity<object>`, improving reusability. - Added `TryGetVariable` to `IActivityContext` and implemented it in `WcInterpreterContext` for safer variable resolution across nested scopes.
…ing activity DSL - Removed the centralized `WorkflowInterpreter` and record-based DSL nodes (e.g., `SequenceNode`, `ForEachNode`, `TryNode`). - Introduced an object-oriented `Activity<TData>` base class with self-executing implementations (`Sequence`, `Parallel`, `ForEach`, `Try`, `Condition`, `Inline`, `GateWrapper`). - Renamed `Variable<T>` to `Handle<T>` to better represent workflow-scoped references. - Renamed `IActivityContext` to `IExecutionContext`, renaming the `State` property to `Snapshot` and exposing an `IServiceProvider` for internal dependency resolution. - Renamed `IWorkflowDefinition` to `IWorkflow` and updated implementations to construct activity trees using the new DSL. - Refactored `GateLocker` to be a generic `GateLocker<TGate>` that accepts a resolver function for determining concurrency limits. - Updated all leaf activities to implement `Activity<TData>` instead of `ILeafActivity<TData>`. - Updated WorkflowCore infrastructure adapters (`WcWorkflowService`, `WcInterpreterStep`, `WcInterpreterContext`) to support the new interface types and execute the activity tree directly. - Updated architectural documentation (`INSTRUCTIONS.MD`, `CLAUDE.md`) to reflect the new terminology and scope management guidelines.
Turn RecipeEditorViewModel from a P4.1 skeleton into a real coordinator: owns SlideCanvasViewModel/TextBindingsViewModel/WorksheetSourcesViewModel, loads a mapping's presentation+workbook summaries via ISummaryCache, and feeds all three panels the same flattened column list. MappingEditSession gives the mapping navigator a stable identity per mapping (a record's own identity changes on every edit) and owns the touched-placeholder/shape sets so switching mappings and back no longer reverts a confirmed Normalized binding to Suggested. Fixes two data-loss gaps found while wiring the coordinator: TextBindingsViewModel used to reset its touched set on every Load; and SlideCanvasViewModel had no way to project edits back to ImageInstruction, which would have silently dropped existing RoiOptions/FallbackImagePath. RecipesViewModel now resolves a real RecipeEditorViewModel per edit/new session and RecipeEditorView replaces the P3 placeholder overlay. Mapping add/remove/reorder and the ROI/fallback inspector still need a template-slide picker that doesn't exist yet - left for the next P4.4 pass.
…ping remove/reorder Advisor review of the P4.4 coordinator (2914d60) found a real bug: when a mapping's template slide no longer exists, LoadMappingAsync bails early and the three panels keep showing the previous session's content while SelectedSession already points at the new one - the next switch would project stale content onto the wrong mapping. Fixed with a _loadedSession guard that only lets ProjectCurrentSessionEdits run against the session actually reflected in the panels. Also drop the unconditional IsDirty = true in ProjectCurrentSessionEdits - it fired on pure navigation (just looking at a different mapping), not just on edits. Real dirty tracking belongs to P4.6; leaving it alone here is more correct than a half-measure. Add ToRecipe() (needed for P4.6's save path - nothing previously pulled edits back into the Recipe field) and mapping remove/reorder commands, which turned out not to need the template picker the previous commit's deferral note claimed - only adding a new mapping does.
…P4.4) ShapeOverlayViewModel.RoiOptions is now a live ObservableCollection instead of a read-only snapshot, with EditInstruction recomputed from it on read - move/remove commands live on the overlay itself since they need no external dependency. FallbackImagePath becomes an [ObservableProperty] so the inspector can edit it directly. RecipeEditorViewModel keeps the IFilePicker it already received (only handed to Sources before) and adds PickFallbackImageCommand, since a file picker is the one thing these edits need that ShapeOverlayViewModel shouldn't own itself. Deliberately does not support creating a new RoiOption (Anchor vs Interest configuration is its own UI surface) - task 32 only asked for reordering the existing chain.
RecipeEditorViewModel.AddMappingCommand opens TemplatePickerView (presentation + slide picker with real thumbnails) via IDialogService.ShowTemplatePickerAsync, appending a new Mapping and selecting it. fix(document): configure font fallback on presentation open SfSlide.GetPreview() threw NullReferenceException in Syncfusion's FontSettings.GetFont during text layout for any real presentation, since SfPresentationOpener never configured font substitution/fallback nor set IPresentation.PresentationRenderer. Found via windows-mcp smoke test of the new template picker against a real .pptx fixture (previous smoke tests only used synthetic ViewModel data). Also affects the existing P4.2 canvas preview. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YVAEGbVw6p9tfS271RDBGz
There was a problem hiding this comment.
🟡 Changes recommended
Several reviewed changes introduce concrete correctness/maintenance risks (e.g., backoff overflow behavior, event handler subscription leak, invalid concurrency values, analyzer version split) that should be addressed before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 253/716 changed files
- Comments generated: 7
- Review effort level: Lite
Comment on lines
+27
to
+33
| public static TimeSpan ComputeBackoffDelay(int attempt, TimeSpan maxDelay) | ||
| { | ||
| var jitterMs = Random.Shared.Next(0, 1001); | ||
| var backoff = TimeSpan.FromSeconds(System.Math.Pow(2, attempt)) + TimeSpan.FromMilliseconds(jitterMs); | ||
| return backoff < maxDelay ? backoff : maxDelay; | ||
| } | ||
| } |
Comment on lines
+17
to
+19
| /// <summary>Pure math helpers for retry/backoff calculations.</summary> | ||
| public static class Math | ||
| { |
Comment on lines
+22
to
+34
| public static class Registration | ||
| { | ||
| /// <summary> | ||
| /// Adds summarization services to the service collection. | ||
| /// </summary> | ||
| /// <param name="services">The service collection to add services to.</param> | ||
| /// <returns>The updated service collection.</returns> | ||
| public static IServiceCollection AddSummarizationServices(this IServiceCollection services) | ||
| { | ||
| services.AddSingleton<ISummarizationService, SummarizationService>(); | ||
| return services; | ||
| } | ||
| } |
Comment on lines
+20
to
+32
| /// <summary>View for <see cref="RunDialogViewModel" />. Closes itself when the ViewModel requests it.</summary> | ||
| public sealed partial class RunDialogView : Window | ||
| { | ||
| /// <summary>Constructs the view and loads its XAML.</summary> | ||
| public RunDialogView() | ||
| { | ||
| InitializeComponent(); | ||
| DataContextChanged += (_, _) => | ||
| { | ||
| if (DataContext is RunDialogViewModel vm) vm.RequestClose += started => Close(started); | ||
| }; | ||
| } | ||
| } |
Comment on lines
+21
to
+25
| internal sealed class SettingConcurrencyProvider(ISettingProvider settingProvider) : IJobConcurrencyProvider | ||
| { | ||
| /// <inheritdoc /> | ||
| public int MaxConcurrentJobs => (int)settingProvider.Current.Performance.MaxConcurrentJobs; | ||
| } |
| @@ -0,0 +1 @@ | |||
| SYNCFUSION_LICENSE_KEY= # Syncfustion License Key No newline at end of file | |||
Comment on lines
+6
to
+8
| <ItemGroup> | ||
| <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="all"/> | ||
| </ItemGroup> |
…alidation Canvas: double-click an image-shape overlay opens a quick column-assign dropdown (Flyout), completing the P4.4 remaining item deferred since P4.2. Editor: RecipeEditorViewModel gains Id/Name, a SaveCommand (blocked while clean, while a binding still needs Ambiguous resolution, or while Name is blank) wired to IRecipeRepository Add/Update, and real dirty tracking via explicit Changed events from the three child panels (not from Load, so switching mappings never marks dirty). RecipesView's editor header gets an editable Name textbox and a Lưu button; closing the editor while dirty now confirms via the existing ConfirmDialog before discarding edits. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YVAEGbVw6p9tfS271RDBGz
…ting RecipeEditorViewModel gains IsGuided/GuidedStep (Template/Data/Binding/ Review) driving which panels RecipeEditorView shows — one ViewModel, one Recipe, the flag only swaps the displayed template per plan §5.2.a. New recipes start Guided; recipes opened from the list start Advanced. All four Guided steps reuse the existing SlideCanvasView/TextBindingsView/ WorksheetSourcesView panels, no new controls. Also corrects a P4.6 regression found while reading the Guided-mode design more carefully: the plan blocks "Lưu và chạy" on unresolved Ambiguous bindings, not plain "Lưu" (which the previous commit incorrectly gated on both). SaveCommand now only requires dirty + a name; the new SaveAndRunCommand (Guided step ④) carries the Ambiguous-block and opens the existing run dialog after saving. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YVAEGbVw6p9tfS271RDBGz
…bout)
New SettingsViewModel/SettingsView: Theme + Language + reduced-motion
radios/checkbox, MaxConcurrentJobs slider with a scoped "restore default"
that resets only Performance, proxy/retry/download-size network fields, and
an About block (version/license/repo + a working "check for updates" button
wired to UpdateChecker, whose CheckForUpdatesAsync now returns a result
instead of only logging). Every field persists immediately through
ISettingManager, matching ThemeService's existing no-debounce convention.
Also fixes a pre-existing gap this page's Language switch surfaced: neither
UpdateChecker's TODO ("hook into a real notification once the main UI
exists") nor live language refresh had ever been exercised by a real UI
control before. Live language refresh itself turned out not to work in this
app's compiled-binding pipeline — reverted to the known-safe (restart-to-
apply) form and documented the gap in TrExtension rather than risk further
regressions chasing it; Theme's live refresh is unaffected (different,
working mechanism).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YVAEGbVw6p9tfS271RDBGz
Indexer binding (Binding("[key]")) against LocalizationService's
INotifyPropertyChanged instance never re-fired in this app's compiled
XAML pipeline, even though every other named-property binding worked.
Add ILocalizationService.Revision (a plain int counter bumped on each
SetLanguage call) and bind TrExtension to that instead, routing the
actual resource lookup through a new LocalizedTextConverter parameterized
by the resource key. Confirmed live via windows-mcp: switching the
Settings language radio now updates the whole UI immediately.
Ctrl+S saves the open recipe, Ctrl+F focuses the recipe search box, Delete removes the selected recipe/run (list-scoped via ListBox.KeyBindings so it doesn't collide with per-row delete buttons), Escape closes ConfirmDialog/RunDialogView/TemplatePickerView via Button.HotKey.
…ngs (P5 task 37/38 audit) ReducedMotion persisted correctly but had no consumer — Tokens.axaml and SplashView.axaml comments described an App.axaml.cs ApplyReducedMotion that was never written. ThemeService.ApplyFromSettings now also zeroes (or restores) the MotionUi/MotionBrand duration resources; Settings applies it live on toggle, same pattern as Theme. Known remaining gap: ShellView's CrossFade page transition sets Duration as a literal (not bindable) so it still animates regardless of this setting. Also fixes 4 em-dash violations found during the pre-flight audit (plan §10 bans them in display strings): SettingsMaxConcurrentJobsHint in both resx files, and two update-check status messages.
…design-system gate (P1) Two pre-existing bugs blocked all further frontend work and had to be fixed before any visual polish could be verified: - ThemeService.ApplyReducedMotion read MotionUi/MotionBrand via the Resources indexer, which does not walk merged dictionaries (where Tokens.axaml actually lives) — returned null, and casting null to the non-nullable TimeSpan threw a NullReferenceException on every startup. Confirmed independent of any other change by reverting to a clean stash and reproducing identically. Fixed via Application.TryGetResource, which does walk merged dictionaries. - ThemeService.SetThemeAsync awaited settingManager.Update(...) with ConfigureAwait(false); since SettingManager.Update() also uses ConfigureAwait(false) internally, execution resumed on a thread-pool thread, where ApplyFromSettings() then set Application.RequestedThemeVariant (an Avalonia styled property) off the UI thread — silently swallowed, since SettingsViewModel.OnThemeChanged calls SetThemeAsync fire-and-forget with no continuation to observe a fault. Runtime theme switching therefore never worked. Fixed by using ConfigureAwait(true), matching the UI-thread-affinity convention already documented in App.axaml.cs's StartupAsync. Verified live: toggling Light/Dark in Settings now re-themes the running app immediately. Also, foundation work for the Avalonia frontend overhaul (blueprint P1): - DesignSystemTests.cs: a machine-checkable design-system gate (contrast, raw-color/duration/radius, spacing grid, placeholder markers, divider discipline) that parses the token XAML directly, no Avalonia runtime needed. Found 5 real WCAG AA contrast failures on first run; fixed by darkening/lightening NeutralLight500/NeutralDark400 and switching dark mode's TextOnAccentBrush off white (3.24:1 on BrandDodger). - Extended tokens: elevation (BoxShadows), RadiusFull, Thickness1-6, focus-ring shadow, accent hover/pressed brushes (unused pending a follow-up to brand Semi's native Primary/Danger button colors). - Removed custom .primary/.danger/.ghost/.icon button-variant styles after confirming via Avalonia DevTools that Semi.Avalonia's own Button ControlTheme implements :pointerover/:pressed at StyleTrigger priority, which always outranks a plain Style-priority override — idle-state colors applied fine, hover/pressed silently fell back to Semi's own grey. RecipesView's New recipe/Import buttons now use Semi's native Classes="Primary"/"Tertiary" instead. - MainWindow/ShellView page transitions moved from a literal XAML <CrossFade Duration="0:0:0.15"/> (ignored Appearance.ReducedMotion, and is a raw-duration violation of the new gate) to code-built CrossFade reading the same Motion* resource. 102/102 Desktop tests pass (95 existing + 7 new). No ViewModel touched.
…ination (P2a) Application shell rebuild per the Avalonia frontend overhaul blueprint, phase P2a (shell chrome). Splits the old 200px sidebar out in favor of a persistent top toolbar: brand mark, a centered Recipes/Runs nav pill, theme-toggle/ About/Settings icon buttons, and self-drawn caption buttons — all in one 48px row (TitleToolbar.axaml), replacing the system titlebar via ExtendClientAreaToDecorationsHint + WindowDecorations="BorderOnly" (the one configuration, of the three tried during the P0 spike, where maximize actually fills the work area and Win11 Snap Layouts still triggers on hovering the maximize button). ShellDestination gains a fourth value, About, landing on the existing (previously unreachable) PlaceholderPageViewModel default branch until P6 builds the real page. ShellViewModel gains ToggleThemeCommand (cycles System -> Light -> Dark) so the toolbar's theme button doesn't need its own DI wiring — it binds to the same ShellViewModel instance ShellView's page host already uses, inherited via DataContext, no plumbing added. TitleToolbar deliberately lives in ShellView, not MainWindow: the nav pill's active-state binding needs ShellViewModel, which isn't associated with any content while Splash is showing. Splash therefore has no toolbar at all (accepted trade-off: no mouse-driven move/minimize/close during the brief, non-interactive splash window; taskbar/Alt+F4 still work). Found by hands-on testing, not covered by any prior research: every interactive control placed inside a Border carrying WindowDecorationProperties.ElementRole="TitleBar" silently stops receiving clicks unless it also declares its own ElementRole. The three caption buttons worked because MinimizeButton/MaximizeButton/CloseButton are themselves recognized roles; the nav pill and the three icon buttons had none and were swallowed by the ambient drag surface exactly like clicking blank space on a real titlebar. Fixed by adding ElementRole="User" (documented as "an interactive element set by user code that should receive input even when overlapping chrome areas") to all five. Verified live: Recipes/Runs/ About now switch correctly. Theme reveal animation, page-transition upgrade, window-title progress, and BrandLockup extraction are deferred to P2b to keep this diff reviewable. 102/102 Desktop tests pass. No ViewModel other than ShellViewModel touched (it owns 0 tests and is explicitly in scope for this phase per the plan).
… 4-stage BrandLockup (P2b)
Motion polish pass on top of P2a's shell chrome, per the frontend overhaul
blueprint:
- ThemeService.SetThemeAnimatedAsync(mode, origin): the toolbar's theme
button now switches with a circle expanding outward from the clicked
button instead of an instant repaint. Renders the old theme into a
RenderTargetBitmap, overlays it via Avalonia's built-in OverlayLayer (no
need to hand-roll an adorner Panel in MainWindow.axaml), yields one frame
so the overlay is actually composited, swaps the theme underneath it, then
animates a growing exclude-clip (CombinedGeometry + EllipseGeometry) via
TopLevel.RequestAnimationFrame. Falls back to the instant SetThemeAsync
under ReducedMotion, no main window, or a zero-size window.
TitleToolbar's code-behind computes the origin (the button's own
translated position) since that's a view-layer concern, not ShellViewModel
state; ShellViewModel.ToggleThemeAsync(Point? origin) picks animated vs.
instant based on whether one was supplied.
- BrandLockup extracted from SplashView into Components/, upgraded from one
simultaneous 400ms beat to four real sequential stages (hold on the icon
alone, animate, land on the full lockup, hold) matching the product
brief's "logo -> animation -> full -> hold". Both holds collapse to zero
under ReducedMotion. App.axaml.cs's splash-duration floor is now computed
from BrandLockup.GetTotalDuration(motionBrand) instead of a fixed 420ms,
so the shell can no longer swap in before the (now ~900ms) animation
finishes.
- ThemeService.BuildPageTransition composes CrossFade + PageSlide via
CompositePageTransition, replacing P1's plain CrossFade for both
MainWindow and ShellView's page hosts.
- MainWindowViewModel.WindowTitle shows how many jobs are active
("SlideGenerator - N job running"), tracking IProgressHub.Jobs. Simplified
from the v1-style "5/727 slide" the product brief references: JobSnapshot
has no row-total field yet (that's P5 scope), so this counts active jobs
rather than fabricating a percentage from data that doesn't exist.
Verified live, not just by code review: built and ran the app, used Win32
SetForegroundWindow/MoveWindow (via a PowerShell Add-Type shim) to target
the exact window handle — this session's own Rider IDE window also titles
itself "SlideGenerator", which was causing window-switching tools to land on
the wrong window — then clicked the theme toggle twice in a row. Both
switches completed with the correct end-state theme, no exception in the
log, and no leftover overlay artifact.
102/102 Desktop tests pass, full solution builds clean. No ViewModel other
than ShellViewModel/MainWindowViewModel touched (both explicitly in scope
for this phase; neither carries a test fence).
…ning hardcode (P5 P3) Renames all 70 resx keys from PascalCase to dot-key (nav.recipes, settings.theme.dark, ...), adds the 16 missing vi.resx Recipes* keys, and extracts remaining hardcoded strings in Shell/Recipes/Runs/Settings (confirm dialogs, file-picker titles, update-status messages) behind the same keys. Adds EnumLocalizedTextConverter (MultiBinding + Revision, mirrors TrExtension's live-refresh trick) and wires it onto the JobStatus/JobPhase bindings in RunsView/RecipesView so those no longer show raw English enum names under vi. Fixes two bugs found while migrating: LocalizationService.Instance threw NRE in ViewModel unit tests that construct the VM outside DI (now lazily self-initializes instead of staying null), and DesignSystemTests' line-pinned KnownViolations entry for RecipesView.axaml had drifted after an insertion above it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0178xLpgqmda2BotNXUetREm
…adge (P4a) Recipe selection now fetches the full RecipeEntry (mirrors the existing recent-runs fetch) to compute mapping/source/text/image counts, plus a records estimate summed from each worksheet source's ISummaryCache row count — failures fall back to an em dash rather than blocking the view. Renders as five reused pill chips with a skeleton placeholder while counting, and adds a Saved/Unsaved pill next to the editor's Save button using two loc:Tr-bound TextBlocks toggled by IsDirty (no new converter). Also swaps the recent-runs loading "..." for the existing skeleton style, shrinking DesignSystemTests' KnownViolations by one tracked entry. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0178xLpgqmda2BotNXUetREm
…P4b)
Gives the two dead suggested/needs-selection classes on the column
ComboBoxes a real style (amber/red border) instead of no matching selector
at all, adds :pointerover feedback to canvas shape overlays, and wraps the
column-label chip in a proper padded/radius pill instead of a bare
margined TextBlock.
Replaces the Guided mode's numbered title ("① Mẫu slide") with a real
4-dot stepper plus a plain localized title, and swaps the ROI list's raw
Anchor/Interest mode text for a new RoiDescriptionConverter that explains
each option's concrete AnchorType/InterestType in plain language.
Extracts the remaining hardcoded Vietnamese across RecipeEditorView,
TextBindingsView, WorksheetSourcesView, BindingSummaryConverter, and
GuidedStepTitle behind new recipeEditor.*/enums.roiMode.*/enums.anchorType.*/
enums.interestType.* keys (40 new entries, both locales).
Swaps the editor's "..." loading overlay for Semi's ProgressBar
IsIndeterminate, clearing DesignSystemTests' KnownViolations to empty —
every entry tracked since P1 is now fixed rather than re-pinned.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178xLpgqmda2BotNXUetREm
…ger variant (P4c) TemplatePicker's slide grid becomes a real ListBox bound to SelectedSlide instead of a plain ItemsControl with a manual pointer-pressed handler — gets a free :selected pseudo-class, a check badge, and an accent border on the picked thumbnail with no new binding machinery. Same style selectors also sharpen the existing selected-row look on Recipes/Runs' own lists, which reuse the same hairline-row class. Fixes RunDialog's "Sẽ tạo3file" (no spaces around the count) by folding the spacing into the resx values instead of bare XAML literals, and splits its single generic conflict badge into the two real ConflictKind values via EnumLocalizedTextConverter. Adds an opt-in danger variant to ConfirmDialog (IDialogService.ConfirmAsync gains a defaulted `danger` parameter — no existing call site needs changes): a warning icon plus a red confirm button, wired to the two actual delete confirmations (recipe, request) but left off the leave-unsaved-editor prompt, which isn't destructive in the same sense. Extracts remaining hardcoded strings across both dialogs and their ViewModels behind new recipeEditor.templatePicker.*/runDialog.*/ enums.conflictKind.* resx keys. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0178xLpgqmda2BotNXUetREm
…ns live UI (P5) JobSnapshot/JobSummary gain an optional TotalRows (known from the moment SlideGenerationWorkload starts, threaded through every phase-transition and per-row report). Persisting it needs a new migration — 002_add-total-rows-to-jobs.sql (ALTER TABLE) — since 001_2.0.0.sql already ran on any existing Data.db and CREATE TABLE IF NOT EXISTS alone would silently no-op there; verified by relaunching against this session's own pre-existing Data.db, not just a fresh test database. Runs' ProgressBar becomes determinate (Value=CurrentIndex/Maximum=TotalRows) with a fallback to indeterminate when TotalRows is null, correct for every job status rather than only "indeterminate while Running". Wires up IProgressHub.Rows (previously collected but never consumed) into a live per-job activity line (stage + note), following the same CollectionChanged-subscribe pattern already used for Jobs. Also gives the log pane a header (using the previously-dead runs.logsHeader key), a copy-to-clipboard button, level-based coloring (new LogLevelBrushConverter), and scroll-to-end on new lines. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0178xLpgqmda2BotNXUetREm
…s (P6) Adds Features/About: an AboutViewModel/View showing the BrandLockup replay, bilingual tagline, an update-check card (moved from Settings' old closing "Giới thiệu" block), live Developers (GitHub REST contributors API) and Supporters (sponsors.json published by a new CI workflow) lists, and repository/sponsor links. Both network calls are disk-cached (24h) and fail soft to an empty list — a network problem never surfaces as an error on this page. Developers/Supporters show only real, fetched data — no fabricated fallback roster, since no official contributor role map exists yet (plan §8-Q2). Avatars are an initials-circle placeholder rather than downloaded images, a deliberate scope cut noted in code. Settings: the theme picker becomes three card-style ToggleButtons (icon + label, reusing the .chip technique already proven for Runs' filter row rather than restyling RadioButton's own template untested), and the four Retry/Network TextBoxes become NumericUpDown. The old "Giới thiệu" block is gone, superseded by the About page. Adds .github/workflows/sponsors.yml: reads FUNDING.yml's github: profiles, queries GraphQL sponsorshipsAsMaintainer, and publishes sponsors.json to a data branch the app reads from — degrades to an empty file until the SPONSORS_PAT secret is added (plan §8-Q5). Validated with actionlint per the authoring-github-workflows skill (exit 0, no regressions in existing workflows). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0178xLpgqmda2BotNXUetREm
…aceholderPageView (P7 part 1) - Add 4 HeadlessUnitTestSession-driven smoke tests (Recipes/Runs/Settings/About views construct + resolve resources without throwing). Bypasses Avalonia.Headless.XUnit's [AvaloniaFact] entirely: its AvaloniaFactDiscoverer reflects into a Xunit.v3.TestIntrospectionHelper overload that no longer matches this repo's xunit.v3 4.0.0, throwing MissingMethodException at test discovery. HeadlessUnitTestSession itself has no xunit coupling, so driving it from a plain [Fact] sidesteps the incompatible discoverer while keeping the real App resource pipeline. - Swap PackageReference from Avalonia.Headless.XUnit to plain Avalonia.Headless (only the runtime package is needed now). - Remove PlaceholderPageViewModel/PlaceholderPageView: all 4 ShellDestination values now resolve to a real page, so the fallback branch was dead. Replace it with a throw (UnreachableException) so a future missing case fails loudly instead of silently showing a placeholder. Drop the now-unused shell.placeholder.message resx key and the matching DesignSystemTests allowlist entry. - Fix CLAUDE.md drift found while investigating: actual migration scripts are 001_2.0.0.sql + 002_add-total-rows-to-jobs.sql (not the old 0001/0002/0003-named scripts); DatabaseMigrator.Migrate is now called from SlideGenerator.Desktop/Program.cs (SlideGenerator.Stdio no longer exists); NameAndPaths.cs lives under Settings/Immutable/, not Settings/Rules/; test package versions (xunit.v3, xunit.runner.visualstudio, NSubstitute) were documented as older majors than what every test project actually pins. Full solution: 536/537 tests green (1 pre-existing Syncfusion-license skip), Desktop test project 106/106 (102 existing + 4 new). Known gap (flagged, not fixed here): CLAUDE.md's "IPC Layer (SlideGenerator.Stdio)" section and related tables still describe the removed JSON-RPC sidecar architecture wholesale — a much larger rewrite than this pass's scope, needs explicit go-ahead before touching.
…, reduced motion (P7 part 2) - RecipesViewModel: stats aggregation across mappings/sources (record count sums per-worksheet counts from ISummaryCache) and the failure fallback (a summary-cache error nulls RecordCount but leaves the other counts intact). - JobRunViewModel: TotalRows round-trips through both entry points (JobSummary constructor, ApplyLiveUpdate from JobSnapshot); ApplyLiveRowUpdate overwrites rather than accumulates. - AboutViewModel.LoadAsync against a mocked IAboutDataService: populates both lists, empty results don't throw, second call doesn't re-fetch. - ThemeService.ApplyFromSettings reduced-motion branch: toggling zeroes and restores the three Motion* tokens. Add HeadlessTestSession as the one shared HeadlessUnitTestSession for the whole test assembly. Each test class independently calling HeadlessUnitTestSession.StartNew raced to initialize the process-wide Application.Current when xunit ran the classes in parallel, causing intermittent failures in ViewConstructionTests/ThemeServiceTests only when run together (not alone) — confirmed by running the full suite 4x after the fix with no flakes. Full solution: 548 tests, 547 green, 1 pre-existing Syncfusion-license skip.
…vatar contrast (P7 part 3) Extended DesignSystemTests.ContrastPairs to cover brush pairs added since P1 that weren't yet gated: TextOnAccentBrush on Success/Warning/DangerBrush (Border.pill.success/.warning/.danger), and AccentBrush on AccentMutedBrush (Border.pill.info + Button.nav-item.active + Border.avatar-circle). The new AccentBrush/AccentMutedBrush pair failed immediately: dark theme was 2.57:1, well under WCAG AA. This pair is live in two places already shipped: the toolbar's active nav-pill tab text, and the initials text on About page's developer/supporter avatar circles — both currently render under the readability threshold in dark mode. Fix: darken dark-theme AccentMutedBrush from #2A4E85 to #152742 (halved RGB, same hue/ratio, just darker) — 4.63:1, clears AA. Light theme's value was already fine, left unchanged. Only 5 usages of this brush exist app-wide, all "muted background + accent text/border", so darkening only helps contrast in every one of them. Full solution: 548 tests, 547 green, 1 pre-existing skip.
Complete domain reference derived from the V2 codebase (not CLAUDE.md, which still describes the removed IPC sidecar / Tauri frontend). 21 sections: core domain, every concept and entity, relationships, user workflows, state machines, use cases, Recipe/Template/Runs deep-dives, settings, file-system model, persistence, events, validation/errors, architecture, glossary, V1-vs-V2 differences, domain diagram, and design constraints. Intended for an external UI/UX design agent. Also records code/doc contradictions found while writing: the inert AllowLocalPaths request flag, the missing request-level Error aggregate status, and four declared-but-never-emitted enum values (JobPhase.Queued, RowStatus.Waiting, RowStatus.Error, RowStage.CroppingImage). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qa7AsUQCAXew9twMU6A5h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.