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
15 changes: 10 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Avoid Type Casting

We **never ever** cast types unless it's absolutely necessary. This includes:

- Manual generic type parameters (e.g., `<Type>`)
- Type assertions using `as`
- Type assertions using `satisfies`
Expand All @@ -15,23 +16,26 @@ We **never ever** cast types unless it's absolutely necessary. This includes:
### Prefer Type Inference

Always infer types and go up the logical chain as far as we can control to determine types. The preferred approach is:

1. **Schema validation** - Use schema definitions (e.g., Convex schema, Zod, etc.) as the source of truth
2. **Type inference from concrete sources** - Let TypeScript infer types from function return types, API responses, etc.
3. **Go up the chain** - Trace types back to their source rather than casting at the point of use

### Example

❌ **Bad:**

```typescript
const result = api.getData() as MyType;
const value = getValue<MyType>();
const result = api.getData() as MyType
const value = getValue<MyType>()
```

✅ **Good:**

```typescript
// Infer from schema or API definition
const result = api.getData(); // Type inferred from api.getData return type
const value = getValue(); // Type inferred from function implementation
const result = api.getData() // Type inferred from api.getData return type
const value = getValue() // Type inferred from function implementation
```

If types need to be fixed, fix them at the source (schema, API definition, function signature) rather than casting at the point of use.
Expand All @@ -45,6 +49,7 @@ If types need to be fixed, fix them at the source (schema, API definition, funct
Only include the properties from `search` (or other sources) that are actually used in the loader function. This ensures proper cache invalidation and prevents unnecessary re-runs when unrelated search params change.

❌ **Bad:**

```typescript
loaderDeps: ({ search }) => search, // Includes everything, even unused params
loader: async ({ deps }) => {
Expand All @@ -54,6 +59,7 @@ loader: async ({ deps }) => {
```

✅ **Good:**

```typescript
loaderDeps: ({ search }) => ({
page: search.page,
Expand All @@ -66,4 +72,3 @@ loader: async ({ deps }) => {
```

This ensures the loader only re-runs when the specific dependencies change, not when unrelated search params (like `expanded`, `viewMode`, etc.) change.

10 changes: 9 additions & 1 deletion agents/tasks/feed-admin-fixes.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,43 @@
## Issues Found and Fixed

### 1. Route Navigation - ✅ FIXED

**File**: `src/routes/admin/feed.tsx`
**Issue**: Using `/admin/feed/new` which doesn't exist as a route
**Fix**: Changed to `/admin/feed/$id` with `params={{ id: 'new' }}`

### 2. Navigation Search Params - ✅ FIXED

**File**: `src/routes/admin/feed.tsx`
**Issue**: Direct object assignment to search params
**Fix**: Updated to use function form: `search: (s) => ({ ...s, ...updates })`

### 3. Window Confirm - ✅ FIXED

**File**: `src/routes/admin/feed.tsx`
**Issue**: Using `confirm()` without `window.` prefix
**Fix**: Changed to `window.confirm()`

### 4. Type Error in FeedEntryEditor - ✅ FIXED

**File**: `src/routes/admin/feed.$id.tsx`
**Issue**: `entryQuery` could be `undefined` but type expects `FeedEntry | null`
**Fix**: Added null coalescing: `entryQuery ?? null`

### 5. Unused Imports - ✅ FIXED

**Files**: Multiple admin files
**Issue**: Unused imports causing warnings
**Fix**: Removed unused imports (`useState`, `useEffect`, `validateManualEntry`)

### 6. Library Filter Property - ✅ FIXED

**File**: `src/components/admin/FeedEntryEditor.tsx`
**Issue**: Accessing `lib.visible` property that doesn't exist
**Fix**: Removed filter for non-existent property

### 7. Actions Export - ✅ FIXED

**File**: `src/components/admin/FeedSyncStatus.tsx`
**Issue**: `api.feed.actions` doesn't exist in generated API
**Fix**: Removed `syncAllSources` button and updated to use `useAction` hook. GitHub sync works via `api.feed.github.syncGitHubReleases`.
Expand All @@ -41,11 +48,13 @@
## Remaining Issues

### 1. Actions Not Exported

**File**: `convex/feed/actions.ts`
**Issue**: Actions file exists but `api.feed.actions` is not available
**Status**: Need to verify Convex file structure. Actions may need to be in root `convex/` directory or registered differently.

### 2. Route Search Params Type Errors - ✅ FIXED

**File**: `src/routes/admin/feed.tsx`
**Issue**: TypeScript errors with search param updates
**Fix**: Changed from `useNavigate()` to `Route.useNavigate()` and updated search param updates to use object spread instead of function form
Expand All @@ -69,4 +78,3 @@
- Delete post
- Toggle visibility
- Toggle featured

1 change: 0 additions & 1 deletion agents/tasks/feed-feature-plan.plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,4 +506,3 @@ src/
3. Backfill recent blog posts
4. Announce feature to users
5. Monitor and iterate based on feedback

8 changes: 7 additions & 1 deletion agents/tasks/feed-validation-fixes.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,41 @@
## Issues Found

### 1. Schema Index Issue - ✅ FIXED

**File**: `convex/schema.ts`
**Issue**: The `by_library` index on `libraryIds` array field won't work. Convex doesn't support direct array indexing.
**Fix**: ✅ Removed the invalid index and added a comment explaining why.

### 2. Hydration Warning - ✅ FIXED

**File**: `src/routes/_libraries/feed.tsx`
**Issue**: React hydration mismatch warning due to localStorage access during SSR.
**Fix**: ✅ Updated to use `useMounted` hook to ensure localStorage access only happens client-side.

### 3. Missing sourceId Field

**File**: `convex/schema.ts`
**Issue**: Schema defines `sourceId` in the plan but it's missing from the actual schema.
**Status**: Check if this is needed - if entries need to track original source IDs (e.g., GitHub release ID), add it.

### 4. FeedList Loading State

**File**: `src/components/FeedList.tsx`
**Issue**: Need to verify loading states are properly displayed.
**Status**: Check implementation - should show loading spinner while query is pending.

### 5. Admin Authentication

**File**: `src/routes/admin/feed.tsx`
**Status**: ✅ Working correctly - shows auth prompt when not logged in.

### 6. Filter URL Encoding

**File**: `src/routes/_libraries/feed.tsx`
**Status**: ✅ Working correctly - filters update URL params properly.

### 7. Console Errors

**Issue**: Some unrelated 404 errors for auth endpoints (`/api/auth/display-name/to-string`, `/api/auth/display-name/value-of`)
**Status**: These appear to be unrelated to feed feature - likely existing auth system issues.

Expand Down Expand Up @@ -60,4 +67,3 @@
3. Test admin interface with authenticated user
4. Test GitHub sync when webhook is configured
5. Test blog sync functionality

6 changes: 4 additions & 2 deletions convex/feed/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export const verifyGitHubSignature = action({
const crypto = require('crypto')
const hmac = crypto.createHmac('sha256', args.secret)
const digest = 'sha256=' + hmac.update(args.payload).digest('hex')
return crypto.timingSafeEqual(Buffer.from(args.signature), Buffer.from(digest))
return crypto.timingSafeEqual(
Buffer.from(args.signature),
Buffer.from(digest)
)
},
})

