fix(gui): polish sidebar layout, wordmark, tooltip, and fonts to figma

Fixed sidebar width (never concedes), figma session-row rail (16px twist +
status slots, triangle arrows, mount fade), exact brand wordmark svg with
tooltip'd rail controls, form controls inheriting the app font stack, and
inlined the twist button reset since the tsdown CSS pipeline drops composes.
This commit is contained in:
Yif
2026-07-23 17:08:47 +08:00
parent 9c01696a7e
commit 20720ef238
17 changed files with 523 additions and 272 deletions
+15 -26
View File
@@ -19,7 +19,7 @@ describe('clampWidth', () => {
describe('computeColumns', () => {
it('step 1: everything fits at preferred widths', () => {
const cols = computeColumns(1920, open(SIDEBAR_DEFAULT), open(DETAILS_DEFAULT))
expect(cols).toEqual({ sidebar: 300, center: 1920 - 300 - 360, details: 360 })
expect(cols).toEqual({ sidebar: 280, center: 1920 - 280 - 360, details: 360 })
})
it('closed sidebar keeps its compact rail while closed details contribute zero width', () => {
@@ -31,12 +31,13 @@ describe('computeColumns', () => {
const cols = computeColumns(1920, open(9999), open(1))
expect(cols.sidebar).toBe(420)
expect(cols.details).toBe(300)
expect(computeColumns(1920, open(1), open(DETAILS_DEFAULT)).sidebar).toBe(SIDEBAR_MIN)
})
it('step 2: details shrinks first, center pinned at min', () => {
// 300 + 360 + 640 = 1300 > 1250; details concedes to 1250-300-640 = 310.
// 280 + 360 + 640 = 1280 > 1250; details concedes to 1250-280-640 = 330.
const cols = computeColumns(1250, open(SIDEBAR_DEFAULT), open(DETAILS_DEFAULT))
expect(cols).toEqual({ sidebar: 300, center: CENTER_MIN, details: 310 })
expect(cols).toEqual({ sidebar: 280, center: CENTER_MIN, details: 330 })
})
it('boundary: exactly at the step-1/step-2 seam', () => {
@@ -46,28 +47,16 @@ describe('computeColumns', () => {
expect(one).toEqual({ sidebar: 300, center: CENTER_MIN, details: 359 })
})
it('step 3: sidebar concedes after details hits its min', () => {
// details floor 300: sidebar = 1220-300-640 = 280.
const cols = computeColumns(1220, open(SIDEBAR_DEFAULT), open(DETAILS_DEFAULT))
expect(cols).toEqual({ sidebar: 280, center: CENTER_MIN, details: DETAILS_MIN })
it('step 3: details auto-closes when its min still starves center — sidebar holds its preference', () => {
// 280 + 300 + 640 = 1220 > 1210 → details 0; sidebar untouched: center = 1210-280 = 930.
const cols = computeColumns(1210, open(SIDEBAR_DEFAULT), open(DETAILS_DEFAULT))
expect(cols).toEqual({ sidebar: 280, center: 930, details: 0 })
})
it('step 4: details auto-closes when both panels are at min and center still starves', () => {
// 240 + 300 + 640 = 1180 > 1100 → details 0; sidebar preference (300) fits: 1100-300 = 800 center.
const cols = computeColumns(1100, open(SIDEBAR_DEFAULT), open(DETAILS_DEFAULT))
expect(cols).toEqual({ sidebar: 300, center: 800, details: 0 })
})
it('step 4 keeps squeezing sidebar when preference no longer fits', () => {
// 900 < 300+640: sidebar = max(240, 900-640) = 260.
const cols = computeColumns(900, open(SIDEBAR_DEFAULT), open(DETAILS_DEFAULT))
expect(cols).toEqual({ sidebar: 260, center: CENTER_MIN, details: 0 })
})
it('step 5: center absorbs the deficit as last resort (details closed)', () => {
// 700 < 240+640: sidebar floors at 240, center takes 460 < CENTER_MIN.
it('the sidebar never concedes: center absorbs the deficit below CENTER_MIN', () => {
// 700 < 280+640: sidebar keeps 280, center takes 420 < CENTER_MIN.
const cols = computeColumns(700, open(SIDEBAR_DEFAULT), closed(DETAILS_DEFAULT))
expect(cols).toEqual({ sidebar: SIDEBAR_MIN, center: 460, details: 0 })
expect(cols).toEqual({ sidebar: SIDEBAR_DEFAULT, center: 420, details: 0 })
})
it('sidebar-closed narrow window: details concedes then auto-closes', () => {
@@ -81,11 +70,11 @@ describe('computeColumns', () => {
})
})
it('tiny viewport: both panels yield everything to center', () => {
it('tiny viewport: details closes, sidebar holds, center takes the remainder', () => {
const cols = computeColumns(400, open(SIDEBAR_DEFAULT), open(DETAILS_DEFAULT))
expect(cols.details).toBe(0)
expect(cols.sidebar).toBe(SIDEBAR_MIN)
expect(cols.center).toBe(Math.max(0, 400 - SIDEBAR_MIN))
expect(cols.sidebar).toBe(SIDEBAR_DEFAULT)
expect(cols.center).toBe(Math.max(0, 400 - SIDEBAR_DEFAULT))
})
it('recovery is pure: re-widening restores preferred widths untouched', () => {
@@ -99,7 +88,7 @@ describe('computeColumns', () => {
describe('computeColumns — degenerate viewports', () => {
it('sidebar closed and viewport below CENTER_MIN: details auto-closes, center takes the rest', () => {
// Reaches step 4's re-solve with the compact rail as the sidebar floor.
// Reaches step 3's auto-close with the compact rail sidebar.
expect(computeColumns(500, closed(300), open(DETAILS_DEFAULT)))
.toEqual({ sidebar: SIDEBAR_COLLAPSED, center: 500 - SIDEBAR_COLLAPSED, details: 0 })
})