Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d847084
chore(release): bump to 1.9.5-rc.1 [skip ci]
github-actions[bot] Aug 13, 2026
a44206c
fix(recording): open the recording a failed stop left playable
EtienneLescot Aug 13, 2026
db101b9
fix(editor): import a recording once, so reopening keeps the project …
EtienneLescot Aug 13, 2026
39d830f
docs(e2e): say what injected input can never prove about the HUD
EtienneLescot Aug 13, 2026
b146de5
docs(e2e): give a binary-string check that works on Windows
EtienneLescot Aug 13, 2026
7fe2089
docs(agents): name the thing that actually builds a capture helper
EtienneLescot Aug 13, 2026
dbd0a29
docs(agents): the HUD click-through rule is macOS too, not Windows only
EtienneLescot Aug 13, 2026
7c4ce51
docs(agents): point at the testing docs, which only linked one way
EtienneLescot Aug 13, 2026
3ba1305
docs(testing): log the 1.9.5-rc.1 Windows pass in the results table
EtienneLescot Aug 13, 2026
f26fc45
docs(testing): the manual tester is an agent, so say what not to driv…
EtienneLescot Aug 13, 2026
2ec6473
docs(testing): give the editor sections their own reason not to inject
EtienneLescot Aug 13, 2026
6496934
docs(agents): orient an agent toward computer-use before it needs to …
EtienneLescot Aug 13, 2026
b1b81de
docs(agents): show the control search, not just require it
EtienneLescot Aug 13, 2026
dfff6e2
docs(testing): log the rc.2 regression pass, and what nearly faked a bug
EtienneLescot Aug 14, 2026
d8b49e3
docs(testing): "the machine is at 100%" is not a reason to skip DPI
EtienneLescot Aug 14, 2026
e3c332c
docs(testing): batch the computer-use grants so the operator can leave
EtienneLescot Aug 14, 2026
322fe94
docs(testing): fix the grant ordering, and name apps the way the reso…
EtienneLescot Aug 14, 2026
ba1d746
docs(testing): log the macOS rc.1 pass, and why a clean stop proves n…
EtienneLescot Aug 14, 2026
a3dd4a1
docs(testing): fold in the by-hand macOS repro, and correct the 'no e…
EtienneLescot Aug 14, 2026
a37dca9
docs(testing): kill the last confound — audio off, no screenshots, sa…
EtienneLescot Aug 14, 2026
9535bc1
docs(testing): point the macOS blocker at its fix
EtienneLescot Aug 14, 2026
ee2a1ee
docs(testing): downgrade four claims the evidence does not carry
EtienneLescot Aug 14, 2026
155ba4c
fix(recording): stop macOS fragments carrying an offset the box canno…
EtienneLescot Aug 14, 2026
327e742
fix(recording): separate the two writer-failure events, and quote the…
EtienneLescot Aug 14, 2026
fcd96d6
chore(release): bump to 1.9.5-rc.2 [skip ci]
github-actions[bot] Aug 14, 2026
afbfb7b
chore(release): bump to 1.9.5 [skip ci]
github-actions[bot] Aug 14, 2026
b162b18
fix(store): give msstore a project to publish, not just a package
EtienneLescot Aug 14, 2026
dc874af
fix(store): drop the write token from the Store job, and stop calling a
EtienneLescot Aug 14, 2026
cdd5fa6
ci(store): add a retry path for Store submission that rebuilds nothing
EtienneLescot Aug 14, 2026
19e40ce
ci(store): fix an empty expression, a silent skip, and an unchecked r…
EtienneLescot Aug 14, 2026
7d253c5
fix(store): the option is --inputDirectory, and it takes a directory
EtienneLescot Aug 14, 2026
765b6f1
ci(aur): say which key AUR refused, instead of just that it refused one
EtienneLescot Aug 15, 2026
596bdda
ci(aur): stop overclaiming what the probe proves, bound it, pin the key
EtienneLescot Aug 15, 2026
d49385e
fix(hud): give click-through a way out that Windows cannot revoke
EtienneLescot Aug 18, 2026
7402366
test(hud): make the click-through negative case able to fail
EtienneLescot Aug 18, 2026
99a806a
docs(agents): the HUD lift is a cursor poll now, not a WH_MOUSE_LL hook
EtienneLescot Aug 18, 2026
b9686be
docs(agents): the poll keys off the cursor RELATIVE to the window
EtienneLescot Aug 18, 2026
f9fbe8f
chore(release): bump to 1.9.6-rc.1 [skip ci]
github-actions[bot] Aug 18, 2026
585312d
chore(release): bump to 1.9.6 [skip ci]
github-actions[bot] Aug 18, 2026
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
37 changes: 37 additions & 0 deletions .github/workflows/aur-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -366,10 +366,47 @@ jobs:
HostName aur.archlinux.org
User aur
IdentityFile ~/.ssh/aur_key
# Offer this key and nothing else. Without it ssh walks whatever
# else it can find first, and AUR can refuse on a key that is not
# the one being diagnosed -- so the probe below would be reporting
# on a different identity than the push. It is also the standard
# cause of "permission denied" against AUR with more than one key.
IdentitiesOnly yes
StrictHostKeyChecking yes
UserKnownHostsFile ~/.ssh/aur_known_hosts
SSHCONF

