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
33 changes: 22 additions & 11 deletions apps/desktop/e2e/send-message.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,22 +83,33 @@ test('renders a settled Mermaid fence as a diagram', async ({ window: page }) =>
await expect(diagram.locator('.maka-mermaid-source')).toContainText('flowchart TB');
await viewSource.click();

const toolbar = diagram.locator('.maka-mermaid-toolbar');
const toolbarBeforeZoom = await toolbar.boundingBox();
const diagramBeforeZoom = await diagram.boundingBox();
const viewportHeightBeforeZoom = await viewport.evaluate((element) => element.getBoundingClientRect().height);
// Zoom moves the diagram's content, never its chrome. Read the toolbar
// offset and the viewport height inside one evaluate: the transcript is a
// bottom-pinned scroller that re-pins on every ResizeObserver update, so two
// separate boundingBox() round-trips sample the same element at two scroll
// positions and turn that drift into a phantom offset change (#2000).
const readChrome = () => diagram.evaluate((element) => {
const diagramTop = element.getBoundingClientRect().top;
const toolbarTop = element.querySelector('.maka-mermaid-toolbar')?.getBoundingClientRect().top;
const viewportHeight = element.querySelector('.maka-mermaid-viewport')?.getBoundingClientRect().height;
return { toolbarOffset: (toolbarTop ?? 0) - diagramTop, viewportHeight: viewportHeight ?? 0 };
});
const chromeBeforeZoom = await readChrome();
const zoomIn = diagram.getByRole('button', { name: '放大图表' });
await zoomIn.click();
await expect(diagram).toHaveAttribute('data-maka-mermaid-zoom', '1.25');
await zoomIn.click();
await expect(diagram).toHaveAttribute('data-maka-mermaid-zoom', '1.50');
const toolbarAfterZoom = await toolbar.boundingBox();
const diagramAfterZoom = await diagram.boundingBox();
const viewportHeightAfterZoom = await viewport.evaluate((element) => element.getBoundingClientRect().height);
const toolbarOffsetBeforeZoom = (toolbarBeforeZoom?.y ?? 0) - (diagramBeforeZoom?.y ?? 0);
const toolbarOffsetAfterZoom = (toolbarAfterZoom?.y ?? 0) - (diagramAfterZoom?.y ?? 0);
expect(Math.abs(toolbarOffsetAfterZoom - toolbarOffsetBeforeZoom)).toBeLessThanOrEqual(1);
expect(Math.abs(viewportHeightAfterZoom - viewportHeightBeforeZoom)).toBeLessThanOrEqual(1);
// Poll for the steady state: the zoomed layout settles over a rAF, a
// ResizeObserver pass, and the scroller's re-pin, so a single instantaneous
// read asserts a frame the user never sees.
await expect.poll(async () => {
const chrome = await readChrome();
return Math.max(
Math.abs(chrome.toolbarOffset - chromeBeforeZoom.toolbarOffset),
Math.abs(chrome.viewportHeight - chromeBeforeZoom.viewportHeight),
);
}).toBeLessThanOrEqual(1);
await expect.poll(() => viewport.evaluate((element) =>
element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight)).toBe(true);
const zoomedBounds = await diagram.evaluate((element) => {
Expand Down
22 changes: 19 additions & 3 deletions packages/headless/src/__tests__/harbor-cell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1216,9 +1216,19 @@ describe('runHarborCell', () => {
const fallback = new Promise<IsolatedCommandResult>((resolve) => {
releaseFallback = () => resolve({ exitCode: 0, stdout: '', stderr: '' });
});
const fallbackTimer = setTimeout(releaseFallback, 5_000);
// The deadline is a wall-clock timer started when the cell is set up, so
// it races the cell's own storage/session/first-send cost before the turn
// ever reaches the tool. Lose that race and the run is still cancelled by
// `benchmark.deadline` while the backend is never stopped — the empty
// `stopModes` seen in CI. Budget the setup generously and name the race,
// so a future loss reports itself instead of a bare deepEqual mismatch.
const settleAfterMs = 3_000;
const startedAt = Date.now();
let toolActiveAfterMs: number | undefined;
const fallbackTimer = setTimeout(releaseFallback, settleAfterMs + 5_000);
const executor: IsolatedToolExecutor = {
exec: async (_input, control) => {
toolActiveAfterMs ??= Date.now() - startedAt;
const signal = control?.abortSignal;
if (!signal) return await fallback;
return await new Promise<IsolatedCommandResult>((_resolve, reject) => {
Expand All @@ -1233,7 +1243,7 @@ describe('runHarborCell', () => {
cwd: workspaceDir,
outputDir,
storageRoot,
settleAfterMs: 1_000,
settleAfterMs,
realBackendIsolation: {
kind: 'external',
label: 'cancellable test executor',
Expand All @@ -1247,11 +1257,17 @@ describe('runHarborCell', () => {
});
},
}),
3_000,
settleAfterMs + 10_000,
'Harbor cell did not cancel its active isolated tool',
);
clearTimeout(fallbackTimer);

assert.ok(
toolActiveAfterMs !== undefined && toolActiveAfterMs < settleAfterMs,
toolActiveAfterMs === undefined
? `the isolated tool must be active when the deadline fires, but it never started within the ${settleAfterMs}ms budget`
: `the isolated tool must be active when the deadline fires, but it started ${toolActiveAfterMs}ms in, past the ${settleAfterMs}ms budget`,
);
assert.equal(result.settledByDeadline, true);
assert.deepEqual(backend?.stopModes, ['immediate']);
assert.equal(result.output.tokenSummary?.total, 18);
Expand Down
7 changes: 6 additions & 1 deletion packages/ui/src/plan-reminder-form-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,12 @@ export function PlanReminderFormDialog(props: {
id="maka-plan-title"
className="maka-plan-title-input"
value={title}
autoFocus
// Not React's `autoFocus`: it runs during commit, before the
// dialog's showModal() makes the field visible, so the focus
// silently fails. Astryx's Dialog focuses `[data-autofocus]`
// after opening — the same seam TextInput's `hasAutoFocus`
// emits, which this bare input replaced.
data-autofocus
onChange={(event) => {
setTitleTouched(true);
setTitle(event.target.value.slice(0, 120));
Expand Down
Loading