feat(desktop): make Projects workspaces selectable (#6368)

## Summary

Projects workspaces now support consistent selection across projects,
repositories, reviews, tasks, and channel results. A user can collect
related entities, see the active selection without losing their current
workspace, and pass a bounded, injection-safe representation to an agent
for discussion.

Detail views now keep repository source and availability explicit while
preserving contextual actions across local and remote workspaces.
Missing or inaccessible repositories produce a recoverable state instead
of leaving the workspace ambiguous.

This is part 2 of the Projects v6 stack. Parts 3 and 4 will add
context-aware collaboration and navigation/detail-page polish.

### Related issue

Related: #6335

### Testing

- Full pre-push gate passed: file-size checks, Biome, TypeScript,
Desktop unit tests, Tauri checks, and Rust tests
- Project context safety suite: 9/9 passed, including bounded untrusted
overview data and hidden-context stripping
- Updated Playwright coverage exercises grouped selection,
selection-aware agent context, repository source states, and detail
actions

---------

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
This commit is contained in:
thomaspblock
2026-08-20 10:42:02 -04:00
committed by GitHub
parent a9640c7cc4
commit 2b7be9340d
76 changed files with 5363 additions and 1688 deletions

View File

@@ -74,6 +74,7 @@ export function useThreadPanelWidth(
const [widthPx, setWidthPx] = React.useState<number>(() =>
getInitialThreadPanelWidth({ defaultWidthPx, minWidthPx, sessionKey }),
);
const [isResizing, setIsResizing] = React.useState(false);
React.useEffect(() => {
if (typeof window === "undefined") {
@@ -98,6 +99,7 @@ export function useThreadPanelWidth(
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
setIsResizing(true);
const handlePointerMove = (moveEvent: PointerEvent) => {
const deltaX = startX - moveEvent.clientX;
@@ -111,14 +113,18 @@ export function useThreadPanelWidth(
setWidthPx(nextWidth);
};
const handlePointerUp = () => {
const handlePointerEnd = () => {
document.body.style.cursor = previousCursor;
document.body.style.userSelect = previousUserSelect;
setIsResizing(false);
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerEnd);
window.removeEventListener("pointercancel", handlePointerEnd);
};
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp, { once: true });
window.addEventListener("pointerup", handlePointerEnd);
window.addEventListener("pointercancel", handlePointerEnd);
},
[getAvailableWidth, minWidthPx, widthPx],
);
@@ -129,6 +135,7 @@ export function useThreadPanelWidth(
return {
canReset: widthPx !== defaultWidthPx,
isResizing,
onResetWidth,
onResizeStart,
widthPx,