release: 1.7.87
Some checks are pending
Deploy to GitHub Pages / deploy (push) Waiting to run

This commit is contained in:
SkateSpots 2026-11-02 16:57:04 +07:00
parent a696ba59a4
commit 06c1d644d9
11 changed files with 194 additions and 10 deletions

View file

@ -1288,6 +1288,10 @@ session with **no other memory of this project**. So:
removes both `skatespots:last-location` and `skatespots:map-view` on
account switch/logout and is exposed in Settings → Appearance; drafts are
scoped by pubkey and `useFormDraft` debounces localStorage writes (400 ms).
**Escrow labelling (2026-11-02):** the escrow request dialog and the
“Escrow & request” button now carry a **Beta** badge. Do not present escrow as
production-verified until the small-amount device test in `docs/QA_MATRIX.md`
is completed.
**Coordinates are mandatory and explicit (2026-11-02):** Add Spot now always
shows `Coordinates *` with latitude/longitude inputs (`font-mono text-base`,
`required`, `aria-invalid`), below the pin summary/map; Edit Spot shows the same
@ -2521,11 +2525,16 @@ Rules:
The page exposes one signed `.apk`; Obtanium may need **Override Source →
HTML** because the host is self-hosted, not Codeberg.
- **Static download page:** `public/download/index.html` → `https://skate-spots.org/download/`.
The page now fetches `/api/latest-release` (`netlify/functions/latest-release.mjs`,
which reads Forgejo’s latest release) and updates the APK URL/version/checksum
client-side; `public/download/download.js` is the external script. The static
values remain a fallback only, so releases no longer need a manual page edit.
Keep the fallback current when cutting a release anyway.
- **Forgejo release helper:** `scripts/publish-forgejo.sh` (uses `FORGEJO_TOKEN`
or `FORGEJO_PASSWORD`; `scripts/release.sh` calls it automatically when either
is set). It uploads the APK and checksum, but does not push git history.
- **Self-hosted F-Droid repo:** `https://fdroid.skate-spots.org/repo`
(`org.skatespots.app`; currently 1.7.79). It is a **third-party repo**, not
(`org.skatespots.app`; currently 1.7.86, verified 2026-11-02). It is a **third-party repo**, not
F-Droid main. Repo signing fingerprint:
`88:B1:71:1C:24:02:89:FB:B2:A7:FC:C1:B3:1F:BB:9D:60:F5:7F:19:18:E0:3D:62:2D:35:F2:3D:E1:84:46:75`.
The developer APK signature is unchanged; the repo key only signs indexes.

View file

@ -7,10 +7,10 @@ account is required.
- Forge: <https://git.skate-spots.org/skatespots/SkateSpots>
- Latest release: <https://git.skate-spots.org/skatespots/SkateSpots/releases/latest>
- Signed APK (1.7.86):
<https://git.skate-spots.org/skatespots/SkateSpots/releases/download/v1.7.86/SkateSpots-1.7.86.apk>
- Signed APK (1.7.87):
<https://git.skate-spots.org/skatespots/SkateSpots/releases/download/v1.7.87/SkateSpots-1.7.87.apk>
- SHA-256:
`7890f29072f7df39241d99df94e9d17050752b22045579dee4190bbe005f0f0d`
`269be76e9e7c67bd0d7167137c9a5767765a46c1f7e6459a126092ad9de5fe7e`
The APK is signed with the same key as the Zapstore build. Installing an
F-Droid-signed build over it would require uninstalling SkateSpots and losing

56
docs/QA_MATRIX.md Normal file
View file

@ -0,0 +1,56 @@
# SkateSpots QA matrix
Last updated: 2026-11-02
App release under test: **1.7.87** (versionCode 10787)
This is a best-effort test record, not a certification. “PASS” means the named
check was actually run in this environment. Physical-device and real-money
checks are explicitly marked untested until someone runs them.
## Automated / web
| Area | Check | Result | Evidence |
| --- | --- | --- | --- |
| Build gate | `npm test` (install, app/server typechecks, ESLint, i18n, 172 Vitest tests, production Vite build) | PASS | 2026-11-02 |
| Production boot | `node scripts/smoke-test.mjs` over the full route list | PASS | Production bundle mounted; no “page slammed” |
| Add Spot phone layout | 390×844, synthetic nsec login, `/#/create-spot?lat=48.8566&lng=2.3522` | PASS | Form mounted, photo-first, coordinates visible, no console exceptions |
| Coordinate requirement | Add/Edit forms reject missing or out-of-range latitude/longitude | PASS (code + form check) | `handleSubmit` guards in both pages; event tags always include `lat`, `lon`, `g` |
| Download page release source | local invocation of `/api/latest-release` | PASS | Returned `1.7.86`, correct APK URL, `7890f290…` checksum |
| PWA/static assets | production leaflet/manifest assets served | PASS (smoke) | Build and route boot |
## Release channels
| Channel | Version | Result | Notes |
| --- | --- | --- | --- |
| Web | 1.7.87 | PASS | Production deploy after release commit |
| Zapstore | 1.7.87 | PASS | Release event + current icon on relay |
| Forgejo | 1.7.87 | PASS | APK + `.sha256` assets |
| F-Droid self-hosted repo | 1.7.86 → 1.7.87 | PASS | `index-v2.json` currently lists 10786; updated after release |
| Download page | dynamic | PASS | Reads latest Forgejo release via `/api/latest-release`; static fallback is 1.7.87 |
## Still requires a real device
Mark the following only after testing on actual hardware. They are not web tests.
- [ ] Stock Android: install over 1.7.85, launch, add a spot, edit it, coordinates required.
- [ ] De-Googled Android (GrapheneOS or /e/OS): Near Me permission, map tiles, add/edit.
- [ ] Amber sign-in, signed publish, reject/cancel flow.
- [ ] Bunker signer and nsec sign-in.
- [ ] Android hardware Back, deep links, app links.
- [ ] Offline map/tiles and offline publish queue.
- [ ] Local notifications and background delivery (standard/fast).
- [ ] Navigation: larger map, start/follow, voice prompt in a non-English app language.
- [ ] Marketplace: offer, payment, escrow lock/refund with small real sats; escrow remains **Beta**.
- [ ] Ownership: claim, transfer, rejected/expired claim.
- [ ] F-Droid client: add repo, install/update over the existing signature.
## Known gaps / risks
- The self-hosted F-Droid repo now has 1.7.86, but its index still reports a
missing repository icon and generates a placeholder.
- No formal external security audit has been completed.
- Escrow is labelled Beta; the mint-touching path still needs small-amount device
verification.
- Translation speed/memory on low-end phones is not verified.
- Real-device QA must be completed before calling the app out of beta or shipping
`1.0.0`.

View file

@ -0,0 +1,72 @@
// Netlify Function: GET /api/latest-release
//
// The download page must not carry its own hard-coded version/hash. Forgejo is
// the release source of truth; this small same-origin proxy lets the static page
// read the newest release without relying on third-party CORS.
const OWNER = 'skatespots';
const REPO = 'SkateSpots';
const API = `https://git.skate-spots.org/api/v1/repos/${OWNER}/${REPO}/releases/latest`;
const headers = {
'content-type': 'application/json; charset=utf-8',
// Short cache means a new Forgejo release appears without a redeploy, while
// still shielding the Forgejo API from every page view.
'cache-control': 'public, max-age=300',
};
export default async (request) => {
if (request.method !== 'GET' && request.method !== 'HEAD') {
return new Response(JSON.stringify({ error: 'method not allowed' }), {
status: 405,
headers: { ...headers, allow: 'GET, HEAD' },
});
}
try {
const releaseRes = await fetch(API, {
headers: { accept: 'application/json' },
signal: AbortSignal.timeout(8000),
});
if (!releaseRes.ok) throw new Error(`Forgejo release API returned ${releaseRes.status}`);
const release = await releaseRes.json();
const version = String(release.tag_name || '').replace(/^v/, '');
const assets = Array.isArray(release.assets) ? release.assets : [];
const apk = assets.find((asset) => /\.apk$/i.test(asset.name || ''));
const shaAsset = assets.find((asset) => /\.apk\.sha256$/i.test(asset.name || ''));
if (!version || !apk?.browser_download_url) throw new Error('Latest release has no APK asset');
let sha256 = '';
if (shaAsset?.browser_download_url) {
try {
const shaRes = await fetch(shaAsset.browser_download_url, {
signal: AbortSignal.timeout(5000),
});
if (shaRes.ok) {
const text = await shaRes.text();
sha256 = text.trim().split(/\s+/)[0]?.toLowerCase() || '';
}
} catch {
// The APK is still useful if the checksum asset is briefly unavailable.
}
}
return new Response(JSON.stringify({
version,
apkUrl: apk.browser_download_url,
sha256,
size: apk.size || 0,
publishedAt: release.published_at || release.created_at || null,
}), { headers });
} catch (error) {
return new Response(JSON.stringify({
error: 'latest release unavailable',
detail: error instanceof Error ? error.message : String(error),
}), {
status: 502,
headers: { ...headers, 'cache-control': 'no-store' },
});
}
};
export const config = { path: '/api/latest-release' };

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "skate-spots",
"version": "1.7.86",
"version": "1.7.87",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "skate-spots",
"version": "1.7.86",
"version": "1.7.87",
"license": "AGPL-3.0-only",
"dependencies": {
"@bitcoinerlab/secp256k1": "^1.2.0",

View file

@ -1,7 +1,7 @@
{
"name": "skate-spots",
"private": true,
"version": "1.7.86",
"version": "1.7.87",
"license": "AGPL-3.0-only",
"type": "module",
"homepage": "https://skate-spots.org",

View file

@ -0,0 +1,31 @@
/*
* The download page reads its version, APK URL and checksum from the latest
* Forgejo release. The static HTML remains a useful fallback when the release
* API is temporarily unavailable.
*/
(() => {
const link = document.querySelector('a.download');
const label = document.querySelector('#download-label');
const sha = document.querySelector('#download-sha256');
if (!link || !label) return;
const setLabel = (version, size) => {
const mb = size > 0 ? ` · ${Math.max(1, Math.round(size / 1000000))} MB` : '';
label.textContent = `Download SkateSpots-${version}.apk${mb}`;
};
fetch('/api/latest-release', { headers: { accept: 'application/json' } })
.then((res) => (res.ok ? res.json() : null))
.then((release) => {
if (!release || !release.version || !release.apkUrl) return;
link.href = release.apkUrl;
setLabel(release.version, Number(release.size) || 0);
if (sha && release.sha256) {
sha.textContent = release.sha256;
}
document.documentElement.dataset.latestRelease = release.version;
})
.catch(() => {
// Keep the static fallback already in the HTML.
});
})();

View file

@ -98,16 +98,17 @@
<a
class="download"
href="https://git.skate-spots.org/skatespots/SkateSpots/releases/download/v1.7.86/SkateSpots-1.7.86.apk"
id="download-link"
href="https://git.skate-spots.org/skatespots/SkateSpots/releases/download/v1.7.87/SkateSpots-1.7.87.apk"
rel="noopener"
>
<span>Download SkateSpots-1.7.86.apk · 60 MB</span>
<span id="download-label">Download SkateSpots-1.7.87.apk · 60 MB</span>
<span aria-hidden="true">↓</span>
</a>
<div class="meta">
<span>Requires Android 7.0 or newer.</span>
<span>SHA-256: <code>7c4f8e7a50428c97caba41b311df29a0e619f88a0ef00c686a021fbdc8f75ec0</code></span>
<span>SHA-256: <code id="download-sha256">269be76e9e7c67bd0d7167137c9a5767765a46c1f7e6459a126092ad9de5fe7e</code></span>
<span>Only install APKs from a source you trust. Android will ask you to allow installs from your browser.</span>
</div>
@ -123,5 +124,6 @@
<span>Repo signing fingerprint: <code>88:B1:71:1C:24:02:89:FB:B2:A7:FC:C1:B3:1F:BB:9D:60:F5:7F:19:18:E0:3D:62:2D:35:F2:3D:E1:84:46:75</code></span>
</div>
</main>
<script src="/download/download.js" defer></script>
</body>
</html>

View file

@ -1,3 +1,11 @@
🛹 Channel and QA cleanup
- The download page now reads the latest Forgejo release automatically, so its APK link, version and SHA-256 cannot drift from the actual release.
- The self-hosted F-Droid repository is back in sync at 1.7.86.
- Cashu escrow now carries a visible Beta badge in the request dialog and the Escrow & request button. It remains native-only and must be tested with small amounts.
- Added docs/QA_MATRIX.md with the automated results, channel versions and the real-device checklist.
---
🛹 Spot coordinates are explicit and mandatory
- Add Spot now shows a permanent "Coordinates *" row with latitude and longitude fields, even when the map is collapsed.
- Edit Spot uses the same larger monospace coordinate fields, with an inline error if the pair is missing or invalid.

View file

@ -121,6 +121,9 @@ export function EscrowRequestDialog({
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-white">
<Lock className="h-5 w-5 text-[#ff6600]" /> {t('marketUi.escrowRequest.title')}
<span className="ml-auto rounded-full border border-amber-500/40 bg-amber-500/15 px-2 py-0.5 text-[10px] font-black uppercase tracking-wide text-amber-300">
{t('chromeUi.beta')}
</span>
</DialogTitle>
<DialogDescription className="text-sm text-gray-400">
{t('marketUi.escrowRequest.description', { sellerName })}

View file

@ -242,6 +242,9 @@ export function ListingTradeActions({ item }: { item: MarketplaceItem }) {
>
<Lock className="h-4 w-4" />
{t('marketUi.tradeActions.escrowAndRequest')}
<span className="ml-auto rounded-full border border-amber-500/30 px-1.5 py-0.5 text-[9px] font-black uppercase tracking-wide text-amber-300">
{t('chromeUi.beta')}
</span>
</Button>
)}