- @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.
- 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.
- 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/.
- 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.
- 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).
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).
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).
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).
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).
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).
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).
- 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).
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.
Replace the license file with the unedited AGPL-3.0 text (copied in full
from the teambtcmap/btcmap-android LICENSE, byte-identical). No project
header and no 'or later' grant, so SPDX is AGPL-3.0-only. AGENTS.md records
the rationale and the section-13 source-offer implication.
Add the canonical AGPL-3.0 text at /LICENSE (with the project copyright
notice on top) and set package.json license to AGPL-3.0-or-later. Chosen
because the trust work reviews AGPL reference implementations and we
reimplement rather than copy; AGPL section 13 source-offer is covered by
the public repo + the License page's source link. Recorded in AGENTS.md.
Export ▾ now offers: PDF (full sheet), PDF (client quote — uses an optional
businessName), PNG summary card (canvas), XLSX multi-sheet workbook (lazy
SheetJS), CSV, Markdown, JSON, and Copy summary (clipboard).
Import auto-detects the CSV shape: a `name,order_cost,…` header is treated as a
supplier price list and replaces the Materials table; otherwise the CSV
round-trips the whole sheet. JSON still loads a backup.
The toolbar's primary Export now generates a real PDF (lazy-loaded jspdf) — a
formatted one-page summary: labour, equipment, operations, materials, processes,
packaging, margins and the selling prices, plus the date/currency. Print-to-PDF
was rejected because window.print() doesn't work in the Capacitor WebView.
A secondary JSON export stays (for Import/backup); Import still reads JSON.
jspdf is dynamically imported so it never enters the initial bundle.
Web NFC isn't exposed in Capacitor's WebView, so the APK needs a native writer.
Adds packages/skatespots-nfc — a local Capacitor Android plugin (Kotlin,
enableReaderMode + NDEF URL record), registered BY NAME (not bundled). lib/nfc.ts
now uses the plugin on native and Web NFC on the web. iOS can't write tags.
⚠️ UNVERIFIED (needs cap sync + a device). README documents it.
- First-visit language auto-detect (Spanish devices land in es; stored choice wins).
- Sasquatch AI replies in the app language (skate trick names kept English).
- i18n safety net: scripts/check-i18n.mjs wired into npm test — fails on any t()
key missing from en/es or any en/es key drift.
- ~1,680 new keys (2,297 total) across mapUi / spotUi / marketUi / labUi and
~65 files wired to t(): Map, spots (detail/create/edit/chat), marketplace,
trades, offers, escrow, shipping, The Lab, /become/* forms, coaches, skaters.
- Policy: only skate TRICK names stay English; user-generated content untouched.
packages/skatespots-background: a local Capacitor Android plugin that polls our
own push-service /pending and raises local notifications. Modes: off / standard
(WorkManager ~15 min) / fast (foreground service ~5 min). Kotlin +
AndroidManifest (POST_NOTIFICATIONS, FOREGROUND_SERVICE_DATA_SYNC, WAKE_LOCK,
battery-exemption) + Gradle module. Client: useBackgroundDelivery (registers a
poll token via NIP-98, seeds the plugin) + a Background delivery (Android)
control in NotificationSettingsCard. The app registers by NAME, not by importing
the package, so the web bundle is unaffected. UNVERIFIED - needs cap sync + a
device.
Server: /native/subscribe + /native/unsubscribe (NIP-98 proof), FCM device store,
and FCM HTTP v1 sending (service-account OAuth2, cached ~1h); /health reports
native + device count. 503 without PUSH_FIREBASE_SERVICE_ACCOUNT_JSON.
Client: @capacitor/push-notifications (lazily imported, native-only) in
usePushNotifications - request permission, register, store the FCM token, and
subscribe via /native/subscribe; tap deep-links into the app.
Needs a Firebase project: google-services.json + the Gradle plugin (android/),
and the service-account JSON in the host .env.
Our own keyless Bun service (not Linky's): watches relays for kind-1059 wraps
carrying the plaintext marker ["skatespots","push"] on the recipient copy,
extracts only {recipient, wrapId}, and sends a Web Push. NEVER decrypts - the
client fetches + decrypts the named wrap itself.
Endpoints: GET /health, GET /vapid-public-key, POST /auth/challenge ->
/subscribe|/unsubscribe with a one-time NIP-98 (kind 27235) challenge proof.
State is a JSON file; VAPID keys from env. Wired like the other standalone
servers (typecheck:push + check:push in npm test, deploy:push, Caddyfile +
systemd unit). Verified locally end-to-end: auth -> subscribe -> marked wrap ->
watcher -> push attempt, and proof replay rejected.
Client half (service worker, subscribe hook + Settings toggle, marker on DM
sends) is the next step.
Rebuilds the APK with everything shipped since 1.5.9 (all previously web-only):
destination search + bigger follow map, MintRadar ratings/reliability in a mint
detail sheet, counter-offers + barter with notifications and a Trades bell tab,
the crews type control, one-close spot sheet, and the floating map toolbar.
Rebuilt the APK so the phone gets:
- voice turn prompts (device TTS) + arrival, wake lock, bigger follow map
- floating map controls (map fills the screen)
Web was already deployed; this only bumps the Android version.
Rebuilt the release APK so the phone gets what shipped this session:
- Settings -> Coordinator (pick/add/health, run-your-own hand-off)
- crew form accepts a phone-run coordinator (nprofile/cordn link) + 'Use my coordinator'
- profile discovery: publish your coordinator, one-tap add on profiles
- Edit Profile preview + 'finish your profile' checklist
Web was already deployed; this only bumps the Android version.
An OS alarm an hour before each upcoming event the user RSVP'd going or
interested to. Because the OS holds the alarm it fires with the app fully
closed — the only closed-app notification achievable without a server.
- src/lib/reminders.ts: schedule/cancel via @capacitor/local-notifications,
tracking handed-out ids in localStorage so withdrawn RSVPs and passed events
are cancelled without disturbing one-off notifications. Web is a no-op.
- src/hooks/useLocalReminders.ts: reuses the existing upcoming-events and
my-RSVPs queries; no new relay destination, no signing.
- Bridge mounts the hook only on native and only when the new 'reminders'
pref is on; unmount cancels everything (opt-out/logout).
- Skipped the Pro-expiry reminder on purpose: it would drag the heavy
useSasquatchPro query into a nudge.
Bumps to v1.5.7 (versionCode 10507); APK rebuilt.
Rebuilt the release APK so the phone gets everything currently on main:
opt-in on-device notifications (incl. the @capacitor/local-notifications
native plugin), the batched/smoother map markers, profile badges, the
'closest spot to me' Sasquatch intent, and the 'Keep the map free' support
element. Bumps only package.json (version sync script derives versionCode).
Mirrors the activity the app already computes (bell, crews, DMs, newsletter)
to the OS notification shade, with no new relay traffic and no new network
destination.
- src/lib/osNotify.ts: one delivery API. Web uses the Notification API; native
uses @capacitor/local-notifications (lazily imported, so the web bundle never
pays for it). Tap-through navigates in-app.
- src/components/OsNotificationBridge.tsx: mounted once in Layout, inert until
opted in. Per-source silent priming + a seen-id set means history is never
replayed as a flood, and it stays quiet while the app is visible (the bell
already covers that).
- DMs notify from the gift-wrap ENVELOPE only (src/hooks/useDmEnvelopes.ts,
no decryption) — so Amber is never prompted just to be told you have a
message. Preview is honestly generic.
- src/lib/notificationPrefs.ts + useNotificationPrefs: device-local, opt-in,
default off, per-category toggles. Settings -> Notifications UI.
Deliberately local-only: a fully closed app needs a server push, which is not
built (Phase 2 = on-device background runner; Phase 3 = content-free FCM).
Bump 1.5.4 -> 1.5.5 and give today's ships their own release block: offline map
tiles, photo mirror fallback, the DM 'No messages yet' fix, and free
name@skate-spots.org handles. 1.5.4's notes are restored below.
APK rebuilt: versionName 1.5.5 / versionCode 10505, and its bundle carries the
ai.skate-spots.org base.
The package.json homepage pointed at skate-spots.com — a different domain on a
different host, not this app.
Also adds a deploy guard: auto-deploy.sh now hashes exactly what would be
uploaded (dist/ + netlify/ + netlify.toml) and skips the Netlify deploy when it
is byte-identical to what is already live, so metadata-only commits (version
bumps, homepage) stop costing 15 credits.
Same proxy as netlify/functions/ai.mjs, rebuilt as a standalone Bun service on
the CVM host so AI calls stop spending Netlify compute/bandwidth credits and
the sats-paid Routstr key lives on our box.
- ai-proxy/server.ts: Bun.serve on 127.0.0.1:8787 — NIP-98 + Pro gate, model
allowlist, per-IP throttle, /api/ai + /api/ai/status. Own tsconfig
(typecheck:ai in npm test); eslint-ignored like cvm-server.
- ai-proxy/{package.json,.env.example,skatespots-ai.service,Caddyfile,DEPLOY.md}.
- scripts/deploy-ai-proxy.sh (npm run deploy:ai): scp + bun install + unit +
restart, mirroring deploy-cvm.sh.
- Client: one place decides the AI base, src/lib/sasquatchAi.ts (also the NIP-98
u tag). Defaults unchanged (/api/ai on Netlify); set VITE_SASQUATCH_AI_BASE to
move it. Fully reversible.
Deployed and verified on the host: /api/ai/status returns the live Routstr
balance. Public TLS (DNS + Caddy) is the remaining step before switching the app
over, so no client behaviour changes yet.