Skip to content

Use fixed user-profile state for UI and Sandbox - #931

Merged
Nikola Metulev (nmetulev) merged 4 commits into
mainfrom
nmetulev-unify-winapp-state-locations
Sep 23, 2026
Merged

Nikola Metulev (nmetulev) merged 4 commits into
mainfrom
nmetulev-unify-winapp-state-locations

Conversation

@nmetulev

@nmetulev Nikola Metulev (nmetulev) commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Description

Keep unreleased UI coordination and Windows Sandbox target state in a shared physical user-profile location, rather than LocalAppData or package-redirected storage. UI locks now default to %USERPROFILE%\.winapp\state\ui; each Sandbox target defaults to %USERPROFILE%\.winapp\state\targets\<target-key>. These paths do not follow WINAPP_CLI_CACHE_DIRECTORY. Existing WINAPP_UI_LOCK_DIRECTORY and WINAPP_TARGET_STATE_ROOT overrides retain precedence. A profile .winapp used for state is not mistaken for a project's local cache when the global cache is overridden. Invalid or inaccessible target-state storage reports sandbox_state_unavailable, not a stale Sandbox; one troubleshooting row describes recovery.

Usage Example

winapp ui click Submit -a MyApp coordinates through %USERPROFILE%\.winapp\state\ui by default; commands on --on sandbox keep target records under %USERPROFILE%\.winapp\state\targets\<target-key>. The exact paths and override precedence are covered by focused tests, not a live Sandbox session.

Related Issue

N/A — intentionally separate from the broader, still-open #873; no changes to that PR.

Type of Change

  • 🐛 Bug fix
  • 🧪 Test update

Checklist

  • New tests added for new functionality
  • Tested locally on Windows (80 focused tests; x64 and arm64 NativeAOT builds; generated docs and npm build)
  • Required build-and-package CI on latest commit 03a124dc (in progress; previous head passed)
  • Packaged/unpackaged live Sandbox probe (no Sandbox instance running; no newly packaged app installed)

Screenshots / Demo

N/A — nonvisual path relocation.

Additional Notes

The released 0.6.2 behavior has no UI coordination or Sandbox target state contract to migrate. This PR intentionally excludes filesystem fallback, cache relocation, ACL redesign, and changes to #873. No merge or auto-merge requested.

AI Description

This section is auto-generated by AI when the PR is opened or updated. To opt out, delete this entire section including the marker comments.

Keep the unreleased coordination and target records in fixed user-profile state, independent of the cache override and package identity.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings September 23, 2026 02:45
@nmetulev Nikola Metulev (nmetulev) added the agent-preparing Agent is addressing feedback or completing required validation and CI label Sep 23, 2026
Comment thread src/winapp-CLI/WinApp.Cli/Services/WinappDirectoryService.cs
Comment thread src/winapp-CLI/WinApp.Cli/Services/WinappDirectoryService.cs
Comment thread src/winapp-CLI/WinApp.Cli.Tests/InteractiveDesktopStoreTests.cs
Comment thread src/winapp-CLI/WinApp.Cli.Tests/InteractiveDesktopStoreTests.cs
Comment thread src/winapp-CLI/WinApp.Cli.Tests/WinappDirectoryServiceTests.cs
Drop premature user documentation for unreleased internal state paths.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Two moderate storage-path and error-classification issues remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Moves UI coordination and Sandbox target state to stable, cache-independent user-profile storage.

Changes:

  • Adds %USERPROFILE%\.winapp\state path resolution.
  • Updates UI and Sandbox defaults while preserving overrides.
  • Adds focused tests and documentation.

Unresolved comments:

  • Moderate (2 votes): Use a distinct storage-unavailable error instead of sandbox_target_stale for invalid profile paths, with tests and documentation.
  • Moderate (1 vote): Prevent the fallback cache path from placing project files alongside shared profile state.
