This commit is contained in:
parent
eebc9c06f4
commit
c8428f2f78
9 changed files with 56 additions and 28 deletions
|
|
@ -7,10 +7,10 @@ account is required.
|
||||||
|
|
||||||
- Forge: <https://git.skate-spots.org/skatespots/SkateSpots>
|
- Forge: <https://git.skate-spots.org/skatespots/SkateSpots>
|
||||||
- Latest release: <https://git.skate-spots.org/skatespots/SkateSpots/releases/latest>
|
- Latest release: <https://git.skate-spots.org/skatespots/SkateSpots/releases/latest>
|
||||||
- Signed APK (1.7.87):
|
- Signed APK (1.7.88):
|
||||||
<https://git.skate-spots.org/skatespots/SkateSpots/releases/download/v1.7.87/SkateSpots-1.7.87.apk>
|
<https://git.skate-spots.org/skatespots/SkateSpots/releases/download/v1.7.88/SkateSpots-1.7.88.apk>
|
||||||
- SHA-256:
|
- SHA-256:
|
||||||
`269be76e9e7c67bd0d7167137c9a5767765a46c1f7e6459a126092ad9de5fe7e`
|
`d7e713618c4c57105fc690a42f3c0845536d05491bbf1b5f925d8d7b8d272450`
|
||||||
|
|
||||||
The APK is signed with the same key as the Zapstore build. Installing an
|
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
|
F-Droid-signed build over it would require uninstalling SkateSpots and losing
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# SkateSpots QA matrix
|
# SkateSpots QA matrix
|
||||||
|
|
||||||
Last updated: 2026-11-02
|
Last updated: 2026-11-02
|
||||||
App release under test: **1.7.87** (versionCode 10787)
|
App release under test: **1.7.88** (versionCode 10788)
|
||||||
|
|
||||||
This is a best-effort test record, not a certification. “PASS” means the named
|
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
|
check was actually run in this environment. Physical-device and real-money
|
||||||
|
|
@ -22,11 +22,11 @@ checks are explicitly marked untested until someone runs them.
|
||||||
|
|
||||||
| Channel | Version | Result | Notes |
|
| Channel | Version | Result | Notes |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| Web | 1.7.87 | PASS | Production deploy from `06c1d64` |
|
| Web | 1.7.88 | PASS | Production deploy from `06c1d64` |
|
||||||
| Zapstore | 1.7.87 | PASS | Release event + current icon on relay |
|
| Zapstore | 1.7.88 | PASS | Release event + current icon on relay |
|
||||||
| Forgejo | 1.7.87 | PASS | APK + `.sha256` assets |
|
| Forgejo | 1.7.88 | PASS | APK + `.sha256` assets |
|
||||||
| F-Droid self-hosted repo | 1.7.87 | PASS | `index-v2.json` lists versionCode 10787 |
|
| F-Droid self-hosted repo | 1.7.88 | PASS | `index-v2.json` lists versionCode 10788 |
|
||||||
| Download page | dynamic | PASS | Live `/api/latest-release` returns 1.7.87; static fallback is 1.7.87 |
|
| Download page | dynamic | PASS | Live `/api/latest-release` returns 1.7.88; static fallback is 1.7.88 |
|
||||||
|
|
||||||
## Still requires a real device
|
## Still requires a real device
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||||
<meta name="referrer" content="strict-origin-when-cross-origin" />
|
<meta name="referrer" content="strict-origin-when-cross-origin" />
|
||||||
|
<link rel="preconnect" href="https://a.tile.openstreetmap.org" crossorigin />
|
||||||
|
<link rel="preconnect" href="https://tile.openstreetmap.org" crossorigin />
|
||||||
|
<link rel="dns-prefetch" href="//tile.openstreetmap.org" />
|
||||||
<title>SkateSpots — Open-sourced Skate Community App</title>
|
<title>SkateSpots — Open-sourced Skate Community App</title>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "skate-spots",
|
"name": "skate-spots",
|
||||||
"version": "1.7.87",
|
"version": "1.7.88",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "skate-spots",
|
"name": "skate-spots",
|
||||||
"version": "1.7.87",
|
"version": "1.7.88",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bitcoinerlab/secp256k1": "^1.2.0",
|
"@bitcoinerlab/secp256k1": "^1.2.0",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "skate-spots",
|
"name": "skate-spots",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.7.87",
|
"version": "1.7.88",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"homepage": "https://skate-spots.org",
|
"homepage": "https://skate-spots.org",
|
||||||
|
|
|
||||||
|
|
@ -99,16 +99,16 @@
|
||||||
<a
|
<a
|
||||||
class="download"
|
class="download"
|
||||||
id="download-link"
|
id="download-link"
|
||||||
href="https://git.skate-spots.org/skatespots/SkateSpots/releases/download/v1.7.87/SkateSpots-1.7.87.apk"
|
href="https://git.skate-spots.org/skatespots/SkateSpots/releases/download/v1.7.88/SkateSpots-1.7.88.apk"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
>
|
>
|
||||||
<span id="download-label">Download SkateSpots-1.7.87.apk · 60 MB</span>
|
<span id="download-label">Download SkateSpots-1.7.88.apk · 60 MB</span>
|
||||||
<span aria-hidden="true">↓</span>
|
<span aria-hidden="true">↓</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<span>Requires Android 7.0 or newer.</span>
|
<span>Requires Android 7.0 or newer.</span>
|
||||||
<span>SHA-256: <code id="download-sha256">269be76e9e7c67bd0d7167137c9a5767765a46c1f7e6459a126092ad9de5fe7e</code></span>
|
<span>SHA-256: <code id="download-sha256">d7e713618c4c57105fc690a42f3c0845536d05491bbf1b5f925d8d7b8d272450</code></span>
|
||||||
<span>Only install APKs from a source you trust. Android will ask you to allow installs from your browser.</span>
|
<span>Only install APKs from a source you trust. Android will ask you to allow installs from your browser.</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,11 @@
|
||||||
|
🛹 Faster open + community chat recovery
|
||||||
|
- Shortened the branded splash: reveal is capped at 0.48s and the previous artificial waits are gone.
|
||||||
|
- Added early connections to OpenStreetMap tiles and the SkateSpots relay so the map starts warming sooner.
|
||||||
|
- Cordn join/check/device-link actions now have a short local cooldown, so repeated taps cannot flood the coordinator or Amber and trigger rate limits.
|
||||||
|
- Restarted the community room bot; existing members who are adding a new phone should use "Link this device" from an already-joined device, or ask to join again once.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
🛹 Channel and QA cleanup
|
🛹 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 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.
|
- The self-hosted F-Droid repository is back in sync at 1.7.86.
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,11 @@ import { useTranslation } from '@/contexts/language';
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const SESSION_KEY = 'splash-shown-session';
|
const SESSION_KEY = 'splash-shown-session';
|
||||||
const MIN_MS = 380; // keep the brand beat, lose the artificial wait
|
const MIN_MS = 140; // keep a brand beat, not an artificial wait
|
||||||
const WARM_GRACE_MS = 180; // a short breath after relays connect
|
const WARM_GRACE_MS = 70; // a short breath after relays connect
|
||||||
const MAX_MS = 900; // hard cap — reveal the shell even on a dead network
|
const MAX_MS = 480; // hard cap — reveal the shell even on a dead network
|
||||||
const FADE_MS = 280; // iris-close exit
|
const FADE_MS = 170; // iris-close exit
|
||||||
const READY_HOLD_MS = 120; // show the ready check, then move
|
const READY_HOLD_MS = 0; // reveal immediately once ready
|
||||||
|
|
||||||
const SPARKS = [
|
const SPARKS = [
|
||||||
{ sx: '-8px', sy: '18px', ex: '-54px', ey: '44px', delay: '0ms' },
|
{ sx: '-8px', sy: '18px', ex: '-54px', ey: '44px', delay: '0ms' },
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,18 @@ export function CordnNativePanel({
|
||||||
const fileRef = useRef<HTMLInputElement>(null);
|
const fileRef = useRef<HTMLInputElement>(null);
|
||||||
const checkingRef = useRef(false);
|
const checkingRef = useRef(false);
|
||||||
const loadedRequestsRef = useRef(false);
|
const loadedRequestsRef = useRef(false);
|
||||||
|
// A short local cooldown prevents a manual double-tap (or repeated retries)
|
||||||
|
// from flooding the coordinator/Amber and triggering rate limits.
|
||||||
|
const [cooldownUntil, setCooldownUntil] = useState(0);
|
||||||
|
const [, setCooldownTick] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (cooldownUntil <= Date.now()) return;
|
||||||
|
const id = window.setInterval(() => setCooldownTick(Date.now()), 1000);
|
||||||
|
return () => window.clearInterval(id);
|
||||||
|
}, [cooldownUntil]);
|
||||||
|
|
||||||
|
const coolingDown = cooldownUntil > Date.now();
|
||||||
const { mutateAsync: uploadFile, isPending: uploading } = useUploadFile();
|
const { mutateAsync: uploadFile, isPending: uploading } = useUploadFile();
|
||||||
|
|
||||||
const inviteUrl = useMemo(() => {
|
const inviteUrl = useMemo(() => {
|
||||||
|
|
@ -575,10 +587,12 @@ export function CordnNativePanel({
|
||||||
}, [deviceLinkCode]);
|
}, [deviceLinkCode]);
|
||||||
|
|
||||||
const addScannedDevice = useCallback(async (raw: string) => {
|
const addScannedDevice = useCallback(async (raw: string) => {
|
||||||
|
if (coolingDown) return;
|
||||||
|
setCooldownUntil(Date.now() + 20_000);
|
||||||
setNotice(null);
|
setNotice(null);
|
||||||
const ok = await native.addDevice(raw);
|
const ok = await native.addDevice(raw);
|
||||||
setNotice(ok ? t('cordnUi.deviceLinked') : t('cordnUi.deviceLinkFailed'));
|
setNotice(ok ? t('cordnUi.deviceLinked') : t('cordnUi.deviceLinkFailed'));
|
||||||
}, [native, t]);
|
}, [native, t, coolingDown]);
|
||||||
|
|
||||||
const checkNow = useCallback(async () => {
|
const checkNow = useCallback(async () => {
|
||||||
if (checkingRef.current) return false;
|
if (checkingRef.current) return false;
|
||||||
|
|
@ -591,12 +605,14 @@ export function CordnNativePanel({
|
||||||
}, [checkInviteNative]);
|
}, [checkInviteNative]);
|
||||||
|
|
||||||
const handleRequestJoin = useCallback(async () => {
|
const handleRequestJoin = useCallback(async () => {
|
||||||
|
if (coolingDown) return;
|
||||||
|
setCooldownUntil(Date.now() + 20_000);
|
||||||
setNotice(null);
|
setNotice(null);
|
||||||
const requested = await native.requestJoin();
|
const requested = await native.requestJoin();
|
||||||
if (!requested || !user?.pubkey) return;
|
if (!requested || !user?.pubkey) return;
|
||||||
writePendingJoin(user.pubkey, native.groupId);
|
writePendingJoin(user.pubkey, native.groupId);
|
||||||
setAwaiting(true);
|
setAwaiting(true);
|
||||||
}, [native, user?.pubkey]);
|
}, [native, user?.pubkey, coolingDown]);
|
||||||
|
|
||||||
const handleApprove = useCallback(async (request: { pk: string; kp_ref: string; at: number }) => {
|
const handleApprove = useCallback(async (request: { pk: string; kp_ref: string; at: number }) => {
|
||||||
const ok = await native.approve(
|
const ok = await native.approve(
|
||||||
|
|
@ -611,7 +627,8 @@ export function CordnNativePanel({
|
||||||
}, [native, t]);
|
}, [native, t]);
|
||||||
|
|
||||||
const handleCheckInvite = useCallback(async () => {
|
const handleCheckInvite = useCallback(async () => {
|
||||||
if (native.busy) return;
|
if (native.busy || coolingDown) return;
|
||||||
|
setCooldownUntil(Date.now() + 10_000);
|
||||||
setNotice(null);
|
setNotice(null);
|
||||||
const joined = await checkNow();
|
const joined = await checkNow();
|
||||||
if (joined) {
|
if (joined) {
|
||||||
|
|
@ -620,7 +637,7 @@ export function CordnNativePanel({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!awaiting) setNotice(t(noInviteKey));
|
if (!awaiting) setNotice(t(noInviteKey));
|
||||||
}, [checkNow, awaiting, t, noInviteKey, native.busy, user?.pubkey, native.groupId]);
|
}, [checkNow, awaiting, t, noInviteKey, native.busy, user?.pubkey, native.groupId, coolingDown]);
|
||||||
|
|
||||||
// Keep the latest checker in a ref: the poll must not restart just because a
|
// Keep the latest checker in a ref: the poll must not restart just because a
|
||||||
// parent re-render gave the hook callbacks new identities.
|
// parent re-render gave the hook callbacks new identities.
|
||||||
|
|
@ -939,7 +956,7 @@ export function CordnNativePanel({
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
disabled={!native.ready || native.busy || awaiting}
|
disabled={!native.ready || native.busy || awaiting || coolingDown}
|
||||||
onClick={() => void handleRequestJoin()}
|
onClick={() => void handleRequestJoin()}
|
||||||
className="gap-1.5 bg-teal-500 font-semibold text-black hover:bg-teal-400"
|
className="gap-1.5 bg-teal-500 font-semibold text-black hover:bg-teal-400"
|
||||||
>
|
>
|
||||||
|
|
@ -949,7 +966,7 @@ export function CordnNativePanel({
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
disabled={!native.ready || native.busy}
|
disabled={!native.ready || native.busy || coolingDown}
|
||||||
onClick={() => void handleCheckInvite()}
|
onClick={() => void handleCheckInvite()}
|
||||||
className="gap-1.5"
|
className="gap-1.5"
|
||||||
>
|
>
|
||||||
|
|
@ -960,7 +977,7 @@ export function CordnNativePanel({
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
disabled={!native.ready || native.busy}
|
disabled={!native.ready || native.busy || coolingDown}
|
||||||
onClick={() => void openDeviceLink()}
|
onClick={() => void openDeviceLink()}
|
||||||
className="gap-1.5"
|
className="gap-1.5"
|
||||||
>
|
>
|
||||||
|
|
@ -989,7 +1006,7 @@ export function CordnNativePanel({
|
||||||
{awaiting && !deviceLinkPending && (
|
{awaiting && !deviceLinkPending && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!native.ready || native.busy}
|
disabled={!native.ready || native.busy || coolingDown}
|
||||||
onClick={() => void handleRequestJoin()}
|
onClick={() => void handleRequestJoin()}
|
||||||
className="mx-auto block text-[11px] text-teal-300 underline underline-offset-2 hover:text-teal-200 disabled:opacity-50"
|
className="mx-auto block text-[11px] text-teal-300 underline underline-offset-2 hover:text-teal-200 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue