fix(workspace): single scroll area, drag cleanup, thin note titles with bundled Inter
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m18s

- the workspace fills the window; only the note scrolls (also on phones, where the
  sideways scroll area used to scroll on its own)
- clear drag state on dragend, calmer sidebar toggle when collapsed
- ship Inter as variable font, light and larger note titles; header shows the
  title only after the big title scrolled away and stays one row on phones
- main builds show the package version without -dev

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-30 18:58:55 +01:00
1 parent ad4829796c
commit 6c154af251
11 files changed
+88 -19

No files matched your search

+12 -3
View File
@@ -750,7 +750,11 @@ export const App: React.FC = () => {
};
return (
<div className="min-h-screen bg-canvas text-main flex flex-col font-sans selection:bg-accent/30">
<div
className={`bg-canvas text-main flex flex-col font-sans selection:bg-accent/30 ${
activeTab === 'workspace' ? 'h-[100dvh] overflow-hidden' : 'min-h-screen'
}`}
>
{/* Top Header Bar: Floating on mobile, solid panel on desktop */}
<header className="sticky top-0 z-30 bg-canvas/85 backdrop-blur-md sm:bg-panel sm:border-b sm:border-border sm:shadow-sm">
{/* Mobile Header: Floating Bar with Logo Action Dropdown Left & Profile Avatar Right */}
@@ -1113,9 +1117,14 @@ export const App: React.FC = () => {
)}
{/* Main Container */}
<main className="flex-1 max-w-6xl w-full mx-auto px-3 sm:px-4 pt-3 pb-24 sm:pb-6">
{/* The workspace fills the window exactly and scrolls only inside the note, never the page */}
<main
className={`flex-1 max-w-6xl w-full mx-auto px-3 sm:px-4 pt-3 pb-24 sm:pb-6 ${
activeTab === 'workspace' ? 'min-h-0 flex flex-col' : ''
}`}
>
<ViewErrorBoundary onReset={() => setActiveTab('triage')}>
<section key={dataEpoch}>
<section key={dataEpoch} className={activeTab === 'workspace' ? 'flex-1 min-h-0 flex flex-col' : undefined}>
{activeTab === 'triage' && (
<TriageDeck
deck={deck}
@@ -230,6 +230,9 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
const titleInputRef = useRef<HTMLTextAreaElement | HTMLInputElement | null>(null);
const scrollRef = useRef<HTMLDivElement | null>(null);
// The header repeats the title only once the big title has scrolled out of view
const [titleScrolledAway, setTitleScrolledAway] = useState(false);
const outlinerTreeRef = useRef<OutlinerTreeHandle | null>(null);
const docRef = useRef<Document | null>(doc);
docRef.current = doc;
@@ -239,6 +242,22 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
const currentBlocksRef = useRef<DocumentBlock[]>(blocks);
currentBlocksRef.current = blocks;
useEffect(() => {
const root = scrollRef.current;
const el = titleInputRef.current;
if (!root || !el || typeof IntersectionObserver === 'undefined') {
setTitleScrolledAway(false);
return;
}
const observer = new IntersectionObserver(
([entry]) => setTitleScrolledAway(!entry.isIntersecting),
// the sticky header covers the top of the scroll area
{ root, rootMargin: '-56px 0px 0px 0px' }
);
observer.observe(el);
return () => observer.disconnect();
}, [zoomedBlockId, doc?.id]);
useEffect(() => {
if (!loading && (autoFocusTitle || (doc?.title === 'Neue Notiz' && (!doc?.blocks || doc?.blocks.length <= 1)))) {
const focusTitle = () => {
@@ -763,15 +782,15 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
}
return (
<div className="flex-1 flex flex-col h-full min-h-0 bg-editor overflow-y-auto" data-sync-channel={engineState?.channel}>
<div ref={scrollRef} className="flex-1 flex flex-col h-full min-h-0 bg-editor overflow-y-auto" data-sync-channel={engineState?.channel}>
{/* Top Document Action Bar */}
<div className="sticky top-0 z-20 flex flex-wrap items-center justify-between gap-2 px-4 sm:px-6 py-2 bg-editor border-b border-border">
<div className="flex items-center gap-2 min-w-0">
<div className="sticky top-0 z-20 flex items-center justify-between gap-2 px-4 sm:px-6 py-2 bg-editor border-b border-border">
<div className="flex flex-1 items-center gap-2 min-w-0">
{onBackMobile && (
<button
type="button"
onClick={onBackMobile}
className="lg:hidden btn-icon h-11 w-11 sm:h-7 sm:w-7"
className="lg:hidden btn-icon shrink-0 h-11 w-11 sm:h-7 sm:w-7"
title="Zurück zur Seitenleiste"
>
<ArrowLeft className="w-3.5 h-3.5" />
@@ -823,7 +842,12 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
})}
</nav>
) : (
<div className="flex items-center gap-1.5 text-xs text-muted">
<div
className={`flex min-w-0 items-center gap-1.5 text-xs text-muted transition-opacity duration-200 ${
titleScrolledAway ? 'opacity-100' : 'opacity-0 pointer-events-none'
}`}
aria-hidden={!titleScrolledAway}
>
<FileText className="w-3.5 h-3.5 text-accent shrink-0" />
<span className="truncate max-w-[180px] font-medium text-main">
{title || doc?.title || 'Dokument'}
@@ -868,7 +892,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
</div>
{/* Right Action Buttons */}
<div className="flex items-center gap-1.5">
<div className="flex shrink-0 items-center gap-1.5">
{!inTrash && (
<>
<button
@@ -1022,7 +1046,8 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
{/* Main Content Area */}
{/* Phones: almost no side margin; very deep outlines scroll sideways (rows keep a minimum width) */}
<div className="w-full pl-2 pr-1 sm:px-8 py-5 flex-1 overflow-x-auto sm:overflow-x-visible [-webkit-overflow-scrolling:touch]">
{/* shrink-0: the sideways scroll area must keep its full height, so only the editor scrolls vertically */}
<div className="w-full pl-2 pr-1 sm:px-8 py-5 flex-auto shrink-0 overflow-x-auto sm:overflow-x-visible [-webkit-overflow-scrolling:touch]">
{zoomedBlock ? (
/* =========================================================================
FOKUS-MODUS & ZOOM-IN (HOISTED SUBTREE VIEW)
@@ -1061,7 +1086,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
}}
placeholder="Ast-Titel…"
readOnly={!canEdit}
className="w-full text-2xl sm:text-3xl font-extrabold text-main bg-transparent border-0 focus:outline-none focus:ring-0 tracking-tight leading-tight"
className="w-full text-2xl sm:text-3xl font-light text-main bg-transparent border-0 focus:outline-none focus:ring-0 tracking-tight leading-tight"
/>
</div>
@@ -1183,7 +1208,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
}}
onBlur={handleTitleBlur}
placeholder="Titel der Notiz…"
className="flex-1 min-w-0 text-xl sm:text-2xl font-bold text-main bg-transparent border-0 focus:outline-none focus:ring-0 tracking-tight resize-none leading-snug whitespace-pre-wrap break-words [overflow-wrap:anywhere]"
className="flex-1 min-w-0 text-2xl sm:text-3xl font-light text-main bg-transparent border-0 focus:outline-none focus:ring-0 tracking-tight resize-none leading-snug whitespace-pre-wrap break-words [overflow-wrap:anywhere]"
/>
</div>
@@ -408,6 +408,17 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
}, []);
const [draggingFolderPath, setDraggingFolderPath] = useState<string | null>(null);
const [dragOverFolder, setDragOverFolder] = useState<string | null>(null);
// A drag that ends without a drop (released elsewhere, Escape) fires only dragend: clear every drag state
useEffect(() => {
const clear = () => {
setDraggingDocId(null);
setDraggingFolderPath(null);
setDragOverFolder(null);
setMergeHoverId(null);
};
document.addEventListener('dragend', clear);
return () => document.removeEventListener('dragend', clear);
}, []);
// Rollup drilldown & View Mode
const [todoFolderDrilldown, setTodoFolderDrilldown] = useState<string | null>(null);
@@ -1218,7 +1229,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
}, [tagsSummary, tagSearchQuery, tagSortBy]);
return (
<div className="w-full space-y-3">
<div className="w-full h-full min-h-0 flex flex-col gap-3">
{/* Top Header: Workspace Tabs & Actions */}
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border pb-2">
{/* Workspace selector tabs & left sidebar toggle; on phones the three tabs share the full width */}
@@ -1231,8 +1242,8 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
subView !== 'editor'
? 'opacity-35 bg-canvas border-border text-faint cursor-not-allowed pointer-events-none'
: isSidebarCollapsed
? 'bg-panel border-accent text-accent cursor-pointer'
: 'bg-canvas border-border text-muted hover:text-main cursor-pointer'
? 'bg-canvas border-border text-accent hover:bg-panel cursor-pointer'
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel cursor-pointer'
}`}
title={
subView !== 'editor'
@@ -1436,7 +1447,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
</div>
{/* Main Workspace Split Pane */}
<div className="flex h-[calc(100vh-7.5rem)] bg-editor rounded-card border border-border overflow-hidden">
<div className="flex flex-1 min-h-0 bg-editor rounded-card border border-border overflow-hidden">
{/* LEFT MASTER SIDEBAR */}
<aside
className={`${
+1 -1
View File
@@ -28,7 +28,7 @@
body {
background-color: var(--bg-app);
color: var(--text-main);
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-family: 'Inter Variable', 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
overflow-x: hidden;
transition: background-color 140ms ease, color 140ms ease;
}
+2
View File
@@ -2,6 +2,8 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { MobileActionProvider } from './context/MobileActionContext';
// Inter ships with the app (no external font service); the variable font covers all weights
import '@fontsource-variable/inter';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(