Files
buzz/desktop/src/features/projects/lib/useProjectSelection.tsx
thomaspblock de188ebf06 feat(projects): add agent and CLI project-home support (#6590)
## 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>
2026-08-26 12:09:06 -07:00

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);
}