clay-server 4.0.0-beta.2 → 4.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/background-task-timing.js +105 -0
- package/lib/capsule-display-floor.js +51 -0
- package/lib/capsule-frame-server.js +255 -0
- package/lib/capsule-mate-turn.js +105 -0
- package/lib/capsule-pig-logic.js +300 -0
- package/lib/capsule-server-runtimes.js +39 -0
- package/lib/capsules/pig/display.js +198 -0
- package/lib/capsules/pig/manifest.json +9 -0
- package/lib/capsules/pig/ui.json +84 -0
- package/lib/config.js +25 -1
- package/lib/daemon-projects.js +13 -8
- package/lib/daemon.js +127 -14
- package/lib/git-cli.js +95 -1
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +240 -0
- package/lib/mate-knowledge-mcp-server.js +151 -0
- package/lib/mate-knowledge-migration.js +283 -0
- package/lib/mate-knowledge-service.js +488 -0
- package/lib/mate-knowledge-sync.js +482 -0
- package/lib/migrate-single-user.js +2 -0
- package/lib/notes-lifecycle.js +149 -0
- package/lib/notes.js +60 -3
- package/lib/os-users.js +3 -1
- package/lib/project-capsule-catalog.js +9 -1
- package/lib/project-capsule-turn.js +96 -0
- package/lib/project-connection.js +14 -1
- package/lib/project-http.js +15 -1
- package/lib/project-knowledge.js +22 -0
- package/lib/project-log-context.js +172 -0
- package/lib/project-log-feedback-delivery.js +118 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-context-state.js +42 -0
- package/lib/project-logs-mcp-server.js +328 -0
- package/lib/project-logs-query.js +185 -0
- package/lib/project-logs-root.js +75 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +463 -0
- package/lib/project-logs-snapshot.js +205 -0
- package/lib/project-logs-store.js +495 -0
- package/lib/project-logs-versioning.js +132 -0
- package/lib/project-logs.js +400 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +22 -0
- package/lib/project-message-delivery.js +70 -0
- package/lib/project-pair-lifecycle.js +460 -0
- package/lib/project-session-handoff.js +1 -1
- package/lib/project-session-notes.js +64 -13
- package/lib/project-session-pair.js +159 -146
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +58 -13
- package/lib/project-user-message.js +53 -4
- package/lib/project-vendor-login.js +20 -3
- package/lib/project-worker-permission.js +430 -0
- package/lib/project-worker-proposal.js +208 -62
- package/lib/project.js +178 -6
- package/lib/public/app.js +58 -35
- package/lib/public/css/admin.css +2 -2
- package/lib/public/css/capsule-ui.css +38 -0
- package/lib/public/css/command-palette.css +19 -9
- package/lib/public/css/git-placard.css +154 -0
- package/lib/public/css/home-hub.css +4 -0
- package/lib/public/css/home-session-actions.css +66 -0
- package/lib/public/css/home-sidebar.css +66 -0
- package/lib/public/css/input.css +163 -28
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +52 -0
- package/lib/public/css/project-logs.css +198 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/session-actions.css +16 -101
- package/lib/public/css/sidebar.css +103 -0
- package/lib/public/css/sticky-notes.css +237 -229
- package/lib/public/css/worker-proposal.css +34 -1
- package/lib/public/index.html +45 -11
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +109 -4
- package/lib/public/modules/app-home-hub.js +19 -7
- package/lib/public/modules/app-message-cards.js +2 -1
- package/lib/public/modules/app-messages.js +84 -7
- package/lib/public/modules/app-misc.js +2 -14
- package/lib/public/modules/app-notifications.js +11 -7
- package/lib/public/modules/app-panels.js +46 -4
- package/lib/public/modules/app-projects.js +13 -8
- package/lib/public/modules/app-rendering.js +1 -0
- package/lib/public/modules/background-tasks-ui.js +139 -16
- package/lib/public/modules/capsule-preference.js +44 -0
- package/lib/public/modules/chat-bubble-renderer.js +1 -1
- package/lib/public/modules/clay-log-links.js +70 -0
- package/lib/public/modules/command-palette.js +52 -15
- package/lib/public/modules/context-sources.js +0 -3
- package/lib/public/modules/git-agent-sessions.js +85 -0
- package/lib/public/modules/git-panel.js +111 -84
- package/lib/public/modules/git-placard.js +161 -0
- package/lib/public/modules/home-chat-empty-state.js +39 -0
- package/lib/public/modules/home-chat-identity.js +9 -0
- package/lib/public/modules/home-conversations-sheet.js +102 -48
- package/lib/public/modules/home-mate-chat.js +15 -35
- package/lib/public/modules/home-session-actions.js +6 -0
- package/lib/public/modules/home-sidebar-chat-list.js +101 -41
- package/lib/public/modules/home-sidebar.js +7 -0
- package/lib/public/modules/home-tool-frame.js +182 -0
- package/lib/public/modules/home-tools.js +138 -1
- package/lib/public/modules/input.js +38 -7
- package/lib/public/modules/markdown.js +2 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/message-delivery.js +97 -0
- package/lib/public/modules/paste-modal.js +84 -0
- package/lib/public/modules/project-logs-render.js +405 -0
- package/lib/public/modules/project-logs.js +461 -0
- package/lib/public/modules/project-removal-target.js +26 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/session-actions.js +29 -105
- package/lib/public/modules/session-hierarchy.js +54 -0
- package/lib/public/modules/sidebar-mobile.js +40 -15
- package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
- package/lib/public/modules/sidebar-sessions.js +77 -11
- package/lib/public/modules/sidebar.js +17 -5
- package/lib/public/modules/split-group-helpers.js +8 -0
- package/lib/public/modules/split-view.js +5 -2
- package/lib/public/modules/sticky-note-markdown.js +5 -0
- package/lib/public/modules/sticky-notes-browser.js +326 -0
- package/lib/public/modules/sticky-notes-card.js +360 -0
- package/lib/public/modules/sticky-notes-editor.js +291 -0
- package/lib/public/modules/sticky-notes-shared.js +90 -0
- package/lib/public/modules/sticky-notes.js +170 -946
- package/lib/public/modules/tool-palette-order.js +143 -0
- package/lib/public/modules/tool-palette-overlays.js +159 -0
- package/lib/public/modules/tool-palette.js +21 -188
- package/lib/public/modules/tools.js +21 -1
- package/lib/public/modules/update-snooze.js +169 -0
- package/lib/public/modules/user-settings.js +4 -0
- package/lib/public/modules/worker-pane-lock.js +176 -0
- package/lib/public/modules/worker-proposal-state.js +16 -0
- package/lib/public/modules/worker-proposal.js +49 -12
- package/lib/public/style.css +3 -1
- package/lib/sdk-bridge.js +132 -46
- package/lib/sdk-message-processor.js +60 -14
- package/lib/server-experimental-settings.js +49 -0
- package/lib/server-global-ws.js +11 -0
- package/lib/server-home-chat-events.js +31 -1
- package/lib/server-home-chat.js +27 -4
- package/lib/server-home-clay-entry.js +1 -1
- package/lib/server-home-clay-session-links.js +34 -5
- package/lib/server-settings.js +5 -0
- package/lib/server-tools.js +127 -9
- package/lib/server.js +124 -18
- package/lib/session-driver-eligibility.js +38 -0
- package/lib/session-driver-orchestration.js +31 -0
- package/lib/session-hygiene.js +3 -0
- package/lib/session-notes-mcp-server.js +25 -5
- package/lib/session-pair-factory.js +247 -0
- package/lib/session-pair-mcp-server.js +46 -8
- package/lib/session-pair-prompts.js +79 -0
- package/lib/session-pair-turn-control.js +153 -0
- package/lib/session-provenance.js +119 -0
- package/lib/session-spawn-mcp-server.js +1 -1
- package/lib/session-split-groups.js +4 -1
- package/lib/session-title-policy.js +60 -0
- package/lib/session-visibility.js +39 -0
- package/lib/sessions.js +61 -11
- package/lib/tools-registry.js +89 -10
- package/lib/update-snooze.js +437 -0
- package/lib/users-experimental-preferences.js +30 -0
- package/lib/users-permissions.js +2 -2
- package/lib/users.js +22 -16
- package/lib/workspace-query-access.js +102 -0
- package/lib/workspace-query-service.js +24 -17
- package/lib/worktree.js +25 -38
- package/lib/ws-schema.js +38 -7
- package/lib/yoke/adapters/claude-worker.js +16 -6
- package/lib/yoke/adapters/claude.js +81 -4
- package/lib/yoke/adapters/codex.js +63 -15
- package/lib/yoke/codex-background-tasks.js +8 -2
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +3 -3
|
@@ -6,6 +6,7 @@ import { showHomeHub, minimizeHomeHub } from './app-home-hub.js';
|
|
|
6
6
|
import { openHomeConversation } from './home-mate-chat.js';
|
|
7
7
|
import { startSearchClayChat, attachSearchClayChat, detachSearchClayChat, getSearchClayChatSummary } from './search-clay-chat.js';
|
|
8
8
|
import { getWs } from './ws-ref.js';
|
|
9
|
+
import { openProjectLog } from './project-logs.js';
|
|
9
10
|
|
|
10
11
|
function formatRelativeDate(ts) {
|
|
11
12
|
if (!ts) return "";
|
|
@@ -27,6 +28,7 @@ var ctx;
|
|
|
27
28
|
var paletteEl = null, inputEl = null, resultsEl = null, footerEl = null;
|
|
28
29
|
var activeIndex = -1, items = [], debounceTimer = null, abortCtrl = null;
|
|
29
30
|
var pendingNav = null, cachedHomeData = [], cachedVersion = null;
|
|
31
|
+
var pendingLogNav = null;
|
|
30
32
|
var searchResults = [], searchQuery = "", searchPending = false, chatMode = false;
|
|
31
33
|
|
|
32
34
|
export function initCommandPalette(value) {
|
|
@@ -52,43 +54,62 @@ export function initCommandPalette(value) {
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
function handleClaySessionLinkClick(event) {
|
|
55
|
-
var link = event.target.closest(".clayos-session-link");
|
|
57
|
+
var link = event.target.closest(".clayos-session-link, .clayos-log-link");
|
|
56
58
|
if (!link || link.classList.contains("is-loading")) return;
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
event.stopPropagation();
|
|
61
|
+
if (link.classList.contains("clayos-log-link") && !link.closest(".search-clay-chat, #home-mate-chat")) {
|
|
62
|
+
openProjectLog(link.dataset.logRef);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
57
65
|
var ws = getWs();
|
|
58
66
|
if (!ws || ws.readyState !== 1) {
|
|
59
67
|
link.classList.add("is-error");
|
|
60
|
-
link.title = "Reconnect to open this
|
|
68
|
+
link.title = "Reconnect to open this item";
|
|
61
69
|
return;
|
|
62
70
|
}
|
|
63
|
-
var
|
|
64
|
-
|
|
71
|
+
var isLog = link.classList.contains("clayos-log-link");
|
|
72
|
+
var requestId = "clayos-" + (isLog ? "log-" : "session-") + Date.now() + "-" + Math.random().toString(36).slice(2, 9);
|
|
73
|
+
if (isLog) link.dataset.logRequest = requestId;
|
|
74
|
+
else link.dataset.sessionRequest = requestId;
|
|
65
75
|
link.classList.remove("is-error");
|
|
66
76
|
link.classList.add("is-loading");
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
var request = { requestId: requestId, surface: link.closest(".search-clay-chat") ? "search" : "home" };
|
|
78
|
+
if (isLog) {
|
|
79
|
+
request.type = "home_clay_log_resolve";
|
|
80
|
+
request.ref = link.dataset.logRef;
|
|
81
|
+
} else {
|
|
82
|
+
request.type = "home_clay_session_resolve";
|
|
83
|
+
request.sessionRef = link.dataset.sessionRef;
|
|
84
|
+
}
|
|
85
|
+
ws.send(JSON.stringify(request));
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
export function handleClaySessionTarget(msg) {
|
|
76
|
-
if (!msg || msg.type !== "home_clay_session_target") return false;
|
|
77
|
-
var
|
|
89
|
+
if (!msg || (msg.type !== "home_clay_session_target" && msg.type !== "home_clay_log_target")) return false;
|
|
90
|
+
var isLog = msg.type === "home_clay_log_target";
|
|
91
|
+
var links = document.querySelectorAll(isLog ? ".clayos-log-link[data-log-request]" : ".clayos-session-link[data-session-request]");
|
|
78
92
|
var link = null;
|
|
79
|
-
for (var i = 0; i < links.length; i++)
|
|
93
|
+
for (var i = 0; i < links.length; i++) {
|
|
94
|
+
if ((isLog ? links[i].dataset.logRequest : links[i].dataset.sessionRequest) === msg.requestId) link = links[i];
|
|
95
|
+
}
|
|
80
96
|
if (link) {
|
|
81
97
|
link.classList.remove("is-loading");
|
|
82
|
-
delete link.dataset.
|
|
98
|
+
if (isLog) delete link.dataset.logRequest;
|
|
99
|
+
else delete link.dataset.sessionRequest;
|
|
83
100
|
}
|
|
84
101
|
if (msg.status !== "ready" || !msg.target) {
|
|
85
102
|
if (link) {
|
|
86
103
|
link.classList.add("is-error");
|
|
87
|
-
link.title = msg.error || "
|
|
104
|
+
link.title = msg.error || "Item not available";
|
|
88
105
|
}
|
|
89
106
|
return true;
|
|
90
107
|
}
|
|
91
108
|
closeCommandPalette();
|
|
109
|
+
if (isLog) {
|
|
110
|
+
navigateToLog(msg.target);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
92
113
|
if (msg.target.isMate && msg.target.mateId && msg.target.homeSessionId) {
|
|
93
114
|
showHomeHub();
|
|
94
115
|
openHomeConversation(msg.target.mateId, msg.target.homeSessionId);
|
|
@@ -129,6 +150,11 @@ export function closeCommandPalette() {
|
|
|
129
150
|
export function setPaletteVersion(version) { cachedVersion = version; }
|
|
130
151
|
|
|
131
152
|
export function handlePaletteSessionSwitch() {
|
|
153
|
+
if (pendingLogNav && ctx.currentSlug && ctx.currentSlug() === pendingLogNav.projectSlug) {
|
|
154
|
+
var logNav = pendingLogNav;
|
|
155
|
+
pendingLogNav = null;
|
|
156
|
+
openProjectLog(logNav.ref);
|
|
157
|
+
}
|
|
132
158
|
if (!pendingNav) return;
|
|
133
159
|
var nav = pendingNav;
|
|
134
160
|
pendingNav = null;
|
|
@@ -138,6 +164,17 @@ export function handlePaletteSessionSwitch() {
|
|
|
138
164
|
}
|
|
139
165
|
}
|
|
140
166
|
|
|
167
|
+
function navigateToLog(target) {
|
|
168
|
+
closeCommandPalette();
|
|
169
|
+
minimizeHomeHub();
|
|
170
|
+
if (ctx.currentSlug && ctx.currentSlug() === target.projectSlug) {
|
|
171
|
+
openProjectLog(target.ref);
|
|
172
|
+
} else {
|
|
173
|
+
pendingLogNav = { projectSlug: target.projectSlug, ref: target.ref };
|
|
174
|
+
ctx.switchProject(target.projectSlug);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
141
178
|
function buildDOM() {
|
|
142
179
|
paletteEl = document.createElement("div");
|
|
143
180
|
paletteEl.className = "cmd-palette hidden";
|
|
@@ -230,10 +230,8 @@ function getActiveSourceRowsHTML() {
|
|
|
230
230
|
function renderChips() {
|
|
231
231
|
// Update add button — show badge count when sources are active
|
|
232
232
|
var addBtn = document.getElementById("context-sources-add");
|
|
233
|
-
var labelSpan = addBtn.querySelector(".ctx-label");
|
|
234
233
|
var existingBadge = addBtn.querySelector(".ctx-badge");
|
|
235
234
|
if (activeSourceIds.size > 0) {
|
|
236
|
-
if (labelSpan) labelSpan.style.display = "none";
|
|
237
235
|
if (!existingBadge) {
|
|
238
236
|
existingBadge = document.createElement("span");
|
|
239
237
|
existingBadge.className = "ctx-badge";
|
|
@@ -251,7 +249,6 @@ function renderChips() {
|
|
|
251
249
|
}
|
|
252
250
|
addBtn.removeAttribute("title");
|
|
253
251
|
} else {
|
|
254
|
-
if (labelSpan) { labelSpan.style.display = ""; }
|
|
255
252
|
if (existingBadge) existingBadge.remove();
|
|
256
253
|
addBtn.setAttribute("data-tip", "Add context sources");
|
|
257
254
|
addBtn.removeAttribute("data-tip-html");
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// git-agent-sessions.js — agent handoffs launched from the Git surface.
|
|
2
|
+
//
|
|
3
|
+
// Extracted from git-panel.js so that module stays under the size limit while
|
|
4
|
+
// the Git data path (one status reading, one summary reading, one placard)
|
|
5
|
+
// remains in a single place. This module owns only the "hand this over to a
|
|
6
|
+
// focused agent session" concern, plus the small path/title helpers those
|
|
7
|
+
// prompts and the panel's own file rows share, so neither is defined twice.
|
|
8
|
+
|
|
9
|
+
import { store } from './store.js';
|
|
10
|
+
import { showToast } from './utils.js';
|
|
11
|
+
import { sendTextMessage } from './input.js';
|
|
12
|
+
import { resolveDefaultVendor, startNewSession } from './sidebar-sessions.js';
|
|
13
|
+
import { getWs } from './ws-ref.js';
|
|
14
|
+
|
|
15
|
+
export function splitFilePath(filePath) {
|
|
16
|
+
var slash = filePath.lastIndexOf("/");
|
|
17
|
+
if (slash === -1) return { name: filePath, dir: "" };
|
|
18
|
+
return { name: filePath.slice(slash + 1), dir: filePath.slice(0, slash + 1) };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function compactSessionTitle(title) {
|
|
22
|
+
var value = String(title || "Agent session").replace(/\s+/g, " ").trim();
|
|
23
|
+
if (/^Review the current uncommitted changes/i.test(value)) return "File review";
|
|
24
|
+
if (/^Create a Git commit for the currently staged changes/i.test(value)) return "Commit session";
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Close the Git surface through its own close control, which owns the panel's
|
|
29
|
+
// exit animation and the return to the sessions panel. Going through the
|
|
30
|
+
// existing control keeps one lifecycle rather than a second teardown path.
|
|
31
|
+
export function closeGitSurface() {
|
|
32
|
+
var gitPanel = document.getElementById("sidebar-panel-git");
|
|
33
|
+
var closeBtn = document.getElementById("git-panel-close");
|
|
34
|
+
if (closeBtn && gitPanel && !gitPanel.classList.contains("hidden")) closeBtn.click();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function startFocusedAgentSession(prompt, successMessage, sessionTitle) {
|
|
38
|
+
if (!store.get('connected')) {
|
|
39
|
+
showToast("Not connected — agent session was not started.", "warn");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
var previousSessionId = store.get('activeSessionId');
|
|
43
|
+
var unsubscribe = null;
|
|
44
|
+
var sessionReady = false;
|
|
45
|
+
var timer = setTimeout(function () {
|
|
46
|
+
if (unsubscribe) unsubscribe();
|
|
47
|
+
showToast("Agent session could not be started", "warn");
|
|
48
|
+
}, 10000);
|
|
49
|
+
unsubscribe = store.subscribe(function (state) {
|
|
50
|
+
if (sessionReady || !state.activeSessionId || state.activeSessionId === previousSessionId) return;
|
|
51
|
+
sessionReady = true;
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
setTimeout(function () {
|
|
54
|
+
if (unsubscribe) unsubscribe();
|
|
55
|
+
unsubscribe = null;
|
|
56
|
+
var newSessionId = store.get('activeSessionId');
|
|
57
|
+
var ws = getWs();
|
|
58
|
+
if (sessionTitle && ws && ws.readyState === WebSocket.OPEN && newSessionId) {
|
|
59
|
+
ws.send(JSON.stringify({ type: "rename_session", id: newSessionId, title: sessionTitle }));
|
|
60
|
+
}
|
|
61
|
+
closeGitSurface();
|
|
62
|
+
if (sendTextMessage(prompt)) showToast(successMessage || "Agent session started");
|
|
63
|
+
}, 0);
|
|
64
|
+
});
|
|
65
|
+
startNewSession(resolveDefaultVendor(), { mode: "gui", forceNew: true });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function startAgentCommitSession(stagedCount) {
|
|
69
|
+
if (!stagedCount) return;
|
|
70
|
+
var prompt = "Create a Git commit for the currently staged changes.\n\n" +
|
|
71
|
+
"Inspect `git diff --cached` and the repository instructions before committing. " +
|
|
72
|
+
"Commit exactly the staged changes; do not stage additional files. " +
|
|
73
|
+
"Write a concise Angular Commit Convention message, use the angular-commit skill, " +
|
|
74
|
+
"and do not add Co-Authored-By lines. If nothing is staged, explain that and stop.";
|
|
75
|
+
startFocusedAgentSession(prompt, "Commit session started", "Commit staged changes");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function startAgentFileReview(filePath) {
|
|
79
|
+
var prompt = "Review the current uncommitted changes in `" + filePath + "`.\n\n" +
|
|
80
|
+
"Inspect the Git diff and relevant repository context. Explain the intent of the change, " +
|
|
81
|
+
"identify correctness or regression risks, and suggest specific improvements. " +
|
|
82
|
+
"Do not edit files or run Git actions unless I ask in this session.";
|
|
83
|
+
var parts = splitFilePath(filePath);
|
|
84
|
+
startFocusedAgentSession(prompt, "File review session started", "Review · " + parts.name);
|
|
85
|
+
}
|
|
@@ -2,16 +2,41 @@ import { store } from './store.js';
|
|
|
2
2
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
3
3
|
import { escapeHtml, showToast } from './utils.js';
|
|
4
4
|
import { openWorkingTreeDiff } from './filebrowser.js';
|
|
5
|
-
import { sendTextMessage } from './input.js';
|
|
6
|
-
import { resolveDefaultVendor, startNewSession } from './sidebar-sessions.js';
|
|
7
5
|
import { getWs } from './ws-ref.js';
|
|
6
|
+
import { hideGitPlacard, mergeStatusIntoSummary, renderGitPlacard } from './git-placard.js';
|
|
7
|
+
import {
|
|
8
|
+
closeGitSurface,
|
|
9
|
+
compactSessionTitle,
|
|
10
|
+
splitFilePath,
|
|
11
|
+
startAgentCommitSession,
|
|
12
|
+
startAgentFileReview
|
|
13
|
+
} from './git-agent-sessions.js';
|
|
8
14
|
|
|
9
15
|
var currentStatus = null;
|
|
16
|
+
var currentSummary = null;
|
|
10
17
|
var busy = false;
|
|
11
18
|
var lastError = "";
|
|
12
19
|
var statusBasePath = null;
|
|
13
20
|
var expandedFileActionsPath = null;
|
|
14
21
|
|
|
22
|
+
// One timer drives both readings so there is never a second poll. The full
|
|
23
|
+
// status runs at the panel's existing cadence only while the panel is open;
|
|
24
|
+
// with the panel closed the placard settles for the cheap summary at a much
|
|
25
|
+
// slower cadence, and neither path ever does diff work.
|
|
26
|
+
var POLL_MS = 2500;
|
|
27
|
+
var SUMMARY_TICKS = 6;
|
|
28
|
+
var summaryTicks = 0;
|
|
29
|
+
|
|
30
|
+
// A project surface always has a /p/<slug>/ base path. Home and any surface
|
|
31
|
+
// without a project must not issue a repository request at all.
|
|
32
|
+
function hasProjectBasePath() {
|
|
33
|
+
return String(store.get('basePath') || "").indexOf("/p/") === 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function placardMounted() {
|
|
37
|
+
return !!document.getElementById("git-placard");
|
|
38
|
+
}
|
|
39
|
+
|
|
15
40
|
function apiPath(path) {
|
|
16
41
|
return store.get('basePath') + "api/git/" + path;
|
|
17
42
|
}
|
|
@@ -33,12 +58,6 @@ function shortOrigin(origin) {
|
|
|
33
58
|
.replace(/\.git$/, "");
|
|
34
59
|
}
|
|
35
60
|
|
|
36
|
-
function splitFilePath(filePath) {
|
|
37
|
-
var slash = filePath.lastIndexOf("/");
|
|
38
|
-
if (slash === -1) return { name: filePath, dir: "" };
|
|
39
|
-
return { name: filePath.slice(slash + 1), dir: filePath.slice(0, slash + 1) };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
61
|
function fileStatusLabel(file, staged) {
|
|
43
62
|
if (file.conflicted) return "!";
|
|
44
63
|
if (file.untracked) return "U";
|
|
@@ -46,13 +65,6 @@ function fileStatusLabel(file, staged) {
|
|
|
46
65
|
return code === "." ? "M" : code;
|
|
47
66
|
}
|
|
48
67
|
|
|
49
|
-
function compactSessionTitle(title) {
|
|
50
|
-
var value = String(title || "Agent session").replace(/\s+/g, " ").trim();
|
|
51
|
-
if (/^Review the current uncommitted changes/i.test(value)) return "File review";
|
|
52
|
-
if (/^Create a Git commit for the currently staged changes/i.test(value)) return "Commit session";
|
|
53
|
-
return value;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
68
|
function renderFileMeta(file, directory) {
|
|
57
69
|
var sessions = Array.isArray(file.sessions) ? file.sessions : [];
|
|
58
70
|
var first = sessions.length > 0 ? sessions[0] : null;
|
|
@@ -200,19 +212,54 @@ function render() {
|
|
|
200
212
|
var stagedList = body.querySelector(".git-section-staged .git-file-list");
|
|
201
213
|
if (changesList) changesList.scrollTop = changesScrollTop;
|
|
202
214
|
if (stagedList) stagedList.scrollTop = stagedScrollTop;
|
|
203
|
-
|
|
215
|
+
syncPlacard();
|
|
204
216
|
}
|
|
205
217
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (!
|
|
210
|
-
|
|
218
|
+
// The placard is a projection of whichever reading is current. A full status
|
|
219
|
+
// supersedes the summary when it exists, so the two never disagree.
|
|
220
|
+
function syncPlacard() {
|
|
221
|
+
if (!placardMounted()) return;
|
|
222
|
+
if (currentStatus && currentStatus.isRepository) {
|
|
223
|
+
currentSummary = mergeStatusIntoSummary(currentStatus, currentSummary);
|
|
224
|
+
}
|
|
225
|
+
if (!currentSummary || !currentSummary.isRepository) {
|
|
226
|
+
hideGitPlacard();
|
|
211
227
|
return;
|
|
212
228
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
229
|
+
renderGitPlacard(currentSummary);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Cheap placard-only reading. Used on sidebar lifecycle (connect, reconnect,
|
|
233
|
+
// project switch) and on the slow tick while the full panel is closed.
|
|
234
|
+
export function refreshGitSummary() {
|
|
235
|
+
if (busy || !hasProjectBasePath() || !placardMounted()) return Promise.resolve(currentSummary);
|
|
236
|
+
var basePath = store.get('basePath');
|
|
237
|
+
return requestJson(basePath + "api/git/summary", { cache: "no-store" }).then(function (summary) {
|
|
238
|
+
if (store.get('basePath') !== basePath) return currentSummary;
|
|
239
|
+
currentSummary = summary && summary.isRepository ? summary : null;
|
|
240
|
+
syncPlacard();
|
|
241
|
+
return currentSummary;
|
|
242
|
+
}).catch(function () {
|
|
243
|
+
// An unavailable or forbidden repository shows nothing rather than an
|
|
244
|
+
// error placeholder; the full panel still reports errors when opened.
|
|
245
|
+
if (store.get('basePath') !== basePath) return currentSummary;
|
|
246
|
+
currentSummary = null;
|
|
247
|
+
syncPlacard();
|
|
248
|
+
return null;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Project switch and reconnect both land here, so no stale repository ever
|
|
253
|
+
// survives into another project and nothing flashes before detection lands.
|
|
254
|
+
export function resetGitSurface() {
|
|
255
|
+
currentStatus = null;
|
|
256
|
+
currentSummary = null;
|
|
257
|
+
lastError = "";
|
|
258
|
+
expandedFileActionsPath = null;
|
|
259
|
+
statusBasePath = store.get('basePath');
|
|
260
|
+
summaryTicks = 0;
|
|
261
|
+
hideGitPlacard();
|
|
262
|
+
render();
|
|
216
263
|
}
|
|
217
264
|
|
|
218
265
|
export function refreshGitStatus() {
|
|
@@ -221,8 +268,10 @@ export function refreshGitStatus() {
|
|
|
221
268
|
if (statusBasePath !== basePath) {
|
|
222
269
|
statusBasePath = basePath;
|
|
223
270
|
currentStatus = null;
|
|
271
|
+
currentSummary = null;
|
|
224
272
|
lastError = "";
|
|
225
273
|
expandedFileActionsPath = null;
|
|
274
|
+
hideGitPlacard();
|
|
226
275
|
render();
|
|
227
276
|
}
|
|
228
277
|
var refreshBtn = document.getElementById("git-panel-refresh");
|
|
@@ -309,63 +358,7 @@ function returnToSession(sessionId) {
|
|
|
309
358
|
var ws = getWs();
|
|
310
359
|
if (!ws || ws.readyState !== WebSocket.OPEN || !sessionId) return;
|
|
311
360
|
ws.send(JSON.stringify({ type: "switch_session", id: sessionId }));
|
|
312
|
-
|
|
313
|
-
var gitPanel = document.getElementById("sidebar-panel-git");
|
|
314
|
-
if (gitBtn && gitPanel && !gitPanel.classList.contains("hidden")) gitBtn.click();
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function startFocusedAgentSession(prompt, successMessage, sessionTitle) {
|
|
318
|
-
if (!store.get('connected')) {
|
|
319
|
-
showToast("Not connected — agent session was not started.", "warn");
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
var previousSessionId = store.get('activeSessionId');
|
|
323
|
-
var unsubscribe = null;
|
|
324
|
-
var sessionReady = false;
|
|
325
|
-
var timer = setTimeout(function () {
|
|
326
|
-
if (unsubscribe) unsubscribe();
|
|
327
|
-
showToast("Agent session could not be started", "warn");
|
|
328
|
-
}, 10000);
|
|
329
|
-
unsubscribe = store.subscribe(function (state) {
|
|
330
|
-
if (sessionReady || !state.activeSessionId || state.activeSessionId === previousSessionId) return;
|
|
331
|
-
sessionReady = true;
|
|
332
|
-
clearTimeout(timer);
|
|
333
|
-
setTimeout(function () {
|
|
334
|
-
if (unsubscribe) unsubscribe();
|
|
335
|
-
unsubscribe = null;
|
|
336
|
-
var newSessionId = store.get('activeSessionId');
|
|
337
|
-
var ws = getWs();
|
|
338
|
-
if (sessionTitle && ws && ws.readyState === WebSocket.OPEN && newSessionId) {
|
|
339
|
-
ws.send(JSON.stringify({ type: "rename_session", id: newSessionId, title: sessionTitle }));
|
|
340
|
-
}
|
|
341
|
-
var gitBtn = document.getElementById("git-sidebar-btn");
|
|
342
|
-
var gitPanel = document.getElementById("sidebar-panel-git");
|
|
343
|
-
if (gitBtn && gitPanel && !gitPanel.classList.contains("hidden")) gitBtn.click();
|
|
344
|
-
if (sendTextMessage(prompt)) showToast(successMessage || "Agent session started");
|
|
345
|
-
}, 0);
|
|
346
|
-
});
|
|
347
|
-
startNewSession(resolveDefaultVendor(), { mode: "gui", forceNew: true });
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function startAgentCommitSession() {
|
|
351
|
-
if (busy || !currentStatus) return;
|
|
352
|
-
var hasStaged = currentStatus.files.some(function (file) { return file.staged; });
|
|
353
|
-
if (!hasStaged) return;
|
|
354
|
-
var prompt = "Create a Git commit for the currently staged changes.\n\n" +
|
|
355
|
-
"Inspect `git diff --cached` and the repository instructions before committing. " +
|
|
356
|
-
"Commit exactly the staged changes; do not stage additional files. " +
|
|
357
|
-
"Write a concise Angular Commit Convention message, use the angular-commit skill, " +
|
|
358
|
-
"and do not add Co-Authored-By lines. If nothing is staged, explain that and stop.";
|
|
359
|
-
startFocusedAgentSession(prompt, "Commit session started", "Commit staged changes");
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function startAgentFileReview(filePath) {
|
|
363
|
-
var prompt = "Review the current uncommitted changes in `" + filePath + "`.\n\n" +
|
|
364
|
-
"Inspect the Git diff and relevant repository context. Explain the intent of the change, " +
|
|
365
|
-
"identify correctness or regression risks, and suggest specific improvements. " +
|
|
366
|
-
"Do not edit files or run Git actions unless I ask in this session.";
|
|
367
|
-
var parts = splitFilePath(filePath);
|
|
368
|
-
startFocusedAgentSession(prompt, "File review session started", "Review \u00b7 " + parts.name);
|
|
361
|
+
closeGitSurface();
|
|
369
362
|
}
|
|
370
363
|
|
|
371
364
|
function toggleFileActions(fileMain) {
|
|
@@ -394,10 +387,32 @@ function toggleFileActions(fileMain) {
|
|
|
394
387
|
}
|
|
395
388
|
}
|
|
396
389
|
|
|
390
|
+
// Sidebar lifecycle. A project switch resets everything and asks once; a
|
|
391
|
+
// reconnect re-asks without resetting the surface the user is looking at.
|
|
392
|
+
// Entering a Mate DM hides the placard, since that surface has no repository.
|
|
393
|
+
function initGitLifecycle() {
|
|
394
|
+
statusBasePath = store.get('basePath');
|
|
395
|
+
store.subscribe(function (state, prev) {
|
|
396
|
+
if (state.basePath !== prev.basePath || state.currentSlug !== prev.currentSlug) {
|
|
397
|
+
resetGitSurface();
|
|
398
|
+
if (!state.dmMode) refreshGitSummary();
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (state.dmMode !== prev.dmMode) {
|
|
402
|
+
if (state.dmMode) hideGitPlacard();
|
|
403
|
+
else refreshGitSummary();
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (state.connected && !prev.connected && !state.dmMode) refreshGitSummary();
|
|
407
|
+
});
|
|
408
|
+
if (!store.get('dmMode')) refreshGitSummary();
|
|
409
|
+
}
|
|
410
|
+
|
|
397
411
|
export function initGitPanel() {
|
|
398
412
|
var body = document.getElementById("git-panel-body");
|
|
399
413
|
var refreshBtn = document.getElementById("git-panel-refresh");
|
|
400
414
|
if (!body || !refreshBtn) return;
|
|
415
|
+
initGitLifecycle();
|
|
401
416
|
refreshBtn.addEventListener("click", function () { refreshGitStatus(); });
|
|
402
417
|
body.addEventListener("click", function (event) {
|
|
403
418
|
var openDiff = event.target.closest("[data-git-open-diff]");
|
|
@@ -427,7 +442,9 @@ export function initGitPanel() {
|
|
|
427
442
|
}
|
|
428
443
|
var agentCommitButton = event.target.closest("[data-git-agent-commit]");
|
|
429
444
|
if (agentCommitButton) {
|
|
430
|
-
if (!agentCommitButton.disabled)
|
|
445
|
+
if (!agentCommitButton.disabled && !busy && currentStatus) {
|
|
446
|
+
startAgentCommitSession(currentStatus.files.filter(function (file) { return file.staged; }).length);
|
|
447
|
+
}
|
|
431
448
|
return;
|
|
432
449
|
}
|
|
433
450
|
var actionButton = event.target.closest("[data-git-action]");
|
|
@@ -451,6 +468,16 @@ export function initGitPanel() {
|
|
|
451
468
|
});
|
|
452
469
|
setInterval(function () {
|
|
453
470
|
var panel = document.getElementById("sidebar-panel-git");
|
|
454
|
-
|
|
455
|
-
|
|
471
|
+
var panelOpen = !!(panel && !panel.classList.contains("hidden"));
|
|
472
|
+
if (panelOpen) {
|
|
473
|
+
summaryTicks = 0;
|
|
474
|
+
if (store.get('connected') && !busy) refreshGitStatus();
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
summaryTicks++;
|
|
478
|
+
if (summaryTicks < SUMMARY_TICKS) return;
|
|
479
|
+
summaryTicks = 0;
|
|
480
|
+
if (document.hidden || store.get('dmMode') || !store.get('connected') || busy) return;
|
|
481
|
+
refreshGitSummary();
|
|
482
|
+
}, POLL_MS);
|
|
456
483
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// git-placard.js — compact always-visible repository placard.
|
|
2
|
+
//
|
|
3
|
+
// Git is no longer a tool-palette tile. Instead, a real Git repository gets a
|
|
4
|
+
// quiet placard directly under the tool strip in the project sidebar: repo
|
|
5
|
+
// identity, current branch, and a bounded status glance. The placard is a
|
|
6
|
+
// projection, never a second source of truth — git-panel.js owns the fetch and
|
|
7
|
+
// hands data here, so there is exactly one Git request path in the client.
|
|
8
|
+
//
|
|
9
|
+
// The placard renders only server-derived, bounded fields. It never receives or
|
|
10
|
+
// renders a repository path, and it stays hidden entirely until a real
|
|
11
|
+
// repository is confirmed, so a non-Git project, an unavailable worktree, a
|
|
12
|
+
// Home/Mate surface, or a still-pending detection shows nothing at all rather
|
|
13
|
+
// than an empty placeholder.
|
|
14
|
+
|
|
15
|
+
import { escapeHtml } from './utils.js';
|
|
16
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
17
|
+
|
|
18
|
+
var lastRenderedHtml = "";
|
|
19
|
+
var lastRenderedLabel = "";
|
|
20
|
+
|
|
21
|
+
function countChangedFiles(files) {
|
|
22
|
+
var counts = { changed: 0, staged: 0, unstaged: 0, untracked: 0, conflicted: 0 };
|
|
23
|
+
var list = Array.isArray(files) ? files : [];
|
|
24
|
+
counts.changed = list.length;
|
|
25
|
+
for (var i = 0; i < list.length; i++) {
|
|
26
|
+
var file = list[i];
|
|
27
|
+
if (file.conflicted) counts.conflicted++;
|
|
28
|
+
if (file.untracked) counts.untracked++;
|
|
29
|
+
if (file.staged) counts.staged++;
|
|
30
|
+
if (file.unstaged && !file.untracked) counts.unstaged++;
|
|
31
|
+
}
|
|
32
|
+
return counts;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Fold a full panel status into the placard shape. The panel status is the
|
|
36
|
+
// fresher, more authoritative reading whenever it is available, so it wins for
|
|
37
|
+
// branch and counts. `remote` is kept from the last server summary because the
|
|
38
|
+
// label is derived server-side; the client never parses a remote URL itself.
|
|
39
|
+
export function mergeStatusIntoSummary(status, previous) {
|
|
40
|
+
if (!status || !status.isRepository) return null;
|
|
41
|
+
var base = previous || {};
|
|
42
|
+
var counts = countChangedFiles(status.files);
|
|
43
|
+
return {
|
|
44
|
+
isRepository: true,
|
|
45
|
+
name: status.name || base.name || null,
|
|
46
|
+
branch: status.detached ? null : (status.branch || null),
|
|
47
|
+
detached: !!status.detached,
|
|
48
|
+
shortOid: status.oid ? String(status.oid).slice(0, 8) : (base.shortOid || null),
|
|
49
|
+
isWorktree: !!status.isWorktree,
|
|
50
|
+
hasUpstream: !!status.upstream,
|
|
51
|
+
ahead: status.ahead || 0,
|
|
52
|
+
behind: status.behind || 0,
|
|
53
|
+
changed: counts.changed,
|
|
54
|
+
staged: counts.staged,
|
|
55
|
+
unstaged: counts.unstaged,
|
|
56
|
+
untracked: counts.untracked,
|
|
57
|
+
conflicted: counts.conflicted,
|
|
58
|
+
remote: base.remote || null,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function branchLabel(summary) {
|
|
63
|
+
if (summary.detached) return summary.shortOid ? "Detached at " + summary.shortOid : "Detached HEAD";
|
|
64
|
+
return summary.branch || "No commits yet";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function changeLabel(summary) {
|
|
68
|
+
if (summary.conflicted > 0) {
|
|
69
|
+
return summary.conflicted + " conflicted";
|
|
70
|
+
}
|
|
71
|
+
if (summary.changed > 0) {
|
|
72
|
+
return summary.changed + " changed";
|
|
73
|
+
}
|
|
74
|
+
return "Clean";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// One plain-text sentence for assistive technology. The rendered rows are
|
|
78
|
+
// marked aria-hidden so the group is announced once, in full, rather than as a
|
|
79
|
+
// stream of bare numbers and glyphs.
|
|
80
|
+
export function accessibleLabel(summary) {
|
|
81
|
+
if (!summary || !summary.isRepository) return "";
|
|
82
|
+
var parts = ["Repository " + (summary.name || "unknown")];
|
|
83
|
+
parts.push(summary.detached ? branchLabel(summary) : "branch " + branchLabel(summary));
|
|
84
|
+
if (summary.isWorktree) parts.push("linked worktree");
|
|
85
|
+
parts.push(summary.changed > 0 ? changeLabel(summary) + " files" : "working tree clean");
|
|
86
|
+
if (summary.hasUpstream && summary.ahead > 0) parts.push(summary.ahead + " ahead");
|
|
87
|
+
if (summary.hasUpstream && summary.behind > 0) parts.push(summary.behind + " behind");
|
|
88
|
+
if (summary.remote) parts.push("remote " + summary.remote);
|
|
89
|
+
return parts.join(", ");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function metaHtml(summary) {
|
|
93
|
+
var html = "";
|
|
94
|
+
if (summary.isWorktree) html += '<span class="git-placard-badge">Linked worktree</span>';
|
|
95
|
+
if (summary.detached) html += '<span class="git-placard-badge git-placard-badge-warn">Detached</span>';
|
|
96
|
+
|
|
97
|
+
var stateClass = "git-placard-stat";
|
|
98
|
+
if (summary.conflicted > 0) stateClass += " git-placard-stat-warn";
|
|
99
|
+
else if (summary.changed > 0) stateClass += " git-placard-stat-active";
|
|
100
|
+
html += '<span class="' + stateClass + '">' + escapeHtml(changeLabel(summary)) + '</span>';
|
|
101
|
+
|
|
102
|
+
if (summary.hasUpstream && summary.ahead > 0) {
|
|
103
|
+
html += '<span class="git-placard-stat" title="' + summary.ahead + ' to push">' +
|
|
104
|
+
iconHtml("arrow-up") + summary.ahead + '</span>';
|
|
105
|
+
}
|
|
106
|
+
if (summary.hasUpstream && summary.behind > 0) {
|
|
107
|
+
html += '<span class="git-placard-stat" title="' + summary.behind + ' to pull">' +
|
|
108
|
+
iconHtml("arrow-down") + summary.behind + '</span>';
|
|
109
|
+
}
|
|
110
|
+
return '<div class="git-placard-meta">' + html + '</div>';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function placardHtml(summary) {
|
|
114
|
+
var html = '<div class="git-placard-identity">' +
|
|
115
|
+
'<span class="git-placard-glyph">' + iconHtml("git-branch") + '</span>' +
|
|
116
|
+
'<span class="git-placard-names">' +
|
|
117
|
+
'<span class="git-placard-repo">' + escapeHtml(summary.name || "Repository") + '</span>' +
|
|
118
|
+
'<span class="git-placard-branch">' + escapeHtml(branchLabel(summary)) + '</span>' +
|
|
119
|
+
'</span>' +
|
|
120
|
+
'</div>';
|
|
121
|
+
html += metaHtml(summary);
|
|
122
|
+
if (summary.remote) {
|
|
123
|
+
html += '<div class="git-placard-remote">' + iconHtml("cloud") +
|
|
124
|
+
'<span>' + escapeHtml(summary.remote) + '</span></div>';
|
|
125
|
+
}
|
|
126
|
+
return html;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function hideGitPlacard() {
|
|
130
|
+
var el = document.getElementById("git-placard");
|
|
131
|
+
var body = document.getElementById("git-placard-body");
|
|
132
|
+
lastRenderedHtml = "";
|
|
133
|
+
lastRenderedLabel = "";
|
|
134
|
+
if (body) body.innerHTML = "";
|
|
135
|
+
if (el) {
|
|
136
|
+
el.classList.add("hidden");
|
|
137
|
+
el.removeAttribute("aria-label");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function renderGitPlacard(summary) {
|
|
142
|
+
var el = document.getElementById("git-placard");
|
|
143
|
+
var body = document.getElementById("git-placard-body");
|
|
144
|
+
if (!el || !body) return;
|
|
145
|
+
if (!summary || !summary.isRepository) {
|
|
146
|
+
hideGitPlacard();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
var html = placardHtml(summary);
|
|
150
|
+
if (html !== lastRenderedHtml) {
|
|
151
|
+
body.innerHTML = html;
|
|
152
|
+
lastRenderedHtml = html;
|
|
153
|
+
refreshIcons();
|
|
154
|
+
}
|
|
155
|
+
var label = accessibleLabel(summary);
|
|
156
|
+
if (label !== lastRenderedLabel) {
|
|
157
|
+
el.setAttribute("aria-label", label);
|
|
158
|
+
lastRenderedLabel = label;
|
|
159
|
+
}
|
|
160
|
+
el.classList.remove("hidden");
|
|
161
|
+
}
|