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
2 changes: 1 addition & 1 deletion .github/workflows/make-pdf-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:
- name: ad-hoc codesign (Apple Silicon)
if: matrix.os == 'macos-latest'
run: |
for bin in browse/dist/browse browse/dist/find-browse design/dist/design make-pdf/dist/pdf; do
for bin in browse/dist/browse design/dist/design make-pdf/dist/pdf; do
codesign --remove-signature "$bin" 2>/dev/null || true
codesign -s - -f "$bin" || true
done
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/windows-setup-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ on:
- 'setup'
- 'runtime/**'
- 'browse/src/cli.ts'
- 'browse/src/find-browse.ts'
- 'bin/gstack-paths'
- '.github/workflows/windows-setup-e2e.yml'
workflow_dispatch:
Expand Down
1 change: 0 additions & 1 deletion BROWSER.md
Original file line number Diff line number Diff line change
Expand Up @@ -1258,7 +1258,6 @@ browse/
│ ├── error-handling.ts # safeUnlink / safeKill / isProcessAlive
│ ├── platform.ts # OS detection (macOS, Linux, Windows)
│ ├── telemetry.ts # Anonymous opt-in usage telemetry
│ ├── find-browse.ts # Locate running daemon or bootstrap
│ └── config.ts # Config resolution (env / files)
├── test/ # Integration tests + HTML fixtures
└── dist/
Expand Down
21 changes: 0 additions & 21 deletions browse/bin/find-browse

This file was deleted.

21 changes: 14 additions & 7 deletions browse/src/browser-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,10 @@ export class BrowserManager {
// ─── Watch Mode ─────────────────────────────────────────
private watching = false;
public watchInterval: ReturnType<typeof setInterval> | null = null;
private watchSnapshots: string[] = [];
// watch stop only ever shows the most recent snapshot, so we keep just that
// plus a count instead of retaining every 5s snapshot (~126 MB/hr of dead strings).
private lastWatchSnapshot: string | null = null;
private watchSnapshotCount = 0;
private watchStartTime: number = 0;

// ─── Headed State ────────────────────────────────────────
Expand Down Expand Up @@ -299,25 +302,29 @@ export class BrowserManager {

startWatch(): void {
this.watching = true;
this.watchSnapshots = [];
this.lastWatchSnapshot = null;
this.watchSnapshotCount = 0;
this.watchStartTime = Date.now();
}

stopWatch(): { snapshots: string[]; duration: number } {
stopWatch(): { count: number; last: string | null; duration: number } {
this.watching = false;
if (this.watchInterval) {
clearInterval(this.watchInterval);
this.watchInterval = null;
}
const snapshots = this.watchSnapshots;
const count = this.watchSnapshotCount;
const last = this.lastWatchSnapshot;
const duration = Date.now() - this.watchStartTime;
this.watchSnapshots = [];
this.lastWatchSnapshot = null;
this.watchSnapshotCount = 0;
this.watchStartTime = 0;
return { snapshots, duration };
return { count, last, duration };
}

addWatchSnapshot(snapshot: string): void {
this.watchSnapshots.push(snapshot);
this.lastWatchSnapshot = snapshot;
this.watchSnapshotCount++;
}

/**
Expand Down
7 changes: 7 additions & 0 deletions browse/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
* Zero side effects. Safe to import from build scripts and tests.
*/

// Implicit post-action settle window (click/fill/select and end-of-chain).
// networkidle resolves instantly on quiet pages, so this only caps the wait on
// pages holding a connection open (polling, analytics, websockets) — where the
// old 2000ms made every interaction feel slow. Explicit `wait --networkidle`
// stays available for a full wait.
export const NETWORK_SETTLE_MS = 500;

export const READ_COMMANDS = new Set([
'text', 'html', 'links', 'forms', 'accessibility',
'js', 'eval', 'css', 'attrs',
Expand Down
110 changes: 0 additions & 110 deletions browse/src/find-browse.ts

This file was deleted.

12 changes: 6 additions & 6 deletions browse/src/meta-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import type { BrowserManager } from './browser-manager';
import { handleSnapshot } from './snapshot';
import { getCleanText } from './read-commands';
import { READ_COMMANDS, WRITE_COMMANDS, META_COMMANDS, PAGE_CONTENT_COMMANDS, wrapUntrustedContent, canonicalizeCommand } from './commands';
import { READ_COMMANDS, WRITE_COMMANDS, META_COMMANDS, PAGE_CONTENT_COMMANDS, wrapUntrustedContent, canonicalizeCommand, NETWORK_SETTLE_MS } from './commands';
import { handleDomainSkillCommand } from './domain-skill-commands';
import { handleSkillCommand } from './browser-skill-commands';
import { validateNavigationUrl } from './url-validation';
Expand Down Expand Up @@ -706,9 +706,9 @@ export async function handleMetaCommand(
}
}

// Wait for network to settle after write commands before returning
// Settle window after the last write in a chain (see NETWORK_SETTLE_MS).
if (lastWasWrite) {
await bm.getPage().waitForLoadState('networkidle', { timeout: 2000 }).catch(() => {});
await bm.getPage().waitForLoadState('networkidle', { timeout: NETWORK_SETTLE_MS }).catch(() => {});
}

return results.join('\n\n');
Expand Down Expand Up @@ -842,11 +842,11 @@ export async function handleMetaCommand(
if (!bm.isWatching()) return 'Not currently watching.';
const result = bm.stopWatch();
const durationSec = Math.round(result.duration / 1000);
const lastSnapshot = result.snapshots.length > 0
? wrapUntrustedContent(result.snapshots[result.snapshots.length - 1], bm.getCurrentUrl())
const lastSnapshot = result.last
? wrapUntrustedContent(result.last, bm.getCurrentUrl())
: '(none)';
return [
`WATCH STOPPED (${durationSec}s, ${result.snapshots.length} snapshots)`,
`WATCH STOPPED (${durationSec}s, ${result.count} snapshots)`,
'',
'Last snapshot:',
lastSnapshot,
Expand Down
7 changes: 7 additions & 0 deletions browse/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1154,16 +1154,23 @@ async function handleCommandInternalImpl(
});
// Start periodic snapshot interval when watch mode begins
if (command === 'watch' && args[0] !== 'stop' && browserManager.isWatching()) {
// A full interactive snapshot can take >1s on large pages; guard against
// overlapping runs piling up when a snapshot outlasts the 5s interval.
let snapshotInFlight = false;
const watchInterval = setInterval(async () => {
if (!browserManager.isWatching()) {
clearInterval(watchInterval);
return;
}
if (snapshotInFlight) return;
snapshotInFlight = true;
try {
const snapshot = await handleSnapshot(['-i'], browserManager.getActiveSession());
browserManager.addWatchSnapshot(snapshot);
} catch {
// Page may be navigating — skip this snapshot
} finally {
snapshotInFlight = false;
}
}, 5000);
browserManager.watchInterval = watchInterval;
Expand Down
16 changes: 10 additions & 6 deletions browse/src/write-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { TEMP_DIR, isPathWithin } from './platform';
import { SAFE_DIRECTORIES } from './path-security';
import { modifyStyle, undoModification, resetModifications, getModificationHistory } from './cdp-inspector';
import { withCdpSession } from './cdp-bridge';
import { NETWORK_SETTLE_MS } from './commands';

/**
* Aggressive page cleanup selectors and heuristics.
Expand Down Expand Up @@ -374,8 +375,11 @@ export async function handleWriteCommand(
}
throw err;
}
// Wait for network to settle (catches XHR/fetch triggered by clicks)
await page.waitForLoadState('networkidle', { timeout: 2000 }).catch(() => {});
// Settle window for XHR/fetch triggered by the click. Idle pages resolve
// instantly; only busy/polling pages ever wait. Capped low so analytics-
// heavy apps that never reach networkidle don't tax every action 2s.
// Explicit `wait --networkidle` stays available when a full wait is needed.
await page.waitForLoadState('networkidle', { timeout: NETWORK_SETTLE_MS }).catch(() => {});
return `Clicked ${selector} → now at ${page.url()}`;
}

Expand All @@ -389,8 +393,8 @@ export async function handleWriteCommand(
} else {
await target.locator(resolved.selector).fill(value, { timeout: 5000 });
}
// Wait for network to settle (form validation XHRs)
await page.waitForLoadState('networkidle', { timeout: 2000 }).catch(() => {});
// Settle window for form-validation XHRs (see NETWORK_SETTLE_MS note above).
await page.waitForLoadState('networkidle', { timeout: NETWORK_SETTLE_MS }).catch(() => {});
return `Filled ${selector}`;
}

Expand All @@ -404,8 +408,8 @@ export async function handleWriteCommand(
} else {
await target.locator(resolved.selector).selectOption(value, { timeout: 5000 });
}
// Wait for network to settle (dropdown-triggered requests)
await page.waitForLoadState('networkidle', { timeout: 2000 }).catch(() => {});
// Settle window for dropdown-triggered requests (see NETWORK_SETTLE_MS note above).
await page.waitForLoadState('networkidle', { timeout: NETWORK_SETTLE_MS }).catch(() => {});
return `Selected "${value}" in ${selector}`;
}

Expand Down
61 changes: 0 additions & 61 deletions browse/test/find-browse.test.ts

This file was deleted.

Loading