Commit graph

1194 commits

Author SHA1 Message Date
SkateSpots
5ac9e67860 fix(cordn): keep the invite poll alive across re-renders 2026-10-27 16:43:49 +07:00
SkateSpots
7a7b1c4950 docs: record Basque tokenizer fix and Cordn UX polish 2026-10-27 16:42:47 +07:00
SkateSpots
4009dd3a72 release: 1.7.3 2026-10-27 16:40:54 +07:00
SkateSpots
7881bbc50f fix(cordn): share public invite URL from the native app 2026-10-27 16:38:33 +07:00
SkateSpots
ac5ac85004 release: 1.7.2 2026-10-27 16:36:29 +07:00
SkateSpots
8a5ac47a19 fix(translation): repair Basque tokenizer IDs and evict stale cache 2026-10-27 16:32:29 +07:00
SkateSpots
690c9c93bd feat(cordn): polish private chat onboarding, polling and invites 2026-10-27 16:22:25 +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
67bc89861d fix(chat): clear translation memory + worker on logout
Translations are memory-only; on logout/account switch we now clear the cache,
reject pending requests and terminate the worker (the model itself is public
and stays in the browser cache).
2026-10-27 10:52:01 +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
b87e81545b fix(ops-report): say 'Monday morning' (the scan timer has jitter) 2026-10-27 09:39:44 +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
118b91188e release: 1.7.1 2026-10-27 09:13:09 +07:00
SkateSpots
5fa0ae2903 fix(release): tolerant Zapstore version match; --force bump with dirty notes 2026-10-27 09:10:48 +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
37fa5c1f58 chore(security): zod 4 + nostrify 0.50 migration; audit 35 -> 13
- zod 3.25 -> 4.6.7 (only AppProvider uses zod; no zod-3-isms, zero TS changes).
- @nostrify/nostrify 0.47.1 -> 0.50.5, @nostrify/react 0.2.17 -> 0.2.31,
  @nostrify/types 0.36.9 (0.50 moved to zod 4; this clears the nostrify chain).
- npm audit fix applied the safe transitive fixes (react-router 6.30.6,
  dompurify 3.4.17, lodash 4.18.1, @modelcontextprotocol/sdk 1.32.2).
Remaining: dev-only vitest/tinypool (major), xlsx (no fix; write-only export),
react-router moderate (needs v7), build tooling (tailwind/postcss/capacitor-cli).
npm test green.
2026-10-26 12:29:55 +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
221ba5c253 chore(deps): curated fixes (vite, vitest, unhead, yaml) + pin TypeScript
Targeted, tests-green update instead of a blanket npm update:
- vite 6.3.5 -> 6.4.5, vitest 3.1.4 -> 3.2.7, @unhead/react 2.1.17 (unhead
  2.1.17), yaml 2.9.1; npm dedupe.
- typescript pinned to 5.8.3 (exact) so npm i can't drift into the 5.9
  typed-array breakage that failed the last auto-update.
OSV findings: 107 -> 59. Remaining are transitive/major (vitest 4, valibot 1,
uuid 11, zod 4) or xlsx (no fix; write-only export, parse CVEs don't apply).
npm test green.
2026-10-26 11:24:36 +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
a18997d348 docs(cordn): mark i18n drift + shareable invite done; note unread needs a signed fetch 2026-10-13 19:19:08 +07:00
SkateSpots
a9669c17b8 feat(crew-chat): shareable 'join this crew's chat' invite
The MLS private-chat panel gets an admin 'Invite' action that shares the crew's
private-chat link (…?tab=mls) via the OS share sheet or clipboard, landing the
invitee on the Ask-to-join flow. Added cordnUi.shareInvite/shareInviteText/
inviteCopied to en/es and backfilled them into all 20 locale drift fragments so
i18n stays at 100%.
2026-10-13 19:18:14 +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