Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; } }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand All @@ -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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 <details className="research-turn-costs">
<summary>{zh ? "定位耗时与输入最多的轮次" : "Find time and input hotspots"}</summary>
<p>{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."}</p>
<label htmlFor={id}>{zh ? "排序" : "Sort by"}</label>{" "}
<select id={id} value={sort} onChange={event => {setSort(event.target.value); setPage(0);}}>
<option value="input">{zh ? "输入最多" : "Most input"}</option>
<option value="elapsed">{zh ? "耗时最长" : "Longest elapsed"}</option>
<option value="time">{zh ? "时间顺序" : "Chronological"}</option>
</select>
<div className="research-turn-costs-toolbar">
<div className="research-turn-costs-sort">
<label htmlFor={id}>{zh ? "排序" : "Sort by"}</label>{" "}
<select id={id} value={sort} onChange={event => {setSort(event.target.value); setPage(0);}}>
<option value="input">{zh ? "输入最多" : "Most input"}</option>
<option value="elapsed">{zh ? "耗时最长" : "Longest elapsed"}</option>
<option value="time">{zh ? "时间顺序" : "Chronological"}</option>
</select>
</div>
<ContextHelp label={zh ? "轮次耗时与输入说明" : "Turn elapsed time and input note"}>
<p>{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."}</p>
</ContextHelp>
</div>
{rows.length ? <ol className="research-turn-cost-list">
{ordered.slice(current * size, (current + 1) * size).map(turn => <li key={turn.turn_id}>
<div><strong>{zh ? `第 ${turn.ordinal} 轮` : `Turn ${turn.ordinal}`}</strong>
Expand Down
20 changes: 19 additions & 1 deletion examples/personal-workspace-browser/research-turn-costs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading