diff --git a/AGENTS.md b/AGENTS.md index 87ed21ebd..7e54c0746 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 assertions using `as` - Type assertions using `satisfies` @@ -15,6 +16,7 @@ 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 @@ -22,16 +24,18 @@ Always infer types and go up the logical chain as far as we can control to deter ### Example ❌ **Bad:** + ```typescript -const result = api.getData() as MyType; -const value = getValue(); +const result = api.getData() as MyType +const value = getValue() ``` ✅ **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. @@ -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 }) => { @@ -54,6 +59,7 @@ loader: async ({ deps }) => { ``` ✅ **Good:** + ```typescript loaderDeps: ({ search }) => ({ page: search.page, @@ -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. - diff --git a/agents/tasks/feed-admin-fixes.md b/agents/tasks/feed-admin-fixes.md index 73ac40d36..e3315285b 100644 --- a/agents/tasks/feed-admin-fixes.md +++ b/agents/tasks/feed-admin-fixes.md @@ -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`. @@ -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 @@ -69,4 +78,3 @@ - Delete post - Toggle visibility - Toggle featured - diff --git a/agents/tasks/feed-feature-plan.plan.md b/agents/tasks/feed-feature-plan.plan.md index 4cf488af1..858a2c3ae 100644 --- a/agents/tasks/feed-feature-plan.plan.md +++ b/agents/tasks/feed-feature-plan.plan.md @@ -506,4 +506,3 @@ src/ 3. Backfill recent blog posts 4. Announce feature to users 5. Monitor and iterate based on feedback - diff --git a/agents/tasks/feed-validation-fixes.md b/agents/tasks/feed-validation-fixes.md index f8d1a7e9d..9ea070cca 100644 --- a/agents/tasks/feed-validation-fixes.md +++ b/agents/tasks/feed-validation-fixes.md @@ -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. @@ -60,4 +67,3 @@ 3. Test admin interface with authenticated user 4. Test GitHub sync when webhook is configured 5. Test blog sync functionality - diff --git a/convex/feed/crypto.ts b/convex/feed/crypto.ts index 461b187d3..f6339d693 100644 --- a/convex/feed/crypto.ts +++ b/convex/feed/crypto.ts @@ -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) + ) }, }) - diff --git a/convex/feed/queries.ts b/convex/feed/queries.ts index 09e646047..51592d880 100644 --- a/convex/feed/queries.ts +++ b/convex/feed/queries.ts @@ -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, diff --git a/convex/feed/timestamps.ts b/convex/feed/timestamps.ts index 21d32e3e3..116114885 100644 --- a/convex/feed/timestamps.ts +++ b/convex/feed/timestamps.ts @@ -1,6 +1,6 @@ /** * 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 @@ -8,7 +8,7 @@ * - 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 @@ -72,4 +72,3 @@ export function validatePublishedAt(publishedAt: number): { return { valid: true } } - diff --git a/convex/schema.ts b/convex/schema.ts index 66ffcdbd3..42b13ca38 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -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 diff --git a/src/components/DocsLayout.tsx b/src/components/DocsLayout.tsx index 5f80863fa..dd7a0ef23 100644 --- a/src/components/DocsLayout.tsx +++ b/src/components/DocsLayout.tsx @@ -5,7 +5,7 @@ import { Link, useMatches, useParams } from '@tanstack/react-router' 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' @@ -248,23 +248,9 @@ export function DocsLayout({ 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} diff --git a/src/components/FeedEntry.tsx b/src/components/FeedEntry.tsx index 770bc80a5..9f0560761 100644 --- a/src/components/FeedEntry.tsx +++ b/src/components/FeedEntry.tsx @@ -253,7 +253,7 @@ export function FeedEntry({ 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} - + {/* Excerpt */} diff --git a/src/components/FeedFilters.tsx b/src/components/FeedFilters.tsx index 4a0506458..520fb6043 100644 --- a/src/components/FeedFilters.tsx +++ b/src/components/FeedFilters.tsx @@ -206,7 +206,6 @@ export function FeedFilters({ releaseLevelsDiffer || (includePrerelease !== undefined && includePrerelease !== true) - // Render filter content (shared between mobile and desktop) const renderFilterContent = () => ( <> @@ -216,9 +215,7 @@ export function FeedFilters({ - handleFeaturedChange(featured ? undefined : true) - } + onChange={() => handleFeaturedChange(featured ? undefined : true)} count={facetCounts?.featured} /> @@ -234,9 +231,7 @@ export function FeedFilters({ onSelectNone={() => { onFiltersChange({ releaseLevels: undefined }) }} - isAllSelected={ - selectedReleaseLevels?.length === RELEASE_LEVELS.length - } + isAllSelected={selectedReleaseLevels?.length === RELEASE_LEVELS.length} isSomeSelected={ selectedReleaseLevels !== undefined && selectedReleaseLevels.length > 0 && @@ -452,10 +447,7 @@ export function FeedFilters({ compact /> )} -
e.stopPropagation()} - className="flex-shrink-0" - > +
e.stopPropagation()} className="flex-shrink-0"> - +
) @@ -498,8 +487,16 @@ export function FeedFilters({ desktopHeader={desktopHeader} > {/* Search - Desktop */} - ) } - diff --git a/src/components/FrameworkSelect.tsx b/src/components/FrameworkSelect.tsx index 10ebbd4db..b9ff97f64 100644 --- a/src/components/FrameworkSelect.tsx +++ b/src/components/FrameworkSelect.tsx @@ -14,6 +14,7 @@ export function FrameworkSelect({ libraryId }: { libraryId: LibraryId }) { const frameworkConfig = useFrameworkConfig({ frameworks: library.frameworks }) return ( TanStack Store is widely adopted across the TanStack ecosystem, - powering libraries like TanStack Form, TanStack Router, and more. It - has been{' '} + powering libraries like TanStack Form, TanStack Router, TanStack + Pacer, and more. It has been{' '} battle-tested in production environments, ensuring reliability and performance @@ -88,12 +88,12 @@ export const storeProject = { framework agnostic - , with adapters available for popular frameworks like React, Vue, - Angular, Solid, and Svelte. This flexibility allows developers to use - the store in their preferred framework without being locked into a + , with adapters available for popular frameworks like React, Preact, + Vue, Angular, Solid, and Svelte. This flexibility allows developers to + use the store in their preferred framework without being locked into a specific ecosystem.
), }, ], -} +} satisfies Library diff --git a/src/routes/_libraries/account.tsx b/src/routes/_libraries/account.tsx index 50c4af7e0..6e9e4ba2f 100644 --- a/src/routes/_libraries/account.tsx +++ b/src/routes/_libraries/account.tsx @@ -22,10 +22,14 @@ function UserSettings() { const { adsDisabled } = args const currentValue = localStore.getQuery(api.auth.getCurrentUser) if (currentValue !== undefined) { - localStore.setQuery(api.auth.getCurrentUser, {}, { - ...currentValue, - adsDisabled: adsDisabled, - }) + localStore.setQuery( + api.auth.getCurrentUser, + {}, + { + ...currentValue, + adsDisabled: adsDisabled, + } + ) } }) diff --git a/src/routes/admin/roles.index.tsx b/src/routes/admin/roles.index.tsx index 697654ce6..a6a903951 100644 --- a/src/routes/admin/roles.index.tsx +++ b/src/routes/admin/roles.index.tsx @@ -1,5 +1,10 @@ import { createFileRoute, Link } from '@tanstack/react-router' -import { FilterBar, FilterSearch, FilterCheckbox, FilterSection } from '~/components/FilterComponents' +import { + FilterBar, + FilterSearch, + FilterCheckbox, + FilterSection, +} from '~/components/FilterComponents' import { Table, TableHeader, @@ -71,7 +76,9 @@ function RolesPage() { [search.cap] ) - const [expandedSections, setExpandedSections] = useState>({ + const [expandedSections, setExpandedSections] = useState< + Record + >({ capabilities: true, }) @@ -135,7 +142,9 @@ function RolesPage() { }), }) }} - isAllSelected={capabilityFilters.length === availableCapabilities.length} + isAllSelected={ + capabilityFilters.length === availableCapabilities.length + } isSomeSelected={ capabilityFilters.length > 0 && capabilityFilters.length < availableCapabilities.length diff --git a/src/routes/admin/users.tsx b/src/routes/admin/users.tsx index f3034deef..2349db4bd 100644 --- a/src/routes/admin/users.tsx +++ b/src/routes/admin/users.tsx @@ -15,7 +15,12 @@ import { FaSpinner, } from 'react-icons/fa' import { PaginationControls } from '~/components/PaginationControls' -import { FilterBar, FilterSearch, FilterCheckbox, FilterSection } from '~/components/FilterComponents' +import { + FilterBar, + FilterSearch, + FilterCheckbox, + FilterSection, +} from '~/components/FilterComponents' import { Table, TableHeader, @@ -169,7 +174,9 @@ function UsersPage() { const [updatingAdsUserId] = useState(null) const [selectedUserIds, setSelectedUserIds] = useState>(new Set()) const [bulkActionRoleId, setBulkActionRoleId] = useState(null) - const [expandedSections, setExpandedSections] = useState>({ + const [expandedSections, setExpandedSections] = useState< + Record + >({ capabilities: true, ads: true, }) diff --git a/src/utils/feedSchema.ts b/src/utils/feedSchema.ts index dc8f24b05..e1c086af6 100644 --- a/src/utils/feedSchema.ts +++ b/src/utils/feedSchema.ts @@ -10,4 +10,3 @@ export { type FeedCategory, type ReleaseLevel, } from '../../convex/feed/schema' -