Commit graph

393 commits

Author SHA1 Message Date
SkateSpots
d741823039 feat: Android back/app links and web app-shell cache 2026-10-28 16:39:07 +07:00
SkateSpots
21be1ed5d6 docs: record 1.7.16 Zapstore media refresh 2026-10-28 14:25:55 +07:00
SkateSpots
fc64dd8f34 chore: refresh Zapstore screenshots and listing images 2026-10-28 14:17:40 +07:00
SkateSpots
140f11ff48 docs: record 1.7.15 Amber/Zapstore polish 2026-10-28 14:12:29 +07:00
SkateSpots
0c0917b70b docs: record 1.7.14 repo slimming 2026-10-28 13:46:44 +07:00
SkateSpots
580469a7b0 chore: drop unused threaded ONNX WASM and document repo size 2026-10-28 13:38:39 +07:00
SkateSpots
8d4889e2a4 docs: record 1.7.13 image/crew query optimizations 2026-10-28 13:22:31 +07:00
SkateSpots
11c440b5ac docs: record 1.7.12 lazy notification bell 2026-10-28 13:07:24 +07:00
SkateSpots
7040a90630 docs: record 1.7.11 load/replay optimizations 2026-10-28 12:51:21 +07:00
SkateSpots
e94c16a479 perf: batch profiles, gate map layers, warm public cache 2026-10-28 12:42:39 +07:00
SkateSpots
cdeedefb74 docs: record 1.7.10 map footer, Cordn controls/push, matrix 2026-10-28 11:15:21 +07:00
SkateSpots
66fc47cc28 feat: restore map footer, inline crew roles, Cordn reactions/push
- Keep the map footer links permanently visible; remove the hide toggle.
- Publish inline founder promote/demote from the member row.
- Add encrypted MLS reaction/delete/read control messages and UI.
- Add the minute Cordn join watcher: NIP-17 DMs + encrypted bell notices.
- Add the browser translation-matrix runner and a low-end performance finding.
2026-10-28 11:02:47 +07:00
SkateSpots
d230f02328 docs: note the weekly newsletter admin DM 2026-10-28 09:55:34 +07:00
SkateSpots
13f138222d docs: record 1.7.9 newsletter automation 2026-10-28 09:50:36 +07:00
SkateSpots
f029b5a516 feat(newsletter): weekly generated drafts, covers, and admin preview
- Add a browser-safe editorial generator with rotating satire and shipped highlights.
- Generate a fresh 1200x630 cover from the Drop #006 foundation and upload it to Blossom.
- Publish weekly drafts as NIP-78; add the admin Load draft/Preview composer flow and banner.
- Add the Monday user timer (draft by default) and a manual newsletter:weekly script.
- Remove static/live issue duplicates; translate the new admin strings to all 24 locales.
2026-10-28 09:42:44 +07:00
SkateSpots
dfff2a8c40 docs: record 1.7.8 device/member/dependency work 2026-10-28 09:22:05 +07:00
SkateSpots
ec5e63663b docs: record 1.7.7 Cordn unread/pending-join work 2026-10-28 08:45:30 +07:00
SkateSpots
2cec2b4782 docs: record 1.7.6 auth/security audit tail 2026-10-28 00:12:20 +07:00
SkateSpots
84efaba001 docs: record 1.7.5 auth/menu transition fix 2026-10-27 20:54:40 +07:00
SkateSpots
7abeb2d030 docs: record 1.7.4 community room and bot 2026-10-27 17:43:33 +07:00
SkateSpots
2ec861befd feat(community): add the app-wide Cordn room and always-on bot 2026-10-27 17:37:09 +07:00
SkateSpots
27f3434475 feat(cordn): expose and copy the Cordn group share link 2026-10-27 17:02:43 +07:00
SkateSpots
7a7b1c4950 docs: record Basque tokenizer fix and Cordn UX polish 2026-10-27 16:42:47 +07:00
SkateSpots
1fc91d1192 fix(sasquatch): reply in all 24 locales; record translation/Basque state
- Sasquatch UI/guide/FAQ were already 100% in all 24 locales; the AI reply
  language directive only covered es/th/ja. SASQUATCH_LANGUAGES now maps all 24
  app languages (trick names/core vocabulary stay English).
- Document the Sasquatch privacy difference: its AI calls the CVM/AI proxy
  (OpenRouter), unlike the on-device translation.
- Record the Basque status: IR 9->8 and target-side tokenizer fixed; model now
  emits Basque but loops on EOS/generation; remaining gap before 1.7.2.
2026-10-27 16:06:50 +07:00
SkateSpots
e85b9aacb6 feat(chat): all-language on-device translation (M2M100 + Basque Opus)
- M2M100 418M (MIT, 617 MB q8) covers 23 app languages; Basque uses converted
  Opus eu-en/en-eu q8 packs; all self-hosted under relay.skate-spots.org/models.
- Worker uses M2M100 directly and pivots Basque through English; UI targets all
  languages; old pilot restriction/note removed; memory-only + guard unchanged.
- Verified en<->es, ca<->gl, hi<->en from the host. Basque packs are converted
  and hosted; the final MarianTokenizer/ONNX runtime check is still pending.
- 138 tests, 24 locales, build and smoke green. SpotChat deliberately skipped.
2026-10-27 13:06:04 +07:00
SkateSpots
d2a24679f0 feat(chat): on-device translation pilot (English ↔ Spanish)
- @xenova/transformers 2.17.2 in a Web Worker; Opus-MT en↔es ONNX q8 packs
  self-hosted on the relay VPS (/models + CORS); ONNX Runtime 1.14 WASM
  self-hosted in public/ort/ (37MB) loaded from ../ort/.
- Hard network guard: only model GET/HEAD (no query) and /ort/ assets allowed.
  Message text travels only via postMessage; 9 tests prove fetch is never called
  with the text and every other request is blocked.
- TranslateMessage in DMs + crew chat; Settings auto-translate off by default;
  memory-only translations; per-model download progress; 12 UI strings in all
  24 locales.
- Both model directions verified from the hosted files; tsc, 138 tests, build
  and smoke green.
