Attempt to fix note resizing with React keys and Muuri sync

This commit is contained in:
2026-01-24 19:52:13 +01:00
parent d59ec592eb
commit 8e35780717
48 changed files with 3369 additions and 279 deletions

View File

@@ -177,7 +177,7 @@ export function AI_TESTER({ type }: { type: 'tags' | 'embeddings' }) {
{type === 'tags' && result.success && result.tags && (
<div className="space-y-3">
<div className="flex items-center gap-2">
<Info className="h-4 w-4 text-blue-600" />
<Info className="h-4 w-4 text-primary" />
<span className="text-sm font-medium">Generated Tags:</span>
</div>
<div className="flex flex-wrap gap-2">
@@ -247,7 +247,7 @@ export function AI_TESTER({ type }: { type: 'tags' | 'embeddings' }) {
{/* Loading State */}
{isLoading && (
<div className="text-center py-4">
<Loader2 className="h-8 w-8 animate-spin mx-auto text-blue-600" />
<Loader2 className="h-8 w-8 animate-spin mx-auto text-primary" />
<p className="text-sm text-muted-foreground mt-2">
Testing {type === 'tags' ? 'tags generation' : 'embeddings'}...
</p>

View File

@@ -36,8 +36,8 @@ export default async function AITestPage() {
<div className="grid gap-6 md:grid-cols-2">
{/* Tags Provider Test */}
<Card className="border-blue-200 dark:border-blue-900">
<CardHeader className="bg-blue-50/50 dark:bg-blue-950/20">
<Card className="border-primary/20 dark:border-primary/30">
<CardHeader className="bg-primary/5 dark:bg-primary/10">
<CardTitle className="flex items-center gap-2">
<span className="text-2xl">🏷</span>
Tags Generation Test

View File

@@ -21,7 +21,7 @@ export default async function AdminAIPage() {
title: 'Avg Response Time',
value: '1.2s',
trend: { value: 5, isPositive: true },
icon: <Activity className="h-5 w-5 text-blue-600 dark:text-blue-400" />,
icon: <Activity className="h-5 w-5 text-primary dark:text-primary-foreground" />,
},
{
title: 'Active Features',
@@ -103,7 +103,7 @@ export default async function AdminAIPage() {
className={`px-2 py-1 text-xs font-medium rounded-full ${
provider.status === 'Connected'
? 'text-green-700 dark:text-green-400 bg-green-100 dark:bg-green-900'
: 'text-blue-700 dark:text-blue-400 bg-blue-100 dark:bg-blue-900'
: 'text-primary dark:text-primary-foreground bg-primary/10 dark:bg-primary/20'
}`}
>
{provider.status}

View File

@@ -11,7 +11,7 @@ export default async function AdminPage() {
title: 'Total Users',
value: users.length,
trend: { value: 12, isPositive: true },
icon: <Users className="h-5 w-5 text-blue-600 dark:text-blue-400" />,
icon: <Users className="h-5 w-5 text-primary dark:text-primary-foreground" />,
},
{
title: 'Active Sessions',

View File

@@ -213,9 +213,9 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<form action={handleSaveAI}>
<CardContent className="space-y-6">
{/* Tags Generation Section */}
<div className="space-y-4 p-4 border rounded-lg bg-blue-50/50 dark:bg-blue-950/20">
<div className="space-y-4 p-4 border rounded-lg bg-primary/5 dark:bg-primary/10">
<h3 className="text-base font-semibold flex items-center gap-2">
<span className="text-blue-600">🏷</span> Tags Generation Provider
<span className="text-primary">🏷</span> Tags Generation Provider
</h3>
<p className="text-xs text-muted-foreground">AI provider for automatic tag suggestions. Recommended: Ollama (free, local).</p>
@@ -264,7 +264,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<div className="space-y-2">
<Label htmlFor="OPENAI_API_KEY">API Key</Label>
<Input id="OPENAI_API_KEY" name="OPENAI_API_KEY" type="password" defaultValue={config.OPENAI_API_KEY || ''} placeholder="sk-..." />
<p className="text-xs text-muted-foreground">Your OpenAI API key from <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">platform.openai.com</a></p>
<p className="text-xs text-muted-foreground">Your OpenAI API key from <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">platform.openai.com</a></p>
</div>
<div className="space-y-2">
<Label htmlFor="AI_MODEL_TAGS_OPENAI">Model</Label>
@@ -278,7 +278,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<option key={model} value={model}>{model}</option>
))}
</select>
<p className="text-xs text-muted-foreground"><strong className="text-green-600">gpt-4o-mini</strong> = Best value <strong className="text-blue-600">gpt-4o</strong> = Best quality</p>
<p className="text-xs text-muted-foreground"><strong className="text-green-600">gpt-4o-mini</strong> = Best value <strong className="text-primary">gpt-4o</strong> = Best quality</p>
</div>
</div>
)}
@@ -372,7 +372,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<div className="space-y-2">
<Label htmlFor="OPENAI_API_KEY">API Key</Label>
<Input id="OPENAI_API_KEY" name="OPENAI_API_KEY" type="password" defaultValue={config.OPENAI_API_KEY || ''} placeholder="sk-..." />
<p className="text-xs text-muted-foreground">Your OpenAI API key from <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">platform.openai.com</a></p>
<p className="text-xs text-muted-foreground">Your OpenAI API key from <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">platform.openai.com</a></p>
</div>
<div className="space-y-2">
<Label htmlFor="AI_MODEL_EMBEDDING_OPENAI">Model</Label>
@@ -386,7 +386,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<option key={model} value={model}>{model}</option>
))}
</select>
<p className="text-xs text-muted-foreground"><strong className="text-green-600">text-embedding-3-small</strong> = Best value <strong className="text-blue-600">text-embedding-3-large</strong> = Best quality</p>
<p className="text-xs text-muted-foreground"><strong className="text-green-600">text-embedding-3-small</strong> = Best value <strong className="text-primary">text-embedding-3-large</strong> = Best quality</p>
</div>
</div>
)}

