From e5e2f7d7a1dea12b3a8e6438cdf56002ef81f71f Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Sun, 27 Sep 2026 16:42:39 +0700 Subject: [PATCH] fix(nav,amber,reputation,routing): header crowding, no read-state popups, anti-collusion rep, instant directions - Header: shrink-0 logo + whitespace-nowrap + gap, nav shrinks/scrolls so it can never collide with (or glow over) the SkateSpots wordmark; tighter item padding. Fixes the top-left logo/nav overlap. - Amber: useReadState keeps read markers LOCAL-ONLY for interactive signers, so merely opening the bell/DM no longer publishes (encrypt + sign = 2 popups). - Reputation: lead with verifiedCounterparties (distinct people), cap each partner at 3 in the WoT-weighted score, warn when every confirmation is with one account. NIP.md documents the collusion limit honestly. - Directions: skip the live GPS call when the cached fix is <2 min old; the blocking 'Finding your location' overlay only shows with no seed (silent refine chip otherwise). Near Me also flips the sort to proximity. - .gitignore: cvm-server/package.json (local dev copy of package.cvm.json). --- .gitignore | 3 +++ AGENTS.md | 26 ++++++++++++++++++++++---- NIP.md | 24 ++++++++++++++++++++++++ src/components/Layout.tsx | 16 ++++++++++------ src/components/RouteDialog.tsx | 25 ++++++++++++++++++------- src/components/TradeReputationCard.tsx | 20 ++++++++++++++++---- src/hooks/useReadState.ts | 17 +++++++++++++++-- src/hooks/useTradeAttestations.ts | 11 ++++++++++- src/pages/MapPage.tsx | 3 +++ 9 files changed, 121 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index b984352..56fbb31 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,7 @@ stats.json /resources/ /bun.lock cvm-server/bun.lock +# CVM server local dev manifest — the tracked manifest is package.cvm.json +# (DEPLOY.md does `cp package.cvm.json package.json` on the server). +cvm-server/package.json *.bak diff --git a/AGENTS.md b/AGENTS.md index 3942bfb..fe8cf74 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -67,7 +67,15 @@ session with **no other memory of this project**. So: detail page at **`/listing/:pubkey/:identifier`**. Trade/escrow actions live in the shared **`ListingTradeActions`** (used by BOTH the card and the detail page — one implementation). Buyer/seller attestations build mutual, WoT-weighted - reputation. **No payments are implemented** — "Buy" is intentionally absent; + reputation. **Anti-collusion (2026-09-27):** two accounts can fake mutual + confirmations, so reputation leads with **`verifiedCounterparties`** (distinct + people mutually confirmed with) instead of the raw `verified` count, weights + by WoT with a **per-partner cap of 3**, and shows a warning when several + confirmations all come from one account (`TradeReputationCard`). `NIP.md` → + "Trust model, sybil/collusion limits" documents that attestations are a + signal, **not proof of payment** — never present them as trustless. Confirming + a trade also records an optional **1–10 deck `rating`** (see `rating` tag). + **No payments are implemented** — "Buy" is intentionally absent; buyers contact the seller and settle off-app. A **`/trades`** page ("My Trades") manages incoming requests, pending confirmations, and the user's trade records, and can **revoke** a record (`s=revoked`, excluded from reputation) for a dispute @@ -186,7 +194,11 @@ session with **no other memory of this project**. So: labelled honestly. **Transit** mode uses **Transitous** (`api.transitous.org`, MOTIS 2, open CORS) — note origin/destination go to that service, unlike road modes. **No external map-app handoff anywhere** (product decision) and no - scooter/moped profile. + scooter/moped profile. **Speed (2026-09-27):** `RouteDialog` seeds origin from + `getLastLocation()` and skips the live GPS call entirely when that fix is + **< 2 min old**; the blocking "Finding your location…" overlay shows only when + there is no seed (with a seed it refines silently via a corner chip). The map + "Near Me" button also flips the sort to `proximity`. - **`Layout` is a router layout** — `AppRouter` wraps every route (except `/amber-callback` and the `*` 404) in **one** `}>` with an ``, so the chrome (header, nav, notifications, Sasquatch) @@ -196,7 +208,11 @@ session with **no other memory of this project**. So: `` wrappers** — remounting the chrome re-runs its hooks (and with Amber that means signer popups). Encrypted-on-mount hooks are also gated for interactive signers (`staleTime/gcTime: Infinity`, `refetchOnMount: false`) — - see `useSyncedAppData` / `useStash`. + see `useSyncedAppData` / `useStash`. **Read-state is local-only for interactive + signers**: `useReadState` skips the encrypted publish when + `isInteractiveSigner` is true, so merely opening the notification bell (which + marks read) no longer pops Amber twice (encrypt + sign). Interactive signers + only prompt for real signed actions. - **Mints directory**: `/mints` page lists Cashu mints from the community **MintRadar** project. MintRadar has **no CORS**, so it's fetched same-origin at `/api/mints/*` — prod proxies via `public/_redirects` (`/api/mints/* → @@ -218,7 +234,9 @@ session with **no other memory of this project**. So: Renaming a component to `_Name` also trips `rules-of-hooks` — delete it or suppress instead. - **Untracked local junk — do NOT commit** (add to `.gitignore`, not the repo): - `assets/`, `resources/`, `/bun.lock`, `cvm-server/bun.lock`, `*.bak`. + `assets/`, `resources/`, `/bun.lock`, `cvm-server/bun.lock`, + `cvm-server/package.json` (local dev copy of the tracked `package.cvm.json`), + `*.bak`. (`cvm-server/` *source* IS tracked — only its locks/backups are junk.) Watch out: `git add -A` in the repo root sweeps these in. - **Never `git add -A` from the repo root** without checking `git status` first. diff --git a/NIP.md b/NIP.md index c159f07..4a1a989 100644 --- a/NIP.md +++ b/NIP.md @@ -3191,6 +3191,30 @@ The base NIP has no counterparty field for this use case, so we add: `method`/`amount`/`rating` are **self-reported**, never proof of payment or a verified score — the app cannot observe off-app (cash/trade) settlement. +## Trust model, sybil/collusion limits (honest) + +Trades here are **attestations, not escrow receipts**. Anyone can mint a listing +and two accounts can confirm each other with no goods exchanged. So the app +**never** presents mutual confirmation as proof — it is one signal among others. +What we do to blunt fake reputation: + +- **Lead with distinct counterparties, not raw counts.** The meaningful headline + is `verifiedCounterparties` — the number of *different* people a trade was + mutually confirmed with. Two accounts trading only with each other can grow the + raw count without bound but can never grow this number past `1`. +- **Weight by web-of-trust, and cap per partner.** Each counterparty's confirmed + trades are weighted by their trust score and capped (max 3) so a single partner + can't dominate. +- **Flag the pattern.** When a profile has several confirmations but only one + distinct counterparty, the UI shows an explicit warning. +- **Revocation is honest.** An attestation can be retracted (`s=revoked`) by its + author; revoked records are excluded from reputation (they remain on relays, + which we do not hide). + +There is deliberately **no** fake "trustless trade proof" — off-app cash/trade +settlement cannot be verified by the app. A payment-proof-based reputation would +require assuming Lightning/zaps, which the marketplace intentionally does not. + ## States & revocation `["s", "verifying" | "valid" | "revoked"]` follows the NIP's lifecycle. Because diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 242bb52..31e2ae4 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -534,26 +534,30 @@ export function Layout({ children, noPadding }: LayoutProps) { {/* Desktop Header */}
-
- +
+ {/* Brand lockup — `shrink-0` + `whitespace-nowrap` guarantee the + logo never gets squeezed into (or glowed over by) the nav. */} + SkateSpots - SKATESPOTS -