-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Refresh working tree diff after turns #3906
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import { EnvironmentId } from "@t3tools/contracts"; | ||
| import { describe, expect, it } from "@effect/vitest"; | ||
| import { Atom, AtomRegistry } from "effect/unstable/reactivity"; | ||
|
|
||
| import { invalidateReviewDiffPreviews, reviewDiffPreviewRevisionAtom } from "./review.ts"; | ||
|
|
||
| const TARGET = { | ||
| environmentId: EnvironmentId.make("environment-1"), | ||
| }; | ||
|
|
||
| describe("review diff preview invalidation", () => { | ||
| it("scopes revision changes to the invalidated environment", () => { | ||
| const registry = AtomRegistry.make(); | ||
| const otherEnvironment = { | ||
| environmentId: EnvironmentId.make("environment-2"), | ||
| }; | ||
|
|
||
| expect(registry.get(reviewDiffPreviewRevisionAtom(TARGET))).toBe(0); | ||
| expect(registry.get(reviewDiffPreviewRevisionAtom(otherEnvironment))).toBe(0); | ||
|
|
||
| invalidateReviewDiffPreviews(registry, TARGET); | ||
|
|
||
| expect(registry.get(reviewDiffPreviewRevisionAtom(TARGET))).toBe(1); | ||
| expect(registry.get(reviewDiffPreviewRevisionAtom(otherEnvironment))).toBe(0); | ||
| registry.dispose(); | ||
| }); | ||
|
|
||
| it("notifies reactive dependents without replacing their atom identity", () => { | ||
| const registry = AtomRegistry.make(); | ||
| const dependent = Atom.make((get) => get(reviewDiffPreviewRevisionAtom(TARGET))); | ||
| const unmount = registry.mount(dependent); | ||
|
|
||
| expect(registry.get(dependent)).toBe(0); | ||
| invalidateReviewDiffPreviews(registry, TARGET); | ||
| expect(registry.get(dependent)).toBe(1); | ||
|
|
||
| unmount(); | ||
| registry.dispose(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ import { | |
| type VcsListRefsInput, | ||
| type VcsListRefsResult, | ||
| type VcsStatusResult, | ||
| type VcsStatusStreamEvent, | ||
| WS_METHODS, | ||
| } from "@t3tools/contracts"; | ||
| import { applyGitStatusStreamEvent } from "@t3tools/shared/git"; | ||
|
|
@@ -15,14 +16,15 @@ import * as Stream from "effect/Stream"; | |
| import * as SubscriptionRef from "effect/SubscriptionRef"; | ||
| import { Atom, AtomRegistry } from "effect/unstable/reactivity"; | ||
|
|
||
| import { createEnvironmentRpcCommand, createEnvironmentSubscriptionAtomFamily } from "./runtime.ts"; | ||
| import { createEnvironmentRpcCommand } from "./runtime.ts"; | ||
| import type { EnvironmentRegistry } from "../connection/registry.ts"; | ||
| import { EnvironmentSupervisor } from "../connection/supervisor.ts"; | ||
| import { safeErrorLogAttributes } from "../errors/safeLog.ts"; | ||
| import { EnvironmentCacheStore } from "../platform/persistence.ts"; | ||
| import { request, subscribe, type EnvironmentRpcInput } from "../rpc/client.ts"; | ||
| import { followStreamInEnvironment } from "./runtime.ts"; | ||
| import { vcsCommandConcurrency, vcsCommandScheduler } from "./vcsCommandScheduler.ts"; | ||
| import { invalidateReviewDiffPreviews } from "./review.ts"; | ||
| import { | ||
| invalidateCachedVcsRefs, | ||
| vcsRefsCacheStateAtom, | ||
|
|
@@ -233,6 +235,34 @@ export function cachedVcsRefsChanges( | |
| ); | ||
| } | ||
|
|
||
| export function projectVcsStatusChanges<E, R>( | ||
| environmentId: EnvironmentId, | ||
| events: Stream.Stream<VcsStatusStreamEvent, E, R>, | ||
| ) { | ||
| return events.pipe( | ||
| Stream.mapAccumEffect( | ||
| () => null as VcsStatusResult | null, | ||
| (current, event) => { | ||
| const next = applyGitStatusStreamEvent(current, event); | ||
| return Effect.gen(function* () { | ||
| if (current !== null) { | ||
| const registry = yield* AtomRegistry.AtomRegistry; | ||
| invalidateReviewDiffPreviews(registry, { environmentId }); | ||
|
Comment on lines
+248
to
+250
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a later turn edits an already-dirty file without changing its path or insertion/deletion counts, this invalidation never runs: Useful? React with 👍 / 👎.
Comment on lines
+249
to
+250
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the automatic status fetch moves a selected remote base such as Useful? React with 👍 / 👎. |
||
| } | ||
| return [next, [next]] as const; | ||
| }); | ||
| }, | ||
| ), | ||
| ); | ||
| } | ||
|
|
||
| export function vcsStatusChanges( | ||
| environmentId: EnvironmentId, | ||
| input: EnvironmentRpcInput<typeof WS_METHODS.subscribeVcsStatus>, | ||
| ) { | ||
| return projectVcsStatusChanges(environmentId, subscribe(WS_METHODS.subscribeVcsStatus, input)); | ||
| } | ||
|
|
||
| export function createVcsEnvironmentAtoms<R, E>( | ||
| runtime: Atom.AtomRuntime<EnvironmentRegistry | EnvironmentCacheStore | R, E>, | ||
| ) { | ||
|
|
@@ -259,6 +289,23 @@ export function createVcsEnvironmentAtoms<R, E>( | |
| readonly environmentId: EnvironmentId; | ||
| readonly input: VcsListRefsInput; | ||
| }) => listRefsByEnvironment(target.environmentId)(JSON.stringify(target.input)); | ||
| const statusByEnvironment = Atom.family((environmentId: EnvironmentId) => | ||
| Atom.family((inputKey: string) => { | ||
| const input = JSON.parse(inputKey) as EnvironmentRpcInput< | ||
| typeof WS_METHODS.subscribeVcsStatus | ||
| >; | ||
| return runtime | ||
| .atom(followStreamInEnvironment(environmentId, vcsStatusChanges(environmentId, input))) | ||
| .pipe( | ||
| Atom.setIdleTTL(5 * 60_000), | ||
| Atom.withLabel(`environment-data:vcs:status:${environmentId}:${inputKey}`), | ||
| ); | ||
| }), | ||
| ); | ||
| const status = (target: { | ||
| readonly environmentId: EnvironmentId; | ||
| readonly input: EnvironmentRpcInput<typeof WS_METHODS.subscribeVcsStatus>; | ||
| }) => statusByEnvironment(target.environmentId)(JSON.stringify(target.input)); | ||
| const invalidateRefs = ( | ||
| target: { readonly environmentId: EnvironmentId; readonly input: { readonly cwd: string } }, | ||
| registry: AtomRegistry.AtomRegistry, | ||
|
|
@@ -270,19 +317,7 @@ export function createVcsEnvironmentAtoms<R, E>( | |
|
|
||
| return { | ||
| listRefs, | ||
| status: createEnvironmentSubscriptionAtomFamily(runtime, { | ||
| label: "environment-data:vcs:status", | ||
| subscribe: (input: EnvironmentRpcInput<typeof WS_METHODS.subscribeVcsStatus>) => | ||
| subscribe(WS_METHODS.subscribeVcsStatus, input).pipe( | ||
| Stream.mapAccum( | ||
| () => null as VcsStatusResult | null, | ||
| (current, event) => { | ||
| const next = applyGitStatusStreamEvent(current, event); | ||
| return [next, [next]] as const; | ||
| }, | ||
| ), | ||
| ), | ||
| }), | ||
| status, | ||
| pull: createEnvironmentRpcCommand(runtime, { | ||
| label: "environment-data:vcs:pull", | ||
| tag: WS_METHODS.vcsPull, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When one environment contains multiple projects or worktrees, every post-snapshot status event increments this single environment-wide revision, so an event from any visible sidebar thread forces the active diff preview for an unrelated
cwdto execute another potentially large Git diff and send it over the WebSocket. Web and mobile both mount additional status streams outside their review panels, making parallel agent activity in one environment a practical source of repeated unrelated preview requests; key the revision by normalized repository/CWD (and invalidate only matching preview inputs) instead.AGENTS.md reference: AGENTS.md:L15-L17
Useful? React with 👍 / 👎.