Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ jobs:
- name: Verify release security policy
run: pnpm release:check-security

- name: Verify Microsoft Store package policy
run: pnpm store:check

- name: Verify Microsoft Store MSIX policy
run: pnpm run store:msix:check

- name: Typecheck
run: pnpm --filter ./ui exec tsc --noEmit

Expand Down
67 changes: 67 additions & 0 deletions .github/workflows/microsoft-store-msix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Microsoft Store MSIX candidate

on:
workflow_dispatch:
inputs:
tag:
description: 'Existing release tag to package (for example v1.1.1).'
required: true
identity_name:
description: 'Exact Package/Identity/Name from Partner Center.'
required: true
publisher:
description: 'Exact Package/Identity/Publisher from Partner Center.'
required: true
publisher_display_name:
description: 'Exact Package/Properties/PublisherDisplayName from Partner Center.'
required: true

jobs:
build:
runs-on: windows-latest
permissions:
contents: read
env:
VERSION: ${{ inputs.tag }}
MSIX_IDENTITY_NAME: ${{ inputs.identity_name }}
MSIX_PUBLISHER: ${{ inputs.publisher }}
MSIX_PUBLISHER_DISPLAY_NAME: ${{ inputs.publisher_display_name }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2
with:
key: microsoft-store-msix-x64

- run: pnpm install --frozen-lockfile
- run: node scripts/check-release-version.mjs
- run: pnpm release:check-security
- run: pnpm run store:msix:check

- name: Build unsigned Store MSIX
shell: pwsh
run: |
./scripts/build-msix.ps1 `
-IdentityName $env:MSIX_IDENTITY_NAME `
-Publisher $env:MSIX_PUBLISHER `
-PublisherDisplayName $env:MSIX_PUBLISHER_DISPLAY_NAME `
-StoreSubmission

- uses: actions/upload-artifact@v4
with:
name: strand-microsoft-store-msix-${{ inputs.tag }}
path: |
target/msix/dist/*.msix
target/msix/dist/*.msixupload
if-no-files-found: error
111 changes: 111 additions & 0 deletions .github/workflows/microsoft-store.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: Microsoft Store candidate

on:
workflow_dispatch:
inputs:
tag:
description: 'Existing release tag to package (for example v1.1.1).'
required: true
publish_asset:
description: 'Upload the verified Store MSI to the existing GitHub release.'
type: boolean
default: false

jobs:
build:
runs-on: windows-latest
permissions:
contents: write
env:
VERSION: ${{ inputs.tag }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2
with:
key: microsoft-store-x64

- run: pnpm install --frozen-lockfile
- run: node scripts/check-release-version.mjs
- run: pnpm release:check-security
- run: pnpm store:check

- name: Import Windows publisher certificate
shell: pwsh
env:
WINDOWS_CERTIFICATE_BASE64: ${{ secrets.WINDOWS_CERTIFICATE_BASE64 }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: |
$bytes = [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_BASE64)
$flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet `
-bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::UserKeySet
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new(
$bytes,
$env:WINDOWS_CERTIFICATE_PASSWORD,
$flags
)
if (-not $cert.HasPrivateKey) {
throw 'The Windows publisher certificate has no private key.'
}
$store = [System.Security.Cryptography.X509Certificates.X509Store]::new('My', 'CurrentUser')
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
try {
$store.Add($cert)
} finally {
$store.Close()
}
"WINDOWS_CERTIFICATE_THUMBPRINT=$($cert.Thumbprint)" >> $env:GITHUB_ENV

- name: Prepare signing configuration
run: >
node scripts/prepare-microsoft-store-config.mjs
crates/strand-tauri/tauri.microsoftstore.conf.json
target/microsoft-store/tauri.microsoftstore.conf.json

- name: Build signed offline Store MSI
run: >
pnpm tauri build
--config target/microsoft-store/tauri.microsoftstore.conf.json
--ci

- name: Verify publisher and updater signatures
shell: pwsh
run: |
$version = '${{ inputs.tag }}'.TrimStart('v')
$msi = "target/release/bundle/msi/Strand_${version}_x64_en-US.msi"
./scripts/verify-microsoft-store-package.ps1 `
-MsiPath $msi `
-ExecutablePath target/release/strand.exe
pnpm release:check-updater-signatures

New-Item -ItemType Directory -Force target/microsoft-store/dist | Out-Null
$asset = "target/microsoft-store/dist/Strand_${version}_x64_en-US_store.msi"
Copy-Item -LiteralPath $msi -Destination $asset
Copy-Item -LiteralPath "$msi.sig" -Destination "$asset.sig"
"STORE_ASSET=$asset" >> $env:GITHUB_ENV

- uses: actions/upload-artifact@v4
with:
name: strand-microsoft-store-${{ inputs.tag }}
path: target/microsoft-store/dist/*
if-no-files-found: error

- name: Upload versioned package URL asset
if: inputs.publish_asset
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload '${{ inputs.tag }}' $env:STORE_ASSET "$env:STORE_ASSET.sig"
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ members = [
]

[workspace.package]
version = "1.1.0"
version = "1.1.1"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Daniel Schwarz"]
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,14 @@ real-world repositories daily. The annotated `v1.0.0` tag and draft release
artifacts now exist; the updater-signature gates passed, macOS was notarized,
and Linux AppImages are keyless-signed with Sigstore. Public release remains
held for Windows publisher signing and the real macOS/GNOME/KDE candidate
runs. See the
runs. Microsoft Store engineering now has a verified packaged-classic MSIX
path: a development-identity package registered and launched from WindowsApps,
Store builds disable the direct updater in favor of Store-managed updates, and
the workflow produces an unsigned `.msixupload` for Partner Center to sign.
The signed offline-WebView2 MSI remains a fallback. Listing copy, privacy and
user-content policies, in-product inappropriate-content reporting, and
screenshots are prepared; the real Partner Center MSIX identity, trademark
approval, and Store certification remain owner gates. See the
[`1.0 parity audit`](./docs/git-client-1.0-audit.md), [`ROADMAP.md`](./ROADMAP.md),
[`release checklist`](./docs/release-checklist.md), and [`TASKS.md`](./TASKS.md).

Expand Down Expand Up @@ -272,6 +279,7 @@ strand/
│ ├── strand-azdo-protocol/ # Shared optional-helper JSON contract
│ ├── strand-azdo/ # Azure DevOps Server REST helper CLI
│ └── strand-tauri/ # Tauri 2 app shell + IPC commands
├── packaging/ # Store/distribution manifests assembled around release binaries
├── ui/ # Vite + React + TypeScript frontend
├── website/ # strand.danielss.dev: landing page + user guide (website/docs/, no build step)
├── docs/ # design notes, perf baseline, packaging
Expand Down
32 changes: 32 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2101,6 +2101,38 @@ tag itself is annotated but not cryptographically signed because this checkout
has no tag-signing identity. This owner-authorized push does not close the
Windows publisher, trademark, real-platform smoke, or public-release gates.

**Microsoft Store engineering path prepared (2026-07-24):** Strand now has a
separate x64 Store MSI flavor so GitHub's normal installer stays lean while the
Store candidate embeds WebView2 for fully offline silent installation. A manual
exact-tag workflow imports the external publisher PFX, requires timestamped
Authenticode on both the executable and MSI, rechecks the established updater
key, and can attach an immutable versioned Store asset. Partner Center copy,
live-generative-AI disclosure, public privacy and user-content policies,
certification notes, and an in-product inappropriate-content reporting route
plus four sanitized
2160×1380 screenshots are checked in. The local 1.1.1 engineering
build produced a 221.8 MB offline MSI; signature inspection confirmed that
both the MSI and embedded executable remain intentionally unpromoted and
unsigned. Partner Center reservation, the real publisher identity/secrets,
trademark/privacy approval, and clean
install/update/uninstall certification remain external gates.

**Microsoft Store MSIX path verified (2026-07-25):** The preferred Store route
is now an x64 packaged-classic MSIX assembled with MakeAppx from Strand's
release executable and parameterized Partner Center identity. The MSIX build
uses a compile-time distribution channel to disable the direct GitHub updater
and show Store-managed updates instead. A fresh 17,656,211-byte 1.1.1
development package passed full MakeAppx validation, was copied and signed with
a temporary test certificate, registered as
`dev.danielss.strand.msix.test_1.1.1.0_x64__94yh9fqhspgzm`, and launched a
responsive Strand window from the protected WindowsApps location. Cleanup
removed the exact package, process, and certificate from every touched store.
The manual **Microsoft Store MSIX candidate** workflow now requires the exact
Partner Center Name, Publisher, and PublisherDisplayName and emits an unsigned
`.msix` plus `.msixupload` for Microsoft to sign. The existing MSI path remains
a CA-certificate-dependent fallback. Product creation, real identity, upload,
and Store certification remain external gates.

---

## 1.1+ — Post-1.0
Expand Down
23 changes: 22 additions & 1 deletion TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1972,7 +1972,28 @@ quick-wins from that audit already landed (see ROADMAP changelog).
`STRAND` class-9 registrations exist in the US and EU for Signify lighting-
control software/equipment, plus other related software marks; owner/counsel
clearance or a rename decision is required before this row can close.
- ☐ Reserve `dev.danielss.strand` IDs in macOS App Store + Microsoft Store
- ☐ Reserve `dev.danielss.strand` in the macOS App Store; create **Strand** as
an MSIX/PWA product in Microsoft Partner Center and copy its exact Product
identity values
- ☑ Prepare the preferred Microsoft Store MSIX submission path
(`AppxManifest.xml.in`, `build-msix.ps1`, MSIX distribution channel,
`.msixupload` workflow, and local WindowsApps registration/launch proof;
2026-07-25). The fresh 1.1.1 development package passed full MakeAppx
validation, registered with a temporary test signature, and launched a
responsive Strand window from its real package identity. The test package
and certificates were removed and audited clean. Exact Partner Center
identity values, Store upload/certification, and a Store-signed clean-machine
pass remain external gates in `docs/microsoft-store-submission.md`.
- ☑ Prepare the Microsoft Store MSI/EXE fallback path
(`tauri.microsoftstore.conf.json`, `microsoft-store.yml`, fail-closed package
checks, Partner Center copy, privacy and user-content policies, in-product
inappropriate-content reporting, and four sanitized listing screenshots;
2026-07-25). A local
1.1.1 Store-format MSI build passed the repository gates; Authenticode
inspection correctly remains `NotSigned`. External Partner Center name
reservation, publisher certificate, trademark/privacy approval, and
clean-install certification remain open in
`docs/microsoft-store-submission.md`.
- ☑ Create GitHub org / repo + decide visibility (`danielss-dev/strand`,
made public 2026-06-12 — AGPL-3.0 LICENSE + COMMERCIAL.md at root)
- ☐ Social handles (X, Mastodon)
Expand Down
2 changes: 1 addition & 1 deletion crates/strand-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Strand",
"version": "1.1.0",
"version": "1.1.1",
"identifier": "dev.danielss.strand",
"build": {
"frontendDist": "../../ui/dist",
Expand Down
15 changes: 15 additions & 0 deletions crates/strand-tauri/tauri.microsoftstore.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://schema.tauri.app/config/2",
"bundle": {
"targets": ["msi"],
"publisher": "Daniel Schwarz Campos",
"windows": {
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.digicert.com",
"webviewInstallMode": {
"type": "offlineInstaller",
"silent": true
}
}
}
}
32 changes: 32 additions & 0 deletions docs/learnings.md
Original file line number Diff line number Diff line change
Expand Up @@ -1965,3 +1965,35 @@ metadata plus echoed stdin containing repository paths, prompts, and patches.
Preserve only bounded single-line diagnostics; classify known failures from the
diagnostic prefix before any prompt boundary, and replace every unclassified
transcript with a stable Strand-authored recovery hint.

**The Microsoft Store MSI is a separate, fail-closed release flavor
(2026-07-24).** Tauri's supported Store path links Partner Center to an
unpackaged MSI; do not improvise an unverified MSIX conversion. Keep the normal
GitHub MSI lean and merge `tauri.microsoftstore.conf.json` only for the Store
candidate so WebView2 is embedded and installed silently. The submitted URL is
immutable and version-bearing. Require both trust layers before upload:
timestamped Authenticode on `strand.exe` plus the MSI, and Tauri updater
signatures matching embedded key `84FCBFD2A981CE5D`. Store metadata must
explicitly disclose the optional live-generative-AI drafts, link the public
privacy notice, and keep a keyboard-reachable in-product route for reporting
inappropriate provider, user-generated, or generated content; keyboard support
alone is not evidence for claiming an audited accessibility standard.
When Strand displays or sends Git-hosted user content, Store policy 11.12 also
requires public user-content guidelines. Keep
`website/docs/content-guidelines.md` linked from the documentation manifest and
public site alongside the privacy notice, and keep reporting/removal guidance
aligned with the actual in-product reporting path.

**The verified packaged-classic MSIX is the preferred Microsoft Store route
(2026-07-25), superseding the earlier prohibition on an unverified conversion.**
Tauri still does not generate MSIX, but Microsoft's documented manual
packaging path is now proven for Strand: MakeAppx completed semantic validation,
a temporary test-signed package registered, and Windows launched the real
WindowsApps executable through its AUMID. Keep the manifest identity fully
parameterized and require the exact Partner Center `Name`, `Publisher`, and
`PublisherDisplayName`; never submit the development `.test` identity. Build
MSIX with `VITE_DISTRIBUTION=msix` so Microsoft Store owns updates and the
direct GitHub updater is neither checked nor offered. The Store candidate
workflow must emit an unsigned `.msixupload` for Partner Center to sign—do not
reintroduce a CA certificate requirement into this route. Retain the signed,
offline-WebView2 MSI workflow only as a fallback.
Loading
Loading