effect-browser 0.2.0-beta.8 → 0.2.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -316,6 +316,8 @@ Descriptors compare the full exact kind and label, optional destination and stab
316
316
 
317
317
  `Plan.recorded(ran)` projects only completely captured successful intent. Literal fill/type values become deterministic named input slots; existing input names and source conditions remain unchanged. `Plan.inputSlots(plan)` lists every named input a plan reads, with its step, action path and whether it is typed `text` or a replacement `value`, so a host binds `inputs` to replay a recording without the literal ever entering the stored plan. Failed or incomplete whole forms and unknown attempts are not reusable plans. After a later failure, `Plan.recorded(yield* operation.attempts, { through: completedStepId })` can explicitly retain an acknowledged completed prefix. Capture is based on checked native facts, and missing or omitted identity facts produce `Incomplete` rather than executable descriptor intent.
318
318
 
319
+ `Plan.recordedNavigation(operation, { id })` records a navigation started with `startNavigation`, such as the operation an agent-browser `Navigation` receipt holds, as a plan of one `Navigate` step named `id` (`"navigate"` by default). It joins `completed`, which stops nothing. Only a navigation that reached DOMContentLoaded is recorded; one that failed, was stopped or abandoned, or whose outcome is unknown fails `RecordingIncomplete` with `Unacknowledged`, as an unacknowledged run step does. The step holds `operation.request`, the address asked for, not the `NavigationResult.url` a redirect reached, so a replay repeats the request, as a recorded run's `Navigate` step does.
320
+
319
321
  `yield* page.settled({ quiet: "100 millis", within: "2 seconds" })` uses one owned wait in the pinned document. It reports quiet DOM mutation, scroll, root geometry and viewport signals. It does not establish network, descendant animation or business completion. Native timers work on hidden pages, but browser throttling can still cause an honest timeout. Cancellation retains wait capacity until native observer disposal is confirmed. A page-initiated navigation that replaces the observed document ends the wait `Stale` and confirms that disposal, so the next wait can start. Plain plans use the same browser inputs as ordinary operations; presentation and recording composition stay with the host.
320
322
 
321
323
  ### Replayable session evidence
@@ -587,7 +589,11 @@ reports `ScheduleMissed` without inventing an attempt; an insufficient pacing bu
587
589
  offsets, so a slow reply delays one stroke rather than every later one. Before each stroke's
588
590
  first key, the rest of the schedule must still fit the deadline at the round trips the browser
589
591
  has actually taken; a stroke that could not finish is refused whole, and strokes already
590
- acknowledged stay `performed`. Receipts retain requested/intended/actual start, deadline
592
+ acknowledged stay `performed`. A performed click is held to the same rule. Playwright's click
593
+ checks the node again before its first input event, and a deadline inside those checks would
594
+ report `unknown` for input never sent, so a click the deadline cannot fit at the press's
595
+ measured round trip is refused `TimingBudgetExceeded`, undispatched, and its Page stays open.
596
+ Receipts retain requested/intended/actual start, deadline
591
597
  and lateness. Cancellation stops future submissions and keeps readable attempt history with the
592
598
  original Effect Cause. Known preparatory work followed by refusal is rejected with its subphase
593
599
  receipts; pending input remains unknown, and acknowledged logical input stays performed when a
@@ -664,6 +670,7 @@ const { url: loaded } = yield * operation.completed;
664
670
 
665
671
  The owner's permit is released as soon as the navigation is dispatched. While it loads, reads, checkpoints, holds and every other page proceed, and anything that would change _this_ page fails `busy` and `undispatched`. `navigate` runs on the same machinery, so there is one navigator.
666
672
 
673
+ - `request` is what was asked for, checked before dispatch. `Plan.recordedNavigation` records it once `completed` succeeds.
667
674
  - `completed` belongs to that one navigation: a successor reaching the same URL fails it instead of completing it, and its URL is the page that navigated, not whichever page is selected by then. **Interrupting a waiter stops nothing.** The browser keeps loading and nothing is dispatched again.
668
675
  - `stop` asks the browser to stop loading. Its acknowledgement is a known outcome: `completed` then fails `interrupted`, the page holds whatever had loaded, and the session stays usable. It does not undo anything the page already did. Concurrent callers share their active stop attempt. A busy refusal or cancellation before dispatch allows a later request only after native setup and its port retire; pending or unconfirmed retirement keeps setup capacity occupied. Once a stop is dispatched, every later caller receives that attempt's recorded result, including failure or interruption, without sending it again. A completed navigation cannot stop its successor.
669
676
  - A main-frame loading timeout asks the same stop coordinator to retire that exact navigation. Acknowledged stop completes it with `Timeout/unknown` and leaves the owner open: partial content remains inspectable and deliberate subsequent input is allowed. An explicit stop racing recovery shares its attempt; a completed predecessor cannot stop a successor. The `unknown` outcome still says nothing about page effects before cancellation.
@@ -898,6 +905,17 @@ receives the original containment policy. A later failure never weakens what was
898
905
 
899
906
  A character the US layout cannot produce is committed as text, the way an input method commits it: the field changes and no key event says so. Plain `type` sends a shifted character as its own key with `shiftKey` false. When a page reads the modifier, send that stroke through `press` with `Shift` held, spelling the key as the page will see it: `{ key: "A", modifiers: ["Shift"] }`. Spelled `"a"`, the engine sends `a` with Shift down, which is what Shift produces with Caps Lock on. Control characters are refused in text because the engine presses Enter for a line break; a named key is always its own `press`. Performed plans hold Shift for uppercase and shifted punctuation and pace complete balanced strokes under one logical action. A stroke remains unresolved until its key and modifiers are released; quiet intervals do not renew the absolute deadline or retain unresolved native replies.
900
907
 
908
+ Like a plain window, a performed stroke submits its modifiers, its key and their releases in order
909
+ without waiting for each reply, so its key-up is submitted its planned hold after its key-down
910
+ rather than a round trip later, and a held Shift adds no round trip of its own. Delivery jitter
911
+ on the way to a remote browser is not controlled. The owner is still checked before every
912
+ command, nothing more is sent once a reply has failed, and the stroke drains all of its at most ten
913
+ replies before it resolves. Each stroke therefore still costs its releases' round trip, and its
914
+ focus check's when it names an element: against a remote browser, a stroke whose planned interval
915
+ is shorter than those starts late. A slow renderer still handles a stroke's events one at a time,
916
+ so the deadline check charges each stroke what strokes with as many commands have taken, scaled up
917
+ from smaller strokes until one of its size has drained.
918
+
901
919
  A press waits for native input acknowledgement, without waiting for resulting navigation. If Enter submits a form, wait for what the next document shows with `waitFor`. A receipt carries the same target, pointer position and interval as any other input, and never says which key was pressed or what was typed. Typing a secret is still more observable than one `fill`, because the page sees every stroke; prefer `fill` for one unless the page requires keys. Both operations are available in the model-facing toolkit in `effect-agent-browser`.
902
920
 
903
921
  The connection endpoint is read through the exact allocated session, so a provider reply that names a different session is refused before any CDP attachment.
@@ -910,7 +928,7 @@ and reads do not hold it up; the pause refuses them as `Stale`. Work that cannot
910
928
  such as a navigation still loading, fails the request with `Timeout/undispatched`: no handoff is
911
929
  granted, the barrier lifts and automation continues. Resume requires an explicit operator-release signal
912
930
  and returns fresh bounded Page inventory under the lifecycle permit; observing a newly acquired
913
- Page is explicit. Once the pause is installed, a failed Live View authorization does not silently resume automation. Live View URLs are temporary bearer material; iframe styling is not an authorization boundary. Live View is also where browser-window presentation already exists for watching a session as it runs, and it is the provider's: beside each full-screen URL Browserbase issues a bordered one (`debuggerUrl`, "mimic a real browser with borders"), and a navbar that `navbar=false` hides. This package decodes and returns only the full-screen URL. The bordered one carries the same control authority and would be issued under the same rules, so surfacing it is a small host-only addition whenever something needs it; nothing here does yet, so it is not exported.
931
+ Page is explicit. Every Page and Frame issued before the handoff, `initialPage` included, stays retired after resume, though the connection is the same: its operations fail `Stale` and `undispatched`, so issue Pages again from `inventory.pages` with `session.page(info)`. Once the pause is installed, a failed Live View authorization does not silently resume automation. Live View URLs are temporary bearer material; iframe styling is not an authorization boundary. Live View is also where browser-window presentation already exists for watching a session as it runs, and it is the provider's: beside each full-screen URL Browserbase issues a bordered one (`debuggerUrl`, "mimic a real browser with borders"), and a navbar that `navbar=false` hides. This package decodes and returns only the full-screen URL. The bordered one carries the same control authority and would be issued under the same rules, so surfacing it is a small host-only addition whenever something needs it; nothing here does yet, so it is not exported.
914
932
 
915
933
  ## Registrations, capabilities and document readiness
916
934
 
@@ -88,6 +88,17 @@ declare const inputSlots: (plan: Plan$1 | LivePlan) => ReadonlyArray<InputSlot>;
88
88
  declare const recorded: (value: Ran | AttemptSnapshot, options?: {
89
89
  readonly through?: string;
90
90
  }) => Effect.Effect<Plan$1, RecordingIncomplete>;
