fix: type errors cleanup (161 -> 130 errors)

- Fix Party.svelte: add null checks for existingChar/existingWeapon/existingSummon
- Fix DropdownItem.svelte: replace asChild with child snippet pattern for bits-ui v2
- Fix UncapStar.svelte, TranscendenceStar.svelte: tabIndex -> tabindex
- Fix Party.svelte, Navigation.svelte: remove asChild prop usage
- Fix images.ts: add | undefined to pose/element params for exactOptionalPropertyTypes
- Fix ItemHeader.svelte, UncapIndicator.svelte: accept number | null | undefined
- Fix GridRepCollection.svelte, GuidebookUnit.svelte: PartyView -> Party type
- Fix search.adapter.ts: add optional type property to SearchResult
- Update various Props interfaces for exactOptionalPropertyTypes compliance

Co-Authored-By: Justin Edmund <justin@jedmund.com>
This commit is contained in:
Devin AI 2025-11-28 21:58:11 +00:00
parent 8178b8592f
commit 67eb624bfc
15 changed files with 118 additions and 96 deletions

View file

@ -54,6 +54,8 @@ export interface SearchResult {
series?: number
/** URL for entity image */
imageUrl?: string
/** Type of entity (lowercase for compatibility) */
type?: 'weapon' | 'character' | 'summon'
/** Type of entity */
searchableType: 'Weapon' | 'Character' | 'Summon'
}
@ -294,4 +296,4 @@ export class SearchAdapter extends BaseAdapter {
* Default singleton instance for search operations
* Use this for most search needs unless you need custom configuration
*/
export const searchAdapter = new SearchAdapter(DEFAULT_ADAPTER_CONFIG)
export const searchAdapter = new SearchAdapter(DEFAULT_ADAPTER_CONFIG)

View file

@ -191,7 +191,7 @@
<DropdownMenu.Portal>
<DropdownMenu.Content class="dropdown-content" sideOffset={5}>
<DropdownItem asChild>
<DropdownItem>
<button onclick={() => (settingsModalOpen = true)}>
{m.nav_settings()}
</button>
@ -201,7 +201,7 @@
{/if}
{#if isAuth}
<DropdownMenu.Separator class="dropdown-separator" />
<DropdownItem asChild>
<DropdownItem>
<button onclick={handleLogout}>{m.nav_logout()}</button>
</DropdownItem>
{/if}

View file

@ -1,13 +1,13 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { PartyView } from '$lib/api/schemas/party'
import type { Party } from '$lib/types/api/party'
export let item: any | undefined
export let position: number // 1..3
type PartyCtx = {
getParty: () => PartyView
updateParty: (p: PartyView) => void
getParty: () => Party
updateParty: (p: Party) => void
canEdit: () => boolean
services: { partyService: any }
}

View file

@ -10,11 +10,11 @@
import DropZone from '$lib/components/dnd/DropZone.svelte'
interface Props {
characters?: GridCharacter[]
characters?: GridCharacter[] | undefined
mainWeaponElement?: number | null | undefined
partyElement?: number | null | undefined
job?: Job
container?: string
job?: Job | undefined
container?: string | undefined
}
let {

View file

@ -13,16 +13,16 @@
import Icon from '$lib/components/Icon.svelte'
interface Props {
job?: Job
jobSkills?: JobSkillList
accessory?: JobAccessory
canEdit?: boolean
gender?: Gender
element?: number
onSelectJob?: () => void
onSelectSkill?: (slot: number) => void
onRemoveSkill?: (slot: number) => void
onSelectAccessory?: () => void
job?: Job | undefined
jobSkills?: JobSkillList | undefined
accessory?: JobAccessory | undefined
canEdit?: boolean | undefined
gender?: Gender | undefined
element?: number | undefined
onSelectJob?: (() => void) | undefined
onSelectSkill?: ((slot: number) => void) | undefined
onRemoveSkill?: ((slot: number) => void) | undefined
onSelectAccessory?: (() => void) | undefined
}
let {

View file

@ -397,7 +397,7 @@
try {
// Update skills with the new skill in the slot
const updatedSkills = { ...party.jobSkills }
updatedSkills[slot as keyof typeof updatedSkills] = skill
updatedSkills[String(slot) as keyof typeof updatedSkills] = skill
console.log('[Party] Current jobSkills:', party.jobSkills)
console.log('[Party] Updated jobSkills object:', updatedSkills)
@ -459,7 +459,7 @@
try {
// Remove skill from slot
const updatedSkills = { ...party.jobSkills }
delete updatedSkills[slot as keyof typeof updatedSkills]
delete updatedSkills[String(slot) as keyof typeof updatedSkills]
console.log('[Party] Removing skill from slot:', slot)
console.log('[Party] Current jobSkills:', party.jobSkills)
@ -526,7 +526,7 @@
try {
// Remove skill from slot
const updatedSkills = { ...party.jobSkills }
delete updatedSkills[slot as keyof typeof updatedSkills]
delete updatedSkills[String(slot) as keyof typeof updatedSkills]
// Convert skills object to array format expected by API
const skillsArray = Object.entries(updatedSkills)
@ -554,6 +554,7 @@
if (items.length === 0 || !canEdit()) return
const item = items[0]
if (!item) return
loading = true
error = null
@ -649,7 +650,7 @@
localId = partyService.getLocalId()
// Get edit key for this party if it exists
editKey = partyService.getEditKey(party.shortcode)
editKey = partyService.getEditKey(party.shortcode) ?? undefined
// No longer need to verify party data integrity after hydration
// since $state.raw prevents the hydration mismatch
@ -787,14 +788,20 @@
(c: any) => c.id === gridCharacterId
)
if (charIndex !== -1) {
// Preserve the character object reference but update uncap fields
updatedParty.characters[charIndex] = {
...updatedParty.characters[charIndex],
uncapLevel: updatedChar.uncapLevel ?? updatedChar.uncap_level,
transcendenceStep: updatedChar.transcendenceStep ?? updatedChar.transcendence_step
// Preserve the character object reference but update uncap fields
const existingChar = updatedParty.characters[charIndex]
if (existingChar) {
updatedParty.characters[charIndex] = {
...existingChar,
id: existingChar.id,
position: existingChar.position,
character: existingChar.character,
uncapLevel: updatedChar.uncapLevel ?? updatedChar.uncap_level,
transcendenceStep: updatedChar.transcendenceStep ?? updatedChar.transcendence_step
}
}
return updatedParty
}
return updatedParty
}
}
}
return party // Return unchanged party if update failed
@ -825,15 +832,21 @@
if (updatedParty.weapons) {
const weaponIndex = updatedParty.weapons.findIndex((w: any) => w.id === gridWeaponId)
if (weaponIndex !== -1) {
// Preserve the weapon object reference but update uncap fields
updatedParty.weapons[weaponIndex] = {
...updatedParty.weapons[weaponIndex],
uncapLevel: updatedWeapon.uncapLevel ?? updatedWeapon.uncap_level,
transcendenceStep:
updatedWeapon.transcendenceStep ?? updatedWeapon.transcendence_step
// Preserve the weapon object reference but update uncap fields
const existingWeapon = updatedParty.weapons[weaponIndex]
if (existingWeapon) {
updatedParty.weapons[weaponIndex] = {
...existingWeapon,
id: existingWeapon.id,
position: existingWeapon.position,
weapon: existingWeapon.weapon,
uncapLevel: updatedWeapon.uncapLevel ?? updatedWeapon.uncap_level,
transcendenceStep:
updatedWeapon.transcendenceStep ?? updatedWeapon.transcendence_step
}
}
return updatedParty
}
return updatedParty
}
}
}
return party // Return unchanged party if update failed
@ -864,15 +877,21 @@
if (updatedParty.summons) {
const summonIndex = updatedParty.summons.findIndex((s: any) => s.id === gridSummonId)
if (summonIndex !== -1) {
// Preserve the summon object reference but update uncap fields
updatedParty.summons[summonIndex] = {
...updatedParty.summons[summonIndex],
uncapLevel: updatedSummon.uncapLevel ?? updatedSummon.uncap_level,
transcendenceStep:
updatedSummon.transcendenceStep ?? updatedSummon.transcendence_step
// Preserve the summon object reference but update uncap fields
const existingSummon = updatedParty.summons[summonIndex]
if (existingSummon) {
updatedParty.summons[summonIndex] = {
...existingSummon,
id: existingSummon.id,
position: existingSummon.position,
summon: existingSummon.summon,
uncapLevel: updatedSummon.uncapLevel ?? updatedSummon.uncap_level,
transcendenceStep:
updatedSummon.transcendenceStep ?? updatedSummon.transcendence_step
}
}
return updatedParty
}
return updatedParty
}
}
}
return party // Return unchanged party if update failed
@ -967,26 +986,26 @@
<DropdownMenu.Portal>
<DropdownMenu.Content class="dropdown-content" sideOffset={6} align="end">
{#if canEdit()}
<DropdownItem asChild>
<DropdownItem>
<button onclick={openEditDialog} disabled={loading}>Edit</button>
</DropdownItem>
{/if}
{#if authUserId}
<DropdownItem asChild>
<DropdownItem>
<button onclick={toggleFavorite} disabled={loading}>
{party.favorited ? 'Remove from favorites' : 'Add to favorites'}
</button>
</DropdownItem>
{/if}
<DropdownItem asChild>
<DropdownItem>
<button onclick={remixParty} disabled={loading}>Remix</button>
</DropdownItem>
{#if party.user?.id === authUserId}
<DropdownMenu.Separator class="dropdown-separator" />
<DropdownItem asChild>
<DropdownItem>
<button onclick={() => (deleteDialogOpen = true)} disabled={loading}>
Delete
</button>

View file

@ -1,8 +1,8 @@
<script lang="ts">
import type { PartyView } from '$lib/api/schemas/party'
import type { Party } from '$lib/types/api/party'
import GridRep from '$lib/components/reps/GridRep.svelte'
export let items: PartyView[] = []
export let items: Party[] = []
</script>
<ul class="collection" role="list">

View file

@ -12,8 +12,8 @@
type: 'character' | 'weapon' | 'summon'
item: GridCharacter | GridWeapon | GridSummon
itemData: any
gridUncapLevel: number | null
gridTranscendence: number | null
gridUncapLevel: number | null | undefined
gridTranscendence: number | null | undefined
}
let { type, item, itemData, gridUncapLevel, gridTranscendence }: Props = $props()

View file

@ -7,21 +7,22 @@
type Props = {
children: Snippet
href?: string
asChild?: boolean
class?: string
}
const { children, href, asChild = false, class: className = '' }: Props = $props()
const { children, href, class: className = '' }: Props = $props()
</script>
{#if href}
<DropdownMenu.Item class="dropdown-item {className}" asChild>
<a {href}>
{@render children()}
</a>
<DropdownMenu.Item class="dropdown-item {className}">
{#snippet child({ props })}
<a {...props} {href}>
{@render children()}
</a>
{/snippet}
</DropdownMenu.Item>
{:else}
<DropdownMenu.Item class="dropdown-item {className}" {asChild}>
<DropdownMenu.Item class="dropdown-item {className}">
{@render children()}
</DropdownMenu.Item>
{/if}

View file

@ -10,7 +10,7 @@
type?: 'character' | 'weapon' | 'summon'
editable?: boolean
interactive?: boolean
tabIndex?: number
tabindex?: number
onStarClick?: () => void
onFragmentClick?: (newStage: number) => void
onFragmentHover?: (newStage: number) => void
@ -22,7 +22,7 @@
type = 'character',
editable = false,
interactive = false,
tabIndex,
tabindex,
onStarClick,
onFragmentClick,
onFragmentHover
@ -168,7 +168,7 @@
onclick={handleClick}
onmouseleave={interactive ? handleMouseLeave : undefined}
bind:this={starElement}
{tabIndex}
{tabindex}
role={editable ? 'button' : undefined}
aria-label={editable ? 'Transcendence star' : undefined}
>
@ -399,4 +399,4 @@
transform: translateY(0);
}
}
</style>
</style>

View file

@ -6,17 +6,17 @@
interface Props {
type: 'character' | 'weapon' | 'summon'
rarity?: number
uncapLevel?: number
transcendenceStage?: number
flb?: boolean
ulb?: boolean
transcendence?: boolean
special?: boolean
className?: string
editable?: boolean
updateUncap?: (index: number) => void
updateTranscendence?: (index: number) => void
rarity?: number | undefined
uncapLevel?: number | null | undefined
transcendenceStage?: number | null | undefined
flb?: boolean | undefined
ulb?: boolean | undefined
transcendence?: boolean | undefined
special?: boolean | undefined
className?: string | undefined
editable?: boolean | undefined
updateUncap?: ((index: number) => void) | undefined
updateTranscendence?: ((index: number) => void) | undefined
}
interface StarRender {
@ -182,4 +182,4 @@
padding: 0;
align-items: center;
}
</style>
</style>

View file

@ -7,7 +7,7 @@
flb?: boolean
ulb?: boolean
index: number
tabIndex?: number
tabindex?: number
onStarClick: (index: number, empty: boolean) => void
}
@ -17,7 +17,7 @@
flb = false,
ulb = false,
index,
tabIndex,
tabindex,
onStarClick
}: Props = $props()
@ -33,7 +33,7 @@
class:mlb={!special}
class:flb
class:ulb
{tabIndex}
{tabindex}
onclick={handleClick}
role="button"
aria-label="Uncap star"
@ -106,4 +106,4 @@
}
}
}
</style>
</style>

