Skip to content

Commit 1e53a12

Browse files
jherforthclaude
andcommitted
Soft card layout: panes float on the page gradient, zero ruled lines
bolt-inspired separation without imitation: the app root is transparent so the body gradient is the page background; sidebar, chat, editor, and preview are rounded-xl cards on a raised surface tone with an 8px gutter as the only divider. The top bar floats with no bottom rule, the composer loses its top rule (the inset input carries the edge), hovers step up one tone, message bubbles and welcome cards inset one tone down, and the chat watermark is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent b30320b commit 1e53a12

5 files changed

Lines changed: 33 additions & 38 deletions

File tree

packages/web-ui/src/App.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,9 @@ export default function App() {
516516
];
517517

518518
return (
519-
<div className="h-screen w-screen flex flex-col bg-monastery-dark-bg overflow-hidden">
519+
// Transparent root: the body gradient is the page background, and panes float on it
520+
// as rounded cards — the gutter between them is the divider (no ruled lines).
521+
<div className="h-screen w-screen flex flex-col overflow-hidden">
520522
<CommandPalette
521523
commands={paletteCommands}
522524
files={flattenFiles(projectFiles)}
@@ -566,11 +568,11 @@ export default function App() {
566568
templateCtx={{ pocketbaseConfigured: !!pocketbaseConn }}
567569
/>
568570

569-
<div className="flex-1 flex overflow-hidden">
571+
<div className="flex-1 flex overflow-hidden gap-2 px-2 pb-2">
570572
{/* Left Sidebar — slides in/out with CSS transition */}
571573
<div
572-
className={`transition-all duration-300 ease-in-out overflow-hidden ${
573-
sidebarCollapsed ? 'w-0 border-r-0' : 'w-64 border-r border-monastery-dark-border'
574+
className={`transition-all duration-300 ease-in-out overflow-hidden rounded-xl ${
575+
sidebarCollapsed ? 'w-0' : 'w-64'
574576
}`}
575577
>
576578
<div className="w-64 h-full flex-shrink-0">
@@ -596,7 +598,7 @@ export default function App() {
596598
minSize={20}
597599
onResize={(size) => updatePaneLayout({ ...paneLayout, chat: size })}
598600
>
599-
<div className="h-full flex flex-col">
601+
<div className="h-full flex flex-col rounded-xl overflow-hidden">
600602
<div className="flex-1 min-h-0">
601603
<ChatPane
602604
activeTaskLabel={workflow.activeTask ? `${workflow.activeTask.title} · ${STAGE_LABEL[workflow.activeTask.stage]}` : null}
@@ -649,7 +651,7 @@ export default function App() {
649651
{/* Code Editor — has its own toggle, independent of the file-tree sidebar */}
650652
{!editorCollapsed && (
651653
<>
652-
<PanelResizeHandle className="w-1 bg-transparent hover:bg-monastery-lantern/50 transition-colors cursor-col-resize" />
654+
<PanelResizeHandle className="w-2 bg-transparent hover:bg-monastery-lantern/50 rounded transition-colors cursor-col-resize" />
653655
<Panel
654656
defaultSize={previewCollapsed ? 100 - paneLayout.chat : paneLayout.editor}
655657
minSize={20}
@@ -691,13 +693,13 @@ export default function App() {
691693
{/* Preview Pane — slides in/out */}
692694
{!previewCollapsed && (
693695
<>
694-
<PanelResizeHandle className="w-1 bg-transparent hover:bg-monastery-lantern/50 transition-colors cursor-col-resize" />
696+
<PanelResizeHandle className="w-2 bg-transparent hover:bg-monastery-lantern/50 rounded transition-colors cursor-col-resize" />
695697
<Panel
696698
defaultSize={paneLayout.preview}
697699
minSize={15}
698700
onResize={(size) => updatePaneLayout({ ...paneLayout, preview: size })}
699701
>
700-
<div className="h-full animate-slideInRight">
702+
<div className="h-full animate-slideInRight rounded-xl overflow-hidden bg-monastery-dark-surface">
701703
<PreviewPane projectId={currentProject?.id} />
702704
</div>
703705
</Panel>

packages/web-ui/src/components/ChatPane.tsx

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -322,21 +322,14 @@ export function ChatPane({
322322
};
323323

324324
return (
325-
<div className="flex flex-col h-full bg-monastery-dark-bg relative overflow-hidden">
326-
{/* Monastery SVG background */}
327-
<img
328-
src={theme === 'monastery-dark' ? '/images/monasteryDark.svg' : '/images/monasteryLight.svg'}
329-
alt=""
330-
className="absolute inset-0 w-full h-full object-cover opacity-[0.07] pointer-events-none select-none"
331-
/>
332-
325+
<div className="flex flex-col h-full bg-monastery-dark-surface relative overflow-hidden">
333326
{/* Chat header — session switcher (sessions belong to the chat, not the file tree) */}
334327
{onSelectSession && (
335328
<div className="relative z-20 flex items-center gap-1 px-3 py-1.5 shrink-0">
336329
<div className="relative min-w-0">
337330
<button
338331
onClick={() => setSessionMenuOpen(o => !o)}
339-
className="flex items-center gap-1.5 px-2 py-1 max-w-full hover:bg-monastery-dark-surface rounded-md transition-colors text-xs text-monastery-text-secondary"
332+
className="flex items-center gap-1.5 px-2 py-1 max-w-full hover:bg-monastery-dark-tertiary rounded-md transition-colors text-xs text-monastery-text-secondary"
340333
title="Switch chat session"
341334
>
342335
<MessageSquare size={13} className="shrink-0" />
@@ -396,7 +389,7 @@ export function ChatPane({
396389
{onCreateSession && (
397390
<button
398391
onClick={onCreateSession}
399-
className="p-1 hover:bg-monastery-dark-surface rounded-md transition-colors text-monastery-text-secondary hover:text-monastery-text-primary shrink-0"
392+
className="p-1 hover:bg-monastery-dark-tertiary rounded-md transition-colors text-monastery-text-secondary hover:text-monastery-text-primary shrink-0"
400393
title="New session"
401394
>
402395
<Plus size={14} />
@@ -410,7 +403,7 @@ export function ChatPane({
410403
className={`ml-auto flex items-center gap-1.5 px-2 py-1 rounded-md text-xs transition-colors shrink-0 max-w-[45%] ${
411404
activeTaskLabel
412405
? 'bg-monastery-lantern/15 text-monastery-lantern hover:bg-monastery-lantern/25'
413-
: 'text-monastery-text-secondary hover:bg-monastery-dark-surface hover:text-monastery-text-primary'
406+
: 'text-monastery-text-secondary hover:bg-monastery-dark-tertiary hover:text-monastery-text-primary'
414407
}`}
415408
title={activeTaskLabel ? 'Open the active task' : 'Structured tasks: Plan → Implement → Verify → Review'}
416409
>
@@ -446,28 +439,28 @@ export function ChatPane({
446439
<div className="grid grid-cols-2 gap-2 text-left max-w-sm mx-auto">
447440
<button
448441
onClick={() => onSendMessage('Create a Next.js app with authentication')}
449-
className="p-3 bg-monastery-dark-surface rounded-xl text-xs text-monastery-text-secondary hover:bg-monastery-dark-tertiary hover:text-monastery-text-primary transition-all border border-monastery-dark-border hover:border-monastery-pine text-left"
442+
className="p-3 bg-monastery-dark-bg rounded-xl text-xs text-monastery-text-secondary hover:bg-monastery-dark-tertiary hover:text-monastery-text-primary transition-all border border-monastery-dark-border hover:border-monastery-pine text-left"
450443
>
451444
<div className="font-medium text-monastery-text-primary mb-0.5">Web App</div>
452445
Create a Next.js app with authentication
453446
</button>
454447
<button
455448
onClick={() => onSendMessage('Explain how this project structure works')}
456-
className="p-3 bg-monastery-dark-surface rounded-xl text-xs text-monastery-text-secondary hover:bg-monastery-dark-tertiary hover:text-monastery-text-primary transition-all border border-monastery-dark-border hover:border-monastery-pine text-left"
449+
className="p-3 bg-monastery-dark-bg rounded-xl text-xs text-monastery-text-secondary hover:bg-monastery-dark-tertiary hover:text-monastery-text-primary transition-all border border-monastery-dark-border hover:border-monastery-pine text-left"
457450
>
458451
<div className="font-medium text-monastery-text-primary mb-0.5">Understand</div>
459452
Explain this codebase structure
460453
</button>
461454
<button
462455
onClick={() => onSendMessage('Add unit tests to the existing module')}
463-
className="p-3 bg-monastery-dark-surface rounded-xl text-xs text-monastery-text-secondary hover:bg-monastery-dark-tertiary hover:text-monastery-text-primary transition-all border border-monastery-dark-border hover:border-monastery-pine text-left"
456+
className="p-3 bg-monastery-dark-bg rounded-xl text-xs text-monastery-text-secondary hover:bg-monastery-dark-tertiary hover:text-monastery-text-primary transition-all border border-monastery-dark-border hover:border-monastery-pine text-left"
464457
>
465458
<div className="font-medium text-monastery-text-primary mb-0.5">Testing</div>
466459
Add tests to the existing module
467460
</button>
468461
<button
469462
onClick={() => onSendMessage('Deploy this project to my homelab server')}
470-
className="p-3 bg-monastery-dark-surface rounded-xl text-xs text-monastery-text-secondary hover:bg-monastery-dark-tertiary hover:text-monastery-text-primary transition-all border border-monastery-dark-border hover:border-monastery-pine text-left"
463+
className="p-3 bg-monastery-dark-bg rounded-xl text-xs text-monastery-text-secondary hover:bg-monastery-dark-tertiary hover:text-monastery-text-primary transition-all border border-monastery-dark-border hover:border-monastery-pine text-left"
471464
>
472465
<div className="font-medium text-monastery-text-primary mb-0.5">Deploy</div>
473466
Deploy this to my homelab
@@ -519,8 +512,8 @@ export function ChatPane({
519512
message.role === 'user'
520513
? 'bg-monastery-pine text-white'
521514
: message.role === 'system'
522-
? 'bg-monastery-dark-tertiary border border-monastery-dark-border text-center'
523-
: 'bg-monastery-dark-surface border border-monastery-dark-border'
515+
? 'bg-monastery-dark-tertiary text-center'
516+
: 'bg-monastery-dark-bg'
524517
}`}
525518
>
526519
{/* Agent role chips on a user message (which role(s) it was sent under) */}
@@ -665,8 +658,8 @@ export function ChatPane({
665658
<div ref={messagesEndRef} />
666659
</div>
667660

668-
{/* Input */}
669-
<form onSubmit={handleSubmit} className="border-t border-monastery-dark-border p-4">
661+
{/* Input — no rule above it; the inset field carries its own edge */}
662+
<form onSubmit={handleSubmit} className="p-4 pt-2">
670663
{/* Composer toolbar: a "+ Context" popover holds the registry-driven options (skills,
671664
behaviors) so new ones never widen this row; active ones surface as removable chips.
672665
The Chat/Agent mode selector sits at the right, with roles behind it in a popover. */}
@@ -857,7 +850,7 @@ export function ChatPane({
857850
)}
858851

859852
<div className="flex items-end gap-2">
860-
<label className="p-2 hover:bg-monastery-dark-surface rounded-lg transition-colors cursor-pointer">
853+
<label className="p-2 hover:bg-monastery-dark-tertiary rounded-lg transition-colors cursor-pointer">
861854
<Paperclip size={20} className="text-monastery-text-secondary" />
862855
<input
863856
type="file"
@@ -882,7 +875,7 @@ export function ChatPane({
882875
disabled={!activeEndpoint && messages.length === 0}
883876
rows={1}
884877
style={{ maxHeight: MAX_INPUT_HEIGHT }}
885-
className="flex-1 bg-monastery-dark-surface border border-monastery-dark-border rounded-xl px-4 py-3 text-sm resize-y overflow-y-auto focus:outline-none focus:border-monastery-pine transition-colors"
878+
className="flex-1 bg-monastery-dark-bg border border-monastery-dark-border rounded-xl px-4 py-3 text-sm resize-y overflow-y-auto focus:outline-none focus:border-monastery-pine transition-colors"
886879
/>
887880

888881
{isGenerating ? (

packages/web-ui/src/components/EditorPane.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function EditorPane({
3434
onAddTests,
3535
}: EditorPaneProps) {
3636
return (
37-
<div className="h-full bg-monastery-dark-surface flex flex-col animate-slideInRight">
37+
<div className="h-full bg-monastery-dark-surface flex flex-col animate-slideInRight rounded-xl overflow-hidden">
3838
{/* Tab Bar — tabs separate themselves by background, not ruled lines */}
3939
{tabs.length > 0 && (
4040
<div className="flex items-center bg-monastery-dark-bg overflow-x-auto shrink-0">

packages/web-ui/src/components/Sidebar.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function FileTreeItem({ node, depth, onSelectFile, onDeleteFile, onCreateFile, o
8585
onDragOver={isDirectory ? handleDragOver : undefined}
8686
onDragLeave={isDirectory ? handleDragLeave : undefined}
8787
onDrop={isDirectory ? handleDrop : undefined}
88-
className={`w-full flex items-center gap-1.5 px-2 py-1 hover:bg-monastery-dark-surface rounded-md transition-colors text-sm ${
88+
className={`w-full flex items-center gap-1.5 px-2 py-1 hover:bg-monastery-dark-tertiary rounded-md transition-colors text-sm ${
8989
isDragOver ? 'bg-monastery-pine/20 ring-1 ring-monastery-pine' : ''
9090
}`}
9191
style={{ paddingLeft: `${depth * 12 + 8}px` }}
@@ -225,9 +225,8 @@ export function Sidebar({
225225
}, [showCreateMenu]);
226226

227227
return (
228-
// No border-r on the aside — the sidebar wrapper in App.tsx already draws the pane
229-
// seam, and the two together rendered a doubled line.
230-
<aside className="w-64 bg-monastery-dark-bg flex flex-col shrink-0">
228+
// A rounded card on the page gradient (App wraps it) — surface tone, no borders.
229+
<aside className="w-64 bg-monastery-dark-surface flex flex-col shrink-0 h-full">
231230
{/* The sidebar is the file tree — sessions live in the chat header, agent roles in the
232231
composer, and connections in Settings. */}
233232
<div className="flex-1 overflow-y-auto py-2">
@@ -243,7 +242,7 @@ export function Sidebar({
243242
{onRefreshFiles && (
244243
<button
245244
onClick={onRefreshFiles}
246-
className="p-1 hover:bg-monastery-dark-surface rounded transition-colors text-monastery-text-secondary hover:text-monastery-text-primary"
245+
className="p-1 hover:bg-monastery-dark-tertiary rounded transition-colors text-monastery-text-secondary hover:text-monastery-text-primary"
247246
title="Refresh files from disk (e.g. changes written by Hermes)"
248247
>
249248
<RefreshCw size={14} />
@@ -253,7 +252,7 @@ export function Sidebar({
253252
{/* Upload button */}
254253
<button
255254
onClick={() => fileInputRef.current?.click()}
256-
className="p-1 hover:bg-monastery-dark-surface rounded transition-colors text-monastery-text-secondary hover:text-monastery-text-primary"
255+
className="p-1 hover:bg-monastery-dark-tertiary rounded transition-colors text-monastery-text-secondary hover:text-monastery-text-primary"
257256
title="Upload file to project"
258257
>
259258
<Upload size={14} />
@@ -263,7 +262,7 @@ export function Sidebar({
263262
<div className="relative" ref={createMenuRef}>
264263
<button
265264
onClick={(e) => { e.stopPropagation(); setShowCreateMenu(!showCreateMenu); }}
266-
className="p-1 hover:bg-monastery-dark-surface rounded transition-colors text-monastery-text-secondary hover:text-monastery-text-primary"
265+
className="p-1 hover:bg-monastery-dark-tertiary rounded transition-colors text-monastery-text-secondary hover:text-monastery-text-primary"
267266
title="Create file or directory"
268267
>
269268
<Plus size={14} />

packages/web-ui/src/components/TopBar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ export function TopBar({ availableProjects = [], endpoints = [], onRefreshProjec
144144

145145
return (
146146
<>
147-
<header className="h-14 bg-monastery-dark-bg border-b border-monastery-dark-border flex items-center justify-between px-4 shrink-0">
147+
{/* Floats on the page gradient — the gutter below it is the separation */}
148+
<header className="h-14 flex items-center justify-between px-4 shrink-0">
148149
{/* Left: Logo + Project */}
149150
<div className="flex items-center gap-3">
150151
<button

0 commit comments

Comments
 (0)