- 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.
- 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.
- 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.
- 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.
- 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).
- 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.
- 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.
- 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.
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.
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.
- 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.
- 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.
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.
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.
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.
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.
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.
New data-driven Lab craft, mirroring the artist role: three skill
sections (What You Make [required] / How You Make It / Materials) plus
minimum order, turnaround, size range, samples and customisation extras,
inheriting the shared Lab extras (rate, payment, gallery, reel,
languages, availability, booking).
Wired through types.ts (kind, label/description maps, parse, collective-
capable), labelKeys, useLabProfiles (kind list + dispatch), becomeRoles
(role card), craftFields, LabPage (tab, filters, offer button, empty
state, counts, deep links), LabCrewsStrip, NIP.md, and en/es i18n
(labOptions, becomeRolesUi, labUi). /become/clothing added to the smoke
route list. tsc, eslint, check-i18n, vitest 101/101, build and smoke all
pass.
The archive kind list had drifted: it omitted four app kinds that shipped
after it was written — 32271 rider cards, 35679 park access passes, 34967
shipment status, 1293 witnessed skill badges. Without them a restore to a
new relay would have dropped that content. Verified the archive/restore
loop end-to-end: 113 events archived, restored into a throwaway relay,
113 read back, same kinds and authors, 0 failures.
P0 fixes from UX_AUDIT_2026-10-09.md:
- useMarketplaceItem now filters admin-hidden listings and skips
showcase-only (not-for-sale) items, so a direct /listing/... URL cannot
surface removed or non-sale content.
- Listing detail hides Make an offer for logged-out users and for
fixed-price listings (Buy is the single primary CTA there).
Borrowed from the Plebeian market review: a /marketplace-guidelines page
(peer-to-peer, buyer-beware, no checkout, prohibited items, soft
moderation), linked from the marketplace hub; new marketTerms en/es keys.
Records the remaining borrowables in the audit doc.
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.
- 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.
Legibility pass: a trust verdict has to be explainable and sourced.
- TrustBadge tooltips now attribute the claim (from your network / the
community provider / the provider you chose) and link to /trust.
- New /trust page: the three states, why absence is not distrust, who
computes it, presence-is-not-endorsement, and point-of-view. Public,
linked from the badge; added to the smoke-test route list.
- New trustUi.* keys and four walletUi.trust attribution keys (en/es).
New /diy/deck-designer tool. src/lib/deckDesign.ts holds the pure geometry
(outline, waist, rounded ends, kicks, concave) and the exporters; the component
renders a live top view, side profile and concave section with presets, saved
locally.
Exports: true-size SVG, a tiled 1:1 PDF (A4/Letter, split across sheets with
corner registration crosses), and an R12 DXF (outline polyline + 3/16" truck
holes + centreline, $INSUNITS=4). Truck patterns: New school 53.975x41.275 mm
and Old school 63.5x41.275 mm.
Deliberately 2D: this is the outline a builder traces and cuts, not a SK8CAD
clone (SK8CAD is proprietary; only its JSCAD engine is MIT, and we copied
nothing).
- Removed the sticky category-chip row from /diy (sections still group the cards).
- New in-app tool at /diy/deck-cost (components/DeckCostCalculator.tsx): a
self-contained pricing model — labour rate + overhead, equipment amortisation,
operation expenses, materials (with order/unload time), process time,
packaging, profit margin and scrap → cost per board and a fair selling price
(no-packaging / with-packaging). State persists to localStorage; reset to the
worked example. The "Deck cost & pricing" card now links to it instead of
showing "Coming soon".
- Smoke test walks /diy/deck-cost.
New /diy hub: curated resources for making your own gear, with cross-links into
the rest of the app.
- src/lib/diyGuides.ts: seed data — categories (Boards, Ramps & wooden
obstacles, Concrete & DIY spots, Repairs & tuning, Tools & plans), resource
cards (Open Source Skateboards / SK8CAD / Instructables / YouTube), and an
actions block (find a Builder, find a DIY crew, map a DIY spot, sell builds,
find a coach).
- src/pages/DiyPage.tsx renders it (category chips + card grid + actions);
external links go through sanitizeUrl and credit the source.
- Route /diy (lazyWithRetry) + a nav-catalog entry (id diy) so it appears in the
desktop "More" menu and the mobile burger; also linked from the site footer
and the map links strip. Smoke test walks /diy.
Addressable events (30818/30017) are author-signed, so "transferring" them
means re-signing each under the destination key + publishing, and optionally
publishing NIP-09 deletes for the originals. This script does that; it needs
TO_NSEC (destination) and, only for --delete-originals, FROM_NSEC. --dry-run
(default) needs no keys and just lists what would move.