mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
* fix(editor): settle a save's shared record when the saving tab closed mid-write With one file open in two workspaces, closing the saving tab while its write was in flight left the other tab's disk baseline, dirty flag, draft and loaded text on the pre-save version: every completion step looked the closed tab up by id and found nothing, and the saved event listed only the closed tab as the record's views. An undo back to the old text then read as clean and closed without asking. The write now captures its record id with no await before the write, and completion (rebaseline, dirty/draft settle, conflict clear or refusal banner, saved event's view list) goes through a live view of that record, preferring a writable one. The save runner and the write-lock wait do the same, so a save queued by a tab that closes still writes through the record's remaining view. The chat visual's saved-event listener also falls back to the record's views. Co-Authored-By: Claude <noreply@anthropic.com> * fix(editor): follow a closed saver's record through re-keys, kept records and listeners Review follow-ups to the closed-saving-tab fix: - Completion now finds the record through the view ids captured before the write, not the record id: a re-key mid-write (an unpinned runtime tab resolving its pairing) keeps views but changes the id, which brought the original bug back. - A fallback writer (asking view closed while queued or waiting on the file lock) is chosen like autosave/restart's writer: writable and on a current connection, so a stale-connection sibling no longer throws when a current view exists. - With no view of the record left, a kept record (no-prompt removal) is dropped when the write saved its text, else rebaselined; other open views of the file (a detached peer) take the written baseline, keeping their edits. - A save may mark a record clean through a read-only or CSV-preview view (markFileDirty `written`); such views still never become dirty. - Markdown preview refresh and the CSV pending-cell listener match the saved event's record views, not only the saving view's id. Co-Authored-By: Claude <noreply@anthropic.com> * test(editor): name the closed-view save harness as a test fixture The focus-read ratchet scans non-test sources; the `.test-fixture.ts` suffix marks this harness as test-only like the repo's other fixtures. Co-Authored-By: Claude <noreply@anthropic.com> * test(ci): list the closed-view save rig as a unit support bridge The rig builds on the autosave test fixture, which takes the editor slice harness, so the routing check saw it as a product consumer of that harness. Bridges are an explicit list, like the browser drawn-from-main fixture. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
156 lines
6.2 KiB
JavaScript
156 lines
6.2 KiB
JavaScript
import { beforeAll, describe, expect, it } from 'vitest'
|
|
import { classifyPrJobs } from './pr-code-change-scope.mjs'
|
|
import { selectPrE2eSpecs } from './pr-e2e-source-routing.mjs'
|
|
import {
|
|
buildUnitDependencyGraph,
|
|
collectUnitDependencyGraph,
|
|
unitConsumers
|
|
} from './ci-unit-dependency-graph.mjs'
|
|
import { discoverUnitFiles } from './ci-unit-files.mjs'
|
|
import { planUnitSelection } from './ci-unit-selection.mjs'
|
|
|
|
const harness = 'src/renderer/src/runtime/web-session-tabs-sync-test-harness.ts'
|
|
const structuredSessionHarness =
|
|
'src/renderer/src/components/native-chat/NativeChatStructuredSession.test-harness.tsx'
|
|
|
|
const unitHarnesses = [
|
|
harness,
|
|
structuredSessionHarness,
|
|
'src/renderer/src/store/slices/editor-slice-test-harness.ts',
|
|
'src/renderer/src/store/slices/browser-slice-test-harness.ts',
|
|
'src/renderer/src/runtime/web-runtime-session-test-harness.ts',
|
|
'src/renderer/src/runtime/sync-runtime-graph-test-harness.ts'
|
|
]
|
|
const supportBridges = new Set([
|
|
'src/renderer/src/runtime/web-runtime-browser-creation-placement-test-rig.ts',
|
|
'src/renderer/src/components/editor/editor-model-lifetime-fixture.ts',
|
|
// Autosave suites' store rig; it takes the editor harness's tab-layout maps.
|
|
'src/renderer/src/components/editor/editor-autosave-controller-test-fixture.ts',
|
|
// Closed-saving-view save suites' rig; it builds on the autosave rig above.
|
|
'src/renderer/src/components/editor/editor-save-closed-view.test-fixture.ts',
|
|
// Browser suites' store with main drawing their tabs; it takes the browser harness's store.
|
|
'src/renderer/src/store/slices/browser-slice-drawn-from-main.test-fixture.ts'
|
|
])
|
|
|
|
function productConsumers(file, graph, unitFiles) {
|
|
return [...unitConsumers([file], graph.reverse)].filter(
|
|
(consumer) => consumer !== file && !unitFiles.has(consumer) && !supportBridges.has(consumer)
|
|
)
|
|
}
|
|
|
|
describe('unit support routing', () => {
|
|
it.each(unitHarnesses)(
|
|
'keeps unit checks and lint while excluding packaging for %s',
|
|
(unitHarness) => {
|
|
const result = classifyPrJobs([unitHarness])
|
|
expect(result.should_run).toBe(true)
|
|
expect(result.static_analysis).toBe(true)
|
|
expect(result.typecheck).toBe(true)
|
|
expect(result.test).toBe(true)
|
|
expect(result.package).toBe(false)
|
|
expect(result.package_windows).toBe(false)
|
|
}
|
|
)
|
|
|
|
it.each(unitHarnesses)(
|
|
'retains packaging for unknown helpers and mixed product changes with %s',
|
|
(unitHarness) => {
|
|
for (const file of [
|
|
unitHarness.replace(/\.(tsx?)$/, '-next.$1'),
|
|
'src/renderer/src/runtime/web-session-tabs-sync-next-test-harness.ts',
|
|
'src/renderer/src/runtime/web-session-tabs-sync/snapshot-api.ts',
|
|
'src/renderer/src/components/native-chat/NativeChatStructuredSession.next-test-harness.tsx',
|
|
'src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx',
|
|
'src/main/providers/windows-conpty-wide-char-duplication.node-pty.test.ts',
|
|
'src/main/browser/browser-route-tcp-egress.electron.test.ts'
|
|
]) {
|
|
const result = classifyPrJobs([unitHarness, file])
|
|
expect(result.should_run, file).toBe(true)
|
|
expect(result.test, file).toBe(true)
|
|
expect(result.package, file).toBe(
|
|
file !== 'src/main/providers/windows-conpty-wide-char-duplication.node-pty.test.ts'
|
|
)
|
|
expect(result.package_windows, file).toBe(true)
|
|
}
|
|
}
|
|
)
|
|
|
|
it.each(unitHarnesses)('does not select product E2E for %s', (unitHarness) => {
|
|
expect(selectPrE2eSpecs([unitHarness])).toEqual([])
|
|
})
|
|
|
|
it('retains E2E selection for unknown helpers, product changes and selected specs', () => {
|
|
const spec = 'tests/e2e/paired-client-hosted-browser-restart-survival.spec.ts'
|
|
for (const file of [
|
|
'src/renderer/src/runtime/web-session-tabs-sync-next-test-harness.ts',
|
|
'src/renderer/src/runtime/web-session-tabs-sync/snapshot-api.ts',
|
|
spec
|
|
]) {
|
|
expect(selectPrE2eSpecs([harness, file]), file).toContain(spec)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('unit support import ownership', () => {
|
|
let graph
|
|
let unitFiles
|
|
beforeAll(() => {
|
|
graph = collectUnitDependencyGraph()
|
|
unitFiles = new Set(discoverUnitFiles())
|
|
}, 60_000)
|
|
|
|
it.each(unitHarnesses)('rejects any product consumer of %s', (unitHarness) => {
|
|
expect(graph.files.has(unitHarness)).toBe(true)
|
|
expect(
|
|
[...unitConsumers([unitHarness], graph.reverse)].some((file) => unitFiles.has(file))
|
|
).toBe(true)
|
|
expect(productConsumers(unitHarness, graph, unitFiles)).toEqual([])
|
|
})
|
|
|
|
it.each(unitHarnesses)('keeps the full unit inventory for a normal PR editing %s', (file) => {
|
|
const files = [...unitFiles]
|
|
const plan = planUnitSelection({
|
|
files,
|
|
changed: [file],
|
|
graph,
|
|
timings: {},
|
|
event: { pull_request: { draft: false } },
|
|
mode: 'selected'
|
|
})
|
|
expect(plan.mode).toBe('shadow')
|
|
expect(plan.executionFiles).toEqual(files)
|
|
expect(plan.shards).toHaveLength(5)
|
|
})
|
|
|
|
it.each([
|
|
"import '@/runtime/web-session-tabs-sync-test-harness'",
|
|
"export * from '@/runtime/web-session-tabs-sync-test-harness'",
|
|
"import('@/runtime/web-session-tabs-sync-test-harness')",
|
|
"import type { Fixture } from '@/runtime/web-session-tabs-sync-test-harness'"
|
|
])('detects a product importer using %s', (source) => {
|
|
const importer = 'src/renderer/src/runtime/product.ts'
|
|
const test = 'src/renderer/src/runtime/ownership.test.ts'
|
|
const synthetic = buildUnitDependencyGraph(
|
|
new Map([
|
|
[harness, 'export const value = 1'],
|
|
[importer, source],
|
|
[test, "import './web-session-tabs-sync-test-harness'"]
|
|
])
|
|
)
|
|
expect(productConsumers(harness, synthetic, new Set([test]))).toEqual([importer])
|
|
})
|
|
|
|
it('rejects product consumers reached through an audited support bridge', () => {
|
|
const bridge = 'src/renderer/src/components/editor/editor-model-lifetime-fixture.ts'
|
|
const importer = 'src/renderer/src/components/editor/product.ts'
|
|
const synthetic = buildUnitDependencyGraph(
|
|
new Map([
|
|
[harness, 'export const value = 1'],
|
|
[bridge, "import '@/runtime/web-session-tabs-sync-test-harness'"],
|
|
[importer, "import './editor-model-lifetime-fixture'"]
|
|
])
|
|
)
|
|
expect(productConsumers(harness, synthetic, new Set())).toEqual([importer])
|
|
})
|
|
})
|