feat(ui): 文件二级页面间距规整(8px 网格)+ 浮窗默认尺寸改手机竖屏比例
- 文件页 chrome 行统一:路径行与树搜索行同为 41px 高(路径输入框改 border-box 28px 总高)、gap 统一 6px、内边距统一 6px 8px - 树体侧距 6→8px 对齐 chrome 行;markdown 预览 10/14→12/16(与错误 文案块一致);CodeMirror scroller 新增同款 12/16 内边距(预览/编辑 切换文字位置不跳);截断横幅 12→8px - 浮窗默认尺寸 480×360 → 390×780(1:2 手机竖屏比例),创建时先按 视口减 24px 钳制再居中,矮视口得到更矮的窗而非溢出 - 测试/文档同步:state/free-window 预期重算(含 clampMove 回推的 y=24 语义)、e2e 断言宽度上界、AGENTS §7.5 与设计文档
This commit is contained in:
@@ -174,7 +174,7 @@ test('float a tab, move the window, reload restores it, dock it back', async ({
|
||||
const leftBefore = await floatWindow.evaluate((node) => Number.parseFloat((node as HTMLElement).style.left))
|
||||
expect(Number.isFinite(leftBefore), `style.left must be numeric, got ${leftBefore}`).toBe(true)
|
||||
expect(leftBefore).toBeGreaterThan(0)
|
||||
expect(leftBefore).toBeLessThan(1440 - 480)
|
||||
expect(leftBefore).toBeLessThan(1440 - 390)
|
||||
await assertNoCrash(page)
|
||||
|
||||
// 2. The header drag moves the window (pointer capture + commit).
|
||||
|
||||
+22
-17
@@ -213,9 +213,10 @@ describe('free windows: the window', () => {
|
||||
const state = store.getSnapshot().state!
|
||||
expect(state.floats).toHaveLength(1)
|
||||
expect(state.floats[0]!.tab.id).toBe('notes')
|
||||
// Born at the conversation column's center, minus half the default
|
||||
// size (480x360), clamped into the 1024x768 viewport.
|
||||
expect(state.floats[0]).toMatchObject({ x: 400 - 240, y: 325 - 180, w: 480, h: 360 })
|
||||
// Born at the conversation column's center with the phone-ratio
|
||||
// default (390x780), created capped to the jsdom viewport (h 744) and
|
||||
// clamped in (the centered y would be negative).
|
||||
expect(state.floats[0]).toMatchObject({ x: 400 - 195, y: 0, w: 390, h: 744 })
|
||||
expect(container.querySelector('[data-dsh-float-window]')).not.toBeNull()
|
||||
} finally {
|
||||
unmount()
|
||||
@@ -230,20 +231,22 @@ describe('free windows: the window', () => {
|
||||
act(() => { store.reduce(s => floatTabReducer(s, 'notes', 512, 384)) })
|
||||
const win = container.querySelector<HTMLElement>('[data-dsh-float-window]')!
|
||||
const header = win.querySelector<HTMLElement>('[class*="floatHeader"]')!
|
||||
expect(win.style.left).toBe('272px')
|
||||
expect(win.style.left).toBe('317px')
|
||||
// Grab at (300, 220) — the drag records the window's own origin, so
|
||||
// moving the pointer to (200, 250) lands the window at (172, 234).
|
||||
// moving the pointer to (200, 250) lands the window at (217, 42) —
|
||||
// clamped back up to y 24 (the 744-tall window must stay in the
|
||||
// 768-tall viewport: 768 - 744 = 24).
|
||||
act(() => {
|
||||
header.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true, clientX: 300, clientY: 220, button: 0 }))
|
||||
header.dispatchEvent(new MouseEvent('pointermove', { bubbles: true, clientX: 200, clientY: 250 }))
|
||||
})
|
||||
await flushFrame()
|
||||
expect(win.style.left).toBe('172px')
|
||||
expect(win.style.top).toBe('234px')
|
||||
expect(win.style.left).toBe('217px')
|
||||
expect(win.style.top).toBe('24px')
|
||||
// The store is untouched mid-drag (DOM is the only record).
|
||||
expect(store.getSnapshot().state!.floats[0]!.x).toBe(272)
|
||||
expect(store.getSnapshot().state!.floats[0]!.x).toBe(317)
|
||||
act(() => { header.dispatchEvent(new MouseEvent('pointerup', { bubbles: true, clientX: 200, clientY: 250, button: 0 })) })
|
||||
expect(store.getSnapshot().state!.floats[0]).toMatchObject({ x: 172, y: 234 })
|
||||
expect(store.getSnapshot().state!.floats[0]).toMatchObject({ x: 217, y: 24 })
|
||||
} finally {
|
||||
unmount()
|
||||
}
|
||||
@@ -258,14 +261,16 @@ describe('free windows: the window', () => {
|
||||
const win = container.querySelector<HTMLElement>('[data-dsh-float-window]')!
|
||||
const handle = win.querySelector<HTMLElement>('[class*="floatResize"]')!
|
||||
act(() => {
|
||||
handle.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true, clientX: 752, clientY: 564, button: 0 }))
|
||||
handle.dispatchEvent(new MouseEvent('pointermove', { bubbles: true, clientX: 852, clientY: 614 }))
|
||||
handle.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true, clientX: 700, clientY: 750, button: 0 }))
|
||||
handle.dispatchEvent(new MouseEvent('pointermove', { bubbles: true, clientX: 800, clientY: 800 }))
|
||||
})
|
||||
await flushFrame()
|
||||
expect(win.style.width).toBe('580px')
|
||||
expect(win.style.height).toBe('410px')
|
||||
act(() => { handle.dispatchEvent(new MouseEvent('pointerup', { bubbles: true, clientX: 852, clientY: 614, button: 0 })) })
|
||||
expect(store.getSnapshot().state!.floats[0]).toMatchObject({ w: 580, h: 410 })
|
||||
// +100/+50 from the created 390x744: height clamps to the viewport
|
||||
// ceiling (768 - y 12 = 756).
|
||||
expect(win.style.width).toBe('490px')
|
||||
expect(win.style.height).toBe('756px')
|
||||
act(() => { handle.dispatchEvent(new MouseEvent('pointerup', { bubbles: true, clientX: 800, clientY: 800, button: 0 })) })
|
||||
expect(store.getSnapshot().state!.floats[0]).toMatchObject({ w: 490, h: 756 })
|
||||
} finally {
|
||||
unmount()
|
||||
}
|
||||
@@ -279,7 +284,7 @@ describe('free windows: the window', () => {
|
||||
act(() => { store.reduce(s => floatTabReducer(s, 'notes', 512, 384)) })
|
||||
const win = container.querySelector<HTMLElement>('[data-dsh-float-window]')!
|
||||
const header = win.querySelector<HTMLElement>('[class*="floatHeader"]')!
|
||||
expect(win.style.left).toBe('272px')
|
||||
expect(win.style.left).toBe('317px')
|
||||
act(() => { header.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, cancelable: true, clientX: 300, clientY: 220 })) })
|
||||
// The menu list is PORTALED to document.body, yet its events still
|
||||
// bubble through the React tree into the header's onPointerDown —
|
||||
@@ -293,7 +298,7 @@ describe('free windows: the window', () => {
|
||||
header.dispatchEvent(new MouseEvent('pointermove', { bubbles: true, clientX: 200, clientY: 250 }))
|
||||
})
|
||||
await flushFrame()
|
||||
expect(win.style.left).toBe('272px')
|
||||
expect(win.style.left).toBe('317px')
|
||||
act(() => { dockRow.dispatchEvent(new MouseEvent('click', { bubbles: true })) })
|
||||
expect(store.getSnapshot().state!.floats).toHaveLength(0)
|
||||
expect(paneTabs(store)).toContain('notes')
|
||||
|
||||
+6
-4
@@ -919,8 +919,9 @@ describe('free windows (v0.16.0)', () => {
|
||||
expect(leaf.active).toBe('term:1')
|
||||
expect(floated.floats).toHaveLength(1)
|
||||
expect(floated.floats[0]!.tab).toEqual(git)
|
||||
// Default size centered on the drop point (no viewport → floors only).
|
||||
expect(floated.floats[0]).toMatchObject({ x: 300 - 240, y: 200 - 180, w: 480, h: 360 })
|
||||
// Phone-ratio default (390x780) centered on the drop point (no viewport
|
||||
// → size cap never bites; only the negative y clamps to 0).
|
||||
expect(floated.floats[0]).toMatchObject({ x: 300 - 195, y: 0, w: 390, h: 780 })
|
||||
expect(floated.activePane).toBe(leaf.id)
|
||||
})
|
||||
|
||||
@@ -961,8 +962,9 @@ describe('free windows (v0.16.0)', () => {
|
||||
expect(moved.floats[0]).toMatchObject({ x: 0, y: 0 })
|
||||
// Beyond the right/bottom edge clamps the top-left so the window fits.
|
||||
moved = moveFloat(moved, id, 5000, 5000)
|
||||
expect(moved.floats[0]!.x).toBe(1024 - 480)
|
||||
expect(moved.floats[0]!.y).toBe(768 - 360)
|
||||
// The float was CREATED capped to the viewport: w 390, h 768-24=744.
|
||||
expect(moved.floats[0]!.x).toBe(1024 - 390)
|
||||
expect(moved.floats[0]!.y).toBe(768 - 744)
|
||||
// No-op move returns the same reference (no persist churn).
|
||||
expect(moveFloat(moved, id, moved.floats[0]!.x, moved.floats[0]!.y)).toBe(moved)
|
||||
// Resize: floors, viewport ceiling, and the SE-corner anchor (x/y keep).
|
||||
|
||||
Reference in New Issue
Block a user