The first-run screen said "double-click punktfunk-sync.pext" and stopped there — no hint that the file is a download, let alone where from. Nothing in the console linked to it on any screen. The only copy that existed was in the README, pointing at the repo's Actions tab: sign in to Gitea, find the newest run, download a zip artifact, unzip it. That is not a download link, and it is not something a user discovers.
Worth recording, since it came up: the README of the very first commit promised the console "can also deploy the exporter for you… drops the extension into %APPDATA%\Playnite\Extensions\". No commit ever implemented it — git grep -i deploy at that commit finds that sentence and nothing else — and the claim was later deleted. So there is no lost self-install capability here to restore; the discoverability gap has been there since day one.
Real releases
A v* tag now creates a Gitea release with the freshly built punktfunk-sync.pext attached, a .sha256 sidecar so the download can be verified, and a body carrying the install instructions — that page is now the destination for every "where do I get this" link, so it has to answer on its own.
scripts/ci/gitea-release.sh is a trimmed sibling of the monorepo helper, kept as a copy rather than shared because this repo is standalone and a CI helper living in another repository is a dependency nobody can see. It carries over the two hard-won behaviours:
a plain asset POST 409s on a duplicate name, so re-running a tag's workflow would fail after publishing perfectly — upsert_asset deletes first;
every asset gets a checksum sidecar.
The branch-build artifact stays, for testing an unreleased exporter. It is just no longer where users are sent.
⚠️REGISTRY_TOKEN must carry write:repository, not only write:package — a package-only token 403s on the release attach while every other job stays green.
Links in the console
Overview's first-run state and the Library empty state both grow a "Download punktfunk-sync.pext" button pointing at /releases/latest.
Gitea serves /releases/latest as a 303 to the current tag's page (verified against git.unom.io). It has no GitHub-style /releases/latest/download/<file> route — that 404s — so this lands on the page and the user picks the asset, rather than faking a direct file link that would break the moment it was clicked. The console frames plugin UIs cross-origin with allow-popups, so a new tab is the one navigation that works from in there.
The launcher toggle, surfaced in Settings
It was config-file-only, which made it a dead end. A host that cannot open Playnite's Fullscreen app refuses that tile — and until unom/punktfunk#225, that refusal cost the entire library, not just the tile. The escape hatch existed and nobody could find it.
Verification
bunx biome check clean
tsc --noEmit clean in all three workspaces (contract / plugin / ui)
50/50 domain tests pass
bun run build:all succeeds; both CI sanity gates pass (valid default export, no fixtures in the production bundle)
the link survives minification: the bundle contains `${…/releases}/latest` resolving to the right URL, and the button label ships twice (Overview + Library)
workflow YAML parses; the release step's body generation was simulated end-to-end and renders as markdown; the helper's JSON/sha256/urlencode functions were unit-checked (correct empty-file digest, top-level id disambiguated from nested author.id)
⚠️bun.lock workspace-version drift is real and CI-invisible. After bumping plugin/package.json to 0.4.3, both bun install and bun install --force left the lock recording 0.4.2, and --frozen-lockfilepassed on the drift. Corrected by hand — same trap as the 0.4.1 lock-only fix.
After merge
Publishing alone reaches no store users — punktfunk-plugin-index pins exact versions. The chain is: merge → push tag v0.4.3 → bump the index pin. minHost can stay 0.25.0; nothing here asks anything new of the host.
The first-run screen said "double-click `punktfunk-sync.pext`" and stopped there — no hint that the file is a download, let alone where from. Nothing in the console linked to it on any screen. The only copy that existed was in the README, pointing at the repo's **Actions tab**: sign in to Gitea, find the newest run, download a zip artifact, unzip it. That is not a download link, and it is not something a user discovers.
> Worth recording, since it came up: the README of the *very first* commit promised the console "can also **deploy the exporter for you**… drops the extension into `%APPDATA%\Playnite\Extensions\`". No commit ever implemented it — `git grep -i deploy` at that commit finds that sentence and nothing else — and the claim was later deleted. So there is no lost self-install capability here to restore; the discoverability gap has been there since day one.
## Real releases
A `v*` tag now creates a Gitea release with the freshly built `punktfunk-sync.pext` attached, a `.sha256` sidecar so the download can be verified, and a body carrying the install instructions — that page is now the destination for every "where do I get this" link, so it has to answer on its own.
`scripts/ci/gitea-release.sh` is a trimmed sibling of the monorepo helper, kept as a copy rather than shared because this repo is standalone and a CI helper living in another repository is a dependency nobody can see. It carries over the two hard-won behaviours:
- a plain asset POST **409s on a duplicate name**, so re-running a tag's workflow would fail *after* publishing perfectly — `upsert_asset` deletes first;
- every asset gets a checksum sidecar.
The branch-build artifact stays, for testing an unreleased exporter. It is just no longer where users are sent.
⚠️ **`REGISTRY_TOKEN` must carry `write:repository`, not only `write:package`** — a package-only token 403s on the release attach while every other job stays green.
## Links in the console
Overview's first-run state and the Library empty state both grow a **"Download punktfunk-sync.pext"** button pointing at `/releases/latest`.
Gitea serves `/releases/latest` as a **303** to the current tag's page (verified against git.unom.io). It has **no** GitHub-style `/releases/latest/download/<file>` route — that **404s** — so this lands on the page and the user picks the asset, rather than faking a direct file link that would break the moment it was clicked. The console frames plugin UIs cross-origin with `allow-popups`, so a new tab is the one navigation that works from in there.
## The `launcher` toggle, surfaced in Settings
It was config-file-only, which made it a dead end. A host that cannot open Playnite's Fullscreen app refuses that tile — and until unom/punktfunk#225, that refusal cost the **entire library**, not just the tile. The escape hatch existed and nobody could find it.
## Verification
- `bunx biome check` clean
- `tsc --noEmit` clean in all three workspaces (contract / plugin / ui)
- 50/50 domain tests pass
- `bun run build:all` succeeds; both CI sanity gates pass (valid default export, no fixtures in the production bundle)
- the link survives minification: the bundle contains `` `${…/releases}/latest` `` resolving to the right URL, and the button label ships twice (Overview + Library)
- workflow YAML parses; the release step's body generation was simulated end-to-end and renders as markdown; the helper's JSON/sha256/urlencode functions were unit-checked (correct empty-file digest, top-level `id` disambiguated from nested `author.id`)
⚠️ **bun.lock workspace-version drift is real and CI-invisible.** After bumping `plugin/package.json` to 0.4.3, both `bun install` and `bun install --force` left the lock recording `0.4.2`, and `--frozen-lockfile` **passed on the drift**. Corrected by hand — same trap as the 0.4.1 lock-only fix.
## After merge
Publishing alone reaches no store users — `punktfunk-plugin-index` pins exact versions. The chain is: merge → push tag `v0.4.3` → bump the index pin. `minHost` can stay `0.25.0`; nothing here asks anything new of the host.
The first-run screen said "double-click punktfunk-sync.pext" and stopped
there — no hint the file is a download, let alone where from. Nothing in the
console linked to it on any screen. The only copy that existed was in the
README, and it pointed at the repo's Actions tab: sign in to Gitea, find the
newest run, download a zip artifact, unzip it. That is not a download link,
and it is not something a user discovers.
(The README of the very first commit did promise the console "can also deploy
the exporter for you", dropping the extension into %APPDATA%\Playnite\
Extensions\. No commit ever implemented it — `git grep -i deploy` finds that
sentence and nothing else — and the claim was later deleted. So there is no
lost capability here to restore; the discoverability gap has been there since
day one.)
Two halves to the fix, because a link needs somewhere to point:
- **Real releases.** A `v*` tag now creates a Gitea release with the freshly
built `punktfunk-sync.pext` attached, plus a `.sha256` sidecar so the
download can be verified, and a body that carries the install instructions —
that page is now the destination for every "where do I get this" link, so it
has to answer on its own. `scripts/ci/gitea-release.sh` is a trimmed sibling
of the monorepo helper, carrying over its two hard-won behaviours: a plain
asset POST 409s on a duplicate name (so re-running a tag fails after
publishing fine), and every asset gets a checksum sidecar.
The branch-build artifact stays, for testing an unreleased exporter. It is
just no longer where users are sent.
- **Links in the console.** Overview's first-run state and the Library empty
state both grow a "Download punktfunk-sync.pext" button pointing at
/releases/latest. Gitea serves that as a 303 to the current tag; it has no
GitHub-style /releases/latest/download/<file> route (that 404s), so this
lands on the page rather than faking a direct file link that would break the
moment it was clicked. The console frames plugin UIs cross-origin with
`allow-popups`, so a new tab is the one navigation that works from in there.
Also surfaces the `launcher` toggle in Settings. It was config-file-only,
which made it a dead end: a host that cannot open Playnite's Fullscreen app
refuses that tile, and until the host learned to drop just the tile, that
refusal cost the entire library. The escape hatch existed and nobody could
find it.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The first-run screen said "double-click
punktfunk-sync.pext" and stopped there — no hint that the file is a download, let alone where from. Nothing in the console linked to it on any screen. The only copy that existed was in the README, pointing at the repo's Actions tab: sign in to Gitea, find the newest run, download a zip artifact, unzip it. That is not a download link, and it is not something a user discovers.Real releases
A
v*tag now creates a Gitea release with the freshly builtpunktfunk-sync.pextattached, a.sha256sidecar so the download can be verified, and a body carrying the install instructions — that page is now the destination for every "where do I get this" link, so it has to answer on its own.scripts/ci/gitea-release.shis a trimmed sibling of the monorepo helper, kept as a copy rather than shared because this repo is standalone and a CI helper living in another repository is a dependency nobody can see. It carries over the two hard-won behaviours:upsert_assetdeletes first;The branch-build artifact stays, for testing an unreleased exporter. It is just no longer where users are sent.
⚠️
REGISTRY_TOKENmust carrywrite:repository, not onlywrite:package— a package-only token 403s on the release attach while every other job stays green.Links in the console
Overview's first-run state and the Library empty state both grow a "Download punktfunk-sync.pext" button pointing at
/releases/latest.Gitea serves
/releases/latestas a 303 to the current tag's page (verified against git.unom.io). It has no GitHub-style/releases/latest/download/<file>route — that 404s — so this lands on the page and the user picks the asset, rather than faking a direct file link that would break the moment it was clicked. The console frames plugin UIs cross-origin withallow-popups, so a new tab is the one navigation that works from in there.The
launchertoggle, surfaced in SettingsIt was config-file-only, which made it a dead end. A host that cannot open Playnite's Fullscreen app refuses that tile — and until unom/punktfunk#225, that refusal cost the entire library, not just the tile. The escape hatch existed and nobody could find it.
Verification
bunx biome checkcleantsc --noEmitclean in all three workspaces (contract / plugin / ui)bun run build:allsucceeds; both CI sanity gates pass (valid default export, no fixtures in the production bundle)`${…/releases}/latest`resolving to the right URL, and the button label ships twice (Overview + Library)iddisambiguated from nestedauthor.id)⚠️ bun.lock workspace-version drift is real and CI-invisible. After bumping
plugin/package.jsonto 0.4.3, bothbun installandbun install --forceleft the lock recording0.4.2, and--frozen-lockfilepassed on the drift. Corrected by hand — same trap as the 0.4.1 lock-only fix.After merge
Publishing alone reaches no store users —
punktfunk-plugin-indexpins exact versions. The chain is: merge → push tagv0.4.3→ bump the index pin.minHostcan stay0.25.0; nothing here asks anything new of the host.