File Description
src/​winapp-CLI/​WinApp.Cli/​Services/​WinappDirectoryService.cs Resolves profile state and separates it from project cache discovery.
src/​winapp-CLI/​WinApp.Cli/​Services/​InteractiveDesktop/​InteractiveDesktopPaths.cs Uses profile-based UI coordination state.
src/​winapp-CLI/​WinApp.Cli/​ExecutionTargets/​Orchestration/​TargetStateDirectoryProvider.cs Uses profile-based Sandbox target state.
src/​winapp-CLI/​WinApp.Cli.Tests/​WinappDirectoryServiceTests.cs Tests cache and profile-state separation.
src/​winapp-CLI/​WinApp.Cli.Tests/​TargetStateDirectoryProviderTests.cs Tests target paths and override precedence.
src/​winapp-CLI/​WinApp.Cli.Tests/​InteractiveDesktopStoreTests.cs Tests cache-independent UI paths.
docs/​usage.md Documents shared runtime state.
docs/​ui-automation.md Documents UI coordination storage.
docs/​sandbox-execution.md Documents Sandbox target storage.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Avoid treating an unusable user-profile state root as a stale Sandbox; give callers a storage-specific recovery code.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Build Metrics Report

Validation passed. All required build and validation jobs succeeded.

Binary Sizes

Artifact Baseline Current Delta
CLI (ARM64) 57.26 MB 57.26 MB 📉 -7.5 KB (-0.01%)
CLI (x64) 57.31 MB 57.30 MB 📉 -7.0 KB (-0.01%)
MSIX (ARM64) 23.79 MB 23.79 MB 📈 +0.1 KB (+0.00%)
MSIX (x64) 25.24 MB 25.24 MB 📉 -4.5 KB (-0.02%)
NPM Package 49.61 MB 49.60 MB 📉 -13.4 KB (-0.03%)
NuGet Package 49.71 MB 49.70 MB 📉 -12.0 KB (-0.02%)

.NET Test Results (TRX reports)

Other suites are reflected in the overall validation status above.

✅ 7843 passed, 37 skipped out of 7880 tests in 1249.6s (+7 tests, -39.7s vs. baseline)

Test Coverage

✅ 86% line coverage, 80.7% branch coverage · ✅ no change vs. baseline

CLI Startup Time

64ms median (x64, winapp --version) · ✅ no change vs. baseline

Try This Build

Installs the MSIX for your architecture, replacing any previously installed build. Needs the GitHub CLI — the command offers to install it and sign you in if it is missing.

& ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) 931
Switching between builds often?

Put the tool on your PATH once:

& ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) -AddToPath

Then this build is just:

winapp-pr 931

Run winapp-pr with no arguments to pick from a list of open PRs.


Updated 2026-09-23 04:46:03 UTC · commit 03a124d · workflow run

@nmetulev

Copy link
Copy Markdown
Member Author

On the overview's remaining fallback-cache concern: when a project is beneath the user profile, GetLocalWinappDirectory skips the profile .winapp even if WINAPP_CLI_CACHE_DIRECTORY points elsewhere, then returns the project's own .winapp (covered by GetLocalWinappDirectory_WithCacheOverride_DoesNotUseProfileStateAsProjectCache). If the user profile itself is the project directory, its fallback has always been profile/.winapp; the new coordination data sits in a distinct state/ subdirectory, so no project file is placed in the coordination directory. Changing project-cache fallback for that pre-existing case would expand this location-only PR without fixing a collision.

@nmetulev Nikola Metulev (nmetulev) added ready-for-review Agent work and technical checks complete; awaiting review or re-review, not approval or merge and removed agent-preparing Agent is addressing feedback or completing required validation and CI labels Sep 23, 2026
@zateutsch

Copy link
Copy Markdown
Contributor

🤖 AI-generated review (winappcli pr-review skill) — verify before acting.

Decision: mergeable as-is. Clean, well-scoped relocation; build is warning-free and all changed-area tests pass. One non-blocking gap noted below.

