From 78b4b6ee1a2f8fbe6d7e899e603d00f74d479771 Mon Sep 17 00:00:00 2001 From: navin10sharma <3096611+navin10sharma@users.noreply.github.com> Date: Fri, 25 Sep 2026 13:30:17 +0530 Subject: [PATCH] Docs: specific README intro; public release guide without local paths or internal notes --- README.md | 12 +- RELEASE.md | 485 +++++++++++++---------------------------------------- 2 files changed, 126 insertions(+), 371 deletions(-) diff --git a/README.md b/README.md index a2807d4..1813dc6 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@ [![WordPress plugin](https://img.shields.io/wordpress/plugin/v/seatlayer-seating-charts.svg)](https://wordpress.org/plugins/seatlayer-seating-charts/) [![License: MIT](https://img.shields.io/badge/license-MIT-111827.svg)](LICENSE) -SeatLayer is interactive seating chart software built for stadium scale. Platforms embed the white-label seat picker with their own checkout; organizers sell seated events on their own website with their own payment gateway. - -The official SeatLayer plugin adds interactive seating charts and seat maps to -WordPress for event ticket sales with reserved seats. Buyers choose exact seats -or find a group with Best Available, explore 3D on supported devices, and check -out through the payment gateway the organizer connects to SeatLayer. +The official WordPress seating chart plugin for SeatLayer. It adds interactive +seating charts and seat maps to WordPress for event ticket sales with reserved +seats. Buyers choose exact seats or find a group with Best Available, explore 3D +on supported devices, and check out through the payment gateway the organizer +connects to SeatLayer. SeatLayer is seating chart and reserved-seat ticketing +software built for venues up to stadium scale. Create theatre, auditorium, concert and dinner-show layouts in SeatLayer, then publish them on WordPress with a Gutenberg block or shortcode. diff --git a/RELEASE.md b/RELEASE.md index b69d0a0..2388a80 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,434 +1,189 @@ # Releasing Two destinations: **GitHub** is the source of truth and **wordpress.org** is where -users install the plugin. The normal release path is automated by -`.github/workflows/release-wordpress.yml`; the detailed manual steps below are -the recovery procedure. - -> **Status, 2026-08-12.** `0.2.0` is published at -> https://wordpress.org/plugins/seatlayer-seating-charts/. GitHub release -> automation is configured for subsequent versions. -> -> Two questions below are settled and must not be re-opened. **The slug is -> `seatlayer-seating-charts`** — wp.org assigned it, it is permanent, and the -> "short slug `seatlayer`" reversal block before 0.1 is dead. **The contributor is -> `navincse`**, which resolves; `seatlayer` never existed as a wordpress.org -> account, so 0.1 is history rather than work. Every other URL the plugin declares -> was re-checked on 2026-08-12 and resolves. - -Read Part 0 first; everything after it is meant to be run start to finish without -stopping to decide anything. - -**The whole sweep, in order:** - -| | | -|---|---| -| **0** | Two account facts to confirm, and one optional asset call. Nothing to code. | -| **1** | Verify locally: syntax → Plugin Check → click through it by hand. | -| **2** | GitHub: verify the public repository, push, and tag the release. | -| **3** | wp.org: build the zip → submit → *(wait for approval)* → SVN trunk + tag → listing assets. | +users install the plugin (https://wordpress.org/plugins/seatlayer-seating-charts/). +The normal release path is automated by `.github/workflows/release-wordpress.yml`. +The manual SVN steps further down are the recovery procedure. -## Normal automated release - -1. Update the version in `seatlayer.php`, `SEATLAYER_VERSION`, and the - `Stable tag` in `readme.txt` to the same semantic version. -2. Merge the verified change to `main`. -3. Create and publish a GitHub Release whose tag is that version, preferably - `v0.3.0` style. -4. GitHub Actions verifies the tag and focused tests, deploys it to the permanent - WordPress.org SVN repository, and attaches the installable ZIP to the GitHub - Release. - -The workflow intentionally rejects prereleases and mismatched versions. It needs -the repository secrets `SVN_USERNAME` and `SVN_PASSWORD`. WordPress.org still -uses SVN as its publishing backend; GitHub Actions performs that SVN operation -for maintainers. - -The manual process below remains available if GitHub Actions or WordPress.org is -unavailable. - ---- - -## Part 0 — confirm these BEFORE you submit anything - -Two account facts and one optional call, all cheap now and awkward later. - -The slug/text-domain question that used to head this list is **settled and -applied in code** — kept here only so the decision is reversible, not because -anything is pending. - -### 0.0 The slug and text domain — SETTLED, no action needed - -wordpress.org derives the plugin slug from the **Plugin Name** header, and the -slug it assigns is permanent. The text domain has to equal that slug or the -language packs built on translate.wordpress.org never load, and Plugin Check -reports the mismatch as an error. - -These used to disagree. They now agree: - -| | value | -|---|---| -| Plugin Name | `SeatLayer Seating Charts` | -| Expected wp.org slug | `seatlayer-seating-charts` | -| Text Domain | `seatlayer-seating-charts` | - -The **name** was kept and the **domain** was lengthened to match it, rather than -the other way round: the name is already public-facing in `readme.txt` and on -seatlayer.io, and a text domain is invisible to everyone except the translation -tooling. Churning the branding to save nine characters in a URL is the wrong -trade. - -So: **do nothing here.** Use `seatlayer-seating-charts` as the slug everywhere -below — it is already the `--prefix` in Part 3.1 and the SVN path in Part 3.3. - -
-Only if you would rather have the short slug seatlayer — the full reversal - -Worth doing only if `seatlayer` is actually available (check that -https://wordpress.org/plugins/seatlayer/ returns 404) and only **before** the -first submission — after wp.org assigns a slug it is permanent. - -This renames the plugin so the name-derived slug becomes `seatlayer`, and puts -the text domain back to `seatlayer`. - -```sh -cd /Users/paiteq/projects/seatlayer-sdks/wordpress - -# 1. The public name, in both places it appears. -sed -i '' 's/^ \* Plugin Name: SeatLayer Seating Charts$/ * Plugin Name: SeatLayer/' seatlayer.php -sed -i '' 's/^=== SeatLayer Seating Charts ===$/=== SeatLayer ===/' readme.txt - -# 2. The text domain: 62 call sites + the header. -grep -rl "'seatlayer-seating-charts'" seatlayer.php includes assets/js \ - | xargs sed -i '' "s/'seatlayer-seating-charts'/'seatlayer'/g" -sed -i '' 's/^ \* Text Domain: seatlayer-seating-charts$/ * Text Domain: seatlayer/' seatlayer.php - -# 3. The slug in this file's own commands (Parts 1.2, 3.1, 3.3). -sed -i '' 's/seatlayer-seating-charts/seatlayer/g' RELEASE.md -``` - -Then verify — expect **62** hits, and confirm the two non-gettext domain -registrations came along: - -```sh -grep -rc "'seatlayer'" seatlayer.php includes assets/js -grep -n "load_plugin_textdomain" seatlayer.php -grep -n "wp_set_script_translations" includes/class-seatlayer-block.php -``` - -**One thing the blanket `sed` in step 2 is safe about, and you must keep safe if -you hand-edit instead:** `includes/class-seatlayer-settings.php` line ~126 holds -a bare `'seatlayer'` that is **not** a text domain — it is the `add_options_page()` -**menu slug**, which is what puts the settings screen at -`options-general.php?page=seatlayer`. Changing it would move the settings page -and break any bookmark or documentation link to it. The step-2 command only -matches `'seatlayer-seating-charts'`, so it cannot touch that line; a reversed -search-and-replace (`'seatlayer'` → something else) would. +Run every command below from the root of this repository. The examples use a +shell variable for the version being released: ```sh -# The menu slug must still read 'seatlayer' after any of this. -sed -n '122,128p' includes/class-seatlayer-settings.php +VERSION=0.3.0 ``` -Re-lint afterwards (Part 1.1), then re-run Plugin Check (1.2). - -
- -### 0.1 `Contributors: seatlayer` must be a real wordpress.org account - -Not a GitHub account — a wordpress.org one. Confirm -https://profiles.wordpress.org/seatlayer/ resolves; if it does not, register it -(or list the account that will actually own the plugin) before submitting. A -non-existent contributor is a stall in the review queue. - -### 0.2 `Tested up to:` must name the current WordPress release - -`readme.txt` says `6.8`. Check the current version at -https://wordpress.org/download/ and set it to that. A stale value shows users a -"may not be compatible" warning on the plugin page. - -### 0.3 Screenshots — the readme no longer promises any. Optional, recommended. - -`readme.txt` used to carry a `== Screenshots ==` section listing three images -that have never existed. **That section has been removed**, deliberately. - -The reasoning, so it can be overruled knowingly: screenshot files do not live in -this repository and are not in the plugin zip — wp.org reads them from the SVN -`assets/` directory, which you only get **after** approval (Part 3.4, which comes -after 3.3). A readme that names three screenshots renders three broken images on -the public plugin page for as long as the files are missing, and the natural -order of the steps below guarantees a window where they are. Between a listing -with no screenshots (unremarkable — plenty of good plugins have none) and a -listing with three broken images (looks abandoned), the empty one is the honest -default, and it is the one that stays correct if this step is never done. - -They are still worth adding, and adding them is four lines. To do it: - -**1. Capture them.** Needs a running WordPress with the plugin active and a real -event — the same throwaway install from 1.2/1.3 is fine. Requirements: - -| | | -|---|---| -| Location | SVN `assets/` (sibling of `trunk/`, **not** shipped to users) | -| Filenames | `screenshot-1.png`, `screenshot-2.png`, … — lowercase, numbered from 1, no gaps | -| Formats | PNG, JPG, or GIF. PNG for UI. | -| Size | No hard limit is enforced. wp.org displays them about 772px wide, so shoot **1544px wide or more** (2× for high-DPI) and keep every shot the same aspect ratio — mismatched ratios make the gallery jump. | - -Shoot the frontend chart on a desktop viewport with real seat colours visible — -that one is the reason someone installs this — and crop out browser chrome and -any test data that reads as placeholder. - -**2. Put the captions back** in `readme.txt`, immediately above `== Changelog ==`. -The list is positional: item *N* captions `screenshot-N.png`, so the order must -match the filenames exactly and a gap silently shifts every caption after it. - -``` -== Screenshots == +## Normal automated release -1. A seating chart on a WordPress page, with live availability. -2. Choosing the event in the block editor. -3. The settings screen, including the optional in-page checkout. -``` +1. Bump the version in **three** places, which must agree or wordpress.org serves + the wrong code: + - the `Version:` header in `seatlayer.php` + - `SEATLAYER_VERSION` in `seatlayer.php` + - `Stable tag:` in `readme.txt` +2. Add a `== Changelog ==` entry in `readme.txt`, and check that `Tested up to:` + names the current WordPress release (https://wordpress.org/download/). +3. Verify locally (next section), then merge the change to `main`. +4. Create and publish a GitHub Release whose tag is that version, in `v0.3.0` + style. +5. GitHub Actions verifies the tag and the focused tests, deploys the release to + the WordPress.org SVN repository, and attaches the installable ZIP to the + GitHub Release. -**3. Commit them to SVN** in the same session as the `trunk/` commit that -contains the matching readme (Part 3.4), so the captions and the files are never -live without each other. +The workflow rejects prereleases and mismatched versions. It needs the repository +secrets `SVN_USERNAME` and `SVN_PASSWORD`. WordPress.org still uses SVN as its +publishing backend; GitHub Actions performs that SVN operation for maintainers. ---- +`Stable tag` is what decides what users download. A tag that does not exist in +SVN, or a `Stable tag` still pointing at the old version, ships the old code to +everyone with no error anywhere. -## Part 1 — verify locally +## Verify locally -### 1.1 Syntax and logic — no WordPress needed +### Syntax and logic (no WordPress needed) ```sh -cd /Users/paiteq/projects/seatlayer-sdks/wordpress - -# Syntax. for f in seatlayer.php uninstall.php includes/*.php; do php -l "$f"; done node --check assets/js/frontend.js node --check assets/js/block.js -# Logic. Both print ALL PASS and exit 0; anything else is a blocker. +# Both print ALL PASS and exit 0. Anything else blocks the release. php tests/settings-logic.php node tests/frontend-options.js ``` -The two harnesses stub only what the code under test actually touches, so what -they exercise is the **real** file rather than a copy that can drift. They are -`export-ignore`d, so they are not in the zip. - -- `tests/settings-logic.php` — the settings class's pure logic: the checkbox - sanitizer (an unchecked box arrives as `null` and must mean OFF), the secret-key - sanitizer (empty submission KEEPS the stored key, `__remove__` clears it, a bad - shape keeps it), the base-URL sanitizer refusing `javascript:`/`data:`/`ftp:`, - and `site_origin()` against the exact shape the server stores embed domains in. -- `tests/frontend-options.js` — runs `frontend.js` against a stub SDK and asserts - what a `SeatPicker` is actually constructed with: `returnUrl` sent **only** - under hosted checkout and carrying path and query verbatim, never sent in - handoff mode, the handoff fallback still wired in both modes, and the - no-SDK-on-page path showing one error without marking the container mounted. - -**What they cannot prove**, and why 1.2 and 1.3 are not optional: nothing here -loads WordPress. Hook registration, block registration, the REST route and its -`edit_posts` gate, `wp_options` round-trips, `uninstall.php` (which only ever -runs inside WordPress's uninstall path), and every rendered admin screen are all -untested until an actual install runs them. - -### 1.2 Plugin Check — the real gate - -This is the tool the wp.org review team runs first, and it is the only check -here that exercises the plugin inside WordPress. It needs a WordPress install; -if you do not have one to hand, `wp-env` gives you a throwaway in one command. +The two harnesses stub only what the code under test touches, so they exercise +the real files. They are `export-ignore`d and are not in the ZIP. + +- `tests/settings-logic.php` covers the settings class's pure logic: the checkbox + sanitizer, the secret-key sanitizer (an empty submission keeps the stored key, + `__remove__` clears it, a bad shape keeps it), the base-URL sanitizer refusing + `javascript:`, `data:` and `ftp:`, and `site_origin()`. +- `tests/frontend-options.js` runs `frontend.js` against a stub SDK and checks + what a `SeatPicker` is constructed with, including when `returnUrl` is sent. + +These harnesses do not load WordPress. Hook and block registration, the REST +route and its `edit_posts` gate, option round-trips, `uninstall.php` and the admin +screens are only exercised by the next two steps. + +### Plugin Check + +Plugin Check is the tool the wordpress.org review team runs first. `wp-env` gives +you a throwaway WordPress in Docker with this plugin mounted: ```sh -# Throwaway WordPress in Docker, this plugin mounted into it. -cd /Users/paiteq/projects/seatlayer-sdks/wordpress npx @wordpress/env start npx @wordpress/env run cli wp plugin install plugin-check --activate -# wp-env mounts the plugin under THIS REPOSITORY'S DIRECTORY NAME (`wordpress`), -# not under the wp.org slug — so confirm what to call it before checking it. +# wp-env mounts the plugin under this repository's directory name, +# not under the wordpress.org slug, so confirm the name first. npx @wordpress/env run cli wp plugin list -npx @wordpress/env run cli wp plugin check wordpress +npx @wordpress/env run cli wp plugin check npx @wordpress/env stop ``` -Plugin Check's "plugin slug does not match text domain" test reads the *folder* -name, so under wp-env it will flag `wordpress` vs `seatlayer-seating-charts`. -That is an artefact of the mount, not a real defect. To see the result the -reviewers will see, check the built zip instead — unzip it into the wp-env -plugins directory under the real slug, or simply confirm by hand that -`Text Domain:` equals the `--prefix` used in Part 3.1. +Under wp-env, the "plugin slug does not match text domain" test compares against +the folder name, so it can flag a mismatch that is only an artefact of the mount. +The real slug and the `Text Domain:` header are both `seatlayer-seating-charts`. Fix every **ERROR**. Read every **WARNING** and either fix it or be able to say -why not — the one already knowingly left is the CDN script's missing version, -which is annotated in `class-seatlayer-render.php`. +why not. The one known warning is the CDN script's missing version, which is +annotated in `includes/class-seatlayer-render.php`. -### 1.3 Try it by hand +### Try it by hand -Code review does not tell you whether a buyer lands on a working page. On that -same throwaway install: +On the same throwaway install: -- Activate, then **Settings → SeatLayer**: save a bad key (expect the shape - warning, and that the previous key survives), then save nothing (expect the - key to survive), then tick Remove. -- Put the block on a page, pick an event, view the page as a logged-out visitor. +- Activate, then open **Settings, SeatLayer**: save a bad key (expect the shape + warning, and the previous key survives), save nothing (the key survives), then + tick Remove. +- Put the block on a page, pick an event, and view the page as a logged-out + visitor. - Select seats and check out against an event with a **test-mode** gateway. -- Tick the Checkout setting and repeat. Confirm the fallback: with an account - that does not have in-page checkout, the buyer must still get the redirect. -- **The return trip, both ways round.** This is the one behaviour that cannot be - proven without a real gateway, so prove it here. With the Checkout setting on - and a **test-mode Stripe** event: - - First declare this site under **Embed domains** in the SeatLayer dashboard, - copying the address the settings screen prints verbatim. Buy a seat. The - buyer must land back on the WordPress page, with `?order=…&status=success` - appended and any query string the page already had still intact. - - Then remove that embed domain and buy again. The buyer must still complete - the purchase and still receive tickets — finishing on SeatLayer's own page. - A failed payment, or an error, means the fallback is broken and is a release - blocker; finishing on SeatLayer's page is the correct degraded behaviour. - - Razorpay is unaffected by either — it never navigates away. Confirm it still - completes in place. -- Delete the plugin from the Plugins screen and confirm the options are gone: - `npx @wordpress/env run cli wp option get seatlayer_secret_key` → error. - ---- - -## Part 2 — GitHub - -The public source repository is -https://github.com/seatlayer/seatlayer-wordpress. `origin` is already configured. -For recovery releases, confirm the intended commit and push it before creating -the version tag. +- Tick the Checkout setting and repeat. With an account that does not have + in-page checkout, the buyer must still get the redirect. +- Check the return trip with the Checkout setting on and a test-mode Stripe event: + - Declare this site under **Embed domains** in the SeatLayer dashboard, copying + the address the settings screen prints. Buy a seat. The buyer lands back on + the WordPress page with `?order=…&status=success` appended and any existing + query string intact. + - Remove that embed domain and buy again. The buyer must still complete the + purchase and receive tickets, finishing on SeatLayer's own page. A failed + payment or an error here blocks the release. + - Razorpay never navigates away. Confirm it still completes in place. +- Delete the plugin from the Plugins screen and confirm its options are gone: + `npx @wordpress/env run cli wp option get seatlayer_secret_key` returns an + error. + +## Manual recovery release + +Use this only if GitHub Actions or the automated deployment is unavailable. Do +not also publish the GitHub Release while following these steps, or the same +version is deployed twice. + +### Tag on GitHub ```sh -cd /Users/paiteq/projects/seatlayer-sdks/wordpress - -# Confirm what you are about to publish. -git log --oneline git status --short # expect: clean - -git push -u origin main - -# Tag the release; replace 0.3.0 with the version being released. -git tag -a v0.3.0 -m "0.3.0" -git push origin v0.3.0 -``` - -Publishing the GitHub Release normally starts the automated WordPress.org -deployment. Do not publish it while following the manual SVN recovery path, -because that would deploy the same version twice. - -```sh -gh release create v0.3.0 \ - --title "0.3.0" \ - --notes "See readme.txt changelog." +git log --oneline -5 # confirm the commit being released +git tag -a "v$VERSION" -m "$VERSION" +git push origin "v$VERSION" ``` ---- - -## Part 3 — wordpress.org - -### 3.1 Build the zip +### Build the ZIP -`.gitattributes` marks the repo-only files `export-ignore`, so `git archive` -produces exactly the tagged commit minus those — no manual deleting, and no risk -of shipping something that was not reviewed. - -**The `--prefix` is the folder name users end up with, so it must be the slug — -and it must equal the `Text Domain:` header.** Both are `seatlayer-seating-charts` -(0.0); if you took the reversal in 0.0, both become `seatlayer`. +`.gitattributes` marks the repository-only files `export-ignore`, so `git archive` +produces exactly the tagged commit minus those files. The `--prefix` is the folder +name users end up with, so it must be the slug, which equals the `Text Domain:` +header. ```sh -cd /Users/paiteq/projects/seatlayer-sdks/wordpress git archive --format=zip \ --prefix=seatlayer-seating-charts/ \ - -o seatlayer-seating-charts-0.2.0.zip v0.2.0 + -o "seatlayer-seating-charts-$VERSION.zip" "v$VERSION" -# Check what is in it before uploading anything. -unzip -l seatlayer-seating-charts-0.2.0.zip +unzip -l "seatlayer-seating-charts-$VERSION.zip" ``` -Expect: `seatlayer.php`, `uninstall.php`, `readme.txt`, `LICENSE`, `includes/`, -`assets/js/`, `assets/css/`. Expect NOT to see `.gitignore`, `.gitattributes`, -`CONTRIBUTING.md`, `RELEASE.md`. - -### 3.2 Submit for review - -Manual, and only once: - -1. Sign in at https://wordpress.org/plugins/developers/ with the account from 0.1. -2. Go to https://wordpress.org/plugins/developers/add/ and upload - `seatlayer-seating-charts-0.2.0.zip`. -3. Wait. Review is a human reading the source, typically days to a few weeks. - They will email the account in 0.1 with anything they want changed; reply to - that thread with a corrected zip rather than resubmitting through the form. +Expect `seatlayer.php`, `uninstall.php`, `readme.txt`, `LICENSE`, `includes/`, +`assets/js/` and `assets/css/`. Expect **not** to see `.gitignore`, +`.gitattributes`, `CONTRIBUTING.md`, `RELEASE.md` or `tests/`. -**Expect them to ask about the CDN script.** Loading executable code from -`cdn.seatlayer.io` is the one thing in this plugin that gets a second look. -The answer is that the renderer is the service's own SDK and the plugin is -useless without it — the same footing as `stripe.js` — and that this is -disclosed in the `== External services ==` section of `readme.txt`, per host, -naming what is sent. Point at that section; do not argue the general principle. - -### 3.3 After approval — SVN - -wp.org gives you an SVN repository, not a Git one. You get the URL by email. +### Commit to SVN ```sh -cd ~ # anywhere outside this Git repo -svn checkout https://plugins.svn.wordpress.org/seatlayer-seating-charts/ seatlayer-svn -cd seatlayer-svn +ZIP="$PWD/seatlayer-seating-charts-$VERSION.zip" +WORK="$(mktemp -d)" + +svn checkout https://plugins.svn.wordpress.org/seatlayer-seating-charts/ "$WORK/svn" +unzip -o "$ZIP" -d "$WORK/unpacked" -# Unpack the same zip's contents into trunk. +cd "$WORK/svn" rm -rf trunk/* -unzip -o /Users/paiteq/projects/seatlayer-sdks/wordpress/seatlayer-seating-charts-0.2.0.zip -d /tmp/sl -cp -R /tmp/sl/seatlayer-seating-charts/. trunk/ +cp -R "$WORK/unpacked/seatlayer-seating-charts/." trunk/ svn add --force trunk svn status # read this before committing -svn commit -m "0.2.0" +svn commit -m "$VERSION" -# Tag it. `Stable tag` in readme.txt points here — this is what users install. -svn copy trunk tags/0.2.0 -svn commit -m "Tag 0.2.0" +# `Stable tag` in readme.txt points here. This is what users install. +svn copy trunk "tags/$VERSION" +svn commit -m "Tag $VERSION" ``` -### 3.4 Listing assets +## Listing assets -Banners, icon, and any screenshots go in `assets/`, which is a sibling of -`trunk/` and is **not** shipped to users. +Banners, the icon and any screenshots live in the SVN `assets/` directory, a +sibling of `trunk/` that is **not** shipped to users: -```sh -cd ~/seatlayer-svn -# assets/banner-1544x500.png, assets/banner-772x250.png -# assets/icon-256x256.png, assets/icon-128x128.png -svn add --force assets -svn commit -m "Listing assets" -``` +- `assets/banner-1544x500.png`, `assets/banner-772x250.png` +- `assets/icon-256x256.png`, `assets/icon-128x128.png` +- `assets/screenshot-1.png`, `assets/screenshot-2.png`, … (optional) The icon is what shows in wp-admin's plugin search, so it is the one asset worth not skipping. -**Screenshots are optional and the readme currently promises none** — see 0.3. If -you are adding them, put the `screenshot-N.png` files here **and** the matching -`== Screenshots ==` captions in `trunk/readme.txt` in the same visit, so the -listing never renders a caption without its image. - ---- - -## Subsequent releases - -1. Land the change on `main`. -2. Bump the version in **three** places, which must agree or wp.org serves the - wrong code: the `Version:` header in `seatlayer.php`, `SEATLAYER_VERSION` in - the same file, and `Stable tag:` in `readme.txt`. -3. Add a `== Changelog ==` entry. -4. Push the tag and publish its GitHub Release. The release workflow performs the - SVN update and attaches the installable ZIP. Use Part 3 manually only to - recover from an automation outage. - -`Stable tag` is what actually decides what users download. A tag that does not -exist in SVN, or a `Stable tag` still pointing at the old version, ships the old -code to everyone with no error anywhere. +`readme.txt` currently lists no screenshots. If you add them, commit the +`screenshot-N.png` files and the matching `== Screenshots ==` captions in +`trunk/readme.txt` in the same visit. The captions are positional (item *N* +captions `screenshot-N.png`), so a gap shifts every caption after it. Shoot at +1544px wide or more and keep one aspect ratio across the set.