impel-cli 0.20.33 → 0.20.35
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/RELEASE_NOTES.md +19 -0
- package/package.json +1 -1
- package/src/apps.js +1 -1
- package/src/desktopTasks.js +86 -29
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Release notes
|
|
2
2
|
|
|
3
|
+
## 0.20.35 — Normalize the Claude Tasks destination
|
|
4
|
+
|
|
5
|
+
- Waits for Claude's native New row before cloning the Tasks destination,
|
|
6
|
+
preventing the earlier Home/Code switcher from leaking its Code glyph into
|
|
7
|
+
the injected control.
|
|
8
|
+
- Removes cloned vendor glyphs and their empty wrappers so Tasks renders with
|
|
9
|
+
exactly one board icon in both Claude modes.
|
|
10
|
+
- Rebuilds existing managed bundles onto the corrected navigation lifecycle.
|
|
11
|
+
|
|
12
|
+
## 0.20.34 — Stabilize desktop Tasks navigation
|
|
13
|
+
|
|
14
|
+
- Places Tasks immediately after Claude's persistent Home/Code switcher in both
|
|
15
|
+
modes and uses the New row as its visual template, so the destination no
|
|
16
|
+
longer jumps above the switcher when the selected mode changes.
|
|
17
|
+
- Prefers matching controls inside the host sidebar, preventing similarly named
|
|
18
|
+
content controls from moving the injected destination into the main surface.
|
|
19
|
+
- Preserves ChatGPT/Codex placement immediately before Pull requests and
|
|
20
|
+
rebuilds existing managed bundles onto the corrected navigation contract.
|
|
21
|
+
|
|
3
22
|
## 0.20.33 — Faster Codex native-agent relay
|
|
4
23
|
|
|
5
24
|
- Uses the ultra-fast Codex Spark model at low reasoning for fixed-binding
|
package/package.json
CHANGED
package/src/apps.js
CHANGED
|
@@ -299,7 +299,7 @@ export const CURRENT_CONFIG_VERSION = 32;
|
|
|
299
299
|
// — which is what made every `impel update` re-trigger macOS permission
|
|
300
300
|
// prompts. Bump this ONLY when a code change alters the bytes of a built
|
|
301
301
|
// bundle; leave it alone for changes that don't touch bundle contents.
|
|
302
|
-
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-08-
|
|
302
|
+
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-08-09.4";
|
|
303
303
|
|
|
304
304
|
/** Parse the tenant's install manifest, or null when absent/corrupt. */
|
|
305
305
|
export function readTenantManifest(homeDir = os.homedir(), tenantId = null) {
|
package/src/desktopTasks.js
CHANGED
|
@@ -2,6 +2,19 @@ import path from "node:path";
|
|
|
2
2
|
|
|
3
3
|
export const DESKTOP_TASKS_RESOURCE_URI = "ui://impel/tasks/v1/index.html";
|
|
4
4
|
|
|
5
|
+
// Claude's mode switch is the stable landmark across Home and Code, but its
|
|
6
|
+
// plain New row is the visual template. ChatGPT/Codex keeps the established
|
|
7
|
+
// position immediately before Pull requests.
|
|
8
|
+
export const DESKTOP_TASKS_NAV_ANCHOR_RULES = Object.freeze([
|
|
9
|
+
{ pattern: "^pull requests?\\b", position: "before" },
|
|
10
|
+
{ pattern: "^code\\b", position: "after", template: "\\bnew\\b" },
|
|
11
|
+
{ pattern: "^(?:start\\s+)?new\\s+(?:chat|thread)\\b", position: "before" },
|
|
12
|
+
{ pattern: "^new\\b", position: "before" },
|
|
13
|
+
{ pattern: "^scheduled\\b", position: "before" },
|
|
14
|
+
{ pattern: "^plugins?\\b", position: "before" },
|
|
15
|
+
{ pattern: "^cowork\\b", position: "before" },
|
|
16
|
+
]);
|
|
17
|
+
|
|
5
18
|
export function desktopTasksAssetPaths(root) {
|
|
6
19
|
const directory = path.join(root, "desktop-tasks");
|
|
7
20
|
return {
|
|
@@ -12,10 +25,17 @@ export function desktopTasksAssetPaths(root) {
|
|
|
12
25
|
};
|
|
13
26
|
}
|
|
14
27
|
|
|
28
|
+
const NAV_ANCHOR_RULES_SOURCE = JSON.stringify(DESKTOP_TASKS_NAV_ANCHOR_RULES);
|
|
29
|
+
|
|
15
30
|
const NAVIGATION_SOURCE = String.raw`(() => {
|
|
16
31
|
const NAV_ID = "impel-desktop-tasks-nav";
|
|
17
32
|
const NAV_SLOT_ID = "impel-desktop-tasks-nav-slot";
|
|
18
33
|
const ACTIVE_CLASS = "impel-desktop-tasks-active";
|
|
34
|
+
const NAV_ANCHOR_RULES = ${NAV_ANCHOR_RULES_SOURCE}.map((rule) => ({
|
|
35
|
+
...rule,
|
|
36
|
+
pattern: new RegExp(rule.pattern, "i"),
|
|
37
|
+
template: rule.template ? new RegExp(rule.template, "i") : null,
|
|
38
|
+
}));
|
|
19
39
|
const TASKS_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"></rect><path d="M9 3v18"></path><path d="M15 3v18"></path></svg>';
|
|
20
40
|
if (window.__impelDesktopTasksCleanup) window.__impelDesktopTasksCleanup();
|
|
21
41
|
|
|
@@ -56,6 +76,12 @@ const NAVIGATION_SOURCE = String.raw`(() => {
|
|
|
56
76
|
|
|
57
77
|
const findControl = (pattern) => {
|
|
58
78
|
const matches = controls().filter((element) => pattern.test(label(element)));
|
|
79
|
+
const sidebar = findSidebar();
|
|
80
|
+
if (sidebar) {
|
|
81
|
+
const sidebarMatch = matches.find((element) => sidebar.contains(element) && visible(element))
|
|
82
|
+
|| matches.find((element) => sidebar.contains(element));
|
|
83
|
+
if (sidebarMatch) return sidebarMatch;
|
|
84
|
+
}
|
|
59
85
|
return matches.find(visible) || matches[0] || null;
|
|
60
86
|
};
|
|
61
87
|
|
|
@@ -134,9 +160,9 @@ const NAVIGATION_SOURCE = String.raw`(() => {
|
|
|
134
160
|
let changed = false;
|
|
135
161
|
while (walker.nextNode()) {
|
|
136
162
|
const text = walker.currentNode.nodeValue || "";
|
|
137
|
-
if (!/(?:new
|
|
163
|
+
if (!/(?:new(?:\s+(?:chat|thread))?|pull requests?|scheduled|plugins?|skills?|cowork|code)\b/i.test(text)) continue;
|
|
138
164
|
walker.currentNode.nodeValue = text.replace(
|
|
139
|
-
/(?:new
|
|
165
|
+
/(?:new(?:\s+(?:chat|thread))?|pull requests?|scheduled|plugins?|skills?|cowork|code)\b/i,
|
|
140
166
|
"Tasks",
|
|
141
167
|
);
|
|
142
168
|
changed = true;
|
|
@@ -158,26 +184,44 @@ const NAVIGATION_SOURCE = String.raw`(() => {
|
|
|
158
184
|
element.querySelectorAll("kbd, [data-slot=\"shortcut\"]").forEach((candidate) => candidate.remove());
|
|
159
185
|
};
|
|
160
186
|
|
|
187
|
+
const stripTemplateGlyphs = (element) => {
|
|
188
|
+
element.querySelectorAll("svg, [aria-hidden=\"true\"]").forEach((candidate) => candidate.remove());
|
|
189
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
|
190
|
+
const glyphs = [];
|
|
191
|
+
while (walker.nextNode()) {
|
|
192
|
+
if (/^(?:\+|+|<\/>|<>)$/u.test((walker.currentNode.nodeValue || "").trim())) {
|
|
193
|
+
glyphs.push(walker.currentNode);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
glyphs.forEach((candidate) => {
|
|
197
|
+
const parent = candidate.parentElement;
|
|
198
|
+
candidate.remove();
|
|
199
|
+
if (
|
|
200
|
+
parent
|
|
201
|
+
&& parent !== element
|
|
202
|
+
&& !parent.textContent?.trim()
|
|
203
|
+
&& !parent.querySelector("svg, img")
|
|
204
|
+
) parent.remove();
|
|
205
|
+
});
|
|
206
|
+
[...element.querySelectorAll("*")].reverse().forEach((candidate) => {
|
|
207
|
+
if (
|
|
208
|
+
!candidate.textContent?.trim()
|
|
209
|
+
&& !candidate.querySelector("svg, img, input")
|
|
210
|
+
) candidate.remove();
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
|
|
161
214
|
const makeNav = (template) => {
|
|
162
215
|
const button = template ? template.cloneNode(true) : document.createElement("button");
|
|
163
216
|
stripVendorActions(button);
|
|
217
|
+
stripTemplateGlyphs(button);
|
|
164
218
|
button.id = NAV_ID;
|
|
165
219
|
button.type = "button";
|
|
166
220
|
button.setAttribute("aria-label", "Tasks");
|
|
167
221
|
button.setAttribute("title", "Tasks");
|
|
168
222
|
button.classList.add("impel-desktop-tasks-nav");
|
|
169
223
|
if (!replaceText(button)) button.textContent = "Tasks";
|
|
170
|
-
|
|
171
|
-
if (icon) {
|
|
172
|
-
icon.setAttribute("viewBox", "0 0 24 24");
|
|
173
|
-
icon.setAttribute("fill", "none");
|
|
174
|
-
icon.setAttribute("stroke", "currentColor");
|
|
175
|
-
icon.setAttribute("stroke-width", "2");
|
|
176
|
-
icon.setAttribute("stroke-linecap", "round");
|
|
177
|
-
icon.setAttribute("stroke-linejoin", "round");
|
|
178
|
-
icon.innerHTML = '<rect x="3" y="3" width="18" height="18" rx="2"></rect><path d="M9 3v18"></path><path d="M15 3v18"></path>';
|
|
179
|
-
}
|
|
180
|
-
else button.insertAdjacentHTML("afterbegin", TASKS_ICON);
|
|
224
|
+
button.insertAdjacentHTML("afterbegin", TASKS_ICON);
|
|
181
225
|
button.addEventListener("click", (event) => {
|
|
182
226
|
event.preventDefault();
|
|
183
227
|
event.stopPropagation();
|
|
@@ -195,32 +239,42 @@ const NAVIGATION_SOURCE = String.raw`(() => {
|
|
|
195
239
|
|
|
196
240
|
const placement = (control) => {
|
|
197
241
|
if (!control) return null;
|
|
242
|
+
const controlRect = control.getBoundingClientRect();
|
|
198
243
|
let child = control;
|
|
199
244
|
let parent = control.parentElement;
|
|
200
245
|
while (parent && parent !== document.body) {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
246
|
+
const rect = parent.getBoundingClientRect();
|
|
247
|
+
const sameVisualRow = Math.abs(rect.top - controlRect.top) <= 8
|
|
248
|
+
&& Math.abs(rect.bottom - controlRect.bottom) <= 8;
|
|
249
|
+
if (!sameVisualRow) break;
|
|
205
250
|
child = parent;
|
|
206
251
|
parent = parent.parentElement;
|
|
207
252
|
}
|
|
208
|
-
return null;
|
|
253
|
+
return parent && parent !== document.body ? { stack: parent, child } : null;
|
|
209
254
|
};
|
|
210
255
|
|
|
211
256
|
const ensureNav = () => {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
257
|
+
let anchorRule = null;
|
|
258
|
+
let anchor = null;
|
|
259
|
+
for (const rule of NAV_ANCHOR_RULES) {
|
|
260
|
+
anchor = findControl(rule.pattern);
|
|
261
|
+
if (anchor) {
|
|
262
|
+
anchorRule = rule;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const template = anchorRule?.template ? findControl(anchorRule.template) : anchor;
|
|
218
267
|
lastAnchor = anchor;
|
|
219
|
-
|
|
268
|
+
// Claude mounts its Home/Code switcher before the New row. Do not clone the
|
|
269
|
+
// switcher as a temporary fallback: its Code glyph would remain in the
|
|
270
|
+
// injected control after the intended visual template appears.
|
|
271
|
+
if (!anchor || !template) return;
|
|
272
|
+
if (!nav?.isConnected) nav = makeNav(template);
|
|
220
273
|
const target = placement(anchor);
|
|
274
|
+
const templateTarget = placement(template);
|
|
221
275
|
if (target) {
|
|
222
276
|
if (!slot?.isConnected || (slot !== nav && !slot.contains(nav))) {
|
|
223
|
-
slot =
|
|
277
|
+
slot = templateTarget?.child === template ? nav : templateTarget?.child.cloneNode(false) || nav;
|
|
224
278
|
if (slot !== nav) {
|
|
225
279
|
slot.id = NAV_SLOT_ID;
|
|
226
280
|
slot.removeAttribute("aria-label");
|
|
@@ -228,11 +282,14 @@ const NAVIGATION_SOURCE = String.raw`(() => {
|
|
|
228
282
|
slot.append(nav);
|
|
229
283
|
}
|
|
230
284
|
}
|
|
231
|
-
|
|
232
|
-
|
|
285
|
+
const after = anchorRule?.position === "after";
|
|
286
|
+
const correctlyPlaced = slot.parentElement === target.stack
|
|
287
|
+
&& (after ? slot.previousElementSibling === target.child : slot.nextElementSibling === target.child);
|
|
288
|
+
if (!correctlyPlaced) {
|
|
289
|
+
target.stack.insertBefore(slot, after ? target.child.nextElementSibling : target.child);
|
|
233
290
|
}
|
|
234
291
|
} else if (anchor?.parentElement) {
|
|
235
|
-
anchor.insertAdjacentElement("afterend", nav);
|
|
292
|
+
anchor.insertAdjacentElement(anchorRule?.position === "after" ? "afterend" : "beforebegin", nav);
|
|
236
293
|
slot = nav;
|
|
237
294
|
} else if (!nav.isConnected) {
|
|
238
295
|
slot = null;
|