mirror of
https://github.com/block/buzz.git
synced 2026-11-15 05:41:14 +00:00
## Summary - inject bounded project-home identity and repository context into managed agent sessions - add project-aware CLI flows for creating projects, repositories, issues, and related channels - match project homes through the existing relay query surface, then filter channel metadata client-side This is Part 1 of the channel-first Projects stack. Part 2 contains project creation and model foundations. ## Testing - `cargo fmt --all -- --check` - `cargo clippy -p buzz-cli -p buzz-acp --all-targets -- -D warnings` - `cargo test -p buzz-cli -p buzz-acp` — 1,184 tests passed, 1 doc test ignored - full pre-push gate passed ## Post-Deploy Monitoring & Validation - validate project-home agent context and project-aware CLI commands against a staging relay - healthy signals: project context matches the active channel, explicit repo coordinates remain stable, and normal channels receive no project block - failure signals: cross-channel project context, duplicate project creation, or commands targeting an unrelated repository; mitigate by reverting this PR --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
106 lines
2.9 KiB
TypeScript
106 lines
2.9 KiB
TypeScript
import * as React from "react";
|
|
|
|
import {
|
|
EMPTY_PROJECT_SELECTION,
|
|
nextProjectGroupSelection,
|
|
nextProjectSelection,
|
|
type ProjectSelectionItem,
|
|
type ProjectSelectionState,
|
|
} from "./projectSelection";
|
|
|
|
type ProjectSelectionContextValue = {
|
|
active: boolean;
|
|
clear: () => void;
|
|
isSelected: (id: string) => boolean;
|
|
items: ProjectSelectionItem[];
|
|
toggleGroup: (items: ProjectSelectionItem[]) => void;
|
|
toggle: (
|
|
item: ProjectSelectionItem,
|
|
options?: { rangeItems?: ProjectSelectionItem[]; shiftKey?: boolean },
|
|
) => void;
|
|
};
|
|
|
|
const ProjectSelectionContext =
|
|
React.createContext<ProjectSelectionContextValue | null>(null);
|
|
|
|
export function ProjectSelectionProvider({
|
|
children,
|
|
onClear,
|
|
onSelect,
|
|
resetKey,
|
|
}: {
|
|
children: React.ReactNode;
|
|
onClear?: () => void;
|
|
onSelect?: () => void;
|
|
resetKey: string;
|
|
}) {
|
|
const [state, setState] = React.useState<ProjectSelectionState>(
|
|
EMPTY_PROJECT_SELECTION,
|
|
);
|
|
const [prevResetKey, setPrevResetKey] = React.useState(resetKey);
|
|
if (resetKey !== prevResetKey) {
|
|
setPrevResetKey(resetKey);
|
|
setState(EMPTY_PROJECT_SELECTION);
|
|
}
|
|
const onSelectRef = React.useRef(onSelect);
|
|
onSelectRef.current = onSelect;
|
|
const onClearRef = React.useRef(onClear);
|
|
onClearRef.current = onClear;
|
|
const wasActiveRef = React.useRef(false);
|
|
|
|
const clear = React.useCallback(() => {
|
|
setState(EMPTY_PROJECT_SELECTION);
|
|
}, []);
|
|
|
|
const toggle = React.useCallback(
|
|
(
|
|
item: ProjectSelectionItem,
|
|
options?: { rangeItems?: ProjectSelectionItem[]; shiftKey?: boolean },
|
|
) => {
|
|
setState((current) => nextProjectSelection(current, item, options));
|
|
},
|
|
[],
|
|
);
|
|
const toggleGroup = React.useCallback((items: ProjectSelectionItem[]) => {
|
|
setState((current) => nextProjectGroupSelection(current, items));
|
|
}, []);
|
|
|
|
React.useEffect(() => {
|
|
const active = state.items.length > 0;
|
|
if (active && !wasActiveRef.current) onSelectRef.current?.();
|
|
if (!active && wasActiveRef.current) onClearRef.current?.();
|
|
wasActiveRef.current = active;
|
|
}, [state.items.length]);
|
|
|
|
React.useEffect(() => {
|
|
if (state.items.length === 0) return;
|
|
const onKeyDown = (event: KeyboardEvent) => {
|
|
if (event.key === "Escape" && !event.defaultPrevented) clear();
|
|
};
|
|
window.addEventListener("keydown", onKeyDown);
|
|
return () => window.removeEventListener("keydown", onKeyDown);
|
|
}, [clear, state.items.length]);
|
|
|
|
const value = React.useMemo<ProjectSelectionContextValue>(
|
|
() => ({
|
|
active: state.items.length > 0,
|
|
clear,
|
|
isSelected: (id) => state.items.some((item) => item.id === id),
|
|
items: state.items,
|
|
toggle,
|
|
toggleGroup,
|
|
}),
|
|
[clear, state.items, toggle, toggleGroup],
|
|
);
|
|
|
|
return (
|
|
<ProjectSelectionContext.Provider value={value}>
|
|
{children}
|
|
</ProjectSelectionContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useProjectSelection() {
|
|
return React.useContext(ProjectSelectionContext);
|
|
}
|