feat: update notice after a server update, keep the open tab on reload, chunked Whisper in Quick Dump, compact emoji picker
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m31s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-11-01 15:09:21 +01:00
1 parent 20b907078f
commit 55112b940c
17 files changed
+250 -61

No files matched your search

@@ -80,9 +80,10 @@ export const HoldToRecordFab: React.FC<HoldToRecordFabProps> = ({ onTap, onTrans
}
};
// Own Whisper server (text after release) or the browser's recognition (text while speaking)
// Own Whisper server (text in chunks while speaking) or the browser's recognition (text while speaking)
const voice = useVoiceInput({
continuous: true,
chunked: true,
onText: (text, final) => {
transcriptRef.current = text;
if (!final) {
@@ -70,6 +70,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
// Voice input: own Whisper server or the browser's recognition (lib/voice.ts)
const voice = useVoiceInput({
chunked: true,
onText: (text, final) => {
const base = baseVoiceTextRef.current;
if (text) setContent(base ? `${base} ${text}` : text);
@@ -206,7 +207,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
setIsMultiline(false);
setPriority('normal');
setDueDate('');
setTarget(fixedTarget || 'inbox');
// Target and list stay for the next dump; only priority and due date belong to one entry
setShowOptions(defaultShowOptions || hideTargetPicker);
setIsSubmitting(true);
triggerHaptic(15);
@@ -544,7 +545,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
}}
className={`min-h-[30px] px-2.5 border rounded-btn text-xs flex items-center gap-1.5 transition-colors cursor-pointer ${
isSelected
? 'bg-accent text-on-accent border-accent font-medium'
? 'bg-accent/10 text-main border-accent font-medium'
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover'
}`}
>
@@ -578,7 +579,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
}}
className={`min-h-[30px] px-2.5 border rounded-btn text-xs flex items-center gap-1.5 transition-colors cursor-pointer ${
isSelected
? 'bg-accent text-on-accent border-accent font-medium'
? 'bg-accent/10 text-main border-accent font-medium'
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover'
}`}
>
@@ -29,6 +29,8 @@ interface EmojiPickerModalProps {
// Inside a dialog (which clips and transforms its content): the picker is put on the page instead and
// opens below this button on desktop
anchor?: DOMRect | null;
// Offer "Icon entfernen" (notes; a create dialog only has its default icon to go back to)
allowRemove?: boolean;
}
export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
@@ -36,11 +38,12 @@ export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
onSelectEmoji,
onClose,
anchor,
allowRemove = true,
}) => {
const { inset, visibleHeight } = useKeyboardInset();
const isPhone = typeof window !== 'undefined' && window.innerWidth < 640;
// On phones the picker shrinks with the keyboard so search field and results stay visible
const pickerHeight = isPhone ? Math.max(200, Math.min(350, visibleHeight - 110)) : 350;
const pickerHeight = isPhone ? Math.max(200, Math.min(350, visibleHeight - 110)) : 300;
const [isDark, setIsDark] = useState<boolean>(() =>
typeof document !== 'undefined'
? document.documentElement.classList.contains('dark')
@@ -98,8 +101,8 @@ export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
const anchorStyle = anchor
? ({
'--pick-left': `${Math.max(12, Math.min(anchor.left, window.innerWidth - 372))}px`,
'--pick-top': `${Math.min(anchor.bottom + 4, window.innerHeight - 532)}px`,
'--pick-left': `${Math.max(12, Math.min(anchor.left, window.innerWidth - 312))}px`,
'--pick-top': `${Math.min(anchor.bottom + 4, window.innerHeight - 360)}px`,
} as React.CSSProperties)
: undefined;
@@ -117,7 +120,7 @@ export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
ref={containerRef}
className={`
fixed inset-x-0 bottom-[var(--kb-inset)] rounded-t-2xl max-h-[var(--vv-max)] bg-panel border-t border-border shadow-2xl flex flex-col p-3 space-y-3
sm:inset-auto sm:w-[360px] sm:max-h-[520px] sm:rounded-card sm:border sm:border-border sm:shadow-xl ${
sm:inset-auto sm:w-[300px] sm:max-h-[360px] sm:p-1 sm:space-y-0 sm:bg-panel-elevated sm:rounded-card sm:border sm:border-border sm:shadow-xl ${
anchor
? 'z-[61] sm:fixed sm:left-[var(--pick-left)] sm:top-[max(12px,var(--pick-top))]'
: 'z-50 sm:absolute sm:left-0 sm:top-full sm:mt-1 sm:z-40'
@@ -126,17 +129,10 @@ export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
style={
{
...anchorStyle,
// Inject MindSift CSS variables into emoji-picker-react for seamless theming
'--epr-bg-color': 'var(--bg-panel)',
'--epr-category-label-bg-color': 'var(--bg-panel)',
'--epr-picker-border-color': 'var(--border-color)',
'--epr-text-color': 'var(--text-main)',
'--epr-search-input-bg-color': 'var(--bg-app)',
'--epr-search-input-bg-color-active': 'var(--bg-panel-elevated)',
'--epr-hover-bg-color': 'var(--bg-panel-elevated)',
'--epr-focus-bg-color': 'var(--bg-panel-elevated)',
'--epr-highlight-color': 'var(--accent-primary)',
'--epr-category-icon-active-color': 'var(--accent-primary)',
// Theme colours for the picker (index.css); desktop: its own raised surface, so it stands apart
// from the dialog it opens from
'--pick-surface': isPhone ? 'var(--bg-panel)' : 'var(--bg-panel-elevated)',
'--pick-hover': isPhone ? 'var(--bg-panel-elevated)' : 'var(--bg-panel-hover)',
'--kb-inset': `${inset}px`,
'--vv-max': `${Math.round(visibleHeight * 0.9)}px`,
} as React.CSSProperties
@@ -145,8 +141,8 @@ export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
{/* Mobile handle indicator */}
<div className="w-10 h-1 bg-border rounded-full mx-auto sm:hidden shrink-0" />
{/* Top Header: Title, Actions & Close */}
<div className="flex items-center justify-between gap-2 shrink-0 border-b border-border pb-2">
{/* Top Header: Title, Actions & Close (phone only; on desktop a click outside closes it) */}
<div className="flex sm:hidden items-center justify-between gap-2 shrink-0 border-b border-border pb-2">
<div className="flex items-center gap-1.5 text-xs font-semibold text-main">
<span>Icon auswählen</span>
{currentIcon && (
@@ -155,7 +151,7 @@ export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
</div>
<div className="flex items-center gap-1">
{currentIcon && (
{currentIcon && allowRemove && (
<button
type="button"
onClick={() => onSelectEmoji('')}
@@ -208,6 +204,18 @@ export const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
/>
</Suspense>
</div>
{/* Desktop: removing the icon as a quiet last row, like an entry of a context menu */}
{currentIcon && allowRemove && (
<button
type="button"
onClick={() => onSelectEmoji('')}
className="hidden sm:flex items-center gap-1.5 w-full h-7 px-2 mt-1 rounded-btn text-xs text-muted hover:text-status-err hover:bg-panel-hover transition-colors cursor-pointer"
>
<Trash2 className="w-3.5 h-3.5" />
Icon entfernen
</button>
)}
</div>
</>
);
@@ -1891,20 +1891,6 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
if (e.target === e.currentTarget && markedDocIds.size > 0) setMarkedDocIds(new Set());
}}
>
{markedDocIds.size > 0 && (
<div className="sticky top-0 z-10 flex items-center gap-2 px-2 py-1.5 rounded-btn bg-accent/10 border border-accent/30 text-[11px] text-main">
<span className="flex-1">
{markedDocIds.size} markiert · in einen Ordner oder auf den Papierkorb ziehen
</span>
<button
type="button"
onClick={() => setMarkedDocIds(new Set())}
className="text-accent hover:underline shrink-0"
>
Aufheben
</button>
</div>
)}
{/* Journal on top; open, it becomes a panel with the calendar (click the heading to close).
Panel and calendar animate open (grid rows 0fr → 1fr) instead of jumping. Only from lg on:
below, the journal is a view of its own and the button stays a plain entry like the folders