View File

@@ -26,7 +26,7 @@ import { cn } from '@/lib/utils'
import { LabelFilter } from '@/components/label-filter'
export default function HomePage() {
console.log('[HomePage] Component rendering')
const searchParams = useSearchParams()
const router = useRouter()
// Force re-render when search params change (for filtering)
@@ -58,7 +58,7 @@ export default function HomePage() {
// Callback for NoteInput to trigger notebook suggestion and update UI
const handleNoteCreated = useCallback((note: Note) => {
console.log('[NotebookSuggestion] Note created:', { id: note.id, notebookId: note.notebookId, contentLength: note.content?.length })
// Update UI immediately by adding the note to the list if it matches current filters
setNotes((prevNotes) => {
@@ -120,19 +120,19 @@ export default function HomePage() {
// Only suggest if note has no notebook and has 20+ words
if (!note.notebookId) {
const wordCount = (note.content || '').trim().split(/\s+/).filter(w => w.length > 0).length
console.log('[NotebookSuggestion] Word count:', wordCount)
if (wordCount >= 20) {
console.log('[NotebookSuggestion] Triggering suggestion for note:', note.id)
setNotebookSuggestion({
noteId: note.id,
content: note.content || ''
})
} else {
console.log('[NotebookSuggestion] Not enough words, need 20+')
}
} else {
console.log('[NotebookSuggestion] Note has notebook, skipping')
}
// Refresh in background to ensure data consistency (non-blocking)
@@ -265,10 +265,10 @@ export default function HomePage() {
// Helper for Breadcrumbs
const Breadcrumbs = ({ notebookName }: { notebookName: string }) => (
<div className="flex items-center gap-2 text-sm text-gray-500 mb-1">
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-1">
<span>Notebooks</span>
<ChevronRight className="w-4 h-4" />
<span className="font-medium text-blue-600">{notebookName}</span>
<span className="font-medium text-primary">{notebookName}</span>
</div>
)
@@ -283,12 +283,12 @@ export default function HomePage() {
<div className="flex items-start justify-between">
{/* Title Section */}
<div className="flex items-center gap-5">
<div className="p-3 bg-blue-50 dark:bg-blue-900/20 rounded-xl">
<div className="p-3 bg-primary/10 dark:bg-primary/20 rounded-xl">
{(() => {
const Icon = getNotebookIcon(currentNotebook.icon || 'folder')
return (
<Icon
className={cn("w-8 h-8", !currentNotebook.color && "text-blue-600 dark:text-blue-400")}
className={cn("w-8 h-8", !currentNotebook.color && "text-primary dark:text-primary-foreground")}
style={currentNotebook.color ? { color: currentNotebook.color } : undefined}
/>
)
@@ -311,7 +311,7 @@ export default function HomePage() {
/>
<Button
onClick={() => setShowNoteInput(!showNoteInput)}
className="h-10 px-6 rounded-full bg-blue-600 hover:bg-blue-700 text-white font-medium shadow-sm gap-2 transition-all"
className="h-10 px-6 rounded-full bg-primary hover:bg-primary/90 text-primary-foreground font-medium shadow-sm gap-2 transition-all"
>
<Plus className="w-5 h-5" />
Add Note
@@ -329,7 +329,7 @@ export default function HomePage() {
{/* Title Section */}
<div className="flex items-center gap-5">
<div className="p-3 bg-white border border-gray-100 dark:bg-gray-800 dark:border-gray-700 rounded-xl shadow-sm">
<FileText className="w-8 h-8 text-blue-600" />
<FileText className="w-8 h-8 text-primary" />
</div>
<h1 className="text-4xl font-bold text-gray-900 dark:text-white tracking-tight">Notes</h1>
</div>
@@ -362,7 +362,7 @@ export default function HomePage() {
<Button
onClick={() => setShowNoteInput(!showNoteInput)}
className="h-10 px-6 rounded-full bg-blue-600 hover:bg-blue-700 text-white font-medium shadow-sm gap-2 transition-all"
className="h-10 px-6 rounded-full bg-primary hover:bg-primary/90 text-primary-foreground font-medium shadow-sm gap-2 transition-all"
>
<Plus className="w-5 h-5" />
Add Note

View File

@@ -30,13 +30,15 @@ export function AppearanceSettingsForm({ initialTheme, initialFontSize }: Appear
if (window.matchMedia('(prefers-color-scheme: dark)').matches) root.classList.add('dark')
} else if (value === 'dark') {
root.classList.add('dark')
} else if (value === 'light') {
root.setAttribute('data-theme', 'light')
} else {
root.setAttribute('data-theme', value)
if (['midnight'].includes(value)) root.classList.add('dark')
if (['midnight', 'blue', 'sepia'].includes(value)) root.classList.add('dark')
}
// Save to DB (no need for router.refresh - localStorage handles immediate visuals)
await updateUser({ theme: value as 'light' | 'dark' | 'auto' | 'sepia' | 'midnight' })
await updateUser({ theme: value as 'light' | 'dark' | 'auto' | 'sepia' | 'midnight' | 'blue' })
}
const handleFontSizeChange = async (value: string) => {
@@ -71,10 +73,11 @@ export function AppearanceSettingsForm({ initialTheme, initialFontSize }: Appear
description="Select app's visual theme"
value={theme}
options={[
{ value: 'light', label: 'Light' },
{ value: 'slate', label: 'Light' },
{ value: 'dark', label: 'Dark' },
{ value: 'sepia', label: 'Sepia' },
{ value: 'midnight', label: 'Midnight' },
{ value: 'blue', label: 'Blue' },
{ value: 'auto', label: 'Auto (system)' },
]}
onChange={handleThemeChange}

View File

@@ -100,7 +100,7 @@ export default function SettingsPage() {
</Link>
<Link href="/settings/profile">
<div className="p-4 border rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors cursor-pointer">
<RefreshCw className="h-6 w-6 text-blue-500 mb-2" />
<RefreshCw className="h-6 w-6 text-primary mb-2" />
<h3 className="font-semibold">Profile Settings</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
Manage your account and preferences