Skip to content

Reconcile stats/encryption iteration + global counter + GIFs (KLIPY) - #4

Merged
maxonary merged 6 commits into
mainfrom
reconcile-stats-encryption
Jul 15, 2026
Merged

Reconcile stats/encryption iteration + global counter + GIFs (KLIPY)#4
maxonary merged 6 commits into
mainfrom
reconcile-stats-encryption

Conversation

@maxonary

Copy link
Copy Markdown
Owner

Merges the max/create-first-iteration-3oo product line into the renamed/clean main, 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, keeping main's optional-message + launch-at-login:

  • 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: NudgeTeamSecret, NudgeRoster (dynamic peer discovery), NudgeStats, NudgePasswordPickerView.
  • Two-step onboarding (identity → team password); transport starts only when both are set.
  • Preserved: optional per-ping message (now inside the encrypted payload), launch-at-login (SMAppService).

2. Global counter (per request)

  • NudgeStats is now all-time (no weekly reset), tracking sent and received separately (per-peer + totals).
  • The number is removed from the menu-bar label (just the 👋 icon); "Nudges sent / received" totals show inside the expanded menu, with an all-time leaderboard + breakdowns in Settings.

3. GIFs via KLIPY

  • Attach a GIF to a nudge: search KLIPY in a dedicated panel (GIF button in the notch + "Send a GIF…" menu item), pick a teammate, optional message, send. The GIF URL rides inside the same encrypted payload; the recipient's notch renders it (NSImageView, no dependency).
  • (Tenor was originally used but Google shut its API down 2026-06-30 → pivoted to KLIPY, verified against the live API.)
  • Team key: injected at build time from the KLIPY_API_KEY Actions 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.yml change) also lands.

🤖 Generated with Claude Code

maxonary and others added 6 commits July 15, 2026 12:08
… 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>
@maxonary
maxonary merged commit 4675120 into main Jul 15, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant