- Switch the hook headline to Bebas Neue (font-display family) at heavier
size with a synthetic webkit-text-stroke fatten, italic, uppercase, and
tight leading so 'Connecting activists to / unstoppable funding.' reads
as a single editorial statement.
- Force the orange highlight onto its own line via <br>; tune left/right
padding and inner text offset so the U sits flush with 'Connecting'
above while the box extends past the word as a flourish.
- Fix two horizontal slashes across the world map caused by
antimeridian-crossing rings (Russia, Antarctica): detect any longitude
step > 180° and close+restart the SVG subpath instead of drawing the
connecting line.
- Drop the 2008-era left-edge darkening gradient and the bottom
vignette behind the map.
- Dim the central radial brand-orange glow (~half alpha).
- Tighten the arc-flow dash period and pixel size.
Derive a static sp1q… identifier from the user's nsec via BIP-352's
spend/scan key paths (m/352'/0'/0'/0'/0 and m/352'/0'/0'/1'/0), then
bech32m-encode scan_pubkey || spend_pubkey with HRP "sp" and version 0.
The Receive dialog now has two tabs: the existing BIP86 fresh-address
flow and a Silent payment tab that shows the static address with QR
and copy. The SP tab is labelled receive-only — the wallet doesn't yet
scan for incoming silent payments, so funds sent there won't appear in
the balance until scan + spend support is wired in.
A production-grade BIP86 Taproot HD wallet, separate from the single-address
wallet at /wallet. The seed is derived deterministically from the user's nsec
via HKDF-SHA-256 with an app-specific info string, so there is no new secret
for the user to back up \u2014 if they have their nsec they have the wallet.
Architecture:
- src/lib/hdwallet/derivation.ts \u2014 HKDF seed, BIP86 (m/86'/0'/0'),
receive (0) and change (1) chains, per-leaf P2TR addresses, TapTweaked
signing keys.
- src/lib/hdwallet/scan.ts \u2014 Standard gap-limit (20) scan across both
chains via Esplora. Aggregated UTXO set, balance, and tx history
(merged by txid so send-with-change shows as one row).
- src/lib/hdwallet/transaction.ts \u2014 Largest-first coin selection
(confirmed first), multi-input P2TR PSBT build with per-input
tapInternalKey from re-derived child keys, fresh change addresses on
the internal chain (no address reuse).
- useHdWalletAccess \u2014 Gates on login type === 'nsec'. Extension and
bunker logins keep the key isolated, so the page shows an explanatory
card with a link back to /wallet.
- useHdWallet \u2014 Scan + tx history queries (60 s refresh), persisted
receive-cursor in secure storage (Keychain on native, localStorage on
web), auto-advance when chain catches up so old addresses are never
re-shown.
- HDWalletPage \u2014 Mirrors /wallet's clean UX: big USD balance, send
button, QR + truncated address, 'New address' rotator, collapsible tx
history.
- HDSendBitcoinDialog \u2014 Mirrors SendBitcoinDialog (USD presets, fee
speed picker, two-tap arm for large amounts, raw-address privacy
disclaimer, success screen) but uses the HD UTXO set across many
addresses and signs with per-input HD-derived keys.
The @samthomson/nostr-messaging library opens fresh NRelay1 sockets per
participant per relay outside the shared NPool, fanning out to every
conversation partner's NIP-65 + NIP-17 inbox relays plus all
discoveryRelays in hybrid mode. In practice this drives connection counts
to several hundred relays per session.
Rather than band-aid the fan-out, drop the feature entirely and point
users to White Noise for end-to-end encrypted Nostr chat.
- Replace /messages with a 'Install White Noise' CTA card (route kept)
- Delete MessagingSettingsPage, DMProviderWrapper, messaging-intro.png
- Remove DMProvider wrapper and PROTOCOL_MODE config from App.tsx
- Drop messaging config from AppConfig, AppConfigSchema,
EncryptedSettingsSchema, EncryptedSettings, and the NostrSync /
useInitialSync sync paths
- Remove messages sidebar entry, default sidebarOrder slot, and
SettingsPage messaging card
- Uninstall @samthomson/nostr-messaging and drop its tailwind content
glob and vitest deps.inline entry
- Update copy in PrivacyPolicy, AdvancedSettings delete-account warning,
ProfileSettings nsec warning, RequestToVanishDialog deletion checklist,
MainLayout comment, and NIP.md
- Leave kind 4 rendering (EncryptedMessageContent) intact so DM events
authored elsewhere still display in feeds and quote embeds
Mirror the existing Android publishing flow for iOS. The pipeline
gains two jobs: build-ipa runs on a self-hosted Mac runner and
produces a signed App Store IPA; publish-app-store runs on a shared
Linux runner and submits the prebuilt IPA to App Store Connect.
Build pipeline (.gitlab-ci.yml):
- build-ipa (Mac, stage build, parallel with build-apk): decodes the
ASC API key, runs match (with api_key, so cert validity is verified
against Apple before xcodebuild starts), builds web assets, syncs
Capacitor, stamps MARKETING_VERSION. Uploads Ditto-${CI_COMMIT_TAG}
.ipa to GitLab's Generic Packages registry.
- publish-app-store (Linux ruby:3.3, needs: [build-ipa]): gem
install fastlane, decode the ASC API key, extract the changelog
section into release_notes.txt, fastlane submit_release with
IPA_PATH pointing at the inherited artifact. No Xcode, no signing,
no keychain \u2014 pure Apple API call.
- release job now needs both build-apk and build-ipa, and links three
assets (APK / AAB / IPA).
fastlane (ios/fastlane/Fastfile, Matchfile, Appfile, metadata/):
- Four lanes: build_ipa (CI build), submit_release (CI publish, reads
IPA_PATH from env), release (single-step convenience for local
dev), submit_only (debug lane to re-submit an already-uploaded
build).
- Match config points at the private gitlab.com/soapbox-pub
/certificates repo. App Store Connect API key is built inline in
the Fastfile to avoid a collision with match's APP_STORE_CONNECT
_API_KEY_PATH env var (match wants a JSON descriptor, the action
writes a raw .p8). CI overrides CODE_SIGN_STYLE=Manual via xcargs
so the Xcode project can stay on Automatic for local development.
Vite config (vite.config.ts):
- Renames the build-time config override env var from CONFIG_FILE to
DITTO_CONFIG_FILE. GitLab Runner sets CONFIG_FILE to its own TOML
config in job env, which broke vite's loader.
App-side changes:
- ios/App/App.xcodeproj/project.pbxproj: team GZLTTH5DLM stamped in;
MARKETING_VERSION gets stamped from the tag at build time.
- public/CHANGELOG.md, package.json: v2.14.3.
Skills + AGENTS.md updated to reflect the six-job pipeline (test /
deploy unchanged, build now has two jobs, release / publish updated)
and to document Mac-runner operations, fastlane match cert rotation,
and local debugging workflows.
Ignore .agents skill bundles from eslint (template files not part of
the build), enable eslint --cache and tsc --incremental so warm re-runs
skip unchanged files.
When a user tapped "Open Signer App", the dialog previously stayed
frozen on the same screen — same button, same copy-URI fallback, no
feedback — until the login either succeeded (and the dialog dismissed)
or timed out after two minutes. With slow or flaky signers (Amber's
current listening-REQ bug being the immediate trigger, but any NIP-46
signer that takes more than a second or two to respond hits the same
hole) this looked indistinguishable from a hang. Users retapped the
button, closed the dialog, gave up.
Now the dialog swaps the QR / Open Signer App area for a centered
spinner with a live status line as the handshake advances:
- "Waiting for signer connection…" while the signer app has the user
and we're listening on kind 24133 for the connect-ack.
- "Getting public key…" once the connect-ack arrives and we're
issuing the NIP-46 get_public_key RPC.
On mobile the swap happens synchronously when the user taps "Open
Signer App" so they see the progress state the moment they return
from the signer — this is the most important window, since that's
exactly when the original UI left them staring at a button they
were worried they needed to re-tap. On desktop the QR stays visible
through the awaiting-connect phase (they may still be scanning with
a different device) and only swaps in once the signer has
acknowledged.
The progress view includes a Cancel link (primary color, matches the
"Create account" affordance) that aborts the in-flight subscription
and regenerates fresh connect params — equivalent to the existing
Retry path, but reachable while the handshake is live instead of
only after a failure.
The handshake phases are surfaced via the new `onStatus` callback
on `NLogin.fromNostrConnect` in @nostrify/react 0.6.0. Bumps
@nostrify/react to ^0.6.0 and @nostrify/nostrify / @nostrify/types
to their matching versions (^0.52.0 / ^0.37.0) to avoid duplicate
nested package copies that would otherwise split type identity.
Incidental cleanup while editing the dialog: the Copy URI button and
the "Tap to open your signer app" / "Scan with your signer app"
status lines are removed. The primary Open Signer App button is
self-explanatory on mobile, and the QR on desktop doesn't need a
caption.
Adds feed support for kind 2473 (bird-by-ear detections) and kind 30621
(user-drawn star figures) from Birdstar. Detections render as species
cards using the existing Wikidata + Wikipedia summary hooks; constellations
render as gnomonically-projected SVG star-maps backed by the Hipparcos
catalog from d3-celestial. The 1.1 MB catalog is code-split via lazy() so
it only loads when a constellation event is actually viewed.
Install @capacitor/haptics and add a centralized haptics utility
(src/lib/haptics.ts) that uses the native taptic engine on iOS/Android
and falls back to navigator.vibrate() on web.
Haptics added to:
- Switch component (covers 36+ toggle switches app-wide)
- PullToRefresh threshold (covers 15+ pages)
- MobileBottomNav tab taps
- ReactionButton (like/unlike, double-click heart)
- RepostMenu (repost/undo repost)
- ZapDialog button press + payment success (NWC and WebLN)
- FollowButton and ProfilePage follow toggle
- ComposeBox (post, voice message, and poll publish success)
- NoteMoreMenu (bookmark, pin, mute)
- VinesFeedPage reaction and repost buttons
- ProfileReactionButton and ExternalReactionButton
- NoteCard share button
- BlobbiRoomShell swipe navigation
Replaces raw navigator.vibrate() calls in GameControls and
SendAnimation with the new cross-platform haptics utility, fixing
haptic feedback on iOS where the Vibration API is not available.