From 2648a0b2660f6fa5d1f7427eff82578768c959e8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 28 Aug 2026 19:57:43 +0000 Subject: [PATCH 1/2] Polish mosaic drag/maximize/splitter affordances Co-authored-by: Gao Yu --- src/lib/i18n/messages/dock.ts | 14 ++++ src/lib/workbench/Mosaic.svelte | 133 +++++++++++++++++++++++++++----- 2 files changed, 127 insertions(+), 20 deletions(-) diff --git a/src/lib/i18n/messages/dock.ts b/src/lib/i18n/messages/dock.ts index f886abd..9705f2b 100644 --- a/src/lib/i18n/messages/dock.ts +++ b/src/lib/i18n/messages/dock.ts @@ -17,6 +17,13 @@ const dock = { empty: '没有打开的面板', hint: '选一个面板打开,或点右上角' }, + mosaic: { + stackHere: '叠放到这里', + splitHere: '拆分到这里', + maximizedHint: '已最大化 · Esc 还原', + maximize: '最大化', + restore: '还原' + }, turns: { empty: '本会话还没有文件改动', turnN: '第 {n} 回合', @@ -187,6 +194,13 @@ const dock = { empty: 'No open panels', hint: 'Pick a panel to open, or click the top-right' }, + mosaic: { + stackHere: 'Stack here', + splitHere: 'Split here', + maximizedHint: 'Maximized · Esc to restore', + maximize: 'Maximize', + restore: 'Restore' + }, turns: { empty: 'No file changes in this session yet', turnN: 'Turn {n}', diff --git a/src/lib/workbench/Mosaic.svelte b/src/lib/workbench/Mosaic.svelte index 09b586a..4113a03 100644 --- a/src/lib/workbench/Mosaic.svelte +++ b/src/lib/workbench/Mosaic.svelte @@ -1,6 +1,7 @@ + +
{#if layout.root} {@render node(layout.root)} @@ -137,7 +159,15 @@
{/if} {#if drag?.live} -
{drag.label}
+
+ {drag.label} +
{/if} @@ -160,7 +190,12 @@ {/snippet} {#snippet leafView(leaf: LeafNode)} -
+
barDblClick(e, leaf)} role="tablist" tabindex="-1">
{#each leaf.tabs as tab (tab.id)} @@ -189,19 +224,23 @@
{/each}
-
- {#if addOptions.length} +
+ {#if layout.maximized === leaf.id} + {t('dock.mosaic.maximizedHint')} + {/if} + {#if addOptions.length} {/if} -
@@ -226,7 +265,11 @@ {/each}
{#if drag?.live && hover?.leafId === leaf.id} -
+
+ + {hover.zone === 'center' ? t('dock.mosaic.stackHere') : t('dock.mosaic.splitHere')} + +
{/if}
{/snippet} @@ -263,21 +306,36 @@ min-width: 0; min-height: 0; } + /* Splitter: a 2px visible line, but the ::before pseudo-element widens the + pointer target to ~10px on the drag axis without shifting layout. */ .gutter { + position: relative; + z-index: 3; flex-shrink: 0; background: var(--hairline); transition: background var(--t-fast) var(--ease-out); } + .gutter::before { + content: ''; + position: absolute; + } .gutter.row { - width: 4px; + width: 2px; cursor: col-resize; } + .gutter.row::before { + inset: 0 -4px; + } .gutter.col { - height: 4px; + height: 2px; cursor: row-resize; } - .gutter:hover { - background: var(--accent-soft); + .gutter.col::before { + inset: -4px 0; + } + .gutter:hover, + .gutter:active { + background: color-mix(in oklab, var(--accent) 55%, var(--hairline)); } .leaf { position: relative; @@ -289,11 +347,17 @@ overflow: hidden; background: var(--panel); } - /* Maximize = reposition over the whole mosaic; nothing re-mounts. */ + /* Maximize = reposition over the whole mosaic; nothing re-mounts. The + accent ring says "this pane is covering the others". */ .leaf.maxed { position: absolute; inset: 0; z-index: 6; + box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--accent) 45%, var(--border)); + } + /* The pane a dragged tab would land in: a border on the pane itself. */ + .leaf.droptarget { + box-shadow: inset 0 0 0 2px color-mix(in oklab, var(--accent) 65%, transparent); } .lbar { position: relative; @@ -329,15 +393,18 @@ user-select: none; white-space: nowrap; flex-shrink: 0; + /* Inactive tabs keep a visible surface so they read as tabs, not text. */ + background: var(--surface); + box-shadow: inset 0 0 0 1px var(--hairline); } .ltab:hover { - background: var(--surface); + background: var(--surface2); color: var(--text); } .ltab.on { background: var(--surface2); color: var(--text); - box-shadow: inset 0 0 0 1px var(--hairline); + box-shadow: inset 0 0 0 1px var(--border); } .ltab.lifted { opacity: 0.45; @@ -380,6 +447,16 @@ gap: 2px; flex-shrink: 0; } + .lmax-hint { + padding: 2px 8px; + margin-right: 4px; + font-size: 11px; + font-weight: 600; + white-space: nowrap; + color: var(--accent-bright); + background: var(--accent-soft); + border-radius: var(--r-full); + } .lbtn { display: inline-flex; padding: 4px; @@ -444,15 +521,31 @@ .lpane.hidden { display: none; } - /* Landing preview while dragging a tab over this leaf. */ + /* Landing preview while dragging a tab over this leaf: a thin border and + a faint fill outlining the exact area, plus a label saying what a drop + does (stack into the tab group vs split the pane). */ .dropzone { position: absolute; z-index: 5; pointer-events: none; - background: color-mix(in oklab, var(--accent) 13%, transparent); - border: 1px solid color-mix(in oklab, var(--accent) 45%, transparent); + display: flex; + align-items: center; + justify-content: center; + background: color-mix(in oklab, var(--accent) 6%, transparent); + border: 1px solid color-mix(in oklab, var(--accent) 60%, transparent); border-radius: var(--r-sm); } + .dropzone-label { + padding: 3px 9px; + font-size: 11.5px; + font-weight: 600; + white-space: nowrap; + color: var(--text); + background: var(--panel); + border: 1px solid var(--border); + border-radius: var(--r-full); + box-shadow: var(--shadow-pop); + } .dropzone.center { inset: 0; } From bf6c8e14ad0e7b8bab4384d045a246a3f8eb0687 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 28 Aug 2026 19:57:43 +0000 Subject: [PATCH 2/2] Drop the faded wordmark from the empty chat state Co-authored-by: Gao Yu --- src/routes/+page.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 22876c3..17b2208 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1303,7 +1303,6 @@ {:else if chat.messages.length === 0 && !chat.busy}
- JuCode

{t('shell.welcomeTip')}

/ {t('shell.hintCommand')}