2026-10-27 10:49:54 +07:00
SkateSpots
8c7de4d86d feat(chat): on-device language detector (translation Phase 1)
- tinyld (MIT, lazy, local) + script rules + a weighted
  Catalan/Galician/Basque/Spanish/Portuguese rule for the close/short cases
  tinyld confuses. No network, no logs, no storage; conversation/contact prior
  supported.
- Lingua with 24 languages produced a 99 MB WASM, so it was rejected as a
  detector; tinyld + the gl rule covers all 24 at a fraction of the size.
- 28/28 language samples pass, including a test that fetch is never called.
- Phase 2: on-device Opus-MT (English pivot) in a worker, memory-only
  translations, model packs on Blossom, sentinel/network-guard test.
2026-10-27 10:30:50 +07:00
SkateSpots
bf28519e9e feat(ops): push-marked admin alerts, scan-change DMs, deploy/health alerts
- NIP-17 wraps now carry the app's ['skatespots','push'] marker on every admin
  copy (including the sender), so the push service wakes the phone with the app
  closed. Push test wraps 39fbbd4e…/c88c9f55… verified on the community relay.
- The weekly scan-only job DMs only when the OSV summary changes; unchanged
  scans are logged, not messaged.
- auto-deploy alerts on Netlify failure and on a post-deploy production URL
  health-check failure; deploy-failed/health-failed appear in ops-health and
  the daily report.
2026-10-27 09:52:37 +07:00
SkateSpots
8473aaa994 chore(ops): plain-English daily report + NIP-17 alerts to both admins
- scripts/ops-report.sh builds a simple daily report (app, website, deps,
  security, weekly scan); --send DMs it to the two admins from src/lib/admins.ts
  via NIP-17 (scripts/send-ops-report.ts), sender = app identity.
- Delivery discovers kind-10050 inboxes + NIP-65 write relays and always
  includes the community relay; OPS_DRY_RUN=1 previews without publishing.
- scripts/send-ops-alert.sh sends short alerts; wired into auto-deploy
  (preflight/smoke), update-deps (full+fallback failure) and ops-health --notify.
- skatespots-ops-report.timer daily 07:00 + installer.
- Signer moved into the 0600 zsp.env; old history copies removed.
2026-10-27 09:36:44 +07:00
SkateSpots
39c388a6d7 chore(ops): pre-push/pre-deploy test gate + patch-only deps fallback
- scripts/preflight.sh: app+server tsc, eslint, i18n, vitest; installed as
  .githooks/pre-push (scripts/install-hooks.sh) and run by auto-deploy before
  Netlify. Failures record preflight-failed and ops-health flags them.
- update-deps.sh: if the blanket npm update fails, restore the lock and retry
  patch-level packages one at a time, keeping those that pass a fast gate; the
  final npm test + smoke still gates the result, otherwise revert + 3-day retry.
- release.sh/deps set SKIP_PREFLIGHT=1 (they run the full npm test themselves).
2026-10-27 09:23:07 +07:00
SkateSpots
16ad6ad882 chore(ops): ops-health deps status, release --deploy flag, .nvmrc 2026-10-27 09:17:04 +07:00
SkateSpots
ab3ceb0363 docs(release): record 1.7.1; release.sh reads protected zsp.env by default 2026-10-27 09:15:01 +07:00
SkateSpots
ac5ad39515 chore(ops): 16-day deps gate, weekly CVE scan, overrides, release helper
- Dependency job is now 16 days (service env + script default); a separate
  weekly scan-only timer reports npm/OSV findings without changing the lockfile.
- TimeoutStartSec=30min on the deps job and 20min on the deploy job (systemd's
  90s default could kill a legitimate run); both scripts resolve the newest nvm
  Node instead of hardcoding v22.23.1.
- npm override pins @tanstack/query-core to $@tanstack/react-query so a blanket
  npm update cannot create a second copy (the Oct 26 tsc failure).
- Failed runs keep test/smoke logs + attempted package files in failures/
  (rotated, 8 kept) and a last-failure marker; reports rotate (12 kept).
- scripts/release.sh: notes check -> npm test -> bump -> Android build ->
  zsp publish -> relay verify -> commit/push + tag.
- Deploy/deps systemd units are tracked under scripts/systemd/.
2026-10-27 09:09:45 +07:00
SkateSpots
d53b8cd0cc fix(ui): harden menus for all locales
- LanguageSelector was still rendering the new SVG flag paths as text; use
  <Flag> there too and give every flag a fixed 24px slot (SVG/emoji align).
- Language dropdown w-48 -> w-72 with truncate + hover titles, so
  'Português (Brasil)' / 'Português (Portugal)' no longer clip.
- Settings language grid 1 column on phones / 2 on sm+.
- Header More menu w-48 -> w-64; header nav keeps its scroll, bottom nav keeps
  truncation.
- :lang(hi/th/ja/ko/zh) disables uppercase/tracking-wide so Devanagari, Thai
  and CJK menu labels are not letter-spaced.
- tsc, 101 tests, build and full-route smoke green.
2026-10-26 21:44:16 +07:00
SkateSpots
828684cf15 feat(i18n): add Hindi (24 dictionaries, 25 codes, all 100%)
- Full 8,703-key Hindi dictionary in src/i18n/hi (7 namespace batches),
  placeholders verified, only 2% identical strings (brand/protocol names).
- Register hi in Language/LANGUAGE_LOADERS, detectLanguage (hi-IN),
  LanguageToggle (🇮🇳, हिन्दी) and dateFnsI18n (hi-IN).
- Lazy-load Noto Sans Devanagari for hi via lib/localeFonts.ts (renamed from
  cjkFonts.ts) and add it to the CSS font stack; other locales don't download it.
- check-i18n covers hi: 24 dictionaries / 25 codes at 100%; tsc/tests/build green.
2026-10-26 16:35:53 +07:00
SkateSpots
7e48884eb9 feat(map,i18n): single-copy world clamp + regional flags + pt-PT
- Map: min zoom = smallest integer zoom whose 256*2^z world covers the
  viewport (no grey bands, no duplicated continents). Horizontal wrap is
  restored (worldCopyJump, no longitude maxBounds) so Hawaii/Pacific are
  reachable by dragging; vertical panning stays clamped.
