April 30, 2026
Per-community notification preferences: Each community membership now has a “Manage Notifications” screen with nine independent toggles — new requests, new events, gear shared, event completed, transfer updates, request updates, RSVPs, planning updates, and chats. The server filters push delivery against these preferences before dispatch (#1107, #1553)
Add to calendar from the Event tab: An inline calendar icon on each event row drops the event into the device calendar in one tap, without opening the detail screen (#1546, #1550, reported by lesliehall)
Streaming community creation: AI-assisted community creation now streams results to the preview the same way gear, request, and event generation do. Fields populate incrementally instead of all at once at the end (#1540, #1567)
Image-mode and webpage-mode AI streaming: Generating gear from an image and generating events from a webpage URL now stream provider responses end to end, including image-mode flows for requests and events. Description is emitted mid-stream and time/location skeletons render before the structured response completes (#1452, #1456, #1471, #1487, #1502)
Event-tab UI sweep — “Experience” → “Event”:
Every user-visible string that referred to an “experience” now says
“event,” in the app, in push notifications, in emails, and in
screen-reader labels. Code identifiers, RPC names, and proto messages
still use Experience internally (#1107,
#1570,
#1603)
Recipient withdrawal ended a giveaway: When a recipient withdrew from a giveaway, the entire giveaway was being marked complete instead of just releasing that recipient’s slot. Cancellation now scopes correctly to the withdrawing recipient (#1230, #1565)
Stale chat history after backgrounding: Reopening a conversation after the app had been backgrounded for a while would show messages that were out of date. Chat now refreshes on resume and gap-fills any messages missed while the stream was disconnected (#1256, #1566, reported by thomas)
Replacing a video left the old preview onscreen: Swapping a video in gear/event creation flows kept showing the previous preview because the old controller wasn’t being torn down. Media replacement now clears the controller and stabilizes the background image while the new media loads (#1544, #1598, reported by lesliehall)
“Offer help” failed when a stale community was active: Tapping “offer help” on a request from a community the user was no longer a member of errored out instead of falling back to a community both parties share. The stale-community guard now extends to the gear and event flows too (#1547, #1549, #1571, #1590)
Second community settings screen showed the first community: Opening the governance screen for a second community always rendered the first community’s data because of a state bleed across community contexts (#1371, #1373, reported by briangbrown)
Gear-share epoch-timestamp bug: A missing-timestamp path was rendering shares as “20568 days ago.” Empty timestamps now suppress the relative display instead of formatting epoch-zero (#1374, #1375, reported by briangbrown)
Stats screen swipe order was wrong: Horizontal swipe order on the stats screen didn’t match the tab order (#1376, #1385, reported by briangbrown)
Help-offered and events-hosted counts hardcoded to
0: The user-stats RPC was returning literal zeros for
HelpOfferedCount and EventsHostedCount instead
of querying the underlying tables (#1355,
#1402)
Request stats lost help_value_usd:
GetRequestStats was recomputing the dollar value instead of
reading the persisted ImpactEstimate, so the number drifted across views
(#1357,
#1420)
Experience time skeleton showed wrong place coordinates: Lat/lng and RSVP counts weren’t being populated by the experience API mapper, so the detail view rendered with empty coordinates (#1356, #1403)
Phone-auth refresh dropped phone_number: The
refresh-token-issued access token was missing the
phone_number claim, breaking flows that read the user’s
phone number from the token (#1450,
#1455,
#1482)
Feed flashed on detail-level real-time events: The feed was triggering a full re-render whenever any community event arrived, even ones unrelated to the visible items. Only feed-relevant events re-render now (#1137, #1321)
JWT signing secret hardening: The server now requires a flag-supplied JWT signing secret of at least 32 bytes, rejecting startup if the secret is missing or weak. Wired through Terraform and the Dockerfile (#1284, #1572)
Removed FindUserByEmail enumeration oracle: The
unauthenticated FindUserByEmail RPC let an attacker probe
whether an email was registered. Removed; GetUser now
requires authentication (#1530,
#1564)
Authentication endpoints rate-limited: Per-IP and per-email rate limits on the LoginService authentication paths (#1531, #1561)
Audit logging on failed authentication attempts: Failures now produce structured audit log entries with masked email and rate-limit context (#1342, #1422)
Stop leaking raw internals via Connect
CodeInternal: Bare
connect.NewError(connect.CodeInternal, err) calls leaked
storage / SDK / decoder error text to clients. Replaced with a
connecterr.Internal helper that logs err
server-side and returns a generic public message; CI gate enforces the
new pattern going forward (#1340,
#1597)
Sanitized client-side error messages: Client
viewmodels now classify errors into a typed UserError and
localize at render time instead of surfacing raw exception strings to
the UI (#1518,
#1576)
Access token moved to FlutterSecureStorage: The auth access token is now stored in the OS-backed secure store instead of SharedPreferences (#1528, #1600)
Panic recovery for streaming and notification goroutines: Background goroutines in chat, AI streaming, and notification dispatch now recover from panics so a single crash can’t take down the server. Also added HTTP-level panic recovery middleware (#1275, #1276, #1369, #1378, #1517, #1563, #1595, #1596)
Hardcoded DB credentials and password in startup logs: Removed the default DB password baked into the server binary and redacted the password from the startup log line (#1351, #1352, #1405, #1406)
Production CORS allowlist + HTTP server timeouts: CORS now restricts to an explicit origin allowlist in production, and the HTTP server has read/write/idle timeouts to prevent connection exhaustion (#1282, #1285, #1309, #1312)
SQL identifier injection hardening: protosql/embedding helpers now validate identifier inputs against a strict allowlist (#1353, #1410)
Dependency security updates: Bumped Go runtime
to 1.25.9 (5 stdlib CVEs), golang.org/x/image to v0.39.0
(GO-2026-4815, GO-2026-4961), anthropic-sdk-go to v1.38.0,
mark3labs/mcp-go to v0.49.0, and genkit to
v1.6.1 (#1273,
#1274,
#1337,
#1338,
#1311,
#1365,
#1379,
#1380)
N+1 query elimination across hot paths: Batch
fetches replaced loops in ListCommunities, ListCommunityEvents,
ListConversations, GetUnreadCounts, GetUserTransferStatus, ListUserGear,
leaderboard member-count, portfolio unread counts, and
buildAPIExperience. Server query count under the load
harness dropped meaningfully on every list endpoint (#1280,
#1281,
#1283,
#1345,
#1346,
#1347,
#1348,
#1350,
#1525,
#1534)
Stream video thumbnail bytes through disk: Thumbnail generation no longer holds the full-resolution bytes on the heap, eliminating a memory spike on large uploads (#1449, #1573)
GCS server-side copy for stock images:
CopyStockImageForUser now uses GCS server-side Copy instead
of streaming through the server, so large stock images don’t traverse
the app’s heap (#1262,
#1415)
Index on ChatConversation.participant_ids: Removes an unbounded ListAll on every conversation lookup (#1524, #1577)
Bounded fetchNudgeImagery fan-out: Process-wide semaphore (cap 4) prevents the nudge-imagery generator from saturating the AI provider during a community-wide fan-out (#1263, #1310)
Server-side Prometheus metrics endpoint: Added a
server/metrics package, an HTTP middleware that observes
ripls_http_request_*, a Connect interceptor that emits
ripls_connect_rpcs_total{connect_code}, SQL query and
pool-stats instrumentation, and a /metrics endpoint
protected by a Secret Manager token. The observability doc links to the
endpoint (#1520,
#1589)
Renamed entity-level request_id log key to
target_request_id: Avoids collision with the
X-Request-ID HTTP correlation key in structured logs (#1548,
#1592)
Migrate 6 services to ObservableLogger: Six
services moved from raw slog.Logger to the project’s
wrapper that auto-attaches request and user context (#1343,
#1368)
Readiness probe endpoint: Added
/readyz for orchestrator readiness probes;
HEALTHCHECK now hits /health instead of
/ (#1278,
#1308,
#1521,
#1562)
Accessibility migration: Full WCAG-A coverage
with a CI ratchet that prevents regressions; lint guard catches literal
semanticsLabel: arguments (#1439,
#1489,
#1512)
Dart-file-size CI gate: Files over 1,000 lines
under app/lib/ fail CI; existing offenders were split below
the cap (#1293,
#1324,
#1416)
Wave 1 golangci-lint linters enabled: Including
revive.exported at error severity (every exported symbol
now has a doc comment that starts with the symbol name and avoids
package stuttering) (#1428,
#1474,
#1514,
#1605)
Go coverage reporting + zero-coverage gate: CI publishes coverage and fails when a package has no tests at all (#1433, #1437)
Streaming Go test output via gotestfmt: Live-streamed test output in CI logs instead of buffered-at-end JSON (#1239, #1435)
Phase 1 AI eval harness: Restructured prompt + assertion design for Request/Experience generation evals; community eval suite added (#1216, #1298)
Claude workflows for issue triage and CI repair: Split issue handler into Opus plan + Sonnet implement workflows, added CI failure investigation and PR conflict resolution workflows, and an address-PR-feedback workflow (#1294, #1457, #1459, #1484)