Skip to content

Microsoft Store packaging (TunnelDeck) + July reliability fixes - #50

Merged
paulocorcino merged 17 commits into
mainfrom
feat/fixes-202606
Jul 7, 2026
Merged

Microsoft Store packaging (TunnelDeck) + July reliability fixes#50
paulocorcino merged 17 commits into
mainfrom
feat/fixes-202606

Conversation

@paulocorcino

Copy link
Copy Markdown
Owner

Integrates the July work and the Microsoft Store publishing effort into main.

Microsoft Store (TunnelDeck for Dev Tunnels)

  • New `store` cargo feature: MSIX-safe build (no self-install, update-checker, or HKCU auto-start; auto-start via manifest `windows.startupTask`).
  • `packaging/msix/`: AppxManifest, build-msix.ps1 (build + pack + sign + WACK), gen_msix_assets, capture-screenshots.ps1, .env-driven identity.
  • `docs/store/`: publishing runbook, listing copy, privacy policy + GitHub Pages site + deploy workflow.
  • In-app branding aligned to "TunnelDeck" / "TunnelDeck for Dev Tunnels".
  • Validated end-to-end: build -> pack -> sign, and WACK PASS (24 tests).

July reliability fixes (already released as rc.8/rc.9)

  • Update checker + banner, non-blocking stderr writer, open-URL-in-browser, i18n plural fix, stale-cache fix, host authorizing-loop fix, two-host relay probe.

🤖 Generated with Claude Code

paulocorcino and others added 17 commits June 18, 2026 21:52
Issue #46's make-before-break re-mint is gated on a question that cannot be
answered statically: how does the Dev Tunnels relay react to a second
in-process host on a tunnel id that is already hosted? This adds a throwaway
spike binary (sibling of host_spike, behind the `spike` feature) that brings
up host A, then connects host B to the same id and classifies the service
behavior from authoritative SDK signals — coexist / evict-old / evict-new /
reject — plus a best-effort public-URL serving poll.

Result (5/5 reproducible runs against the live relay): the relay ACCEPTS the
second host and EVICTS the incumbent the instant B's handshake completes
(host A's relay handle resolves Ok(()) within ~16-37us). New-evicts-old, not
coexist and not reject. The earlier flaky-DNS (WSANO_DATA 11001) failures are
classified as transient/inconclusive, not as a service reject.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A tunnel that is deleted or expires while it is in the auto-host set made
the keep-alive engine spin forever: the periodic token re-mint hit
"Tunnel not found", which `is_fatal_connect_error` did not recognize, so
it was classified as Transient and retried on backoff — re-emitting the
`Authorizing` phase every cycle and leaving the status pill stuck on
"authorizing…".

- Classify "tunnel not found" / 404 as fatal so the engine surfaces an
  error and stops instead of looping identical inputs.
- Add `is_missing_tunnel_error` and drop a gone tunnel from the persisted
  auto-host set in two places: on the engine's mid-session error, and
  during auto-resume when the tunnel is absent from a successful load
  (distinguishing "gone" from merely "portless").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The instant-paint row cache (last successful load) was painted unconditionally
on launch, so a tunnel deleted while the app was closed flashed as a phantom
row — e.g. the header chip showed "1 port" over a "No groups yet" body — for
the seconds the slow live `devtunnel list` takes to land.

Stamp the cache with a `saved_at` time and skip it on startup once it is older
than 24h (or has a future timestamp from clock skew). Quick relaunches keep the
snappy paint; after a long gap the UI waits for the live load instead. The old
bare-array cache format is treated as unparseable and ignored, self-healing on
the next save.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… 1 port)

The Fluent bundle was built with the system locale's langid while always
loading en-US strings, so Fluent applied the requested locale's CLDR plural
rules to English text. On a pt-BR system, pt classifies 0 as `one`, so
`status-port-count` with count 0 selected the `[one]` branch and rendered
"1 port" over an empty "No groups yet" list.

Resolve the request to the locale we actually ship strings for and build the
bundle with that resolved langid, so plural selection always matches the loaded
patterns. Add `resolve_lang` (kept in lockstep with `ftl_source`) plus tests
covering en-US plurals and the pt-BR regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clicking the port URL now opens it in the default browser instead of
copying it. The redundant standalone open (open-in-browser) button is
removed; copy stays available via the copy icon button.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Prepare for a Microsoft Store (MSIX) release as "TunnelDeck for Dev Tunnels".
The MSIX container virtualizes the registry/filesystem and manages install and
update itself, so several existing behaviors are broken or against Store policy
inside the package. A new `store` cargo feature compiles them out:

- Update checker (src/update.rs) becomes a no-op — the Store delivers updates.
- Self-install relocation, uninstall, and the HKCU Run-key auto-start are hidden
  in the UI via Strings.store-build; auto-start moves to the manifest's
  windows.startupTask (user-managed in Windows Settings).
- The winget "Install CLI" button is hidden (can't run from the sandbox).

`store` pulls in `hosting` since the Host button is core to the product. Also
renames the in-app title/About to "Dev Tunnels Manager".

Packaging lives under packaging/msix/: AppxManifest.xml (identity placeholders,
full-trust app, startupTask), build-msix.ps1 (build + pack + optional sign/WACK),
and a gen_msix_assets bin that renders the tile/logo PNGs from the app icon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- README.md: end-to-end publishing runbook (Partner Center account, name
  reservation, identity, build, WACK, submission).
- listing.md: Store listing copy — name, description, features, search terms,
  category, screenshots, and IARC age-rating guidance.
- privacy-policy.md: privacy policy to publish and link (required by the Store).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…en packaging

- Deploy Pages workflow + standalone privacy policy (docs/store/site/index.html)
  served at https://paulocorcino.github.io/devtunnel_gui/ for the required
  Store privacy-policy URL.
- capture-screenshots.ps1: grabs the app window and composes it on a 1920x1080
  canvas for Store screenshots (rejects the tray-sized window with guidance).
- build-msix.ps1 now loads the Partner Center identity from a gitignored .env
  (.env.example template); identity params are optional and override the file.
- Fix: use case-sensitive -creplace for the Identity Version so it no longer
  corrupts the <?xml version="1.0"?> declaration (makeappx validation error).

Validated end-to-end: build -> assets -> pack -> sign produces a signed .msix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
So the .env's CERT_PATH works regardless of the directory the packaging script
is invoked from.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…levation for -Wack

Find-SdkTool only searched Windows Kits\10\bin, but appcert.exe (WACK) lives in
the 'App Certification Kit' folder — so -Wack could never find it. Look there
explicitly and fail fast with a clear message when the prompt isn't elevated
(WACK requires Administrator).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Windows PowerShell 5.1 reads BOM-less scripts as ANSI, so a UTF-8 em-dash inside
a string literal ('local test only - do NOT ...') was mangled and broke parsing.
Replace all em-dashes with ASCII hyphens in build-msix.ps1 and capture-screenshots.ps1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Align the in-app identity with the Microsoft Store product name: window title and
header show "TunnelDeck"; the About panel shows the full "TunnelDeck for Dev
Tunnels". Resolves the contradiction with the old "Dev Tunnels Manager" label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tight bounds)

- Enumerate the process's top-level windows and pick the largest visible one;
  the Slint UI is a separate 'Window Class' window, not MainWindowHandle (which
  is a 16x16 winit helper, the source of the earlier tray-sized capture).
- Pin the window TOPMOST before grabbing so the screen capture isn't of whatever
  was covering it (SetForegroundWindow alone is blocked from a background process).
- Use DwmGetWindowAttribute(EXTENDED_FRAME_BOUNDS) so the grab excludes the
  invisible DWM resize border / drop shadow (no more edge bleed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI runs cargo fmt --all --check; apply it. Expands the Square asset table in
gen_msix_assets.rs and fixes pre-existing formatting drift in devtunnel.rs,
locale.rs, and two_host_probe.rs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ftl_source keeps a match on the resolved lang tag on purpose - adding a locale is
a one-line arm (per CLAUDE.md i18n). Allow the single-binding lint until a second
locale ships. Pre-existing code, surfaced now that PR CI runs clippy -D warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@paulocorcino
paulocorcino merged commit d5a842c into main Jul 7, 2026
2 checks passed
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.

1 participant