- Flags: real SVGs for Catalonia, Basque Country, Galicia and England
  (no Unicode region flags exist; English is not the UK). Portuguese now has
  Brasil (pt) and Portugal (pt-PT).
- pt-PT shares the pt dictionary with a European drift for the visible chrome
  (Guardar, Palavra-passe, Autocarro, Utilizador, ...).
- check-i18n covers pt-PT: 24 codes at 100%; tsc/tests/build green.
2026-10-26 16:09:21 +07:00
SkateSpots
d4c6261b81 feat(i18n): add Catalan, Basque and Galician (23 locales, all 100%)
- Full dictionaries (8,703 keys each) in src/i18n/{ca,eu,gl}, 7 namespace
  batches, composed with deepMerge; placeholders verified, 2-4% identical
  strings are only brand/protocol names.
- Register in Language/LANGUAGE_LOADERS, detectLanguage (before es),
  LanguageToggle (CA/EU/GL text badges - no Unicode region flags exist) and
  dateFnsI18n (ca-ES/eu-ES/gl-ES).
- check-i18n.mjs now covers ca/eu/gl: all 23 locales at 100%, tsc/tests/build
  green.
2026-10-26 15:56:56 +07:00
SkateSpots
67f21bf1ac release: 1.7.0 (authoritative map, pass-the-app, clearer sign-in, security)
Bump 1.6.8 -> 1.7.0. Notes: canonical map + restored spot; Android share-app
card; 3-card sign-in with per-session desktop welcome; zod 4 + nostrify 0.50 +
react-router 7 + vitest 5 (audit 35 -> 8). APK built + published + verified.
2026-10-26 12:45:10 +07:00
SkateSpots
66e2ac20ac chore(security): react-router 7 + vitest 5; audit 13 -> 8
- react-router-dom/react-router 7.18.4: the app only uses the v6-compatible API
  (Link/NavLink/Routes/HashRouter + hooks), so zero code changes; tsc, tests and
  the full-route boot smoke all pass. Clears the open-redirect advisory.
- vitest 5.0.5 (dev): clears vitest/@vitest/mocker/tinypool criticals. Setup now
  imports @testing-library/jest-dom/vitest for the new expect types.
npm audit: 8 total (0 critical, 1 high = xlsx write-only, 7 moderate = build
tooling).
2026-10-26 12:41:45 +07:00
SkateSpots
93abe599f9 a11y: label the language trigger + settings icon; record security/menu audit
- aria-label on the language dropdown trigger (flag-only) and the header
  settings icon (title-only) so screen readers announce them.
- AGENTS: final security scan (audit 35->13, zod 4/nostrify 0.50, APK manifest
  and CSP verified) + menu/a11y conclusions.
2026-10-26 12:31:47 +07:00
SkateSpots
8badbeacf1 fix(auth): show the desktop welcome sign-in once per session
The dismissal was stored in localStorage, so one early ✕ suppressed the prompt
forever. Switch to sessionStorage (key skatespots:desktop-welcome-snoozed): it
reappears in a new tab/session but respects 'keep browsing' for the current one.
2026-10-26 12:19:52 +07:00
SkateSpots
fe3d9bae22 ui(auth): compact, scroll-safe sign-in dialog
- Fix the real bug: the dialog clipped its bottom links (flex column + max-h
  calc(100dvh-2rem), content min-h-0 flex-1 overflow-y-auto).
- One dominant action per state: 'New here' is a compact row, not a second hero.
- Plain card labels (Browser extension / Signer app / Sign in with your phone)
  with tiny NIP-07/46/55 hints; protocol jargon out of the main labels.
- Bunker: input + Connect with instructions collapsed under 'Where do I get a
  bunker?'; no duplicate heading/divider.
- Secret key moved to a quiet bottom link; subtitle says 'or keep browsing'.
- 8px spacing grid (px-5 gutters, space-y-4 sections, gap-2 cards, p-2.5 cards,
  448px max width). New strings backfilled into all 20 locales.
