diff --git a/assets/i18n/en.json b/assets/i18n/en.json
index 999c286..08c2e11 100644
--- a/assets/i18n/en.json
+++ b/assets/i18n/en.json
@@ -438,4 +438,44 @@
,"Temperatura": "Temperature"
,"Tipo": "Type"
,"VERIFICA": "CHECK"
+ ,"+ Aggiungi Master Library": "+ Add Master Library"
+ ,"+ Singoli file": "+ Individual files"
+ ,"↑ Priorità": "↑ Priority"
+ ,"↓ Priorità": "↓ Priority"
+ ,"Camera, gain, offset e temperatura determinano gli abbinamenti.": "Camera, gain, offset and temperature determine matching."
+ ,"Collega le calibrazioni, importa le immagini e avvia la prima analisi. Nessun originale viene modificato.": "Connect calibrations, import images and start the first analysis. Original files are never modified."
+ ,"Collega librerie, importa immagini e avvia l’analisi": "Connect libraries, import images and start analysis"
+ ,"Collega una o più raccolte di Master. Camera, gain, offset e temperatura determinano gli abbinamenti.": "Connect one or more Master collections. Camera, gain, offset and temperature determine matching."
+ ,"Dark e Bias automatici": "Automatic Dark and Bias"
+ ,"facoltativa": "optional"
+ ,"Gli originali restano in sola lettura.": "Original files remain read-only."
+ ,"Gli originali restano sempre in sola lettura.": "Original files always remain read-only."
+ ,"Hai già una Master Library?": "Do you already have a Master Library?"
+ ,"Il programma leggerà gli header, ricostruirà le notti e proporrà Flat, Dark e Bias.": "The app will read headers, reconstruct observing nights and suggest Flat, Dark and Bias frames."
+ ,"Il programma leggerà gli header, ricostruirà le notti osservative e proporrà gli abbinamenti Flat, Dark e Bias.": "The app will read headers, reconstruct observing nights and suggest Flat, Dark and Bias matches."
+ ,"Immagini": "Images"
+ ,"IMMAGINI DEL PROGETTO": "PROJECT IMAGES"
+ ,"Importa le acquisizioni": "Import your acquisitions"
+ ,"In pochi passaggi colleghi le calibrazioni, importi le immagini e avvii la prima analisi. Nessun originale viene modificato.": "In a few steps you can connect calibrations, import images and start the first analysis. Original files are never modified."
+ ,"Master Library": "Master Library"
+ ,"MASTER LIBRARY": "MASTER LIBRARY"
+ ,"Non ce l’hai? Continua senza: potrai aggiungerla o crearne una in qualsiasi momento.": "Do not have one? Continue without it: you can add or create one at any time."
+ ,"Non ce l’hai? Continua senza: potrai aggiungerla o crearne una in seguito.": "Do not have one? Continue without it: you can add or create one later."
+ ,"Passo 1 · Calibrazioni": "Step 1 · Calibrations"
+ ,"Passo 2 · Immagini": "Step 2 · Images"
+ ,"Pronto per la prima analisi": "Ready for the first analysis"
+ ,"Puoi aggiungerne più di una e ordinarle per priorità.": "You can add more than one and order them by priority."
+ ,"Puoi aggiungerne più di una.": "You can add more than one."
+ ,"Raccoglie Dark e Bias riutilizzabili. Camera, gain, offset, temperatura ed esposizione determinano gli abbinamenti automatici.": "It contains reusable Dark and Bias frames. Camera, gain, offset, temperature and exposure determine automatic matching."
+ ,"Salta": "Skip"
+ ,"Scegli cartelle complete oppure singoli file FITS/XISF provenienti da qualsiasi software con header utilizzabili.": "Choose complete folders or individual FITS/XISF files from any software with usable headers."
+ ,"Scegli cartelle complete oppure singoli file. Sono accettati FITS e XISF provenienti da qualsiasi software, purché contengano metadati utilizzabili.": "Choose complete folders or individual files. FITS and XISF files from any software are accepted when they contain usable metadata."
+ ,"Scegli una cartella contenente Master Dark e Bias": "Choose a folder containing Master Dark and Bias frames"
+ ,"sessioni e calibrazioni": "sessions and calibrations"
+ ,"Sì, aggiungi libreria": "Yes, add a library"
+ ,"SORGENTI": "SOURCES"
+ ,"Una Master Library raccoglie Dark e Bias riutilizzabili. Il programma sceglie quelli compatibili usando camera, gain, offset, temperatura ed esposizione.": "A Master Library contains reusable Dark and Bias frames. The app selects compatible ones using camera, gain, offset, temperature and exposure."
+ ,"Continua senza libreria": "Continue without a library"
+ ,"Analizza ora": "Analyze now"
+ ,"Vai al progetto": "Go to project"
}
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index f88cce8..658b095 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,5 +1,15 @@
# AstroProject Forge — note di rilascio
+## 1.2.0 — 29 luglio 2026
+
+- Il primo avvio usa ora un setup rapido in quattro passaggi: introduzione, Master
+ Library facoltativa, importazione FITS/XISF e riepilogo.
+- Dal passaggio finale è possibile avviare immediatamente l'analisi del progetto.
+- La configurazione iniziale può essere riaperta in qualsiasi momento dal menu.
+- La Master Library è ora presentata come funzione principale, con descrizione,
+ conteggio e un comando di aggiunta ben visibile nel pannello Sorgenti.
+- Lo stesso flusso è disponibile nelle interfacce Windows, Linux e macOS.
+
## 1.1.2 — 28 luglio 2026
- Anche gli aggiornamenti avviati dalle versioni precedenti mostrano ora una finestra
diff --git a/docs/PIANO_READY_TO_SELL.md b/docs/PIANO_READY_TO_SELL.md
index 908bb5a..1b71094 100644
--- a/docs/PIANO_READY_TO_SELL.md
+++ b/docs/PIANO_READY_TO_SELL.md
@@ -20,6 +20,8 @@ La stessa logica Core è usata dall'interfaccia Avalonia per Linux e macOS.
- [x] Nomi e descrizioni per screen reader sui controlli principali.
- [x] Focus tastiera ad alto contrasto.
- [x] Avanzamento visibile per download e installazione degli aggiornamenti Windows.
+- [x] Setup rapido operativo al primo avvio, riapribile dal menu.
+- [x] Gestione Master Library evidenziata nel pannello Sorgenti.
- [ ] Test manuale completo con Narrator su Windows.
- [ ] Test manuale tastiera, VoiceOver e Orca sui pacchetti nativi.
diff --git a/dotnet/AstroForge.App/MainWindow.xaml b/dotnet/AstroForge.App/MainWindow.xaml
index c874fb0..260d894 100644
--- a/dotnet/AstroForge.App/MainWindow.xaml
+++ b/dotnet/AstroForge.App/MainWindow.xaml
@@ -119,6 +119,7 @@
+
@@ -164,11 +165,20 @@
-
+
+
+
+
+
+
+
+
+
+
-
+
@@ -694,35 +704,31 @@
-
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
+
+
+
diff --git a/dotnet/AstroForge.App/MainWindow.xaml.cs b/dotnet/AstroForge.App/MainWindow.xaml.cs
index 24428b4..fd6356a 100644
--- a/dotnet/AstroForge.App/MainWindow.xaml.cs
+++ b/dotnet/AstroForge.App/MainWindow.xaml.cs
@@ -221,6 +221,13 @@ private void ShowAbout_Click(object sender, RoutedEventArgs e)
private void OpenManualDownload_Click(object sender, RoutedEventArgs e) =>
OpenExternalUrl(_availableUpdate?.ReleaseNotesUrl ?? ReleasesUrl);
private void ReportIssue_Click(object sender, RoutedEventArgs e) { MorePopup.IsOpen = false; OpenExternalUrl(IssueUrl); }
+ private void OpenOnboarding_Click(object sender, RoutedEventArgs e)
+ {
+ MorePopup.IsOpen = false;
+ _onboardingStep = 1;
+ UpdateOnboarding();
+ _viewModel.OpenOnboarding();
+ }
private static void OpenExternalUrl(string url) => Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
private async void CheckUpdates_Click(object sender, RoutedEventArgs e) => await CheckUpdatesAsync(true);
private async Task CheckUpdatesAsync(bool interactive)
@@ -326,7 +333,12 @@ private void DensitySelector_SelectionChanged(object sender, System.Windows.Cont
private void OnboardingBack_Click(object sender, RoutedEventArgs e) { _onboardingStep = Math.Max(1, _onboardingStep - 1); UpdateOnboarding(); }
private void OnboardingNext_Click(object sender, RoutedEventArgs e)
{
- if (_onboardingStep == 4) { _viewModel.CompleteOnboarding(); return; }
+ if (_onboardingStep == 4)
+ {
+ _viewModel.CompleteOnboarding();
+ if (_viewModel.CanAnalyzeProject) Scan_Click(this, new RoutedEventArgs());
+ return;
+ }
_onboardingStep++;
UpdateOnboarding();
}
@@ -338,7 +350,15 @@ private void UpdateOnboarding()
OnboardingStep4.Visibility = _onboardingStep == 4 ? Visibility.Visible : Visibility.Collapsed;
OnboardingProgress.Text = $"{_onboardingStep} / 4";
OnboardingBackButton.Visibility = _onboardingStep > 1 ? Visibility.Visible : Visibility.Collapsed;
- OnboardingNextButton.Content = _onboardingStep switch { 1 => "Inizia", 4 => "Vai al progetto", _ => "Continua" };
+ OnboardingNextButton.Content = _onboardingStep switch
+ {
+ 1 => "Inizia",
+ 2 when _viewModel.MasterLibraries.Count == 0 => "Continua senza libreria",
+ 4 when _viewModel.CanAnalyzeProject => "Analizza ora",
+ 4 => "Vai al progetto",
+ _ => "Continua"
+ };
+ ScheduleLocalization();
}
private void ApplyUiPreferences()
{
diff --git a/dotnet/AstroForge.CrossPlatform/MainWindow.axaml b/dotnet/AstroForge.CrossPlatform/MainWindow.axaml
index 043e19d..52fcd4f 100644
--- a/dotnet/AstroForge.CrossPlatform/MainWindow.axaml
+++ b/dotnet/AstroForge.CrossPlatform/MainWindow.axaml
@@ -32,9 +32,8 @@
-
-
-
+
+
@@ -111,6 +110,7 @@
+
@@ -119,8 +119,40 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/AstroForge.CrossPlatform/MainWindow.axaml.cs b/dotnet/AstroForge.CrossPlatform/MainWindow.axaml.cs
index 1c6b888..12db182 100644
--- a/dotnet/AstroForge.CrossPlatform/MainWindow.axaml.cs
+++ b/dotnet/AstroForge.CrossPlatform/MainWindow.axaml.cs
@@ -21,6 +21,7 @@ public sealed partial class MainWindow : Window
private CancellationTokenSource? _qualityCancellation;
private CancellationTokenSource? _previewCancellation;
private bool _sourcesVisible = true;
+ private int _onboardingStep = 1;
private int _blinkIndex = -1;
private double _qualityZoom = 1;
private bool _localizationPending;
@@ -200,13 +201,58 @@ private void ToggleSources_Click(object? sender, RoutedEventArgs e)
}
private void ToggleSettings_Click(object? sender, RoutedEventArgs e) => SettingsPanel.IsVisible = !SettingsPanel.IsVisible;
- private void OpenOnboarding_Click(object? sender, RoutedEventArgs e) { SettingsPanel.IsVisible = false; _viewModel.OpenOnboarding(); }
+ private void OpenOnboarding_Click(object? sender, RoutedEventArgs e)
+ {
+ SettingsPanel.IsVisible = false;
+ _onboardingStep = 1;
+ UpdateOnboarding();
+ _viewModel.OpenOnboarding();
+ }
private void OpenGuide_Click(object? sender, RoutedEventArgs e) { SettingsPanel.IsVisible = false; OpenUrl(GuideUrl); }
private void OpenRepository_Click(object? sender, RoutedEventArgs e) => OpenUrl(RepositoryUrl);
private void ReportIssue_Click(object? sender, RoutedEventArgs e) { SettingsPanel.IsVisible = false; OpenUrl(IssueUrl); }
private void OpenDiagnosticsTab_Click(object? sender, RoutedEventArgs e) { SettingsPanel.IsVisible = false; WorkspaceTabs.SelectedIndex = 7; _viewModel.RefreshDiagnostics(); }
private static void OpenUrl(string url) => Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
- private void CompleteOnboarding_Click(object? sender, RoutedEventArgs e) => _viewModel.CompleteOnboarding();
+ private void OnboardingAddLibrary_Click(object? sender, RoutedEventArgs e) => AddLibrary_Click(sender, e);
+ private void OnboardingAddSources_Click(object? sender, RoutedEventArgs e) => AddSources_Click(sender, e);
+ private void OnboardingAddFiles_Click(object? sender, RoutedEventArgs e) => AddFiles_Click(sender, e);
+ private void OnboardingSkip_Click(object? sender, RoutedEventArgs e) => _viewModel.CompleteOnboarding();
+ private void OnboardingBack_Click(object? sender, RoutedEventArgs e)
+ {
+ _onboardingStep = Math.Max(1, _onboardingStep - 1);
+ UpdateOnboarding();
+ }
+ private async void OnboardingNext_Click(object? sender, RoutedEventArgs e)
+ {
+ if (_onboardingStep == 4)
+ {
+ _viewModel.CompleteOnboarding();
+ if (_viewModel.CanAnalyzeProject)
+ await RunAsync("AF-SCAN-001", () => _viewModel.ScanAsync());
+ return;
+ }
+ _onboardingStep++;
+ UpdateOnboarding();
+ }
+
+ private void UpdateOnboarding()
+ {
+ OnboardingStep1.IsVisible = _onboardingStep == 1;
+ OnboardingStep2.IsVisible = _onboardingStep == 2;
+ OnboardingStep3.IsVisible = _onboardingStep == 3;
+ OnboardingStep4.IsVisible = _onboardingStep == 4;
+ OnboardingProgress.Text = $"{_onboardingStep} / 4";
+ OnboardingBackButton.IsVisible = _onboardingStep > 1;
+ OnboardingNextButton.Content = _onboardingStep switch
+ {
+ 1 => "Inizia",
+ 2 when _viewModel.MasterLibraries.Count == 0 => "Continua senza libreria",
+ 4 when _viewModel.CanAnalyzeProject => "Analizza ora",
+ 4 => "Vai al progetto",
+ _ => "Continua"
+ };
+ ScheduleLocalization();
+ }
private async void Analyze_Click(object? sender, RoutedEventArgs e) => await RunAsync("AF-SCAN-001", () => _viewModel.ScanAsync());
diff --git a/scripts/check-ui-contract.ps1 b/scripts/check-ui-contract.ps1
index 2758f71..60b8def 100644
--- a/scripts/check-ui-contract.ps1
+++ b/scripts/check-ui-contract.ps1
@@ -57,6 +57,8 @@ $contracts = @{
'Save and Save As behavior (Avalonia)' = $avaloniaCode.Contains('SaveProjectAsync(bool saveAs)')
'Visible Windows update progress' = $wpfCode.Contains('startInfo.ArgumentList.Add("/SILENT")') -and -not $wpfCode.Contains('startInfo.ArgumentList.Add("/VERYSILENT")')
'Visible progress from legacy updaters' = $wpfCode.Contains('startInfo.ArgumentList.Add("/APFVISIBLE=1")') -and $installer.Contains('CurInstallProgressChanged') -and $installer.Contains('NeedsCompatibilityProgress')
+ 'WPF operational onboarding' = $wpfXaml.Contains('x:Name="OnboardingStep4"') -and $wpfCode.Contains('if (_viewModel.CanAnalyzeProject) Scan_Click')
+ 'Avalonia operational onboarding' = (Read-Raw $avaloniaXamlPath).Contains('x:Name="OnboardingStep4"') -and $avaloniaCode.Contains('if (_viewModel.CanAnalyzeProject)')
}
$failed = @($contracts.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object Key)
diff --git a/scripts/verify-cross-platform-parity.ps1 b/scripts/verify-cross-platform-parity.ps1
index 92ada77..5b7d3bb 100644
--- a/scripts/verify-cross-platform-parity.ps1
+++ b/scripts/verify-cross-platform-parity.ps1
@@ -30,7 +30,7 @@ $requiredModelCapabilities = @(
$viewModelText = Get-Content -LiteralPath $viewModel -Raw
$missing = @($requiredModelCapabilities | Where-Object { $viewModelText -notmatch [regex]::Escape($_) })
if ($missing.Count -gt 0) { throw "Shared model capabilities missing: $($missing -join ', ')" }
-$requiredUiContracts = @('TreeRoots','PlannedTreeRoots','ApplyOverridesCommand','LinkFlatSetCommand','WbppKeywords','FilterStatistics','QualitySeries','ReviewQueue','MasterOrganizerItems','DiagnosticEvents','Export_Click','RestoreRecovery_Click','ShowOnboarding','CompleteOnboarding_Click')
+$requiredUiContracts = @('TreeRoots','PlannedTreeRoots','ApplyOverridesCommand','LinkFlatSetCommand','WbppKeywords','FilterStatistics','QualitySeries','ReviewQueue','MasterOrganizerItems','DiagnosticEvents','Export_Click','RestoreRecovery_Click','ShowOnboarding','OnboardingNext_Click')
$missing = @($requiredUiContracts | Where-Object { $windowText -notmatch [regex]::Escape($_) })
if ($missing.Count -gt 0) { throw "Capabilities not exposed by the cross-platform UI: $($missing -join ', ')" }
diff --git a/version.json b/version.json
index 1b2b99c..a3eb51f 100644
--- a/version.json
+++ b/version.json
@@ -1,6 +1,6 @@
{
- "version": "1.1.2",
+ "version": "1.2.0",
"channel": "Stable",
- "releaseDate": "2026-07-28",
+ "releaseDate": "2026-07-29",
"minimumWindowsBuild": 19045
}