View file

@ -2,10 +2,10 @@ import { sidebar } from '$lib/stores/sidebar.svelte'
import DescriptionSidebar from '$lib/components/sidebar/DescriptionSidebar.svelte'
interface DescriptionSidebarOptions {
title?: string
description?: string
canEdit?: boolean
onEdit?: () => void
title?: string | undefined
description?: string | undefined
canEdit?: boolean | undefined
onEdit?: (() => void) | undefined
}
export function openDescriptionSidebar(options: DescriptionSidebarOptions) {
@ -22,4 +22,4 @@ export function openDescriptionSidebar(options: DescriptionSidebarOptions) {
export function closeDescriptionSidebar() {
sidebar.close()
}
}

View file

@ -5,16 +5,16 @@ import type { Job, JobSkill } from '$lib/types/api/entities'
import type { JobSkillList } from '$lib/types/api/party'
interface JobSelectionOptions {
currentJobId?: string
onSelectJob?: (job: Job) => void
currentJobId?: string | undefined
onSelectJob?: ((job: Job) => void) | undefined
}
interface JobSkillSelectionOptions {
job?: Job
currentSkills?: JobSkillList
job?: Job | undefined
currentSkills?: JobSkillList | undefined
targetSlot: number
onSelectSkill?: (skill: JobSkill) => void
onRemoveSkill?: () => void
onSelectSkill?: ((skill: JobSkill) => void) | undefined
onRemoveSkill?: (() => void) | undefined
}
export function openJobSelectionSidebar(options: JobSelectionOptions) {
@ -59,4 +59,4 @@ export function openJobSkillSelectionSidebar(options: JobSkillSelectionOptions)
export function closeJobSidebar() {
sidebar.close()
}
}

View file

@ -51,8 +51,8 @@ export function getImageUrl(
id: string | number | null | undefined,
variant: ImageVariant,
options?: {
pose?: string // For character poses
element?: number // For element-specific weapon grids
pose?: string | undefined // For character poses
element?: number | undefined // For element-specific weapon grids
}
): string {
// Return placeholder if no ID
@ -191,4 +191,4 @@ export function getWeaponGridImage(
return getImageUrl('weapon', id, 'grid', { element: instanceElement })
}
return getImageUrl('weapon', id, 'grid')
}
}