sketch/webui: add untracked files notification to diff view
Add warning in diff view about untracked files.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s45fbbdf5b3d703e4k
diff --git a/webui/src/web-components/demo/mock-git-data-service.ts b/webui/src/web-components/demo/mock-git-data-service.ts
index 6b5df65..3316e1f 100644
--- a/webui/src/web-components/demo/mock-git-data-service.ts
+++ b/webui/src/web-components/demo/mock-git-data-service.ts
@@ -561,4 +561,15 @@
);
// Return void as per interface
}
+
+ async getUntrackedFiles(): Promise<string[]> {
+ console.log("[MockGitDataService] Getting untracked files");
+ // Return some mock untracked files for demo purposes
+ return [
+ "temp.txt",
+ "debug.log",
+ "config/local.json",
+ "node_modules/.cache/something"
+ ];
+ }
}
diff --git a/webui/src/web-components/git-data-service.ts b/webui/src/web-components/git-data-service.ts
index 6ff89af..bfe306e 100644
--- a/webui/src/web-components/git-data-service.ts
+++ b/webui/src/web-components/git-data-service.ts
@@ -65,6 +65,12 @@
* @returns List of changed files
*/
getUnstagedChanges(from?: string): Promise<GitDiffFile[]>;
+
+ /**
+ * Fetches list of untracked files in the repository
+ * @returns List of untracked file paths
+ */
+ getUntrackedFiles(): Promise<string[]>;
}
/**
@@ -232,4 +238,22 @@
throw error;
}
}
+
+ async getUntrackedFiles(): Promise<string[]> {
+ try {
+ const response = await fetch("git/untracked");
+
+ if (!response.ok) {
+ throw new Error(
+ `Failed to fetch untracked files: ${response.statusText}`,
+ );
+ }
+
+ const data = await response.json();
+ return data.untracked_files || [];
+ } catch (error) {
+ console.error("Error fetching untracked files:", error);
+ throw error;
+ }
+ }
}
diff --git a/webui/src/web-components/sketch-diff2-view.ts b/webui/src/web-components/sketch-diff2-view.ts
index 69763a8..04f3fc2 100644
--- a/webui/src/web-components/sketch-diff2-view.ts
+++ b/webui/src/web-components/sketch-diff2-view.ts
@@ -161,6 +161,12 @@
@state()
private viewMode: "all" | "single" = "all";
+ @state()
+ private untrackedFiles: string[] = [];
+
+ @state()
+ private showUntrackedPopup: boolean = false;
+
@property({ attribute: false, type: Object })
gitService!: GitDataService;
@@ -279,6 +285,24 @@
} else {
this.loadDiffData();
}
+
+ // Add click listener to close popup when clicking outside
+ document.addEventListener("click", this.handleDocumentClick.bind(this));
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+ document.removeEventListener("click", this.handleDocumentClick.bind(this));
+ }
+
+ handleDocumentClick(event: Event) {
+ if (this.showUntrackedPopup) {
+ const target = event.target as HTMLElement;
+ // Check if click is outside the popup and button
+ if (!target.closest(".relative")) {
+ this.showUntrackedPopup = false;
+ }
+ }
}
// Toggle hideUnchangedRegions setting for a specific file
@@ -309,6 +333,7 @@
.gitService="${this.gitService}"
@range-change="${this.handleRangeChange}"
></sketch-diff-range-picker>
+ ${this.renderUntrackedFilesNotification()}
<div class="flex-1"></div>
${this.renderFileSelector()}
</div>
@@ -346,6 +371,89 @@
`;
}
+ renderUntrackedFilesNotification() {
+ if (!this.untrackedFiles || this.untrackedFiles.length === 0) {
+ return "";
+ }
+
+ const fileCount = this.untrackedFiles.length;
+ const fileCountText =
+ fileCount === 1 ? "1 untracked file" : `${fileCount} untracked files`;
+
+ return html`
+ <div class="relative">
+ <button
+ class="flex items-center gap-2 px-3 py-1.5 text-sm bg-gray-100 text-gray-700 border border-gray-300 rounded hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-500"
+ @click="${this.toggleUntrackedFilesPopup}"
+ type="button"
+ >
+ ${fileCount} untracked
+ <svg
+ class="w-4 h-4"
+ fill="none"
+ stroke="currentColor"
+ viewBox="0 0 24 24"
+ >
+ <path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
+ />
+ </svg>
+ </button>
+
+ ${this.showUntrackedPopup
+ ? html`
+ <div
+ class="absolute top-full left-0 mt-2 w-80 bg-white border border-gray-300 rounded-lg shadow-lg z-50"
+ >
+ <div class="p-4">
+ <div class="flex items-start gap-3 mb-3">
+ <svg
+ class="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5"
+ fill="none"
+ stroke="currentColor"
+ viewBox="0 0 24 24"
+ >
+ <path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
+ />
+ </svg>
+ <div class="flex-1">
+ <div class="font-medium text-gray-900 mb-1">
+ ${fileCountText}
+ </div>
+ <div class="text-sm text-gray-600 mb-3">
+ These files are not tracked by git. They will be lost if the session ends now. The agent typically does not add files to git until it is ready for feedback.
+ </div>
+ </div>
+ </div>
+
+ <div class="max-h-32 overflow-y-auto">
+ <div class="text-sm text-gray-700">
+ ${this.untrackedFiles.map(
+ (file) => html`
+ <div
+ class="py-1 px-2 hover:bg-gray-100 rounded font-mono text-xs"
+ >
+ ${file}
+ </div>
+ `,
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ `
+ : ""}
+ </div>
+ `;
+ }
+
renderDiffContent() {
if (this.loading) {
return html`<div class="flex items-center justify-center h-full">
@@ -398,6 +506,14 @@
this.files = [];
}
+ // Load untracked files for notification
+ try {
+ this.untrackedFiles = await this.gitService.getUntrackedFiles();
+ } catch (error) {
+ console.error("Error loading untracked files:", error);
+ this.untrackedFiles = [];
+ }
+
// Load content for all files
if (this.files.length > 0) {
// Initialize expand states for new files (default to collapsed)
@@ -773,6 +889,10 @@
this.requestUpdate();
}
+ toggleUntrackedFilesPopup() {
+ this.showUntrackedPopup = !this.showUntrackedPopup;
+ }
+
/**
* Get display name for file in the selector
*/