The WeaponRep (and other rep components) weren't visually updating when
new items were added to the party. The issue was that Svelte's reactivity
wasn't properly propagating through the {#each} blocks.
Changes:
- PartySegmentedControl: Add derived values for party sub-properties to
ensure reactivity propagates through snippet boundaries
- WeaponRep: Pre-compute rows as explicit $derived value and use keyed
{#each} blocks for proper change detection
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Overview
Complete migration from service layer to TanStack Query v6 with
mutations, queries, and automatic cache management.
## What Was Completed
### Phase 1: Entity Queries & Database Pages ✅
- Created `entity.queries.ts` with query options for weapons,
characters, and summons
- Migrated all database detail pages to use TanStack Query with
`withInitialData()` pattern
- SSR with client-side hydration working correctly
### Phase 2: Server Load Cleanup ✅
- Removed PartyService dependency from teams detail server load
- Server loads now use adapters directly instead of service layer
- Cleaner separation of concerns
### Phase 3: Party.svelte Refactoring ✅
- Removed all PartyService, GridService, and ConflictService
dependencies
- Migrated to TanStack Query mutations for all operations:
- Grid operations: create, update, delete, swap, move
- Party operations: update, delete, remix, favorite, unfavorite
- Added swap/move mutations for drag-and-drop operations
- Automatic cache invalidation and query refetching
### Phase 4: Service Layer Removal ✅
- Deleted all service layer files (~1,345 lines removed):
- `party.service.ts` (620 lines)
- `grid.service.ts` (450 lines)
- `conflict.service.ts` (120 lines)
- `gridOperations.ts` (unused utility)
- Deleted empty `services/` directory
- Created utility functions for cross-cutting concerns:
- `localId.ts`: Anonymous user local ID management
- `editKeys.ts`: Edit key management for anonymous editing
- `party-context.ts`: Extracted PartyContext type
### Phase 5: /teams/new Migration ✅
- Migrated party creation wizard to use TanStack Query mutations
- Replaced all direct adapter calls with mutations (7 locations)
- Maintains existing behavior and flow
- Automatic cache invalidation for newly created parties
## Benefits
### Performance
- Automatic request deduplication
- Better cache utilization across pages
- Background refetching for fresh data
- Optimistic updates for instant UI feedback
### Developer Experience
- Single source of truth for data fetching
- Consistent patterns across entire app
- Query devtools for debugging
- Less boilerplate code
### Code Quality
- ~1,088 net lines removed
- Simpler mental model (no service layer)
- Better TypeScript inference
- Easier to test
### Architecture
- **100% TanStack Query coverage** - no service layer, no direct adapter
calls
- Clear separation: UI ← Queries/Mutations ← Adapters ← API
- Automatic cache management
- Consistent mutation patterns everywhere
## Testing
All features verified:
- Party creation (anonymous & authenticated)
- Grid operations (add, remove, update, swap, move)
- Party operations (update, delete, remix, favorite)
- Cache invalidation across tabs
- Error handling and rollback
- SSR with hydration
## Files Modified
### Created (3)
- `src/lib/types/party-context.ts`
- `src/lib/utils/editKeys.ts`
- `src/lib/utils/localId.ts`
### Deleted (4)
- `src/lib/services/party.service.ts`
- `src/lib/services/grid.service.ts`
- `src/lib/services/conflict.service.ts`
- `src/lib/utils/gridOperations.ts`
### Modified (13)
- `src/lib/api/mutations/grid.mutations.ts`
- `src/lib/components/grids/CharacterGrid.svelte`
- `src/lib/components/grids/SummonGrid.svelte`
- `src/lib/components/grids/WeaponGrid.svelte`
- `src/lib/components/party/Party.svelte`
- `src/routes/teams/new/+page.svelte`
- Database entity pages (characters, weapons, summons)
- Other supporting files
## Migration Complete
This PR completes the TanStack Query migration. The entire application
now uses TanStack Query v6 for all data fetching and mutations, with
zero remaining service layer code.
---------
Co-authored-by: Claude <noreply@anthropic.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>
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>
- 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
- 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)
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>
- 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>
- 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>
- 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>
- 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>
- Fix DetailItem.svelte to accept boolean and null values
- Fix proficiency access in database/characters/[id]/+page.svelte
Co-Authored-By: Justin Edmund <justin@jedmund.com>
- Fix RequestOptions cache type incompatibility in adapters/types.ts
- Add missing properties to Character type in entity.adapter.ts and entities.ts
- Create adapters index.ts for module exports
- Update users.ts to use userAdapter instead of removed core module
- Fix UserSettingsModal.svelte switch import and type errors
- Add type shims for wx-svelte-grid and $env/static/public
- Accept upstream versions for SearchSidebar.svelte and teams/new/+page.svelte
- Add CLEANUP_PLAN.md documenting remaining work
Reduces type errors from ~412 to ~378. See CLEANUP_PLAN.md for remaining fixes.
Co-Authored-By: Justin Edmund <justin@jedmund.com>
## Summary
This PR performs low-risk cleanup tasks on the svelte-main branch:
1. **Remove unused imports** from `base.adapter.ts` - `snakeToCamel` and
`camelToSnake` were imported but never used
2. **Add `/.next` to `.gitignore`** - This is a SvelteKit project but
had a stale Next.js build artifact showing in git status
3. **Fix broken auth import** - `map.ts` was importing
`UserInfoResponse` from `$lib/api/resources/users` which never existed.
Created the type in `oauth.ts` based on actual usage in `buildCookies()`
4. **Remove test/example routes** - Deleted development scaffolding that
had no references elsewhere:
- `src/lib/components/examples/SearchExample.svelte`
- `src/routes/test-sidebar/+page.svelte`
- `src/routes/test/drag-drop/+page.svelte`
## Review & Testing Checklist for Human
- [ ] **Verify `UserInfoResponse` type matches actual API response** - I
inferred the type from usage in `map.ts`, but haven't verified against
the actual `/users/info` endpoint response from hensei-api. Fields:
`id`, `username`, `role`, `avatar.picture`, `avatar.element`,
`language`, `gender`, `theme`
- [ ] **Confirm test routes are not needed** - These appeared to be dev
scaffolding with no code references, but verify they're not used in any
manual QA workflows
- [ ] **Test auth flow** - Login/signup should still work correctly with
the new type location
**Recommended test plan:** Log in to the app and verify user info
(avatar, language, theme preferences) loads correctly after
authentication.
### Notes
- The broken `$lib/api/resources/users` import was pre-existing (the
file never existed), not caused by the previous API layer cleanup PR
- Running `pnpm check` confirms the auth/map error is resolved;
remaining type errors are unrelated pre-existing issues
Link to Devin run:
https://app.devin.ai/sessions/611580bc2db94e20a48c3692d3cbd432
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>
- let backend compute default uncap levels
- fix InfiniteScroll resource lifecycle (don't destroy on unmount)
- improve party resource error handling with type narrowing
- use SvelteMap/SvelteDate for reactivity
- use shortcode instead of id for job updates
- wrap job_id in party object for rails API
- only send editable skill slots (1-3), skip null values
- add updateAccessory method stub
- update JobSkill type with emp/base boolean flags
- use new skill fields in jobUtils and components
- update job adapter with locale and filter params
- restyle empty skill slots with cleaner placeholder
- simplify ML badge to not show level number
- track active item in sidebar store
- add element accent colors and focus ring mixins
- units show pulsing focus ring when selected in sidebar
- refactor units to use shared menu components
- update context menu test page
- UnitMenuContainer for context/dropdown menu wrapper
- MenuItems for reusable action items
- GearMenuButton for hover-triggered dropdown
- shared scss styles for both menu variants
- replace manual fetch with createInfiniteQuery
- debounced search input with reactive filters
- pagination handled automatically via infinite scroll
- cleaner state management via query result
- integrate @tanstack/svelte-query into layout
- add query client factory and query keys
- new InfiniteScrollQuery component for paginated data
- search query options for weapons/characters/summons
- update dev port to 5174