impel-cli 0.20.34 → 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 CHANGED
@@ -1,5 +1,14 @@
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
+
3
12
  ## 0.20.34 — Stabilize desktop Tasks navigation
4
13
 
5
14
  - Places Tasks immediately after Claude's persistent Home/Code switcher in both
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "impel-cli",
3
- "version": "0.20.34",
3
+ "version": "0.20.35",
4
4
  "description": "Prepare isolated Claude and Codex workspaces for every accessible Impel tenant",
5
5
  "type": "module",
6
6
  "bin": {
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-09.3";
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) {
@@ -7,7 +7,7 @@ export const DESKTOP_TASKS_RESOURCE_URI = "ui://impel/tasks/v1/index.html";
7
7
  // position immediately before Pull requests.
8
8
  export const DESKTOP_TASKS_NAV_ANCHOR_RULES = Object.freeze([
9
9
  { pattern: "^pull requests?\\b", position: "before" },
10
- { pattern: "^code\\b", position: "after", template: "^new\\b" },
10
+ { pattern: "^code\\b", position: "after", template: "\\bnew\\b" },
11
11
  { pattern: "^(?:start\\s+)?new\\s+(?:chat|thread)\\b", position: "before" },
12
12
  { pattern: "^new\\b", position: "before" },
13
13
  { pattern: "^scheduled\\b", position: "before" },
@@ -184,26 +184,44 @@ const NAVIGATION_SOURCE = String.raw`(() => {
184
184
  element.querySelectorAll("kbd, [data-slot=\"shortcut\"]").forEach((candidate) => candidate.remove());
185
185
  };
186
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
+
187
214
  const makeNav = (template) => {
188
215
  const button = template ? template.cloneNode(true) : document.createElement("button");
189
216
  stripVendorActions(button);
217
+ stripTemplateGlyphs(button);
190
218
  button.id = NAV_ID;
191
219
  button.type = "button";
192
220
  button.setAttribute("aria-label", "Tasks");
193
221
  button.setAttribute("title", "Tasks");
194
222
  button.classList.add("impel-desktop-tasks-nav");
195
223
  if (!replaceText(button)) button.textContent = "Tasks";
196
- const icon = button.querySelector("svg");
197
- if (icon) {
198
- icon.setAttribute("viewBox", "0 0 24 24");
199
- icon.setAttribute("fill", "none");
200
- icon.setAttribute("stroke", "currentColor");
201
- icon.setAttribute("stroke-width", "2");
202
- icon.setAttribute("stroke-linecap", "round");
203
- icon.setAttribute("stroke-linejoin", "round");
204
- icon.innerHTML = '<rect x="3" y="3" width="18" height="18" rx="2"></rect><path d="M9 3v18"></path><path d="M15 3v18"></path>';
205
- }
206
- else button.insertAdjacentHTML("afterbegin", TASKS_ICON);
224
+ button.insertAdjacentHTML("afterbegin", TASKS_ICON);
207
225
  button.addEventListener("click", (event) => {
208
226
  event.preventDefault();
209
227
  event.stopPropagation();
@@ -245,8 +263,12 @@ const NAVIGATION_SOURCE = String.raw`(() => {
245
263
  break;
246
264
  }
247
265
  }
248
- const template = anchorRule?.template ? findControl(anchorRule.template) || anchor : anchor;
266
+ const template = anchorRule?.template ? findControl(anchorRule.template) : anchor;
249
267
  lastAnchor = anchor;
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;
250
272
  if (!nav?.isConnected) nav = makeNav(template);
251
273
  const target = placement(anchor);
252
274
  const templateTarget = placement(template);