Skip to content

Commit 324609d

Browse files
committed
fix(analytics): normalize OAuth provider names (fixes #2)
feat(wrapped): add Daily Sync Wrapped feature with 10 CSS templates, countdown timer, PNG download, social sharing, and rotate design test button
1 parent 7cfc9c1 commit 324609d

17 files changed

Lines changed: 1848 additions & 14 deletions

.cursor/rules/changelog.mdc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,10 @@ When you push changelog.md changes to main:
129129
2. Parses the latest `## [X.X.X] - YYYY-MM-DD` version
130130
3. Extracts content between that version and the next `##`
131131
4. Creates a GitHub Release with tag `vX.X.X`
132-
5. Skips if that release already exists
132+
5. Creates a GitHub Discussion in the Changelog category
133+
6. Skips both if that release already exists
134+
135+
**Important:** Only versioned sections trigger releases. `[Unreleased]` content is ignored until moved to a version.
133136

134137
## Example Entry
135138

changelog.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,31 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## [Unreleased]
88

9+
## [1.2.0] - 2026-01-22
10+
11+
### Added
12+
13+
- Daily Sync Wrapped feature: AI-generated visualization of past 24-hour coding activity
14+
- New Wrapped tab in Dashboard (5th view mode alongside Overview, Sessions, Evals, Analytics)
15+
- 10 CSS template designs inspired by modern visual styles (minimal dark, gradient noise, geometric, tech cards, bold typography, vinyl record, orange gradient, dark minimal, blue landscape, color shapes)
16+
- Google Imagen API integration for AI-generated wrapped images
17+
- CSS fallback templates when AI generation unavailable
18+
- 24-hour countdown timer showing time until next generation
19+
- Download as PNG button using html2canvas
20+
- Share to Twitter/X and LinkedIn buttons
21+
- Rotate Design button for testing different template styles
22+
- Daily cron job at 9:30 AM PT generates wrapped for all active users
23+
- Automatic cleanup of expired wrapped records every 6 hours
24+
- New convex/wrapped.ts with queries (getTodayWrapped, getWrappedStats, get24HourStats, getCountdownInfo), mutations (createWrapped, deleteExpired), and actions (generateWrappedImage, generateForUser, generateAllWrapped)
25+
- New convex/crons.ts for scheduled wrapped generation and cleanup
26+
- New dailyWrapped table in schema with indexes (by_user_date, by_user, by_expires)
27+
- html2canvas dependency for client-side PNG export
28+
29+
### Changed
30+
31+
- Dashboard ViewMode type now includes "wrapped" as 5th option
32+
- Dashboard view toggles updated to include Wrapped tab
33+
934
## [1.1.0] - 2025-01-21
1035

1136
### Added
@@ -218,7 +243,8 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
218243
- Updated embeddings:store to use replace pattern instead of delete+insert
219244
- Added idempotency checks with early returns when no meaningful changes detected
220245
- Fixed provider display showing "unknown" for antigravity-oauth and anthropic-oauth sessions (fixes #2)
221-
- Added inferProvider helper to derive provider from model name when provider field is missing
246+
- Added OAuth provider normalization to inferProvider helper: antigravity-oauth maps to "google" (Google Antigravity platform), anthropic-oauth maps to "anthropic"
247+
- Strips -oauth suffix from other OAuth provider names for cleaner display
222248
- Applied provider inference consistently in providerStats, sessionsWithDetails query, and filter logic
223249
- Fixed auth session persistence: users no longer need to sign in again on page refresh (fixes #1)
224250
- Added dedicated CallbackHandler component for OAuth callback processing with 10s timeout

convex/_generated/api.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import type * as analytics from "../analytics.js";
1212
import type * as api_ from "../api.js";
13+
import type * as crons from "../crons.js";
1314
import type * as embeddings from "../embeddings.js";
1415
import type * as evals from "../evals.js";
1516
import type * as http from "../http.js";
@@ -18,6 +19,8 @@ import type * as rag from "../rag.js";
1819
import type * as search from "../search.js";
1920
import type * as sessions from "../sessions.js";
2021
import type * as users from "../users.js";
22+
import type * as wrapped from "../wrapped.js";
23+
import type * as wrappedActions from "../wrappedActions.js";
2124

2225
import type {
2326
ApiFromModules,
@@ -28,6 +31,7 @@ import type {
2831
declare const fullApi: ApiFromModules<{
2932
analytics: typeof analytics;
3033
api: typeof api_;
34+
crons: typeof crons;
3135
embeddings: typeof embeddings;
3236
evals: typeof evals;
3337
http: typeof http;
@@ -36,6 +40,8 @@ declare const fullApi: ApiFromModules<{
3640
search: typeof search;
3741
sessions: typeof sessions;
3842
users: typeof users;
43+
wrapped: typeof wrapped;
44+
wrappedActions: typeof wrappedActions;
3945
}>;
4046

4147
/**

convex/analytics.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,23 @@ function filterBySource(sessions: any[], source?: string) {
88
return sessions.filter((s) => (s.source || "opencode") === source);
99
}
1010

11-
// Helper to infer provider from model name when provider field is missing
12-
// This fixes GitHub issue #2: antigravity-oauth and anthropic-oauth showing as "unknown"
11+
// Helper to infer and normalize provider from session data
12+
// Fixes GitHub issue #2: antigravity-oauth and anthropic-oauth showing as "unknown"
1313
function inferProvider(session: { model?: string; provider?: string }): string {
14-
// Return existing provider if set
15-
if (session.provider) return session.provider;
14+
// Normalize OAuth provider names to user-friendly display names
15+
if (session.provider) {
16+
const provider = session.provider.toLowerCase();
17+
if (provider.includes("anthropic")) return "anthropic";
18+
if (provider.includes("antigravity")) return "google"; // Google Antigravity platform
19+
if (provider.includes("openai")) return "openai";
20+
// Strip -oauth suffix for other OAuth providers
21+
if (provider.endsWith("-oauth")) {
22+
return provider.replace("-oauth", "");
23+
}
24+
return session.provider;
25+
}
1626

27+
// Fall through to model-based inference when provider field is missing
1728
const model = (session.model || "").toLowerCase();
1829

1930
// Anthropic/Claude models

convex/crons.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { cronJobs } from "convex/server";
2+
import { internal } from "./_generated/api";
3+
4+
const crons = cronJobs();
5+
6+
// Generate daily wrapped images at 9:30 AM PT (17:30 UTC)
7+
// PT is UTC-8 (or UTC-7 during DST), so 9:30 AM PT = 17:30 UTC (standard) or 16:30 UTC (DST)
8+
// Using 17:30 UTC as the baseline (standard time)
9+
crons.cron(
10+
"generate daily wrapped",
11+
"30 17 * * *", // 9:30 AM PT (standard time)
12+
internal.wrappedActions.generateAllWrapped,
13+
{}
14+
);
15+
16+
// Clean up expired wrapped records every 6 hours
17+
crons.interval(
18+
"cleanup expired wrapped",
19+
{ hours: 6 },
20+
internal.wrapped.deleteExpired,
21+
{}
22+
);
23+
24+
export default crons;

convex/schema.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,37 @@ export default defineSchema({
154154
})
155155
.index("by_user", ["userId"])
156156
.index("by_user_created", ["userId", "createdAt"]),
157+
158+
// Daily Wrapped images - AI-generated visualization of 24h activity
159+
dailyWrapped: defineTable({
160+
userId: v.id("users"),
161+
date: v.string(), // "2026-01-22" format
162+
designIndex: v.number(), // 0-9 for template rotation
163+
imageStorageId: v.optional(v.id("_storage")), // Generated image
164+
generatedAt: v.number(), // timestamp
165+
expiresAt: v.number(), // timestamp (24h later)
166+
// Snapshot of data at generation time
167+
stats: v.object({
168+
totalTokens: v.number(),
169+
promptTokens: v.number(),
170+
completionTokens: v.number(),
171+
totalMessages: v.number(),
172+
cost: v.number(),
173+
topModels: v.array(
174+
v.object({
175+
model: v.string(),
176+
tokens: v.number(),
177+
})
178+
),
179+
topProviders: v.array(
180+
v.object({
181+
provider: v.string(),
182+
tokens: v.number(),
183+
})
184+
),
185+
}),
186+
})
187+
.index("by_user_date", ["userId", "date"])
188+
.index("by_user", ["userId"])
189+
.index("by_expires", ["expiresAt"]),
157190
});

0 commit comments

Comments
 (0)