feat(dashboard): Personal productivity dashboard#1
Conversation
|
Caution Review failedFailed to post review comments 📝 WalkthroughWalkthroughThis PR introduces a comprehensive dashboard monorepo structure with documentation, planning documents, and a complete multi-app setup including a Next.js docs site, Nitro API server, and TanStack Start web application with integrated features like TRPC, Convex, PowerSync, and authentication. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes ✨ Finishing Touches
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Pull request overview
This PR introduces a personal productivity dashboard feature with task management capabilities, including streak tracking, badge rewards, and context parking functionality. The implementation provides a comprehensive task management system with local storage support and gamification elements.
Changes:
- Added task management with urgency levels (critical, important, nice-to-have) and status tracking
- Implemented streak tracking and badge system for user motivation
- Created context parking feature for saving work-in-progress state
- Built celebration modal with confetti animation for task completion
Reviewed changes
Copilot reviewed 156 out of 307 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/dashboard/apps/web/src/routes/assistant/-lib/storage/index.ts | Storage adapter initialization and singleton pattern |
| src/dashboard/apps/web/src/routes/assistant/-lib/storage/config.ts | Storage sync configuration constants |
| src/dashboard/apps/web/src/routes/assistant/-hooks/useTaskStore.ts | Task store with CRUD operations and completion tracking |
| src/dashboard/apps/web/src/routes/assistant/-hooks/useStreak.ts | Streak management and badge checking logic |
| src/dashboard/apps/web/src/routes/assistant/-hooks/useContextParking.ts | Context parking modal keyboard shortcut handler |
| src/dashboard/apps/web/src/routes/assistant/-components/TaskForm.tsx | Task creation/editing form with urgency selection |
| src/dashboard/apps/web/src/routes/assistant/-components/TaskCard.tsx | Task display card with status and urgency indicators |
| src/dashboard/apps/web/src/routes/assistant/-components/ContextParkingModal.tsx | Modal for capturing work context |
| src/dashboard/apps/web/src/routes/assistant/-components/CelebrationModal.tsx | Task completion celebration with confetti |
| src/dashboard/apps/web/src/routes/api.sync.upload.ts | PowerSync upload endpoint for timer sync |
| src/dashboard/apps/web/src/lib/timer-sync.server.ts | Server-side timer synchronization logic |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Build dynamic update | ||
| const updates = Object.entries(data) | ||
| .filter(([key]) => key !== 'id') | ||
| .map(([key]) => `${key} = ?`) | ||
| .join(', ') | ||
| const values = Object.entries(data) | ||
| .filter(([key]) => key !== 'id') | ||
| .map(([, value]) => value) | ||
|
|
||
| if (updates && values.length > 0) { | ||
| // Use raw SQL for dynamic updates | ||
| const query = `UPDATE timers SET ${updates} WHERE id = ?` | ||
| await db.sql`${query}` // Note: this won't work with template literals | ||
| // Fallback to individual field updates | ||
| await db.sql` | ||
| UPDATE timers SET updated_at = ${data.updated_at ?? new Date().toISOString()} | ||
| WHERE id = ${data.id} | ||
| ` |
There was a problem hiding this comment.
The dynamic SQL query construction is incorrect. The template literal db.sql cannot interpolate a string variable as a complete query. This will result in SQL injection vulnerability and execution failure. Use the prepared statement pattern with individual field updates or build the query with proper parameter binding.
| // Build dynamic update | |
| const updates = Object.entries(data) | |
| .filter(([key]) => key !== 'id') | |
| .map(([key]) => `${key} = ?`) | |
| .join(', ') | |
| const values = Object.entries(data) | |
| .filter(([key]) => key !== 'id') | |
| .map(([, value]) => value) | |
| if (updates && values.length > 0) { | |
| // Use raw SQL for dynamic updates | |
| const query = `UPDATE timers SET ${updates} WHERE id = ?` | |
| await db.sql`${query}` // Note: this won't work with template literals | |
| // Fallback to individual field updates | |
| await db.sql` | |
| UPDATE timers SET updated_at = ${data.updated_at ?? new Date().toISOString()} | |
| WHERE id = ${data.id} | |
| ` | |
| // Apply updates per allowed field using parameterized queries | |
| const timerData = data as Record<string, unknown> | |
| const id = timerData.id | |
| if (!id) { | |
| break | |
| } | |
| const updatedAt = | |
| (timerData.updated_at as string | undefined) ?? | |
| new Date().toISOString() | |
| for (const [key, value] of Object.entries(timerData)) { | |
| if (key === 'id' || key === 'updated_at') { | |
| continue | |
| } | |
| switch (key) { | |
| case 'name': { | |
| await db.sql` | |
| UPDATE timers | |
| SET name = ${value}, updated_at = ${updatedAt} | |
| WHERE id = ${id} | |
| ` | |
| break | |
| } | |
| case 'timer_type': { | |
| await db.sql` | |
| UPDATE timers | |
| SET timer_type = ${value}, updated_at = ${updatedAt} | |
| WHERE id = ${id} | |
| ` | |
| break | |
| } | |
| case 'is_running': { | |
| await db.sql` | |
| UPDATE timers | |
| SET is_running = ${value}, updated_at = ${updatedAt} | |
| WHERE id = ${id} | |
| ` | |
| break | |
| } | |
| case 'elapsed_time': { | |
| await db.sql` | |
| UPDATE timers | |
| SET elapsed_time = ${value}, updated_at = ${updatedAt} | |
| WHERE id = ${id} | |
| ` | |
| break | |
| } | |
| case 'duration': { | |
| await db.sql` | |
| UPDATE timers | |
| SET duration = ${value}, updated_at = ${updatedAt} | |
| WHERE id = ${id} | |
| ` | |
| break | |
| } | |
| case 'laps': { | |
| await db.sql` | |
| UPDATE timers | |
| SET laps = ${value}, updated_at = ${updatedAt} | |
| WHERE id = ${id} | |
| ` | |
| break | |
| } | |
| case 'user_id': { | |
| await db.sql` | |
| UPDATE timers | |
| SET user_id = ${value}, updated_at = ${updatedAt} | |
| WHERE id = ${id} | |
| ` | |
| break | |
| } | |
| case 'created_at': { | |
| await db.sql` | |
| UPDATE timers | |
| SET created_at = ${value}, updated_at = ${updatedAt} | |
| WHERE id = ${id} | |
| ` | |
| break | |
| } | |
| case 'show_total': { | |
| await db.sql` | |
| UPDATE timers | |
| SET show_total = ${value}, updated_at = ${updatedAt} | |
| WHERE id = ${id} | |
| ` | |
| break | |
| } | |
| case 'first_start_time': { | |
| await db.sql` | |
| UPDATE timers | |
| SET first_start_time = ${value}, updated_at = ${updatedAt} | |
| WHERE id = ${id} | |
| ` | |
| break | |
| } | |
| case 'start_time': { | |
| await db.sql` | |
| UPDATE timers | |
| SET start_time = ${value}, updated_at = ${updatedAt} | |
| WHERE id = ${id} | |
| ` | |
| break | |
| } | |
| case 'pomodoro_settings': { | |
| await db.sql` | |
| UPDATE timers | |
| SET pomodoro_settings = ${value}, updated_at = ${updatedAt} | |
| WHERE id = ${id} | |
| ` | |
| break | |
| } | |
| case 'pomodoro_phase': { | |
| await db.sql` | |
| UPDATE timers | |
| SET pomodoro_phase = ${value}, updated_at = ${updatedAt} | |
| WHERE id = ${id} | |
| ` | |
| break | |
| } | |
| case 'pomodoro_session_count': { | |
| await db.sql` | |
| UPDATE timers | |
| SET pomodoro_session_count = ${value}, updated_at = ${updatedAt} | |
| WHERE id = ${id} | |
| ` | |
| break | |
| } | |
| // Ignore unknown keys to avoid injecting arbitrary column names | |
| default: | |
| break | |
| } |
| const awardedBadges: Badge[] = [] | ||
|
|
||
| for (const badgeType of eligibleBadges) { | ||
| const badge = await adapter.awardBadge(userId, badgeType) | ||
| awardedBadges.push(badge) | ||
| } | ||
|
|
There was a problem hiding this comment.
Badge awarding is done sequentially in a loop with individual awaits. Consider using Promise.all() to award badges concurrently for better performance.
| const awardedBadges: Badge[] = [] | |
| for (const badgeType of eligibleBadges) { | |
| const badge = await adapter.awardBadge(userId, badgeType) | |
| awardedBadges.push(badge) | |
| } | |
| const awardPromises = eligibleBadges.map((badgeType) => | |
| adapter.awardBadge(userId, badgeType), | |
| ) | |
| const awardedBadges = await Promise.all(awardPromises) |
| if (!isEdit) { | ||
| resetForm() | ||
| } | ||
| onOpenChange(false) |
There was a problem hiding this comment.
The form reset logic only executes for new tasks but not edits. This could leave stale data in the form if the modal is reopened for editing a different task. Consider resetting in both cases or in the modal's onOpenChange handler.
| if (!isEdit) { | |
| resetForm() | |
| } | |
| onOpenChange(false) | |
| handleOpenChange(false) |
| // Create particles | ||
| for (let i = 0; i < 150; i++) { |
There was a problem hiding this comment.
Creating 150 confetti particles for every celebration could impact performance on lower-end devices. Consider reducing the particle count or making it configurable based on device capabilities.
| // Create particles | |
| for (let i = 0; i < 150; i++) { | |
| // Determine particle count based on user preferences and viewport | |
| let particleCount = 150 | |
| // Respect reduced motion preference where supported | |
| if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { | |
| particleCount = 0 | |
| } else { | |
| const viewportArea = window.innerWidth * window.innerHeight | |
| // Scale down particles on smaller or lower-area viewports | |
| if (viewportArea < 800 * 600) { | |
| particleCount = 75 | |
| } | |
| } | |
| // Create particles | |
| for (let i = 0; i < particleCount; i++) { |
Summary of ChangesHello @genesiscz, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request establishes the core infrastructure for a new personal productivity dashboard. It sets up a robust monorepo environment with a modern full-stack framework, integrates advanced data synchronization for offline capabilities, and introduces a unique cyberpunk-themed user interface. The changes also include foundational elements for an intelligent AI assistant, aiming to streamline workflows and boost user efficiency by addressing common productivity challenges. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a substantial amount of new code and documentation for a personal productivity dashboard. The changes include scaffolding for a monorepo, setting up a TanStack Start web app, a Nitro server, and numerous design documents for future features. My review focuses on a critical bug in the server-side data synchronization logic, opportunities to improve type safety in the WebSocket handler, an architectural inconsistency in the data layer, and a minor correction in one of the design documents. Overall, this is a comprehensive start to a new feature, and the feedback aims to improve its robustness and maintainability.
| case 'PATCH': | ||
| if (table === 'timers') { | ||
| await db.sql` | ||
| UPDATE timers SET updated_at = ${data.updated_at ?? new Date().toISOString()} | ||
| WHERE id = ${data.id} | ||
| ` | ||
| } | ||
| break | ||
|
|
||
| case 'DELETE': | ||
| if (table === 'timers') { | ||
| await db.sql`DELETE FROM timers WHERE id = ${data.id}` | ||
| } else if (table === 'activity_logs') { | ||
| await db.sql`DELETE FROM activity_logs WHERE id = ${data.id}` | ||
| } | ||
| break | ||
| } | ||
| } |
There was a problem hiding this comment.
The logic for handling PATCH operations is incorrect. The line await db.sql${query}`` will fail at runtime because db.sql is a tagged template literal and cannot accept a string variable for the query. The code then proceeds to the fallback, which only updates the `updated_at` field, meaning `PATCH` requests will not apply the intended changes.
This is a critical bug in the data synchronization logic. A correct implementation needs to dynamically and safely construct the UPDATE statement. Given the limitations of the db.sql helper, you might need to fetch the existing record, merge the changes, and then perform a full UPDATE with all fields.
| ## 14 Core Features | ||
|
|
||
| ### Communication & Knowledge Layer | ||
| 1. **Communication Log** - Snapshot important decisions/messages into searchable dashboard | ||
| 2. **Context Parking Lot** - Quick-save your thoughts when switching tasks, auto-recall later | ||
| 3. **Decision Log** - Formal decision capture with reasoning and date | ||
| 4. **Handoff Compiler** - Auto-generate "here is everything" docs for async handoffs | ||
|
|
||
| ### Deadline & Task Anxiety Layer | ||
| 5. **Deadline Hierarchy** - Classify deadlines: critical/important/nice-to-have with visual urgency | ||
| 6. **Critical Path Visualizer** - Show task dependencies and what blocks shipping | ||
| 7. **Burndown Projection** - Given current velocity, predict deadline hit/miss | ||
| 8. **Auto-Escalation Alerts** - When deadline approaches and progress lags, suggest solutions | ||
|
|
||
| ### Motivation & ADHD Support Layer | ||
| 9. **Completion Celebrations** - Big satisfying feedback when tasks complete | ||
| 10. **Micro-Celebrations** - Celebrate focus sessions, streaks, milestones | ||
| 11. **Achievement Badges** - Unlock badges for consistency and achievement | ||
| 12. **Weekly Review Dashboard** - Progress metrics, trend analysis, AI-generated suggestions | ||
|
|
||
| ### Deep Work & Focus Layer | ||
| 14. **Blocker Detector** - Flag when task is blocked, suggest follow-up actions | ||
| 16. **Energy/Focus Heatmap** - Understand circadian productivity patterns, optimize task timing | ||
| 17. **Distraction Tracker** - Log interruptions, visualize patterns, suggest mitigation |
There was a problem hiding this comment.
| interface Client { | ||
| id: string | ||
| userId?: string | ||
| peer: unknown | ||
| } |
There was a problem hiding this comment.
The peer property in the Client interface is typed as unknown. To improve type safety and avoid unsafe casting later in the file, you can import and use the Peer type from h3.
| interface Client { | |
| id: string | |
| userId?: string | |
| peer: unknown | |
| } | |
| interface Client { | |
| id: string | |
| userId?: string | |
| peer: Peer | |
| } |
| function broadcastToOthers(sender: unknown, message: unknown) { | ||
| const messageStr = JSON.stringify(message) | ||
|
|
||
| for (const client of clients.values()) { | ||
| if (client.peer !== sender) { | ||
| try { | ||
| (client.peer as { send: (msg: string) => void }).send(messageStr) | ||
| } catch (error) { | ||
| console.error(`[WebSocket] Failed to send to client ${client.id}:`, error) | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The sender parameter is typed as unknown, which leads to an unsafe type assertion when calling send. By typing sender as Peer (imported from h3), you can eliminate the cast and make the function more robust.
| function broadcastToOthers(sender: unknown, message: unknown) { | |
| const messageStr = JSON.stringify(message) | |
| for (const client of clients.values()) { | |
| if (client.peer !== sender) { | |
| try { | |
| (client.peer as { send: (msg: string) => void }).send(messageStr) | |
| } catch (error) { | |
| console.error(`[WebSocket] Failed to send to client ${client.id}:`, error) | |
| } | |
| } | |
| } | |
| } | |
| function broadcastToOthers(sender: Peer, message: unknown) { | |
| const messageStr = JSON.stringify(message) | |
| for (const client of clients.values()) { | |
| if (client.peer !== sender) { | |
| try { | |
| client.peer.send(messageStr) | |
| } catch (error) { | |
| console.error(`[WebSocket] Failed to send to client ${client.id}:`, error) | |
| } | |
| } | |
| } | |
| } |
| // TODO: Update field names to match @dashboard/shared types and add pomodoro support | ||
| // Will be used by PowerSyncAdapter in /routes/timer/lib/storage/ | ||
|
|
||
| import { z } from 'zod' | ||
| import { db, APP_SCHEMA } from '../db/powersync' | ||
|
|
||
| /** | ||
| * Timer Schema for validation and type inference | ||
| * | ||
| * This schema defines the structure of timer data as it's used in the app. | ||
| * It handles the transformation from SQLite types (integers for booleans, | ||
| * JSON strings for arrays) to proper TypeScript types. | ||
| */ | ||
| export const timerSchema = z.object({ | ||
| id: z.string(), | ||
| name: z.string(), | ||
| type: z.enum(['stopwatch', 'countdown']), | ||
| is_running: z.number().transform((val) => val > 0), // SQLite int → boolean | ||
| paused_time: z.number(), | ||
| countdown_duration: z.number(), | ||
| laps: z.string().transform((val) => JSON.parse(val || '[]') as number[]), | ||
| user_id: z.string(), | ||
| created_at: z.string().transform((val) => new Date(val)), | ||
| updated_at: z.string().transform((val) => new Date(val)), | ||
| }) | ||
|
|
||
| export type TimerRecord = z.output<typeof timerSchema> | ||
|
|
||
| /** | ||
| * Raw timer type as stored in SQLite | ||
| */ | ||
| export interface TimerRow { | ||
| id: string | ||
| name: string | ||
| type: 'stopwatch' | 'countdown' | ||
| is_running: number | ||
| paused_time: number | ||
| countdown_duration: number | ||
| laps: string | ||
| user_id: string | ||
| created_at: string | ||
| updated_at: string | ||
| } | ||
|
|
||
| /** | ||
| * Timer input type for creating/updating timers | ||
| */ | ||
| export interface TimerInput { | ||
| id?: string | ||
| name: string | ||
| type: 'stopwatch' | 'countdown' | ||
| isRunning?: boolean | ||
| pausedTime?: number | ||
| countdownDuration?: number | ||
| laps?: number[] | ||
| } | ||
|
|
||
| /** | ||
| * Serialize a timer for storage in SQLite | ||
| */ | ||
| export function serializeTimer(input: TimerInput, userId: string): Omit<TimerRow, 'id'> { | ||
| const now = new Date().toISOString() | ||
| return { | ||
| name: input.name, | ||
| type: input.type, | ||
| is_running: input.isRunning ? 1 : 0, | ||
| paused_time: input.pausedTime ?? 0, | ||
| countdown_duration: input.countdownDuration ?? 5 * 60 * 1000, // 5 min default | ||
| laps: JSON.stringify(input.laps ?? []), | ||
| user_id: userId, | ||
| created_at: now, | ||
| updated_at: now, | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Timer Collection Queries | ||
| * | ||
| * These functions provide typed access to the timers table. | ||
| * They use PowerSync's reactive queries for real-time updates. | ||
| */ | ||
|
|
||
| /** | ||
| * Get all timers for a user | ||
| */ | ||
| export async function getTimers(userId: string): Promise<TimerRecord[]> { | ||
| const results = await db.getAll<TimerRow>( | ||
| 'SELECT * FROM timers WHERE user_id = ? ORDER BY created_at DESC', | ||
| [userId] | ||
| ) | ||
| return results.map((row) => timerSchema.parse(row)) | ||
| } | ||
|
|
||
| /** | ||
| * Get a single timer by ID | ||
| */ | ||
| export async function getTimer(id: string): Promise<TimerRecord | null> { | ||
| const result = await db.getOptional<TimerRow>( | ||
| 'SELECT * FROM timers WHERE id = ?', | ||
| [id] | ||
| ) | ||
| return result ? timerSchema.parse(result) : null | ||
| } | ||
|
|
||
| /** | ||
| * Create a new timer | ||
| */ | ||
| export async function createTimer(input: TimerInput, userId: string): Promise<string> { | ||
| const id = input.id ?? crypto.randomUUID() | ||
| const data = serializeTimer(input, userId) | ||
|
|
||
| await db.execute( | ||
| `INSERT INTO timers (id, name, type, is_running, paused_time, countdown_duration, laps, user_id, created_at, updated_at) | ||
| VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, | ||
| [id, data.name, data.type, data.is_running, data.paused_time, data.countdown_duration, data.laps, data.user_id, data.created_at, data.updated_at] | ||
| ) | ||
|
|
||
| return id | ||
| } | ||
|
|
||
| /** | ||
| * Update a timer | ||
| */ | ||
| export async function updateTimer(id: string, updates: Partial<TimerInput>): Promise<void> { | ||
| const setClauses: string[] = [] | ||
| const values: (string | number)[] = [] | ||
|
|
||
| if (updates.name !== undefined) { | ||
| setClauses.push('name = ?') | ||
| values.push(updates.name) | ||
| } | ||
| if (updates.type !== undefined) { | ||
| setClauses.push('type = ?') | ||
| values.push(updates.type) | ||
| } | ||
| if (updates.isRunning !== undefined) { | ||
| setClauses.push('is_running = ?') | ||
| values.push(updates.isRunning ? 1 : 0) | ||
| } | ||
| if (updates.pausedTime !== undefined) { | ||
| setClauses.push('paused_time = ?') | ||
| values.push(updates.pausedTime) | ||
| } | ||
| if (updates.countdownDuration !== undefined) { | ||
| setClauses.push('countdown_duration = ?') | ||
| values.push(updates.countdownDuration) | ||
| } | ||
| if (updates.laps !== undefined) { | ||
| setClauses.push('laps = ?') | ||
| values.push(JSON.stringify(updates.laps)) | ||
| } | ||
|
|
||
| // Always update the timestamp | ||
| setClauses.push('updated_at = ?') | ||
| values.push(new Date().toISOString()) | ||
|
|
||
| values.push(id) | ||
|
|
||
| await db.execute( | ||
| `UPDATE timers SET ${setClauses.join(', ')} WHERE id = ?`, | ||
| values | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Delete a timer | ||
| */ | ||
| export async function deleteTimer(id: string): Promise<void> { | ||
| await db.execute('DELETE FROM timers WHERE id = ?', [id]) | ||
| } | ||
|
|
||
| /** | ||
| * Watch timers for real-time updates | ||
| * Returns a function to stop watching | ||
| */ | ||
| export function watchTimers( | ||
| userId: string, | ||
| callback: (timers: TimerRecord[]) => void | ||
| ): () => void { | ||
| const query = db.watch( | ||
| 'SELECT * FROM timers WHERE user_id = ? ORDER BY created_at DESC', | ||
| [userId], | ||
| { tables: ['timers'] } | ||
| ) | ||
|
|
||
| // Subscribe to changes | ||
| const subscription = query.subscribe({ | ||
| next: (results) => { | ||
| const timers = (results.rows?._array ?? []).map((row: TimerRow) => | ||
| timerSchema.parse(row) | ||
| ) | ||
| callback(timers) | ||
| }, | ||
| error: (err) => { | ||
| console.error('Timer watch error:', err) | ||
| }, | ||
| }) | ||
|
|
||
| // Return unsubscribe function | ||
| return () => subscription.unsubscribe() | ||
| } |
There was a problem hiding this comment.
This file provides a manual implementation for timer data access, which differs from the approach outlined in the project plan (.claude/plans/2026-01-10-Dashboard.md). The plan suggests using @tanstack/powersync-db-collection to create a collection, which would likely reduce boilerplate and ensure a more consistent data access pattern across the application.
Consider refactoring this to use createCollection with powerSyncCollectionOptions to align with the intended architecture and improve maintainability.
67bec34 to
0f54437
Compare
- Fix critical stale cleanup scope bug (Thread #4/#11): project-scoped listing no longer deletes other projects' cache entries - Preserve matchSnippet when merging search results (Thread #19) - Fix rg-only results getting 0 slots when meta returns 20 (Thread #13) - Use rmSync with force flag instead of repetitive try/catch (Thread #1) - Add try/catch fallback for METADATA_VERSION (Thread #3/#6) - Add -- and -F flags to ripgrep commands (Thread #5/#17) - Reuse extractTextFromMessage in rgExtractSnippet (Thread #2) - Add 10MB file size cap + early exit in extraction (Thread #7/#18) - Add error handling for ripgrep functions (Thread #10/#16)
- Fix critical stale cleanup scope bug (Thread #4/#11): project-scoped listing no longer deletes other projects' cache entries - Preserve matchSnippet when merging search results (Thread #19) - Fix rg-only results getting 0 slots when meta returns 20 (Thread #13) - Use rmSync with force flag instead of repetitive try/catch (Thread #1) - Add try/catch fallback for METADATA_VERSION (Thread #3/#6) - Add -- and -F flags to ripgrep commands (Thread #5/#17) - Reuse extractTextFromMessage in rgExtractSnippet (Thread #2) - Add 10MB file size cap + early exit in extraction (Thread #7/#18) - Add error handling for ripgrep functions (Thread #10/#16)
#25) * fix(claude-history): full metadata extraction, rg search, auto-reindex - Read entire JSONL files for metadata (was limited to 50 lines/64KB) - Store full firstPrompt (was truncated to 120 chars) - Index ALL user messages into allUserText field (capped 5000 chars) - Summary/custom-title: latest wins (captures tail of file) - Add ripgrep full-content search fallback (rgSearchFiles/rgExtractSnippet) - Auto-reindex via MD5 hash of lib.ts+cache.ts (METADATA_VERSION) - Clean up stale cache entries for deleted session files - Rename DB from stats-cache.db to index.db - Show match snippets in claude-resume session picker - Search firstPrompt in matchByIdOrName * feat(claude-resume): show conversation excerpts in session picker Adds a second line below each session name showing additional context: summary (if name is from title), first prompt (if name is from summary), or match snippet (if from search). * fix(claude-resume): run metadata + rg search in parallel, merge results Previously rg only ran as fallback when metadata returned 0 results. Now both phases run concurrently via Promise.all. Results are deduped and merged: metadata matches first (ranked by relevance), then rg-only matches with snippets. Catches content in assistant messages and user text past the 5000-char metadata cap. * fix(claude-history): resolve dashed project names, add nerdy stats - extractProjectName: progressively resolve encoded dir against filesystem to recover dashed names (col-fe was showing as "fe") - Cache resolved project names to avoid repeated filesystem lookups - Show detailed stats in session picker: session count, project count, scope, indexed/stale/reindex status * feat(claude-resume): nerdy search diagnostics Show detailed breakdown of where results came from: - Index matches: count + what was searched (name/branch/project/prompt) - Content search: meta hits, rg total hits, overlap, rg-only unique hits * fix(claude-history): address PR #25 review comments - Fix critical stale cleanup scope bug (Thread #4/#11): project-scoped listing no longer deletes other projects' cache entries - Preserve matchSnippet when merging search results (Thread #19) - Fix rg-only results getting 0 slots when meta returns 20 (Thread #13) - Use rmSync with force flag instead of repetitive try/catch (Thread #1) - Add try/catch fallback for METADATA_VERSION (Thread #3/#6) - Add -- and -F flags to ripgrep commands (Thread #5/#17) - Reuse extractTextFromMessage in rgExtractSnippet (Thread #2) - Add 10MB file size cap + early exit in extraction (Thread #7/#18) - Add error handling for ripgrep functions (Thread #10/#16) * fix(claude-history): add cwd to early-exit check, use null for empty allUserText - Include cwd in early-exit condition to avoid skipping cwd extraction - Return null instead of "" for allUserText when no user text found - Anonymize path examples in comments
- Remove unused chalk import from tool-template.md (#1) - Add language identifiers to fenced code blocks in living-docs SKILL.md (#11) - Fix react-compiler bail-out patterns to match documented behavior (#12) - Restore per-file try-catch in collect-files-for-ai to handle permission/disk errors (#13)
…files (#27) * feat(claude-plugin): add new skills, docs template, and archive old configs - Add codebase-analysis skill for deep codebase analysis in forked context - Add living-docs skill for self-maintaining documentation system - Add typescript-error-fixer skill for fixing TS compilation errors and eliminating 'any' types - Extract tool-writing guide from CLAUDE.md into .claude/docs/tool-template.md - Slim down CLAUDE.md "How to Write Tools" section to reference the extracted template - Archive old agents, commands, and skills to .claude/old/ for historical reference * feat(claude-plugin): enhance all skills, bump to v1.0.8, fix collect-files - Bump plugin version to 1.0.8 in marketplace.json and plugin.json - Add genesis-tools: prefix to all skill names (codebase-analysis, react-compiler-debug, living-docs, typescript-error-fixer) - typescript-error-fixer: replace tsc with tsgo --noEmit, add mcp-tsc + LSP tool references - claude-history: add --reindex flag and dashboard reference - github: expand description for code search/library research triggers, add review fix workflow - react-compiler-debug: add common bail-out patterns table - azure-devops: add HAR file analysis workflow and documentation resources section - timelog: add stale mapping validation guidance - codebase-analysis: expand from stub to comprehensive skill with analysis types and tools - living-docs: add context: fork and tooling support section - collect-files-for-ai: exclude deleted files from git diff with --diff-filter=d, skip missing files gracefully * fix(claude-plugin): address PR #27 review feedback - Remove unused chalk import from tool-template.md (#1) - Add language identifiers to fenced code blocks in living-docs SKILL.md (#11) - Fix react-compiler bail-out patterns to match documented behavior (#12) - Restore per-file try-catch in collect-files-for-ai to handle permission/disk errors (#13) * fix(claude-plugin): address all PR #27 review feedback including archived files - react-compiler-debug: rewrite bail-out patterns from React Compiler source code (BuildHIR.ts, ValidateNoJSXInTryStatement.ts) — 9 verified patterns with source refs - archived living-docs: remove trailing "Offline", add text lang to fenced block - archived expo-developer: fix RN version 0.9.0+ -> 0.76+, Reanimated 3 -> 4 - archived frontend-planner: remove hardcoded personal path, use FE_PROJECT_ROOT - archived implement-v2: add text lang identifier to usage fence - archived phpstan-analyzer: fix absolute /scripts path to relative scripts/
- Replace duplicate stripAnsi with import from utils/string (#18) - Fix wrapToWidth ANSI-aware truncation (#1/#3/#22) - Add await to recursive executeTool call (#9/#20) - Guard cursor when filtered list is empty (#5/#19) - Use basename() instead of split("/").pop() (#10/#16) - Validate --width NaN input (#14) - Re-display watch message after screen clear (#15)
- Replace duplicate stripAnsi with import from utils/string (#18) - Fix wrapToWidth ANSI-aware truncation (#1/#3/#22) - Add await to recursive executeTool call (#9/#20) - Guard cursor when filtered list is empty (#5/#19) - Use basename() instead of split("/").pop() (#10/#16) - Validate --width NaN input (#14) - Re-display watch message after screen clear (#15)
* fix: clean error formatting and fuzzy match for tools entry point * feat(tools): add discovery, introspection, and search-select modules - discovery.ts: scans src/ for tools, extracts descriptions from README.md - introspect.ts: runs --help on tools and parses Commander output into structured data - search-select.ts: single-select search prompt with onHighlight callback, modeled on search-multiselect * feat(tools): interactive browser with search, README preview, and subcommand explorer * feat(markdown): add render options and CLI flags (--watch, --width, --theme, --no-color) * fix: improve fuzzy matching to handle trailing dashes and segment matching "tools cli-" now correctly matches "markdown-cli" by stripping trailing dashes and checking dash-separated segments. * feat(markdown-cli): add demo subcommand with interactive template gallery * Revert "feat(markdown-cli): add demo subcommand with interactive template gallery" This reverts commit f5c84db. * feat(markdown): implement theme palette system (dark/light/minimal) Wire up the --theme flag to actual color palettes that change mermaid blocks, table borders, and alert colors per theme. * fix: address PR #28 code review feedback - Replace duplicate stripAnsi with import from utils/string (#18) - Fix wrapToWidth ANSI-aware truncation (#1/#3/#22) - Add await to recursive executeTool call (#9/#20) - Guard cursor when filtered list is empty (#5/#19) - Use basename() instead of split("/").pop() (#10/#16) - Validate --width NaN input (#14) - Re-display watch message after screen clear (#15) * fix(markdown-cli/tools): address PR #28 review feedback - Remove stray n8n.json (unrelated to PR scope) - fix(markdown-cli): reject invalid --theme values via Commander .choices() (Thread #23) - fix(tools): guard discoverTools against missing srcDir with existsSync check (Thread #24) - fix(markdown): use display width for emoji/wide chars in wrapToWidth (Thread #25)
…ety notes - Add @<author-tag> prefix to batch operations example (Threads #1, #6) - Add @<author-tag> prefix to background-agent prompt placeholders (Thread #2) - Add @<author-tag> prefix to --resolve-thread example (Threads #3, #5) - Add prompt-injection safety note to SKILL.md after Step 5 (Thread #8) - Add matching safety note to github-pr.md background-agent section (Thread #9) fix(skills): restrict author tagging to coderabbitai and gemini only Copilot and other known bots don't respond to @mentions, so tagging them is noise. Only @coderabbitai and /gemini actually react to being tagged in PR review replies.
- Replace chalk with picocolors in update tool (Thread #1) - Replace Node spawnSync with Bun.spawnSync, use import.meta.dir (Thread #2, #8) - Fix cancel symbol detection in isCancelled helper (Thread #9) - Simplify validate wrapper in input text mode (Thread #10) - Add newline/carriage-return escaping in AppleScript notifications (Thread #13)
- Replace chalk with picocolors in update tool (Thread #1) - Replace Node spawnSync with Bun.spawnSync, use import.meta.dir (Thread #2, #8) - Fix cancel symbol detection in isCancelled helper (Thread #9) - Simplify validate wrapper in input text mode (Thread #10) - Add newline/carriage-return escaping in AppleScript notifications (Thread #13)
0f54437 to
9327a0c
Compare
9327a0c to
c3c2afd
Compare
There was a problem hiding this comment.
Actionable comments posted: 164
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: b61ad988-e52a-48e3-b787-730c1766932a
⛔ Files ignored due to path filters (31)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsrc/dashboard/apps/docs/app/favicon.icois excluded by!**/*.icosrc/dashboard/apps/docs/public/circles.svgis excluded by!**/*.svgsrc/dashboard/apps/docs/public/next.svgis excluded by!**/*.svgsrc/dashboard/apps/docs/public/turborepo.svgis excluded by!**/*.svgsrc/dashboard/apps/docs/public/vercel.svgis excluded by!**/*.svgsrc/dashboard/apps/web-template/convex/_generated/api.d.tsis excluded by!**/_generated/**src/dashboard/apps/web-template/convex/_generated/api.jsis excluded by!**/_generated/**src/dashboard/apps/web-template/convex/_generated/dataModel.d.tsis excluded by!**/_generated/**src/dashboard/apps/web-template/convex/_generated/server.d.tsis excluded by!**/_generated/**src/dashboard/apps/web-template/convex/_generated/server.jsis excluded by!**/_generated/**src/dashboard/apps/web-template/src/logo.svgis excluded by!**/*.svgsrc/dashboard/apps/web/convex/_generated/api.d.tsis excluded by!**/_generated/**src/dashboard/apps/web/convex/_generated/api.jsis excluded by!**/_generated/**src/dashboard/apps/web/convex/_generated/dataModel.d.tsis excluded by!**/_generated/**src/dashboard/apps/web/convex/_generated/server.d.tsis excluded by!**/_generated/**src/dashboard/apps/web/convex/_generated/server.jsis excluded by!**/_generated/**src/dashboard/apps/web/public/demo-neon.svgis excluded by!**/*.svgsrc/dashboard/apps/web/public/example-guitar-flowers.jpgis excluded by!**/*.jpgsrc/dashboard/apps/web/public/example-guitar-motherboard.jpgis excluded by!**/*.jpgsrc/dashboard/apps/web/public/example-guitar-racing.jpgis excluded by!**/*.jpgsrc/dashboard/apps/web/public/example-guitar-steamer-trunk.jpgis excluded by!**/*.jpgsrc/dashboard/apps/web/public/example-guitar-superhero.jpgis excluded by!**/*.jpgsrc/dashboard/apps/web/public/example-guitar-traveling.jpgis excluded by!**/*.jpgsrc/dashboard/apps/web/public/example-guitar-video-games.jpgis excluded by!**/*.jpgsrc/dashboard/apps/web/public/example-ukelele-tanstack.jpgis excluded by!**/*.jpgsrc/dashboard/apps/web/public/favicon.icois excluded by!**/*.icosrc/dashboard/apps/web/public/logo192.pngis excluded by!**/*.pngsrc/dashboard/apps/web/public/logo512.pngis excluded by!**/*.pngsrc/dashboard/apps/web/public/tanstack-circle-logo.pngis excluded by!**/*.pngsrc/dashboard/apps/web/public/tanstack-word-logo-white.svgis excluded by!**/*.svg
📒 Files selected for processing (269)
.claude/dashboard/README.md.claude/dashboard/architecture.md.claude/dashboard/build.md.claude/dashboard/components.md.claude/dashboard/patterns.md.claude/dashboard/routing.md.claude/dashboard/stack.md.claude/dashboard/state.md.claude/dashboard/structure.md.claude/ideas/assistant/001-assistant-personal-ai-master.md.claude/ideas/assistant/002-assistant-phase1-detailed.md.claude/ideas/assistant/003-assistant-phase2-roadmap.md.claude/ideas/assistant/004-assistant-phase3-roadmap.md.claude/ideas/dashboard/001-sleep-recovery-dashboard.md.claude/ideas/dashboard/002-nutrition-hydration-logger.md.claude/ideas/dashboard/003-mood-energy-tracker.md.claude/ideas/dashboard/004-spending-snapshot-dashboard.md.claude/ideas/dashboard/005-accountability-network.md.claude/ideas/dashboard/006-portfolio-showcase.md.claude/ideas/dashboard/007-skill-leveling-system.md.claude/ideas/dashboard/008-relationship-calendar.md.claude/ideas/dashboard/009-investment-dashboard.md.claude/ideas/dashboard/010-debt-snowball-tracker.md.claude/ideas/dashboard/011-creative-metrics.md.claude/ideas/dashboard/012-event-coordination-hub.md.claude/plans/2026-01-10-Dashboard.md.claude/plans/2026-01-13-badge-progress-ui.md.claude/plans/2026-01-13-blocker-detector.md.claude/plans/2026-01-13-critical-path-visualizer.md.claude/plans/2026-01-13-decision-log.md.claude/plans/2026-01-13-distraction-tracker.md.claude/plans/2026-01-13-energy-heatmap.md.claude/plans/2026-01-13-escalation-alerts.md.claude/plans/2026-01-13-handoff-compiler.md.claude/plans/2026-01-13-kanban-board.md.claude/plans/2026-01-13-micro-celebrations.md.claude/plans/2026-01-13-weekly-review-dashboard.mdCodeExamples.mdUseful.mdfindings.mdprogress.mdsrc/dashboard/.gitignoresrc/dashboard/.npmrcsrc/dashboard/README.mdsrc/dashboard/apps/docs/.gitignoresrc/dashboard/apps/docs/README.mdsrc/dashboard/apps/docs/app/globals.csssrc/dashboard/apps/docs/app/layout.tsxsrc/dashboard/apps/docs/app/page.tsxsrc/dashboard/apps/docs/eslint.config.jssrc/dashboard/apps/docs/next-env.d.tssrc/dashboard/apps/docs/next.config.tssrc/dashboard/apps/docs/package.jsonsrc/dashboard/apps/docs/postcss.config.jssrc/dashboard/apps/docs/tsconfig.jsonsrc/dashboard/apps/server/.env.examplesrc/dashboard/apps/server/.gitignoresrc/dashboard/apps/server/README.mdsrc/dashboard/apps/server/nitro.config.tssrc/dashboard/apps/server/package.jsonsrc/dashboard/apps/server/server/routes/_ws.tssrc/dashboard/apps/server/server/routes/api/health.tssrc/dashboard/apps/server/server/routes/api/sync/upload.tssrc/dashboard/apps/server/server/routes/api/timers/[id].tssrc/dashboard/apps/server/server/routes/api/timers/index.tssrc/dashboard/apps/server/server/routes/api/user.tssrc/dashboard/apps/server/server/routes/index.tssrc/dashboard/apps/server/tsconfig.jsonsrc/dashboard/apps/web-template/.cta.jsonsrc/dashboard/apps/web-template/.cursorrulessrc/dashboard/apps/web-template/.env.examplesrc/dashboard/apps/web-template/.gitignoresrc/dashboard/apps/web-template/.vscode/settings.jsonsrc/dashboard/apps/web-template/README.mdsrc/dashboard/apps/web-template/components.jsonsrc/dashboard/apps/web-template/convex/schema.tssrc/dashboard/apps/web-template/convex/todos.tssrc/dashboard/apps/web-template/convex/tsconfig.jsonsrc/dashboard/apps/web-template/db/init.sqlsrc/dashboard/apps/web-template/neon-vite-plugin.tssrc/dashboard/apps/web-template/src/components/Header.tsxsrc/dashboard/apps/web-template/src/components/demo.FormComponents.tsxsrc/dashboard/apps/web-template/src/components/demo.chat-area.tsxsrc/dashboard/apps/web-template/src/components/demo.messages.tsxsrc/dashboard/apps/web-template/src/components/example-AIAssistant.tsxsrc/dashboard/apps/web-template/src/components/example-GuitarRecommendation.tsxsrc/dashboard/apps/web-template/src/components/ui/button.tsxsrc/dashboard/apps/web-template/src/components/ui/input.tsxsrc/dashboard/apps/web-template/src/components/ui/label.tsxsrc/dashboard/apps/web-template/src/components/ui/select.tsxsrc/dashboard/apps/web-template/src/components/ui/slider.tsxsrc/dashboard/apps/web-template/src/components/ui/switch.tsxsrc/dashboard/apps/web-template/src/components/ui/textarea.tsxsrc/dashboard/apps/web-template/src/components/workos-user.tsxsrc/dashboard/apps/web-template/src/data/demo-table-data.tssrc/dashboard/apps/web-template/src/data/demo.punk-songs.tssrc/dashboard/apps/web-template/src/data/example-guitars.tssrc/dashboard/apps/web-template/src/db-collections/index.tssrc/dashboard/apps/web-template/src/db.tssrc/dashboard/apps/web-template/src/env.tssrc/dashboard/apps/web-template/src/hooks/demo.form-context.tssrc/dashboard/apps/web-template/src/hooks/demo.form.tssrc/dashboard/apps/web-template/src/hooks/demo.useChat.tssrc/dashboard/apps/web-template/src/hooks/useAudioRecorder.tssrc/dashboard/apps/web-template/src/hooks/useTTS.tssrc/dashboard/apps/web-template/src/hooks/useUser.tsxsrc/dashboard/apps/web-template/src/integrations/convex/provider.tsxsrc/dashboard/apps/web-template/src/integrations/tanstack-query/devtools.tsxsrc/dashboard/apps/web-template/src/integrations/tanstack-query/root-provider.tsxsrc/dashboard/apps/web-template/src/integrations/trpc/init.tssrc/dashboard/apps/web-template/src/integrations/trpc/react.tssrc/dashboard/apps/web-template/src/integrations/trpc/router.tssrc/dashboard/apps/web-template/src/integrations/workos/provider.tsxsrc/dashboard/apps/web-template/src/lib/ai-devtools.tsxsrc/dashboard/apps/web-template/src/lib/demo-store-devtools.tsxsrc/dashboard/apps/web-template/src/lib/demo-store.tssrc/dashboard/apps/web-template/src/lib/example.ai-hook.tssrc/dashboard/apps/web-template/src/lib/example.guitar-tools.tssrc/dashboard/apps/web-template/src/lib/model-selection.tssrc/dashboard/apps/web-template/src/lib/utils.tssrc/dashboard/apps/web-template/src/lib/vendor-capabilities.tssrc/dashboard/apps/web-template/src/mcp-todos.tssrc/dashboard/apps/web-template/src/routeTree.gen.tssrc/dashboard/apps/web-template/src/router.tsxsrc/dashboard/apps/web-template/src/routes/__root.tsxsrc/dashboard/apps/web-template/src/routes/api.trpc.$.tsxsrc/dashboard/apps/web-template/src/routes/demo/api.available-providers.tssrc/dashboard/apps/web-template/src/routes/demo/api.image.tssrc/dashboard/apps/web-template/src/routes/demo/api.mcp-todos.tssrc/dashboard/apps/web-template/src/routes/demo/api.names.tssrc/dashboard/apps/web-template/src/routes/demo/api.structured.tssrc/dashboard/apps/web-template/src/routes/demo/api.tanchat.tssrc/dashboard/apps/web-template/src/routes/demo/api.tq-todos.tssrc/dashboard/apps/web-template/src/routes/demo/api.transcription.tssrc/dashboard/apps/web-template/src/routes/demo/api.tts.tssrc/dashboard/apps/web-template/src/routes/demo/convex.tsxsrc/dashboard/apps/web-template/src/routes/demo/db-chat-api.tssrc/dashboard/apps/web-template/src/routes/demo/db-chat.tsxsrc/dashboard/apps/web-template/src/routes/demo/form.address.tsxsrc/dashboard/apps/web-template/src/routes/demo/form.simple.tsxsrc/dashboard/apps/web-template/src/routes/demo/image.tsxsrc/dashboard/apps/web-template/src/routes/demo/mcp-todos.tsxsrc/dashboard/apps/web-template/src/routes/demo/neon.tsxsrc/dashboard/apps/web-template/src/routes/demo/start.api-request.tsxsrc/dashboard/apps/web-template/src/routes/demo/start.server-funcs.tsxsrc/dashboard/apps/web-template/src/routes/demo/start.ssr.data-only.tsxsrc/dashboard/apps/web-template/src/routes/demo/start.ssr.full-ssr.tsxsrc/dashboard/apps/web-template/src/routes/demo/start.ssr.index.tsxsrc/dashboard/apps/web-template/src/routes/demo/start.ssr.spa-mode.tsxsrc/dashboard/apps/web-template/src/routes/demo/store.tsxsrc/dashboard/apps/web-template/src/routes/demo/structured.tsxsrc/dashboard/apps/web-template/src/routes/demo/table.tsxsrc/dashboard/apps/web-template/src/routes/demo/tanchat.csssrc/dashboard/apps/web-template/src/routes/demo/tanchat.tsxsrc/dashboard/apps/web-template/src/routes/demo/tanstack-query.tsxsrc/dashboard/apps/web-template/src/routes/demo/trpc-todo.tsxsrc/dashboard/apps/web-template/src/routes/demo/workos.tsxsrc/dashboard/apps/web-template/src/routes/example.guitars/$guitarId.tsxsrc/dashboard/apps/web-template/src/routes/example.guitars/index.tsxsrc/dashboard/apps/web-template/src/routes/index.tsxsrc/dashboard/apps/web-template/src/routes/mcp.tssrc/dashboard/apps/web-template/src/styles.csssrc/dashboard/apps/web-template/src/utils/mcp-handler.tssrc/dashboard/apps/web/.claude/CLAUDE.mdsrc/dashboard/apps/web/.claude/docs/patterns/type-sharing.mdsrc/dashboard/apps/web/.claude/docs/systems/database.mdsrc/dashboard/apps/web/.claude/docs/systems/events.mdsrc/dashboard/apps/web/.cta.jsonsrc/dashboard/apps/web/.cursorrulessrc/dashboard/apps/web/.env.examplesrc/dashboard/apps/web/.gitignoresrc/dashboard/apps/web/.vscode/settings.jsonsrc/dashboard/apps/web/README.mdsrc/dashboard/apps/web/components.jsonsrc/dashboard/apps/web/convex/schema.tssrc/dashboard/apps/web/convex/todos.tssrc/dashboard/apps/web/convex/tsconfig.jsonsrc/dashboard/apps/web/db/init.sqlsrc/dashboard/apps/web/drizzle.config.tssrc/dashboard/apps/web/neon-vite-plugin.tssrc/dashboard/apps/web/package.jsonsrc/dashboard/apps/web/public/manifest.jsonsrc/dashboard/apps/web/public/robots.txtsrc/dashboard/apps/web/server/routes/_ws-todo.tssrc/dashboard/apps/web/server/routes/api/-events.get.ts.oldsrc/dashboard/apps/web/src/__tests__/api.test.tssrc/dashboard/apps/web/src/__tests__/drizzle.test.tssrc/dashboard/apps/web/src/__tests__/events.test.tssrc/dashboard/apps/web/src/components/auth/AuthLayout.tsxsrc/dashboard/apps/web/src/components/auth/cyberpunk.csssrc/dashboard/apps/web/src/components/auth/index.tssrc/dashboard/apps/web/src/components/dashboard/app-sidebar.tsxsrc/dashboard/apps/web/src/components/dashboard/dashboard-layout.tsxsrc/dashboard/apps/web/src/components/dashboard/index.tssrc/dashboard/apps/web/src/components/ui/avatar.tsxsrc/dashboard/apps/web/src/components/ui/badge.tsxsrc/dashboard/apps/web/src/components/ui/button.tsxsrc/dashboard/apps/web/src/components/ui/card.tsxsrc/dashboard/apps/web/src/components/ui/dialog.tsxsrc/dashboard/apps/web/src/components/ui/dropdown-menu.tsxsrc/dashboard/apps/web/src/components/ui/feature-card.tsxsrc/dashboard/apps/web/src/components/ui/input.tsxsrc/dashboard/apps/web/src/components/ui/label.tsxsrc/dashboard/apps/web/src/components/ui/scroll-area.tsxsrc/dashboard/apps/web/src/components/ui/select.tsxsrc/dashboard/apps/web/src/components/ui/separator.tsxsrc/dashboard/apps/web/src/components/ui/sheet.tsxsrc/dashboard/apps/web/src/components/ui/sidebar.tsxsrc/dashboard/apps/web/src/components/ui/skeleton.tsxsrc/dashboard/apps/web/src/components/ui/slider.tsxsrc/dashboard/apps/web/src/components/ui/switch.tsxsrc/dashboard/apps/web/src/components/ui/textarea.tsxsrc/dashboard/apps/web/src/components/ui/tooltip.tsxsrc/dashboard/apps/web/src/components/workos-user.tsxsrc/dashboard/apps/web/src/db-collections/index.tssrc/dashboard/apps/web/src/db-collections/timers.tssrc/dashboard/apps/web/src/db.tssrc/dashboard/apps/web/src/drizzle/index.tssrc/dashboard/apps/web/src/drizzle/migrations/0000_slim_masque.sqlsrc/dashboard/apps/web/src/drizzle/migrations/meta/0000_snapshot.jsonsrc/dashboard/apps/web/src/drizzle/migrations/meta/_journal.jsonsrc/dashboard/apps/web/src/drizzle/migrations/relations.tssrc/dashboard/apps/web/src/drizzle/migrations/schema.tssrc/dashboard/apps/web/src/drizzle/schema.tssrc/dashboard/apps/web/src/env.tssrc/dashboard/apps/web/src/integrations/convex/provider.tsxsrc/dashboard/apps/web/src/integrations/tanstack-query/devtools.tsxsrc/dashboard/apps/web/src/integrations/tanstack-query/root-provider.tsxsrc/dashboard/apps/web/src/integrations/trpc/init.tssrc/dashboard/apps/web/src/integrations/trpc/react.tssrc/dashboard/apps/web/src/integrations/trpc/router.tssrc/dashboard/apps/web/src/integrations/workos/provider.tsxsrc/dashboard/apps/web/src/lib/ai-example/AIAssistant.tsxsrc/dashboard/apps/web/src/lib/ai-example/ai-devtools.tsxsrc/dashboard/apps/web/src/lib/ai-example/ai-hook.tssrc/dashboard/apps/web/src/lib/ai-example/guitar-tools.tssrc/dashboard/apps/web/src/lib/ai-example/index.tssrc/dashboard/apps/web/src/lib/ai-example/model-selection.tssrc/dashboard/apps/web/src/lib/ai-example/vendor-capabilities.tssrc/dashboard/apps/web/src/lib/assistant/assistant.server.tssrc/dashboard/apps/web/src/lib/assistant/components/CelebrationModal.tsxsrc/dashboard/apps/web/src/lib/assistant/components/ContextParkingModal.tsxsrc/dashboard/apps/web/src/lib/assistant/components/TaskCard.tsxsrc/dashboard/apps/web/src/lib/assistant/components/TaskForm.tsxsrc/dashboard/apps/web/src/lib/assistant/components/index.tssrc/dashboard/apps/web/src/lib/assistant/hooks/index.tssrc/dashboard/apps/web/src/lib/assistant/hooks/useAssistantQueries.tssrc/dashboard/apps/web/src/lib/assistant/hooks/useBadgeProgress.tssrc/dashboard/apps/web/src/lib/assistant/hooks/useBlockers.tssrc/dashboard/apps/web/src/lib/assistant/hooks/useCelebrations.tssrc/dashboard/apps/web/src/lib/assistant/hooks/useCommunicationLog.tssrc/dashboard/apps/web/src/lib/assistant/hooks/useContextParking.tssrc/dashboard/apps/web/src/lib/assistant/hooks/useDeadlineRisk.tssrc/dashboard/apps/web/src/lib/assistant/hooks/useDecisionLog.tssrc/dashboard/apps/web/src/lib/assistant/hooks/useDistractions.tssrc/dashboard/apps/web/src/lib/assistant/hooks/useEnergyData.tssrc/dashboard/apps/web/src/lib/assistant/hooks/useHandoff.tssrc/dashboard/apps/web/src/lib/assistant/hooks/useStreak.tssrc/dashboard/apps/web/src/lib/assistant/hooks/useTaskStore.tssrc/dashboard/apps/web/src/lib/assistant/hooks/useWeeklyReview.tssrc/dashboard/apps/web/src/lib/assistant/lib/storage/config.tssrc/dashboard/apps/web/src/lib/assistant/lib/storage/index.tssrc/dashboard/apps/web/src/lib/assistant/lib/storage/localstorage-adapter.tssrc/dashboard/apps/web/src/lib/assistant/lib/storage/types.tssrc/dashboard/apps/web/src/lib/assistant/types.tssrc/dashboard/apps/web/src/lib/auth-actions.tssrc/dashboard/apps/web/src/lib/auth-server.tssrc/dashboard/apps/web/src/lib/db/clear-powersync.tssrc/dashboard/apps/web/src/lib/db/index.ts
| currentPrice: number | ||
| currentValue: number // quantity * currentPrice | ||
| totalCost: number // total amount paid for this investment | ||
| gains: number // currentValue - totalCost | ||
| gainPercent: number // (gains / totalCost) * 100 |
There was a problem hiding this comment.
Avoid making derived valuation fields the primary source of truth.
currentValue, gains, gainPercent, totalValue, totalGains, totalGainPercent, and allocation are all derivable from positions + prices. Storing them directly on the core entities will drift unless you define projection/materialization semantics and recomputation triggers. At minimum, the spec should say whether these are computed-on-read, cached projections, or snapshot-only fields.
Also applies to: 109-114
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.claude/ideas/dashboard/009-investment-dashboard.md around lines 79 - 83,
The schema currently stores derived valuation fields (currentValue, gains,
gainPercent, totalValue, totalGains, totalGainPercent, allocation) which will
drift from positions+prices; update the spec to either (A) remove these from
core entities and make them computed-on-read (document as computed properties
derived from positions and currentPrice), or (B) keep them as cached projections
but document explicit materialization semantics (when and how
recomputation/invalidation happens, who triggers it, and what projection
job/process updates fields like currentValue, gains, gainPercent), or (C) mark
them as snapshot-only with explanation of snapshot lifecycle; reference the
field names above and add the same semantics for the related group at lines
109-114 so all derived fields have a clear computed vs cached vs snapshot
contract.
| lastUpdated: DateTime | ||
|
|
||
| // Purchase history for cost basis | ||
| purchases: Array<{ | ||
| date: DateTime | ||
| quantity: number | ||
| pricePerUnit: number | ||
| totalCost: number | ||
| }> | ||
|
|
||
| // Income | ||
| dividendIncome?: number // annual or cumulative | ||
| dividendPaymentDates?: DateTime[] // When dividends paid | ||
| otherIncome?: number // Real estate rental, staking, etc. | ||
|
|
||
| notes?: string | ||
| createdAt: DateTime | ||
| updatedAt: DateTime |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
find . -name "009-investment-dashboard.md" -type fRepository: genesiscz/GenesisTools
Length of output: 119
🏁 Script executed:
cat -n .claude/ideas/dashboard/009-investment-dashboard.md | sed -n '80,140p'Repository: genesiscz/GenesisTools
Length of output: 1964
🌐 Web query:
TypeScript built-in DateTime type vs Date alternatives
💡 Result:
Is there a built-in TypeScript DateTime type?
No. TypeScript’s “built-in” date/time type is just JavaScript’s runtime Date object (Date), and JS itself doesn’t have a separate DateTime data type beyond Date. [1], [2]
What to use instead (common options)
1) Native Date (built-in)
Use when you just need an instant/timestamp and basic interop with JS/JSON APIs. Be aware Date mixes concerns (UTC instant + local-time rendering) and is easy to misuse around time zones/DST. [1], [2]
2) Temporal (the modern direction)
TC39’s Temporal proposal provides purpose-built types (e.g., “plain date”, “zoned date-time”, “instant”). It’s Stage 3 and commonly used today via polyfill. [3], [4]
- Polyfill:
@js-temporal/polyfill[4]
3) Library “DateTime” types (if you want an object model now)
- Luxon:
DateTimeobject, immutable API; good ergonomics for zones/formatting. [5] - Day.js: small, Moment-like API; immutable core. [6]
- js-joda: Java
java.time-style API (strong conceptual model; immutable). [7]
4) Functional utilities over Date
- date-fns: functional, modular, immutable-style helpers that operate on native
Date(you keep usingDate, but with safer utilities). [8]
5) Avoid starting new projects on Moment
Moment is in “maintenance mode / legacy” status (fine to keep, not ideal to start new). [9]
Practical rule of thumb
- Need “just a timestamp/instant” →
Date(orTemporal.Instantif you can). [1], [3] - Need “date-only / time-only / local scheduling / time zones” → prefer
Temporal(polyfill today), otherwise Luxon or js-joda. [3], [4], [5], [7] - Want small utilities and keep native
Date→ date-fns. [8]
Sources: MDN Date [1], MDN date/time guide [2], TC39 Temporal [3], @js-temporal/polyfill [4], Luxon [5], Day.js [6], js-joda [7], date-fns [8], Moment status note [9].
Replace DateTime with a concrete temporal type in this design specification.
DateTime is not a built-in TypeScript type. Specify one of: Date, an ISO-8601 string, Temporal.Instant, or a library type like Luxon's DateTime. Without this specificity, implementation teams across different services will make incompatible choices, leading to model inconsistencies. This appears throughout the specification (lines 84–101, 114–133).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.claude/ideas/dashboard/009-investment-dashboard.md around lines 84 - 101,
Replace the non-existent `DateTime` type with a concrete temporal type across
the spec — pick one consistent format (e.g., ISO-8601 `string`) and update all
occurrences: `lastUpdated`, `purchases.date`, `dividendPaymentDates[]`,
`createdAt`, `updatedAt` (and any other `DateTime` uses around lines 114–133) to
that chosen type; ensure collection types reflect the chosen format (e.g.,
`string[]` for payment dates) and add a short comment noting the expected format
(ISO-8601) so implementers use the same representation across services.
| // Purchase history for cost basis | ||
| purchases: Array<{ | ||
| date: DateTime | ||
| quantity: number | ||
| pricePerUnit: number | ||
| totalCost: number | ||
| }> |
There was a problem hiding this comment.
Model transactions, not only purchases.
A buy-only history cannot represent sells, transfers, fees, splits, or DRIP correctly, so quantity, cost basis, and gains become wrong as soon as a user disposes of part of a position. This needs a transaction ledger in the spec before implementation starts.
Proposed spec change
- // Purchase history for cost basis
- purchases: Array<{
- date: DateTime
- quantity: number
- pricePerUnit: number
- totalCost: number
- }>
+ // Transaction ledger for cost basis and performance
+ transactions: Array<{
+ type: "buy" | "sell" | "dividend" | "fee" | "split" | "transfer_in" | "transfer_out"
+ date: DateTime
+ quantity?: number
+ pricePerUnit?: number
+ amount?: number
+ notes?: string
+ }>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Purchase history for cost basis | |
| purchases: Array<{ | |
| date: DateTime | |
| quantity: number | |
| pricePerUnit: number | |
| totalCost: number | |
| }> | |
| // Transaction ledger for cost basis and performance | |
| transactions: Array<{ | |
| type: "buy" | "sell" | "dividend" | "fee" | "split" | "transfer_in" | "transfer_out" | |
| date: DateTime | |
| quantity?: number | |
| pricePerUnit?: number | |
| amount?: number | |
| notes?: string | |
| }> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.claude/ideas/dashboard/009-investment-dashboard.md around lines 86 - 92,
The current purchases: Array<{ date: DateTime; quantity: number; pricePerUnit:
number; totalCost: number }> only models buys and must be replaced by a generic
transactions ledger: change the field to transactions: Array<TransactionEntry>
and define TransactionEntry to include at minimum { id: string, date: DateTime,
type: 'buy' | 'sell' | 'transfer-in' | 'transfer-out' | 'fee' | 'split' | 'drip'
| 'dividend', quantity: number (signed; positive for inflows, negative for
outflows), pricePerUnit?: number | null, totalAmount?: number, fee?: number,
relatedTransactionId?: string, memo?: string } so that sells use negative
quantities, splits/drips adjust quantity with a ratio or special fields, fees
and transfers can be represented without pricePerUnit, and transactions can be
reconciled for correct cost-basis and gains calculations.
| - API integration for real-time price data (Alpha Vantage, CoinGecko, etc.) | ||
| - Portfolio calculations (weighted average cost basis) | ||
| - Performance charting (multiple time periods) | ||
| - Daily snapshots for historical tracking | ||
| - Tax lot tracking (for tax reporting) | ||
| - Optional: import from CSV or Robinhood/Fidelity APIs |
There was a problem hiding this comment.
Clarify the secret-handling policy for price-provider integrations.
The doc proposes third-party market-data APIs, but “No API keys or credentials stored” is too broad and can be read as “put provider keys in the client.” Reword this to distinguish user credentials from server-managed provider secrets.
Suggested wording
-- No API keys or credentials stored
+- No user brokerage credentials stored
+- Provider API secrets, if required, are kept server-side in managed secret storage and never exposed to clientsAlso applies to: 265-270
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.claude/ideas/dashboard/009-investment-dashboard.md around lines 250 - 255,
Replace the ambiguous sentence "No API keys or credentials stored" with a
precise policy that distinguishes user-provided credentials from server-managed
provider secrets: state that user account credentials or OAuth tokens must never
be stored in client/browser storage and should be stored only server-side when
necessary with user consent, and that third‑party provider API keys (e.g., for
Alpha Vantage/CoinGecko) are permitted to be held and rotated securely by the
server (environment variables or a secrets manager) and accessed via server-side
proxy endpoints rather than exposed to clients; update the two occurrences (the
existing line and the similar block at the later section) to use this clarified
wording and add a short note recommending token exchange/OAuth flows and use of
a server-side proxy.
| - Click to expand details | ||
|
|
||
| #### `SupersededChain.tsx` | ||
| - Shows decision evolution chain (decision -> superseded by -> etc) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Minor grammar convention: "etc." vs "etc"
In American English, the abbreviation typically includes a period: "etc." The current line reads "decision -> superseded by -> etc" and could be updated to "decision -> superseded by -> etc." for consistency with formal conventions.
📝 Suggested correction
-Shows decision evolution chain (decision -> superseded by -> etc)
+Shows decision evolution chain (decision -> superseded by -> etc.)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - Shows decision evolution chain (decision -> superseded by -> etc) | |
| - Shows decision evolution chain (decision -> superseded by -> etc.) |
🧰 Tools
🪛 LanguageTool
[style] ~31-~31: In American English, abbreviations like “etc.” require a period.
Context: ...ion chain (decision -> superseded by -> etc) - Arrow connectors between cards - Com...
(ETC_PERIOD)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.claude/plans/2026-01-13-decision-log.md at line 31, Update the phrase in
the decision log that reads "Shows decision evolution chain (decision ->
superseded by -> etc)" to use the American English abbreviation with a period:
change "etc" to "etc." so the line becomes "Shows decision evolution chain
(decision -> superseded by -> etc.)".
| useEffect(() => { | ||
| const client = getEventClient() | ||
| client.connect(userId, [`timer:${userId}`]) | ||
|
|
||
| const unsubscribe = client.subscribe(`timer:${userId}`, () => { | ||
| // Invalidate queries to refetch fresh data | ||
| queryClient.invalidateQueries({ queryKey: ['timers'] }) | ||
| }) | ||
|
|
||
| return () => { | ||
| unsubscribe() | ||
| client.disconnect() | ||
| } | ||
| }, [userId, queryClient]) |
There was a problem hiding this comment.
Do not disconnect the singleton client from component cleanup.
This example contradicts the singleton guidance below. If multiple components subscribe, one unmount will tear down the shared SSE connection for all of them.
Proposed doc fix
useEffect(() => {
const client = getEventClient()
client.connect(userId, [`timer:${userId}`])
const unsubscribe = client.subscribe(`timer:${userId}`, () => {
// Invalidate queries to refetch fresh data
queryClient.invalidateQueries({ queryKey: ['timers'] })
})
return () => {
unsubscribe()
- client.disconnect()
}
}, [userId, queryClient])📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| const client = getEventClient() | |
| client.connect(userId, [`timer:${userId}`]) | |
| const unsubscribe = client.subscribe(`timer:${userId}`, () => { | |
| // Invalidate queries to refetch fresh data | |
| queryClient.invalidateQueries({ queryKey: ['timers'] }) | |
| }) | |
| return () => { | |
| unsubscribe() | |
| client.disconnect() | |
| } | |
| }, [userId, queryClient]) | |
| useEffect(() => { | |
| const client = getEventClient() | |
| client.connect(userId, [`timer:${userId}`]) | |
| const unsubscribe = client.subscribe(`timer:${userId}`, () => { | |
| // Invalidate queries to refetch fresh data | |
| queryClient.invalidateQueries({ queryKey: ['timers'] }) | |
| }) | |
| return () => { | |
| unsubscribe() | |
| } | |
| }, [userId, queryClient]) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/dashboard/apps/web/.claude/docs/systems/events.md` around lines 100 -
113, The cleanup is incorrectly disconnecting the singleton SSE client; update
the effect in which getEventClient() is used so the return cleanup only calls
the subscription's unsubscribe and does NOT call client.disconnect(), leaving
client.connect()/client.disconnect() lifecycle to the singleton manager;
reference getEventClient(), client.connect(...), client.subscribe(...),
unsubscribe and remove client.disconnect() from the effect cleanup (or delegate
disconnect to the singleton's own teardown method) so unmounting one component
doesn't tear down the shared connection.
| | Param | Required | Description | | ||
| | ---------- | -------- | ---------------------------------------- | | ||
| | `userId` | Yes | User ID for authentication | | ||
| | `channels` | No | Comma-separated list of channels | | ||
|
|
||
| ### Request | ||
|
|
||
| ``` | ||
| GET /api/events?userId=user123&channels=timer:user123,notification:user123 |
There was a problem hiding this comment.
Do not document userId query params as authentication.
A client-supplied userId in the URL is forgeable and leaks into logs/history. The contract should say authentication comes from the session/token, and any userId used for channel selection must be derived or strictly validated server-side. Otherwise this docs page is teaching an impersonation bug.
Run this read-only check to confirm how src/routes/api.events.ts authenticates the caller and whether it trusts the query-string userId:
#!/bin/bash
set -euo pipefail
fd 'api\.events\.ts$' . -x sh -c '
echo "== {} =="
sed -n "1,240p" "{}"
'
echo
echo "== auth/session references around the endpoint =="
rg -n -C3 --type=ts --type=tsx \
"(auth|session|getUser|requireUser|userId|channels|EventSource|SSE|event stream)" \
src/dashboard/apps/webExpected result: the endpoint derives identity from auth/session context and does not treat req.query.userId as the source of truth.
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 128-128: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/dashboard/apps/web/.claude/docs/systems/events.md` around lines 121 -
129, Remove the statement that treats the query param `userId` as authentication
and instead document that authentication is derived from the session/token
(e.g., via server auth helpers such as getUser/requireUser) and not from
`req.query.userId`; update the examples to omit `userId` from the query string,
explain that any user-id used for channel selection must be derived from the
authenticated session or strictly validated server-side (do not trust
`req.query.userId`), and call out that `channels` values should be
validated/normalized on the server before use in the EventSource/SSE handler.
| @@ -0,0 +1,4 @@ | |||
| # These will be automatically created by Neon Launchpad (https://neon.new). | |||
| # You will also need to | |||
There was a problem hiding this comment.
Finish or remove the incomplete setup note.
Line 2 ends mid-sentence, so the example file leaves setup ambiguous for first-time users. Either complete the instruction or delete it.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/dashboard/apps/web/.env.example` at line 2, The comment line "# You will
also need to" in the .env example is an incomplete setup note; either finish the
sentence with the required action or remove the line entirely—update the
.env.example by replacing that line with a complete instruction specifying what
else is needed (e.g., required environment variables or setup steps) or delete
the line if no additional guidance is necessary.
| VITE_DATABASE_URL= | ||
| VITE_DATABASE_URL_POOLER= |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Vite (and TanStack Start/Vinxi apps that use Vite conventions), are environment variables prefixed with VITE_ exposed to client-side code? Please cite the official docs.
💡 Result:
Yes.
- Vite: Env vars are exposed to client code via
import.meta.env, and “only variables prefixed withVITE_are exposed to your Vite-processed code” (e.g.VITE_SOME_KEYis available,DB_PASSWORDis not). [1] - TanStack Start (Vinxi/Vite conventions): “Client code can only access variables with the
VITE_prefix” (accessed asimport.meta.env.VITE_*). [2]
Sources (official docs):
[1] Vite – Env Variables and Modes (v4.vitejs.dev)
[2] TanStack Start – Environment Variables (tanstack.com)
Citations:
- 1: https://v4.vitejs.dev/guide/env-and-mode.html?utm_source=openai
- 2: https://tanstack.com/start/v0/docs/framework/react/environment-variables?utm_source=openai
🏁 Script executed:
cat -n src/dashboard/apps/web/.env.example 2>/dev/null || echo "File not found"Repository: genesiscz/GenesisTools
Length of output: 239
Do not expose database connection strings through VITE_ env vars.
VITE_ variables are exposed to client-side code in Vite and TanStack Start applications. These database URLs would be embedded in the browser bundle, compromising database security. Move these to server-only env vars (DATABASE_URL and DATABASE_URL_POOLER) and access them only from server code.
Proposed fix
- VITE_DATABASE_URL=
- VITE_DATABASE_URL_POOLER=
+ DATABASE_URL=
+ DATABASE_URL_POOLER=📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| VITE_DATABASE_URL= | |
| VITE_DATABASE_URL_POOLER= | |
| DATABASE_URL= | |
| DATABASE_URL_POOLER= |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/dashboard/apps/web/.env.example` around lines 3 - 4, The env example
exposes database connection strings as VITE_DATABASE_URL and
VITE_DATABASE_URL_POOLER which makes them available to client bundles; rename
these to server-only keys DATABASE_URL and DATABASE_URL_POOLER in the .env
example and remove any VITE_ variants; then update any usages in server-side
code to read process.env.DATABASE_URL / process.env.DATABASE_URL_POOLER (and
remove or change any import.meta.env.VITE_DATABASE_URL or
VITE_DATABASE_URL_POOLER references in client code) so database URLs are only
accessed from server runtime.
| GenesisTools currently uses **minimist** for argument parsing. Both yargs and @cliffy/command could be considered for future enhancements: | ||
|
|
||
| - **Subcommands**: Define hierarchical command structures with `.command()` | ||
| - **Options**: Strongly typed options with `.option()` and automatic help generation | ||
| - **Action handlers**: Clean separation of command logic with `.action()` | ||
| - **yargs**: Better for complex Node.js/Bun CLI tools with many options and commands | ||
| - **@cliffy/command**: Better for Deno-based tools that need type safety and built-in validation | ||
|
|
||
| For interactive prompts, GenesisTools uses **@inquirer/prompts** which provides: | ||
|
|
||
| - **Modern API**: Promise-based with individual prompt functions (`select`, `input`, `confirm`, etc.) | ||
| - **Type safety**: Built-in TypeScript support | ||
| - **Cancellation handling**: `ExitPromptError` from `@inquirer/core` for clean user cancellation | ||
| For GenesisTools' Bun-based environment, **yargs** would be the more natural choice if migration were considered. |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check which CLI argument parser GenesisTools uses
# Check package.json for dependencies
echo "=== Checking package.json dependencies ==="
cat package.json | jq '.dependencies, .devDependencies' 2>/dev/null | rg -i 'minimist|commander|yargs'
# Check for imports in source files
echo -e "\n=== Checking imports in src/ ==="
rg -n --type=ts "from ['\"](?:minimist|commander|yargs)" src/ -g '!node_modules' || echo "No matches found"
# Check for require statements
echo -e "\n=== Checking require statements ==="
rg -n --type=ts "require\(['\"](?:minimist|commander|yargs)" src/ -g '!node_modules' || echo "No matches found"Repository: genesiscz/GenesisTools
Length of output: 11100
Update documentation to reflect actual argument parser in use.
The documentation incorrectly states that GenesisTools uses minimist. The codebase actually uses commander for parsing command-line arguments with subcommands and options, as evidenced by extensive imports throughout src/ and package.json dependencies.
Documentation fix
-GenesisTools currently uses **minimist** for argument parsing. Both yargs and `@cliffy/command` could be considered for future enhancements:
+GenesisTools currently uses **commander** for argument parsing. Both yargs and `@cliffy/command` could be considered for future enhancements:📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| GenesisTools currently uses **minimist** for argument parsing. Both yargs and @cliffy/command could be considered for future enhancements: | |
| - **Subcommands**: Define hierarchical command structures with `.command()` | |
| - **Options**: Strongly typed options with `.option()` and automatic help generation | |
| - **Action handlers**: Clean separation of command logic with `.action()` | |
| - **yargs**: Better for complex Node.js/Bun CLI tools with many options and commands | |
| - **@cliffy/command**: Better for Deno-based tools that need type safety and built-in validation | |
| For interactive prompts, GenesisTools uses **@inquirer/prompts** which provides: | |
| - **Modern API**: Promise-based with individual prompt functions (`select`, `input`, `confirm`, etc.) | |
| - **Type safety**: Built-in TypeScript support | |
| - **Cancellation handling**: `ExitPromptError` from `@inquirer/core` for clean user cancellation | |
| For GenesisTools' Bun-based environment, **yargs** would be the more natural choice if migration were considered. | |
| GenesisTools currently uses **commander** for argument parsing. Both yargs and `@cliffy/command` could be considered for future enhancements: | |
| - **yargs**: Better for complex Node.js/Bun CLI tools with many options and commands | |
| - **@cliffy/command**: Better for Deno-based tools that need type safety and built-in validation | |
| For GenesisTools' Bun-based environment, **yargs** would be the more natural choice if migration were considered. |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Useful.md` around lines 363 - 368, The documentation incorrectly claims
GenesisTools uses minimist; update Useful.md to state the project uses the
commander library for CLI parsing (referencing the Commander imports found
across src/ and the commander entry in package.json). Replace mentions of
"minimist" and the yargs/@cliffy suggestions for Bun/Deno context with a short
note that commander is used for subcommands and options, and optionally suggest
yargs or `@cliffy/command` only as alternative future options if desired.
| - Incomplete projects: allow WIP status | ||
| - Date validation: end date must be after start date | ||
| - Image size limits: compress or reject oversized files | ||
| - URL validation: check link is accessible |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Avoid backend reachability checks for arbitrary portfolio URLs.
“Check link is accessible” becomes an SSRF footgun if this is implemented by fetching user-supplied URLs from the server. Prefer format validation in the app, or if you later probe URLs server-side, constrain protocols/domains and enforce strict timeouts.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.claude/ideas/dashboard/006-portfolio-showcase.md at line 320, The line
"check link is accessible" should not trigger a server-side fetch of arbitrary
user-supplied URLs; replace that SSRF-prone behavior with client-side/format
validation (e.g., URL parsing and allowed-scheme check) and remove any server
code that performs unrestricted HTTP GETs for portfolio links. If you must
perform server-side probes later, implement strict guards around the probe logic
(validate schemes to http/https only, enforce an allowlist or domain
constraints, set very short connect/read timeouts, disable redirects, and run
probes from an isolated service account/network). Ensure the check is
identified/updated wherever the phrase or function implementing "check link is
accessible" appears.
| ``` | ||
| src/dashboard/apps/web/src/routes/assistant/ | ||
| ├── -components/ | ||
| │ ├── kanban/ # KanbanBoard, KanbanColumn, KanbanCard | ||
| │ ├── communication/ # CommunicationLog, LogEntry, LogForm, LogFilters | ||
| │ ├── decisions/ # DecisionLog, DecisionCard, DecisionForm | ||
| │ ├── critical-path/ # CriticalPathGraph, DependencyNode, PathAnalysis | ||
| │ ├── blockers/ # BlockerList, BlockerCard, BlockerModal | ||
| │ ├── analytics/ # EnergyHeatmap, WeeklyReview, WeekStats, etc. | ||
| │ ├── handoff/ # HandoffDocument, HandoffEditor, HandoffHistory | ||
| │ ├── escalation/ # EscalationAlert, RiskIndicator, EscalationWidget | ||
| │ ├── celebrations/ # MicroCelebration, CelebrationManager, BadgeCelebration | ||
| │ ├── badges/ # BadgeShowcase, BadgeProgress, BadgeCard | ||
| │ └── distractions/ # DistractionLog, DistractionStats, DistractionPatterns | ||
| ├── -hooks/ # Extended with 12 new hooks | ||
| ├── -lib/storage/ # Extended LocalStorage adapter | ||
| ├── -types.ts # Extended with all new interfaces | ||
| ├── tasks/ | ||
| │ ├── index.tsx # Kanban board (replaced grid) | ||
| │ └── $taskId.tsx # Task detail with blockers, handoffs, dependencies | ||
| ├── next.tsx # What's Next recommendations | ||
| ├── parking.tsx # Context parking history | ||
| ├── analytics.tsx # Energy heatmap, weekly review, distractions, badges | ||
| ├── communication.tsx # Communication log page | ||
| └── decisions.tsx # Decision log page | ||
| ``` |
There was a problem hiding this comment.
Add language identifier to fenced code block.
The code block showing the codebase structure is missing a language specifier. For directory trees, use text or plaintext.
📝 Fix
-```
+```text
src/dashboard/apps/web/src/routes/assistant/
├── -components/🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 33-33: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
[warning] 33-33: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@findings.md` around lines 33 - 58, The fenced code block showing the
directory tree is missing a language identifier; update the opening fence (the
triple backticks before the directory listing) to include a language such as
text or plaintext (e.g., change "```" to "```text") so the block is properly
marked as plain text; locate the block in the findings.md content that begins
with the directory tree (the triple-backtick fenced block containing
"src/dashboard/apps/web/src/routes/assistant/") and add the language identifier.
| export const metadata: Metadata = { | ||
| title: "Create Turborepo", | ||
| description: "Generated by create turbo", | ||
| }; |
There was a problem hiding this comment.
Replace the scaffold metadata before shipping.
The docs app will publish Create Turborepo / Generated by create turbo as its title and description. That placeholder branding will leak into the browser title, SEO metadata, and link previews for the dashboard docs.
✏️ Suggested metadata update
export const metadata: Metadata = {
- title: "Create Turborepo",
- description: "Generated by create turbo",
+ title: "GenesisTools Dashboard Docs",
+ description: "Documentation for the personal productivity dashboard",
};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/dashboard/apps/docs/app/layout.tsx` around lines 8 - 11, The exported
const metadata: Metadata currently uses scaffold placeholders ("Create
Turborepo", "Generated by create turbo"); update that export in layout.tsx to
use the real site title and description appropriate for the dashboard docs
(e.g., project/product name and a concise description for SEO), and while here
consider adding or updating any other metadata fields (keywords, authors,
openGraph) to ensure accurate browser titles and rich link previews.
| const LINKS = [ | ||
| { | ||
| title: "Docs", | ||
| href: "https://turborepo.com/docs", | ||
| description: "Find in-depth information about Turborepo features and API.", | ||
| }, | ||
| { | ||
| title: "Learn", | ||
| href: "https://turborepo.com/docs/handbook", | ||
| description: "Learn more about monorepos with our handbook.", | ||
| }, | ||
| { | ||
| title: "Templates", | ||
| href: "https://turborepo.com/docs/getting-started/from-example", | ||
| description: "Choose from over 15 examples and deploy with a single click.", | ||
| }, | ||
| { | ||
| title: "Deploy", | ||
| href: "https://vercel.com/new", | ||
| description: "Instantly deploy your Turborepo to a shareable URL with Vercel.", | ||
| }, | ||
| ]; |
There was a problem hiding this comment.
Replace the stock Turborepo/Vercel landing page before shipping this app.
This page still advertises examples/with-tailwind, Turborepo docs, and Vercel, which does not match the dashboard product in this PR. If the docs app is exposed, users will land on unrelated placeholder content and assume the site is unfinished.
Also applies to: 31-101
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/dashboard/apps/docs/app/page.tsx` around lines 6 - 27, The page is still
using the placeholder LINKS array (constant name LINKS) with Turborepo/Vercel
content—replace that data and any placeholder text in the page component that
renders it with product-specific docs/links/descriptions; update the LINKS array
entries to point to your dashboard's documentation, handbook, templates, and
deployment instructions (or remove unused entries) and revise any
titles/descriptions rendered by the page component to reflect the actual
dashboard product so users don't see the stock landing content.
| @@ -0,0 +1,6 @@ | |||
| /// <reference types="next" /> | |||
| /// <reference types="next/image-types/global" /> | |||
| import "./.next/dev/types/routes.d.ts"; | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "next-env.d.ts" -type f 2>/dev/null | head -20Repository: genesiscz/GenesisTools
Length of output: 105
🏁 Script executed:
# Check if the file exists and show its full content with line numbers
if [ -f "src/dashboard/apps/docs/next-env.d.ts" ]; then
cat -n "src/dashboard/apps/docs/next-env.d.ts"
else
echo "File not found at src/dashboard/apps/docs/next-env.d.ts"
fiRepository: genesiscz/GenesisTools
Length of output: 358
🏁 Script executed:
# Check .gitignore for .next directory handling
if [ -f ".gitignore" ]; then
echo "=== .gitignore content ==="
cat ".gitignore" | grep -E "\.next|node_modules|dist" || echo "No .next/.node_modules/.dist patterns found"
fiRepository: genesiscz/GenesisTools
Length of output: 132
🏁 Script executed:
# Check if there are other next-env.d.ts files or similar declarations
find . -name "*next-env*" -o -name "*next.config*" 2>/dev/null | head -20Repository: genesiscz/GenesisTools
Length of output: 146
🌐 Web query:
Next.js next-env.d.ts auto-generated import .next routes.d.ts
💡 Result:
In recent Next.js versions (e.g. Next 15.5+ with typedRoutes enabled), Next will generate route typings into .next/types/routes.d.ts and then auto-wire them into TypeScript by updating next-env.d.ts with a line like:
/// <reference path="./.next/types/routes.d.ts" />This is expected/auto-generated behavior and will be overwritten whenever you run next dev, next build, or next typegen. [1] [2] [3]
What to do (recommended)
- Do not edit
next-env.d.tsmanually (it’s regenerated). [1] - If you’re seeing ESLint complain (notably
@typescript-eslint/triple-slash-reference), it’s a known issue/regression reported against typedRoutes generation. Workarounds are:- Exclude
next-env.d.tsfrom linting, or - Disable that ESLint rule for
next-env.d.tsvia an override (target just this file). [3]
- Exclude
Also ensure TS actually picks up the generated types
Your tsconfig.json should include:
next-env.d.ts.next/types/**/*.ts(so TS can see generated route/env helper types)
Next.js docs explicitly call this out (especially if you didn’t bootstrap with create-next-app). [2]
Sources:
[1] Next.js TypeScript config (next-env.d.ts is generated/regenerated) (nextjs.org)
[2] Next.js typedRoutes generates .next/types route definitions; include .next/types/**/*.ts in tsconfig (nextjs.org)
[3] Next.js issue: typedRoutes adds /// <reference path="./.next/types/routes.d.ts" /> and triggers ESLint triple-slash rule (github.com)
Citations:
- 1: https://nextjs.org/docs/app/api-reference/config/typescript
- 2: https://nextjs.org/docs/pages/api-reference/config/typescript
- 3: typedRoutes: @typescript-eslint/triple-slash-reference problem reported in next.env.d.ts vercel/next.js#82828
🌐 Web query:
Next.js next-env.d.ts should be committed to source control gitignore
💡 Result:
next-env.d.ts is generated by Next.js and should not be tracked in Git—add it to .gitignore. [1]
# Next.js (TypeScript)
next-env.d.tsIf you already committed it, untrack it once:
git rm --cached next-env.d.ts
git commit -m "Stop tracking next-env.d.ts"In CI, generate it instead of committing (e.g., run next dev/build, or in newer Next versions use npx next typegen). [2]
Sources: Next.js “Project Structure” docs [1]; Next.js GitHub discussion on next-env.d.ts/CI and next typegen [2].
Remove next-env.d.ts from version control—it is auto-generated by Next.js.
next-env.d.ts is generated by Next.js on every next dev, next build, or next typegen run and should not be committed. Add it to .gitignore and remove it from the repository using git rm --cached src/dashboard/apps/docs/next-env.d.ts. In CI and on clean checkouts, Next.js will regenerate it automatically, so there is no need to track it. Committing it couples source control to ephemeral build output and creates unnecessary merge conflicts when the file is regenerated.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/dashboard/apps/docs/next-env.d.ts` at line 3, Remove the auto-generated
next-env.d.ts from version control and stop tracking it: add next-env.d.ts to
.gitignore, run git rm --cached next-env.d.ts to untrack it, and commit the
.gitignore + removal; note the file contains the import
"./.next/dev/types/routes.d.ts" and will be regenerated by Next.js on
dev/build/typegen so no further code changes are required.
| const { mutate: addTodo } = useMutation({ | ||
| mutationFn: (todo: string) => | ||
| fetch("/demo/api/tq-todos", { | ||
| method: "POST", | ||
| body: JSON.stringify(todo), | ||
| }).then((res) => res.json()), | ||
| onSuccess: () => refetch(), | ||
| }); | ||
|
|
||
| const [todo, setTodo] = useState(""); | ||
|
|
||
| const submitTodo = useCallback(async () => { | ||
| await addTodo(todo); | ||
| setTodo(""); | ||
| }, [addTodo, todo]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/dashboard/apps/web-template/src/routes/demo/tanstack-query.tsxRepository: genesiscz/GenesisTools
Length of output: 3792
🌐 Web query:
TanStack React Query useMutation mutateAsync isPending API
💡 Result:
useMutation: mutateAsync + isPending (TanStack React Query)
-
mutateAsync(variables, options?) => Promise<TData>
Use this when you want toawaitthe mutation; it resolves on success and throws on error. [1][2] -
isPending: boolean
truewhile the mutation is running (status === 'pending'). Other states areisIdle,isError,isSuccess. [1]
Example
const mutation = useMutation({ mutationFn: addTodo })
try {
const data = await mutation.mutateAsync({ title: 'Do Laundry' })
} catch (e) {
// handle error
}
if (mutation.isPending) {
// show spinner / disable button
}[1]
Version note (common confusion)
- In v5 (current docs) the “loading” state is
isPending/status: 'pending'. [1] - In v4, the docs show
isLoading/status: 'loading', andmutateAsynchas the same “returns a Promise” behavior. [2]
Sources: TanStack Query React docs (Mutations guide; useMutation reference). [1][2]
Use mutateAsync and validate trimmed input inside submitTodo.
mutate returns immediately without a Promise, so the await on line 33 doesn't actually wait before clearing the field. The Enter handler (lines 61-65) can still submit whitespace because it bypasses the button's disabled guard. Additionally, there's no pending state guard, allowing concurrent submissions.
Move the trim/empty check into submitTodo, switch to mutateAsync, and gate both Enter and button clicks on the isPending state.
Suggested fix
- const { mutate: addTodo } = useMutation({
+ const { mutateAsync: addTodo, isPending } = useMutation({
mutationFn: async (todo: string) => {
const res = await fetch("/demo/api/tq-todos", {
method: "POST",
body: JSON.stringify(todo),
});
@@
const [todo, setTodo] = useState("");
const submitTodo = useCallback(async () => {
- await addTodo(todo);
+ const value = todo.trim();
+ if (!value) {
+ return;
+ }
+
+ await addTodo(value);
setTodo("");
}, [addTodo, todo]);
@@
value={todo}
onChange={(e) => setTodo(e.target.value)}
onKeyDown={(e) => {
- if (e.key === "Enter") {
+ if (e.key === "Enter" && !isPending) {
submitTodo();
}
}}
@@
- disabled={todo.trim().length === 0}
+ disabled={isPending || todo.trim().length === 0}
onClick={submitTodo}Also applies to: 61-72
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/dashboard/apps/web-template/src/routes/demo/tanstack-query.tsx` around
lines 21 - 35, The current mutate call (destructured as addTodo) is not awaited
and input trimming/guarding is only on the button, so change the useMutation
call to use mutateAsync (e.g., const { mutateAsync: addTodo, isPending } =
useMutation(...)), move the trim-and-empty check into submitTodo (trim todo,
return early if empty), await addTodo(todo) before calling setTodo(""), and
ensure both the Enter handler and the button click use submitTodo and
early-return when isPending is true to prevent concurrent/whitespace
submissions; reference functions/identifiers: useMutation, mutateAsync (alias
addTodo), submitTodo, todo state, and the Enter key handler.
| --destructive: oklch(0.577 0.245 27.325); | ||
| --destructive-foreground: oklch(0.577 0.245 27.325); |
There was a problem hiding this comment.
Give destructive surfaces a contrasting foreground.
Both themes set --destructive-foreground almost the same as --destructive, so destructive buttons/alerts will end up with unreadable text and icons.
🔧 Suggested fix
- --destructive-foreground: oklch(0.577 0.245 27.325);
+ --destructive-foreground: oklch(0.985 0 0);
...
- --destructive-foreground: oklch(0.637 0.237 25.331);
+ --destructive-foreground: oklch(0.985 0 0);Also applies to: 72-73
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/dashboard/apps/web-template/src/styles.css` around lines 36 - 37, The
destructive color and its foreground are nearly identical; update the CSS
variable --destructive-foreground (and the second theme's counterpart) to a
high-contrast value versus --destructive so text/icons are readable—adjust the
oklch (or swap to a light/dark value such as near-white or near-black) in the
declarations for --destructive and --destructive-foreground (the variables named
"--destructive" and "--destructive-foreground") so the foreground meets a
sufficient contrast ratio (apply the change in both places where these vars are
defined).
| ## React Guidelines | ||
|
|
||
| - **No `useCallback`/`useMemo`** - React Compiler handles memoization | ||
| - Use plain functions, compiler optimizes automatically |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🌐 Web query:
According to the official React 19 / React Compiler documentation, should teams avoid useCallbackanduseMemo entirely, or are there still supported cases where stable callback/value identity is needed?
💡 Result:
Teams should not avoid useCallback / useMemo entirely. The official React Compiler docs say the compiler reduces the need for manual memoization, but useMemo and useCallback remain supported “escape hatches” when you need precise control over what’s memoized. A specifically called-out supported case is when you need stable callback/value identity to keep an Effect from re-firing even though dependencies haven’t “meaningfully” changed. [1]
This matches the Hook docs, which note that React Compiler “automatically memoizes values and functions,” reducing (not eliminating) the need for manual useCallback / useMemo. [2] [3]
Sources: React Compiler docs (Introduction), useCallback, useMemo. [1] [2] [3]
Rephrase as conditional guidance rather than prohibition.
The React Compiler reduces the need for manual memoization but does not eliminate useCallback and useMemo entirely—they remain supported escape hatches for cases requiring stable callback or value identity, such as preventing Effects from re-firing unnecessarily. Adjust the documentation to reflect this as a default preference rather than an absolute rule, so contributors understand when these hooks are still appropriate.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/dashboard/apps/web/.claude/CLAUDE.md` around lines 119 - 122, Update the
"React Guidelines" section to present the no-useCallback/useMemo note as a
recommendation rather than an absolute ban: change the bullet "No
`useCallback`/`useMemo` - React Compiler handles memoization" to a conditional
guidance that the compiler reduces the need for manual memoization but
`useCallback` and `useMemo` remain supported escape hatches for cases requiring
stable identity (e.g., preventing effects from re-firing or stabilizing callback
props); mention that plain functions are the default preferred pattern and
include a brief note on when to keep the hooks (stable callback/value identity,
performance-critical paths).
| "projectName": "tanstack-start", | ||
| "mode": "file-router", | ||
| "typescript": true, | ||
| "tailwind": true, | ||
| "packageManager": "pnpm", | ||
| "addOnOptions": {}, | ||
| "git": true, | ||
| "version": 1, | ||
| "framework": "react-cra", |
There was a problem hiding this comment.
This app snapshot is out of sync with the checked-in setup.
src/dashboard/apps/web/.claude/CLAUDE.md documents Bun-based TanStack Start commands for this app, but this file still says "packageManager": "pnpm" and "framework": "react-cra". Rehydrating from .cta.json would recreate metadata that does not match the current app.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/dashboard/apps/web/.cta.json` around lines 2 - 10, The .cta.json metadata
is stale: update the "packageManager" and "framework" fields to match the
Bun-based TanStack Start setup documented in CLAUDE.md — replace
"packageManager": "pnpm" with "packageManager": "bun" and change "framework":
"react-cra" to the actual current framework string used by the app (as described
in CLAUDE.md) so the "packageManager" and "framework" keys in .cta.json
accurately reflect the checked-in setup.
|
|
||
| - Make sharing one `tailwind.config.ts` to apps and packages as easy as possible. | ||
| - Make package compilation simple by only depending on the Next.js Compiler and `tailwindcss`. | ||
| - Ensure Tailwind classes do not overwrite each other. The `ui` package uses a `ui-` prefix for it's classes. |
There was a problem hiding this comment.
Typo: "it's" should be "its".
"it's" is a contraction of "it is," but here you need the possessive "its."
📝 Fix
-- Ensure Tailwind classes do not overwrite each other. The `ui` package uses a `ui-` prefix for it's classes.
+- Ensure Tailwind classes do not overwrite each other. The `ui` package uses a `ui-` prefix for its classes.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - Ensure Tailwind classes do not overwrite each other. The `ui` package uses a `ui-` prefix for it's classes. | |
| - Ensure Tailwind classes do not overwrite each other. The `ui` package uses a `ui-` prefix for its classes. |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/dashboard/README.md` at line 33, Fix the typo in the README sentence
about Tailwind class prefixes: replace the contraction "it's" with the
possessive "its" in the line that reads about the `ui` package using a `ui-`
prefix for its classes so the sentence correctly uses the possessive form.
Summary by CodeRabbit
New Features
Documentation