Non-blocking: sandbox_state_unavailable doesn't fire for an unwritable state directory

  • What's wrong: In TargetStateDirectoryProvider, the try/catch that maps failures to StateUnavailable is in GetTargetsRoot() and only wraps path resolution. The actual directory.Create()/Refresh() happens in GetTargetRoot(), outside that try. So when %USERPROFILE%\.winapp\state\targets (or a WINAPP_TARGET_STATE_ROOT override) exists but is unwritable, a raw UnauthorizedAccessException/IOException escapes instead of the structured sandbox_state_unavailable.
  • Repro: %USERPROFILE%\.winapp\state on a read-only/full volume (or an unwritable WINAPP_TARGET_STATE_ROOT) → first --on sandbox command → GetTargetRoot(create: true) → directory.Create() throws raw → expected sandbox_state_unavailable with the documented recovery.
  • Why it matters: The new docs troubleshooting row and UserAction text say "Ensure %USERPROFILE%\.winapp\state is writable" — which is exactly the case the code path doesn't produce that code for, and --json consumers miss the stable error contract.
  • Severity: Low in practice — the default path is the user's own profile (essentially always writable), so this only surfaces on a user-set override, disk-full, or corrupted-profile edge. The command was already failing; only the error quality degrades.
  • Smallest fix: Extend the mapping to cover creation — wrap CombineInsideRoot + Create()/Refresh() for the default/env-root path in ExecutionTargetException.Create(StateUnavailable, …), re-throwing any existing ExecutionTargetException untouched.
  • Location: src/winapp-CLI/WinApp.Cli/ExecutionTargets/Orchestration/TargetStateDirectoryProvider.cs

Optional (low): %USERPROFILE%\.winapp parent re-derived in three places

GetUserStateDirectory, GetGlobalWinappDirectory, and the new skip-check in GetLocalWinappDirectory each rebuild Path.Combine(profile, ".winapp") independently. A single GetUserWinappDirectory(profile) helper would prevent future drift. No user impact today.

  • Location: src/winapp-CLI/WinApp.Cli/Services/WinappDirectoryService.cs

Also considered, not issues

  • Path safety: target.StateKey is still combined via TargetPathSafety.CombineInsideRoot; no traversal via the new plain Path.Combine on constant segments.
  • Test isolation: InvokeProgramAsync not isolating WINAPP_UI_LOCK_DIRECTORY is pre-existing (the old %LOCALAPPDATA% default was equally un-isolated) and the path is resolved lazily, so not a regression here.
  • Docs/plugin skills carry no stale old-path references; scope is justified.

Validated: dotnet build (0 warnings) + the four changed test classes (65 passed) on the PR head.

@nmetulev Nikola Metulev (nmetulev) added agent-preparing Agent is addressing feedback or completing required validation and CI and removed ready-for-review Agent work and technical checks complete; awaiting review or re-review, not approval or merge labels Sep 23, 2026
Map state-directory creation failures to the existing storage error while preserving read-only resolution and target-path validation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@nmetulev

Copy link
Copy Markdown
Member Author

Addressed the non-blocking storage-error finding from Zach Teutsch (@zateutsch) in 03a124d. A local regression test reproduced raw IOException when a file obstructs either the default profile root or WINAPP_TARGET_STATE_ROOT; both now return structured sandbox_state_unavailable with an underlying exception. create: false still stays read-only. The 80 focused tests and x64/arm64 NativeAOT publishes/docs generation passed; current-head CI is running. I left the optional profile-path helper extraction out of this location-only PR.

@nmetulev
Nikola Metulev (nmetulev) merged commit f047f86 into main Sep 23, 2026
38 checks passed
@nmetulev
Nikola Metulev (nmetulev) deleted the nmetulev-unify-winapp-state-locations branch September 23, 2026 04:53
@nmetulev Nikola Metulev (nmetulev) removed the agent-preparing Agent is addressing feedback or completing required validation and CI label Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants