diff --git a/apps/presentation/dashboard/src/features/personal-workspace/delivery-review.css b/apps/presentation/dashboard/src/features/personal-workspace/delivery-review.css index 190f6fb4b6..b5cbe730d3 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/delivery-review.css +++ b/apps/presentation/dashboard/src/features/personal-workspace/delivery-review.css @@ -106,7 +106,7 @@ } -.delivery-notice summary { cursor: pointer; min-height: 24px; } +.delivery-notice summary { cursor: pointer; min-height: 44px; display: flex; align-items: center; } .delivery-acceptance-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; } .delivery-acceptance-controls label, .delivery-acceptance-controls details { flex-basis: 100%; min-width: 0; } diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-history.css b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-history.css index 6e48e534c6..2e57910fb4 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-history.css +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-history.css @@ -93,10 +93,13 @@ @media (max-width: 640px) { .research-history-heading { flex-wrap: wrap; gap: 12px; } .research-history-controls { --research-control-height: 44px; width: 100%; column-gap: 4px; } + .research-history-controls button { min-height: 44px; min-width: 44px; } .research-history-canvas { max-height: 350px; } .research-history-detail { padding: 20px; } .research-history-read-error, .research-history-empty { flex-wrap: wrap; } .research-history-read-error > button, .research-history-empty > button { margin-inline-start: 30px; } + .research-history-meta { flex-direction: column; align-items: flex-start; gap: 6px; } + .research-display-language { width: 100%; justify-content: space-between; } } @media (prefers-reduced-motion: reduce) { .research-history * { animation: none !important; transition: none !important; scroll-behavior: auto !important; } } diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.css b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.css index 8e618a113d..ed00edf098 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.css +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.css @@ -149,6 +149,10 @@ } .research-turn-costs { border-top: 1px solid var(--pw-line); padding-top: 12px; margin-top: 12px; } +.research-turn-costs-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; margin-block: 8px; } +.research-turn-costs-sort { display: flex; align-items: center; gap: 8px; font-size: var(--text-body); } +.research-turn-costs-sort label { width: auto; flex-shrink: 0; } +.research-turn-costs-sort select { flex-shrink: 0; min-height: 36px; padding: 4px 8px; border: 1px solid var(--pw-line); border-radius: 6px; background: var(--pw-card); color: var(--pw-text); font: inherit; font-size: var(--text-body); } .research-turn-costs > p { max-width: 72ch; color: var(--pw-muted); font-size: 12px; } .research-turn-cost-list { list-style: none; padding: 0; margin: 12px 0; } .research-turn-cost-list > li { padding: 12px 0; border-top: 1px solid var(--pw-line); } @@ -157,4 +161,7 @@ .research-turn-cost-list dt { font-size: 11px; color: var(--pw-muted); } .research-turn-cost-list dd { margin: 0; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; } .research-turn-costs nav { display: flex; align-items: center; gap: 12px; } -@media (max-width: 600px) { .research-turn-cost-list dl { grid-template-columns: repeat(2, minmax(0, 1fr)); } } +@media (max-width: 600px) { + .research-turn-cost-list dl { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .research-turn-costs-sort select { min-height: 44px; } +} diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.css b/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.css index 4eb0a976ab..7b1fbcfbb5 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.css +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.css @@ -98,7 +98,7 @@ .goal-work-rail-dialog .goal-all-work { border: 0; padding: 0; } .goal-work-rail-close { margin-bottom: 16px; } @media (max-width: 640px) { - .goal-overview .goal-direction-toggle { display: inline-flex; justify-self: start; min-height: 32px; border: 0; padding: 4px 0; background: transparent; color: var(--pw-muted); font-size: var(--text-caption); } + .goal-overview .goal-direction-toggle { display: inline-flex; align-items: center; justify-self: start; min-height: 44px; border: 0; padding: 4px 0; background: transparent; color: var(--pw-muted); font-size: var(--text-caption); } } .goal-owner-group > header .goal-owner-conversation { margin-left: auto; padding: 6px 8px; min-height: 44px; border: 0; background: transparent; color: var(--pw-text); font: inherit; font-size: var(--text-caption); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; } diff --git a/apps/presentation/dashboard/src/features/personal-workspace/research-turn-costs.tsx b/apps/presentation/dashboard/src/features/personal-workspace/research-turn-costs.tsx index f542aa341d..499b988055 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/research-turn-costs.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/research-turn-costs.tsx @@ -1,5 +1,6 @@ import {useId, useState} from "react"; import type {TrajectorySolverUsage} from "../../../../../../loopx/control_plane/capabilities/trajectory_assessment_types.js"; +import {ContextHelp} from "./context-help"; /** Presentation of existing Turn counters; never estimates missing usage or attributes it to control work. */ export function ResearchTurnCosts({usage, zh, onRead}: {usage: TrajectorySolverUsage; zh: boolean; @@ -21,13 +22,19 @@ export function ResearchTurnCosts({usage, zh, onRead}: {usage: TrajectorySolverU const number = (n: number | undefined | null) => n == null ? "—" : n.toLocaleString(zh ? "zh-CN" : "en"); return
{zh ? "定位耗时与输入最多的轮次" : "Find time and input hotspots"} -

{zh ? "仅比较当前捕获内的轮次。耗时含工具、等待与编排,不是可节省时间;轮次输入不等于 LoopX 控制输入。金额未采集。" : "Compares Turns within this capture only. Elapsed time includes tools, waiting and orchestration, not avoidable time. Turn input is not LoopX control input. Dollar cost is unmeasured."}

- {" "} - +
+
+ {" "} + +
+ +

{zh ? "仅比较当前捕获内的轮次。耗时含工具、等待与编排,不是可节省时间;轮次输入不等于 LoopX 控制输入。金额未采集。" : "Compares Turns within this capture only. Elapsed time includes tools, waiting and orchestration, not avoidable time. Turn input is not LoopX control input. Dollar cost is unmeasured."}

+
+
{rows.length ?
    {ordered.slice(current * size, (current + 1) * size).map(turn =>
  1. {zh ? `第 ${turn.ordinal} 轮` : `Turn ${turn.ordinal}`} diff --git a/examples/personal-workspace-browser/research-turn-costs.mjs b/examples/personal-workspace-browser/research-turn-costs.mjs index aa18f98ac2..400be9d5be 100644 --- a/examples/personal-workspace-browser/research-turn-costs.mjs +++ b/examples/personal-workspace-browser/research-turn-costs.mjs @@ -79,15 +79,33 @@ Path(sys.argv[1]).write_text(json.dumps(dict(goal_id=goal,display_name='Syntheti assert.match(await source.innerText(), /unfinished/); await source.getByRole('button', {name: '关闭原文', exact: true}).click(); await costs.getByRole('combobox').selectOption('input'); + const helpButton = costs.getByRole('button', {name: '轮次耗时与输入说明', exact: true}); + const helpPopover = costs.locator('.context-help-popover'); + await helpButton.focus(); + await helpPopover.waitFor({state: 'visible'}); + assert.match(await helpPopover.innerText(), /仅比较当前捕获内的轮次/); + assert.match(await helpPopover.innerText(), /轮次输入不等于 LoopX 控制输入/); + await page.keyboard.press('Escape'); + await helpPopover.waitFor({state: 'hidden'}); + assert.equal(await helpButton.evaluate(element => element === document.activeElement), true); for (const width of [1440, 390]) { await page.setViewportSize({width, height: 1000}); await costs.locator('summary').evaluate((element, offset) => {element.style.scrollMarginTop = `${offset}px`; element.scrollIntoView({block: 'start'});}, width < 600 ? 190 : 125); assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1)); + const sortBox = await costs.getByRole('combobox').boundingBox(); + assert(sortBox && sortBox.width >= 80 && sortBox.height >= (width < 600 ? 44 : 36)); + await helpButton.click(); + await helpPopover.waitFor({state: 'visible'}); + const helpBox = await helpPopover.boundingBox(); + assert(helpBox && helpBox.x >= 12 && helpBox.x + helpBox.width <= width - 12); await page.screenshot({path: resolve(outputDir, `research-turn-costs-${width}.png`), animations: 'disabled'}); + await page.keyboard.press('Escape'); + await helpPopover.waitFor({state: 'hidden'}); + assert.equal(await helpButton.evaluate(element => element === document.activeElement), true); } assert.deepEqual(errors, []); assert.deepEqual(writes, [], 'Cost inspection never starts analysis or mutates Goal state'); - return {coverageEntries: [], note: 'Real registered capture, typed reduction, HTTP and packaged UI: sort, pagination, incomplete/unknown counters, exact source evidence, keyboard return and mobile. No model or live benchmark.'}; + return {coverageEntries: [], note: 'Real registered capture, typed reduction, HTTP and source UI: sort, pagination, incomplete/unknown counters, accessible contextual help, exact source evidence, keyboard return and mobile. No model or live benchmark.'}; } catch (error) { if (page && !page.isClosed()) await page.screenshot({path: resolve(outputDir, 'research-turn-costs-failed.png')}); throw error;