mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
Enable eleven oxlint rules that simplify code without changing behavior, and fix
every existing violation. Each candidate was gated on measured cost rather than
assumption, so rules that regressed runtime performance or type checking were
dropped instead of suppressed.
typescript/no-redundant-type-constituents is the largest addition: 113 sites, no
autofix. Dead constituents are deleted. Where the redundant literal existed to
document intent (`string | 'all'`), it is preserved as `(string & {})`, which
keeps the autocomplete hint the original code was reaching for instead of
flattening it away. The rule also caught a broken import —
remote-shared-control-retirement-probe.ts pulled RuntimeStatus from
src/shared/types, which does not export it, so the type silently degraded to
`any`; no tsconfig covers that file, so tsc never saw it.
oxlint stays at 1.77.0 rather than 1.78.0 because .npmrc sets
minimum-release-age=4320 and 1.78.0 is younger than that window.
Rules evaluated and rejected, with what disqualified each:
- prefer-string-raw: String.raw is a runtime call, not a literal (184x slower)
- prefer-string-replace-all: 26% slower
- text-encoding-identifier-case: ~5% slower, reproducible
- prefer-spread: [...str] is 110% slower than split('') and differs on surrogates
- no-implicit-coercion: `!!x` narrows types and `Boolean(x)` does not (22 tsc errors)
- prefer-arrow-callback: arrows are not constructible, breaking `new` on mocks
- object-shorthand: rewrites source text asserted by a tracked reliability gate
- switch-case-braces: pushes ten files past max-lines, which cannot be suppressed
- no-useless-switch-case: drops `case undefined:` that switch-exhaustiveness-check needs
- arrow-body-style: 115 violations have no fix, and it breaks max-lines
- newline-after-import: false-positives on the leading-semicolon ASI idiom
electron-vite-output-contract asserted on the literal
Object.prototype.hasOwnProperty.call text; retarget it to Object.hasOwn, which
rejects inherited keys identically.
204 lines
6.4 KiB
TypeScript
204 lines
6.4 KiB
TypeScript
import type { Page } from '@stablyai/playwright-test'
|
|
|
|
export type CodexEchoLatencySample = {
|
|
index: number
|
|
char: string
|
|
/** keydown -> xterm finished parsing the echoed glyph (real echo latency). */
|
|
keyToParseMs: number
|
|
/** keydown -> xterm renderer painted the row carrying that glyph. */
|
|
keyToRenderMs: number | null
|
|
}
|
|
|
|
export type CodexEchoProbeReport = {
|
|
samples: CodexEchoLatencySample[]
|
|
keysObserved: number
|
|
parseEvents: number
|
|
renderEvents: number
|
|
cols: number
|
|
rows: number
|
|
}
|
|
|
|
declare global {
|
|
// oxlint-disable-next-line typescript-eslint/consistent-type-definitions -- declaration merging requires interface
|
|
interface Window {
|
|
__codexEchoProbe?: {
|
|
report(): CodexEchoProbeReport
|
|
dispose(): void
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Installs an in-renderer echo-latency recorder on the active terminal pane.
|
|
*
|
|
* Why in-page: polling a serialized buffer over CDP adds serialize + IPC +
|
|
* poll-granularity cost to every sample, which swamped the signal it measured.
|
|
* Timestamps here are taken inside the renderer with performance.now(), so the
|
|
* measured window contains no cross-process work at all.
|
|
*/
|
|
export async function installCodexEchoLatencyProbe(page: Page, target: string): Promise<void> {
|
|
await page.evaluate((target) => {
|
|
type PendingSample = {
|
|
index: number
|
|
char: string
|
|
expected: string
|
|
startedAt: number
|
|
parsedAt: number | null
|
|
}
|
|
|
|
const state = window.__store?.getState()
|
|
const worktreeId = state?.activeWorktreeId
|
|
const tabId =
|
|
state?.activeTabType === 'terminal'
|
|
? state.activeTabId
|
|
: worktreeId
|
|
? (state?.activeTabIdByWorktree?.[worktreeId] ?? null)
|
|
: null
|
|
const manager = tabId ? window.__paneManagers?.get(tabId) : null
|
|
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0] ?? null
|
|
if (!pane) {
|
|
throw new Error('Codex echo probe: no active terminal pane')
|
|
}
|
|
const terminal = pane.terminal
|
|
if (typeof terminal.onWriteParsed !== 'function') {
|
|
throw new Error('Codex echo probe: xterm build has no onWriteParsed')
|
|
}
|
|
|
|
const samples: CodexEchoLatencySample[] = []
|
|
const awaitingRender: { sample: CodexEchoLatencySample; startedAt: number }[] = []
|
|
// Why a queue, not one slot: a slow echo can still be outstanding when the
|
|
// next key is pressed, and a single slot silently discards that sample.
|
|
const pending: PendingSample[] = []
|
|
let keysObserved = 0
|
|
let parseEvents = 0
|
|
let renderEvents = 0
|
|
|
|
// Why concatenated without a separator: a composer line that wraps splits the
|
|
// token across rows, and trailing-trimmed rows rejoin exactly at the break.
|
|
const viewportText = (): string => {
|
|
const buffer = terminal.buffer.active
|
|
let text = ''
|
|
for (let row = 0; row < terminal.rows; row += 1) {
|
|
text += buffer.getLine(buffer.viewportY + row)?.translateToString(true) ?? ''
|
|
}
|
|
return text
|
|
}
|
|
|
|
const observeParse = (): void => {
|
|
parseEvents += 1
|
|
if (pending.length === 0) {
|
|
return
|
|
}
|
|
const text = viewportText()
|
|
// Why drain in order: one parse can land several queued keystrokes at
|
|
// once, and each still gets credited against its own keydown timestamp.
|
|
while (pending.length > 0 && text.includes(pending[0].expected)) {
|
|
const entry = pending.shift()
|
|
if (!entry) {
|
|
break
|
|
}
|
|
entry.parsedAt = performance.now()
|
|
const sample: CodexEchoLatencySample = {
|
|
index: entry.index,
|
|
char: entry.char,
|
|
keyToParseMs: entry.parsedAt - entry.startedAt,
|
|
keyToRenderMs: null
|
|
}
|
|
samples.push(sample)
|
|
awaitingRender.push({ sample, startedAt: entry.startedAt })
|
|
}
|
|
}
|
|
|
|
const observeRender = (): void => {
|
|
renderEvents += 1
|
|
const paintedAt = performance.now()
|
|
for (const entry of awaitingRender.splice(0)) {
|
|
entry.sample.keyToRenderMs = paintedAt - entry.startedAt
|
|
}
|
|
}
|
|
|
|
// Why window capture: a listener on an ancestor in the capture phase is
|
|
// guaranteed to run before xterm's own keydown handler forwards to the PTY,
|
|
// so t0 is stamped before any of the work being measured starts.
|
|
const onKeyDown = (event: KeyboardEvent): void => {
|
|
if (event.key.length !== 1 || keysObserved >= target.length) {
|
|
return
|
|
}
|
|
const index = keysObserved
|
|
keysObserved += 1
|
|
pending.push({
|
|
index,
|
|
char: target[index],
|
|
expected: target.slice(0, index + 1),
|
|
startedAt: performance.now(),
|
|
parsedAt: null
|
|
})
|
|
}
|
|
|
|
window.addEventListener('keydown', onKeyDown, { capture: true })
|
|
const parsedDisposable = terminal.onWriteParsed(observeParse)
|
|
const renderDisposable = terminal.onRender(observeRender)
|
|
|
|
window.__codexEchoProbe = {
|
|
report: () => ({
|
|
samples: [...samples],
|
|
keysObserved,
|
|
parseEvents,
|
|
renderEvents,
|
|
cols: terminal.cols,
|
|
rows: terminal.rows
|
|
}),
|
|
dispose: () => {
|
|
window.removeEventListener('keydown', onKeyDown, { capture: true })
|
|
parsedDisposable.dispose()
|
|
renderDisposable.dispose()
|
|
}
|
|
}
|
|
}, target)
|
|
}
|
|
|
|
/** Drains every recorded sample in a single round-trip once typing has finished. */
|
|
export async function collectCodexEchoLatencyReport(page: Page): Promise<CodexEchoProbeReport> {
|
|
return page.evaluate(() => {
|
|
const probe = window.__codexEchoProbe
|
|
if (!probe) {
|
|
throw new Error('Codex echo probe was never installed')
|
|
}
|
|
const report = probe.report()
|
|
probe.dispose()
|
|
return report
|
|
})
|
|
}
|
|
|
|
export type LatencyDistribution = {
|
|
count: number
|
|
p50: number
|
|
p95: number
|
|
max: number
|
|
}
|
|
|
|
function percentile(sorted: number[], quantile: number): number {
|
|
if (sorted.length === 0) {
|
|
return 0
|
|
}
|
|
const rank = Math.min(sorted.length - 1, Math.ceil(quantile * sorted.length) - 1)
|
|
return sorted[Math.max(0, rank)]
|
|
}
|
|
|
|
export function summarizeLatencies(values: number[]): LatencyDistribution {
|
|
const sorted = [...values].sort((a, b) => a - b)
|
|
return {
|
|
count: sorted.length,
|
|
p50: percentile(sorted, 0.5),
|
|
p95: percentile(sorted, 0.95),
|
|
max: sorted.at(-1) ?? 0
|
|
}
|
|
}
|
|
|
|
export function formatDistribution(label: string, distribution: LatencyDistribution): string {
|
|
return (
|
|
`${label} n=${distribution.count} p50=${distribution.p50.toFixed(1)}ms ` +
|
|
`p95=${distribution.p95.toFixed(1)}ms max=${distribution.max.toFixed(1)}ms`
|
|
)
|
|
}
|