91
+ /**
92
+ * Project one `startNavigation` operation, such as an agent-browser `Navigation` receipt's, as a
93
+ * plan of one `Navigate` step named `id` ("navigate" by default). It joins `completed`, which
94
+ * stops nothing: only a navigation that reached DOMContentLoaded is acknowledged, and one that
95
+ * failed, was stopped or interrupted, or whose outcome is unknown is refused `Unacknowledged`.
96
+ * The step repeats the request, not the address a redirect reached, as a recorded run's
97
+ * Navigate step does. An `id` that is not a step identifier fails `IncompleteCapture`.
98
+ */
99
+ declare const recordedNavigation: (operation: NavigationOperation, options?: {
100
+ readonly id?: string;
101
+ }) => Effect.Effect<Plan$1, RecordingIncomplete>;
91
102
  //#endregion
92
103
  //#region src/Browser.d.ts
93
104
  /**
@@ -277,6 +288,11 @@ interface ElementAdmission {
277
288
  interface NavigationOperation {
278
289
  /** What was navigated, read before dispatch. */
279
290
  readonly target: Target;
291
+ /**
292
+ * What was asked for, as checked before dispatch. `Plan.recordedNavigation` records this
293
+ * request, not the address `completed` reports after any redirect.
294
+ */
295
+ readonly request: StartNavigationRequest;
280
296
  /**
281
297
  * The document reached DOMContentLoaded. It belongs to this one navigation: a successor that
282
298
  * reaches the same URL fails it instead. Interrupting a waiter stops nothing in the browser.
@@ -367,6 +383,6 @@ declare const scoped: {
367
383
  <S, E, AE, AR, A, E2, R2>(open: Effect.Effect<S & BrowserSession<E>, AE, AR>, f: (session: S & BrowserSession<E>) => Effect.Effect<A, E2, R2>): Effect.Effect<A, AE | E | E2 | InitializationError | BrowserError, Exclude<AR | R2, Scope.Scope>>;
368
384
  };
369
385
  //#endregion
370
- export { StepFailed as C, inputSlots as D, encode as E, make as O, RunOptions as S, decode as T, scoped as _, ElementAdmission as a, RecordingIncomplete as b, OpenOptions as c, PageOperations as d, PageStatus as f, checkPage as g, SettledRequest as h, BrowserSession as i, recorded as k, OperationOptions as l, SessionAdmissionStatus as m, AdmissionStatus as n, Frame as o, ResolveOptions as p, AnySession as r, NavigationOperation as s, AdmissionOptions as t, Page as u, InputSlot as v, checkRunOptions as w, RunOperation as x, PlanOperations as y };
386
+ export { recordedNavigation as A, StepFailed as C, inputSlots as D, encode as E, make as O, RunOptions as S, decode as T, scoped as _, ElementAdmission as a, RecordingIncomplete as b, OpenOptions as c, PageOperations as d, PageStatus as f, checkPage as g, SettledRequest as h, BrowserSession as i, recorded as k, OperationOptions as l, SessionAdmissionStatus as m, AdmissionStatus as n, Frame as o, ResolveOptions as p, AnySession as r, NavigationOperation as s, AdmissionOptions as t, Page as u, InputSlot as v, checkRunOptions as w, RunOperation as x, PlanOperations as y };
371
387
  export {};
372
- //# sourceMappingURL=Browser-n-bKgBNN.d.mts.map
388
+ //# sourceMappingURL=Browser-BF9_sORP.d.mts.map
@@ -1,3 +1,3 @@
1
- import { _ as scoped, a as ElementAdmission, c as OpenOptions, d as PageOperations, f as PageStatus, g as checkPage, h as SettledRequest, i as BrowserSession, l as OperationOptions, m as SessionAdmissionStatus, n as AdmissionStatus, o as Frame, p as ResolveOptions, r as AnySession, s as NavigationOperation, t as AdmissionOptions, u as Page } from "./Browser-n-bKgBNN.mjs";
1
+ import { _ as scoped, a as ElementAdmission, c as OpenOptions, d as PageOperations, f as PageStatus, g as checkPage, h as SettledRequest, i as BrowserSession, l as OperationOptions, m as SessionAdmissionStatus, n as AdmissionStatus, o as Frame, p as ResolveOptions, r as AnySession, s as NavigationOperation, t as AdmissionOptions, u as Page } from "./Browser-BF9_sORP.mjs";
2
2
  export { AdmissionOptions, AdmissionStatus, AnySession, BrowserSession, ElementAdmission, Frame, NavigationOperation, OpenOptions, OperationOptions, Page, PageOperations, PageStatus, ResolveOptions, SessionAdmissionStatus, SettledRequest, checkPage, scoped };
3
3
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"Browser.mjs","names":[],"sources":["../src/Browser.ts"],"sourcesContent":["import { type Duration, Effect, type Scope, type Stream } from \"effect\";\nimport { dual } from \"effect/Function\";\n\nimport type * as Bootstrap from \"./Bootstrap.ts\";\nimport type {\n BrowserDiagnostics,\n Checkpoint,\n CheckpointOptions,\n FillFormOptions,\n FillFormRequest,\n FillFormResult,\n FillRequest,\n HoverRequest,\n InputReceipt,\n KeyStroke,\n NavigateRequest,\n NavigationResult,\n ObservationOptions,\n ObservedElement,\n PointerMoveRequest,\n PressRequest,\n ReadTextRequest,\n ScreenshotRequest,\n ScreenshotResult,\n ScrollRequest,\n SelectOptions,\n SessionStatus,\n StartNavigationRequest,\n TextResult,\n TypeRequest,\n WaitForElementRequest,\n WheelRequest,\n ControlFacts,\n ActionResult,\n ClickRequest,\n FrameInfo,\n Observation,\n PageInfo,\n Target,\n Viewport,\n} from \"./BrowserData.ts\";\nimport type { BrowserError, BrowserOperation, Containment, InitializationError } from \"./Errors.ts\";\nimport { resolveTargetControlsForSession } from \"./internal/browser/PageRegistry.ts\";\nimport type { PlanOperations } from \"./Plan.ts\";\nimport type { DescriptorEncoded, ResolveGuard, SettledEvidence } from \"./PlanData.ts\";\nimport type { Timeline } from \"./Timeline.ts\";\nimport type { PageEvent, TimelineError } from \"./TimelineData.ts\";\n\n/**\n * Host-only ordinary admission. Omission or zero fails immediately; positive finite duration\n * queues FIFO. Page closure uses reserved fail-fast admission and a deadline-bounded shared close.\n */\nexport interface AdmissionOptions {\n readonly queue?: Duration.Input;\n}\n\n/** Explicit host options, separate from request data and exact-element admission callbacks. */\nexport interface OperationOptions {\n readonly timeoutMillis?: number;\n readonly admission?: AdmissionOptions;\n}\n\n/** Explicit descriptor guard and ordinary host admission; no selector or geometry fallback. */\nexport interface ResolveOptions extends OperationOptions {\n readonly guard?: typeof ResolveGuard.Encoded;\n}\n\n/** Named native quiet signals within the original positive finite deadline. */\nexport interface SettledRequest {\n readonly quiet: Duration.Input;\n readonly within: Duration.Input;\n}\n\n/** A bounded host-memory snapshot; it grants no priority or native authority. */\nexport interface AdmissionStatus {\n readonly active: BrowserOperation | null;\n readonly waiting: number;\n readonly maximum: number;\n readonly oldestWaitMillis: number | null;\n readonly nativePending: number;\n readonly nativeWaitPending: boolean;\n readonly stopSetupPending: boolean;\n}\n\nexport interface SessionAdmissionStatus {\n readonly waiting: number;\n readonly maximum: number;\n readonly nativePending: number;\n readonly nativeMaximum: number;\n readonly nativeWaits: number;\n readonly stopSetups: number;\n readonly registry: AdmissionStatus;\n}\n\n/** Terminal facts remain readable after this page loses live authority. */\nexport interface PageStatus {\n readonly identity: Target;\n readonly phase: \"open\" | \"paused\" | \"closing\" | \"closed\" | \"stale\";\n readonly containment: Containment;\n readonly admission: AdmissionStatus;\n}\n\nexport interface PageOperations {\n readonly navigate: (\n request: NavigateRequest,\n options?: OperationOptions,\n ) => Effect.Effect<NavigationResult, BrowserError>;\n /** `navigate`, left in flight: the same single dispatch, completed by the caller. */\n readonly startNavigation: (\n request: StartNavigationRequest,\n options?: OperationOptions,\n ) => Effect.Effect<NavigationOperation, BrowserError, Scope.Scope>;\n readonly readText: (\n request: ReadTextRequest,\n options?: OperationOptions,\n ) => Effect.Effect<TextResult, BrowserError>;\n readonly click: (\n request: ClickRequest,\n options?: OperationOptions,\n ) => Effect.Effect<ActionResult, BrowserError>;\n readonly fill: (\n request: FillRequest,\n options?: OperationOptions,\n ) => Effect.Effect<ActionResult, BrowserError>;\n /** Script in the page: instantaneous, and it raises no wheel event. */\n readonly scroll: (\n request: ScrollRequest,\n options?: OperationOptions,\n ) => Effect.Effect<ActionResult, BrowserError>;\n /** One real pointer move, in main-frame viewport pixels. */\n readonly pointerMove: (\n request: PointerMoveRequest,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /** Places the pointer on one exact element where it is, or fails `not-visible` unsent. */\n readonly hover: (\n request: HoverRequest,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /** One real wheel event; the browser decides what under the pointer scrolls. */\n readonly wheel: (\n request: WheelRequest,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /** One real key stroke to whatever has focus, or fails `not-focused` unsent if `into` lacks it. */\n readonly press: (\n request: PressRequest,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /** Text as the real key strokes that produce it, under the same focus rule as `press`. */\n readonly type: (\n request: TypeRequest,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n readonly screenshot: (\n request: ScreenshotRequest,\n options?: OperationOptions,\n ) => Effect.Effect<ScreenshotResult, BrowserError>;\n /**\n * The one observation whose nodes later actions may name. `scope: \"viewport\"` keeps only text\n * and controls that are on screen and reachable, plus bounded choices of visible native\n * selects; the default reads the whole document. It carries no destination, form or field value.\n */\n readonly observe: (\n options?: ObservationOptions,\n operationOptions?: OperationOptions,\n ) => Effect.Effect<Observation, BrowserError>;\n /**\n * Passive evidence for a recorder, with a picture when asked. It issues no references and\n * leaves the observation above exactly as it was. Host-only: it carries control facts.\n */\n readonly checkpoint: (\n options?: CheckpointOptions,\n operationOptions?: OperationOptions,\n ) => Effect.Effect<Checkpoint, BrowserError>;\n /** Host-only facts about one observed control, read from that exact node just now. */\n readonly controlFacts: (\n reference: ObservedElement,\n options?: OperationOptions,\n ) => Effect.Effect<ControlFacts, BrowserError>;\n /**\n * After a page hold, nothing observed on that page may be acted on unchecked. This checks one\n * reference: still attached, and still the control that was inspected. It never searches by\n * selector or label for a substitute, and it sends nothing.\n */\n readonly revalidateElement: (\n reference: ObservedElement,\n options?: OperationOptions,\n ) => Effect.Effect<ObservedElement, BrowserError>;\n readonly clickElement: (\n reference: ObservedElement,\n admission?: ElementAdmission,\n options?: OperationOptions,\n ) => Effect.Effect<ActionResult, BrowserError>;\n readonly fillElement: (\n reference: ObservedElement,\n value: string,\n admission?: ElementAdmission,\n options?: OperationOptions,\n ) => Effect.Effect<ActionResult, BrowserError>;\n /** One selection using this exact select and its issued option element IDs, with fresh checks. */\n readonly selectOption: (\n reference: ObservedElement,\n options: SelectOptions,\n admission?: ElementAdmission,\n operationOptions?: OperationOptions,\n ) => Effect.Effect<ActionResult, BrowserError>;\n readonly hoverElement: (\n reference: ObservedElement,\n admission?: ElementAdmission,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /** A key stroke sent only if the exact node an observation named already has focus. */\n readonly pressElement: (\n reference: ObservedElement,\n stroke: KeyStroke,\n admission?: ElementAdmission,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /** Real typing with `fillElement`'s exactness: that node must already have focus. */\n readonly typeElement: (\n reference: ObservedElement,\n text: string,\n admission?: ElementAdmission,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /**\n * Set several controls of one observation in order, then optionally click one submit control.\n * Every step acts on the exact observed node after the same fresh checks as `fillElement`,\n * except that a control may have become enabled since it was observed, and each is admitted\n * and charged as its own action. The observation stays usable for this form's own steps only;\n * anything else that changes the page still retires it, and the form retires it when it ends.\n * The first refused or uncertain step ends the form. Submit is sent only after every field\n * succeeded and, unless `verify` is false, still holds what its step left there. Fields set\n * before a stop stay set. It fails only when its first step does; otherwise `stopped` says\n * where it ended and why.\n */\n readonly fillForm: (\n request: FillFormRequest,\n admission?: ElementAdmission,\n options?: FillFormOptions,\n operationOptions?: OperationOptions,\n ) => Effect.Effect<FillFormResult, BrowserError>;\n /**\n * Wait on the original observed node within the host deadline. Hidden includes its detachment;\n * document replacement is stale. Success observes a condition, without authorizing later input.\n */\n readonly waitForElement: (\n request: WaitForElementRequest,\n options?: OperationOptions,\n ) => Effect.Effect<void, BrowserError>;\n readonly waitFor: (\n request: {\n readonly selector: string;\n readonly state: \"visible\" | \"hidden\" | \"attached\" | \"detached\";\n },\n options?: OperationOptions,\n ) => Effect.Effect<void, BrowserError>;\n /** Navigation observation is registered before the single click dispatch. */\n readonly clickAndWait: (\n request: ClickRequest,\n options?: OperationOptions,\n ) => Effect.Effect<ActionResult, BrowserError>;\n /**\n * Readiness of the current document only. Dependent operations wait for it themselves;\n * this reports it without charging an action, so a caller can decide what to do about a\n * document that predates the registrations.\n */\n readonly ready: (\n options?: OperationOptions,\n ) => Effect.Effect<Bootstrap.ReadinessOutcome, InitializationError>;\n}\n\n/**\n * Issued live authority for one native frame; navigation keeps the frame and retires its references.\n *\n * Pass the issued object itself. Capture, page control, provider transfers, agent tools and\n * `checkPage` authenticate it by identity, so a spread copy, wrapper or Proxy is refused\n * `UnregisteredSession` even though its copied methods still run. Observe work through the\n * timeline and run receipts rather than by wrapping methods.\n */\nexport interface Frame extends PageOperations, PlanOperations {\n readonly identity: Target;\n readonly status: Effect.Effect<PageStatus>;\n /**\n * The original owner's host monotonic clock, the same one `session.monotonicTimeNanos` reads,\n * so a host bound to this Page or Frame can compute a plan's `startAt` without the session.\n */\n readonly monotonicTimeNanos: Effect.Effect<bigint>;\n /** Resolve full descriptor intent against the complete current native candidate inventory. */\n readonly resolve: (\n descriptor: DescriptorEncoded,\n options?: ResolveOptions,\n ) => Effect.Effect<ObservedElement, BrowserError>;\n /**\n * Quiet DOM mutation, scroll, root geometry and viewport signals in one pinned document.\n * This does not establish network, animation or business completion.\n */\n readonly settled: (\n request: SettledRequest,\n options?: OperationOptions,\n ) => Effect.Effect<SettledEvidence, BrowserError>;\n}\n\n/** Issued live authority for one native page on its original connection; pass it as issued, see `Frame`. */\nexport interface Page extends Frame {\n /** Evidence for this issued Page's original domain; navigation does not change its journal. */\n readonly timeline: Timeline;\n /**\n * Read this page's address and title. Like `listFrames`, it is a read of the page and shares\n * its permit; pass `admission.queue` to wait behind work already running there.\n */\n readonly describe: (options?: OperationOptions) => Effect.Effect<PageInfo, BrowserError>;\n readonly listFrames: (\n options?: OperationOptions,\n ) => Effect.Effect<ReadonlyArray<FrameInfo>, BrowserError>;\n /** Issue a child Frame. Like `session.page`, issuing takes the registry permit, not this page's. */\n readonly frame: (\n info: FrameInfo,\n options?: OperationOptions,\n ) => Effect.Effect<Frame, BrowserError>;\n readonly resizeViewport: (\n viewport: Viewport,\n options?: OperationOptions,\n ) => Effect.Effect<void, BrowserError>;\n /** Reserved fail-fast admission; timeoutMillis bounds joining a close, not admission.queue. */\n readonly close: (options?: OperationOptions) => Effect.Effect<void, BrowserError>;\n}\n\n/**\n * A host's decision about one control, made on facts read from the exact node immediately\n * before input. Returning anything but `true`, or throwing, sends nothing and fails `denied`.\n * It is a plain synchronous function on purpose: it runs while the owner's permit is held, where\n * waiting on a model or a network call would stall other ordinary operations on that Page. It is not an atomic\n * check-and-input transaction, because page script can still run before the native input lands.\n */\nexport interface ElementAdmission {\n readonly admit: (facts: ControlFacts) => boolean;\n}\n\n/**\n * A navigation the browser is still performing. The owner's permit was released when it was\n * dispatched, so while it loads a host may read, `checkpoint`, hold and resume this page, and\n * use any other page. Anything that would change this page fails `busy` until it settles.\n *\n * Leaving its scope unsettled revokes and attempts to close its exact page. Positive closure\n * preserves healthy peers; unconfirmed closure fences the session. Its outcome remains unknown\n * because nothing then knows what the browser did. It is never replayed.\n */\nexport interface NavigationOperation {\n /** What was navigated, read before dispatch. */\n readonly target: Target;\n /**\n * The document reached DOMContentLoaded. It belongs to this one navigation: a successor that\n * reaches the same URL fails it instead. Interrupting a waiter stops nothing in the browser.\n */\n readonly completed: Effect.Effect<NavigationResult, BrowserError>;\n /**\n * Asks the browser to stop loading and waits for this navigation to settle, after which\n * `completed` fails `interrupted`. Success is a known outcome and the session stays usable:\n * the page holds whatever had loaded. It does not undo anything the page already did.\n * An already-completed navigation cannot stop a successor. Concurrent callers share their\n * active attempt. Before dispatch, cancellation or a busy refusal permits a later request\n * once native setup has retired. A dispatched attempt's outcome, including failure or\n * interruption, is retained permanently; an uncertain stop is never replayed.\n */\n readonly stop: Effect.Effect<void, BrowserError>;\n}\n\n/**\n * Host control over one owned browser. This is not a serializable model value: copying a\n * session object cannot copy its capture, page-control or connection authority.\n *\n * Browser operations belong to issued Page and Frame objects. Display selection and owner\n * lifecycle are separate from those exact capabilities.\n */\nexport interface BrowserSession<E = never> {\n /** The original owner's host clock, for same-runtime absolute cue scheduling. */\n readonly monotonicTimeNanos: Effect.Effect<bigint>;\n /** Stable facade: each snapshot/stream captures the current journal when it executes. */\n readonly timeline: Timeline;\n /** Atomic cached Inventory baseline followed by lifecycle evidence on its pinned journal. */\n readonly pages: Stream.Stream<PageEvent, TimelineError>;\n /** The page acquired on the initial connection, independent of later display selection. */\n readonly initialPage: Page;\n /**\n * Authenticate `info` against the live native page and return its canonical issued Page. This\n * takes the registry permit, not the page's own: it never waits for, or fails `busy` on, work\n * already running on that page.\n */\n readonly page: (info: PageInfo, options?: OperationOptions) => Effect.Effect<Page, BrowserError>;\n readonly listPages: (\n options?: OperationOptions,\n ) => Effect.Effect<ReadonlyArray<PageInfo>, BrowserError>;\n /** The implementation which owns this live connection. */\n readonly implementation: string;\n /** Copied host-only state, readable without admission in every lifecycle phase. */\n readonly status: Effect.Effect<SessionStatus>;\n /** Bounded native/policy diagnostics. Typed callback causes remain in bindingDiagnostics. */\n readonly diagnostics: Effect.Effect<BrowserDiagnostics>;\n readonly admission: Effect.Effect<SessionAdmissionStatus>;\n /** Close this scope and require its own ownership-specific cleanup evidence. */\n readonly closeChecked: Effect.Effect<void, BrowserError>;\n /** First fail-session callback cause, preserving the consumer's error type on the host. */\n readonly failure: Effect.Effect<never, E | InitializationError>;\n /** Bounded host-only evidence; consumer causes are never projected into a page reply. */\n readonly bindingDiagnostics: Effect.Effect<Bootstrap.BindingDiagnostics<E>>;\n /**\n * Choose the displayed page, which is also the page `detach` records and `reconnect` resumes on.\n * Nothing else: it never retargets a Page or Frame operation, which keep their own exact\n * authority. Pass a Page this session issued; a closed or stale one is refused unsent.\n */\n readonly selectPage: (\n page: Page,\n options?: OperationOptions,\n ) => Effect.Effect<void, BrowserError>;\n /**\n * Open a page in its own window, sized like the others, without selecting it, and return the\n * Page issued for it. Chromium paints every window, so any page can be pictured and read at\n * speed, not only the one in front. Registry admission fails immediately by default; pass a\n * positive finite admission.queue to wait FIFO within the operation deadline.\n */\n readonly createPage: (options?: OperationOptions) => Effect.Effect<Page, BrowserError>;\n}\n\n/** Helpers that do not supervise callback failures accept any live browser session. */\nexport type AnySession = BrowserSession<unknown>;\n\n/**\n * Check that `page` is a Page or Frame this session issued, by identity, and that its authority\n * is still current: a foreign or copied object fails `UnregisteredSession`, a closed, stale or\n * detached one fails with the same refusal its own operations would report. Adapters such as\n * agent-browser hosts call it once when they bind, so a wrong target fails before any work.\n */\nexport const checkPage = (\n session: AnySession,\n page: Page | Frame,\n): Effect.Effect<void, BrowserError> =>\n resolveTargetControlsForSession(session, page).pipe(Effect.asVoid);\n\n/** Dependencies are captured at acquisition, not erased into an environment-free service Layer. */\nexport interface OpenOptions<E = never, R = never> {\n readonly bootstrap?: Bootstrap.Plan<E, R>;\n}\n\n/**\n * Acquire, supervise and close one browser in a fresh scope. The callback keeps the concrete\n * Chromium or provider session type. Its scoped work is joined before checked browser cleanup,\n * and cleanup failures remain typed even when the callback has already failed or was cancelled.\n * Acquisition is evaluated once; an uncertain result is never retried.\n */\nexport const scoped: {\n <S extends BrowserSession<unknown>, A, E2, R2>(\n f: (session: S) => Effect.Effect<A, E2, R2>,\n ): <E, AE, AR>(\n open: Effect.Effect<S & BrowserSession<E>, AE, AR>,\n ) => Effect.Effect<\n A,\n AE | E | E2 | InitializationError | BrowserError,\n Exclude<AR | R2, Scope.Scope>\n >;\n <S, E, AE, AR, A, E2, R2>(\n open: Effect.Effect<S & BrowserSession<E>, AE, AR>,\n f: (session: S & BrowserSession<E>) => Effect.Effect<A, E2, R2>,\n ): Effect.Effect<\n A,\n AE | E | E2 | InitializationError | BrowserError,\n Exclude<AR | R2, Scope.Scope>\n >;\n} = dual(\n 2,\n <S, E, AE, AR, A, E2, R2>(\n open: Effect.Effect<S & BrowserSession<E>, AE, AR>,\n f: (session: S & BrowserSession<E>) => Effect.Effect<A, E2, R2>,\n ) =>\n Effect.scoped(\n Effect.flatMap(open, (session) =>\n Effect.scoped(\n Effect.raceFirst(\n session.failure,\n Effect.suspend(() => f(session)),\n ),\n ).pipe(Effect.onExit(() => session.closeChecked)),\n ),\n ),\n);\n"],"mappings":";;;;;;;;;;AAkbA,MAAa,aACX,SACA,SAEA,gCAAgC,SAAS,IAAI,CAAC,CAAC,KAAK,OAAO,MAAM;;;;;;;AAanE,MAAa,SAkBT,KACF,IAEE,MACA,MAEA,OAAO,OACL,OAAO,QAAQ,OAAO,YACpB,OAAO,OACL,OAAO,UACL,QAAQ,SACR,OAAO,cAAc,EAAE,OAAO,CAAC,CACjC,CACF,CAAC,CAAC,KAAK,OAAO,aAAa,QAAQ,YAAY,CAAC,CAClD,CACF,CACJ"}
1
+ {"version":3,"file":"Browser.mjs","names":[],"sources":["../src/Browser.ts"],"sourcesContent":["import { type Duration, Effect, type Scope, type Stream } from \"effect\";\nimport { dual } from \"effect/Function\";\n\nimport type * as Bootstrap from \"./Bootstrap.ts\";\nimport type {\n BrowserDiagnostics,\n Checkpoint,\n CheckpointOptions,\n FillFormOptions,\n FillFormRequest,\n FillFormResult,\n FillRequest,\n HoverRequest,\n InputReceipt,\n KeyStroke,\n NavigateRequest,\n NavigationResult,\n ObservationOptions,\n ObservedElement,\n PointerMoveRequest,\n PressRequest,\n ReadTextRequest,\n ScreenshotRequest,\n ScreenshotResult,\n ScrollRequest,\n SelectOptions,\n SessionStatus,\n StartNavigationRequest,\n TextResult,\n TypeRequest,\n WaitForElementRequest,\n WheelRequest,\n ControlFacts,\n ActionResult,\n ClickRequest,\n FrameInfo,\n Observation,\n PageInfo,\n Target,\n Viewport,\n} from \"./BrowserData.ts\";\nimport type { BrowserError, BrowserOperation, Containment, InitializationError } from \"./Errors.ts\";\nimport { resolveTargetControlsForSession } from \"./internal/browser/PageRegistry.ts\";\nimport type { PlanOperations } from \"./Plan.ts\";\nimport type { DescriptorEncoded, ResolveGuard, SettledEvidence } from \"./PlanData.ts\";\nimport type { Timeline } from \"./Timeline.ts\";\nimport type { PageEvent, TimelineError } from \"./TimelineData.ts\";\n\n/**\n * Host-only ordinary admission. Omission or zero fails immediately; positive finite duration\n * queues FIFO. Page closure uses reserved fail-fast admission and a deadline-bounded shared close.\n */\nexport interface AdmissionOptions {\n readonly queue?: Duration.Input;\n}\n\n/** Explicit host options, separate from request data and exact-element admission callbacks. */\nexport interface OperationOptions {\n readonly timeoutMillis?: number;\n readonly admission?: AdmissionOptions;\n}\n\n/** Explicit descriptor guard and ordinary host admission; no selector or geometry fallback. */\nexport interface ResolveOptions extends OperationOptions {\n readonly guard?: typeof ResolveGuard.Encoded;\n}\n\n/** Named native quiet signals within the original positive finite deadline. */\nexport interface SettledRequest {\n readonly quiet: Duration.Input;\n readonly within: Duration.Input;\n}\n\n/** A bounded host-memory snapshot; it grants no priority or native authority. */\nexport interface AdmissionStatus {\n readonly active: BrowserOperation | null;\n readonly waiting: number;\n readonly maximum: number;\n readonly oldestWaitMillis: number | null;\n readonly nativePending: number;\n readonly nativeWaitPending: boolean;\n readonly stopSetupPending: boolean;\n}\n\nexport interface SessionAdmissionStatus {\n readonly waiting: number;\n readonly maximum: number;\n readonly nativePending: number;\n readonly nativeMaximum: number;\n readonly nativeWaits: number;\n readonly stopSetups: number;\n readonly registry: AdmissionStatus;\n}\n\n/** Terminal facts remain readable after this page loses live authority. */\nexport interface PageStatus {\n readonly identity: Target;\n readonly phase: \"open\" | \"paused\" | \"closing\" | \"closed\" | \"stale\";\n readonly containment: Containment;\n readonly admission: AdmissionStatus;\n}\n\nexport interface PageOperations {\n readonly navigate: (\n request: NavigateRequest,\n options?: OperationOptions,\n ) => Effect.Effect<NavigationResult, BrowserError>;\n /** `navigate`, left in flight: the same single dispatch, completed by the caller. */\n readonly startNavigation: (\n request: StartNavigationRequest,\n options?: OperationOptions,\n ) => Effect.Effect<NavigationOperation, BrowserError, Scope.Scope>;\n readonly readText: (\n request: ReadTextRequest,\n options?: OperationOptions,\n ) => Effect.Effect<TextResult, BrowserError>;\n readonly click: (\n request: ClickRequest,\n options?: OperationOptions,\n ) => Effect.Effect<ActionResult, BrowserError>;\n readonly fill: (\n request: FillRequest,\n options?: OperationOptions,\n ) => Effect.Effect<ActionResult, BrowserError>;\n /** Script in the page: instantaneous, and it raises no wheel event. */\n readonly scroll: (\n request: ScrollRequest,\n options?: OperationOptions,\n ) => Effect.Effect<ActionResult, BrowserError>;\n /** One real pointer move, in main-frame viewport pixels. */\n readonly pointerMove: (\n request: PointerMoveRequest,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /** Places the pointer on one exact element where it is, or fails `not-visible` unsent. */\n readonly hover: (\n request: HoverRequest,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /** One real wheel event; the browser decides what under the pointer scrolls. */\n readonly wheel: (\n request: WheelRequest,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /** One real key stroke to whatever has focus, or fails `not-focused` unsent if `into` lacks it. */\n readonly press: (\n request: PressRequest,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /** Text as the real key strokes that produce it, under the same focus rule as `press`. */\n readonly type: (\n request: TypeRequest,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n readonly screenshot: (\n request: ScreenshotRequest,\n options?: OperationOptions,\n ) => Effect.Effect<ScreenshotResult, BrowserError>;\n /**\n * The one observation whose nodes later actions may name. `scope: \"viewport\"` keeps only text\n * and controls that are on screen and reachable, plus bounded choices of visible native\n * selects; the default reads the whole document. It carries no destination, form or field value.\n */\n readonly observe: (\n options?: ObservationOptions,\n operationOptions?: OperationOptions,\n ) => Effect.Effect<Observation, BrowserError>;\n /**\n * Passive evidence for a recorder, with a picture when asked. It issues no references and\n * leaves the observation above exactly as it was. Host-only: it carries control facts.\n */\n readonly checkpoint: (\n options?: CheckpointOptions,\n operationOptions?: OperationOptions,\n ) => Effect.Effect<Checkpoint, BrowserError>;\n /** Host-only facts about one observed control, read from that exact node just now. */\n readonly controlFacts: (\n reference: ObservedElement,\n options?: OperationOptions,\n ) => Effect.Effect<ControlFacts, BrowserError>;\n /**\n * After a page hold, nothing observed on that page may be acted on unchecked. This checks one\n * reference: still attached, and still the control that was inspected. It never searches by\n * selector or label for a substitute, and it sends nothing.\n */\n readonly revalidateElement: (\n reference: ObservedElement,\n options?: OperationOptions,\n ) => Effect.Effect<ObservedElement, BrowserError>;\n readonly clickElement: (\n reference: ObservedElement,\n admission?: ElementAdmission,\n options?: OperationOptions,\n ) => Effect.Effect<ActionResult, BrowserError>;\n readonly fillElement: (\n reference: ObservedElement,\n value: string,\n admission?: ElementAdmission,\n options?: OperationOptions,\n ) => Effect.Effect<ActionResult, BrowserError>;\n /** One selection using this exact select and its issued option element IDs, with fresh checks. */\n readonly selectOption: (\n reference: ObservedElement,\n options: SelectOptions,\n admission?: ElementAdmission,\n operationOptions?: OperationOptions,\n ) => Effect.Effect<ActionResult, BrowserError>;\n readonly hoverElement: (\n reference: ObservedElement,\n admission?: ElementAdmission,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /** A key stroke sent only if the exact node an observation named already has focus. */\n readonly pressElement: (\n reference: ObservedElement,\n stroke: KeyStroke,\n admission?: ElementAdmission,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /** Real typing with `fillElement`'s exactness: that node must already have focus. */\n readonly typeElement: (\n reference: ObservedElement,\n text: string,\n admission?: ElementAdmission,\n options?: OperationOptions,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /**\n * Set several controls of one observation in order, then optionally click one submit control.\n * Every step acts on the exact observed node after the same fresh checks as `fillElement`,\n * except that a control may have become enabled since it was observed, and each is admitted\n * and charged as its own action. The observation stays usable for this form's own steps only;\n * anything else that changes the page still retires it, and the form retires it when it ends.\n * The first refused or uncertain step ends the form. Submit is sent only after every field\n * succeeded and, unless `verify` is false, still holds what its step left there. Fields set\n * before a stop stay set. It fails only when its first step does; otherwise `stopped` says\n * where it ended and why.\n */\n readonly fillForm: (\n request: FillFormRequest,\n admission?: ElementAdmission,\n options?: FillFormOptions,\n operationOptions?: OperationOptions,\n ) => Effect.Effect<FillFormResult, BrowserError>;\n /**\n * Wait on the original observed node within the host deadline. Hidden includes its detachment;\n * document replacement is stale. Success observes a condition, without authorizing later input.\n */\n readonly waitForElement: (\n request: WaitForElementRequest,\n options?: OperationOptions,\n ) => Effect.Effect<void, BrowserError>;\n readonly waitFor: (\n request: {\n readonly selector: string;\n readonly state: \"visible\" | \"hidden\" | \"attached\" | \"detached\";\n },\n options?: OperationOptions,\n ) => Effect.Effect<void, BrowserError>;\n /** Navigation observation is registered before the single click dispatch. */\n readonly clickAndWait: (\n request: ClickRequest,\n options?: OperationOptions,\n ) => Effect.Effect<ActionResult, BrowserError>;\n /**\n * Readiness of the current document only. Dependent operations wait for it themselves;\n * this reports it without charging an action, so a caller can decide what to do about a\n * document that predates the registrations.\n */\n readonly ready: (\n options?: OperationOptions,\n ) => Effect.Effect<Bootstrap.ReadinessOutcome, InitializationError>;\n}\n\n/**\n * Issued live authority for one native frame; navigation keeps the frame and retires its references.\n *\n * Pass the issued object itself. Capture, page control, provider transfers, agent tools and\n * `checkPage` authenticate it by identity, so a spread copy, wrapper or Proxy is refused\n * `UnregisteredSession` even though its copied methods still run. Observe work through the\n * timeline and run receipts rather than by wrapping methods.\n */\nexport interface Frame extends PageOperations, PlanOperations {\n readonly identity: Target;\n readonly status: Effect.Effect<PageStatus>;\n /**\n * The original owner's host monotonic clock, the same one `session.monotonicTimeNanos` reads,\n * so a host bound to this Page or Frame can compute a plan's `startAt` without the session.\n */\n readonly monotonicTimeNanos: Effect.Effect<bigint>;\n /** Resolve full descriptor intent against the complete current native candidate inventory. */\n readonly resolve: (\n descriptor: DescriptorEncoded,\n options?: ResolveOptions,\n ) => Effect.Effect<ObservedElement, BrowserError>;\n /**\n * Quiet DOM mutation, scroll, root geometry and viewport signals in one pinned document.\n * This does not establish network, animation or business completion.\n */\n readonly settled: (\n request: SettledRequest,\n options?: OperationOptions,\n ) => Effect.Effect<SettledEvidence, BrowserError>;\n}\n\n/** Issued live authority for one native page on its original connection; pass it as issued, see `Frame`. */\nexport interface Page extends Frame {\n /** Evidence for this issued Page's original domain; navigation does not change its journal. */\n readonly timeline: Timeline;\n /**\n * Read this page's address and title. Like `listFrames`, it is a read of the page and shares\n * its permit; pass `admission.queue` to wait behind work already running there.\n */\n readonly describe: (options?: OperationOptions) => Effect.Effect<PageInfo, BrowserError>;\n readonly listFrames: (\n options?: OperationOptions,\n ) => Effect.Effect<ReadonlyArray<FrameInfo>, BrowserError>;\n /** Issue a child Frame. Like `session.page`, issuing takes the registry permit, not this page's. */\n readonly frame: (\n info: FrameInfo,\n options?: OperationOptions,\n ) => Effect.Effect<Frame, BrowserError>;\n readonly resizeViewport: (\n viewport: Viewport,\n options?: OperationOptions,\n ) => Effect.Effect<void, BrowserError>;\n /** Reserved fail-fast admission; timeoutMillis bounds joining a close, not admission.queue. */\n readonly close: (options?: OperationOptions) => Effect.Effect<void, BrowserError>;\n}\n\n/**\n * A host's decision about one control, made on facts read from the exact node immediately\n * before input. Returning anything but `true`, or throwing, sends nothing and fails `denied`.\n * It is a plain synchronous function on purpose: it runs while the owner's permit is held, where\n * waiting on a model or a network call would stall other ordinary operations on that Page. It is not an atomic\n * check-and-input transaction, because page script can still run before the native input lands.\n */\nexport interface ElementAdmission {\n readonly admit: (facts: ControlFacts) => boolean;\n}\n\n/**\n * A navigation the browser is still performing. The owner's permit was released when it was\n * dispatched, so while it loads a host may read, `checkpoint`, hold and resume this page, and\n * use any other page. Anything that would change this page fails `busy` until it settles.\n *\n * Leaving its scope unsettled revokes and attempts to close its exact page. Positive closure\n * preserves healthy peers; unconfirmed closure fences the session. Its outcome remains unknown\n * because nothing then knows what the browser did. It is never replayed.\n */\nexport interface NavigationOperation {\n /** What was navigated, read before dispatch. */\n readonly target: Target;\n /**\n * What was asked for, as checked before dispatch. `Plan.recordedNavigation` records this\n * request, not the address `completed` reports after any redirect.\n */\n readonly request: StartNavigationRequest;\n /**\n * The document reached DOMContentLoaded. It belongs to this one navigation: a successor that\n * reaches the same URL fails it instead. Interrupting a waiter stops nothing in the browser.\n */\n readonly completed: Effect.Effect<NavigationResult, BrowserError>;\n /**\n * Asks the browser to stop loading and waits for this navigation to settle, after which\n * `completed` fails `interrupted`. Success is a known outcome and the session stays usable:\n * the page holds whatever had loaded. It does not undo anything the page already did.\n * An already-completed navigation cannot stop a successor. Concurrent callers share their\n * active attempt. Before dispatch, cancellation or a busy refusal permits a later request\n * once native setup has retired. A dispatched attempt's outcome, including failure or\n * interruption, is retained permanently; an uncertain stop is never replayed.\n */\n readonly stop: Effect.Effect<void, BrowserError>;\n}\n\n/**\n * Host control over one owned browser. This is not a serializable model value: copying a\n * session object cannot copy its capture, page-control or connection authority.\n *\n * Browser operations belong to issued Page and Frame objects. Display selection and owner\n * lifecycle are separate from those exact capabilities.\n */\nexport interface BrowserSession<E = never> {\n /** The original owner's host clock, for same-runtime absolute cue scheduling. */\n readonly monotonicTimeNanos: Effect.Effect<bigint>;\n /** Stable facade: each snapshot/stream captures the current journal when it executes. */\n readonly timeline: Timeline;\n /** Atomic cached Inventory baseline followed by lifecycle evidence on its pinned journal. */\n readonly pages: Stream.Stream<PageEvent, TimelineError>;\n /** The page acquired on the initial connection, independent of later display selection. */\n readonly initialPage: Page;\n /**\n * Authenticate `info` against the live native page and return its canonical issued Page. This\n * takes the registry permit, not the page's own: it never waits for, or fails `busy` on, work\n * already running on that page.\n */\n readonly page: (info: PageInfo, options?: OperationOptions) => Effect.Effect<Page, BrowserError>;\n readonly listPages: (\n options?: OperationOptions,\n ) => Effect.Effect<ReadonlyArray<PageInfo>, BrowserError>;\n /** The implementation which owns this live connection. */\n readonly implementation: string;\n /** Copied host-only state, readable without admission in every lifecycle phase. */\n readonly status: Effect.Effect<SessionStatus>;\n /** Bounded native/policy diagnostics. Typed callback causes remain in bindingDiagnostics. */\n readonly diagnostics: Effect.Effect<BrowserDiagnostics>;\n readonly admission: Effect.Effect<SessionAdmissionStatus>;\n /** Close this scope and require its own ownership-specific cleanup evidence. */\n readonly closeChecked: Effect.Effect<void, BrowserError>;\n /** First fail-session callback cause, preserving the consumer's error type on the host. */\n readonly failure: Effect.Effect<never, E | InitializationError>;\n /** Bounded host-only evidence; consumer causes are never projected into a page reply. */\n readonly bindingDiagnostics: Effect.Effect<Bootstrap.BindingDiagnostics<E>>;\n /**\n * Choose the displayed page, which is also the page `detach` records and `reconnect` resumes on.\n * Nothing else: it never retargets a Page or Frame operation, which keep their own exact\n * authority. Pass a Page this session issued; a closed or stale one is refused unsent.\n */\n readonly selectPage: (\n page: Page,\n options?: OperationOptions,\n ) => Effect.Effect<void, BrowserError>;\n /**\n * Open a page in its own window, sized like the others, without selecting it, and return the\n * Page issued for it. Chromium paints every window, so any page can be pictured and read at\n * speed, not only the one in front. Registry admission fails immediately by default; pass a\n * positive finite admission.queue to wait FIFO within the operation deadline.\n */\n readonly createPage: (options?: OperationOptions) => Effect.Effect<Page, BrowserError>;\n}\n\n/** Helpers that do not supervise callback failures accept any live browser session. */\nexport type AnySession = BrowserSession<unknown>;\n\n/**\n * Check that `page` is a Page or Frame this session issued, by identity, and that its authority\n * is still current: a foreign or copied object fails `UnregisteredSession`, a closed, stale or\n * detached one fails with the same refusal its own operations would report. Adapters such as\n * agent-browser hosts call it once when they bind, so a wrong target fails before any work.\n */\nexport const checkPage = (\n session: AnySession,\n page: Page | Frame,\n): Effect.Effect<void, BrowserError> =>\n resolveTargetControlsForSession(session, page).pipe(Effect.asVoid);\n\n/** Dependencies are captured at acquisition, not erased into an environment-free service Layer. */\nexport interface OpenOptions<E = never, R = never> {\n readonly bootstrap?: Bootstrap.Plan<E, R>;\n}\n\n/**\n * Acquire, supervise and close one browser in a fresh scope. The callback keeps the concrete\n * Chromium or provider session type. Its scoped work is joined before checked browser cleanup,\n * and cleanup failures remain typed even when the callback has already failed or was cancelled.\n * Acquisition is evaluated once; an uncertain result is never retried.\n */\nexport const scoped: {\n <S extends BrowserSession<unknown>, A, E2, R2>(\n f: (session: S) => Effect.Effect<A, E2, R2>,\n ): <E, AE, AR>(\n open: Effect.Effect<S & BrowserSession<E>, AE, AR>,\n ) => Effect.Effect<\n A,\n AE | E | E2 | InitializationError | BrowserError,\n Exclude<AR | R2, Scope.Scope>\n >;\n <S, E, AE, AR, A, E2, R2>(\n open: Effect.Effect<S & BrowserSession<E>, AE, AR>,\n f: (session: S & BrowserSession<E>) => Effect.Effect<A, E2, R2>,\n ): Effect.Effect<\n A,\n AE | E | E2 | InitializationError | BrowserError,\n Exclude<AR | R2, Scope.Scope>\n >;\n} = dual(\n 2,\n <S, E, AE, AR, A, E2, R2>(\n open: Effect.Effect<S & BrowserSession<E>, AE, AR>,\n f: (session: S & BrowserSession<E>) => Effect.Effect<A, E2, R2>,\n ) =>\n Effect.scoped(\n Effect.flatMap(open, (session) =>\n Effect.scoped(\n Effect.raceFirst(\n session.failure,\n Effect.suspend(() => f(session)),\n ),\n ).pipe(Effect.onExit(() => session.closeChecked)),\n ),\n ),\n);\n"],"mappings":";;;;;;;;;;AAubA,MAAa,aACX,SACA,SAEA,gCAAgC,SAAS,IAAI,CAAC,CAAC,KAAK,OAAO,MAAM;;;;;;;AAanE,MAAa,SAkBT,KACF,IAEE,MACA,MAEA,OAAO,OACL,OAAO,QAAQ,OAAO,YACpB,OAAO,OACL,OAAO,UACL,QAAQ,SACR,OAAO,cAAc,EAAE,OAAO,CAAC,CACjC,CACF,CAAC,CAAC,KAAK,OAAO,aAAa,QAAQ,YAAY,CAAC,CAClD,CACF,CACJ"}
@@ -1,6 +1,6 @@
1
1
  import { BrowserError, InitializationError } from "./Errors.mjs";
