Reconcile stats/encryption iteration + global counter + GIFs (KLIPY) - #4
Merged
Conversation
… counter Merges the `max/create-first-iteration-3oo` product line into the renamed, cleaned-up main line: - Encrypted transport: one shared team ntfy topic derived from a team password (HKDF-SHA256), AES-GCM payloads, generic "Nudge" title — no names in cleartext. New files: NudgeTeamSecret, NudgeRoster (dynamic peer discovery via hello/ping), NudgeStats, NudgePasswordPickerView. - Two-step onboarding (identity → team password); transport starts only when both are set. - Preserved from main: optional per-ping message, launch-at-login (SMAppService), the Nudge rename/structure. Counter changes (per request): - NudgeStats is now GLOBAL/all-time (no weekly reset), tracking sent and received separately (per-peer + totals). New UserDefaults keys. - The number is removed from the menu-bar label (just the wave icon now); "Nudges sent"/"Nudges received" totals show inside the expanded menu, with an all-time leaderboard + breakdowns in Settings. Also: onboarding finish icon sparkles → hand.wave.fill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- NudgeGifService: Tenor v2 search, provider-abstracted for an easy Giphy swap. API key stored in Defaults (tenorApiKey), pasted in Settings → GIFs. - AnimatedGifView: renders GIFs via NSImageView (no dependency) with an in-process data cache. - NudgeGifPickerView + GifPickerWindowController: a dedicated panel (opened from a GIF button in the notch and a "Send a GIF…" menu item) to search, pick a teammate, add an optional message, and send. - Transport: optional `gif` URL field in the encrypted payload alongside `msg`; send(to:from:message:gif:). Recipient's notch renders the GIF and the backup notification says "sent a GIF". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The repo is public, so the shared GIF key can't live in source. Instead: - Info.plist exposes `TenorAPIKey` = `$(TENOR_API_KEY)`, substituted at build time. Local dev builds leave it empty (fall back to a per-user key in Settings); CI bakes in the real key. - NudgeGifService reads the bundled key from Info.plist, overridden by a per-user key if one is set in Settings → GIFs. - nudge-release.yml passes the `TENOR_API_KEY` GitHub Actions secret into the archive build. Verified: local build → empty TenorAPIKey; `TENOR_API_KEY=…` build → key baked into the app's Info.plist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Google discontinued the Tenor API (keys/requests dead as of 2026-06-30), so
swap the provider-abstracted GIF service to KLIPY (drop-in successor, free
tier, no caps).
- KlipyProvider hits api.klipy.com/api/v1/{key}/gifs/{search,trending},
parsing data.data[].file.<size>.<format>.url (verified against the live
API) with forgiving fallbacks; trending is best-effort on initial load.
- Renamed key plumbing Tenor → Klipy: Defaults(.klipyApiKey), Info.plist
KlipyAPIKey = $(KLIPY_API_KEY), CI secret KLIPY_API_KEY in nudge-release,
Settings "Klipy API key" (klipy.com/developers). Team key set as a repo
Actions secret — not in source.
Verified: live API returns 200 with the expected schema; local build with
KLIPY_API_KEY=… bakes the key into Info.plist and the app launches.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
It deployed updater/ (the Sparkle appcast) to GitHub Pages and had been failing on every push since mid-May. Sparkle was removed in the rebrand, so the feed is dead. Removes the last red check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges the
max/create-first-iteration-3ooproduct line into the renamed/cleanmain, then adds the requested counter changes and GIF support.1. Reconciliation (encrypted transport + roster + stats)
Adopts Line B's richer model onto
main's structure, keepingmain's optional-message + launch-at-login:"Nudge"title — no names in cleartext. New:NudgeTeamSecret,NudgeRoster(dynamic peer discovery),NudgeStats,NudgePasswordPickerView.SMAppService).2. Global counter (per request)
NudgeStatsis now all-time (no weekly reset), tracking sent and received separately (per-peer + totals).3. GIFs via KLIPY
NSImageView, no dependency).KLIPY_API_KEYActions secret (Info.plist$(KLIPY_API_KEY)) — not in source (public repo). Per-user override in Settings → GIFs.Verification
Clean Debug build via Xcode at each step; app launches and runs. Klipy key injection verified both ways (empty locally → falls back to per-user key;
KLIPY_API_KEY=…→ baked into Info.plist).Not runtime-verified: the encrypted send/receive of pings/messages/GIFs between two identities (needs two machines on the same team password). Worth a two-machine test before shipping.
Merge note
Independent of PR #3 (CI). If this merges first, nightly/PR builds won't inject the Klipy key until PR #3 (with the
build.ymlchange) also lands.🤖 Generated with Claude Code