Files
AgentHub/client/src/lib/gitChanged.ts
T
nntrivi2001 f6eb39eb73 feat(git): switch with uncommitted changes (carry / shelve), Create & switch, and shelves with Restore/Delete
- switch takes changes: carry (plain git switch, 409 ECARRYCONFLICT with files) or shelve (shelf first, re-applied if the switch fails)
- branch-create switch: true runs git switch -c, the changes come along
- shelve / unshelve / shelf-drop + GET /git/shelves: only agenthub-shelf: stash entries, addressed by sha, repo root only, never mid-operation; a clean restore drops the shelf, a conflicted one keeps it
- 409 EAGENTRUNNING until confirm: true while the workspace's agent is working
- git bar: Bring my changes / Shelve and switch choice, Create & switch, restore offer after a switch; Changes panel: Shelve (n) and the Shelves list
- fix: after a switch the Changes panel showed the old branch — the post-write re-read joined an in-flight GET started before the write (request() dedupe); writes now close in-flight GETs, and every git write fires agenthub:git-changed so every view of the workspace re-reads
2026-11-02 11:44:35 +07:00

49 lines
1.8 KiB
TypeScript

/**
* @file gitChanged.ts
* @description One window event, `agenthub:git-changed` `{laneId, action}`,
* fired after EVERY git write the dashboard sends for a workspace
* (`api.lanes.gitAction` fires it, success or failure — a failed shelve or
* a conflicted restore still moved files). Every view that shows that
* workspace's git state — the Changes panel of each repo, the header's facts
* (`useLaneGitFacts`, the rail counts included), the workspace home's branch —
* listens and reads again, so a switch made in one of them is never left
* stale in another for a poll interval. The views do not share a callback;
* this is the bus between them.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { useEffect, useRef } from "react";
export const GIT_CHANGED_EVENT = "agenthub:git-changed";
export interface GitChangedDetail {
laneId: number;
/** The git action that ran (`switch`, `commit`, `shelve`, …). */
action: string;
}
/** Tell every view of `laneId` that its git state may have moved. */
export function emitGitChanged(laneId: number, action: string): void {
if (typeof window === "undefined") return;
window.dispatchEvent(
new CustomEvent<GitChangedDetail>(GIT_CHANGED_EVENT, { detail: { laneId, action } })
);
}
/**
* Call `fn` after every git write on workspace `laneId`. The latest `fn` is
* used, so a caller may pass an inline closure without resubscribing.
*/
export function useGitChanged(laneId: number, fn: (action: string) => void): void {
const latest = useRef(fn);
latest.current = fn;
useEffect(() => {
const on = (e: Event) => {
const d = (e as CustomEvent<GitChangedDetail>).detail;
if (d && d.laneId === laneId) latest.current(d.action);
};
window.addEventListener(GIT_CHANGED_EVENT, on);
return () => window.removeEventListener(GIT_CHANGED_EVENT, on);
}, [laneId]);
}