mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Git commit history (#1534)
* feat(app): add fileView preference for changes panel * feat(app): add buildDiffTree util for changed-files tree * feat(app): add changed-files tree directory row * feat(app): changed-files tree view mode in changes panel (#117) * feat(protocol): add checkout.commits.list RPC + commitsList feature flag * feat(server): list branch commits ahead of base with on-remote flags * feat(server): handle checkout.commits.list RPC and advertise capability * feat(client): checkout.commits.list method + useCommitsQuery hook * feat(app): per-commit inline view with local-vs-remote markers (#117) * feat(protocol,server): per-commit file diff RPC (checkout.commits.file_diff) * feat(app): open per-commit file diff on click (#117) * refactor: surface baseRef + commits loading/error, drop dead depth field * fix(app): flip commit local/remote dot — local hollow, remote filled * feat(app): list/tree view for expanded commit file list (#117) * fix(app): syntax-highlight per-commit file diff to match Changes view * feat(app): draggable resize between commits and diff sections * refactor(app): dedupe wrap-text helpers into diff-highlighted-text * fix(app): clean up commits resize drag on unmount + a11y label * fix(app): collapse commits section by default * fix(app): render per-commit file diff with the shared Changes line renderer * perf(app): memoize shared diff line row; drop redundant DiffLineView wrapper * refactor(app): extract shared DiffFileBody; render commit file diff through it * fix(app): hide inline-comment affordance in per-commit diffs (no reviewActions) * feat(app): move commits to a resizable bottom drawer in the Changes panel * feat(review): commitSha scoping for per-commit review drafts + attachment * feat(app): per-commit inline comments wired through to the composer (#117) * refactor(app): own commit file-diff open state in CommitFileList (drop reset effect) * refactor(app): colocate diff-render cluster under git/diff-file-body/ * feat(app): add diff tab target kind (working/commit diff tabs) * feat(app): useDiffFiles hook unifying working + commit diff targets * feat(app): diff tab panel rendering working/commit diffs * feat(app): open a commit diff tab on commit click; drop per-commit drawer/file list * feat(app): open/scroll working diff tab on changed-file click * fix(app): keep commit diff tabs ephemeral; align working diff whitespace * feat(app): collapsible file sections in the diff tab, collapsed by default * feat(app): make the on-remote commit dot more subtle Dims the filled green remote dot (row + legend) to ~0.55 opacity so the local-only ring stays the state that draws the eye. * Reshape commit diffs around the existing Changes view * Fix diff tab migration complexity after rebase * Address diff tab review findings * Clean up diff tab review interfaces * Collapse commit diffs into the existing view * Load commits when expanded --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
133
packages/protocol/src/messages.checkout-commit-file-diff.test.ts
Normal file
133
packages/protocol/src/messages.checkout-commit-file-diff.test.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import {
|
||||
CheckoutCommitFileDiffRequestSchema,
|
||||
CheckoutCommitFileDiffResponseSchema,
|
||||
SessionInboundMessageSchema,
|
||||
SessionOutboundMessageSchema,
|
||||
} from "./messages.js";
|
||||
|
||||
describe("checkout.commits.file_diff schemas", () => {
|
||||
test("parses a valid request", () => {
|
||||
expect(
|
||||
CheckoutCommitFileDiffRequestSchema.parse({
|
||||
type: "checkout.commits.file_diff.request",
|
||||
cwd: "/tmp/repo",
|
||||
sha: "1111111111111111111111111111111111111111",
|
||||
path: "src/a.ts",
|
||||
requestId: "request-file-diff",
|
||||
}),
|
||||
).toEqual({
|
||||
type: "checkout.commits.file_diff.request",
|
||||
cwd: "/tmp/repo",
|
||||
sha: "1111111111111111111111111111111111111111",
|
||||
path: "src/a.ts",
|
||||
requestId: "request-file-diff",
|
||||
});
|
||||
});
|
||||
|
||||
test("parses a valid response with a populated ParsedDiffFile", () => {
|
||||
const payload = {
|
||||
cwd: "/tmp/repo",
|
||||
sha: "1111111111111111111111111111111111111111",
|
||||
path: "src/a.ts",
|
||||
file: {
|
||||
path: "src/a.ts",
|
||||
isNew: false,
|
||||
isDeleted: false,
|
||||
additions: 2,
|
||||
deletions: 1,
|
||||
hunks: [
|
||||
{
|
||||
oldStart: 1,
|
||||
oldCount: 2,
|
||||
newStart: 1,
|
||||
newCount: 3,
|
||||
lines: [
|
||||
{ type: "header" as const, content: "@@ -1,2 +1,3 @@" },
|
||||
{ type: "context" as const, content: "first" },
|
||||
{ type: "remove" as const, content: "old" },
|
||||
{ type: "add" as const, content: "new1" },
|
||||
{ type: "add" as const, content: "new2" },
|
||||
],
|
||||
},
|
||||
],
|
||||
status: "ok" as const,
|
||||
},
|
||||
error: null,
|
||||
requestId: "request-file-diff",
|
||||
};
|
||||
|
||||
const parsed = CheckoutCommitFileDiffResponseSchema.parse({
|
||||
type: "checkout.commits.file_diff.response",
|
||||
payload,
|
||||
});
|
||||
|
||||
expect(parsed.payload).toEqual(payload);
|
||||
expect(parsed.payload.file?.hunks[0]?.lines).toHaveLength(5);
|
||||
});
|
||||
|
||||
test("parses a valid response with a null file", () => {
|
||||
const payload = {
|
||||
cwd: "/tmp/repo",
|
||||
sha: "1111111111111111111111111111111111111111",
|
||||
path: "src/missing.ts",
|
||||
file: null,
|
||||
error: null,
|
||||
requestId: "request-file-diff",
|
||||
};
|
||||
|
||||
expect(
|
||||
CheckoutCommitFileDiffResponseSchema.parse({
|
||||
type: "checkout.commits.file_diff.response",
|
||||
payload,
|
||||
}).payload,
|
||||
).toEqual(payload);
|
||||
});
|
||||
|
||||
test("accepts a null file with an error payload", () => {
|
||||
const payload = {
|
||||
cwd: "/tmp/repo",
|
||||
sha: "1111111111111111111111111111111111111111",
|
||||
path: "src/a.ts",
|
||||
file: null,
|
||||
error: { code: "UNKNOWN" as const, message: "boom" },
|
||||
requestId: "request-file-diff",
|
||||
};
|
||||
|
||||
expect(
|
||||
CheckoutCommitFileDiffResponseSchema.parse({
|
||||
type: "checkout.commits.file_diff.response",
|
||||
payload,
|
||||
}).payload,
|
||||
).toEqual(payload);
|
||||
});
|
||||
|
||||
test("parses the request through the inbound message union", () => {
|
||||
expect(
|
||||
SessionInboundMessageSchema.parse({
|
||||
type: "checkout.commits.file_diff.request",
|
||||
cwd: "/tmp/repo",
|
||||
sha: "1111111111111111111111111111111111111111",
|
||||
path: "src/a.ts",
|
||||
requestId: "request-file-diff",
|
||||
}),
|
||||
).toMatchObject({ type: "checkout.commits.file_diff.request" });
|
||||
});
|
||||
|
||||
test("parses the response through the outbound message union", () => {
|
||||
expect(
|
||||
SessionOutboundMessageSchema.parse({
|
||||
type: "checkout.commits.file_diff.response",
|
||||
payload: {
|
||||
cwd: "/tmp/repo",
|
||||
sha: "1111111111111111111111111111111111111111",
|
||||
path: "src/a.ts",
|
||||
file: null,
|
||||
error: null,
|
||||
requestId: "request-file-diff",
|
||||
},
|
||||
}),
|
||||
).toMatchObject({ type: "checkout.commits.file_diff.response" });
|
||||
});
|
||||
});
|
||||
133
packages/protocol/src/messages.checkout-commits.test.ts
Normal file
133
packages/protocol/src/messages.checkout-commits.test.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import {
|
||||
CheckoutCommitsListRequestSchema,
|
||||
CheckoutCommitsListResponseSchema,
|
||||
ServerInfoStatusPayloadSchema,
|
||||
SessionInboundMessageSchema,
|
||||
SessionOutboundMessageSchema,
|
||||
} from "./messages.js";
|
||||
|
||||
describe("checkout.commits.list schemas", () => {
|
||||
test("parses a valid request", () => {
|
||||
expect(
|
||||
CheckoutCommitsListRequestSchema.parse({
|
||||
type: "checkout.commits.list.request",
|
||||
cwd: "/tmp/repo",
|
||||
requestId: "request-commits",
|
||||
}),
|
||||
).toEqual({
|
||||
type: "checkout.commits.list.request",
|
||||
cwd: "/tmp/repo",
|
||||
requestId: "request-commits",
|
||||
});
|
||||
});
|
||||
|
||||
test("parses a valid response with local-only and remote commits", () => {
|
||||
const payload = {
|
||||
cwd: "/tmp/repo",
|
||||
baseRef: "main",
|
||||
commits: [
|
||||
{
|
||||
sha: "1111111111111111111111111111111111111111",
|
||||
shortSha: "1111111",
|
||||
subject: "Add feature",
|
||||
authorName: "Ada",
|
||||
authorDate: "2026-06-13T10:00:00.000Z",
|
||||
isOnRemote: true,
|
||||
files: [
|
||||
{ path: "src/a.ts", additions: 10, deletions: 2, status: "modified" },
|
||||
{ path: "src/b.ts", additions: 5, deletions: 0, status: "added" },
|
||||
],
|
||||
},
|
||||
{
|
||||
sha: "2222222222222222222222222222222222222222",
|
||||
shortSha: "2222222",
|
||||
subject: "Local only work",
|
||||
authorName: "Ada",
|
||||
authorDate: "2026-06-13T11:00:00.000Z",
|
||||
isOnRemote: false,
|
||||
files: [{ path: "src/c.ts", additions: 1, deletions: 1 }],
|
||||
},
|
||||
],
|
||||
error: null,
|
||||
requestId: "request-commits",
|
||||
};
|
||||
|
||||
const parsed = CheckoutCommitsListResponseSchema.parse({
|
||||
type: "checkout.commits.list.response",
|
||||
payload,
|
||||
});
|
||||
|
||||
expect(parsed.payload).toEqual(payload);
|
||||
expect(parsed.payload.commits[0]?.isOnRemote).toBe(true);
|
||||
expect(parsed.payload.commits[1]?.isOnRemote).toBe(false);
|
||||
expect(parsed.payload.commits[1]?.files[0]?.status).toBeUndefined();
|
||||
});
|
||||
|
||||
test("accepts a null baseRef and an error payload", () => {
|
||||
const payload = {
|
||||
cwd: "/tmp/repo",
|
||||
baseRef: null,
|
||||
commits: [],
|
||||
error: { code: "NOT_GIT_REPO" as const, message: "not a repo" },
|
||||
requestId: "request-commits",
|
||||
};
|
||||
|
||||
expect(
|
||||
CheckoutCommitsListResponseSchema.parse({
|
||||
type: "checkout.commits.list.response",
|
||||
payload,
|
||||
}).payload,
|
||||
).toEqual(payload);
|
||||
});
|
||||
|
||||
test("parses the request through the inbound message union", () => {
|
||||
expect(
|
||||
SessionInboundMessageSchema.parse({
|
||||
type: "checkout.commits.list.request",
|
||||
cwd: "/tmp/repo",
|
||||
requestId: "request-commits",
|
||||
}),
|
||||
).toMatchObject({ type: "checkout.commits.list.request" });
|
||||
});
|
||||
|
||||
test("parses the response through the outbound message union", () => {
|
||||
expect(
|
||||
SessionOutboundMessageSchema.parse({
|
||||
type: "checkout.commits.list.response",
|
||||
payload: {
|
||||
cwd: "/tmp/repo",
|
||||
baseRef: "main",
|
||||
commits: [],
|
||||
error: null,
|
||||
requestId: "request-commits",
|
||||
},
|
||||
}),
|
||||
).toMatchObject({ type: "checkout.commits.list.response" });
|
||||
});
|
||||
|
||||
test("accepts the commitsList server_info feature flag", () => {
|
||||
expect(
|
||||
ServerInfoStatusPayloadSchema.parse({
|
||||
status: "server_info",
|
||||
serverId: "srv_test",
|
||||
features: {
|
||||
commitsList: true,
|
||||
},
|
||||
}).features,
|
||||
).toEqual({ commitsList: true });
|
||||
});
|
||||
|
||||
test("still parses server_info without the commitsList feature flag", () => {
|
||||
expect(
|
||||
ServerInfoStatusPayloadSchema.parse({
|
||||
status: "server_info",
|
||||
serverId: "srv_test",
|
||||
features: {
|
||||
providersSnapshot: true,
|
||||
},
|
||||
}).features,
|
||||
).toEqual({ providersSnapshot: true });
|
||||
});
|
||||
});
|
||||
@@ -1644,6 +1644,37 @@ export const CheckoutGithubSetAutoMergeRequestSchema = z.object({
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
const CheckoutCommitFileSchema = z.object({
|
||||
path: z.string(),
|
||||
additions: z.number(),
|
||||
deletions: z.number(),
|
||||
status: z.enum(["added", "modified", "deleted", "renamed"]).optional(),
|
||||
});
|
||||
|
||||
const CheckoutCommitSchema = z.object({
|
||||
sha: z.string(),
|
||||
shortSha: z.string(),
|
||||
subject: z.string(),
|
||||
authorName: z.string(),
|
||||
authorDate: z.string(), // ISO 8601
|
||||
isOnRemote: z.boolean(), // false = local-only (unpushed)
|
||||
files: z.array(CheckoutCommitFileSchema),
|
||||
});
|
||||
|
||||
export const CheckoutCommitsListRequestSchema = z.object({
|
||||
type: z.literal("checkout.commits.list.request"),
|
||||
cwd: z.string(),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const CheckoutCommitFileDiffRequestSchema = z.object({
|
||||
type: z.literal("checkout.commits.file_diff.request"),
|
||||
cwd: z.string(),
|
||||
sha: z.string(),
|
||||
path: z.string(),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
const GitHubRepoSegmentSchema = z.string().regex(/^[A-Za-z0-9._-]+$/);
|
||||
|
||||
export const CheckoutGithubGetCheckDetailsRequestSchema = z.object({
|
||||
@@ -2246,6 +2277,8 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
||||
CheckoutPrCreateRequestSchema,
|
||||
CheckoutPrMergeRequestSchema,
|
||||
CheckoutGithubSetAutoMergeRequestSchema,
|
||||
CheckoutCommitsListRequestSchema,
|
||||
CheckoutCommitFileDiffRequestSchema,
|
||||
CheckoutGithubGetCheckDetailsRequestSchema,
|
||||
CheckoutPrStatusRequestSchema,
|
||||
PullRequestTimelineRequestSchema,
|
||||
@@ -2523,6 +2556,8 @@ export const ServerInfoStatusPayloadSchema = z
|
||||
workspaceGithubRepositorySearch: z.boolean().optional(),
|
||||
// COMPAT(projectCreateDirectory): added in v0.1.108, remove gate after 2027-01-15.
|
||||
projectCreateDirectory: z.boolean().optional(),
|
||||
// COMPAT(commitsList): added in v0.1.110, remove gate after 2027-01-16.
|
||||
commitsList: z.boolean().optional(),
|
||||
// COMPAT(providerRemoval): added in v0.1.105, drop the gate when floor >= v0.1.105.
|
||||
providerRemoval: z.boolean().optional(),
|
||||
})
|
||||
@@ -3794,6 +3829,31 @@ export const CheckoutGithubSetAutoMergeResponseSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const CheckoutCommitsListResponseSchema = z.object({
|
||||
type: z.literal("checkout.commits.list.response"),
|
||||
payload: z.object({
|
||||
cwd: z.string(),
|
||||
baseRef: z.string().nullable(),
|
||||
commits: z.array(CheckoutCommitSchema),
|
||||
error: CheckoutErrorSchema.nullable(),
|
||||
requestId: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const CheckoutCommitFileDiffResponseSchema = z.object({
|
||||
type: z.literal("checkout.commits.file_diff.response"),
|
||||
payload: z.object({
|
||||
cwd: z.string(),
|
||||
sha: z.string(),
|
||||
path: z.string(),
|
||||
// null when the file is absent from the commit or carries no textual diff
|
||||
// (e.g. binary-only changes).
|
||||
file: ParsedDiffFileSchema.nullable(),
|
||||
error: CheckoutErrorSchema.nullable(),
|
||||
requestId: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
const CheckoutGithubCheckAnnotationSchema = z.object({
|
||||
path: z.string().optional(),
|
||||
startLine: z.number().optional(),
|
||||
@@ -4571,6 +4631,8 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
||||
CheckoutPrCreateResponseSchema,
|
||||
CheckoutPrMergeResponseSchema,
|
||||
CheckoutGithubSetAutoMergeResponseSchema,
|
||||
CheckoutCommitsListResponseSchema,
|
||||
CheckoutCommitFileDiffResponseSchema,
|
||||
CheckoutGithubGetCheckDetailsResponseSchema,
|
||||
CheckoutPrStatusResponseSchema,
|
||||
PullRequestTimelineResponseSchema,
|
||||
@@ -4883,6 +4945,13 @@ export type CheckoutPushRequest = z.infer<typeof CheckoutPushRequestSchema>;
|
||||
export type CheckoutPushResponse = z.infer<typeof CheckoutPushResponseSchema>;
|
||||
export type CheckoutRefreshRequest = z.infer<typeof CheckoutRefreshRequestSchema>;
|
||||
export type CheckoutRefreshResponse = z.infer<typeof CheckoutRefreshResponseSchema>;
|
||||
export type CheckoutCommitFile = z.infer<typeof CheckoutCommitFileSchema>;
|
||||
export type CheckoutCommit = z.infer<typeof CheckoutCommitSchema>;
|
||||
export type CheckoutCommitsListRequest = z.infer<typeof CheckoutCommitsListRequestSchema>;
|
||||
export type CheckoutCommitsListResponse = z.infer<typeof CheckoutCommitsListResponseSchema>;
|
||||
export type CheckoutCommitFileDiffRequest = z.infer<typeof CheckoutCommitFileDiffRequestSchema>;
|
||||
export type CheckoutCommitFileDiffResponse = z.infer<typeof CheckoutCommitFileDiffResponseSchema>;
|
||||
export type ParsedDiffFile = z.infer<typeof ParsedDiffFileSchema>;
|
||||
export type CheckoutPrCreateRequest = z.infer<typeof CheckoutPrCreateRequestSchema>;
|
||||
export type CheckoutPrCreateResponse = z.infer<typeof CheckoutPrCreateResponseSchema>;
|
||||
export type CheckoutPrMergeRequest = z.infer<typeof CheckoutPrMergeRequestSchema>;
|
||||
|
||||
Reference in New Issue
Block a user