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>
|
||||
- Latest release: <https://git.skate-spots.org/skatespots/SkateSpots/releases/latest>
|
||||
- Signed APK (1.7.87):
|
||||
<https://git.skate-spots.org/skatespots/SkateSpots/releases/download/v1.7.87/SkateSpots-1.7.87.apk>
|
||||
- Signed APK (1.7.88):
|
||||
<https://git.skate-spots.org/skatespots/SkateSpots/releases/download/v1.7.88/SkateSpots-1.7.88.apk>
|
||||
- SHA-256:
|
||||
`269be76e9e7c67bd0d7167137c9a5767765a46c1f7e6459a126092ad9de5fe7e`
|
||||
`d7e713618c4c57105fc690a42f3c0845536d05491bbf1b5f925d8d7b8d272450`
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# SkateSpots QA matrix
|
||||
|
||||
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
|
||||
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 |
|
||||
| --- | --- | --- | --- |
|
||||
| Web | 1.7.87 | PASS | Production deploy from `06c1d64` |
|
||||
| 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.87 | PASS | `index-v2.json` lists versionCode 10787 |
|
||||
| Download page | dynamic | PASS | Live `/api/latest-release` returns 1.7.87; static fallback is 1.7.87 |
|
||||
| Web | 1.7.88 | PASS | Production deploy from `06c1d64` |
|
||||
| Zapstore | 1.7.88 | PASS | Release event + current icon on relay |
|
||||
| Forgejo | 1.7.88 | PASS | APK + `.sha256` assets |
|
||||
| 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.88; static fallback is 1.7.88 |
|
||||
|
||||
## Still requires a real device
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<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>
|
||||
|
||||
<!--
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "skate-spots",
|
||||
"version": "1.7.87",
|
||||
"version": "1.7.88",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "skate-spots",
|
||||
"version": "1.7.87",
|
||||
"version": "1.7.88",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@bitcoinerlab/secp256k1": "^1.2.0",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "skate-spots",
|
||||
"private": true,
|
||||
"version": "1.7.87",
|
||||
"version": "1.7.88",
|
||||
"license": "AGPL-3.0-only",
|
||||
"type": "module",
|
||||
"homepage": "https://skate-spots.org",
|
||||
|
|
|
|||
|
|
@ -99,16 +99,16 @@
|
|||
<a
|
||||
class="download"
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
</a>
|
||||
|
||||
<div class="meta">
|
||||
<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>
|
||||
</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
|
||||
- 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.
|
||||
|
|
|
|||
|
|
@ -23,11 +23,11 @@ import { useTranslation } from '@/contexts/language';
|
|||
*/
|
||||
|
||||
const SESSION_KEY = 'splash-shown-session';
|
||||
const MIN_MS = 380; // keep the brand beat, lose the artificial wait
|
||||
const WARM_GRACE_MS = 180; // a short breath after relays connect
|
||||
const MAX_MS = 900; // hard cap — reveal the shell even on a dead network
|
||||
const FADE_MS = 280; // iris-close exit
|
||||
const READY_HOLD_MS = 120; // show the ready check, then move
|
||||
const MIN_MS = 140; // keep a brand beat, not an artificial wait
|
||||
const WARM_GRACE_MS = 70; // a short breath after relays connect
|
||||
const MAX_MS = 480; // hard cap — reveal the shell even on a dead network
|
||||
const FADE_MS = 170; // iris-close exit
|
||||
const READY_HOLD_MS = 0; // reveal immediately once ready
|
||||
|
||||
const SPARKS = [
|
||||
{ sx: '-8px', sy: '18px', ex: '-54px', ey: '44px', delay: '0ms' },
|
||||
|
|
|
|||
|
|
@ -212,6 +212,18 @@ export function CordnNativePanel({
|
|||
const fileRef = useRef<HTMLInputElement>(null);
|
||||
const checkingRef = 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 inviteUrl = useMemo(() => {
|
||||
|
|
@ -575,10 +587,12 @@ export function CordnNativePanel({
|
|||
}, [deviceLinkCode]);
|
||||
|
||||
const addScannedDevice = useCallback(async (raw: string) => {
|
||||
if (coolingDown) return;
|
||||
setCooldownUntil(Date.now() + 20_000);
|
||||
setNotice(null);
|
||||
const ok = await native.addDevice(raw);
|
||||
setNotice(ok ? t('cordnUi.deviceLinked') : t('cordnUi.deviceLinkFailed'));
|
||||
}, [native, t]);
|
||||
}, [native, t, coolingDown]);
|
||||
|
||||
const checkNow = useCallback(async () => {
|
||||
if (checkingRef.current) return false;
|
||||
|
|
@ -591,12 +605,14 @@ export function CordnNativePanel({
|
|||
}, [checkInviteNative]);
|
||||
|
||||
const handleRequestJoin = useCallback(async () => {
|
||||
if (coolingDown) return;
|
||||
setCooldownUntil(Date.now() + 20_000);
|
||||
setNotice(null);
|
||||
const requested = await native.requestJoin();
|
||||
if (!requested || !user?.pubkey) return;
|
||||
writePendingJoin(user.pubkey, native.groupId);
|
||||
setAwaiting(true);
|
||||
}, [native, user?.pubkey]);
|
||||
}, [native, user?.pubkey, coolingDown]);
|
||||
|
||||
const handleApprove = useCallback(async (request: { pk: string; kp_ref: string; at: number }) => {
|
||||
const ok = await native.approve(
|
||||
|
|
@ -611,7 +627,8 @@ export function CordnNativePanel({
|
|||
}, [native, t]);
|
||||
|
||||
const handleCheckInvite = useCallback(async () => {
|
||||
if (native.busy) return;
|
||||
if (native.busy || coolingDown) return;
|
||||
setCooldownUntil(Date.now() + 10_000);
|
||||
setNotice(null);
|
||||
const joined = await checkNow();
|
||||
if (joined) {
|
||||
|
|
@ -620,7 +637,7 @@ export function CordnNativePanel({
|
|||
return;
|
||||
}
|
||||
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
|
||||
// parent re-render gave the hook callbacks new identities.
|
||||
|
|
@ -939,7 +956,7 @@ export function CordnNativePanel({
|
|||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
disabled={!native.ready || native.busy || awaiting}
|
||||
disabled={!native.ready || native.busy || awaiting || coolingDown}
|
||||
onClick={() => void handleRequestJoin()}
|
||||
className="gap-1.5 bg-teal-500 font-semibold text-black hover:bg-teal-400"
|
||||
>
|
||||
|
|
@ -949,7 +966,7 @@ export function CordnNativePanel({
|
|||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={!native.ready || native.busy}
|
||||
disabled={!native.ready || native.busy || coolingDown}
|
||||
onClick={() => void handleCheckInvite()}
|
||||
className="gap-1.5"
|
||||
>
|
||||
|
|
@ -960,7 +977,7 @@ export function CordnNativePanel({
|
|||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={!native.ready || native.busy}
|
||||
disabled={!native.ready || native.busy || coolingDown}
|
||||
onClick={() => void openDeviceLink()}
|
||||
className="gap-1.5"
|
||||
>
|
||||
|
|
@ -989,7 +1006,7 @@ export function CordnNativePanel({
|
|||
{awaiting && !deviceLinkPending && (
|
||||
<button
|
||||
type="button"
|
||||
disabled={!native.ready || native.busy}
|
||||
disabled={!native.ready || native.busy || coolingDown}
|
||||
onClick={() => void handleRequestJoin()}
|
||||
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