Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
529 changes: 529 additions & 0 deletions .cursor/plans/upgrade_check_feature_implementation_7e439e70.plan.md

Large diffs are not rendered by default.

75 changes: 73 additions & 2 deletions .cursor/rules/react-tailwind.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,54 @@ Use the `EmptyState` component for empty/initial states:

**CRITICAL RULE**: All user-facing errors MUST be displayed in the UI. Never rely solely on `console.error` for errors that affect user actions.

**When to Display Errors in UI:**

* ✅ **ALWAYS display** errors from async operations triggered by user actions (button clicks, form submissions)
* ✅ **ALWAYS display** errors from event handlers that perform user-visible operations
* ✅ **ALWAYS display** errors that prevent operations from completing
* ✅ **ALWAYS display** errors in notification components (e.g., upgrade notifications, connection dialogs)
* ⚠️ **May skip UI display** for background operations that fail silently (e.g., periodic checks that don't block user)
* ⚠️ **May skip UI display** for non-critical errors that are logged for debugging only

**Common Mistake - Missing Error Display:**

```tsx
// ❌ WRONG: Only logging to console
const handleDismiss = async () => {
try {
await DismissUpgradeNotification(version);
} catch (error) {
console.error('Failed to dismiss:', error); // User never sees this!
}
};

// ✅ CORRECT: Display error in UI
const [error, setError] = useState<string>('');

const handleDismiss = async () => {
setError('');
try {
await DismissUpgradeNotification(version);
setError(''); // Clear on success
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
setError('Failed to dismiss notification: ' + errorMessage);
// Keep notification visible on error so user can see the error
}
};

// Display error in JSX
{error && (
<div style={{
backgroundColor: 'var(--color-error-bg)',
borderColor: 'var(--color-error-border)',
color: 'var(--color-error)',
}} className="p-2 border rounded text-xs mb-2">
{error}
</div>
)}
```

**Basic Error Display:**
```tsx
{error && (
Expand Down Expand Up @@ -931,13 +979,36 @@ catch (err) {
setError(err.toString()); // Shows raw API error
}

// ❌ WRONG: Missing error display in notification components
const handleAction = async () => {
try {
await SomeWailsMethod();
} catch (error) {
console.error('Failed:', error); // User never sees this in notification!
}
};

// ✅ CORRECT: Display in UI with user-friendly message
const [error, setError] = useState<string>('');

try {
await someOperation();
setError(''); // Clear on success
} catch (err) {
const friendlyError = convertToUserFriendlyMessage(err);
setError(friendlyError);
const errorMessage = err instanceof Error ? err.message : String(err);
setError('Failed: ' + errorMessage); // User sees this in UI
}

// ✅ CORRECT: Display error in notification/component UI
{error && (
<div style={{
backgroundColor: 'var(--color-error-bg)',
borderColor: 'var(--color-error-border)',
color: 'var(--color-error)',
}} className="p-2 border rounded text-xs">
{error}
</div>
)}
```

### Tabs (Radix UI)
Expand Down
55 changes: 55 additions & 0 deletions .cursor/rules/wails.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,61 @@ func (a *App) StartMonitor(subscriptionID string) error {
* Use `Lock()` for write operations
* Minimize lock duration (unlock before expensive operations)

**CRITICAL: Config Field Access in Goroutines**

When accessing `a.config` fields from goroutines (e.g., background tasks, tickers, event handlers), **ALWAYS use mutex protection** if the field can be modified by other goroutines:

```go
// ❌ WRONG: Race condition - accessing config field without mutex
func (a *App) performUpgradeCheck() {
if a.config != nil && a.config.DismissedUpgradeVersion == updateInfo.LatestVersion {
return // Race condition if DismissUpgradeNotification() modifies this concurrently
}
}

// ✅ CORRECT: Lock before reading config field
func (a *App) performUpgradeCheck() {
// Check if this version was already dismissed (with mutex protection)
a.upgradeCheckMu.Lock()
dismissedVersion := ""
if a.config != nil {
dismissedVersion = a.config.DismissedUpgradeVersion
}
a.upgradeCheckMu.Unlock()

if dismissedVersion == updateInfo.LatestVersion {
return
}
}
```

**When to Lock Config Fields:**

* ✅ **Lock when reading** config fields from goroutines (background tasks, tickers, event handlers)
* ✅ **Lock when writing** config fields from any method
* ✅ **Lock when accessing** config fields that are modified by other goroutines
* ❌ **Don't lock** when accessing config in single-threaded contexts (e.g., during startup before goroutines start)
* ❌ **Don't lock unnecessarily** - Only lock fields that are actually shared across goroutines

**Pattern for Config Field Access:**

```go
// Reading config field in goroutine
a.configMu.Lock()
value := ""
if a.config != nil {
value = a.config.SomeField
}
a.configMu.Unlock()

// Writing config field
a.configMu.Lock()
if a.config != nil {
a.config.SomeField = newValue
}
a.configMu.Unlock()
```

## Frontend (React/TypeScript) Patterns

> **Note:** For comprehensive React/TypeScript UI development patterns, component structure, and styling guidelines, see `react-tailwind.mdc`.
Expand Down
Loading