A+B+C pass on /deck-cost.
A - Declutter: each sheet section is now an accordion (CalcSection) with its
live per-board subtotal in the header (Labor open by default, Margins always
visible), plus quick-start templates (Hobby starter / Small shop) that prefll a
realistic sheet.
B - Batch & trade: a quantity field (unit price vs order total), a wholesale
markup giving a wholesale/unit price, profit per deck, and a breakeven
(fixed / profit) with an optional target-monthly-profit goal. The client quote
shows "x N" and the order total; the deposit is taken on the total.
C - Guards: an amber warning block for markup < 1x, wholesale >= retail, rows
with 0 boards, fixed costs with 0 boards/month, no materials, equipment+overhead
over 60% of cost, and equipment spread over a single board; number inputs clamp
negatives to 0.
New state round-trips through CSV (margins rows). The default sheet produces no
warnings.
The arithmetic was already consistent, but three labels described something
other than what the code computes:
- "Overhead factor" multiplied labour only while a separate section held the
real overhead, inviting double-counting -> "Labour burden (x)" + a hint.
- "Labor cost" showed wage x burden, i.e. the loaded rate, not a per-board
cost -> "Loaded rate".
- "Profit margin (x)" was a markup -> "Markup (x)", with the implied gross
margin % shown so the name matches the number.
Also: distinct board-count columns (Lifetime boards / Boards per order /
Boards per run) instead of three identical "# boards"; "Scrap / waste (%)".
PDF and Markdown exports use the same names. CSV machine keys are unchanged so
existing exports still round-trip.
QuoteProfile gains payment methods (multi-select chips), a deposit percent
(PDF splits "Deposit due now" / "Balance on completion"), free-text payment
terms, separate payment details (bank/PayPal), and an optional Incoterm
(EXW/FCA/DAP/DDP). The client PDF gains a payment block; the PNG shows the
deposit. Quote state is now merged over DEFAULT_QUOTE on read so a quote saved
before a new field existed cannot crash the tool.
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).
The Quote details block gains NIP-05, npub and a Lightning address (lud16), plus
a "Fill from profile" button that autofills name/website/NIP-05/Lightning/npub
from the logged-in kind-0 profile. The client PDF lists the NIP-05 and npub in
the contact block and "Pay via Lightning: <lud16>" under the total; the PNG shows
the NIP-05 (or npub) under the business name. A PDF is a snapshot — the identity
is verifiable on Nostr, not on the paper.
- Extract the client-quote identity into its own store (QuoteProfile,
skatespots:deck-cost:quote) so it is NOT part of CalcState — loading or saving
a preset now keeps the same business name/logo/contact details.
- Client quote PDF: a real quote number counter (Q-0007, with a +1 button) and a
configurable validity window, shown as "Quote valid until <date>".
- PNG summary card now also draws the logo (top-right) and the business name.
A "Quote details" block (in the Result section) lets the user set a business/
shop name, a logo (stored as a data URL — no upload/network, capped 500 KB),
email/phone/website/address and notes/terms. The client quote PDF now renders a
logo + contact header, the line item + total, and the notes/terms. Bumped the
localStorage key to :v2 (and guarded preset/JSON loads) so an older saved sheet
without the quote block can't crash the tool.
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.
- Export is now a menu: PDF (quote/print), CSV (spreadsheet-friendly
section,item,field,value rows), Markdown, JSON (backup).
- Import auto-detects .json / .csv: JSON loads a backup, CSV round-trips the
whole sheet (hand-rolled CSV quote/parse so commas in material names are safe).
- Currency picker now offers BOTH Bitcoin units — ₿ sats and ₿ BTC (plus fiat).
BTC is 1 per BTC, sats 1e8; switching converts between them and fiats
(BTC shows up to 8 decimals, trimmed).
- Default Materials is now a complete deck-build checklist, grouped: wood & core
(veneer/edge tape/logs), adhesives & fillers (glue/epoxy/filler), press/mold
consumables (release/vacuum film), shaping (sandpaper/saw+drill bits/masking
tape), finish & graphics (stain/primer/paint/clearcoat/grip) and Other. New
rows default to 0 so the worked example is unchanged.
The first pass overflowed the right margin (long value strings), had no section
separation, and split the Result across pages. Rebuilt the generator:
- accent rule + title, date/currency subtitle;
- each section gets a lime underline + a hairline rule;
- rows are two-line items (name + per-board value, then a grey detail line with
cost/boards/setup), so nothing runs past the margin;
- Result section is kept together (page-break guard) and the two selling prices
render in highlighted boxes;
- page numbers (n / N).
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.
- Equipment now amortises setup time too: (cost + setupHrs × labor/hr) ÷ boards,
so the Setup (h) column actually does something.
- sats values round to whole numbers on currency conversion.
- Each section shows a per-board subtotal (equipment/materials/processes/packaging).
- Sticky summary bar (under the header) keeps cost + selling price visible while
you edit.
- Save/Load named presets (localStorage) + Export/Import JSON; Reset now confirms.
- Results gain "List a deck at this price →" (deep-links CreateListingPage with
?price¤cy, which now reads currency too) and "Get it made →" (Lab Builder).
Coinbase's exchange-rates response includes crypto (BTC, ETH, USDT, …). Filter
the picker to ISO-4217 fiat codes (FIAT_CODES) plus sats, so only real
currencies and bitcoin are selectable.
- getBtcRates()/useBtcRates() read every BTC→fiat rate from the ONE Coinbase
exchange-rates call the app already made, plus sats (1e8). No new endpoint.
- The calculator's currency control is now a searchable Popover: sats + every
fiat Coinbase reports, with a search box. Selecting a currency CONVERTS every
money field (labour, rent, utilities, subscriptions, equipment cost, material
order/shipping, packaging materials) by the live ratio; unitless fields
(overhead factor, board counts, minutes, margins) are untouched. Falls back to
relabel-only if rates aren't loaded.
- Currency symbols/formats for the common fiats.
A chip row at the top switches the whole sheet's unit: $ / € / £ / ₿ sats. Input
labels follow the unit and results reformat (sats are shown rounded, no
decimals). It relabels/reformats only — it does not convert values, since the
app only has a BTC/USD rate (no fiat↔fiat or reliable fiat↔sats conversion);
you enter amounts in the unit you think in. Choice persists with the rest.
- 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.
The desktop footer is hidden on full-bleed map routes, leaving the
Newsletter/License/Privacy/Collaborators links (and the "not sponsored by
OpenSats" note) unreachable from the homepage. MapFooterStrip overlays them
inside the map container without stealing map height:
- Desktop: a slim full-width bottom bar.
- Mobile: a tiny collapsed "Links" chip that expands to a compact, scrollable
row.
- Dismissible (useMapFooterHidden, device-local, same-tab synced) and
restorable from Settings -> Appearance.
- The map's OSM attribution moved up (md:bottom-7) so the bar doesn't cover it.
The admin "It's Monday — write the drop" card was pinned at top-20 on every
route, so on the map it sat on top of the top-right toolbar (Add Spot / Weather
/ More). It now drops below that toolbar on the map routes (top-40) while other
pages keep top-20, and the dismiss button is a proper compact target.
The "Select Content to Delete" dialog was broken on mobile:
- Filter pills had no flex-shrink-0, so they squashed and their labels overlapped
("All (504Spots (Shops…"). Added shrink-0 + min-h-0 so the row scrolls cleanly.
- The global mobile `button { min-height:44px }` rule inflated the checkboxes
into giant squares. Added min-h-0/min-w-0 (and an explicit h-4 w-4).
- The per-row delete on Recent Activity was hover-only (invisible on touch);
it's now always visible on mobile and compact.
The /lab profile card and /stats Top Mappers were overflowing off the right
edge: Tailwind `grid md:grid-cols-2` (no base column) renders one implicit
`auto` track on mobile that can exceed the container when a name is long, so the
whole card got pushed off-screen. Added the base `grid-cols-1` repo-wide (plus
`min-w-0` on truncating flex/grid children).
/stats also gained a "Top Shops — most shops added" leaderboard (kind 30017)
beside Top Mappers (spots): `topShops` in useGrowthStats, a generalized
MapperRow (count + unit), and EN/ES copy. PERSIST_BUSTER → v6 (new persisted
field).
- useEscrowPrefs (device-local): default lock window, preferred escrow mint,
require-escrow default for new listings.
- EscrowSettingsCard on the Wallet (native-only, beside EscrowKeyCard).
- EscrowRequestDialog now defaults its lock window + funding mint from the prefs.
- Listings gain an optional ['escrow','required'] tag: a toggle on
CreateListingPage (defaulted from the pref) and a 🔒 badge on the card.
- Restore the always-on blink/pulse (still motion-reduce:hidden for a11y).
- Drag the bubble off the left edge to hide it; a tiny left-edge tab brings it
back. (The ✕/panel/Settings Hide paths remain.)
- Replaced the cramped inline "Go Pro" header pill with a clean full-width
strip at the top of the panel body.
Sasquatch (the look regressed): I over-corrected last pass by shrinking the
bubble on the map and bolting an always-visible ✕ over the avatar on mobile.
Restored the full-size bubble + glow on every route (the map only loses the
pulse), moved Hide to a desktop-hover ✕ and an EyeOff "Hide assistant" button
in the panel header (Settings → Appearance still re-enables it).
Mints: the add-mint dialog only offered a hand-picked "Recommended" shortlist.
It now also lists the full searchable MintRadar directory (useMintDirectory)
below the recommendations.
Events: CreateEventPage could only link a skate spot. Added an optional
"Link to a Shop" picker (useSkateShops, search, location autofill) that writes
['a','30017:…'] + ['venue-name', …].
One discoverable home for every paid extra, so the pricing model is legible
instead of scattered across the Sasquatch panel and each offer form:
- components/ProSupportScreen.tsx (Settings ?section=pro + a menu row): honest
"prepaid, not a subscription" framing; Sasquatch Pro status (days left) +
PRO_TIERS with per-day sats and a Go Pro / Extend button; Featured placement
(PLATFORM_FEES x DURATION_TIERS with per-day + best-value); your currently
featured Lab listings (featuredUntil); and the free paths (PoW listing, free
AI FAQ).
- Distinct naming in copy: Sasquatch Pro (AI) vs Featured placement (visibility).
- Smoke test now walks /settings?section=pro.
Addresses "the floating Sasquatch flashing is annoying":
- Pulse is now state-driven: the animate-ping halo shows only when there's an
unread tip (message), never perpetually; the unread dot matches.
- Honors prefers-reduced-motion (motion-reduce:hidden).
- Map-aware: Layout passes isFullBleed, so on the map the bubble runs compact
(smaller, no glow, no pulse) instead of competing with map controls.
- Hideable: a ✕ on the FAB hides it (useSasquatchHidden, localStorage
sasquatch-hidden, same-tab synced via an event), with a "Show Sasquatch
assistant" toggle in Settings → Appearance to bring it back.
- FormWizard/WizardStep: a step shell for the long offer forms. Steps stay
mounted (hidden, not unmounted) so nested components with their own state
(LabExtrasFields) keep their values; a controlled step lets the form jump to
the offending step on validation.
- Applied to CraftProfileForm (the 8 data-driven crafts, 5 steps: Identity /
What you offer / Rates / Contact / Media) and the coach page (5 steps).
Submit + Preview live in the wizard footer; submit errors jump to the right
step before scrolling.
- The 4 bespoke pages now scroll to their inline contact error on a failed
submit.
Progress (interpreted as a completeness indicator, not a wizard — the form
stays one scroll):
- LabProfileProgress shows photo/bio/skills/rate/location/contact as done/todo
with a % bar; wired into CraftProfileForm, coach, and the 4 bespoke pages.
Validation (was toast-only):
- CraftProfileForm splits email/website/portfolio into per-field inline errors
with a red field border, gives the first skill section + contact fields ids,
and scrolls to the first problem on submit; required sections show an inline
message after a submit attempt.
- Coach page likewise splits email/website, shows inline errors + red borders,
and scrolls to the contact field or the skills grid.
- useLabDraft persists the form snapshot to localStorage (content-debounced)
and only offers a saved draft as resumable when it differs from the current
state, so opening an existing profile never shows a spurious resume banner and
a draft can't silently clobber a freshly loaded profile.
- LabDraftBanner offers Resume / Discard; wired into CraftProfileForm (8 crafts)
and the coach + filmer/editor/photographer/builder pages.
- The draft clears on a successful publish.
Every provider lived at /coach/<pubkey>/<id> regardless of craft (a barber got
a "coach" URL). New src/lib/labRoutes.ts maps craft kind → segment and builds
the canonical path; the router adds /lab/:craft/:pubkey/:identifier (the craft
segment is cosmetic — the page still resolves by pubkey + identifier) and keeps
/coach/... working for old links. All generated links updated: offer-form
redirects, Lab cards + "Your Lab profiles", cross-links, spot/event/admin links,
the CVM client URL, the share path, and the SPA clean-link shim.
Filling the long form was blind until publish. LabProfilePreview renders a
faithful profile card from the current draft — title, bio, image, location,
skills, structured rate, languages/availability/payments and gallery — with a
not-published-yet note.
- Wired into CraftProfileForm (8 crafts) and the coach + filmer/editor/
photographer/builder pages via a Preview button next to Submit.
- parseLabExtras now accepts any { tags } so a draft tag list can be previewed
without a published event.
- i18n for the preview copy (EN/ES).
Still open: draft/autosave and a multi-step layout.
- After publishing, land with ?published=1 and show a one-time "You're live —
share it" banner on the profile (with a Share button), dismissible.
- /lab gains a "Your Lab profiles" strip listing the viewer's own profiles,
each deep-linking to view/edit — so multi-craft providers aren't hunting
through the list.
The offer → publish loop had two dead ends: a provider's own page only offered
Delete (no Edit), and there was no way to share a listing (the whole point of
announcing a service).
- Owner "Manage" card gains an "Edit profile" button deep-linking to
/become/<craft> (prefills the existing profile). Admins still get only
take-down, not a misleading self-edit link.
- A Share button on every provider page opens ShareProfileDialog (QR + copy +
native share) with the service URL /coach/<pubkey>/<identifier>.
- ShareProfileDialog accepts an optional `path` to override the default
/<npub> share URL.
- main.tsx clean-link shim now also rewrites /coach/<pubkey>/<d> to the hash
route, so shared /coach/... links resolve on the live site.
Follow-ups (not done): live preview before publishing, draft/autosave for the
long forms, a multi-step/progress layout, and a "your Lab profiles" manage
strip on /lab.
The coach portfolio/experience/payments treatment is now a shared layer instead
of 12 copies:
- src/lib/labExtras.ts: parseLabExtras + shared option sets (languages,
availability, structured rate, payment methods, booking fee, gallery/reel).
- components/LabExtrasFields.tsx: the self-contained form block — prefills from
the existing event, owns its state, and reports the managed tags via onChange.
- components/LabExtrasSection.tsx: the detail block (all crafts).
Wired into CraftProfileForm (the 8 data-driven crafts) and the four bespoke
pages (filmer/editor/photographer/builder); CoachDetailPage renders the shared
section for every craft. Per-craft buying units (rateUnits) are set per craft in
becomeRoles.tsx (e.g. barber 'per cut', musician 'per gig', photographer 'per
shoot'). /lab facet filters now read parseLabExtras, so availability/languages
filter across crafts.
Coach-specific formats/levels/ages/credentials remain coach-only. Money stays
custody-free: declared methods + a booking fee paid directly to the provider.
- BackupReminder: wait while the signup dialog is open (the key is minted
mid-flow), so "Save your login" no longer pops before signup is finished.
Uses a small non-reactive signup flag set by SignupDialog.
- BackupReminder: removed the custom close button — the Sheet already renders
its own X (the sheet's close now snoozes for the session).
- CreateEventPage: the "Link to a Skate Spot" row no longer overlaps — the
label/hint can shrink and the Select Spot button stays put.
- New `useBatchSpotCommunityScores(spots)` — ONE query for many spots (same
signals/weights as the single-spot hook; counts distinct accounts, drops the
mapper, caps per account; skip WoT/activity weighting at this scale).
- SpotCard takes an optional `community` prop and shows a small "Community"
(green) or "Seen" chip in the footer.
- MapPage scores the visible spot cards (shops excluded — different kind) and
passes each card its score.
Website/band/social inputs stored whatever was typed, so a bare "crew.com"
wasn't clickable. Now they run through normalizeUrlInput + sanitizeUrl (bare
domains become https://), with an inline "enter a valid link" hint on website
and band URL. A non-URL social (e.g. a @handle) is kept as typed so nothing is
dropped. Matches the profile/craft forms.
The Extras step stacked 9 ungrouped blocks. Now:
- Identity (NIP-05, links) — open by default.
- Sasquatch AI — open when the agent is on.
- Showcase (video, sponsor, band).
- Payments & treasury (treasury + the CLINK tip jar, moved out of "Links").
- Integrations (Lab link).
- Advanced (Coordinator) — collapsed + last, so a new founder isn't asked
about experimental plumbing.
Other fixes:
- The crew AI agent is now labelled "Sasquatch · Pro" (it runs on the same
Pro-gated Sasquatch proxy, falling back to a free model), with an honest note.
- crew founded year is clamped to 1954..current on save and bounded in the input.
- "Skip to end" is no longer hidden on mobile.
- Removed the coordinator copy keys left unused by the previous pass.
For a coordinator addressed by pubkey, the form now probes it on demand (the
same useCoordinatorHealth the Settings → Coordinator card uses) and shows
Connected / Quiet / Not seen / Unreachable. Reuses the existing socialUi
.coordinator status copy; no new keys.
- Coordinator block: shorter copy, a Remove (✕) button, and an honest
"pointer only — real Cordn MLS isn't wired yet" note (the old paragraph was a
wall of jargon).
- Save now blocks on a non-empty but unparseable coordinator (was silently
dropped) instead of publishing a crew with no coordinator.
- One consistent block order for create (wizard "Extras") and edit, grouped:
core -> squad/places -> identity (NIP-05, links, band) -> agent -> showcase
(video, sponsor) -> treasury -> integrations (Lab link, coordinator).
A NIP-05 name maps to one key, so "who owns it" decides where it lives:
- Crew: repeatable nip05 tags (up to 5), verified against the founder key.
- Shop: same on kind 30017, verified against the owner key (new field).
- Profile: kind-0 has ONE standard nip05; extra personal handles live in a
separate NIP-78 record (kind 30078, d=skatespots:profile-handles) so another
client re-saving the profile can't wipe them.
Shared pieces:
- components/Nip05Badges.tsx — generic verify-and-render chip row (any owner).
- components/Nip05HandlesEditor.tsx — shared add/remove editor with live
verified/mismatch status; used by profile, crew and shop forms.
- CrewNip05Badge now delegates to Nip05Badges; CrewForm uses the shared editor.
- Profile page shows primary + extra handles, and each crew/shop chips its own
verified handle.
- lib/profileHandles.ts + hooks/useProfileHandles.ts (read/publish the record).
- types: SkateShop gains nip05/nip05s; parseSkateShop parses repeatable nip05s.
- NIP.md + AGENTS.md + i18n (EN/ES) updated.
A crew is our own tag convention (unlike a kind-0 profile, whose nip05 is a
single string), so it can now advertise up to 5 handles, each verified
independently against the founder key.
- parseSkateCrew: collect all nip05 tags -> SkateCrew.nip05s (normalised,
deduped, regex-validated, cap 5); nip05 stays = first (back-compat).
- CrewForm: add/remove handle rows with a live verified/mismatch/loading status
(useCrewNip05 per row); format validation + max-5 hint; Enter adds.
- CrewNip05Badge renders every handle; CrewHero passes nip05s.
- NIP.md: nip05 documented as repeatable on kind 35006. i18n EN/ES.
The live-location marker used the SkateSpots brand logo. It now uses the
logged-in user's Nostr profile picture (their npub's logo), falling back to the
brand logo when there is none.
- SpotMap takes an optional userPicture; createUserLocationIcon layers the
avatar over the brand logo (CSS multiple backgrounds) so a broken/expired
avatar URL just reveals the logo — no inline JS, CSP-safe.
- The icon refreshes when the picture loads (or changes) without moving the pin.
- MapPage passes the current user's metadata picture.
- Export dialog shows a 4-segment strength meter (length + character classes)
with a Weak/Fair/Good/Strong label as you type the password.
- Shows the filename that will be saved; encrypted exports get an
'-encrypted' suffix so the file is self-describing at a glance.
The stash export was plaintext JSON, so a leaked/shared file exposed the whole
gear list. Export now wraps the payload in a version-2 JSON envelope:
- password mode: scrypt (NIP-49 params, 2^16/r8/p1) -> AES-256-GCM, with the
header (mode+cipher+KDF) bound as AAD so it can't be downgraded.
- nip44 mode: NIP-44-encrypted to your own key (no password to remember; needs
the matching key to open).
- plaintext v1 exports still import unchanged.
New src/lib/stashBackup.ts (verified round-trip, wrong-password, tamper and
KDF-downgrade rejection) + StashBackupExportDialog/StashBackupUnlockDialog.
The import picker only prompts for a password on password-encrypted files.
Password mode needs no signer (Amber-friendly); 'My key' is hidden unless the
signer supports NIP-44.
The marketplace Reputation tab showed a bare 'Loading...' text under
'Reviews & trade records', and TradeRecommendationsCard rendered its
'No recommendations yet.' card while still loading (then it vanished for a
self/empty view) — together they read as broken.
- Reviews/trade-records now render row-shaped skeletons while loading.
- TradeRecommendationsCard renders nothing while its query is loading when the
count is 0 (and only shows for a non-self viewer who can add one).
H14 (NWC double-pay): after the 15s pay() timeout, useNWC now polls
lookupInvoice to confirm settlement before reporting failure; if the status
stays unknown it throws a coded error (PAYMENT_STATUS_UNKNOWN) and the callers
that used to fall through to another rail (useZaps, usePurchasePro,
PlatformFeePayment) now STOP and tell the user to check their wallet — no
automatic second payment.
H1 (spot claim): claim-from now stores the ORIGINAL COORDINATE
(30818:pubkey:d) rather than an ambiguous bare pubkey; SkateSpot parses
claimedFrom/claimReason; a claimed listing shows a 'Claimed listing' notice so
the duplicate is understandable. NIP.md updated. Full reader-side supersede is
documented as the remaining design item (a wrong rule could hide real spots).
- H13 offer threads: the seller is ALWAYS the listing author (derived from the
event pubkey), never a self-declared role tag — a non-owner can no longer
appear as the seller with a payment pointer.
- H12 trade reputation: a preimage/deposit proof counts at most once, so a real
payment can't be reused across attestations to inflate payment-verified.
- Crew alerts: listed crews are filtered against the user's own membership
confirmation labels (one batched query), so a confirmed member no longer gets
a phantom 'confirm to join' badge/notification when the crew is edited.
- Error boundaries (app + route) and DesktopNavManager are translated; the
class boundary renders a translated functional ErrorFallback. New batch4
namespaces errorsUi.* / desktopNav.*.