diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index a5dce93..7c60f0b 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -126,7 +126,12 @@ Plans:
2. The application recovers gracefully when a SharePoint API call is throttled (429/503) — the user sees a retry progress message and the operation eventually completes or surfaces a clear failure
3. The French locale is complete for all UI strings — no English fallback text appears when the language is set to French
4. A scan against a library with more than 5,000 items returns complete, correct results with no silent truncation verified against a known dataset
-**Plans**: TBD
+**Plans**: 3 plans
+
+Plans:
+- [ ] 05-01-PLAN.md — Helper visibility changes + retry/pagination/locale unit tests
+- [ ] 05-02-PLAN.md — FR diacritic corrections + self-contained publish configuration
+- [ ] 05-03-PLAN.md — Full test suite verification + publish smoke test + human checkpoint
## Progress
@@ -139,4 +144,4 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5
| 2. Permissions | 7/7 | Complete | 2026-04-02 |
| 3. Storage and File Operations | 8/8 | Complete | 2026-04-02 |
| 4. Bulk Operations and Provisioning | 10/10 | Complete | 2026-04-03 |
-| 5. Distribution and Hardening | 0/? | Not started | - |
+| 5. Distribution and Hardening | 0/3 | Not started | - |
diff --git a/.planning/phases/05-distribution-and-hardening/05-01-PLAN.md b/.planning/phases/05-distribution-and-hardening/05-01-PLAN.md
new file mode 100644
index 0000000..ba5e372
--- /dev/null
+++ b/.planning/phases/05-distribution-and-hardening/05-01-PLAN.md
@@ -0,0 +1,161 @@
+---
+phase: 05-distribution-and-hardening
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - SharepointToolbox/Core/Helpers/ExecuteQueryRetryHelper.cs
+ - SharepointToolbox/Core/Helpers/SharePointPaginationHelper.cs
+ - SharepointToolbox.Tests/Helpers/ExecuteQueryRetryHelperTests.cs
+ - SharepointToolbox.Tests/Helpers/SharePointPaginationHelperTests.cs
+ - SharepointToolbox.Tests/Localization/LocaleCompletenessTests.cs
+autonomous: true
+requirements:
+ - FOUND-11
+
+must_haves:
+ truths:
+ - "ExecuteQueryRetryHelper.IsThrottleException correctly classifies 429, 503, and throttle messages"
+ - "SharePointPaginationHelper.BuildPagedViewXml injects or replaces RowLimit in CAML XML"
+ - "Every EN key in Strings.resx has a non-empty, non-bracketed FR translation in Strings.fr.resx"
+ artifacts:
+ - path: "SharepointToolbox.Tests/Helpers/ExecuteQueryRetryHelperTests.cs"
+ provides: "Throttle exception classification unit tests"
+ min_lines: 20
+ - path: "SharepointToolbox.Tests/Helpers/SharePointPaginationHelperTests.cs"
+ provides: "CAML XML RowLimit injection unit tests"
+ min_lines: 20
+ - path: "SharepointToolbox.Tests/Localization/LocaleCompletenessTests.cs"
+ provides: "Exhaustive FR locale parity test"
+ min_lines: 15
+ key_links:
+ - from: "SharepointToolbox.Tests/Helpers/ExecuteQueryRetryHelperTests.cs"
+ to: "SharepointToolbox/Core/Helpers/ExecuteQueryRetryHelper.cs"
+ via: "InternalsVisibleTo + internal static IsThrottleException"
+ pattern: "ExecuteQueryRetryHelper\\.IsThrottleException"
+ - from: "SharepointToolbox.Tests/Helpers/SharePointPaginationHelperTests.cs"
+ to: "SharepointToolbox/Core/Helpers/SharePointPaginationHelper.cs"
+ via: "InternalsVisibleTo + internal static BuildPagedViewXml"
+ pattern: "SharePointPaginationHelper\\.BuildPagedViewXml"
+---
+
+
+Create unit tests for the retry helper, pagination helper, and locale completeness — the three testable verification axes of Phase 5. Change private static methods to internal static so tests can access them (established InternalsVisibleTo pattern from Phase 2).
+
+Purpose: These tests prove the reliability guarantees (throttle retry, 5k-item pagination) and locale completeness that FOUND-11's success criteria require. Without them, the only verification is manual smoke testing.
+Output: Three new test files, two visibility changes in helpers.
+
+
+
+@C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md
+@C:/Users/dev/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/05-distribution-and-hardening/05-RESEARCH.md
+
+@SharepointToolbox/Core/Helpers/ExecuteQueryRetryHelper.cs
+@SharepointToolbox/Core/Helpers/SharePointPaginationHelper.cs
+@SharepointToolbox/Localization/Strings.resx
+@SharepointToolbox/Localization/Strings.fr.resx
+@SharepointToolbox/AssemblyInfo.cs
+
+
+
+
+
+ Task 1: Make helper methods internal static and create retry + pagination tests
+
+ SharepointToolbox/Core/Helpers/ExecuteQueryRetryHelper.cs,
+ SharepointToolbox/Core/Helpers/SharePointPaginationHelper.cs,
+ SharepointToolbox.Tests/Helpers/ExecuteQueryRetryHelperTests.cs,
+ SharepointToolbox.Tests/Helpers/SharePointPaginationHelperTests.cs
+
+
+1. In `ExecuteQueryRetryHelper.cs`, change `private static bool IsThrottleException(Exception ex)` to `internal static bool IsThrottleException(Exception ex)`. No other changes to the file.
+
+2. In `SharePointPaginationHelper.cs`, change `private static string BuildPagedViewXml(string? existingXml, int rowLimit)` to `internal static string BuildPagedViewXml(string? existingXml, int rowLimit)`. No other changes.
+
+3. Create `SharepointToolbox.Tests/Helpers/ExecuteQueryRetryHelperTests.cs`:
+ - Namespace: `SharepointToolbox.Tests.Helpers`
+ - Using: `SharepointToolbox.Core.Helpers`
+ - `[Theory]` with `[InlineData]` for throttle messages: "The request has been throttled -- 429", "Service unavailable 503", "SharePoint has throttled your request"
+ - Each creates `new Exception(message)` and asserts `ExecuteQueryRetryHelper.IsThrottleException(ex)` returns true
+ - `[Fact]` for non-throttle: `new Exception("File not found")` returns false
+ - `[Fact]` for nested throttle: `new Exception("outer", new Exception("429"))` — test whether inner exceptions are checked (current implementation only checks top-level Message — test should assert false to document this behavior)
+
+4. Create `SharepointToolbox.Tests/Helpers/SharePointPaginationHelperTests.cs`:
+ - Namespace: `SharepointToolbox.Tests.Helpers`
+ - Using: `SharepointToolbox.Core.Helpers`
+ - `[Fact]` BuildPagedViewXml_NullInput_ReturnsViewWithRowLimit: `BuildPagedViewXml(null, 2000)` returns `"2000"`
+ - `[Fact]` BuildPagedViewXml_EmptyString_ReturnsViewWithRowLimit: same for `""`
+ - `[Fact]` BuildPagedViewXml_ExistingRowLimit_Replaces: input `"100"` with rowLimit 2000 returns `"2000"`
+ - `[Fact]` BuildPagedViewXml_NoRowLimit_Appends: input `""` with rowLimit 2000 returns the same XML with `2000` inserted before ``
+ - `[Fact]` BuildPagedViewXml_WhitespaceOnly_ReturnsViewWithRowLimit: input `" "` returns minimal view
+
+Note: Create the `Helpers/` subdirectory under the test project if it doesn't exist.
+
+
+ dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj --filter "ExecuteQueryRetryHelper|SharePointPagination" -v quiet
+
+ All retry helper and pagination helper tests pass. IsThrottleException correctly classifies 429/503/throttle messages. BuildPagedViewXml correctly handles null, empty, existing RowLimit, and missing RowLimit inputs.
+
+
+
+ Task 2: Create exhaustive FR locale completeness test
+ SharepointToolbox.Tests/Localization/LocaleCompletenessTests.cs
+
+Create `SharepointToolbox.Tests/Localization/LocaleCompletenessTests.cs`:
+- Namespace: `SharepointToolbox.Tests.Localization`
+- Using: `System.Globalization`, `System.Resources`, `System.Collections`, `SharepointToolbox.Localization`
+
+Test 1 — `[Fact] AllEnKeys_HaveNonEmptyFrTranslation`:
+ - Create `ResourceManager` for `"SharepointToolbox.Localization.Strings"` using `typeof(Strings).Assembly`
+ - Get the invariant resource set via `GetResourceSet(CultureInfo.InvariantCulture, true, true)`
+ - Create `CultureInfo("fr")` (not "fr-FR" — the satellite assembly uses neutral "fr" culture)
+ - Iterate all `DictionaryEntry` in the resource set
+ - For each key: call `GetString(key, frCulture)` and assert it is not null or whitespace
+ - Also assert it does not start with `"["` (bracketed fallback indicator)
+ - Collect all failures into a list and assert the list is empty (single assertion with all missing keys listed in the failure message for easy debugging)
+
+Test 2 — `[Fact] FrStrings_ContainExpectedDiacritics`:
+ - Spot-check 5 known keys that MUST have diacritics after the fix in Plan 02:
+ - `"transfer.mode.move"` should contain `"é"` (Deplacer -> Deplacer is wrong, Déplacer is correct)
+ - `"bulksites.execute"` should contain `"é"` (Créer)
+ - `"templates.list"` should contain `"è"` (Modèles)
+ - `"bulk.result.success"` should contain `"é"` (Terminé, réussis, échoués)
+ - `"folderstruct.library"` should contain `"è"` (Bibliothèque)
+ - For each: get FR string via ResourceManager and assert it contains the expected accented character
+
+Note: This test will FAIL until Plan 02 fixes the diacritics — that is correct TDD-style behavior. The test documents the expected state.
+
+
+ dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj --filter "LocaleCompleteness" -v quiet
+
+ LocaleCompletenessTests.cs exists and compiles. Test 1 (AllEnKeys_HaveNonEmptyFrTranslation) passes (all keys have values). Test 2 (FrStrings_ContainExpectedDiacritics) fails until Plan 02 fixes diacritics — expected behavior.
+
+
+
+
+
+All new tests compile and the helper tests pass:
+```bash
+dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj --filter "ExecuteQueryRetryHelper|SharePointPagination|LocaleCompleteness" -v quiet
+```
+Existing test suite remains green (no regressions from visibility changes).
+
+
+
+- ExecuteQueryRetryHelperTests: 4+ tests pass (3 throttle-true, 1 non-throttle-false)
+- SharePointPaginationHelperTests: 4+ tests pass (null, empty, replace, append)
+- LocaleCompletenessTests: Test 1 passes (key parity), Test 2 may fail (diacritics pending Plan 02)
+- Full existing test suite still green
+
+
+
diff --git a/.planning/phases/05-distribution-and-hardening/05-02-PLAN.md b/.planning/phases/05-distribution-and-hardening/05-02-PLAN.md
new file mode 100644
index 0000000..285916a
--- /dev/null
+++ b/.planning/phases/05-distribution-and-hardening/05-02-PLAN.md
@@ -0,0 +1,153 @@
+---
+phase: 05-distribution-and-hardening
+plan: 02
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - SharepointToolbox/Localization/Strings.fr.resx
+ - SharepointToolbox/SharepointToolbox.csproj
+autonomous: true
+requirements:
+ - FOUND-11
+
+must_haves:
+ truths:
+ - "All French strings display with correct diacritics when language is set to French"
+ - "dotnet publish produces a single self-contained EXE with no loose DLLs"
+ artifacts:
+ - path: "SharepointToolbox/Localization/Strings.fr.resx"
+ provides: "Corrected French translations with proper diacritics"
+ contains: "Bibliothèque"
+ - path: "SharepointToolbox/SharepointToolbox.csproj"
+ provides: "Self-contained single-file publish configuration"
+ contains: "PublishSingleFile"
+ key_links:
+ - from: "SharepointToolbox/SharepointToolbox.csproj"
+ to: "dotnet publish"
+ via: "PublishSingleFile + SelfContained + IncludeNativeLibrariesForSelfExtract properties"
+ pattern: "PublishSingleFile.*true"
+---
+
+
+Fix all French diacritic-missing strings in Strings.fr.resx and add self-contained single-file publish configuration to the csproj.
+
+Purpose: Addresses two of the four Phase 5 success criteria — complete French locale and single EXE distribution. These are independent file changes that can run parallel with Plan 01's test creation.
+Output: Corrected FR strings, publish-ready csproj.
+
+
+
+@C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md
+@C:/Users/dev/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/05-distribution-and-hardening/05-RESEARCH.md
+
+@SharepointToolbox/Localization/Strings.fr.resx
+@SharepointToolbox/SharepointToolbox.csproj
+
+
+
+
+
+ Task 1: Fix French diacritic-missing strings in Strings.fr.resx
+ SharepointToolbox/Localization/Strings.fr.resx
+
+Open `Strings.fr.resx` and fix ALL the following strings. The file is XML — edit the `` elements directly. Be careful to preserve the XML structure and encoding.
+
+Corrections (key -> wrong value -> correct value):
+
+1. `transfer.sourcelibrary`: "Bibliotheque source" -> "Bibliothèque source"
+2. `transfer.destlibrary`: "Bibliotheque destination" -> "Bibliothèque destination"
+3. `transfer.mode.move`: "Deplacer" -> "Déplacer"
+4. `transfer.conflict.overwrite`: "Ecraser" -> "Écraser"
+5. `transfer.start`: "Demarrer le transfert" -> "Démarrer le transfert"
+6. `transfer.nofiles`: "Aucun fichier a transferer" -> "Aucun fichier à transférer."
+7. `bulkmembers.preview`: "Apercu" (in the value) -> "Aperçu" (preserve the rest of the value including any format placeholders)
+8. `bulksites.execute`: "Creer les sites" -> "Créer les sites"
+9. `bulksites.preview`: "Apercu" -> "Aperçu" (preserve format placeholders)
+10. `bulksites.owners`: "Proprietaires" -> "Propriétaires"
+11. `folderstruct.execute`: "Creer les dossiers" -> "Créer les dossiers"
+12. `folderstruct.preview`: "Apercu ({0} dossiers a creer)" -> "Aperçu ({0} dossiers à créer)"
+13. `folderstruct.library`: "Bibliotheque cible" -> "Bibliothèque cible"
+14. `templates.list`: "Modeles enregistres" -> "Modèles enregistrés"
+15. `templates.opt.libraries`: "Bibliotheques" -> "Bibliothèques"
+16. `bulk.result.success`: "Termine : {0} reussis, {1} echoues" -> "Terminé : {0} réussis, {1} échoués"
+17. `bulk.result.allfailed`: "Les {0} elements ont echoue." -> "Les {0} éléments ont échoué."
+18. `bulk.result.allsuccess`: "Les {0} elements ont ete traites avec succes." -> "Les {0} éléments ont été traités avec succès."
+19. `bulk.exportfailed`: "Exporter les elements echoues" -> "Exporter les éléments échoués"
+20. `bulk.retryfailed`: "Reessayer les echecs" -> "Réessayer les échecs"
+21. `bulk.validation.invalid`: fix "reimportez" -> "réimportez" (preserve rest of string)
+22. `bulk.csvimport.title`: "Selectionner un fichier CSV" -> "Sélectionner un fichier CSV"
+23. `folderbrowser.title`: "Selectionner un dossier" -> "Sélectionner un dossier"
+24. `folderbrowser.select`: "Selectionner" -> "Sélectionner"
+
+Also check for these templates.* keys (noted in research):
+25. `templates.capture`: if contains "modele" without accent -> "modèle"
+26. `templates.apply`: if contains "modele" without accent -> "modèle"
+27. `templates.name`: if contains "modele" without accent -> "modèle"
+
+IMPORTANT: Do NOT change any key names. Only change `` content. Do NOT add or remove keys. Preserve all XML structure and comments.
+
+
+ dotnet msbuild SharepointToolbox/SharepointToolbox.csproj -t:Compile -p:DesignTimeBuild=true -v:quiet 2>&1 | tail -5
+
+ All 25+ FR strings corrected with proper French diacritics (e, a -> e with accent, a with accent, c with cedilla). Project compiles without errors. No keys added or removed.
+
+
+
+ Task 2: Add self-contained single-file publish configuration to csproj
+ SharepointToolbox/SharepointToolbox.csproj
+
+Add a new `` block to `SharepointToolbox.csproj` specifically for publish configuration. Place it after the existing main `` (the one with `WinExe`).
+
+Add exactly these properties:
+
+```xml
+
+ true
+ win-x64
+ true
+
+```
+
+Using a conditional PropertyGroup so these properties only activate during publish (when PublishSingleFile is passed via CLI or profile). This avoids affecting normal `dotnet build` and `dotnet test` behavior.
+
+The existing `false` MUST remain in the main PropertyGroup — do NOT change it.
+
+After editing, verify the publish command works:
+```bash
+dotnet publish SharepointToolbox/SharepointToolbox.csproj -c Release -p:PublishSingleFile=true -o ./publish
+```
+
+Confirm output is a single EXE (no loose .dll files in the publish folder).
+
+
+ dotnet publish SharepointToolbox/SharepointToolbox.csproj -c Release -p:PublishSingleFile=true -o ./publish 2>&1 | tail -3 && ls ./publish/*.dll 2>/dev/null | wc -l
+
+ SharepointToolbox.csproj has PublishSingleFile configuration. `dotnet publish -p:PublishSingleFile=true` produces a single SharepointToolbox.exe (~200 MB) with zero loose DLL files in the output directory. PublishTrimmed remains false.
+
+
+
+
+
+1. FR resx compiles: `dotnet msbuild SharepointToolbox/SharepointToolbox.csproj -t:Compile -p:DesignTimeBuild=true -v:quiet`
+2. Publish produces single EXE: `dotnet publish SharepointToolbox/SharepointToolbox.csproj -c Release -p:PublishSingleFile=true -o ./publish && ls ./publish/*.dll 2>/dev/null | wc -l` (expect 0)
+3. Full test suite still green: `dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj -v quiet`
+
+
+
+- Strings.fr.resx contains proper diacritics for all 25+ corrected keys
+- SharepointToolbox.csproj has PublishSingleFile + SelfContained + IncludeNativeLibrariesForSelfExtract in conditional PropertyGroup
+- PublishTrimmed remains false
+- dotnet publish produces single EXE with 0 loose DLLs
+- Existing test suite unaffected
+
+
+
diff --git a/.planning/phases/05-distribution-and-hardening/05-03-PLAN.md b/.planning/phases/05-distribution-and-hardening/05-03-PLAN.md
new file mode 100644
index 0000000..3d6215a
--- /dev/null
+++ b/.planning/phases/05-distribution-and-hardening/05-03-PLAN.md
@@ -0,0 +1,111 @@
+---
+phase: 05-distribution-and-hardening
+plan: 03
+type: execute
+wave: 2
+depends_on:
+ - 05-01
+ - 05-02
+files_modified: []
+autonomous: false
+requirements:
+ - FOUND-11
+
+must_haves:
+ truths:
+ - "All unit tests pass including new helper tests and locale completeness tests"
+ - "Published EXE exists as a single self-contained file"
+ - "Application is ready for clean-machine smoke test"
+ artifacts: []
+ key_links: []
+---
+
+
+Run the full test suite (including Plan 01's new tests and Plan 02's diacritic fixes), verify the publish output, and checkpoint for human smoke test on a clean machine.
+
+Purpose: Final gate before shipping. Verifies all three workstreams integrate correctly and the published artifact is ready for distribution.
+Output: Confirmed green test suite, verified publish artifact, human sign-off.
+
+
+
+@C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md
+@C:/Users/dev/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/05-distribution-and-hardening/05-RESEARCH.md
+@.planning/phases/05-distribution-and-hardening/05-01-SUMMARY.md
+@.planning/phases/05-distribution-and-hardening/05-02-SUMMARY.md
+
+
+
+
+
+ Task 1: Run full test suite and verify publish artifact
+
+
+1. Run the full test suite to confirm all tests pass (including the new ExecuteQueryRetryHelper, SharePointPagination, and LocaleCompleteness tests from Plan 01, which should now pass thanks to Plan 02's diacritic fixes):
+
+```bash
+dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj -v quiet
+```
+
+All tests must pass (except known Skip tests for interactive MSAL login). If the diacritic spot-check test (FrStrings_ContainExpectedDiacritics) fails, identify which strings still need fixing and fix them in Strings.fr.resx.
+
+2. Run the publish command and verify the output:
+
+```bash
+dotnet publish SharepointToolbox/SharepointToolbox.csproj -c Release -p:PublishSingleFile=true -o ./publish
+```
+
+3. Verify single-file output:
+ - Count DLL files in ./publish/ — expect 0
+ - Confirm SharepointToolbox.exe exists and is > 150 MB (self-contained)
+ - Note the exact file size for the checkpoint
+
+4. If any test fails or publish produces loose DLLs, diagnose and fix before proceeding.
+
+
+ dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj -v quiet && dotnet publish SharepointToolbox/SharepointToolbox.csproj -c Release -p:PublishSingleFile=true -o ./publish 2>&1 | tail -3 && ls ./publish/*.dll 2>/dev/null | wc -l
+
+ Full test suite green (all new + existing tests pass). Publish produces single SharepointToolbox.exe with 0 loose DLLs.
+
+
+
+ Task 2: Human smoke test — EXE launch and French locale verification
+
+
+Present the published EXE to the user for manual verification. The automated work (test suite, publish) is complete in Task 1. This checkpoint verifies visual and runtime behavior that cannot be automated.
+
+The published EXE is at ./publish/SharepointToolbox.exe (~200 MB self-contained).
+
+Verification checklist for the user:
+1. Single-file: ./publish/ contains only SharepointToolbox.exe (+ optional .pdb), no .dll files
+2. Clean-machine launch: Copy EXE to a machine without .NET 10 runtime, double-click, verify main window renders
+3. French locale: Switch to French in Settings, navigate all tabs, verify accented characters display correctly
+4. Tab health: Click through all 10 tabs and confirm no crashes
+
+ User confirms "approved" after visual and functional verification.
+ Human confirms: EXE launches on clean machine, French locale displays correct diacritics, all tabs render without crashes.
+
+
+
+
+
+Full test suite: `dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj -v quiet`
+Publish artifact: `ls ./publish/SharepointToolbox.exe` exists, `ls ./publish/*.dll 2>/dev/null | wc -l` returns 0
+
+
+
+- All unit tests pass (including new helper + locale tests)
+- Published EXE is a single self-contained file (~200 MB)
+- No loose DLL files in publish output
+- Human confirms app launches and French locale is correct
+
+
+