feat: initial full project - Chrome extension + Next.js dashboard (Weeks 1–4)#25
Merged
Conversation
Scaffold Next.js 16 app with App Router and Tailwind v4. Add Prisma, Zod, TanStack Query v5, Framer Motion, D3, google-auth-library, jsonwebtoken. Document env vars in .env.example. Closes Week 1 AC 1.
Add docker-compose.yml with Postgres 16 on port 5433 (avoids conflict with host-installed Postgres on 5432). Add User, Session, Event, Track models with cascade deletes and FK indexes. Run first migration (init), applied locally and to Neon. Also fix .env.example exclusion in dashboard/.gitignore that silently dropped the file from the AC 1 PR. Closes Week 1 AC 2.
Set up the AI rulebook before introducing AI-generated code in AC 7:
- CLAUDE.md with type-safety, validation, dashboard structure,
auth, extension isolation, and Prisma rules
- .claude/skills/{api-route,prisma-model,extension-message}.md
with task-specific recipes and code examples
- dashboard/server/ folder placeholder for upcoming business logic
Closes Week 1 AC 6.
* feat(api): add POST /api/v1/events with prisma-zod schemas Adds the first Route Handler: parse -> Zod -> server -> Prisma. Schemas in server/schemas/ derived from prisma-zod-generator output. PrismaClient singleton uses @prisma/adapter-pg (required by Prisma 7). Closes Week 1 AC 7. * feat(api): add CORS for chrome-extension origin server/cors.ts: withCors() wrapper + corsPreflight() helper. Echoes request Origin back if it matches the chrome-extension ID pattern, sends "null" otherwise. POST /api/v1/events wrapped + OPTIONS handler exported. Switch Zod error response to z.treeifyError (z.format deprecated). Closes Week 1 AC 3.
MV3 + TypeScript scaffold under extension/. Background SW, content script (<all_urls>), empty popup, placeholder icons. Loads in Chrome via Load unpacked. See plans/AC4-extension-init.md.
…C 5) (#6) * docs(readme): add full root readme (AC 5) * chore(repo): add license, contributing guide, and PR template * ci: add lint and typecheck workflow GitHub Actions workflow that runs on every pull request and push to development/main. Two jobs: - dashboard: prisma generate (required before tsc — generated client is gitignored), npm run lint, tsc --noEmit - extension: tsc --noEmit * chore(lint): tighten typescript rules Stricter eslint rules in dashboard to enforce CLAUDE.md type-safety guarantees at PR time: - @typescript-eslint/no-explicit-any: error - @typescript-eslint/ban-ts-comment: error - @typescript-eslint/consistent-type-assertions: forbid object-literal casts ({ foo } as T) - no-restricted-syntax: forbid casting to unknown (incl. as unknown as T) Also ignore generated/** (output of prisma generate — autogenerated) and remove the now-redundant eslint-disable-next-line in server/db.ts. * chore(tooling): add husky pre-commit, gitattributes, nvmrc Pre-commit hook runs eslint --fix on staged dashboard files via lint-staged. Catches issues locally before CI; typecheck stays in CI because it needs the whole project and is too slow per-commit. - husky + lint-staged installed in a new root package.json (the repo is a monorepo without a workspace tool) - lint-staged.config.mjs uses npm --prefix dashboard to run eslint from the dashboard cwd so the flat config resolves naturally - .gitattributes normalizes line endings to LF — stops the "LF will be replaced by CRLF" warnings on every commit - .nvmrc pins Node 20 for nvm use / IDE auto-switch * fix(ci): provide dummy DATABASE_URL for prisma generate prisma.config.ts uses prisma/config's strict env() helper, which throws PrismaConfigEnvError if DATABASE_URL is missing at load time. The CI runner has no .env file (gitignored), so prisma generate failed before reading the schema. prisma generate does not connect to the database — it only reads the schema and writes the generated client + zod schemas. A dummy URL satisfies the config loader without touching any real database. Set DATABASE_URL only on the prisma generate step, not on the whole job, so an accidental query elsewhere still fails loudly.
* docs(plans): add week 2 AC 1 auth plan * feat(dashboard): add auth server module and /api/auth/extension route * feat(extension): implement google oauth flow, jwt storage and auth popup
…+3) (#8) * docs(plans): add week 2 AC 2+3 content script and session manager plan * feat(extension): add content script page metadata parser (AC 2) * feat(extension): add session manager with start/stop/timer and storage (AC 3) * docs(plans): update AC 2+3 plan with identified gaps for AC 4 and AC 5
… (Week 2 AC 4) (#9) * docs(plans): add week 2 AC 4 popup UI plan * feat(extension): add session pause/resume to session manager (Gap 1 from AC 3) * feat(extension): implement popup UI with session controls and timer (AC 4) * docs(backlog): add email and password auth plan with smtp verification * feat(extension): add logout button and note save feedback to popup * feat(extension): add dev mode fake auth bypass via VITE_DEV_MODE
* docs(plans): add week 2 AC 5 data sync plan * feat(dashboard): add requireUser JWT helper and protect /api/v1/events Verifies Bearer token via JWT_SECRET, checks session ownership on POST. * feat(dashboard): add POST and PATCH /api/v1/sessions endpoints POST creates a session for the authenticated user. PATCH /:id sets endedAt, scoped to the user's own sessions. * feat(extension): create DB session on SESSION_START with best-effort retry Adds dbSessionId to local Session, fire-and-forget POST /api/v1/sessions on start and PATCH on stop. Failures left for sync alarm to retry. * feat(extension): normalize pending event payload shape PageMetadata flattens to content via metaDescription + headings. Notes get worktrace://note/<uuid> URL so z.url() passes without schema changes. * feat(extension): add batched event sync with alarm and retry Every 30s a chrome.alarms tick flushes up to 50 queued events. Final flush also fires on SESSION_STOP. 4xx drops poison events, 401 stops until re-login, network and 5xx keep the queue for the next tick. * feat(extension): show sync status in popup Adds a SYNC_GET_STATUS message that returns lastSyncedAt, lastError and queueSize. Popup renders a second line under the queue count showing time since last sync or the error message. * chore(dashboard): pin turbopack root to dashboard directory Two lockfiles (root husky/lint-staged and dashboard) made Turbopack infer the repo root as workspace root, watching extension and unrelated paths and slowing dev. Setting turbopack.root scopes the watcher. * feat(extension): show logged-in user email in popup header AUTH_GET_STATUS now returns the email decoded from the JWT payload. Popup renders it under the logo while authenticated, hides on logout.
…nedMs (#11) * docs(plans): add AC6 music capture plan * feat(extension): add TrackInfo types and music message definitions * feat(extension): add YouTube Music content script with MutationObserver * feat(extension): add SoundCloud content script with MutationObserver * feat(extension): update manifest with music content scripts and host_permissions * feat(extension): handle TRACK_CAPTURED in background and save to DB * feat(dashboard): add POST /api/v1/tracks route handler * feat(extension): display now-playing track in popup UI * fix(extension): skip sync flush when unauthenticated to stop retry flood * fix(extension): fix track section visibility and show auth error in popup * fix(extension): add TRACK_REQUEST on-demand pull to fix SW race condition * docs: add local dev checklist to CLAUDE.md * fix(extension): save track to DB on on-demand TRACK_REQUEST pull * feat(extension): show track listening duration timer in popup * docs(plans): update AC6 plan with actual commits and bug log * feat(db): add endedAt to Track model for listen duration analytics * feat(dashboard): add PATCH /api/v1/tracks/:id to record track endedAt * feat(extension): track endedAt per song via currentDbTrackId and PATCH on change or session stop * feat: accumulate listenedMs, pause track timer, gray out music block on pause * fix(extension): detect music pause via .time-info DOM polling on YTM Use ytmusic-player-bar .time-info textContent instead of broken slider aria-valuenow (returned a static value). Popup now freezes the track duration timer when the song is paused. * docs(plans): finalize AC6 plan — mark all commits done, add commit 16 YTM pause fix
* docs(plans): add AC7 privacy blocklist plan * feat(extension): blocklist storage module and background message handlers * feat(extension): filter blocked domains in content scripts before sending * feat(extension): add privacy blocklist section to popup * fix(extension): accordion not closing - isAccordionOpen state + CSS hidden override
… AC8) (#13) * docs(backlog): music track duration display bug — popup-local state lost on reopen * docs(plans): add AC8 parsing modes plan * feat(extension): parsing mode storage module and background message handlers * feat(extension): apply parsing mode in content script metadata collection * feat(extension): add parsing mode segmented control to popup * fix(extension): write page events directly to storage to survive SW sleep * fix(extension): freeze track timer on session pause, restore correctly on popup reopen Closes the music track duration display bug from backlog. Three independent issues, three priorities in the new initDurationFromStorage: P1 — session paused/stopped: freeze at the last snapshot value, ignore any delta. Session pause does not pause the music player, so DOM playbackTime keeps advancing in the YTM/SoundCloud tab — we must not count that. P2 — session active + matching snapshot: compute delta from the snapshot's saved playbackTime to the current DOM playbackTime. The song's own playback position is the source of truth for "did music actually play while popup was closed" — it only advances when music is playing. delta > 0 → add deltaSec*1000 to snapshot.displayDurationMs delta = 0 → music was paused in the player → keep snapshot value delta < 0 → user seeked backward → keep snapshot value (best effort) P3 — fallback: background-accumulated trackListenedMs (correct for session pause path through endCurrentDbTrack, best-effort otherwise). Storage additions: - trackListenedMs: accumulated by background in endCurrentDbTrack on every session pause / track change. Reset to 0 in TRACK_CAPTURED, removed in SESSION_STOP. - popupSnapshot { trackKey, playbackTime, displayDurationMs, capturedAt }: written every popup tick in refreshDuration. Removed in TRACK_CAPTURED and SESSION_STOP so a stale snapshot from a previous track never wins. Popup changes: - applyTrack: drop capturedAt-based initialisation, set needsStorageInit=true on track change so the next refreshTrack reads from storage. - refreshTrack: one-shot await initDurationFromStorage when flag is set. - parsePlaybackTime: handles M:SS and H:MM:SS DOM formats. Known edge cases (acceptable as best-effort, DB listenedMs remains accurate): - Song looped/replayed → playbackTime resets to 0, second play undercounts. - Popup closed longer than song length → playbackTime caps, undercounts.
…eware guard (#14) * docs(plans): add week 3 AC 1 dashboard auth plan * feat(dashboard): add web google login + logout routes and session cookie helper * feat(dashboard): add /login page with google identity services button * feat(dashboard): add middleware guarding /dashboard and placeholder page * fix(dashboard): force english locale for google sign-in button * chore(dashboard): remove default next.js boilerplate and set worktrace metadata
* docs(plans): add week 3 AC 2 event feed plan * style(dashboard): port extension color tokens and add sticky app header * feat(dashboard): allow cookie auth in requireUser and add GET /api/v1/events * feat(dashboard): event feed with infinite-scroll cursor pagination * feat(dashboard): add events-per-day and top-tags charts with aggregated stats endpoint * style(dashboard): add cursor-pointer + hover states on filters, logout, cards + future-date guard * feat(dashboard): top-3 sessions panel with per-host duration breakdown - adds GET /api/v1/sessions/top route + getTopSessionsForUser server module - per-event duration computed via LEAD window function over (sessionId, timestamp) - last event of a session contributes 0s so totals = max(ts) - min(ts), gaps capped at 30 min - top host per session via DISTINCT ON ordered by host duration - renders alongside charts in a 3-col grid; client component prefetched on /dashboard * feat(dashboard): match extension favicon and add seo metadata - adopt extension icon-128.png as app/icon.png + apple-icon.png (Next App Router convention) - drop default favicon.ico - root metadata: title template, description, OG and Twitter cards, theme color - /login and /dashboard get page-specific titles and are excluded from index/follow * style(dashboard): widen content container and vertically center events-per-day chart * docs(plans): add stale-session cleanup to backlog Discovered during Week 3 AC 2 — leftover sessions (endedAt=NULL after browser close / SW sleep) can dominate the TOP 3 panel even when nominally 'active'. Other metrics (byDay, topTags, host durations) compute correctly; only session ranking is affected. Documents 4 mitigation strategies, recommends Vercel Cron route for cleanup. * refactor(dashboard): share getCurrentUser helper and useClientLocalized hook * chore(dashboard): zod-validate api responses, align page size, scope top-sessions to 90d * docs(plans): backlog jwt unification and post-logout cache reset
… sampling, and API key management (#16) * docs(plans): add week 3 AC 3 ai session report plan * feat(dashboard): add UserSetting model and encrypted groq api key endpoints * feat(dashboard): add groq client and report context builder with token-budget sampling * feat(ai): add reports/generate route and /dashboard/reports page with markdown render and download * feat(reports): API key modal portal + fix Groq 413 token budget Lower INPUT_TOKEN_BUDGET 30k→7.5k so context fits Groq free-tier 12k TPM limit; add GroqContextLimitError (413) with user-facing message. Wire up ApiKeyModal (createPortal) with save/clear flow on /dashboard/reports. * fix(ui): chips+actions same row, burger on far right, wider logo gap
…t.js (#17) * docs(plans): add week 3 AC4 music analytics plan * refactor(server): extract resolveRange to server/range.ts * feat(music): server module, types, and GET /api/v1/music/stats route * feat(music): /dashboard/music page with Chart.js charts * feat(nav): add MUSIC to header nav * fix(music): aggregate getTopTracks by artist+title to deduplicate rows * fix(music): correct logic in music queries - getTopArtists: COUNT(DISTINCT title) via $queryRaw (was counting rows, not unique tracks) - getMusicProductivity: GROUP BY artist+title + SUM(listenedMs); endedAt null fallback uses capturedAt+listenedMs duration instead of NOW() - getMusicTotals: totalTracks counts distinct (artist,title) pairs via groupBy (was counting all capture rows)
…cations, rate limit handling (#18) * docs(plans): AC5 error handling plan * feat(server): add typed API error helper and standardize route handlers * feat(dashboard): add toast notification system * fix(reports): handle 429 rate limit with retry time + tighten token budget
…ehide (#19) * docs(plans): stale data cleanup plan — sessions + music timer Moves backlog item to week4, adds stale music timer problem (680min bug), resolves all open questions, switches to GitHub Actions cron. * feat(server): closeStaleSessions and closeStaleTracksForClosedSessions * feat(dashboard): GET /api/cron/close-stale route, protected by CRON_SECRET * chore(ci): GitHub Actions cron to close stale sessions every 15 min * fix(extension): send TRACK_STOP on pagehide to close stale music timers
* docs(plans): move post-week3 tech debt plan to week4 * refactor(server): port issueJWT and requireUser to jose * chore(deps): remove jsonwebtoken and @types/jsonwebtoken * fix(dashboard): clear TanStack cache on logout * chore(plans): drop email-password-auth from backlog, out of scope for Week 4
…README (#21) * docs(plans): add week4 deploy-polish plan * chore(extension): add zip script via PowerShell Compress-Archive * docs(readme): update tech stack, zip instructions, remove stale Week 1 notes * chore(ci): auto GitHub Release with extension zip on merge to main
…tive background (#22) * docs: add framer-motion animations plan * chore: install framer-motion * feat(anim): scroll-reactive timers bg, prism wave, Lenis smooth scroll, page transitions * feat(anim): AI report loading panel + modal spring entrance * feat(anim): event feed stagger entrance + card hover lift * feat(anim): nav indicator layoutId, mobile-nav spring, progress bars animate * feat(anim): toasts slide-in, music count-up stats, login stagger entrance * fix(anim): style report + music metadata as pill badges for readability * feat(anim): charts fade-in on data load via AnimatePresence
* feat(reports): localStorage cache per range, auto-load on switch, createdAt badge * feat(account): delete account flow — animated 2-step modal + DELETE /api/auth/delete-account * fix(reports): scope cache key by normalized user email to prevent cross-account bleed * fix(account): escape key, error display, localStorage cleanup on logout/delete, hydration fix * fix(account): SSR-safe portal via typeof document check, remove dead logout-button.tsx
…ening (#24) * fix(extension): unify JWT decode into single decodeJwtPayload helper * fix(extension): log ensureDbSession failure instead of silent void * fix(extension): extract isTrackBlocked helper, remove blocklist check duplication * fix(extension): exhaustiveness guard on message type dispatcher * feat(extension): make WORKTRACE title a link to the dashboard * fix(dashboard): surface localStorage QuotaExceededError as toast in report cache * fix(dashboard): validate Groq key gsk_ prefix before save * feat(dashboard): add footer with GitHub releases link * fix(extension): guard flush() against concurrent runs to prevent duplicate events
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
First sync of all development work into main. Full WorkTrace product - 24 commits, 173 files.
WorkTrace is a Chrome extension + Next.js dashboard that captures developer session context (visited pages, notes, tags, music) and generates AI-powered productivity reports via Groq LLM.
Extension
Dashboard
Closes