# "Permission denied (publickey)" is the same message for three different
# problems: a secret that is not a readable key, a key nobody registered
# on AUR, and a key registered to an account that does not maintain this
# package. v1.9.1, v1.9.2 and v1.9.5 all died here and none said which.
#
# This narrows it by elimination rather than proving the last one. A
# public key is public, so printing it costs nothing and lets what CI
# presents be compared against what is on the account. `help` is the
# documented way to test AUR auth without pushing: if it answers, the key
# parses AND is registered, so only authorization for ${PACKAGE} is left.
# Its reply also enumerates the commands the account may run, which is
# where to look for a repo-listing one if this needs to go further.
- name: Identify the key AUR sees
if: steps.aur_secret.outputs.configured == 'true' && !inputs.dry_run
continue-on-error: true
env:
PACKAGE: ${{ vars.AUR_PACKAGE_NAME }}
run: |
if ! ssh-keygen -y -f ~/.ssh/aur_key > /tmp/aur_key.pub 2>/tmp/aur_key.err; then
echo "::error::AUR_SSH_PRIVATE_KEY is not a readable private key: $(cat /tmp/aur_key.err)"
exit 0
fi
echo "Public key this workflow presents:"
cat /tmp/aur_key.pub
ssh-keygen -lf /tmp/aur_key.pub || true
echo "--- what AUR says about it (auth only; NOT write access to ${PACKAGE}) ---"
# Bounded: a diagnostic must never be the thing that hangs a release.
# Exits non-zero by design; the message is the payload.
timeout -k 5 30 ssh -o BatchMode=yes -o ConnectTimeout=10 \
aur@aur.archlinux.org help 2>&1 || true

- name: Commit and push
if: steps.aur_secret.outputs.configured == 'true' && !inputs.dry_run
working-directory: aur-repo
Expand Down
33 changes: 32 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,12 @@ jobs:
publish-msstore:
name: Publish to Microsoft Store
runs-on: windows-latest
# The workflow-wide token is `contents: write` because publish-release needs
# it. This job only reads: it checks the tree out so the CLI can identify the
# project, downloads a same-run artifact (which uses the runtime token, not
# this one), and talks to Partner Center with its own Entra credentials.
permissions:
contents: read
needs:
- build-windows-store
- publish-release
Expand Down Expand Up @@ -999,6 +1005,24 @@ jobs:
exit 1
fi

# `msstore publish` takes a PROJECT root, not a package: it detects the app
# type there (Electron, via package.json) and only then accepts the built
# package through `--inputDirectory`. This job used to check nothing out, so
# there was no project to point it at. Checkout runs before the artifact
# download on purpose — actions/checkout cleans the workspace, and would
# delete the package if it ran after.
- name: Check out the project
if: steps.store.outputs.enabled == 'true'
uses: actions/checkout@v7
with:
# Nothing here pushes; the tree is only read so the CLI can see it is
# an Electron project. Left at the default, checkout writes the
# workflow's `contents: write` token into .git/config, where every
# later step can read it — including a third-party CLI action and the
# Store submission. See the job-level `permissions` above: same reason,
# other half.
persist-credentials: false

- name: Download Store package
if: steps.store.outputs.enabled == 'true'
uses: actions/download-artifact@v4
Expand Down Expand Up @@ -1031,7 +1055,14 @@ jobs:
throw 'more than one .appx in the artifact — refusing to guess which one to submit'
}
Write-Output "Submitting $($appx.Name) to product $env:PRODUCT_ID"
msstore publish $appx.FullName -id $env:PRODUCT_ID
# The positional argument is the project root, NOT the package — passing
# the .appx there is what failed the first real run of this job on
# v1.9.5: "We could not find a project publisher for the project at
# ...Openscreen.Setup.1.9.5.appx". The package goes through the option
# below, which takes the DIRECTORY holding it — the CLI's own usage
# says `-i, --inputDirectory`, and rejects the `--inputFile` that
# Microsoft Learn documents. The binary wins.
msstore publish . --inputDirectory $appx.Directory.FullName --appId $env:PRODUCT_ID

