mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 06:41:08 +02:00
fix(mana/web): clear remaining type errors — long-tail sweep
The last cleanup pass after the package-level fixes. Each of the
~30 files below had 1-2 distinct errors; they're grouped because
none individually justifies its own commit and they're all the same
shape: small drift between a call site and the type system the
existing-code-doesn't-need-to-change refactor that gets it to clean.
Highlights by file:
vite.config.ts
Switched `defineConfig` import from `vite` to `vitest/config` so
the inline `test:` block (vitest unit-test exclude rule) is
recognized at the type layer. Was the last single error standing.
routes/(app)/news/+page.svelte
Replaced `{#each ranked as { article } (article.id)}` destructure
with `{#each ranked as scored (scored.article.id)}` + two
`{@const}` rows. The destructured-each + immediate-`@const`
combination tripped a Svelte compiler placement error.
routes/(app)/contacts/[id], modules/calendar/EventForm
`(x as Record<string, unknown>)` casts were rejected because the
source type doesn't have a string index signature. Two-step
cast: `as unknown as Record<string, unknown>`.
routes/(app)/inventory/collections/[id]/edit
`collection.schema.fields` round-trips through JSON in the Dexie
row, which widens `type` to plain `string`. Cast back to
`FieldDefinition[]` at the read site; the runtime values match
the FieldType union.
routes/(app)/presi/deck/[id], modules/zitare/QuoteCard,
modules/memoro/views/DetailView
- presi: `currentDeck?.name` → `?.title` (Deck has `title`, not
`name`).
- QuoteCard: `let authorBioText = $derived(() => {...})` was
storing the arrow function itself. Switch to `$derived.by(...)`.
- memoro DetailView: explicit `<QueuedTask | null>` generic on
the useLiveQueryWithDefault call so the unknown-typed default
doesn't poison downstream state.
routes/(app)/memoro/{,/[id]}/+page.svelte + modules/memoro/queries.ts
The Tag flowing through these components is the `@mana/shared-tags`
shape (from `useAllTags`), not memoro's local Tag (which has
isPinned/sortOrder for a UI we never built). Aligned all three
files to the shared shape so the Tag[] arrays compose without
property mismatches.
modules/{questions,context}/index.ts
Re-exported names that didn't exist:
- `questionCollectionTable` → `qCollectionTable`
- `contextDocumentTable` → `documentTable`
Both were leftover from a long-ago rename that the consumers
still call by the new name.
modules/picture/stores/images.svelte.ts, modules/times/EntryItem
- images: `toggleField()` wants a string-keyed Table<>; cast at
the call site (runtime keys are UUIDs anyway).
- EntryItem: `autoSave(updates: Record<string, unknown>)` won't
fit Dexie's `UpdateSpec<LocalTimeEntry>`. Narrowed to
`Partial<LocalTimeEntry>` and added the missing import.
modules/todo: TodoPage + QuickAddTask
- TodoPage was passing `onOpen` to TaskItem (which only accepts
`onClick` + `onContextMenu` + `onToggleComplete`). Replaced
with the proper triplet on the recently-completed branch.
- QuickAddTask `locale?: string` widened the input past the
`ParserLocale` union the parser actually accepts. Imported
the union and tightened the prop.
modules/presi/views/DetailView
`decksStore.deleteDeck` returns `Promise<boolean>`, but
`deleteWithUndo()` expects `Promise<void>`. Wrapped in an async
arrow that discards the return.
routes/(app)/citycorners/.../edit
Self-referential `let locId = $derived(locId ?? '')` from a
search-and-replace gone wrong in the previous commit batch.
Restored to `$derived($page.params.id ?? '')`.
routes/(app)/+layout.svelte, lib/components/onboarding/OnboardingWizard
- layout: `(window as Record<string, unknown>)` → two-step
`(window as unknown as Record<...>)` cast. Same shape as the
contacts/EventForm fixes.
- OnboardingWizard: added optional `onSkip?: () => void` prop
so the layout's analytics callback type-checks. The wizard
always also calls `onComplete()`, so the modal still closes
cleanly without onSkip.
routes/(app)/api-keys/+page.svelte
Removed `min={1}` / `max={1000}` props from the shared `<Input>`
component (it's not a passthrough wrapper for native HTML
attributes). Runtime validation still gates submit.
routes/(auth)/forgot-password
`authStore.forgotPassword(email)` doesn't exist; the wrapper
exposes `resetPassword(email)` for the send-email entry point.
Renamed.
routes/(app)/{gifts,llm-test}, lib/content/help/index.test
- gifts: `balance.freeCreditsRemaining` is now optional (added
in the credits commit). Defaulted to 0 in the math.
- llm-test: enqueueTaskNow union of two tasks with different
output types — widened with `as any` for the enqueue call.
- help index.test: `content.contact` is optional, asserted with
non-null `!`.
lib/components/{SessionWarning,DashboardGrid,onboarding/OnboardingWizard}
- SessionWarning: was calling `getAccessTokenSync` (doesn't
exist) and `refreshToken` (doesn't exist). Switched to
`getAccessToken()` (async, returns Promise) and `getValidToken()`
(refreshes under the hood when expired).
- DashboardGrid: `error?.message` on a `{}`-typed boundary
arg. Cast to `Error | undefined`.
dashboard widgets: ContextDocs / ClockTimers / ActivityFeed
- ContextDocs: `getSpaceName(spaceId: string)` widened to
`string | null | undefined` so the optional doc.spaceId
flows in cleanly.
- ClockTimers: `formatRepeatDays`/`formatRemaining` widened to
accept null|undefined.
- ActivityFeed: `Activity` icon doesn't exist in
`@mana/shared-icons`/phosphor-svelte. Replaced with `Pulse`
everywhere in the file.
lib/app-registry/registry.spec
`Set<AppIconId>.has(stringId)` rejected because the union is
narrower. Widened the Set to `Set<string>`.
Net: -16 type errors. Final count: 0.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c31ce4448f
commit
80b23dd9ff
33 changed files with 110 additions and 66 deletions
|
|
@ -48,7 +48,7 @@ const BRANDING_ONLY = new Set([
|
|||
|
||||
describe('app registry ↔ MANA_APPS consistency', () => {
|
||||
it('every workbench-registry app has a MANA_APPS entry or is in WORKBENCH_ONLY', () => {
|
||||
const brandingIds = new Set(MANA_APPS.map((a) => a.id));
|
||||
const brandingIds = new Set<string>(MANA_APPS.map((a) => a.id));
|
||||
const unaccounted = getAllApps()
|
||||
.map((a) => a.id)
|
||||
.filter((id) => !brandingIds.has(id) && !WORKBENCH_ONLY.has(id));
|
||||
|
|
|
|||
|
|
@ -14,11 +14,13 @@
|
|||
};
|
||||
});
|
||||
|
||||
function checkSession() {
|
||||
async function checkSession() {
|
||||
if (!authStore.isAuthenticated) return;
|
||||
|
||||
// Try to get token expiry from JWT
|
||||
const token = authStore.getAccessTokenSync?.();
|
||||
// Pull the latest access token. The wrapper doesn't expose a sync
|
||||
// variant — getAccessToken() reads from the storage adapter behind
|
||||
// a Promise, which is fine for a polling-style warning.
|
||||
const token = await authStore.getAccessToken();
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
|
|
@ -40,7 +42,11 @@
|
|||
|
||||
async function handleRefresh() {
|
||||
try {
|
||||
await authStore.refreshToken?.();
|
||||
// getValidToken() returns the current token if still valid, or
|
||||
// triggers a refresh under the hood when it isn't. Same effect
|
||||
// as the old `refreshToken()` call site, with the wrapper's
|
||||
// existing public surface.
|
||||
await authStore.getValidToken();
|
||||
showWarning = false;
|
||||
} catch {
|
||||
// Refresh failed, user will be logged out
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
{widget.id} fehlgeschlagen
|
||||
</p>
|
||||
<p class="mb-3 text-xs text-red-500 dark:text-red-500/70">
|
||||
{error?.message || 'Unbekannter Fehler'}
|
||||
{(error as Error | undefined)?.message || 'Unbekannter Fehler'}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
Heart,
|
||||
Lightning,
|
||||
Clock,
|
||||
Activity,
|
||||
Pulse,
|
||||
} from '@mana/shared-icons';
|
||||
import { getIconComponent } from '@mana/shared-icons';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
<div>
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h3 class="flex items-center gap-2 text-lg font-semibold">
|
||||
<Activity size={20} />
|
||||
<Pulse size={20} />
|
||||
{$_('dashboard.widgets.activity_feed.title', { default: 'Aktivität' })}
|
||||
</h3>
|
||||
</div>
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
</div>
|
||||
{:else if items.length === 0}
|
||||
<div class="py-6 text-center">
|
||||
<Activity size={32} class="mx-auto mb-2 text-muted-foreground" />
|
||||
<Pulse size={32} class="mx-auto mb-2 text-muted-foreground" />
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{$_('dashboard.widgets.activity_feed.empty', { default: 'Noch keine Aktivität' })}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -11,14 +11,15 @@
|
|||
|
||||
const DAY_NAMES = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
|
||||
|
||||
function formatRepeatDays(days?: number[]): string {
|
||||
function formatRepeatDays(days?: number[] | null): string {
|
||||
if (!days || days.length === 0) return 'Einmalig';
|
||||
if (days.length === 7) return 'Täglich';
|
||||
if (days.length === 5 && !days.includes(0) && !days.includes(6)) return 'Werktags';
|
||||
return days.map((d) => DAY_NAMES[d]).join(', ');
|
||||
}
|
||||
|
||||
function formatRemaining(seconds: number): string {
|
||||
function formatRemaining(seconds: number | null | undefined): string {
|
||||
if (seconds == null) return '—';
|
||||
const h = Math.floor(seconds / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
const s = seconds % 60;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@
|
|||
const docs = useRecentDocuments(5);
|
||||
const spaces = useSpaces();
|
||||
|
||||
function getSpaceName(spaceId: string): string {
|
||||
function getSpaceName(spaceId: string | null | undefined): string {
|
||||
if (!spaceId) return '';
|
||||
const space = (spaces.value ?? []).find((s) => s.id === spaceId);
|
||||
return space?.name ?? '';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,16 @@
|
|||
|
||||
interface Props {
|
||||
onComplete: () => void;
|
||||
/**
|
||||
* Optional callback fired when the user explicitly skips the wizard
|
||||
* (vs. completing it normally). Layout consumers use it to track
|
||||
* skip-rate analytics; the default skip path still calls onComplete
|
||||
* so the modal closes either way.
|
||||
*/
|
||||
onSkip?: () => void;
|
||||
}
|
||||
|
||||
let { onComplete }: Props = $props();
|
||||
let { onComplete, onSkip }: Props = $props();
|
||||
|
||||
// Reference to profile name for auto-save on step transition
|
||||
let profileNameRef = $state('');
|
||||
|
|
@ -60,6 +67,7 @@
|
|||
|
||||
function handleSkip() {
|
||||
onboardingStore.skip();
|
||||
onSkip?.();
|
||||
onComplete();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ describe('Mana Help Content', () => {
|
|||
|
||||
expect(content.features).toBeDefined();
|
||||
expect(content.contact).toBeDefined();
|
||||
expect(content.contact.supportEmail).toBe('support@mana.how');
|
||||
expect(content.contact!.supportEmail).toBe('support@mana.how');
|
||||
});
|
||||
|
||||
it('returns valid English content', () => {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
let calendarId = $state(event?.calendarId || '');
|
||||
let recurrenceRule = $state(event?.recurrenceRule || '');
|
||||
let selectedTagIds = $state<string[]>(
|
||||
((event as Record<string, unknown>)?.tagIds as string[]) ?? []
|
||||
((event as unknown as Record<string, unknown>)?.tagIds as string[]) ?? []
|
||||
);
|
||||
|
||||
const allTags = useAllTags();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Context module — barrel exports.
|
||||
*/
|
||||
|
||||
export { contextSpaceTable, contextDocumentTable, CONTEXT_GUEST_SEED } from './collections';
|
||||
export { contextSpaceTable, documentTable, CONTEXT_GUEST_SEED } from './collections';
|
||||
export * from './queries';
|
||||
export type {
|
||||
LocalContextSpace,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@
|
|||
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
|
||||
import { db } from '$lib/data/database';
|
||||
import { decryptRecords } from '$lib/data/crypto';
|
||||
// `useAllTags` re-exports the shared-tags hook below; the actual tag
|
||||
// objects flowing through this module are the shared shape, not the
|
||||
// memoro/types.Tag declared next to LocalMemoTag. Importing the shared
|
||||
// type here keeps `getTagsForMemo` and friends in sync with what the
|
||||
// hook actually returns.
|
||||
import type { Tag } from '@mana/shared-tags';
|
||||
import type {
|
||||
LocalMemo,
|
||||
LocalMemory,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import DetailViewShell from '$lib/components/DetailViewShell.svelte';
|
||||
import { memosStore } from '../stores/memos.svelte';
|
||||
import { llmQueueDb } from '$lib/llm-queue';
|
||||
import type { QueuedTask } from '@mana/shared-llm';
|
||||
import type { LlmTier } from '@mana/shared-llm';
|
||||
import { PushPin } from '@mana/shared-icons';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
|
|
@ -97,19 +98,16 @@
|
|||
// Reactive lookup of any LLM queue task tagged with this memo, so the
|
||||
// UI can show "Titel wird generiert..." while a generateTitleTask is
|
||||
// pending or running. Returns the most recent task row (any state).
|
||||
const titleQueueRow = useLiveQueryWithDefault(
|
||||
async () => {
|
||||
if (!memoId) return null;
|
||||
const rows = await llmQueueDb.tasks
|
||||
.where('[refType+refId]')
|
||||
.equals(['memo', memoId])
|
||||
.and((t) => t.taskName === 'common.generateTitle')
|
||||
.reverse()
|
||||
.sortBy('enqueuedAt');
|
||||
return rows[0] ?? null;
|
||||
},
|
||||
null as Awaited<ReturnType<typeof llmQueueDb.tasks.toArray>>[number] | null
|
||||
);
|
||||
const titleQueueRow = useLiveQueryWithDefault<QueuedTask | null>(async () => {
|
||||
if (!memoId) return null;
|
||||
const rows = await llmQueueDb.tasks
|
||||
.where('[refType+refId]')
|
||||
.equals(['memo', memoId])
|
||||
.and((t) => t.taskName === 'common.generateTitle')
|
||||
.reverse()
|
||||
.sortBy('enqueuedAt');
|
||||
return rows[0] ?? null;
|
||||
}, null);
|
||||
|
||||
const titleIsGenerating = $derived(
|
||||
titleQueueRow.value?.state === 'pending' || titleQueueRow.value?.state === 'running'
|
||||
|
|
|
|||
|
|
@ -43,7 +43,14 @@ export const imagesStore = {
|
|||
async toggleFavorite(id: string) {
|
||||
error = null;
|
||||
try {
|
||||
await toggleField(imageTable(), id, 'isFavorite');
|
||||
// Cast: toggleField expects a string-keyed Table, but db.table()
|
||||
// returns the generic IndexableType-keyed shape. The runtime keys
|
||||
// for `images` are all strings (UUIDs).
|
||||
await toggleField(
|
||||
imageTable() as unknown as Parameters<typeof toggleField>[0],
|
||||
id,
|
||||
'isFavorite'
|
||||
);
|
||||
PictureEvents.imageFavorited();
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,11 @@
|
|||
onConfirmDelete={() =>
|
||||
detail.deleteWithUndo({
|
||||
label: 'Präsentation gelöscht',
|
||||
delete: () => decksStore.deleteDeck(deckId),
|
||||
// deleteDeck returns Promise<boolean>; the deleteWithUndo helper
|
||||
// expects Promise<void>, so we discard the result.
|
||||
delete: async () => {
|
||||
await decksStore.deleteDeck(deckId);
|
||||
},
|
||||
goBack,
|
||||
})}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,12 +2,7 @@
|
|||
* Questions module — barrel exports.
|
||||
*/
|
||||
|
||||
export {
|
||||
questionCollectionTable,
|
||||
questionTable,
|
||||
answerTable,
|
||||
QUESTIONS_GUEST_SEED,
|
||||
} from './collections';
|
||||
export { qCollectionTable, questionTable, answerTable, QUESTIONS_GUEST_SEED } from './collections';
|
||||
export * from './queries';
|
||||
export type {
|
||||
LocalCollection,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import { timeEntryTable } from '$lib/modules/times/collections';
|
||||
import { formatDurationCompact } from '$lib/modules/times/queries';
|
||||
import { CurrencyDollar } from '@mana/shared-icons';
|
||||
import type { TimeEntry, Project, Client } from '$lib/modules/times/types';
|
||||
import type { TimeEntry, Project, Client, LocalTimeEntry } from '$lib/modules/times/types';
|
||||
import { ConfirmationModal } from '@mana/shared-ui';
|
||||
|
||||
let {
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
|
||||
let saveDebounce: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function autoSave(updates: Record<string, unknown>) {
|
||||
function autoSave(updates: Partial<LocalTimeEntry>) {
|
||||
if (saveDebounce) clearTimeout(saveDebounce);
|
||||
saveDebounce = setTimeout(async () => {
|
||||
await timeEntryTable.update(entry.id, updates);
|
||||
|
|
|
|||
|
|
@ -10,11 +10,12 @@
|
|||
import type { ParsedTask } from '../utils/task-parser';
|
||||
import type { TaskTag } from '../types';
|
||||
import { getPriorityColor } from '../queries';
|
||||
import type { ParserLocale } from '@mana/shared-utils';
|
||||
import { Plus, CalendarBlank, Flag, ArrowsClockwise, Timer, Tag, Info } from '@mana/shared-icons';
|
||||
|
||||
interface Props {
|
||||
labels?: TaskTag[];
|
||||
locale?: string;
|
||||
locale?: ParserLocale;
|
||||
onShowSyntaxHelp?: () => void;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -285,7 +285,9 @@
|
|||
<TaskItem
|
||||
{task}
|
||||
compact={false}
|
||||
onOpen={onOpenTask ? () => onOpenTask(task) : undefined}
|
||||
onToggleComplete={() => tasksStore.toggleComplete(task.id)}
|
||||
onClick={() => onOpenTask?.(task)}
|
||||
onContextMenu={() => {}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,11 @@
|
|||
let quoteText = $derived(quotesStore.getText(quote));
|
||||
let showBio = $state(false);
|
||||
|
||||
// Get author bio in current language
|
||||
let authorBioText = $derived(() => {
|
||||
// Get author bio in current language. `$derived.by` is the variant
|
||||
// that takes a thunk; plain `$derived(expr)` would have stored the
|
||||
// arrow function itself, making `authorBioText` always truthy and
|
||||
// the {#if} below dead.
|
||||
let authorBioText = $derived.by(() => {
|
||||
if (!quote.authorBio) return '';
|
||||
const lang = quotesStore.language === 'original' ? 'de' : quotesStore.language;
|
||||
return quote.authorBio[lang] || quote.authorBio.de || '';
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@
|
|||
// ── Sync ────────────────────────────────────────────────
|
||||
const SYNC_SERVER_URL =
|
||||
(typeof window !== 'undefined' &&
|
||||
(window as Record<string, unknown>).__PUBLIC_SYNC_SERVER_URL__) ||
|
||||
(window as unknown as Record<string, unknown>).__PUBLIC_SYNC_SERVER_URL__) ||
|
||||
import.meta.env.PUBLIC_SYNC_SERVER_URL ||
|
||||
'http://localhost:3050';
|
||||
let unifiedSync: ReturnType<typeof createUnifiedSync> | null = null;
|
||||
|
|
|
|||
|
|
@ -388,14 +388,7 @@
|
|||
<div class="mb-6">
|
||||
<label for="rateLimit" class="block text-sm font-medium mb-2">Rate Limit</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<Input
|
||||
type="number"
|
||||
id="rateLimit"
|
||||
bind:value={newKeyRateLimit}
|
||||
min={1}
|
||||
max={1000}
|
||||
class="w-24"
|
||||
/>
|
||||
<Input type="number" id="rateLimit" bind:value={newKeyRateLimit} class="w-24" />
|
||||
<span class="text-sm text-muted-foreground">requests per minute</span>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-muted-foreground">
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
const cityCtx = getContext<{ value: LocalCity | undefined }>('currentCity');
|
||||
let city = $derived(cityCtx.value);
|
||||
let citySlug = $derived($page.params.slug ?? '');
|
||||
let locId = $derived(locId ?? '');
|
||||
let locId = $derived($page.params.id ?? '');
|
||||
|
||||
let loading = $state(true);
|
||||
let name = $state('');
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@
|
|||
twitter: contact.twitter,
|
||||
instagram: contact.instagram,
|
||||
github: contact.github,
|
||||
tagIds: ((contact as Record<string, unknown>).tagIds as string[]) ?? [],
|
||||
tagIds: ((contact as unknown as Record<string, unknown>).tagIds as string[]) ?? [],
|
||||
};
|
||||
isEditing = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -437,7 +437,7 @@
|
|||
<div class="mb-6 rounded-lg bg-surface p-4 text-center">
|
||||
<p class="text-sm text-muted-foreground">Verfügbare Credits</p>
|
||||
<p class="text-2xl font-bold text-primary">
|
||||
{formatCredits(balance.balance + balance.freeCreditsRemaining)}
|
||||
{formatCredits(balance.balance + (balance.freeCreditsRemaining ?? 0))}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { collectionsStore } from '$lib/modules/inventory/stores/collections.svelte';
|
||||
import { getCollectionById } from '$lib/modules/inventory/queries';
|
||||
import type { Collection } from '$lib/modules/inventory/queries';
|
||||
import type { CollectionSchema } from '$lib/modules/inventory/constants';
|
||||
import type { CollectionSchema, FieldDefinition } from '$lib/modules/inventory/constants';
|
||||
import SchemaEditor from '$lib/modules/inventory/components/fields/SchemaEditor.svelte';
|
||||
|
||||
const collectionsCtx: { readonly value: Collection[] } = getContext('collections');
|
||||
|
|
@ -25,7 +25,11 @@
|
|||
name = collection.name;
|
||||
description = collection.description || '';
|
||||
icon = collection.icon || '';
|
||||
schema = { fields: [...collection.schema.fields] };
|
||||
// `collection.schema.fields` round-trips through JSON in the
|
||||
// Dexie row, which widens `type` to plain `string`. Cast back
|
||||
// to the FieldDefinition union — the runtime values match the
|
||||
// FieldType union, the loss is purely at the type layer.
|
||||
schema = { fields: [...collection.schema.fields] as FieldDefinition[] };
|
||||
loaded = true;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -67,7 +67,12 @@
|
|||
);
|
||||
|
||||
async function enqueueTaskNow(task: typeof extractDateTask | typeof summarizeTextTask) {
|
||||
queueLastEnqueuedId = await llmTaskQueue.enqueue(task, { text: queueInput });
|
||||
// The two LlmTask shapes have different output types but identical
|
||||
// {text: string} input, so we widen `task` to `any` for the
|
||||
// enqueue call — TypeScript can't unify the union arg with the
|
||||
// generic without specializing per task.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
queueLastEnqueuedId = await llmTaskQueue.enqueue(task as any, { text: queueInput });
|
||||
}
|
||||
|
||||
// --- Router tab state ---
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
formatDuration,
|
||||
getStatusLabel,
|
||||
} from '$lib/modules/memoro/queries';
|
||||
import type { Memo, Tag, LocalMemoTag } from '$lib/modules/memoro/types';
|
||||
import type { Memo, LocalMemoTag } from '$lib/modules/memoro/types';
|
||||
import type { Tag } from '@mana/shared-tags';
|
||||
import {
|
||||
Plus,
|
||||
MagnifyingGlass,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
formatDuration,
|
||||
getStatusLabel,
|
||||
} from '$lib/modules/memoro/queries';
|
||||
import type { Memo, Memory, Tag, LocalMemoTag } from '$lib/modules/memoro/types';
|
||||
import type { Memo, Memory, LocalMemoTag } from '$lib/modules/memoro/types';
|
||||
import type { Tag } from '@mana/shared-tags';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Trash,
|
||||
|
|
|
|||
|
|
@ -314,7 +314,8 @@
|
|||
</div>
|
||||
{:else}
|
||||
<div class="card-grid">
|
||||
{#each ranked as { article } (article.id)}
|
||||
{#each ranked as scored (scored.article.id)}
|
||||
{@const article = scored.article}
|
||||
{@const isSaved = interestedIds.has(article.id)}
|
||||
<article class="card">
|
||||
{#if article.imageUrl}
|
||||
|
|
|
|||
|
|
@ -402,7 +402,7 @@
|
|||
visible={showShare}
|
||||
onClose={() => (showShare = false)}
|
||||
url={typeof window !== 'undefined' ? `${window.location.origin}/presi/deck/${deckId}` : ''}
|
||||
title={currentDeck?.name ?? 'Presentation'}
|
||||
title={currentDeck?.title ?? 'Presentation'}
|
||||
source="presi"
|
||||
description={currentDeck?.description ?? ''}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@
|
|||
let searchTerm = $state('');
|
||||
let sortBy = $state<'default' | 'author'>('default');
|
||||
|
||||
// Filtered and sorted quotes
|
||||
let displayedQuotes = $derived<Quote[]>(() => {
|
||||
// Filtered and sorted quotes — `$derived.by` is the variant that
|
||||
// takes a thunk; plain `$derived(expr)` only takes a single
|
||||
// expression.
|
||||
let displayedQuotes = $derived.by<Quote[]>(() => {
|
||||
let filtered = quotes;
|
||||
|
||||
// Filter by search
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@
|
|||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
|
||||
async function handleForgotPassword(email: string) {
|
||||
return authStore.forgotPassword(email);
|
||||
// resetPassword is the wrapper's "send-the-email" entry point.
|
||||
// resetPasswordWithToken is the actually-perform-the-reset step.
|
||||
return authStore.resetPassword(email);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
// `defineConfig` from vitest/config is a superset of vite's that
|
||||
// recognizes the `test:` block. Without this, the inline test exclude
|
||||
// below trips a "test does not exist on UserConfigExport" type error.
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { SvelteKitPWA } from '@vite-pwa/sveltekit';
|
||||
import { createPWAConfig } from '@mana/shared-pwa';
|
||||
import { MANA_SHARED_PACKAGES, getBuildDefines } from '@mana/shared-vite-config';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue