From 697d96daecc287f50a73961a87fe2d458365ca5a Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 9 Apr 2026 18:48:28 +0200 Subject: [PATCH] fix(mana/web): unblock api-keys, reset-password, dashboard widgets, skilltree stats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six unrelated type-error pockets that were each blocking a different page from compiling clean. Grouped because none individually warrants its own commit and they all touch the same module's call sites. api-keys/+page.svelte - Removed the `key: undefined as unknown as string` workaround for stripping the secret from the local list. Replaced with a clean object-rest destructure that produces a row matching the ApiKey shape (no `key` field). The cast was the source of two type errors AND was lying about the runtime shape. - Badge `variant="secondary"` and `variant="outline"` aren't valid BadgeVariant — narrowed to `default` and `info` respectively. - Button `variant="destructive"` and Badge `variant="destructive"` don't exist in the shared-ui union — both → `danger`. - Rate-limit input bound a `number` to a `` component whose `value` is typed `string`. Switched to a string state and parseInt on submit. Prevents the binding cast that the type checker (correctly) rejected. reset-password/+page.svelte - Calling `authStore.resetPassword(token, password)` with two args on a method that takes one (sends the reset email). The method that actually performs the reset is `resetPasswordWithToken`. Two args, no API contract change needed. - `` — minlength isn't a prop on the shared Input component (it's not a passthrough wrapper). Removed; the runtime check still gates submit. dashboard/widgets/{Credits,Transactions}Widget.svelte - `let state = $state<...>(...)` — variable named `state` shadows the `$state` rune call, which TypeScript flags as "Block-scoped variable '$state' used before its declaration" + "Untyped function calls may not accept type arguments". Renamed both to `loadState`. dashboard/widgets/TasksTodayWidget.svelte - Referenced `task.dueTime`, which doesn't exist on LocalTask (only `dueDate`, ISO timestamp). Dropped the dead branch — the time was already encoded in `dueDate` and the widget never surfaced anything actionable from it anyway. skilltree/components/StatsOverview.svelte - Was manually wiring `.subscribe()` callbacks because the old queries.ts returned raw Dexie Observables. After the Observable→useLiveQueryWithDefault migration, those return `{value, loading, error}` instead — `subscribe` doesn't exist on them. Replaced the manual state plumbing with direct `.value` reads inside `$derived`. Net: less code, fewer levels of indirection. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../dashboard/widgets/CreditsWidget.svelte | 12 ++++---- .../dashboard/widgets/TasksTodayWidget.svelte | 9 ++---- .../widgets/TransactionsWidget.svelte | 12 ++++---- .../skilltree/components/StatsOverview.svelte | 20 +++++-------- .../src/routes/(app)/api-keys/+page.svelte | 28 +++++++++---------- .../routes/(auth)/reset-password/+page.svelte | 4 +-- 6 files changed, 35 insertions(+), 50 deletions(-) diff --git a/apps/mana/apps/web/src/lib/components/dashboard/widgets/CreditsWidget.svelte b/apps/mana/apps/web/src/lib/components/dashboard/widgets/CreditsWidget.svelte index dbce0ec69..c26558c04 100644 --- a/apps/mana/apps/web/src/lib/components/dashboard/widgets/CreditsWidget.svelte +++ b/apps/mana/apps/web/src/lib/components/dashboard/widgets/CreditsWidget.svelte @@ -9,23 +9,23 @@ import WidgetSkeleton from '../WidgetSkeleton.svelte'; import WidgetError from '../WidgetError.svelte'; - let state = $state<'loading' | 'success' | 'error'>('loading'); + let loadState = $state<'loading' | 'success' | 'error'>('loading'); let data = $state(null); let error = $state(null); let retrying = $state(false); async function load() { - if (!data) state = 'loading'; + if (!data) loadState = 'loading'; retrying = true; try { const balance = await creditsService.getBalance(); data = balance; - state = 'success'; + loadState = 'success'; } catch (e) { if (!data) { error = e instanceof Error ? e.message : 'Failed to load credits'; - state = 'error'; + loadState = 'error'; } } finally { retrying = false; @@ -45,9 +45,9 @@ {$_('dashboard.widgets.credits.title')} - {#if state === 'loading'} + {#if loadState === 'loading'} - {:else if state === 'error'} + {:else if loadState === 'error'} {:else if data}
diff --git a/apps/mana/apps/web/src/lib/components/dashboard/widgets/TasksTodayWidget.svelte b/apps/mana/apps/web/src/lib/components/dashboard/widgets/TasksTodayWidget.svelte index 3c9eafb2a..384bc373d 100644 --- a/apps/mana/apps/web/src/lib/components/dashboard/widgets/TasksTodayWidget.svelte +++ b/apps/mana/apps/web/src/lib/components/dashboard/widgets/TasksTodayWidget.svelte @@ -152,14 +152,9 @@ {/if}
- {#if task.dueTime || getSubtaskProgress(task)} + {#if getSubtaskProgress(task)}
- {#if task.dueTime} - {task.dueTime} - {/if} - {#if getSubtaskProgress(task)} - {getSubtaskProgress(task)} - {/if} + {getSubtaskProgress(task)}
{/if} diff --git a/apps/mana/apps/web/src/lib/components/dashboard/widgets/TransactionsWidget.svelte b/apps/mana/apps/web/src/lib/components/dashboard/widgets/TransactionsWidget.svelte index 9a8b1fc07..29e3a4f6e 100644 --- a/apps/mana/apps/web/src/lib/components/dashboard/widgets/TransactionsWidget.svelte +++ b/apps/mana/apps/web/src/lib/components/dashboard/widgets/TransactionsWidget.svelte @@ -9,22 +9,22 @@ import WidgetSkeleton from '../WidgetSkeleton.svelte'; import WidgetError from '../WidgetError.svelte'; - let state = $state<'loading' | 'success' | 'error'>('loading'); + let loadState = $state<'loading' | 'success' | 'error'>('loading'); let data = $state([]); let error = $state(null); let retrying = $state(false); async function load() { - state = 'loading'; + loadState = 'loading'; retrying = true; try { const transactions = await creditsService.getTransactions(5); data = transactions; - state = 'success'; + loadState = 'success'; } catch (e) { error = e instanceof Error ? e.message : 'Failed to load transactions'; - state = 'error'; + loadState = 'error'; } finally { retrying = false; } @@ -63,9 +63,9 @@ - {#if state === 'loading'} + {#if loadState === 'loading'} - {:else if state === 'error'} + {:else if loadState === 'error'} {:else if data.length === 0}

diff --git a/apps/mana/apps/web/src/lib/modules/skilltree/components/StatsOverview.svelte b/apps/mana/apps/web/src/lib/modules/skilltree/components/StatsOverview.svelte index ddd89b6c3..c8c482bce 100644 --- a/apps/mana/apps/web/src/lib/modules/skilltree/components/StatsOverview.svelte +++ b/apps/mana/apps/web/src/lib/modules/skilltree/components/StatsOverview.svelte @@ -3,23 +3,17 @@ import { buildAchievementStatus, getAchievementStats } from '../stores/achievements.svelte'; import { Trophy, Lightning, Target, Fire, Medal } from '@mana/shared-icons'; - // Reactive live queries + // Reactive live queries — useLiveQueryWithDefault wraps Dexie's + // liveQuery and exposes a `.value` getter backed by $state, so we + // just read it inside $derived without manual subscribe plumbing. const allSkills = useAllSkills(); const allActivities = useAllActivities(); const allAchievementsRaw = useAllAchievements(); - let skills = $state([]); - let activities = $state([]); - let achievementsRaw = $state([]); - - $effect(() => { - allSkills.subscribe((v) => (skills = v ?? [])); - allActivities.subscribe((v) => (activities = v ?? [])); - allAchievementsRaw.subscribe((v) => (achievementsRaw = v ?? [])); - }); - - const userStats = $derived(computeUserStats(skills, activities)); - const achievementStats = $derived(getAchievementStats(buildAchievementStatus(achievementsRaw))); + const userStats = $derived(computeUserStats(allSkills.value, allActivities.value)); + const achievementStats = $derived( + getAchievementStats(buildAchievementStatus(allAchievementsRaw.value)) + );

diff --git a/apps/mana/apps/web/src/routes/(app)/api-keys/+page.svelte b/apps/mana/apps/web/src/routes/(app)/api-keys/+page.svelte index 5e4b12dab..02345a6a7 100644 --- a/apps/mana/apps/web/src/routes/(app)/api-keys/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/api-keys/+page.svelte @@ -15,7 +15,9 @@ let creating = $state(false); let newKeyName = $state(''); let newKeyScopes = $state<{ stt: boolean; tts: boolean }>({ stt: true, tts: true }); - let newKeyRateLimit = $state(60); + // Stored as string because the shared component binds to a + // string value; we parseInt at submit time. + let newKeyRateLimit = $state('60'); let createdKey = $state(null); let copied = $state(false); @@ -62,21 +64,17 @@ const result = await apiKeysService.create({ name: newKeyName.trim(), scopes, - rateLimitRequests: newKeyRateLimit, + rateLimitRequests: parseInt(newKeyRateLimit, 10) || 60, }); if (result.error) { error = result.error; } else if (result.data) { createdKey = result.data; - // Add to list (without the secret key) - apiKeys = [ - ...apiKeys, - { - ...result.data, - key: undefined as unknown as string, // Remove secret from local state - }, - ]; + // Add to list — strip the secret `key` field that only appears on + // creation responses, so the local list matches the ApiKey shape. + const { key: _omit, ...withoutSecret } = result.data; + apiKeys = [...apiKeys, withoutSecret]; } creating = false; @@ -110,7 +108,7 @@ createdKey = null; newKeyName = ''; newKeyScopes = { stt: true, tts: true }; - newKeyRateLimit = 60; + newKeyRateLimit = '60'; copied = false; } @@ -186,8 +184,8 @@
{key.name} - {key.scopes.join(', ')} - {key.rateLimitRequests}/min + {key.scopes.join(', ')} + {key.rateLimitRequests}/min