2
2
  import { S as InlineFile, T as Inventory, it as Viewport, l as ClickRequest, o as BrowserPolicy, r as AutomationOptions, t as ActionResult } from "./BrowserData-CPIIIHol.mjs";
3
- import { c as OpenOptions, i as BrowserSession, l as OperationOptions, u as Page } from "./Browser-n-bKgBNN.mjs";
3
+ import { c as OpenOptions, i as BrowserSession, l as OperationOptions, u as Page } from "./Browser-BF9_sORP.mjs";
4
4
  import { Crypto, Effect, Option, Redacted, Schema, Scope } from "effect";
5
5
  //#region src/internal/browser/ConnectionCleanup.d.ts
6
6
  /** Evidence about this connection, independent of the lifetime of its browser process. */
@@ -145,4 +145,4 @@ declare const make: (options: RuntimeOptions) => Effect.Effect<Runtime, BrowserE
145
145
  //#endregion
146
146
  export { reported as S, CleanupStep as _, DownloadEvent as a, cleanupStep as b, Lifetime as c, Runtime as d, RuntimeOptions as f, playwright as g, make as h, Connection as i, PlaywrightOptions as l, TransferOperations as m, Acquisition as n, FileRequest as o, Source as p, BrowserBinding as r, Integration as s, AcquireOptions as t, ResolvedFileSelection as u, ConnectionCleanup as v, noConnection as x, ConnectionState as y };
147
147
  export {};
148
- //# sourceMappingURL=BrowserRuntime-CFe9uUTv.d.mts.map
148
+ //# sourceMappingURL=BrowserRuntime-B6nT1G4X.d.mts.map
@@ -2,8 +2,8 @@ import { BrowserError, InitializationError, Reasons } from "./Errors.mjs";
2
2
  import { ActionResult, AutomationOptions, BrowserPolicy, ClickRequest, ControlFacts, FrameInfo, Identifier, InlineFiles, ObservedControl, PageExecutionState, PageInfo, PageSuspension, SafeFilename, Selector, Viewport, ViewportEvidence } from "./BrowserData.mjs";
3
3
  import { m as snapshot, o as Plan, p as invoke, u as empty } from "./Bootstrap-CeQOWBy6.mjs";
4
4
  import { c as resolvePageControlsForSession } from "./PageRegistry-B3q9DS3g.mjs";
5
- import { C as sanitize, D as capture, E as autonomous, O as span, S as safeDecode, _ as closeWithin, a as acquireSession, b as providerReason, d as move, f as pointer, g as NativeFailure, h as ownerPacing, i as makeSession, m as isPerformed, n as checkedOperationOptions, p as scroll, r as decoded, t as checked, u as keys, v as exitPromise, w as timeout, x as publicError, y as failure } from "./PublicSession-Bs61Eal8.mjs";
6
- import { n as pngGeometry } from "./Images-CaaPNeed.mjs";
5
+ import { C as sanitize, D as capture, E as autonomous, O as span, S as safeDecode, _ as closeWithin, a as acquireSession, b as providerReason, d as move, f as pointer, g as NativeFailure, h as ownerPacing, i as makeSession, m as isPerformed, n as checkedOperationOptions, p as scroll, r as decoded, t as checked, u as keys, v as exitPromise, w as timeout, x as publicError, y as failure } from "./PublicSession-DCSMMqOj.mjs";
6
+ import { n as pngGeometry } from "./Images-yCG9BmRU.mjs";
7
7
  import { TimelineDefaults } from "./TimelineData.mjs";
8
8
  import { Cause, Crypto, Deferred, Effect, Exit, FiberSet, Redacted, Result, Schema, Scope, Tracer } from "effect";
9
9
  //#region src/internal/browser/Binding.ts
@@ -706,42 +706,73 @@ const keyDescription = (key) => {
706
706
  /** Performed text holds Shift for uppercase and shifted punctuation, as a person would. */
707
707
  const heldModifiers = (key, modifiers, typed) => typed && /^[A-Z~!@#$%^&*()_+{}|:"<>?]$/u.test(key) && !modifiers.includes("Shift") ? [...modifiers, "Shift"] : modifiers;
708
708
  const nanos = (milliseconds) => BigInt(Math.round(milliseconds * 1e6));
709
+ /** The commands a stroke submits: inserted text, or its key and held modifiers down and up. */
710
+ const commandsOf = (stroke, modifiers, typed) => keyDescription(stroke.key) === void 0 ? 1 : 2 * (1 + heldModifiers(stroke.key, modifiers, typed).length);
709
711
  /**
710
712
  * The measured pace of one performed key run. Strokes start at their absolute schedule offsets,
711
- * and each key is held from its key-down. Before a stroke's first command, the rest of the
712
- * schedule must still fit the original deadline at the round trips the browser has actually
713
- * taken, so a stroke that could not finish is refused whole, never cut between a key and its
714
- * release.
713
+ * and each key is held from its key-down's submission. Before a stroke's first command, the rest
714
+ * of the schedule must still fit the original deadline at the round trips the browser has
715
+ * actually taken, so a stroke that could not finish is refused whole, never cut between a key and
716
+ * its release.
717
+ *
718
+ * A stroke's commands share their round trip, but a renderer handles them one at a time. So each
719
+ * stroke is measured from its first submission (its span) and from its last (its drain) until its
720
+ * last reply, and charged as strokes of its own command count were. More commands never finish
721
+ * sooner: a larger stroke's measures bound a smaller one's, and a smaller one's are scaled up by
722
+ * command count, which overcharges latency only until a stroke of this size has drained.
715
723
  */
716
724
  const makePace = (pacing, strokes, started, focus, modifiers, typed) => {
717
- const measured = {
718
- focus: {
719
- nanos: 0n,
720
- count: 0
721
- },
722
- command: {
723
- nanos: 0n,
724
- count: 0
725
+ const focused = {
726
+ nanos: 0n,
727
+ count: 0
728
+ };
729
+ const drained = /* @__PURE__ */ new Map();
730
+ const mean = (total, count) => count === 0 ? 0 : Number(total / BigInt(count)) / 1e6;
731
+ const expected = (commands, check) => {
732
+ let bound;
733
+ let below;
734
+ for (const size of drained.keys()) {
735
+ if (size >= commands && (bound === void 0 || size < bound)) bound = size;
736
+ if (size < commands && (below === void 0 || size > below)) below = size;
725
737
  }
738
+ const size = bound ?? below;
739
+ const measured = size === void 0 ? void 0 : drained.get(size);
740
+ if (size === void 0 || measured === void 0) return {
741
+ span: 0,
742
+ drain: check * Math.max(1, commands / 2)
743
+ };
744
+ const scale = size < commands ? commands / size : 1;
745
+ return {
746
+ span: mean(measured.span, measured.count) * scale,
747
+ drain: mean(measured.drain, measured.count) * scale
748
+ };
726
749
  };
727
- const mean = ({ nanos: total, count }) => count === 0 ? 0 : Number(total / BigInt(count)) / 1e6;
728
750
  return {
729
- observe: (kind, elapsed) => {
730
- measured[kind].nanos += elapsed;
731
- measured[kind].count++;
751
+ observeFocus: (elapsed) => {
752
+ focused.nanos += elapsed;
753
+ focused.count++;
754
+ },
755
+ observeDrain: (commands, span, drain) => {
756
+ const measured = drained.get(commands) ?? {
757
+ span: 0n,
758
+ drain: 0n,
759
+ count: 0
760
+ };
761
+ measured.span += span;
762
+ measured.drain += drain;
763
+ measured.count++;
764
+ drained.set(commands, measured);
732
765
  },
733
766
  /** Called after stroke `from`'s focus check, before its first command. */
734
767
  require: (from) => {
735
- const check = focus ? mean(measured.focus) : 0;
736
- const command = measured.command.count > 0 ? mean(measured.command) : check;
768
+ const check = focus ? mean(focused.nanos, focused.count) : 0;
737
769
  let at = Number(pacing.now() - started) / 1e6;
738
770
  for (let index = from; index < strokes.length; index++) {
739
771
  const stroke = strokes[index];
740
772
  if (stroke === void 0) break;
741
- const keyed = keyDescription(stroke.key) !== void 0;
742
- const held = keyed ? heldModifiers(stroke.key, modifiers, typed).length : 0;
743
- const down = Math.max(at, stroke.offsetMillis) + (index === from ? 0 : check) + held * command;
744
- at = Math.max(down + command, down + stroke.holdMillis) + (keyed ? (1 + held) * command : 0);
773
+ const down = Math.max(at, stroke.offsetMillis) + (index === from ? 0 : check);
774
+ const { span, drain } = expected(commandsOf(stroke, modifiers, typed), check);
775
+ at = keyDescription(stroke.key) === void 0 ? down + Math.max(drain, stroke.holdMillis) : down + Math.max(stroke.holdMillis + drain, span);
745
776
  }
746
777
  pacing.requireBy(started + nanos(at));
747
778
  }
@@ -895,7 +926,11 @@ const makeKeyboard = (targets, elements, receipt) => {
895
926
  * One paired native stroke remains unresolved until its key and modifiers are all released.
896
927
  * Playwright's own keyboard builds each event, so a performed stroke carries exactly what a
897
928
  * plain `press` sends, including the editing commands Chromium on macOS needs for Backspace,
898
- * Delete, the arrows, Home and End.
929
+ * Delete, the arrows, Home and End. Like plain typing's windows, a stroke submits its commands
930
+ * in order without waiting for each reply, so a key is held for its planned time rather than for
931
+ * its key-down's round trip, and a held modifier adds no round trip of its own. A stroke has at
932
+ * most ten replies outstanding, its key and up to four modifiers each down and up, and drains
933
+ * them all before it resolves.
899
934
  */
900
935
  const pacedStroke = async (keyboard, stroke, ticket, check, pace, index, element, modifiers = [], typed = true) => {
901
936
  const pacing = ownerPacing(ticket);
@@ -904,38 +939,56 @@ const makeKeyboard = (targets, elements, receipt) => {
904
939
  if (element !== void 0) {
905
940
  const before = pacing.now();
906
941
  await requireFocus(element);
907
- pace.observe("focus", pacing.now() - before);
942
+ pace.observeFocus(pacing.now() - before);
908
943
  }
909
944
  check();
910
945
  pace.require(index);
911
- const submit = async (command) => {
946
+ const pending = [];
947
+ let failed = false;
948
+ let firstFailure;
949
+ const first = pacing.now();
950
+ let submitted = first;
951
+ const reject = (error) => {
952
+ if (!failed) {
953
+ failed = true;
954
+ firstFailure = error;
955
+ }
956
+ };
957
+ const submit = (command) => {
958
+ if (failed) throw firstFailure;
912
959
  check();
913
960
  ticket.dispatch();
914
- const before = pacing.now();
915
- await command();
916
- pace.observe("command", pacing.now() - before);
961
+ submitted = pacing.now();
962
+ pending.push(command().then(() => {}, reject));
917
963
  };
918
- if (keyDescription(stroke.key) === void 0) {
919
- const down = pacing.now();
920
- await submit(() => keyboard.insertText(stroke.key));
964
+ const drain = async () => {
965
+ await Promise.all(pending);
966
+ if (failed) throw firstFailure;
967
+ const replied = pacing.now();
968
+ pace.observeDrain(pending.length, replied - first, replied - submitted);
921
969
  ticket.acknowledge?.({
922
970
  subphase: "key-burst",
923
971
  logicalComplete: false
924
972
  });
925
- await pacing.pauseUntil(down + nanos(stroke.holdMillis));
973
+ };
974
+ if (keyDescription(stroke.key) === void 0) {
975
+ submit(() => keyboard.insertText(stroke.key));
976
+ await drain();
977
+ await pacing.pauseUntil(first + nanos(stroke.holdMillis));
926
978
  return;
927
979
  }
928
980
  const held = heldModifiers(stroke.key, modifiers, typed);
929
- for (const modifier of held) await submit(() => keyboard.down(modifier));
930
- const down = pacing.now();
931
- await submit(() => keyboard.down(stroke.key));
932
- await pacing.pauseUntil(down + nanos(stroke.holdMillis));
933
- await submit(() => keyboard.up(stroke.key));
934
- for (const modifier of held.toReversed()) await submit(() => keyboard.up(modifier));
935
- ticket.acknowledge?.({
936
- subphase: "key-burst",
937
- logicalComplete: false
938
- });
981
+ try {
982
+ for (const modifier of held) submit(() => keyboard.down(modifier));
983
+ const down = pacing.now();
984
+ submit(() => keyboard.down(stroke.key));
985
+ await pacing.pauseUntil(down + nanos(stroke.holdMillis));
986
+ submit(() => keyboard.up(stroke.key));
987
+ for (const modifier of held.toReversed()) submit(() => keyboard.up(modifier));
988
+ } catch (error) {
989
+ reject(error);
990
+ }
991
+ await drain();
939
992
  };
940
993
  const pacedKeys = async (keyboard, schedule, ticket, check, element) => {
941
994
  const pacing = ownerPacing(ticket);
@@ -965,7 +1018,7 @@ const makeKeyboard = (targets, elements, receipt) => {
965
1018
  await requireFocus(element);
966
1019
  const roundTrip = pacing.now() - before;
967
1020
  const pace = makePace(pacing, schedule.strokes, pacing.now() + 2n * roundTrip, true, [], true);
968
- pace.observe("focus", roundTrip);
1021
+ pace.observeFocus(roundTrip);
969
1022
  pace.require(0);
970
1023
  check();
971
1024
  ticket.dispatch();
@@ -3398,11 +3451,20 @@ const makePointer = (targets, elements) => {
3398
3451
  /**
3399
3452
  * Plans and paces the glide to one exact node. A press may first scroll its node into view; a
3400
3453
  * hover never scrolls (`scrollIntoView: false`): it aims inside the node's visible part and
3401
- * refuses a node with none `NotVisible`.
3454
+ * refuses a node with none `NotVisible`. Each read of the node's box is one protocol round trip
3455
+ * on the main frame, and the fastest of them is returned for the caller to charge its input by.
3402
3456
  */
3403
3457
  const preparePress = async (page, element, ticket, check, options = {}) => {
3404
3458
  const pacing = ownerPacing(ticket);
3405
3459
  const { performance } = ticket;
3460
+ let roundTripNanos;
3461
+ const boxOf = async () => {
3462
+ const started = pacing.now();
3463
+ const measured = await element.boundingBox();
3464
+ const elapsed = pacing.now() - started;
3465
+ if (roundTripNanos === void 0 || elapsed < roundTripNanos) roundTripNanos = elapsed;
3466
+ return measured;
3467
+ };
3406
3468
  check();
3407
3469
  const frame = await element.ownerFrame();
3408
3470
  check();
@@ -3471,7 +3533,7 @@ const makePointer = (targets, elements) => {
3471
3533
  height: innerHeight
3472
3534
  })));
3473
3535
  check();
3474
- let box = await element.boundingBox();
3536
+ let box = await boxOf();
3475
3537
  check();
3476
3538
  if (box === null) throw failure(Reasons.NotVisible.make({}), "undispatched");
3477
3539
  const outside = layout.clipped || box.x < 0 || box.y < 0 || box.x + box.width > viewport.width || box.y + box.height > viewport.height;
@@ -3501,7 +3563,7 @@ const makePointer = (targets, elements) => {
3501
3563
  completedMonotonicNanos: pacing.now()
3502
3564
  });
3503
3565
  check();
3504
- const scrolled = await element.boundingBox();
3566
+ const scrolled = await boxOf();
3505
3567
  check();
3506
3568
  if (scrolled === null) throw failure(Reasons.NotVisible.make({}), "undispatched");
3507
3569
  box = scrolled;
@@ -3541,7 +3603,7 @@ const makePointer = (targets, elements) => {
3541
3603
  });
3542
3604
  if (schedule.durationMillis > 0) await pacing.pauseUntil(started + BigInt(Math.round(schedule.durationMillis * 1e6)));
3543
3605
  check();
3544
- const beforeInput = await element.boundingBox();
3606
+ const beforeInput = await boxOf();
3545
3607
  check();
3546
3608
  if (beforeInput === null || Math.abs(beforeInput.x - box.x) > .01 || Math.abs(beforeInput.y - box.y) > .01 || Math.abs(beforeInput.width - box.width) > .01 || Math.abs(beforeInput.height - box.height) > .01) throw failure(Reasons.Stale.make({}), "undispatched");
3547
3609
  await reachablePoint(page, element, schedule.aim, check);
@@ -3552,7 +3614,8 @@ const makePointer = (targets, elements) => {
3552
3614
  position: schedule.aim,
3553
3615
  relativePosition,
3554
3616
  qualification: "checked-exact-node-sample"
3555
- }
3617
+ },
3618
+ roundTripNanos: roundTripNanos ?? 0n
3556
3619
  };
3557
3620
  };
3558
3621
  const hover = (target, ticket, policy, browserTarget) => sanitize(async () => {
@@ -3949,6 +4012,20 @@ const settleInPage = (_node, millis) => new Promise((resolve) => {
3949
4012
  finish();
3950
4013
  }, millis);
3951
4014
  });
4015
+ /**
4016
+ * The round trips a performed click is charged before dispatch. Over relays adding 18 to 70 ms
4017
+ * each way, Playwright 1.63's positioned click took 17 to 23 round trips in all, 14 to 19 of them
4018
+ * checking the node before its first input event. A deadline inside those checks closes the Page
4019
+ * over input never sent, while a refusal leaves it open, so the charge covers the most checking
4020
+ * measured, at the cost of refusing a click that had a few round trips to spare.
4021
+ */
4022
+ const performedClickRoundTrips = 20;
4023
+ /**
4024
+ * What a performed click is charged besides its round trips. Playwright's stability check waits
4025
+ * for the node across animation frames, which no round trip measures: without a relay, a whole
4026
+ * click took 57 ms.
4027
+ */
4028
+ const performedClickFloorMillis = 100;
3952
4029
  /** A step that already dispatched reads back within a bounded wait, and never fails for it. */
3953
4030
  const bounded = async (work, millis, fallback) => {
3954
4031
  let timer;
@@ -4129,11 +4206,13 @@ const makeActions = (context, targets, observation, isTimeoutError) => {
4129
4206
  /** What is left of a step's deadline for reading back, keeping a margin for the owner. */
4130
4207
  const readBack = (ticket, wanted) => Math.min(wanted, Math.max(0, ticket.remainingMillis() - 250));
4131
4208
  const clickElement = (page, element, ticket, capture, check = () => ticket.check(), readmit) => {
4132
- if (isPerformed(ticket)) return pointer.preparePress(page, element, ticket, check).then(async (planned) => {
4209
+ if (isPerformed(ticket)) return pointer.preparePress(page, element, ticket, check).then(async (prepared) => {
4210
+ const { roundTripNanos, ...planned } = prepared;
4133
4211
  await readmit?.();
4134
4212
  check();
4135
4213
  const dispatch = async () => {
4136
4214
  check();
4215
+ ownerPacing(ticket).requireDuration(performedClickFloorMillis + performedClickRoundTrips * Number(roundTripNanos) / 1e6);
4137
4216
  ticket.dispatch();
4138
4217
  pointer.invalidate(page, planned.intended.position);
4139
4218
  await element.click({
@@ -6738,4 +6817,4 @@ const make = Effect.fnUntraced(function* (options) {
6738
6817
  //#endregion
6739
6818
  export { stableIdentityOf as a, issueBinding as c, identityOf as i, make as n, MaximumGroupNodes as o, playwright as r, fromNativeAttempt as s, DownloadEvent as t };
6740
6819
 
6741
- //# sourceMappingURL=BrowserRuntime-HJ1yYlFS.mjs.map
6820
+ //# sourceMappingURL=BrowserRuntime-C1r2aSRK.mjs.map