A local-first note taking app
0

Configure Feed

Select the types of activity you want to include in your feed.

Move split controls to the title bar + title-bar/sidebar polish

- Split Right / Split Down move from the bottom bar to the top bar as
icon buttons (SplitRightIcon / SplitDownIcon) with hover tooltips, left
of the workspace selector, shown only in tiling mode. Wired through the
tiling handle (App.handleSplitRight/Down).
- Title bar: compact fixed-height buttons (size-5) with header padding so
hover highlights no longer reach the screen edge; tooltip + pointer
cursor on the sidebar toggle, split buttons, and workspace menu.
- Sidebar: fix asymmetric collapse animation — the open state now also
transitions width, so re-opening animates instead of snapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Ethan Graf (Jun 28, 2026, 11:42 AM EDT) aa77d352 50fe74d7

+89 -26
+11
src/App.tsx
··· 320 320 [handleOpenEntry], 321 321 ); 322 322 323 + const handleSplitRight = useCallback( 324 + () => tilingHandleRef.current?.splitRight(), 325 + [], 326 + ); 327 + const handleSplitDown = useCallback( 328 + () => tilingHandleRef.current?.splitDown(), 329 + [], 330 + ); 331 + 323 332 const handleNewFile = useCallback(async () => { 324 333 const provider = resolveProvider(selectedProviderId); 325 334 if (!provider) return; ··· 402 411 onToggleSidebar={() => setSidebarOpen((o) => !o)} 403 412 workspaceKind={workspaceKind} 404 413 onWorkspaceKindChange={setWorkspaceKind} 414 + onSplitRight={handleSplitRight} 415 + onSplitDown={handleSplitDown} 405 416 /> 406 417 <AppBottomBar> 407 418 <div
+5 -3
src/components/AppSidebar.tsx
··· 570 570 open 571 571 ? { 572 572 width: sidebarWidth, 573 - transitionProperty: 'background-color, border-color, color', 574 - transitionDuration: '320ms, 320ms, 320ms', 575 - transitionTimingFunction: 'ease-in-out, ease-in-out, ease-in-out', 573 + transitionProperty: 574 + 'width, background-color, border-color, color', 575 + transitionDuration: '200ms, 320ms, 320ms, 320ms', 576 + transitionTimingFunction: 577 + 'ease-out, ease-in-out, ease-in-out, ease-in-out', 576 578 } 577 579 : { 578 580 transitionProperty:
+32 -2
src/components/AppTitleBar.tsx
··· 1 1 import type { CSSProperties } from 'react'; 2 2 import type { WorkspaceKind } from '../workspaces/workspace'; 3 + import { SplitDownIcon, SplitRightIcon } from '../icons'; 3 4 import { WorkspaceMenu } from './WorkspaceMenu'; 4 5 5 6 type AppTitleBarProps = { ··· 7 8 onToggleSidebar: () => void; 8 9 workspaceKind: WorkspaceKind; 9 10 onWorkspaceKindChange: (kind: WorkspaceKind) => void; 11 + onSplitRight: () => void; 12 + onSplitDown: () => void; 10 13 }; 11 14 12 15 function SidebarToggleIcon({ className }: { className?: string }) { ··· 44 47 onToggleSidebar, 45 48 workspaceKind, 46 49 onWorkspaceKindChange, 50 + onSplitRight, 51 + onSplitDown, 47 52 }: AppTitleBarProps) { 48 53 const noDrag = { WebkitAppRegion: 'no-drag' as const }; 49 54 50 55 return ( 51 56 <header 52 - className="bg-background/95 text-foreground flex items-center gap-2 px-1" 57 + className="bg-background/95 text-foreground flex items-center gap-2 px-1 py-1" 53 58 style={macTitleBarStyle} 54 59 > 55 60 <div className="flex h-full min-w-0 flex-1 items-center gap-2"> ··· 57 62 type="button" 58 63 aria-controls="app-sidebar" 59 64 aria-label={sidebarOpen ? 'Hide sidebar' : 'Show sidebar'} 65 + title={sidebarOpen ? 'Hide sidebar' : 'Show sidebar'} 60 66 aria-pressed={sidebarOpen} 61 67 onClick={onToggleSidebar} 62 - className="text-foreground hover:bg-border/40 rounded p-1.5" 68 + className="text-foreground hover:bg-border/40 flex size-5 cursor-pointer items-center justify-center rounded" 63 69 style={noDrag} 64 70 > 65 71 <SidebarToggleIcon className="size-4" /> 66 72 </button> 67 73 </div> 68 74 <div className="flex h-full shrink-0 items-center gap-1 pr-1"> 75 + {workspaceKind === 'tiling' ? ( 76 + <> 77 + <button 78 + type="button" 79 + aria-label="Split right" 80 + title="Split right" 81 + onClick={onSplitRight} 82 + className="text-foreground hover:bg-border/40 flex size-5 cursor-pointer items-center justify-center rounded" 83 + style={noDrag} 84 + > 85 + <SplitRightIcon className="size-4" /> 86 + </button> 87 + <button 88 + type="button" 89 + aria-label="Split down" 90 + title="Split down" 91 + onClick={onSplitDown} 92 + className="text-foreground hover:bg-border/40 flex size-5 cursor-pointer items-center justify-center rounded" 93 + style={noDrag} 94 + > 95 + <SplitDownIcon className="size-4" /> 96 + </button> 97 + </> 98 + ) : null} 69 99 <WorkspaceMenu value={workspaceKind} onChange={onWorkspaceKindChange} /> 70 100 </div> 71 101 </header>
+1 -1
src/components/WorkspaceMenu.tsx
··· 45 45 aria-expanded={open} 46 46 aria-label="Workspace" 47 47 onClick={() => setOpen((o) => !o)} 48 - className="text-foreground/90 hover:bg-border/40 focus-visible:ring-accent/50 inline-flex items-center gap-1 rounded px-2 py-1 text-xs outline-none focus-visible:ring-2" 48 + className="text-foreground/90 hover:bg-border/40 focus-visible:ring-accent/50 inline-flex h-5 cursor-pointer items-center gap-1 rounded px-2 text-xs outline-none focus-visible:ring-2" 49 49 > 50 50 <span className="text-foreground/60">Workspace:</span> 51 51 <span className="font-medium">{current?.label ?? value}</span>
+38
src/icons/index.tsx
··· 217 217 </svg> 218 218 ); 219 219 } 220 + 221 + /** Split the pane to the right: a frame divided by a vertical line. */ 222 + export function SplitRightIcon({ className }: IconProps) { 223 + return ( 224 + <svg 225 + className={className} 226 + viewBox="0 0 24 24" 227 + fill="none" 228 + stroke="currentColor" 229 + strokeWidth="2" 230 + strokeLinecap="round" 231 + strokeLinejoin="round" 232 + aria-hidden 233 + > 234 + <rect x="3" y="4" width="18" height="16" rx="2" /> 235 + <line x1="12" y1="4" x2="12" y2="20" /> 236 + </svg> 237 + ); 238 + } 239 + 240 + /** Split the pane downward: a frame divided by a horizontal line. */ 241 + export function SplitDownIcon({ className }: IconProps) { 242 + return ( 243 + <svg 244 + className={className} 245 + viewBox="0 0 24 24" 246 + fill="none" 247 + stroke="currentColor" 248 + strokeWidth="2" 249 + strokeLinecap="round" 250 + strokeLinejoin="round" 251 + aria-hidden 252 + > 253 + <rect x="3" y="4" width="18" height="16" rx="2" /> 254 + <line x1="3" y1="12" x2="21" y2="12" /> 255 + </svg> 256 + ); 257 + }
+2 -20
src/workspaces/tiling/tiling.tsx
··· 544 544 {renderNode(tree)} 545 545 </div> 546 546 <WorkspaceBarSlot> 547 - <div className="flex min-w-0 flex-1 items-center gap-2"> 548 - <button 549 - type="button" 550 - onClick={() => applySplit('row')} 551 - data-command-id="tiling:split-right" 552 - className="border-border bg-background text-foreground hover:bg-border/30 focus-visible:ring-accent/50 shrink-0 rounded border px-2 py-1 text-xs outline-none focus-visible:ring-2" 553 - > 554 - Split Right 555 - </button> 556 - <button 557 - type="button" 558 - onClick={() => applySplit('column')} 559 - data-command-id="tiling:split-down" 560 - className="border-border bg-background text-foreground hover:bg-border/30 focus-visible:ring-accent/50 shrink-0 rounded border px-2 py-1 text-xs outline-none focus-visible:ring-2" 561 - > 562 - Split Down 563 - </button> 564 - <div className="text-foreground/60 ml-2 truncate text-xs"> 565 - Active: {activeTileId} 566 - </div> 547 + <div className="text-foreground/60 min-w-0 flex-1 truncate text-xs"> 548 + Active: {activeTileId} 567 549 </div> 568 550 </WorkspaceBarSlot> 569 551 </>