Commit Graph

609 Commits

Author SHA1 Message Date
Alex Gleason d71d6de05f Publish a single kind 8333 receipt per donation tx
A single Bitcoin transaction with N outputs now produces a single kind
8333 onchain-zap event listing every recipient under its own `p` tag,
instead of one event per recipient. The `amount` tag carries the total
sats paid to the listed recipients (the full donation, excluding the
donor's change).

This is straight-forward forward-compatibility: legacy single-recipient
events are just the degenerate case (one `p` tag, amount equal to the
one recipient's slice). Aggregators (`useCampaignDonations`,
`useGlobalDonations`) simplify to summing the `amount` tag across every
matching event — under both schemas an event's `amount` is the total
paid to the recipients listed in that event, so the sum across all
events for a campaign is the campaign total either way.

The verifier (`verifyOnchainZap`) now sums tx outputs paying any listed
recipient's derived Taproot address and strips the sender from the
recipient set so a tx that includes the sender plus legitimate
recipients still verifies. The notifications surface uses a new
`getZapAmountSatsForRecipient` helper to attribute only the viewer's
estimated slice (amount / p_count) rather than crediting them with the
full multi-recipient donation. `CampaignDetailPage` keeps its
group-by-(txid, donor) reply rendering so legacy multi-event donations
still collapse to a single donation card.
2026-05-18 16:36:32 -05:00
Chad Curtis 1ac62aac06 Add photo banner heroes to organize and actions 2026-05-18 12:52:54 -05:00
lemon 634e161085 Remove ephemeral geo chat 2026-05-18 09:49:31 -07:00
lemon ae41290b68 Hide blocked campaign 2026-05-18 09:45:30 -07:00
lemon f665ffa0c0 Flatten campaign form details 2026-05-17 23:12:13 -07:00
lemon ba2c541c31 Add country tags to campaigns 2026-05-17 23:12:13 -07:00
Chad Curtis 735de6ece9 Treat Tibet as country-level across post + country chrome
Extends the prior Tibet/CountryFlag work so the Snow Lion flag wins
everywhere a CN-XZ post or page surfaces, not just in the Discover
country pulse strip.

  - Comment context (NoteCard + PostDetailPage)
      * Country pill on the card header swaps in the SVG via
        CountryFlag (was bare emoji span).
      * Pill hover card uses the subdivision's own name, drops the
        parent-country sub-line, and labels it as 'Country' rather
        than 'Region' for codes with a custom flag.
      * CountryFlagBackdrop (the faded full-bleed flag behind a
        country-rooted note) prefers the bundled SVG over the
        Wikipedia lead image, which for Tibet returns an
        administrative map.

  - PostDetailPage 'country above the post' chip
      * CountryPreview now routes through CountryFlag and prefers
        info.subdivisionName when a custom flag is registered, so
        the chip reads as 'Tibet' instead of 'China'.

  - Country page (/i/iso3166:CN-XZ)
      * Hero banner driven by customFlagAsset(code) when present,
        sharing the same <img>+skyOverlay pipeline as Wikipedia
        photos so the day/night tint and bottom fade still apply.
      * Subline beneath the title no longer falls into the
        'subdivision = show parent country' branch for custom-flag
        codes; it now reads the Wikipedia description / official
        name like other countries do.
      * Big flag slot uses CountryFlag too, bypassing the Wikipedia
        subdivision thumbnail.

  - Helpers split out of CountryFlag.tsx into src/lib/customFlags.ts
    (hasCustomFlag, customFlagAsset) so the component file only
    exports a component — fixes the react-refresh warning that came
    out of the first pass.

  - Action cards (feed + detail) now render their country chip
    through CountryFlag, picking up the SVG for any future Tibet-
    tagged action.
2026-05-17 23:39:23 -05:00
Chad Curtis e5f7ece942 Bring back Tibet as a country with Snow Lion flag SVG
The older Pathos/Agora codebase treated CN-XZ as country-level Tibet
with a bundled Wikimedia Snow Lion SVG (commits f03d2400, 351b3be4,
6e04b80d). That fell out somewhere in the port — restore it.

  - public/flag-tibet.svg recovered verbatim from f03d2400.
  - New CountryFlag component centralises the country-flag rendering
    decision: emoji for everyone Unicode covers, bundled SVG for the
    short list of recognised flags that don't have an emoji
    codepoint (Tibet today, room for more later).
  - CountryPulseStrip special-cases CN-XZ as country-level: renders
    'Tibet' (not 'Tibet Autonomous Region, China') and drops the
    XZ subdivision-token badge.

Also adds the subdivisionFlag() helper for RGI tag-sequence
subdivisions (England, Scotland, Wales) — Unicode actually does
ship those, and the strip now picks them up automatically.

Other Unicode-missing subdivisions (US states, Canadian provinces)
still render as parent country flag plus a typographic ISO 3166-2
badge. They have no emoji codepoint and bundling a flag pack for
every state is out of scope for this change.
2026-05-17 23:39:23 -05:00
Alex Gleason 9190f62b9e Consolidate /bitcoin into /wallet, drop Lightning custody
Agora previously shipped two parallel wallets: a heavy 6,400-line Breez
SDK Lightning wallet at /wallet and a lightweight on-chain Taproot view
at /bitcoin derived from the user's Nostr pubkey. Maintaining two key
custody models, two send flows, two zap paths (Lightning via Spark,
on-chain via PSBT), and the Spark-specific UI (CreateWallet, mnemonic
backup/restore, lock screen, payment history, etc.) didn't pay for itself
once on-chain Bitcoin signing via NIP-07/NIP-46 became viable.

This consolidation aligns Agora with Ditto's wallet model:

  - The on-chain Taproot view from /bitcoin becomes the only /wallet UI.
  - /bitcoin redirects to /wallet for back-compat; sidebar and TopNav
    drop the duplicate Bitcoin entry.
  - The Breez/Spark wallet stack is removed: SparkWalletProvider,
    SparkWalletContext, all of src/components/SparkWallet/*, useSparkWallet,
    useCommunityBatchZaps, usePaymentContext, WalletSettingsContent, and
    LightningEffect are deleted (~6,400 lines).
  - Ditto's mature bitcoin/zap stack is ported: useOnchainZap (single-event
    on-chain zaps + kind 8333 receipts), OnchainZapContent, ZapDialog with
    Bitcoin/Lightning tabs, ZapSuccessScreen, BitcoinContentHeader, and the
    larger SendBitcoinDialog. useZaps loses its breezService branch and
    falls back to NWC → WebLN → manual QR.
  - bitcoin.ts now threads esploraBaseUrl through every call, matching
    AppConfig and allowing future relay/Esplora customization.
  - CommunityZapDialog is bitcoin-only; CommunityDetailPage drops the
    sibling Lightning trigger.

Lightning recovery remains intentional. A small "Looking for your old
wallet?" link on /wallet routes to /wallet/recovery, which lazy-loads
@breeztech/breez-sdk-spark (now in its own 67 KB chunk plus the WASM)
only when a user needs to evacuate funds. The recovery page:

  - Auto-detects the NIP-78 kind-30078 d="spark-wallet-backup" relay
    backup and offers one-click NIP-44 decrypt via the user's signer.
  - Accepts a manual 12-word mnemonic as fallback.
  - Connects Breez in-memory, sweeps the entire on-chain balance to the
    user's Nostr-derived Taproot address, then disconnects. Nothing is
    persisted; the old wallet is never "restored" — only evacuated.

Other small carry-overs from Ditto needed by the ported code:
useFormatMoney + AppConfig.currencyDisplay ("usd" | "sats"), and the
nostrId helper (HexId branded type + isNostrId validator).

48 files changed, 2,464 insertions(+), 9,743 deletions(-).
2026-05-17 22:53:20 -05:00
Chad Curtis 42abac7527 Polish hero into hopeful beacon with per-campaign atmosphere
- Anchor the globe's center to the right edge of the `max-w-7xl` content
  container (matching the TopNav account switcher), nudged inward via a
  percentage translate so a substantial slice of the sphere always reads
  inside the hero regardless of viewport width.
- Drop the per-breakpoint width classes in favor of a fluid
  `clamp(360px, 46dvw, 820px)` so the globe scales smoothly with the
  viewport instead of in three discrete jumps. HeroGlobe accepts a
  `style` prop so the page can pass the clamp() inline.

Make the globe feel like a beacon of hope:

- New outer halo div behind the SVG with a wide hue-tinted radial glow,
  heavy blur, and a slow opacity-only breathing animation
  (`hero-globe-halo-breath`, 6.5s) so the layout never shifts.
- Sphere base gradient warmed from cream/cool-earth to dawn-gold/honey
  — the disc reads as 'lit from within' instead of dirt-colored.
- Outer dark rim swapped for a soft back-lit limb light tinted with the
  active hope hue. Narrow band, low opacity — suggests atmosphere
  rather than a neon ring.

Tie the globe to the surrounding atmosphere:

- New `src/lib/hopePalette.ts` exports a curated set of warm sunrise /
  dawn hues (`scrim`, `glow`, `rim` per entry) plus
  `hopeHueFor(seed)` that deterministically hashes a string (e.g. the
  campaign aTag) to a stable palette entry.
- New `HeroAtmosphere` mounts a fresh layer of tinted gradients each
  time the active seed changes and crossfades over 1.5s to match
  `CampaignHeroBackground`. Uses `mix-blend-mode: screen` so it warms
  the photo instead of flattening it.
- `HeroGlobe` takes the active `HopeHue` so the halo and limb tint
  agree with the rest of the hero.

Layer order is now: photo BG → atmosphere → globe → readability scrim →
content. The scrim sits *above* the globe so it can darken whatever
slice of the sphere ends up behind the headline, and is hidden at lg+
where the globe is already pushed outside the headline column.
2026-05-17 21:53:28 -05:00
Chad Curtis 2c8cd11153 Rebuild campaigns hero around photo BG + globe + spotlight
The hero is now layered like Treasures' HeroGallery:

- CampaignHeroBackground (new) — full-bleed banner image from the
  currently-spotlit campaign, crossfading over ~1.5 s and panning left.
  Warm tint + film grain overlay so foreground text stays legible.
- HeroGlobe — pushed to the right edge with a larger radius, slightly
  translucent so the photo bleeds through. Hearts replace the old dots
  for marker symbols; clicking one selects that campaign.
- HeroCampaignSpotlight (new) — minimal text overlay anchored to the
  bottom-left of the hero container (title, summary, avatar + author,
  location, progress bar with goal, 'View' link). No card chrome.

Land polygons are now the full Natural Earth 110m fidelity (~10.5k
vertices) instead of being heavily Douglas-Peucker'd, so coastlines
look organic rather than chunky. Back-hemisphere rings are now
properly hidden by walking each edge and either dropping back-side
vertices outright or interpolating to the sphere limb where a ring
crosses it — fixes the 'phantom continents through the front' bug.
Rings additionally fade in/out over a narrow z-band near the limb
instead of popping at z = 0.

Markers also have proper z-fade and pull off-canvas when on the back
so they can't intercept clicks they aren't visible for. Selected
markers scale 1.35x with a stronger glow so the user can tell which
campaign the spotlight refers to.

Other cleanup:

- formatCampaignAmount + formatSatsShort move out of CampaignCard.tsx
  into src/lib/formatCampaignAmount.ts so CampaignCard stops failing
  the react-refresh/only-export-components lint.
- Hero CTAs drop the 'Unstoppable fundraising on Nostr' pill and the
  em dash from the supporting copy.
- New keyframes (heroPanLeft / heroPanRight) for the slow Ken-Burns
  pan on the background photos, with prefers-reduced-motion respected.
2026-05-17 21:15:05 -05:00
Chad Curtis 2a69747744 Add slow-spinning globe behind campaigns hero
Drops the 'Unstoppable fundraising on Nostr' pill and the em dash from the
hero copy, and adds an ambient SVG globe sitting behind the headline.

The globe is a pure-SVG orthographic projection (no WebGL, no canvas). It
renders Natural Earth 110m country boundaries pre-simplified down to ~1.5k
vertices (17 KB inline). Coloring is intentionally warm — cream sphere with
sandy-amber land — to avoid the satellite/HUD aesthetic. Campaigns whose
location string resolves to an ISO 3166-1 country appear as small glowing
markers, deduped by country.

Rotation is driven by requestAnimationFrame and applied imperatively via
refs (no React re-renders during animation), and respects
prefers-reduced-motion by holding at a static angle.
2026-05-17 21:15:05 -05:00
mkfain 323c613222 Add archive flow for campaigns
Authors can soft-close a campaign by republishing it with a
`["status", "archived"]` tag. Archived campaigns are hidden from the
main fundraisers feed and the donate button is disabled, but the detail
page still loads by direct link so existing donors can find it and
past donations remain attached. The author sees Archive / Reopen
buttons on the detail page and an Archived badge on cards.

useCampaigns gains an `includeArchived` option (default false) so a
future profile view can opt in. NIP.md documents the new status tag.
2026-05-17 20:20:44 -05:00
mkfain babfbc5b10 Round campaign USD amounts to whole dollars
Cents are visual noise on zap goal progress displays. Add satsToUSDWhole
helper and use it in CampaignCard, CampaignDetailPage, and the goal
preview in CreateCampaignPage. Wallet and send-bitcoin flows continue to
use satsToUSD with cents.
2026-05-17 20:11:14 -05:00
lemon 177caded5c Refine campaign creation form 2026-05-17 18:03:46 -07:00
lemon f49c20787e Polish zap cards and clamp the campaign story
Four related changes:

1. Campaign story now clips to three lines (~4.5rem) behind a
   soft fade overlay, with a Read more / Show less toggle.
   When there's no story yet the empty placeholder renders
   unclipped as before.

2. Zap cards in NoteCard gained the same reply/repost/react/
   share action bar as a regular note. Each kind 9735 / 8333
   event is a valid Nostr event in its own right, so NIP-22
   replies target it directly via its event id and reactions
   bind to it the same way.

3. The 'zapped' label is now 'donated' when the receipt's a-tag
   points at a kind 30223 campaign, and 'sent' otherwise. The
   target kind is read from the addressable coordinate; pure
   e-tag Lightning zaps fall back to 'sent' without a fetch.

4. Zap amounts render in USD when a BTC→USD price is cached,
   with the raw sats string moved to the title tooltip. Falls
   back to sats when the price hasn't loaded. A new useBtcPrice
   hook shares the existing 'btc-price' cache key so all
   call sites (NoteCard, CampaignCard, useBitcoinWallet) dedupe
   to one in-flight request.
2026-05-17 17:40:21 -07:00
lemon 162d4eee43 Make campaigns USD-first 2026-05-17 17:38:27 -07:00
Alex Gleason 810cbfba00 Set default featured campaigns 2026-05-17 19:34:51 -05:00
Chad Curtis 77eee4f872 Switch credential domain and Android deep links from ditto.pub to agora.spot
The iOS Associated Domains entitlement, Android intent filters, AASA
file, and assetlinks.json already reference agora.spot. Three call
sites still hard-coded ditto.pub:

- CREDENTIAL_DOMAIN in src/lib/credentialManager.ts, which keys iCloud
  Keychain Shared Web Credentials by domain. Saved nsecs were being
  filed under ditto.pub and so could never be matched against the
  agora.spot AASA file.
- MainActivity.handleNotificationIntent host check, which only routed
  the WebView when the tapped notification's URI host equaled
  ditto.pub.
- NostrPoller.showNotification, which built notification PendingIntents
  pointing at https://ditto.pub/notifications.
2026-05-17 18:42:46 -05:00
Chad Curtis 1b4399df68 Rebrand app identifier and IPA name from Ditto to Agora
Renames the Capacitor app identifier from pub.agora.app to
spot.agora.app and cleans up Ditto-branded artifacts that don't refer
to upstream Ditto-the-project or Ditto-stack services.

App identifier (pub.agora.app -> spot.agora.app):
- capacitor.config.ts appId
- android applicationId, namespace, package_name string, custom_url_scheme
- iOS PRODUCT_BUNDLE_IDENTIFIER (Debug + Release)
- public/.well-known/assetlinks.json package_name
- public/.well-known/apple-app-site-association app id
- Info.plist BGTaskSchedulerPermittedIdentifiers and the matching
  Swift bgTaskIdentifier (previously mismatched: plist said
  pub.agora.app.notification-refresh, Swift said
  pub.ditto.app.notification-refresh, so background refresh would
  silently fail to register)
- src/lib/helpContent.ts Zapstore URLs
- .gitlab-ci.yml --package_name for fastlane supply

Android Java package (pub.ditto.app -> spot.agora.app):
- Move android/app/src/main/java/pub/ditto/app/ ->
  android/app/src/main/java/spot/agora/app/ (4 files: MainActivity,
  DittoNotificationPlugin, NostrPoller, NotificationRelayService)
- Update package declarations to match the new Android namespace
  (was a hard build failure with namespace = spot.agora.app)
- Update proguard -keep rule
- Update NotificationRelayService ACTION_FETCH intent string
  pub.ditto.app.ACTION_FETCH -> spot.agora.app.ACTION_FETCH

Fastlane (pub.ditto.app -> spot.agora.app):
- Appfile, Matchfile, Fastfile provisioning profile specifiers.
  Matchfile still points at Soapbox's certificates git repo; a new
  match repo with certs for spot.agora.app is required before iOS CI
  signing works.

IPA artifact name (Ditto.ipa -> Agora.ipa):
- Fastfile output_name and matching CI artifact paths
- .gitlab-ci.yml: artifacts/Ditto.ipa references and the GitLab
  Generic Packages path from /packages/generic/ditto/ ->
  /packages/generic/agora/ (matches how APK/AAB are already
  published). Existing release artifacts at the old path remain
  reachable; new releases land at the new path.

Release-notes script fallback (Ditto vX.Y.Z -> Agora vX.Y.Z):
- scripts/extract-release-notes.mjs fallback used as the App Store /
  Play Store 'What's New' blurb when a changelog section has no
  summary.

manifest.webmanifest:
- Update related_applications Play Store entry to spot.agora.app.
- Remove the iTunes related_applications entry that pointed at
  the existing Ditto App Store listing; not applicable to Agora
  until Agora has its own listing.

Capacitor sync incidentals:
- npm run cap:sync picked up @capacitor/barcode-scanner registration
  that had been missed in a prior plugin install
  (android/app/capacitor.build.gradle, capacitor.settings.gradle,
  ios/App/CapApp-SPM/Package.swift).

Intentionally NOT touched:
- ditto.json filename, DittoConfigSchema, DittoConfig, and JSDoc
  references to ditto.json. The config-system shape is shared with
  upstream Ditto by design.
- relay.ditto.pub, blossom.ditto.pub, ditto.pub/api/* and other
  Ditto-stack services Agora actively consumes.
- The DittoNotificationPlugin Android/iOS class name, the
  DittoNotification JS bridge name, ditto_notification_config
  SharedPreferences keys, ic_stat_ditto drawables, and the
  DittoBridgeViewController. Renaming requires a coordinated
  JS-side rename plus a SharedPreferences migration or existing
  users on the Ditto fork lose their notification config on upgrade.
- Ditto references in skill docs, NIP.md kind comments, README, and
  zapstore.yaml attribution \u2014 those correctly describe the upstream
  Ditto project that Agora forked from.

Follow-ups required before CI succeeds end-to-end (out of scope here):
- Stand up a new fastlane match git repo containing certs +
  provisioning profiles for spot.agora.app, or update Matchfile
  git_url to point at it.
- Register spot.agora.app in App Store Connect for team GZLTTH5DLM
  and create a new App Store listing.
- Create a new Google Play Console listing for spot.agora.app
  (package name is immutable per app on Play; the existing
  pub.agora.app listing cannot be reused).
- Re-publish to Zapstore under spot.agora.app so the URLs in
  helpContent.ts resolve.
2026-05-17 18:42:46 -05:00
Alex Gleason 1db8b4d5b0 Add fundraising campaigns as the new home surface
Pivot the homepage from a Twitter-style social feed to a GoFundMe-style
fundraising hub. Introduces a new addressable kind 30223 "Campaign" that
carries the marketing-style metadata (title, summary, cover image, story,
category, goal, deadline, location) plus a list of recipient pubkeys with
optional split weights. Documented in NIP.md alongside the kind 8333
onchain-zap spec it builds on.

Donations are sent as a single multi-output Bitcoin transaction (one
output per recipient, derived Taproot addresses) using the existing
buildUnsignedMultiOutputPsbt + useBitcoinSigner infrastructure that
backs community on-chain zaps. After broadcast, the client publishes
one kind 8333 receipt per recipient with the campaign's `a` coordinate
so the donation aggregates into the campaign's totals.

UI surfaces:

- /campaigns is now the default homePage. Hero, two featured slots
  (placeholders in src/lib/featuredCampaigns.ts), then a grid of
  user-submitted campaigns.
- /campaigns/new is a full create form with cover upload, slug
  collision check, recipient builder with per-row weights, and
  preset/custom donation-amount UX.
- naddr1 identifiers for kind 30223 route to CampaignDetailPage via
  NIP19Page (full story rendered through the existing ArticleContent
  markdown component, plus a sticky donate rail with progress).
- DonateDialog presets are tuned for on-chain amounts (10K-1M sats)
  with a dust-aware minimum guard derived from the split math.
- Fundraisers sidebar item with a HandHeart icon.

Kept the existing social-feed pages addressable from the sidebar; the
overhaul is scoped to the home/landing experience rather than removing
the underlying Nostr features.
2026-05-17 16:48:06 -05:00
Alex Gleason b62da321f7 Restore Messages sidebar entry, add WhiteNoise logo to /messages page
- Re-add the Messages item to the left sidebar with its previous
  MessageSquareMore lucide icon. Drop requiresAuth so logged-out users
  also see the entry — the page is a static recommendation.
- Restore 'messages' to the default sidebarOrder in App.tsx.
- Add WhiteNoiseIcon (the logomark from whitenoise.chat, recolored to
  currentColor so it adapts to theme) and use it on the /messages
  install-CTA card in place of the generic Lock glyph.
2026-05-17 14:41:34 -05:00
Alex Gleason c5b929187a Remove Nostr direct messaging feature
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
2026-05-17 14:37:49 -05:00
lemon 3926a1c886 Initialize Bitcoin ECC before address derivation 2026-05-17 01:16:56 -07:00
lemon b87b70fa72 Polish Bitcoin dust limit handling 2026-05-17 01:13:53 -07:00
lemon 84bd0c9e17 Support multi-output Bitcoin transactions 2026-05-17 01:07:03 -07:00
lemon 11999c0e8b Add Bitcoin wallet page 2026-05-17 01:05:51 -07:00
lemon 6c2cedf8ec Add Bitcoin signing support 2026-05-17 01:00:49 -07:00
lemon 0e117fa417 Add Bitcoin wallet primitives 2026-05-17 00:59:36 -07:00
lemon d4595d55bf Reuse in-flight Spark wallet sync 2026-05-16 23:21:37 -07:00
lemon 15f10cd58a Make Spark diagnostics timeout tolerant 2026-05-16 23:18:23 -07:00
lemon d5bf21c853 Add Spark wallet diagnostics 2026-05-16 23:15:26 -07:00
lemon d7996c49db Isolate Spark wallet SDK storage 2026-05-16 23:12:53 -07:00
lemon cd97f854c0 Add community batch zaps 2026-05-16 22:37:13 -07:00
lemon a70caae2da Allow dashboard page sidebar add 2026-05-16 11:18:03 -07:00
lemon 5cfd9a049f Remove dashboard from sidebar 2026-05-16 11:15:46 -07:00
lemon d3fadeca09 Make dashboard public and optional in sidebar 2026-05-16 11:01:29 -07:00
lemon 7b66c795fe Fix unused action icon imports 2026-05-16 09:04:55 -07:00
lemon bc0b8f83d4 Render actions as feed cards 2026-05-16 09:00:02 -07:00
lemon 77752f8b65 Rename action challenge internals 2026-05-16 08:53:03 -07:00
lemon 4dbd52e914 Add dashboard admin 2026-05-16 07:58:48 -07:00
Chad Curtis 90b17255af Use double-bolt logo mark in place of single-bolt zap 2026-05-15 23:59:55 -05:00
Chad Curtis 8e19ccd518 Restyle Actions page: less boxy, default covers, canonical FAB 2026-05-15 23:58:48 -05:00
Chad Curtis 22ef2619d7 Tint country pill with colors sampled from the flag emoji
Render each flag emoji to an offscreen canvas, sample the middle stripe,
bucket pixels by hue, and return the top three saturated colors ordered
by their left-to-right position. The country pill uses that palette as
its background gradient so Venezuelan posts get yellow/blue/red,
Brazilian ones get green/yellow, etc.

The work is memoised per-emoji and deferred via requestAnimationFrame.
While the palette is being extracted (or if canvas access fails in a
test/SSR environment), the pill falls back to the primary->accent
gradient. White text gets a drop shadow so it stays legible against
even the lightest flag palettes.
2026-05-15 23:18:45 -05:00
lemon 4a71c15b28 Align Agent feed sources with app tabs 2026-05-15 19:37:23 -07:00
lemon 07fb778c4c Improve Agent world feed and loading states 2026-05-15 19:37:23 -07:00
lemon 6e101e89ef Configure AI provider settings 2026-05-15 19:37:23 -07:00
lemon 37513cd44c Add slash commands with autocomplete, /tools listing, and styled notice messages 2026-05-15 19:37:23 -07:00
lemon 28ef1d725e Harden AI chat: SSRF protection, capacity tracking, scoped storage, and error handling 2026-05-15 19:37:23 -07:00
lemon e2d30255bc Add AI Agent chat with tool-calling, model selector, and sidebar integration
- Implement 5 read-only tools: get_feed, search_users, search_follow_packs, fetch_page, fetch_event
- Upgrade useShakespeare streaming to support tool calls, AbortSignal, and robust SSE parsing
- Create useAIChatSession hook with streaming, 10-round tool loop, localStorage persistence
- Rewrite AIChatPage with modular architecture, streaming UI, tool call badges, and empty-bubble handling
- Add Agent settings section with model dropdown selector and pre-populated system prompt editor
- Add Agent to left sidebar navigation and right widget sidebar defaults
- Add aiModel and aiSystemPrompt config fields with encrypted settings sync
2026-05-15 19:37:23 -07:00