4 changes: 3 additions & 1 deletion convex/feed/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,9 @@ export const getFeedFacetCounts = query({

// Count featured
const featuredEntries = applyFiltersExcept(entries, 'featured')
const featuredCount = featuredEntries.filter((entry) => entry.featured).length
const featuredCount = featuredEntries.filter(
(entry) => entry.featured
).length

return {
sources: sourceCounts,
Expand Down
5 changes: 2 additions & 3 deletions convex/feed/timestamps.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/**
* Timestamp management for feed entries
*
*
* Timestamp semantics:
* - publishedAt: When the content was originally published (from source or manually set)
* This is the primary timestamp for sorting and display - represents when content was actually published
* - createdAt: When the entry was created in our feed system (when we added it to the database)
* - updatedAt: When the entry was last modified in our system
* - lastSyncedAt: When we last synced from external source (only for auto-synced entries)
* - _createdAt: Convex internal timestamp (automatic, don't use in business logic)
*
*
* Rules:
* - Always sort by publishedAt (when content was published, not when we added it)
* - createdAt should never be used for display/sorting
Expand Down Expand Up @@ -72,4 +72,3 @@ export function validatePublishedAt(publishedAt: number): {

return { valid: true }
}

7 changes: 6 additions & 1 deletion convex/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { feedCategoryValidator } from './feed/schema'

// Zod schema for valid capabilities
// Valid capabilities list (exported for use throughout the app)
export const VALID_CAPABILITIES = ['admin', 'disableAds', 'builder', 'feed'] as const
export const VALID_CAPABILITIES = [
'admin',
'disableAds',
'builder',
'feed',
] as const
export const CapabilitySchema = z.enum(VALID_CAPABILITIES)
export type Capability = z.infer<typeof CapabilitySchema>

Expand Down
22 changes: 4 additions & 18 deletions src/components/DocsLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { useLocalStorage } from '~/utils/useLocalStorage'
import { last } from '~/utils/utils'
import type { ConfigSchema, MenuItem } from '~/utils/config'
import { Framework } from '~/libraries'
import { Framework, frameworkOptions } from '~/libraries'
import { DocsCalloutQueryGG } from '~/components/DocsCalloutQueryGG'
import { twMerge } from 'tailwind-merge'
import { partners, PartnerImage } from '~/utils/partners'
Expand Down Expand Up @@ -173,7 +173,7 @@
const prevItem = flatMenu[index - 1]
const nextItem = flatMenu[index + 1]

const [showBytes, setShowBytes] = useLocalStorage('showBytes', true)

Check warning on line 176 in src/components/DocsLayout.tsx

View workflow job for this annotation

GitHub Actions / PR

'setShowBytes' is assigned a value but never used

Check warning on line 176 in src/components/DocsLayout.tsx

View workflow job for this annotation

GitHub Actions / PR

'showBytes' is assigned a value but never used
const [isFullWidth, setIsFullWidth] = useLocalStorage('docsFullWidth', false)

const activePartners = partners.filter(
Expand Down Expand Up @@ -248,23 +248,9 @@
className={`text-xs ${
props.isActive ? 'opacity-100' : 'opacity-40'
} group-hover:opacity-100 font-bold transition-opacity ${
child.badge === 'react'
? 'text-sky-500'
: child.badge === 'solid'
? 'text-blue-500'
: child.badge === 'svelte'
? 'text-orange-500'
: child.badge === 'vue'
? 'text-green-500'
: child.badge === 'angular'
? 'text-fuchsia-500'
: child.badge === 'qwik'
? 'text-indigo-500'
: child.badge === 'lit'
? 'text-emerald-500'
: child.badge === 'vanilla'
? 'text-yellow-500'
: 'text-gray-500'
frameworkOptions.find(
(f) => f.value === child.badge
)?.fontColor ?? 'text-gray-500'
}`}
>
{child.badge}
Expand Down
2 changes: 1 addition & 1 deletion src/components/FeedEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
}
}

export function FeedEntry({

Check warning on line 42 in src/components/FeedEntry.tsx

View workflow job for this annotation

GitHub Actions / PR

'FeedEntry' is already defined
entry,
showFullContent = false,
expanded: expandedProp,
Expand Down Expand Up @@ -253,7 +253,7 @@
className="text-xs font-semibold text-gray-900 dark:text-gray-100 truncate hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
>
{entry.title}
</Link>
</Link>
</TableCell>

{/* Excerpt */}
Expand Down
31 changes: 14 additions & 17 deletions src/components/FeedFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
// Update filters when debounced search changes
React.useEffect(() => {
onFiltersChange({ search: debouncedSearch || undefined })
}, [debouncedSearch])

Check warning on line 157 in src/components/FeedFilters.tsx

View workflow job for this annotation

GitHub Actions / PR

React Hook React.useEffect has a missing dependency: 'onFiltersChange'. Either include it or remove the dependency array. If 'onFiltersChange' changes too often, find the parent component that defines it and wrap that definition in useCallback

// Sync local state with prop when search prop changes externally
React.useEffect(() => {
Expand Down Expand Up @@ -206,7 +206,6 @@
releaseLevelsDiffer ||
(includePrerelease !== undefined && includePrerelease !== true)


// Render filter content (shared between mobile and desktop)
const renderFilterContent = () => (
<>
Expand All @@ -216,9 +215,7 @@
<FilterCheckbox
label="Featured"
checked={featured ?? false}
onChange={() =>
handleFeaturedChange(featured ? undefined : true)
}
onChange={() => handleFeaturedChange(featured ? undefined : true)}
count={facetCounts?.featured}
/>
</div>
Expand All @@ -234,9 +231,7 @@
onSelectNone={() => {
onFiltersChange({ releaseLevels: undefined })
}}
isAllSelected={
selectedReleaseLevels?.length === RELEASE_LEVELS.length
}
isAllSelected={selectedReleaseLevels?.length === RELEASE_LEVELS.length}
isSomeSelected={
selectedReleaseLevels !== undefined &&
selectedReleaseLevels.length > 0 &&
Expand Down Expand Up @@ -452,10 +447,7 @@
compact
/>
)}
<div
onClick={(e) => e.stopPropagation()}
className="flex-shrink-0"
>
<div onClick={(e) => e.stopPropagation()} className="flex-shrink-0">
<FilterSearch
value={searchInput}
onChange={handleSearchChange}
Expand All @@ -482,10 +474,7 @@
>
<LuHelpCircle className="w-4 h-4 text-gray-400 dark:text-gray-500 cursor-help" />
</Tooltip>
<ViewModeToggle
viewMode={viewMode}
onViewModeChange={onViewModeChange}
/>
<ViewModeToggle viewMode={viewMode} onViewModeChange={onViewModeChange} />
</div>
)

Expand All @@ -498,8 +487,16 @@
desktopHeader={desktopHeader}
>
{/* Search - Desktop */}
<div className={`mb-2 lg:block hidden ${viewMode === 'table' ? 'max-w-xs lg:max-w-none lg:w-full' : 'w-full'}`}>
<FilterSearch value={searchInput} onChange={handleSearchChange} className="w-full" />
<div
className={`mb-2 lg:block hidden ${
viewMode === 'table' ? 'max-w-xs lg:max-w-none lg:w-full' : 'w-full'
}`}
>
<FilterSearch
value={searchInput}
onChange={handleSearchChange}
className="w-full"
/>
</div>

{renderFilterContent()}
Expand Down
10 changes: 8 additions & 2 deletions src/components/FeedPageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,14 @@ function FeedPageLayoutHeader({
}

function FeedPageLayoutFilters() {
const { filters, onFiltersChange, onClearFilters, facetCountsQuery, viewMode = 'table', onViewModeChange } =
useFeedPageLayout()
const {
filters,
onFiltersChange,
onClearFilters,
facetCountsQuery,
viewMode = 'table',
onViewModeChange,
} = useFeedPageLayout()

return (
<aside className="lg:w-64 flex-shrink-0">
Expand Down
1 change: 0 additions & 1 deletion src/components/FilterComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -303,4 +303,3 @@ export function ViewModeToggle({
</div>
)
}

1 change: 1 addition & 0 deletions src/components/FrameworkSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export function FrameworkSelect({ libraryId }: { libraryId: LibraryId }) {
const frameworkConfig = useFrameworkConfig({ frameworks: library.frameworks })
return (
<Select
className="min-w-[120px]"
label={frameworkConfig.label}
selected={frameworkConfig.selected}
available={frameworkConfig.available}
Expand Down
Loading
Loading