# Report what happened, not what was configured. Keyed off `enabled` alone
# under always(), this claimed "Submitted to the Store" when `msstore
Expand Down
228 changes: 228 additions & 0 deletions .github/workflows/publish-msstore.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
name: Publish to Microsoft Store (retry)

# Submits an already-built appx to the Store, without rebuilding anything.
#
# build.yml's own publish-msstore job is the normal path. This exists because
# that job has no usable retry: re-running it replays the workflow definition
# frozen into the original run, so a fix landed afterwards is not picked up, and
# re-dispatching build.yml rebuilds every platform and re-uploads the release
# assets with `--clobber` — rewriting a published release to correct a Store
# submission. v1.9.5 hit exactly that dead end.
#
# So this takes the appx that build already produced and submits it. Nothing is
# rebuilt, no release asset is touched, and the flaky macOS legs are not in the
# way.

on:
workflow_dispatch:
inputs:
release_tag:
description: "Stable tag whose appx should be submitted (e.g. v1.9.5)"
required: true
type: string
run_id:
description: "Build run to take the appx from. Leave empty to use the most recent build for the tag."
required: false
type: string
dry_run:
description: "Create the submission but leave it in draft (--noCommit). Use this to test without shipping."
required: false
type: boolean
default: false

# Read-only: this checks the tree out so the CLI can identify the project, and
# reads a build artifact. Partner Center is reached with its own Entra
# credentials, not with this token.
permissions:
contents: read
actions: read

concurrency:
group: publish-msstore-${{ inputs.release_tag }}
cancel-in-progress: false

jobs:
submit:
name: Submit ${{ inputs.release_tag }} to the Store
runs-on: windows-latest
# No job-level `if` on MSSTORE_PRODUCT_ID, deliberately. build.yml can afford
# to skip: it is one job among many in an automatic release. This one is
# something a person asked for by hand, and a skipped job is green and
# silent — the exact shape that let Homebrew and WinGet report success while
# publishing nothing for eight releases. Missing configuration is checked
# below and fails loudly instead.
steps:
- name: Validate the tag
id: tag
shell: bash
env:
TAG: ${{ inputs.release_tag }}
run: |
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Expected a stable tag like v1.9.5; got '${TAG}'. RCs must never reach the Store."
exit 1
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"

# All-or-nothing, as in build.yml: a half-configured publisher is a
# misnamed secret, and failing loudly beats submitting nothing quietly.
- name: Resolve Store configuration
id: store
shell: bash
env:
MSSTORE_PRODUCT_ID: ${{ vars.MSSTORE_PRODUCT_ID }}
AZURE_AD_TENANT_ID: ${{ secrets.AZURE_AD_TENANT_ID }}
AZURE_AD_APPLICATION_CLIENT_ID: ${{ secrets.AZURE_AD_APPLICATION_CLIENT_ID }}
AZURE_AD_APPLICATION_SECRET: ${{ secrets.AZURE_AD_APPLICATION_SECRET }}
SELLER_ID: ${{ secrets.SELLER_ID }}
run: |
# MSSTORE_PRODUCT_ID is in here rather than in a job-level `if` so an
# unconfigured repository gets an error and a Summary line, not a
# silent skip on a run somebody triggered on purpose.
required=(MSSTORE_PRODUCT_ID AZURE_AD_TENANT_ID
AZURE_AD_APPLICATION_CLIENT_ID AZURE_AD_APPLICATION_SECRET
SELLER_ID)
missing=()
for name in "${required[@]}"; do
[[ -n "${!name}" ]] || missing+=("$name")
done
if [[ ${#missing[@]} -ne 0 ]]; then
echo "::error::Store configuration incomplete; missing: ${missing[*]}"
exit 1
fi

- name: Check out the tag
uses: actions/checkout@v7
with:
# `msstore publish` takes a project root and detects the app type
# there; it is not given a package to introspect. Checking out the tag
# rather than the default branch keeps that project state matching the
# appx being submitted.
ref: ${{ steps.tag.outputs.tag }}
# Nothing here pushes, and the token would otherwise sit in .git/config
# for the third-party CLI action below to read.
persist-credentials: false

- name: Resolve the build run
id: run
shell: bash
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.tag.outputs.tag }}
RUN_ID: ${{ inputs.run_id }}
run: |
if [[ -n "$RUN_ID" ]]; then
# A hand-typed run id is the one input that can quietly ship the
# wrong bytes: nothing downstream re-checks what is inside the
# artifact, so a transposed digit could submit another commit's
# package to the Store under this tag. Confirm it is a build.yml run
# and that it was built from the tag being published.
INFO="$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID}" \
--jq '{path: .path, sha: .head_sha}' 2>/dev/null)" || {
echo "::error::Run ${RUN_ID} not found in ${GITHUB_REPOSITORY}."
exit 1
}
RUN_PATH="$(jq -r .path <<<"$INFO")"
RUN_SHA="$(jq -r .sha <<<"$INFO")"
TAG_SHA="$(git rev-parse HEAD)"
if [[ "$RUN_PATH" != ".github/workflows/build.yml" ]]; then
echo "::error::Run ${RUN_ID} is ${RUN_PATH}, not build.yml."
exit 1
fi
if [[ "$RUN_SHA" != "$TAG_SHA" ]]; then
echo "::error::Run ${RUN_ID} built ${RUN_SHA}, but ${TAG} is ${TAG_SHA}."
exit 1
fi
echo "Using run ${RUN_ID}: build.yml at ${RUN_SHA}"
else
# Deliberately not filtered on conclusion: the run this is most
# likely to be retrying is the one whose Store step failed, so
# requiring success would skip exactly the build we want.
RUN_ID="$(gh run list --workflow build.yml --branch "$TAG" \
--limit 1 --json databaseId --jq '.[0].databaseId')"
if [[ -z "$RUN_ID" || "$RUN_ID" == "null" ]]; then
echo "::error::No build.yml run found for ${TAG}. Pass run_id explicitly."
exit 1
fi
echo "Resolved the most recent build for ${TAG}: $RUN_ID"
fi
echo "id=$RUN_ID" >> "$GITHUB_OUTPUT"

