- Replace direct service calls with TanStack Query mutation hooks
- Add clientGridService wrapper object for child component compatibility
- Update grid mutations to return Promise<Party> for state updates
- Migrate party metadata, grid items, job/skill, and favorite operations
- Add partyShortcode parameter to create/delete mutations for cache invalidation
Co-Authored-By: Justin Edmund <justin@jedmund.com>
# feat: migrate components to TanStack Query v6 (Phase 2)
## Summary
This PR migrates 4 components from the custom
`createInfiniteScrollResource` pattern to TanStack Query v6's
`createInfiniteQuery`:
- **JobSkillSelectionSidebar** - Job skill search with infinite scroll
and category filtering
- **SearchContent** - Search modal for weapons/characters/summons with
element/rarity/proficiency filters
- **User Profile Page** (`[username]/+page.svelte`) - User's teams and
favorites with tab switching
- **Teams Explore Page** (`teams/explore/+page.svelte`) - Public teams
listing
All components now use:
- TanStack Query v6 infinite query pattern with thunk for reactivity
- `IsInViewport` from runed for intersection-based infinite scroll
- Debounced search (debounce the value, not the query)
- Proper loading, error, and empty states
- SSR integration with `initialData` pattern
## Updates since last revision
- **Fixed duplicate key error in SearchContent.svelte** - The API can
return the same item across multiple pages during infinite scroll (e.g.,
due to items being added/removed between page fetches). This caused
Svelte's keyed each block to throw `each_key_duplicate` errors. Fixed by
deduplicating results by `id` using a Map before rendering.
## Review & Testing Checklist for Human
This is a medium-risk change affecting core user-facing pages. Please
verify:
- [ ] **Infinite scroll works on all 4 pages** - Scroll to bottom and
verify more items load automatically
- [ ] **No duplicate items appear in search results** - After the fix,
scrolling through many pages of search results should not show
duplicates
- [ ] **SSR hydration** - Verify no flash of loading state on initial
page load (data should be pre-rendered)
- [ ] **User profile tab switching** - Test switching between "Teams"
and "Favorites" tabs; verify correct data loads
- [ ] **Search debouncing** - Type quickly in JobSkillSelectionSidebar
and SearchContent; verify queries aren't fired on every keystroke
- [ ] **Error states** - Simulate network failure and verify retry
button works
**Recommended Test Plan:**
1. Navigate to `/teams/explore` - verify teams load and infinite scroll
works
2. Navigate to a user profile page - verify teams load, switch to
favorites tab, verify favorites load
3. Open the search sidebar - search for weapons/characters/summons,
scroll through many pages, verify no duplicate key errors and no
duplicate items
4. Open job skill selection - search and filter skills, verify results
### Notes
- The Party component mutations migration (Follow-Up Prompt 5) was
deferred to a follow-up PR due to complexity
- Deprecated resource classes remain in codebase for now; removal
planned for separate PR
- Pre-existing paraglide module errors in build are unrelated to this PR
- Type assertions (`as unknown as`, `as any`) are used to handle
different query result structures between favorites and parties queries
**Link to Devin run:**
https://app.devin.ai/sessions/5aa7ea29edf34f569f95f13acee9e0d9
**Requested by:** Justin Edmund (justin@jedmund.com) / @jedmund
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Justin Edmund <justin@jedmund.com>
## Summary
This PR establishes the foundation for migrating from custom Svelte 5
resource classes to TanStack Query v6 for server state management. It
adds:
**Query Options Factories** (in `src/lib/api/queries/`):
- `party.queries.ts` - Party fetching with infinite scroll support
- `job.queries.ts` - Job and skill queries with pagination
- `user.queries.ts` - User profile, parties, and favorites queries
**Mutation Configurations** (in `src/lib/api/mutations/`):
- `party.mutations.ts` - Party CRUD with cache invalidation
- `grid.mutations.ts` - Weapon/character/summon mutations with
optimistic updates
- `job.mutations.ts` - Job and skill update mutations
**Deprecation Notices**:
- Added `@deprecated` JSDoc to `search.resource.svelte.ts` and
`party.resource.svelte.ts` with migration examples
**SSR Integration** (Phase 4):
- Created `+layout.ts` to initialize QueryClient for SSR support
- Updated `+layout.svelte` to receive QueryClient from load function
- Added SSR utilities in `src/lib/query/ssr.ts`:
- `withInitialData()` - for pages using +page.server.ts
- `prefetchQuery()` / `prefetchInfiniteQuery()` - for pages using
+page.ts
- `setQueryData()` - for direct cache population
- Added documentation in `src/lib/query/README.md`
**Component Wiring Examples** (Phase 5):
- `JobSelectionSidebar.svelte` - Migrated from `createJobResource()` to
`createQuery(() => jobQueries.list())`. Demonstrates client-side query
pattern with automatic loading/error states.
- `teams/[id]/+page.svelte` - Added `withInitialData()` pattern for SSR
integration. Server-fetched party data is used as initial cache value
with background refetching support.
**Migration Guide**:
- Added `src/lib/query/MIGRATION.md` with follow-up prompts for
remaining component migrations (JobSkillSelectionSidebar, search modal,
user profile, teams explore, Party mutations, resource class removal)
## Updates Since Last Revision
Fixed TypeScript type errors in the TanStack Query integration:
- `party.queries.ts`: Made `total` and `perPage` optional in
`PartyPageResult` interface to match adapter return type
- `ssr.ts`: Fixed `withInitialData` to properly handle null values using
`NonNullable<TData>` return type
- `job.mutations.ts`: Fixed slot indexing by casting through `unknown`
to `keyof typeof updatedSkills`
Type checks now pass for all files modified in this PR (16 remaining
errors are pre-existing project issues unrelated to this PR - paraglide
modules not generated, hooks.ts implicit anys).
## Review & Testing Checklist for Human
- [ ] **Verify app loads correctly**: The `+layout.ts` and
`+layout.svelte` changes are critical path - confirm the app still
renders
- [ ] **Test JobSelectionSidebar**: Open job selection sidebar and
verify jobs load correctly, search/filter works, and retry button works
on error
- [ ] **Test teams/[id] page**: Navigate to a party detail page and
verify it renders without loading flash (SSR data should be immediate)
- [ ] **Review type casts**: Check `job.mutations.ts:135` - the `as
unknown as keyof typeof` cast for slot indexing is a workaround for
jobSkills having string literal keys ('0', '1', '2', '3') while slot is
a number
- [ ] **Verify withInitialData behavior**: The `NonNullable<TData>`
return type change in `ssr.ts` should work correctly with `data.party`
which can be `Party | null`
**Recommended test plan**:
1. Run `pnpm install` to ensure dependencies are up to date
2. Start dev server and verify the app loads without errors
3. Navigate to a party detail page (`/teams/[shortcode]`) - should
render immediately without loading state
4. Open job selection sidebar (click job icon on a party you can edit) -
verify jobs load and filtering works
5. Test error handling by temporarily breaking network - verify retry
button appears
### Notes
- Pre-existing project issues remain (paraglide modules not generated,
hooks.ts implicit anys) - these are unrelated to this PR
- Local testing could not run due to missing node_modules (vite not
found) - project setup issue
- TanStack Query devtools installation was skipped due to Storybook
version conflicts
- The existing `search.queries.ts` file was used as the pattern
reference for new query factories
- SSR approach uses hybrid pattern: existing `+page.server.ts` files
work with `withInitialData()`, while new pages can use `prefetchQuery()`
in `+page.ts`
- Migration guide includes 6 follow-up prompts for completing the
remaining component migrations
**Link to Devin run**:
https://app.devin.ai/sessions/33e97a98ae3e415aa4dc35378cad3a2b
**Requested by**: Justin Edmund (justin@jedmund.com) / @jedmund
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Justin Edmund <justin@jedmund.com>
- Created GridItemData type (GridWeapon | GridSummon | GridCharacter)
- Typed GridOperation.data field with GridItemData instead of any
- Typed draggedItem and targetItem parameters with object shapes
- Made targetPosition flexible (number | string) for swaps/moves
- Added type guard for move operation to ensure type safety
Partial implementation of Phase 3.2 - reduced any usages in grid operations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Phase 3.1: Remove duplicate Awakening interface
- Removed unused src/lib/types/Awakening.d.ts
- Using entities.ts as single source of truth for Awakening type
- Awakening now properly uses LocalizedName interface
- No breaking changes (unused file had zero imports)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Phase 2.2: Add proper type definitions for wx-svelte-grid
- Created Cell interface export in wx-svelte-grid declarations
- Updated all 9 database cell components to use Cell type
- Removed custom Props interfaces with loose IRow typing
- Improved IntelliSense support for grid component props
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed from numeric (0/1/2) to string literals ('public'/'private'/'unlisted')
- Created PartyVisibility type with const assertion
- Updated Party and PartyPreview interfaces
- Updated PartyUpdatePayload interface
- Updated Zod schemas for validation
- Updated test mocks to use string literals
- Added deprecated conversion helpers for backward compat
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- create proper UncapStarProps and TranscendenceStarProps interfaces
- use discriminated union for StarRender type
- add function overloads for createStarProps with type safety
- remove 'as any' casts from star component spreading
- remove optionalProps workaround in SegmentedControl
- stop spreading HTMLAttributes to RadioGroupPrimitive (not supported)
- remove unsupported class prop from bits-ui SelectPrimitive.Root
- add type assertion for RadioGroupPrimitive spread props (as any)
- remove wx-svelte-grid Cell import (not exported)
- simplify LastUpdatedCell props with index signature for grid props
- transform job skills pagination meta to include page/perPage
- fix UserInfo avatar type (optional properties -> required when present)
- add visibility number-to-string mapping in party service (0=public, 1=private, 2=unlisted)
- change mapToApiPayload return type from Partial<Party> to CreatePartyParams
- fix raid/job mapping to use IDs instead of nested objects
- add generic type argument to RestDataProvider<any>
- add type assertion in optionalProps (value as T[keyof T])
- make UncapIndicator StarRender props optional for transcendence stars
- add type assertion for star props spreading (as any)
- fix element name type (string -> literal union with type assertion)
- fix PartySegmentedControl props (value/onValueChange -> selectedTab/onTabChange)
- remove userGender prop (component gets it from context)
- add type assertions for ResourceType and ImageVariant comparisons
- add required id/shortcode to Party object in teams/new
- fix auth hooks expiresAt (undefined -> empty string default)
- add type assertion for Select value binding (excludes null/boolean)
- remove recruits property from Character test mock (doesn't exist in type)
- add missing subaura property to Summon test mocks
- consolidate Awakening imports to use entities source
- make awakening type/level optional in GridWeapon/GridSummon
- fix null handling in AwakeningDisplay (null -> undefined)
Fixed remaining test mock data to match actual type schemas.
Changes:
1. entity.adapter.test.ts:
- Removed invalid maxLevel property from Character mock
- Character interface doesn't have maxLevel
2. grid.adapter.test.ts:
- Added missing required properties to Weapon mock:
- maxSkillLevel: 15
- maxAwakeningLevel: 5
- ax: true
- axType: 1
- Removed invalid series property from Summon mock
- Summon interface doesn't have series property
3. settings/+page.svelte:
- Fixed users.update call (removed extra fetch parameter)
All test mocks now match their corresponding type definitions from
entities.ts, ensuring tests can compile and run correctly.
Result: 24 → 22 errors (-2)
Overall progress: 53 → 22 errors (58% reduction)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed incorrect function call to users.update by removing extra fetch argument.
The issue: settings page was calling users.update(fetch, userId, updateData)
but the function signature is users.update(userId, updateData).
The fetch parameter is not needed - the users.update function uses the global
fetch internally via userAdapter.
Changes:
- src/routes/settings/+page.svelte:
- Removed fetch argument from users.update call
- Changed from: users.update(fetch, account.userId, updateData)
- Changed to: users.update(account.userId, updateData)
Result: 24 → 23 errors (-1)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed TypeScript errors where custom drag data was being stored on the
native Touch type, which doesn't allow extension.
The issue: `currentTouch` was typed as `Touch | null` but the code was
trying to store custom properties (item, source, type) on it for pending
drag operations initiated by mouse pointer events.
The fix: Created a new `PendingDragData` interface to properly type the
custom drag data being stored for mouse events.
Changes:
- src/lib/composables/drag-drop.svelte.ts:
- Added PendingDragData interface with item, source, type
- Changed TouchState.currentTouch from `Touch | null` to `PendingDragData | null`
- This allows storing pending drag data without conflicting with native Touch type
Result: 32 → 28 errors (-4)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed TypeScript errors for optional MSW (Mock Service Worker) test dependency.
MSW is intentionally optional - tests work with or without it. The dynamic
imports are wrapped in try-catch to gracefully handle when MSW is not installed.
Added @ts-expect-error comments to suppress TypeScript module resolution errors
for the optional dynamic imports, while maintaining the runtime fallback behavior.
Changes:
- src/lib/api/adapters/test-setup.ts:
- Added @ts-expect-error for 'msw/node' dynamic import
- Added @ts-expect-error for 'msw' dynamic import
- Preserves optional dependency pattern
Result: 34 → 32 errors (-2)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed multiple test fixture type errors to match actual schema definitions.
Changes:
1. Removed leftover optionalProps() call in users.ts (missed in Phase 2)
2. Fixed Character race field in entity.adapter.test.ts
- Changed from object {race1, race2} to array [1, 2]
- Matches entity.adapter.ts Character interface expectation
3. Added missing ULB stat fields to Weapon mocks in grid.adapter.test.ts
- Added maxHpUlb and maxAtkUlb to hp/atk objects
- Required by entities.ts Weapon interface
Result: 36 → 34 errors (-2)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed TypeScript error where ImageVariant type wasn't accessible in the
same file that re-exported it.
The issue: Re-exporting a type with `export type { X } from 'module'`
doesn't create a local binding that can be used in the same file.
The fix: Import the type first to create a local binding, then re-export it.
Changes:
- src/lib/features/database/detail/image.ts:
- Added: import type { ResourceType, ImageVariant } from '$lib/utils/images'
- Kept: export type { ResourceType as ResourceKind, ImageVariant }
- This allows ImageVariant to be used in the ImageArgs interface below
Result: 37 → 36 errors (-1)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Resolved PUBLIC_SIERO_OAUTH_URL import issue by removing the intermediate
config.ts file and importing environment variables directly where needed.
Changes:
- Removed src/lib/config.ts (unnecessary abstraction layer)
- Updated src/lib/auth/oauth.ts to import PUBLIC_SIERO_API_URL directly
- Updated src/routes/auth/refresh/+server.ts to import directly
- Construct OAUTH_BASE locally as `${PUBLIC_SIERO_API_URL}/oauth`
This fixes the TypeScript error where svelte-check couldn't resolve
PUBLIC_SIERO_OAUTH_URL from the config file, even though the variable
was properly defined in .env and .svelte-kit/ambient.d.ts.
Result: 38 → 37 errors (-1)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed all test fixture mock data to match actual type definitions across
4 test files. Resolved 9 distinct type errors by correcting mock object
structures.
Files modified:
- entity.adapter.test.ts: Fixed Character mock to use nested hp/atk objects
- grid.adapter.test.ts: Fixed GridWeapon/GridCharacter/GridSummon mocks
- Added proper entity objects (mockWeapon, mockCharacter, mockSummon)
- Fixed transcendenceStage -> transcendenceStep
- Removed invalid partyId/weaponId/characterId/summonId properties
- party.adapter.test.ts: Fixed Party mock
- Changed visibility from 'public' string to 0 number
- Removed invalid skills array from Job object
- Added complete RaidGroup with all required properties
- user.adapter.test.ts: Fixed User/Party mocks
- Created separate mockUser (User type) vs mockUserInfo (UserInfo type)
- Fixed role type mismatch (number vs string)
- Added required arrays (weapons, characters, summons) to Party objects
Result: 42 → 38 errors (-4)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed three key type definition issues in the adapter layer:
1. Added missing element property to Party interface
- Added `element?: number` to Party type
- Used throughout codebase but was missing from interface
2. Aligned onError callback types for consistency
- Changed ResourceOptions.onError from Error to AdapterError
- Now matches AdapterOptions.onError type signature
3. Exported Grid types from grid.adapter.ts
- Re-exported GridWeapon, GridCharacter, GridSummon
- Makes types available for test files
Files modified:
- src/lib/types/api/party.ts: Added element property
- src/lib/api/adapters/types.ts: Fixed onError callback type
- src/lib/api/adapters/grid.adapter.ts: Added type re-exports
Result: 43 → 42 errors (-1)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed inappropriate use of optionalProps() type shim from our own
codebase where we control the types. Type shims should only be used
for third-party library incompatibilities.
Files modified:
- base.adapter.ts: Removed shim from RequestOptions spreading
- grid.service.ts: Removed 3 usages from update methods
- party.service.ts: Removed import
- users.ts: Removed import and usage
- UserSettingsModal.svelte: Direct object construction
- drag-drop.svelte.ts: Direct object for DragOperation.target
Result: 45 → 43 errors (-2)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Build avatar object separately with proper types before assignment
- Apply optionalProps before passing to updateProfile
This maintains 53 errors (65% reduction from original 151).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add | undefined to all optional properties in Button Props interface
- This fixes 4 type errors related to Button component usage with exactOptionalPropertyTypes
Reduces errors from 57 to 53 (65% reduction from original 151).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update UserUpdateParams to include | undefined for all optional fields
- Add | undefined to CreatePartyParams and UpdatePartyParams interfaces
- Add | undefined to CreateGrid*Params interfaces (Weapon, Character, Summon)
- Transform UserUpdateParams to nested avatar structure in users.ts
- Remove unnecessary optionalProps wrappers (now handled by interface definitions)
- Fix TeamView awakening prop with conditional spreading
Reduces errors from 63 to 60.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix jobUtils proficiency type narrowing by storing intermediate values
- Add default empty string for openDescriptionSidebar title parameter
- Remove explicit undefined assignments in search resource tests
Reduces errors from 68 to 63.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add optionalProps shim to base.adapter.ts for RequestInit compatibility
- Apply optionalProps to UserSettingsModal updateData
- Use conditional spreading for Navigation Button element prop
- Apply optionalProps to drag-drop target object creation
- Apply optionalProps to party.service create/update methods
- Apply optionalProps to grid.service CRUD operations
- Fix transcendenceStage -> transcendenceStep typo in grid.service
- Update UserUpdateParams interface to include | undefined
- Update DragOperation interface properties to include | undefined
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- DatabaseProvider.ts: Remove redundant normalizer check
- Normalizer is applied by parent class, no need to apply twice
- Fixes "Property 'normalizer' does not exist" error
- image.ts: Separate type and value exports for proper type resolution
- CharacterImageCell.svelte: Fix getCharacterImage parameter order
- Changed (id, pose, variant) to (id, variant, pose)
- SearchContent.svelte: Fix getCharacterImage parameter order
Fixes ImageVariant type errors and property access errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- jobUtils.ts: Remove string comparison for job.row (row is typed as number)
- job.row === '1' comparison is always false, removed
- grid.service.ts: Fix swap operation to compare position with position
- Changed i.id === operation.targetPosition to i.position === operation.targetPosition
- targetPosition is a number (position), not a string (id)
Fixes "This comparison appears to be unintentional because the types have no overlap" errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- errors.ts: Only assign retryAfter when defined, not undefined
- user.adapter.ts: Build UserProfileResponse conditionally
- Only include optional properties (total, totalPages, perPage) when defined
- Use intermediate variables to ensure type safety
Fixes exactOptionalPropertyTypes violations where `T | undefined` cannot be
assigned to optional property `prop?: T`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update entities.ts WeaponKey to include all required fields (granblue_id, series, group, order)
- Use snake_case naming (granblue_id) to match API/database conventions
- Update modifiers.ts to import from entities.ts and use snake_case property
- Removes duplicate WeaponKey type and eliminates type conflicts
Fixes "Property 'granblueId' does not exist on type 'WeaponKey'" and type assignment errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- UncapIndicator.svelte: Use nullish coalescing for uncapLevel comparison
- UncapStatusDisplay.svelte: Guard uncapLevel in badge active checks
Fixes "'uncapLevel' is possibly 'null'" errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove duplicate generic SearchResult<T> from types.ts
- Export SearchResult from search.adapter.ts via index.ts
- Eliminates type conflict between two incompatible SearchResult definitions
This fixes "Type 'SearchResult' is not assignable to type 'SearchResult<any>'" errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Select.svelte: Use conditional spreading for optional `disabled` prop and `value` prop
- Switch.svelte: Conditionally spread `name` and `value` props
- Segment/RepSegment: Remove HTMLButtonAttributes extension and handle disabled prop properly
- Replace inline `import('svelte').Snippet` with proper import statements
Fixes type errors where bits-ui prop types don't include explicit `undefined` for optional properties, which conflicts with `exactOptionalPropertyTypes: true`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>