删掉强度滑块的档位圆点;pointercancel 与「松手」同口径提交预览

- 用户口径:「你干脆把圆点删了吧,很丑…要做就做成几乎透明的」→ 按第一选择**删除**(不做半透明备选)
- 标记:`.project-chat-composer-strength-slider` 里那组 `<span class="project-chat-composer-strength-stops">` + 5 个点整体删除
- CSS:`space-between` 排布、`z-index: 2` 抬层、`.is-active{opacity:0}`、点的 `color-mix` 底色、以及只服务点对齐的 `padding: 0 7px` 一并删除;强度区横向内边距 `4px 6px 2px` → `4px 0 2px`(滑块直接铺满卡片内容宽度,填充段与圆钮两端与轨道贴齐,不再留多余空白);滑块容器去掉已无用的 `position: relative`
- 保留:圆钮(实心暖白 + 柔影 + 2px 间隙焦点环)、filled 段铺满轨道高度 6px、`--strength-ratio`、`当前:X`、`aria-valuetext`、←/→/Home/End、点轨道跳档、拖动连续跟手(预览 / 松手提交)
- pointercancel:触屏取消时与「松手」同口径提交当前预览(否则预览值会滞留到下一次提交或卸载 flush)
- 测试:`chatDialogFrameLayout.test.ts` 原来那组「点」断言整段换成**反向守卫**(样式表里不再有 `.project-chat-composer-strength-stops` 规则、组件源码也不再渲染该类名;滑块契约:宽 100% / 高 26 / 圆钮 20×20、强度区左右内边距 0);`home.suite.ts` 首页用例补一条「菜单里不再有那组点」
- 文档:pitfalls 的 D1 条改为现行口径(圆点已删、原因与判据;历史成因「被 6px 轨道盖住 / 与圆钮错位 ±12.2px」只留一句、由 Git 追溯)

