mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-07 00:44:49 +00:00
* Add inline git diff review comments
Adds a review draft store keyed per diff target, inline gutter + thread
rendering in the diff pane, and a generated review attachment that flows
through the composer and agent providers.
* Strip generated review attachments before saving to draft store
Drafts persist only UserComposerAttachment (no review kind), so filter the
auto-generated review attachment out when seeding the draft or restoring
after a failed auto-submit.
* Fix inline review typecheck and test harness drift
* Unslop inline review feature
- Fix stale-closure bug in composer queueMessage: clear drafts for the
actually-queued attachments, not the latest props
- Drop dead useEffect mirroring initialBody in inline review editor
(parent already remounts via key)
- Restore typed AgentAttachment signature on renderPromptAttachmentAsText;
delete schema redispatch helper and drop null-checks across 5 providers
- Inline single-use review prompt formatters and replace marker switch
with const map
- Drop normalizeComment field-copy redundancy after type guard
- Remove __reviewDraftStoreTestUtils export; test migration via
rehydrate() public path
- Refactor findTarget to return the target line directly (no non-null
assertion at call site)
- Drop redundant isGeneratedReviewAttachment helper; inline kind check
* Update review pill to withUnistyles after rebase
Main's unistyles refactor stripped useUnistyles in favor of withUnistyles +
StyleSheet. Convert ReviewAttachmentPill to use ThemedCircleDot + ICON_SIZE so
the icon picks up the foreground-muted mapping like the other pills.
* Fix lint and typecheck after rebase onto main
Resolve drift after main's withUnistyles refactor:
- Composer complexity: dedup voice?.isVoiceSwitching to drop to 20
- Move per-comment Pressable handlers in git-diff-inline-review into
CommentRow subcomponent so callbacks are stable
- Hoist style/onPress factories to module level (iconButton,
iconButtonDestructive, ghostButton)
- Memoize style arrays in git-diff-pane review wrappers
- Replace inline {} placeholders with useMemo styles
- review-draft-store: extract applyCommentUpdates to avoid map-spread,
include input.key in useMemo deps
- Test fixtures: hoist EMPTY_COMMENTS, add buildReviewActions helper
* Drop GitDiffPane default param to keep complexity at 20
After the rebase, main's `enabled = true` default param combined with the
inline review additions pushed cyclomatic complexity to 21. Drop the
default and resolve `enabled !== false` at the call site (comparison
doesn't count toward complexity).
* Reshape inline review behind @/review module
Drop the `generated: true` attachment flag and consolidate inline-review
logic behind a deep `@/review` module so composer and git-diff-pane stop
hand-composing review primitives. Workspace-scoped review snapshots are
now derived at submit/queue time rather than persisted in the draft
store. Composer review references drop from 51 to 4; git-diff-pane drops
~108 lines and now consumes a controller hook + slot components.
* Fix optimistic review attachments
116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
import { useMemo } from "react";
|
|
import { create } from "zustand";
|
|
import type { WorkspaceComposerAttachment } from "./types";
|
|
|
|
const EMPTY_WORKSPACE_ATTACHMENTS: readonly WorkspaceComposerAttachment[] = [];
|
|
|
|
export interface WorkspaceAttachmentScopeInput {
|
|
serverId: string;
|
|
workspaceId?: string | null;
|
|
cwd: string;
|
|
}
|
|
|
|
interface WorkspaceAttachmentsStoreState {
|
|
attachmentsByScope: Record<string, readonly WorkspaceComposerAttachment[]>;
|
|
}
|
|
|
|
interface WorkspaceAttachmentsStoreActions {
|
|
setWorkspaceAttachments: (input: {
|
|
scopeKey: string;
|
|
attachments: readonly WorkspaceComposerAttachment[];
|
|
}) => void;
|
|
clearWorkspaceAttachments: (input: { scopeKey: string }) => void;
|
|
}
|
|
|
|
type WorkspaceAttachmentsStore = WorkspaceAttachmentsStoreState & WorkspaceAttachmentsStoreActions;
|
|
|
|
function encodeScopePart(value: string): string {
|
|
return encodeURIComponent(value.trim());
|
|
}
|
|
|
|
function normalizeCwd(cwd: string): string {
|
|
const trimmed = cwd.trim();
|
|
if (trimmed === "/") {
|
|
return trimmed;
|
|
}
|
|
return trimmed.replace(/\/+$/, "");
|
|
}
|
|
|
|
export function buildWorkspaceAttachmentScopeKey(input: WorkspaceAttachmentScopeInput): string {
|
|
const workspaceId = input.workspaceId?.trim();
|
|
const workspacePart = workspaceId
|
|
? `workspace=${encodeScopePart(workspaceId)}`
|
|
: `cwd=${encodeScopePart(normalizeCwd(input.cwd))}`;
|
|
|
|
return ["workspace-attachments", `server=${encodeScopePart(input.serverId)}`, workspacePart].join(
|
|
":",
|
|
);
|
|
}
|
|
|
|
function areWorkspaceAttachmentsEqual(
|
|
left: readonly WorkspaceComposerAttachment[],
|
|
right: readonly WorkspaceComposerAttachment[],
|
|
): boolean {
|
|
if (left === right) {
|
|
return true;
|
|
}
|
|
if (left.length !== right.length) {
|
|
return false;
|
|
}
|
|
return left.every((attachment, index) => attachment === right[index]);
|
|
}
|
|
|
|
export const useWorkspaceAttachmentsStore = create<WorkspaceAttachmentsStore>()((set) => ({
|
|
attachmentsByScope: {},
|
|
setWorkspaceAttachments: ({ scopeKey, attachments }) => {
|
|
set((state) => {
|
|
const current = state.attachmentsByScope[scopeKey] ?? EMPTY_WORKSPACE_ATTACHMENTS;
|
|
if (areWorkspaceAttachmentsEqual(current, attachments)) {
|
|
return state;
|
|
}
|
|
if (attachments.length === 0) {
|
|
if (!state.attachmentsByScope[scopeKey]) {
|
|
return state;
|
|
}
|
|
const next = { ...state.attachmentsByScope };
|
|
delete next[scopeKey];
|
|
return { attachmentsByScope: next };
|
|
}
|
|
return {
|
|
attachmentsByScope: {
|
|
...state.attachmentsByScope,
|
|
[scopeKey]: attachments,
|
|
},
|
|
};
|
|
});
|
|
},
|
|
clearWorkspaceAttachments: ({ scopeKey }) => {
|
|
set((state) => {
|
|
if (!state.attachmentsByScope[scopeKey]) {
|
|
return state;
|
|
}
|
|
const next = { ...state.attachmentsByScope };
|
|
delete next[scopeKey];
|
|
return { attachmentsByScope: next };
|
|
});
|
|
},
|
|
}));
|
|
|
|
export function useWorkspaceAttachmentScopeKey(input: WorkspaceAttachmentScopeInput): string {
|
|
const { serverId, workspaceId, cwd } = input;
|
|
return useMemo(
|
|
() => buildWorkspaceAttachmentScopeKey({ serverId, workspaceId, cwd }),
|
|
[serverId, workspaceId, cwd],
|
|
);
|
|
}
|
|
|
|
export function useWorkspaceAttachments(scopeKey: string): readonly WorkspaceComposerAttachment[] {
|
|
return useWorkspaceAttachmentsStore(
|
|
(state) => state.attachmentsByScope[scopeKey] ?? EMPTY_WORKSPACE_ATTACHMENTS,
|
|
);
|
|
}
|
|
|
|
export function resetWorkspaceAttachmentsStore(): void {
|
|
useWorkspaceAttachmentsStore.setState({ attachmentsByScope: {} });
|
|
}
|