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 series?: number
/** URL for entity image */ /** URL for entity image */
imageUrl?: string imageUrl?: string
/** Type of entity (lowercase for compatibility) */
type?: 'weapon' | 'character' | 'summon'
/** Type of entity */ /** Type of entity */
searchableType: 'Weapon' | 'Character' | 'Summon' searchableType: 'Weapon' | 'Character' | 'Summon'
} }
@ -294,4 +296,4 @@ export class SearchAdapter extends BaseAdapter {
* Default singleton instance for search operations * Default singleton instance for search operations
* Use this for most search needs unless you need custom configuration * 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.Portal>
<DropdownMenu.Content class="dropdown-content" sideOffset={5}> <DropdownMenu.Content class="dropdown-content" sideOffset={5}>
<DropdownItem asChild> <DropdownItem>
<button onclick={() => (settingsModalOpen = true)}> <button onclick={() => (settingsModalOpen = true)}>
{m.nav_settings()} {m.nav_settings()}
</button> </button>
@ -201,7 +201,7 @@
{/if} {/if}
{#if isAuth} {#if isAuth}
<DropdownMenu.Separator class="dropdown-separator" /> <DropdownMenu.Separator class="dropdown-separator" />
<DropdownItem asChild> <DropdownItem>
<button onclick={handleLogout}>{m.nav_logout()}</button> <button onclick={handleLogout}>{m.nav_logout()}</button>
</DropdownItem> </DropdownItem>
{/if} {/if}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,8 @@
<script lang="ts"> <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' import GridRep from '$lib/components/reps/GridRep.svelte'
export let items: PartyView[] = [] export let items: Party[] = []
</script> </script>
<ul class="collection" role="list"> <ul class="collection" role="list">

View file

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

View file

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

View file

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

View file

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

View file

@ -7,7 +7,7 @@
flb?: boolean flb?: boolean
ulb?: boolean ulb?: boolean
index: number index: number
tabIndex?: number tabindex?: number
onStarClick: (index: number, empty: boolean) => void onStarClick: (index: number, empty: boolean) => void
} }
@ -17,7 +17,7 @@
flb = false, flb = false,
ulb = false, ulb = false,
index, index,
tabIndex, tabindex,
onStarClick onStarClick
}: Props = $props() }: Props = $props()
@ -33,7 +33,7 @@
class:mlb={!special} class:mlb={!special}
class:flb class:flb
class:ulb class:ulb
{tabIndex} {tabindex}
onclick={handleClick} onclick={handleClick}
role="button" role="button"
aria-label="Uncap star" 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' import DescriptionSidebar from '$lib/components/sidebar/DescriptionSidebar.svelte'
interface DescriptionSidebarOptions { interface DescriptionSidebarOptions {
title?: string title?: string | undefined
description?: string description?: string | undefined
canEdit?: boolean canEdit?: boolean | undefined
onEdit?: () => void onEdit?: (() => void) | undefined
} }
export function openDescriptionSidebar(options: DescriptionSidebarOptions) { export function openDescriptionSidebar(options: DescriptionSidebarOptions) {
@ -22,4 +22,4 @@ export function openDescriptionSidebar(options: DescriptionSidebarOptions) {
export function closeDescriptionSidebar() { export function closeDescriptionSidebar() {
sidebar.close() 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' import type { JobSkillList } from '$lib/types/api/party'
interface JobSelectionOptions { interface JobSelectionOptions {
currentJobId?: string currentJobId?: string | undefined
onSelectJob?: (job: Job) => void onSelectJob?: ((job: Job) => void) | undefined
} }
interface JobSkillSelectionOptions { interface JobSkillSelectionOptions {
job?: Job job?: Job | undefined
currentSkills?: JobSkillList currentSkills?: JobSkillList | undefined
targetSlot: number targetSlot: number
onSelectSkill?: (skill: JobSkill) => void onSelectSkill?: ((skill: JobSkill) => void) | undefined
onRemoveSkill?: () => void onRemoveSkill?: (() => void) | undefined
} }
export function openJobSelectionSidebar(options: JobSelectionOptions) { export function openJobSelectionSidebar(options: JobSelectionOptions) {
@ -59,4 +59,4 @@ export function openJobSkillSelectionSidebar(options: JobSkillSelectionOptions)
export function closeJobSidebar() { export function closeJobSidebar() {
sidebar.close() sidebar.close()
} }

View file

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