Refs #600, PR #607
This commit is contained in:
2026-10-05 18:09:25 +08:00
parent 278ebc570e
commit 9c350eeda5
5 changed files with 54 additions and 92 deletions
@@ -20,6 +20,10 @@ const CHAT_CONVERSATION_PATH = repoPath(
const CHAT_COMPOSER_PATH = repoPath(
'apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectComposer/DirectProjectComposer.tsx',
);
/** 同目录的控件实现(推理强度滑块在这里渲染)。 */
const CHAT_COMPOSER_CONTROLS_PATH = repoPath(
'apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectComposer/ComposerControls.tsx',
);
function lengthPx(rawValue: string, label: string): number {
const value = rawValue.trim();
@@ -164,14 +168,13 @@ const SHARED_CARD = '.conversation-model-select .conversation-model-menu';
const SHARED_SHELL =
'.conversation-model-select .conversation-model-menu-shell';
const SHARED_CARD_IN_SHELL = `${SHARED_SHELL} .conversation-model-menu`;
/** 强度区与它那排装饰档位点(issue #600 的复核点:点被轨道压住、横向与圆钮错位)。
这些规则与卡片一样已经抬到共享样式面(组件自带类名,不带宿主祖先链)。 */
/** 强度区与滑块(issue #600 的复核点;档位圆点按用户口径已删除)。
这些规则与卡片一样在共享样式面(组件自带类名,不带宿主祖先链)。 */
const STRENGTH_SECTION = '.project-chat-reasoning-effort.is-section';
const STRENGTH_RANGE = '.project-chat-composer-strength-range';
const STRENGTH_THUMB = `${STRENGTH_RANGE}::-webkit-slider-thumb`;
/** 已删除的档位点容器:留着它做反向守卫(谁都别把点写回来)。 */
const STRENGTH_STOPS = '.project-chat-composer-strength-stops';
const STRENGTH_STOPS_DOT = `${STRENGTH_STOPS} span`;
const STRENGTH_STOPS_DOT_ACTIVE = `${STRENGTH_STOPS} span.is-active`;
const TOPBAR = `${CHAT} .project-chat-topbar`;
/** 列表实际会命中的全部选择器:direct-codex 直连与基础规则。 */
const LIST_SELECTORS = [MESSAGE_LIST, MESSAGE_LIST_PLAIN];
@@ -395,42 +398,37 @@ describe('陶泥儿对话区:Codex 三段式(顶栏 / 唯一滚动区 / 文
expect(pixelValue(trigger, 'height')).toBe(30);
});
it('档位点叠在轨道之上、横向与圆钮同一套算式,当前档位那一点留给圆钮(宽屏)', () => {
it('档位圆点已删除,滑块契约与「两端贴齐」的强度区内边距仍在(宽屏)', () => {
const section = desktopDeclarations(STRENGTH_SECTION);
const range = desktopDeclarations(STRENGTH_RANGE);
const thumb = desktopDeclarations(STRENGTH_THUMB);
const stops = desktopDeclarations(STRENGTH_STOPS);
const dot = desktopDeclarations(STRENGTH_STOPS_DOT);
const activeDot = desktopDeclarations(STRENGTH_STOPS_DOT_ACTIVE);
// 点必须画在轨道**之上**:range 自己是 `z-index: 1`,点得比它高才不会被 6px 轨道压掉。
expect(declaration(stops, 'position')).toBe('absolute');
expect(
Number(declaration(stops, 'z-index')),
'档位点要在轨道(range 的 z-index)之上',
).toBeGreaterThan(Number(declaration(range, 'z-index')));
// 横向跟圆钮共用一套算式:`space-between` + 两侧「圆钮半径 − 半点宽」的内边距,
// 首末点的**中心**落点 = 圆钮两端极限位置(= 轨道两端内缩一个半径)。
expect(declaration(stops, 'justify-content')).toBe('space-between');
const knobRadius = pixelValue(thumb, 'width') / 2;
const dotHalf = pixelValue(dot, 'width') / 2;
const stopsPadding = paddingBox(stops);
expect(
stopsPadding.left,
'首点的中心要落在圆钮的左极限位置(改点宽/钮径都要同步这条算式)',
).toBe(knobRadius - dotHalf);
expect(stopsPadding.right).toBe(knobRadius - dotHalf);
// 当前档位那一点不画:圆钮已经压在那儿,再画出来就是"靶心"。
expect(pixelValue(activeDot, 'opacity')).toBe(0);
// 点:直径 == 轨道高度(6px,不再上下各露 0.5px),浅暖色从既有 token 派生(对比度实测见 PR)。
expect(pixelValue(dot, 'width')).toBe(6);
expect(pixelValue(dot, 'height')).toBe(pixelValue(stops, 'height'));
expect(declaration(dot, 'background')).toContain('color-mix');
expect(declaration(dot, 'background')).toContain(
'var(--platform-subpanel-border)',
// 反向守卫①:样式表里**不再有**档位点的规则(谁把点写回来,这条就红)。
expect(stops.has('position')).toBe(false);
expect(stops.size).toBe(0);
// 反向守卫②:组件源码里也不再渲染那组点。
const controlsSource = readFileSync(CHAT_COMPOSER_CONTROLS_PATH, 'utf8');
expect(controlsSource).not.toContain(
'project-chat-composer-strength-stops',
);
expect(dot.has('border')).toBe(false);
// 滑块本体契约不变:铺满卡片内容宽度,轨道 6px、圆钮 20px。
expect(declaration(range, 'width')).toBe('100%');
expect(pixelValue(range, 'height')).toBe(26);
expect(pixelValue(thumb, 'width')).toBe(20);
expect(pixelValue(thumb, 'height')).toBe(20);
// 强度区左右不留内边距(那条 padding 原先只服务档位点排布):填充段与圆钮两端与轨道贴齐,
// 中间不出现多余留白。上下留白不动(4 / 2)。
const sectionPadding = paddingBox(section);
expect(
sectionPadding.left,
'滑块左右不留白:点删掉后这条内边距必须归零',
).toBe(0);
expect(sectionPadding.right).toBe(0);
expect(sectionPadding.top).toBe(4);
expect(sectionPadding.bottom).toBe(2);
});
it('窄屏(390px)输入盒仍在文档流、四周有边距,列表底部没有留白', () => {