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;
}
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')}