- name: Download the Store package
shell: bash
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ steps.run.outputs.id }}
run: |
mkdir -p artifacts/store
gh run download "$RUN_ID" --name openscreen-windows-store --dir artifacts/store

- name: Configure Microsoft Store CLI
uses: microsoft/microsoft-store-apppublisher@v1.1

- name: Submit the package to the Store
id: submit
shell: pwsh
env:
PRODUCT_ID: ${{ vars.MSSTORE_PRODUCT_ID }}
TENANT_ID: ${{ secrets.AZURE_AD_TENANT_ID }}
SELLER_ID: ${{ secrets.SELLER_ID }}
CLIENT_ID: ${{ secrets.AZURE_AD_APPLICATION_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.AZURE_AD_APPLICATION_SECRET }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
# Secrets arrive through env, not through expression interpolation
# expanded straight into shell source.
msstore reconfigure `
--tenantId $env:TENANT_ID `
--sellerId $env:SELLER_ID `
--clientId $env:CLIENT_ID `
--clientSecret $env:CLIENT_SECRET

$packages = @(Get-ChildItem artifacts/store -Recurse -Include '*.appx','*.msix','*.msixupload')
if ($packages.Count -eq 0) { throw 'no package in the downloaded artifact' }
if ($packages.Count -ne 1) {
throw "expected one package, found $($packages.Count): refusing to guess which to submit"
}
$pkg = $packages[0]

# The positional argument is the project root, NOT the package: passing
# the package there is what failed v1.9.5 ("could not find a project
# publisher"). The package goes through the option below, which takes
# the DIRECTORY holding it: the CLI's own usage says
# `-i, --inputDirectory`, and rejects the `--inputFile` that Microsoft
# Learn documents. The v1.9.5 dry run is what caught that.
# Not $args: that is a PowerShell automatic variable.
$cmdArgs = @('publish', '.', '--inputDirectory', $pkg.Directory.FullName, '--appId', $env:PRODUCT_ID)
if ($env:DRY_RUN -eq 'true') {
# Leaves the submission in draft instead of sending it to
# certification: the only way to test this path without shipping.
$cmdArgs += '--noCommit'
Write-Output "DRY RUN: submitting $($pkg.Name) as a draft only"
} else {
Write-Output "Submitting $($pkg.Name) to product $env:PRODUCT_ID"
}
msstore @cmdArgs

# Report what happened, not what was configured — the mistake that let
# v1.9.5's failed submission read as a success (see 1617c930).
- name: Summary
if: always()
shell: bash
env:
SUBMIT: ${{ steps.submit.outcome }}
TAG: ${{ inputs.release_tag }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
case "$SUBMIT" in
success)
if [[ "$DRY_RUN" == "true" ]]; then
echo "Draft submission created for ${TAG}; nothing was sent to certification." >> "$GITHUB_STEP_SUMMARY"
else
echo "Submitted ${TAG} to the Store. Certification still has to pass before it goes live." >> "$GITHUB_STEP_SUMMARY"
fi
;;
*)
echo "Store submission for ${TAG} did NOT happen (submit step: ${SUBMIT:-did not run}). The appx is unchanged; upload it by hand if this keeps failing." >> "$GITHUB_STEP_SUMMARY"
;;
esac
Loading
Loading