Verified with headless 1280x900 + 390x844 screenshots.
2026-10-26 12:12:36 +07:00
SkateSpots
f56a8b28e5 chore(deps): surface the OSV total + retry 3 days after a failed run
Notifications now include the OSV summary. A failed run restores the lockfile
and schedules a retry in ~3 days instead of consuming the full 20-day interval.
2026-10-26 12:01:12 +07:00
SkateSpots
f88dfe79ab feat(map): spots and shops come only from the community relay
Owner decision: the map's spots + shops are canonical (relay.skate-spots.org
only), not blended with the viewer's personal relays. useSkateSpots and useShops
now use queryCanonical with a stable 'canonical' query-key suffix. The spot
detail page (useSkateSpot) stays blended so shared links to a spot published only
on a user relay still resolve. Verified with a headless map screenshot showing
canonical clusters.
2026-10-26 12:00:21 +07:00
SkateSpots
6751642eed docs(relay): record max_limit/ip_gate fix + spot parity incident
The anon max_limit (1000) was below the map's 2000-spot query, so the map
appeared empty; raised to 5000. Also raised the per-IP connection gate (429s).
A Chengdu spot had landed only on ditto/damus because the app's publish resolves
on the first relay; republished it to the community relay (signature intact).
Parity: 56 spots on our relay vs 26 on ditto/damus (only junk missing).
2026-10-26 11:50:59 +07:00
SkateSpots
87f58cd84e feat(android): pass-the-app card (QR + share sheet + NFC tag)
About page renders ShareAppCard on Android only: a QR of the Zapstore listing,
a share-sheet button for the install link, and NfcWriteButton to write it to a
tag. Single-sourced APP_INSTALL_URL in lib/platform.ts (also used by the About
download button). No peer-to-peer APK transfer (needs native Wi-Fi Direct/BLE +
PackageInstaller + device QA); Zapstore handles install/update.
2026-10-26 11:41:51 +07:00
SkateSpots
77b3c23633 chore(deps): run npm dedupe in the automated job
A minor bump can nest a duplicate transitive copy and break tsc type identity
(@tanstack/query-core, @nostrify/*). Dedupe after npm update before the test gate.
2026-10-26 11:27:51 +07:00
SkateSpots
f8ec7e35c0 release: 1.6.8 (3-card sign-in, curated deps, AGPL on Zapstore)
- 3-card sign-in chooser + desktop welcome modal (from 2164dd6's LoginDialog
  restyle) ships.
- Curated dependency fixes (vite 6.4.5 / vitest 3.2.7 / unhead 2.1.17 /
  yaml 2.9.1, TS pinned 5.8.3) — OSV 107 -> 59.
- zapstore.yaml license: custom -> AGPL-3.0-only (the listing now shows the real
  licence; verified on the 32267 tag).
Bump 1.6.7 -> 1.6.8, APK built + published + verified.
2026-10-26 11:27:04 +07:00
SkateSpots
2164dd61b0 ui(auth): 3-card sign-in chooser (extension / signer / phone) + lockfile note
LoginDialog leads with a three-card chooser (Browser extension, Signer =
Bunker on desktop / Amber on Android, Sign in with your phone) and a quiet
Secret Key link; the NostrConnect QR moved to its own connect panel. The
desktop welcome modal opens on this. Verified with a headless 1280x900
screenshot. Also records why the full npm update was reverted (duplicate
transitive copies + TS bump) and that the remaining fixes are majors.
2026-10-26 11:12:00 +07:00
SkateSpots
aa2a70e9fd feat: osv-scanner in the deps job + desktop welcome sign-in modal
- update-deps.sh now runs osv-scanner (OSV DB) pre/post update, alongside npm
  audit; it immediately found 107 known vulns (5 critical) npm audit missed
  (vite/vitest dev deps, xlsx with no fix, zod patch).
- Layout opens the themed LoginDialog once for a logged-out desktop visitor
  (non-native, >=1024px, ~900ms), dismissible and remembered; it also offers
  SignupDialog. Reuses the existing extension/bunker/Amber/nsec/NostrConnect
  methods.
2026-10-26 10:59:53 +07:00
SkateSpots
4d9389fa48 release: 1.6.7 (Amber result-compression fix)
Bump 1.6.6 -> 1.6.7. Native Amber intents now send compressionType=none and the JS
signer decodes Signer1 gzip results, so long encrypted payloads no longer fail.
APK built + published to Zapstore (verified on relay.zapstore.dev).
2026-10-14 06:31:06 +07:00
SkateSpots
cc7eb58828 fix(amber): disable result compression (native) + decode Signer1 fallback
The web/PWA Amber path set compressionType=none; the native intents set neither
compressionType nor returnType, and the JS gzip decoder was never called. NIP-55
lets Amber gzip a large result (Signer1... base64), so a long nip44_decrypt (a
long DM) could arrive compressed and be parsed as garbage. Native intents now
send compressionType=none; the JS signer also gunzips any Signer1 result. Timeout
message no longer wrongly says Amber may not be installed.
2026-10-13 21:05:29 +07:00
SkateSpots
704714583f release: 1.6.6 (crew chat simplification, decrypt cache, i18n, invite)
Bump 1.6.5 -> 1.6.6, notes for the always-NIP-17 crew chat, instant sends +
persisted decrypt cache, the 20-locale i18n backfill, and the private-chat invite.
APK built + published to Zapstore (verified on relay.zapstore.dev).
2026-10-13 20:57:15 +07:00
SkateSpots
9a5b706b1a i18n: close the drift — all 20 locales back to 100%
245 keys had accumulated in en/es only (trustUi, labOptions, cordnUi, socialUi,
walletUi, marketTerms, …), leaving every other locale at 97%. Backfilled 245x20 =
4,900 strings into src/i18n/<lang>/drift.ts and merged them through a new shared
src/i18n/deepMerge.ts — a shallow ...drift spread would clobber existing
namespaces, so index.ts deep-merges instead. Verified: check-i18n all 20 at 100%,
tsc + eslint clean.
2026-10-13 19:14:17 +07:00
SkateSpots
10299d07f2 perf(dm): persist the decrypt cache so each message decrypts once, ever
The last Amber pain: a fresh launch re-decrypted the whole inbox (2 signer ops
per wrap). Decrypted messages are now persisted to IndexedDB
(src/lib/dmDecryptCache.ts, keyed viewerPk:wrapId, bounded 4000/account, pruned
oldest-first) and hydrated once per session before the first lookup, so a wrap is
decrypted at most once ever. Cleared per-account on logout (plaintext at rest
otherwise readable by another account on the device).
2026-10-13 18:58:35 +07:00
SkateSpots
707dcaf644 refactor(crew-chat): Chat tab is always NIP-17; widen group read window
Per the crew-chat audit recommendations:
- The crew Chat tab no longer silently switches transport on a coordinator tag.
  It is always NIP-17; coordinator routing lives only in the Private chat (MLS).
  useCordnChat / useCrewChatUnread / lib/cordn client+crypto are kept but unwired
  (headers marked). Drops the tab's Cordn-only unread dot.
- Group reads (Groups list + crew thread) widened 200 -> 500 wraps; the shared
  decrypt cache makes the extra reads affordable. 1:1 list stays 200.
2026-10-13 18:35:03 +07:00
SkateSpots
c7be44dfb3 release: 1.6.5 (crew chat fixes)
Messages now appear on send; shared DM decrypt cache stops per-view re-decrypts;
double-send guarded. Bump 1.6.4 -> 1.6.5, APK built + published to Zapstore
(verified on relay.zapstore.dev).
2026-10-13 00:31:07 +07:00
SkateSpots
8e7c5bfa09 fix(crew-chat): show sent messages, stop re-decrypting the inbox
Audit of the crew Chat tab (see CREW_CHAT_AUDIT.md):
- A sent crew message never appeared: useSendDirectMessage invalidated the list
  keys but not ['group-messages', gid, ...] (the thread key). Now optimistically
  inserts the sender's own plaintext (no refetch -> no Amber pop-ups).
- Every reader decrypted every wrap under its own query key, so each view
  (Messages, then each crew thread) re-decrypted the whole inbox. Added a shared,
  coalesced, bounded session decrypt cache (decryptGiftWrap), successes only.
- Threads dedup by rumor id; CrewChat guards double-send with a ref; the Cordn
  path optimistically inserts and invalidates only for silent signers.
2026-10-13 00:21:42 +07:00
SkateSpots
07d4df037d release: 1.6.4 (crew chat sends with Amber no longer fail)
Fix: sending to a crew sequential-ised its per-recipient signer intents, so Amber
approvals land instead of timing out. Bump 1.6.3 -> 1.6.4, APK built + published
to Zapstore (verified on relay.zapstore.dev).
2026-10-13 00:10:12 +07:00
SkateSpots
27084a4e7b fix(amber): send crew messages one signer intent at a time
Sending to a crew fired every recipient's gift wrap in parallel (Promise.all),
and the sealed-relay crew chat encrypted to the whole roster in parallel. Amber
is a single foreground activity: concurrent nostrsigner: intents mean only one is
answered and the rest sit until the 60s native timeout rejects with 'Amber didn't
respond'. Both paths now go sequentially for an interactive signer (parallel kept
for silent signers). Also corrected the approval hint (2 per member: encrypt +
sign, not 1 per recipient).
2026-10-13 00:06:49 +07:00
SkateSpots
6c10d880c6 release: 1.6.3 (Amber pop-ups, Crews in Messages, crew-chat UX)
Bump 1.6.2 -> 1.6.3, notes for the Amber navigation fix, the Messages Crews
section, and the crew encrypted-chat card rewrite. APK built + published to
Zapstore (verified on relay.zapstore.dev).
2026-10-12 23:48:08 +07:00
SkateSpots
fa0fc1cc15 ux(cordn): reconcile the crew chat card with the MLS tab (+ audit)
The sidebar card badged 'Private chat - On' while the MLS tab said 'not created
yet', and led with external apps. Rewrote CrewCoordinatorCard to show two
explicit rows (Chat = NIP-17 always on; Private chat = MLS with real status) and
make the in-app chat the primary CTA (?tab=mls); demoted Cordn web / Staircase
under 'Open in another app'. Panel now gives join feedback + an admin invite
hint. Adds CORDN_UX_AUDIT.md (findings, terminology model, deferred items).
2026-10-12 23:41:29 +07:00
SkateSpots
4bdb949f06 feat(messages): Crews section in the Messages hub -> each crew's private chat
- DirectMessagesPage gains a 'Crews' block listing the viewer's crews with an
  Open/Set-up badge (from one local IndexedDB read via useCordnGroupGids),
  linking into /crew/<pk>/<id>?tab=mls.
- Crew page tabs are now URL-addressable (?tab=events|chat|mls); an explicit
  param wins over the auto-default. useUserCrews + cordnGroupGids added.
2026-10-12 23:35:35 +07:00
SkateSpots
3ae05a0c38 fix(amber): stop DM/read hooks re-decrypting on every navigation
For an interactive signer (Amber) each nip44 decrypt opens the app. The DM/NIP-17
read hooks decrypt 2x per gift wrap and DirectMessagesPage mounts two of them, so
navigating to Messages stormed Amber. Strengthened amberSafeQueryOptions for
interactive signers (staleTime/gcTime Infinity, refetchOnMount/refetchOnReconnect
false) so decryption happens once per session, mirroring useStash; same guard on
usePrivateContacts + useCordnChat. Added a manual Refresh to the Messages header
(Amber gets no polling). Gated useSyncPendingPosts' legacy re-sign loop for
interactive signers.
2026-10-12 23:28:03 +07:00
SkateSpots
f73190ab81 release: 1.6.2 (onboarding pop-up fixes)
Bump 1.6.1 -> 1.6.2, release notes for the first-run checklist + backup-reminder
fixes. APK built and published to Zapstore (verified on relay.zapstore.dev).
2026-10-12 23:15:11 +07:00
SkateSpots
a2bc42c94b fix(onboarding): stop the first-run checklist + backup reminder nagging returning users
- FirstRunChecklist showed to any logged-in user until manually dismissed; it now
  requires a recent signup_completed stamp AND first-run-seen (auto, once), so
  established/imported accounts never see it.
- SignupDialog wrote the backup-confirmed flag under user?.pubkey while the login
  was still settling, so it landed on 'backup-confirmed:anon' and the post-signup
  BackupReminder nagged every session. Now markBackupConfirmed() is called with the
  pubkey derived from the minted nsec.
- NIP-49 export password field autoComplete new-password -> off (fewer browser
  save-password prompts).
2026-10-12 23:11:42 +07:00
SkateSpots
2d6ee43001 docs: record 1.6.1 Zapstore publish (go zsp, inline signer gotcha) 2026-10-12 23:00:09 +07:00
SkateSpots
0d04445bf3 docs: record zsp non-interactive publish + empty zsp.env gotchas 2026-10-12 22:55:44 +07:00
SkateSpots
bcc3eceeee Cordn native: default crew tab to the private chat + founder nudge; release 1.6.1
- Crew page defaults to the Private-chat (MLS) tab when this device already has
  the group (useCordnGroupExists reads the IndexedDB snapshot — local only, no
  signer prompt); a manual tab click always wins.
- Founder/admin banner ('Set up') points at the tab when the crew has no group.
- Bump 1.6.0 -> 1.6.1 and add release notes for everything since 1.6.0 (private
  crew chat/MLS, self-hosted relay + media, marketplace close-the-deal, trust
  controls, map/weather, Clothing Designer, UI fixes).
2026-10-12 22:52:33 +07:00
SkateSpots
a9a5966714 Cordn native: browser probe, group-ref sharing, image messages
- scripts/cordn-browser-probe.mjs: headless-Chromium proof that the built
  native chunk runs in a real browser — imports the chunk, runs a session
  against the coordinator (publish kp, create group, send), then reopens from
  the Dexie/IndexedDB store. Not part of npm test (needs network + Chromium).
- Group ref: the panel shows/copies the group's cordn1... ref
  (encodeGroupRef from the crew coordinate + coordinator), so it can be opened
  in Staircase/cordn-web. Computed in the hook via the lazy native import.
- Images: attach button uploads to Blossom (useUploadFile) and sends the URL
  inside the encrypted MLS envelope (['image', url]); CordnChatMessage.imageUrl
  + ImageWithFallback rendering. Covered by the live test.
- Unread badge deliberately not built (needs a background signed request).

npm test + smoke + browser probe + 3 live scenarios all pass.
2026-10-12 22:42:44 +07:00
SkateSpots
383fb07127 Cordn native UI: crew MLS chat tab, IndexedDB store, membership flow
Milestone 2 for the native Cordn client.

- Session now runs on the viewer's own signer (NIP-07/Amber/nsec) instead of a
  raw key, so the Cordn identity is the user's pubkey. Added idbStore.ts
  (Dexie 'SkateSpotsCordnDB', one snapshot per account) so MLS state survives a
  reload, plus membership delivery: requestJoin / fetchJoinRequests /
  fetchWelcomes / acceptWelcome / addMemberByIdentifier, and addMember now also
  stores the Welcome on the coordinator for the target.
- useCordnNative owns the session lifecycle and lazy-imports the native module,
  keeping @contextvm/sdk + ts-mls in a separate ~678 kB async chunk (entry
  bundle unchanged at 1.9 MB).
- CordnNativePanel: a third 'Private chat' tab on the crew page — founder/admin
  create the group and approve join requests; members ask to join and check for
  an invite; live message list + composer (polling only for silent signers).
- Strings in i18n/legacy/cordnUi.ts (en/es).

Verified live against our coordinator with a new scenario
(join-request -> approve -> welcome) plus the existing two; npm test + smoke
pass. Not yet exercised in a real browser (browser tooling was offline this
session) — the dynamic-import failure path degrades to an inline error.
2026-10-12 22:30:15 +07:00
SkateSpots
1dcaf8cea3 Cordn native: real MLS client (Level 2 foundation)
Add src/lib/cordn/native/ — a browser-safe Cordn client doing real RFC 9420
MLS over a ContextVM coordinator, mirroring Cordn's wire contract (reimplemented,
not copied from @cordn/cli).

- mls.ts: identity, key packages, group create/add/join, application messages,
  the ChaCha20-Poly1305 coordinator wrapper, causal prev tags.
- coordinator.ts: ContextVM/MCP-over-Nostr client on @contextvm/sdk, validated
  with @cordn/core schemas.
- session.ts: CordnSession (key packages, createGroup, addMember->Welcome,
  joinGroup, send, fetch) with persistence behind an injectable CordnStore
  (MemoryCordnStore today; IndexedDB adapter TODO).
- Verified end-to-end against our own coordinator: a two-member round trip
  (CORDN_LIVE=1 vitest run native.live.test.ts) — create, add, join, and
  bidirectional encrypted messages with proven senders.

Deps: @cordn/core, @contextvm/mcp-sdk, @noble/ciphers pinned 2.2.0 (fixes the
previously invalid tree: ts-mls wants 2.2.0, a stale root copy was 0.5.3).
src/test/setup.ts is now guarded for node-environment test files. Not wired to
any UI yet; nothing imports the module, so the web bundle is unchanged.
2026-10-12 22:14:20 +07:00
SkateSpots
9251aea5b1 feat(cordn): crew Cordn group link-out (Level 1)
Crews can carry a Cordn group ref (['cordn', <cordn1-ref-or-url>] -> SkateCrew.cordnGroup): CrewForm gains a 'Cordn group' field and CrewCoordinatorCard shows an 'Open crew chat' (cordn.net/chat/<ref>) + 'Copy for Staircase' row. Verified a real ContextVM client (@contextvm/sdk) connects + lists tools against our self-hosted coordinator.
2026-10-12 21:46:03 +07:00
SkateSpots
4ff5e25fee chore(deps): scheduled dependency update + vulnerability scan every 20 days
scripts/update-deps.sh (systemd user timer skatespots-deps.timer, daily tick, self-gated to 20 days): scans npm outdated + npm audit, applies non-breaking updates only, gates on npm test + smoke, then commits+pushes on green or restores the lockfile on red. Never --force; skips on a dirty tree; bounds the signed push.
2026-10-12 20:21:04 +07:00
SkateSpots
bb1cedb68d perf: self-host Leaflet CSS+icons (drop unpkg), preconnect relay/blossom
Bundles leaflet CSS + MarkerCluster CSS (main.tsx) and the default marker PNGs (SpotMap) instead of loading them from unpkg.com, so boot makes no third-party request; unpkg.com removed from the CSP (index.html meta + _headers + netlify.toml). Adds dns-prefetch/preconnect for relay.skate-spots.org and blossom.skate-spots.org.
2026-10-12 20:14:34 +07:00
SkateSpots
cc250b4d0b perf(fonts): lazy-load CJK fonts per locale (~16MB font payload off the boot path)
Kanit (th), Noto Sans JP (ja) and Noto Sans KR (ko) were imported statically in main.tsx, so every visitor downloaded ~16MB of CJK font files. They're now loaded on demand once per locale (lib/cjkFonts.ts, wired in LanguageContext), so non-CJK users never fetch them and CJK users get their font after the first paint.
2026-10-12 19:52:54 +07:00
SkateSpots
c95dd54a7a fix(coordinator): status badge shows labels (missing socialUi prefix) + 15min announce
STATUS_META labels were 'coordinator.*' but keys live under 'socialUi.coordinator.*', so badges rendered raw keys (e.g. coordinator.statusStale). Prefixed all six. Also dropped relay announce_interval_secs from 86400 (24h) to 900 (15m) so the coordinator health probe reads Connected instead of stale.
2026-10-12 17:36:27 +07:00
SkateSpots
3d9234d65c feat(cordn): web + Staircase MLS entry points on the crew coordinator card
Relabel the cordn.net add-link accurately as 'Cordn (web)' (real MLS in the browser) and add a 'Copy for Staircase' button that copies the coordinator nprofile for the native app. Our coordinator by default; a BYO coordinator's add-link when it has a pubkey.
2026-10-12 17:23:32 +07:00
SkateSpots
e262aed938 feat(cordn): 'Open in Staircase' link on the crew coordinator card
CrewCoordinatorCard now offers a StaircaseLink in both branches: our coordinator (SKATESPOTS_COORDINATOR add-link) when a crew has none, and the crew's own coordinator add-link when it has a pubkey (falls back to the Cordn home otherwise). Lets crews jump to real MLS chat in the native Cordn client.
2026-10-12 17:04:34 +07:00
SkateSpots
a8e411faa0 feat(cordn): embedded coordinator on the relay, crews default to it, disclosure
Newlay runs its embedded Cordn coordinator (identity cordn.key, uid 10001). SKATESPOTS_COORDINATOR is the provided default: Settings -> Coordinator shows a 'Use SkateSpots coordinator' block and CrewForm prefills it for new crews; BYO coordinator stays client-side. Disclosure copy (coordinators see ciphertext + metadata, never contents) in socialUi.coordinator.ourDefault* + existing privacyNote.
2026-10-12 16:36:56 +07:00
SkateSpots
ca810326f3 fix(spot-card): make the whole card clickable again
The stretched Link was z-0 behind the card content, and backdrop-blur made the card its own paint layer above it, so no click ever reached the link (the card looked dead in the map list view). Link -> z-10, drop backdrop-blur, move hover to the wrapper (group + group-hover:), and raise the action row to z-20 so its buttons stay above the link. Verified headlessly: card click navigates, kebab opens its menu without navigating.
2026-10-12 14:03:16 +07:00
SkateSpots
54882285b6 feat(weather): 6-day forecast
FORECAST_DAYS=6 in lib/weather.ts is the single source for the fetch length and the UI labels (spot card + map popup). New count-interpolated labels spotUi.forecastDays / mapUi.forecastDays (en/es; other locales fall back to English). Legend colours moved to lib/skateabilityLevels.ts to keep the react-refresh lint at zero.
2026-10-12 13:51:29 +07:00
SkateSpots
e47c4b9d81 feat(weather): legend, refresh/error states, sunrise/sunset, reason chips
- WeatherLegend overlay on the map (only while the weather layer is on), colours mirroring calculateSkateability
- Spot weather card: freshness line + manual refresh, and an error/retry state when Open-Meteo is unreachable
- Sunrise/sunset row (weather.ts now fetches Open-Meteo daily sunrise,sunset)
- Score reasons render as chips, not a bullet-joined run-on
- Map popup muted text gray-500 -> gray-300 for contrast
Units stay Celsius/km/h (deliberate).
2026-10-12 13:44:19 +07:00
SkateSpots
84c0ba7b3c feat(relays): ensure the community relay is in the NIP-65 list (+ silent republish)
lib/relays gains withCommunityRelay/ensureCommunityRelay; AppProvider and NostrSync add wss://relay.skate-spots.org to a stored list that lacks it (unless useSkateRelay is off). NostrSync republishes kind-10002 once for non-Amber signers so other clients pick up the community relay; Amber users keep it local to avoid an unprompted popup.
2026-10-12 13:29:04 +07:00
SkateSpots
37cc0d5d68 fix(relays): prune retired/dead relays from stored lists (runtime + load + sync)
Adds RETIRED_RELAY_HOSTS (goskatespots.nostr1.com, wotr.relatr.xyz) with isRetiredRelay/pruneRetiredRelays. Applied in resolveRelays (runtime), AppProvider (one-time cleanup on load) and NostrSync (when applying a fetched NIP-65), so existing users stop showing/using the retired relay without manual edits.
2026-10-12 13:20:09 +07:00
SkateSpots
7611f3d266 feat(media): self-host Blossom at blossom.skate-spots.org (primary upload target)
Newlay's Blossom enabled on the relay box (blobs inside the data volume); Caddy + DNS + TLS wired. The app now uploads there first, keeping blossom.primal.net and blossom.band as mirrors, and includes it in BLOSSOM_MIRROR_HOSTS for read fallback.
2026-10-12 13:12:14 +07:00
SkateSpots
6efd6b8deb feat(relay): retire the legacy hosted relay from the app + servers
Verified the legacy relay held the same 115 events as the new one (0 diff) before removing it, so no data is lost. Drops wss://goskatespots.nostr1.com from the default pools, the DM/CVM/zap relay arrays, and the cvm-server + push-service defaults.
2026-10-12 13:06:02 +07:00
SkateSpots
36d87c273f chore(backup): archive via SSH tunnel to the relay host (DNS-independent)
backup-relay.sh now tunnels to the relay host's 127.0.0.1:7777 (ssh alias skatespots-cvm, override SKATESPOTS_RELAY_SSH) and falls back to the public URL, so the weekly archive no longer depends on public DNS. Verified: 115 events archived + restic push.
2026-10-12 12:59:39 +07:00
SkateSpots
909ae8a713 chore(relay): host health check + self-heal + ntfy alerts
scripts/host/relay-health.sh (run by skatespots-health.timer every 5 min on the relay box): checks relay/ai/push/cvm/caddy + public endpoints + disk/mem, restarts the relay when its localhost NIP-11 dies, and alerts via ntfy.sh on state changes.
2026-10-12 12:54:08 +07:00
SkateSpots
fc050691cb chore(relay): configure NIP-86 admins + systemd unit for Newlay
Document the two admin pubkeys' NIP-86 tier/assignments (verified admin 200 / non-admin 403 / no-auth 401) and add scripts/systemd/skatespots-relay.service for the host compose wrapper.
2026-10-12 12:46:35 +07:00
SkateSpots
6407310213 feat(relay): cut over community relay to self-hosted Newlay
Point SKATE_RELAY, the default relay pools, the DM/CVM/zap relay arrays, the cvm-server + push-service defaults, and the archive default at wss://relay.skate-spots.org, keeping the legacy hosted relay as a fallback during the client-update window. Restored 115/115 archived events; the relay runs behind Caddy with TLS. Docs + AGENTS.md updated.
2026-10-12 12:38:13 +07:00
SkateSpots
4b92759913 fix(marketplace): close P0 trade/offer bugs + deep flow audit
Deep audit (MARKETPLACE_FLOW_AUDIT.md): full trade/escrow/offers lifecycle,
severity-ranked findings, and a state-coverage matrix.

Fixes:
- Grid Mark as sold now opens MarkSoldDialog, so the kind-31871 attestation
  (buyer, method) is recorded from the grid too, not just the detail page.
- Offers accept attaches a payment pointer only when the accepting user is the
  listing owner (seller); previously the buyer could publish their own lud16 as
  the payee and misdirect payment.
- advertiseEscrowKey refuses to publish kind 0 before the profile loads, so it
  cannot overwrite name/picture/lud16 with a cashu_escrow-only profile.
- useListingOffers passes requireAny, so the offers panel error state actually
  fires on an outage (previously an outage looked like no offers).
- Offer messages invalidate the offer queries, so accept/counter/decline update
  immediately instead of after the stale window.
2026-10-09 21:35:06 +07:00
SkateSpots
88a2a2ca15 docs: add UX/UI audit with a marketplace-flow focus
Full app audit (UX_AUDIT_2026-10-09.md), heavy on the marketplace flow
sequence. Headline verified findings: useMarketplaceItem does not filter
admin-hidden or exclude showcase-only listings (a hidden/not-for-sale
listing is reachable by URL); the buyer CTAs compete on fixed-price
listings and Make an offer is shown to logged-out users; the grid's Mark
as sold skips MarkSoldDialog so no trade attestation is recorded; the hub
tabs are not URL-addressable; nav.marketplace and nav.trades are
duplicate labels; and the nav customizer Reset hardcodes 3 tabs against
MIDDLE_SLOTS 4. Includes a recommended fix order. Audit only, no code
changes.
2026-10-09 19:11:05 +07:00
SkateSpots
dde52eb1a7 docs: record the own-scorer decision as deferred for later revision
Capture the recommendation in AGENTS.md (and mirror it in TRUST_SCORE.md):
do not build the cvm-server per-observer GrapeRank scorer yet. The full
confidence model is provider-side and consumed via NIP-85; the client has
a local confidence gate; the blockers are the per-observer signing key and
8-hop compute plus lifecycle/deploy/monitoring, with ~zero users making
the ROI negative. A house-perspective scorer is the cheaper path; revisit
when there are users, no trustworthy provider, or skate-specific signals
to bake in.
2026-10-09 18:57:41 +07:00
SkateSpots
db7f4a2707 feat(trust): confidence gate on the local ladder
A lone friend-of-a-friend no longer reads as trust. useViewerGraph now
counts distinct follows per friend-of-a-friend (fofCounts), and
deriveLocalScore returns verified only at FOF_MIN_RATERS (2) or more.
Provider assertions are unaffected; the full GrapeRank confidence model
stays provider-side. Docs updated.
2026-10-09 18:36:50 +07:00
SkateSpots
18a9e28f0b feat(trust): network alerts for reported accounts you follow
Fifth slice of TRUST_SCORE.md. useNetworkAlerts reads the viewer's NIP-85
assertions for the accounts they follow and surfaces those trusted people
reported or muted. New /alerts page lists them with Ignore / Unfollow /
Mute / Report (reusing useFollowActions, useMuteUser, ReportDialog) —
inform, never auto-act. Settings -> Trust links to it with a live count;
ignored accounts are device-local. /alerts added to the smoke list.
2026-10-09 18:32:47 +07:00
SkateSpots
e66888128f feat(trust): a real mute (NIP-51 kind 10000) that hides content
Completes the safety loop. useMuteList reads the viewer's kind-10000 mute
list plus optimistic local mutes (lib/localMutes.ts); useMuteUser
republishes the list with the target added, records it locally and
invalidates content queries. useAdminModeration carries a new
mutedPubkeys set, and the shared filterAdminHidden/filterAdminHiddenMixed
drop muted authors — so every list hook filters them with no per-hook
changes (admin tooling with includeHidden still sees them). The flagged
notice gains a Mute action.
2026-10-09 18:23:04 +07:00
SkateSpots
0f8c77908b perf(trust): compute the local WoT graph once per viewer
TrustBadge called useTrustScore per card, which fetched the viewer's
follow list plus up to 100 friends' follow lists for EVERY card. The
graph does not depend on the target, so useViewerGraph computes it once
per viewer (one shared React Query) and deriveLocalScore returns the same
100/90/60/0 ladder (the NIP-05 30 tier classified as unknown anyway).
TrustBadge now costs no per-card network work on top of the batched
assertion read. Behaviour preserved; logged-out users still use the house
graph. Also records the ANALYZE=1 bundle findings in AGENTS.md.
2026-10-09 18:04:16 +07:00
SkateSpots
3b06e90d15 feat(trust): let the viewer choose the trust provider (NIP-85 kind 10040)
Settings -> Trust is now functional, not read-only. useSetTrustProvider
publishes a kind-10040 designation for a custom provider (npub or 64-hex +
wss relay, the five 30382:<type> rows) and can clear it to fall back to the
community provider. The card validates input, prompts the signer, and
invalidates the provider + assertion queries so scores refresh. Docs
updated.
2026-10-09 17:55:00 +07:00
SkateSpots
fd9316331b feat(trust): Settings -> Trust and name AGPL on the License page
- Settings -> Trust section (?section=trust, TrustSettingsCard): a
  show/hide-marks toggle (useTrustDisplay; Flagged is never hidden) and the
  active provider + relay (useTrustProvider), linked to /trust.
- TrustBadge respects the display preference.
- Name AGPL-3.0 on the License page by updating its existing copy
  (licenseLabel + body) in en/es.
- Settings + card i18n keys; /settings?section=trust added to the smoke
  list. Docs updated.
2026-10-09 17:45:56 +07:00