feat: skill fast-path hardening, lineage retune + colors, per-tab pop-out, reliable tab alerts

- SKILL.md: forbid the standalone preamble check and pre-spawn recon turns
  (measured: two wasted model turns cost ~12s of a 28s two-worker run; the
  hardened flow measured 20.2s cold / 12.8s warm end to end)
- Lineage lines: dip now hangs from the strip's bottom edge (cap 104 -> 64,
  no stacked row offsets), fixing the deep bow in wrapped strips and keeping
  row-1 arcs off row-2 tab labels; per-child color palette (skin blue first,
  then matrix green, pink, violet, red, turquoise, orange) via an inline
  --lineage-color custom property
- Session Options -> Session: per-TAB pop-out (open-in-window) button override
  on top of the general showTabDetachButton setting; per-device localStorage
  map rendered as the tab-show-detach class
- Tab alerts: seed the pending-hook state machine from GET /api/approvals
  regardless of the approvals-inbox setting (reloads used to lose the red tab
  entirely with the inbox off), clear unconditionally on approval_resolved,
  and repaint the alert as a steady red/yellow ring + glow + status dot on a
  ::before overlay so it stays visible on the selected (active) tab until the
  permission is actually resolved
- docs: worker warm-pool design sketch (verified numbers baked in)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-08-15 06:38:26 +02:00
parent 52d113ab12
commit 0af80b417c
11 changed files with 420 additions and 76 deletions
+53
View File
@@ -1283,12 +1283,65 @@ Object.assign(CodemanApp.prototype, {
// Session Options Modal
// ═══════════════════════════════════════════════════════════════
/**
* Per-TAB pop-out button override (Session Options → Session → Identity). The
* general `showTabDetachButton` App Setting stays the per-device default for ALL
* tabs; this map whitelists single sessions on top of it, so one tab can carry
* the ⧉ button while the general toggle stays off. Per-device on purpose, like
* the general setting: it is a display choice, so it lives in localStorage and
* never touches the server schema. Rendered as the `tab-show-detach` class on
* the tab (see _fullRenderSessionTabs), which styles.css exempts from the
* global `display: none` gate; the active-tab reveal rules stay shared, so an
* overridden tab behaves exactly like a tab under the general toggle.
*/
_tabDetachOverrides() {
if (this._tabDetachOverrideMap === undefined) {
try {
this._tabDetachOverrideMap = JSON.parse(localStorage.getItem('codeman:tab-detach-overrides') || '{}') || {};
} catch (_e) {
this._tabDetachOverrideMap = {};
}
}
return this._tabDetachOverrideMap;
},
hasTabDetachOverride(sessionId) {
return !!this._tabDetachOverrides()[sessionId];
},
onSessionTabDetachToggle(on) {
const id = this.editingSessionId;
if (!id) return;
const map = this._tabDetachOverrides();
if (on) map[id] = 1;
else delete map[id];
// Prune ids whose sessions are gone, so closed sessions cannot grow the map.
for (const key of Object.keys(map)) {
if (key !== id && this.sessions && !this.sessions.has(key)) delete map[key];
}
try {
localStorage.setItem('codeman:tab-detach-overrides', JSON.stringify(map));
} catch (_e) {
/* storage full/blocked: the in-memory map still applies this page load */
}
// Apply to the LIVE tab directly: the debounced render may take the
// incremental path (same session set), which patches rather than rebuilds,
// so the template's class would only land on the next full render. Future
// full renders re-emit it from _fullRenderSessionTabs.
const tab = document.querySelector(`.session-tab[data-id="${CSS.escape(id)}"]`);
if (tab) tab.classList.toggle('tab-show-detach', !!on);
},
openSessionOptions(sessionId) {
const session = this.sessions.get(sessionId);
if (!session) return;
this.editingSessionId = sessionId;
// Per-tab pop-out override state (see _tabDetachOverrides above).
const detachToggle = document.getElementById('sessionOptShowTabDetach');
if (detachToggle) detachToggle.checked = this.hasTabDetachOverride(sessionId);
// Reset to an appropriate tab — Summary for external CLIs (Respawn/Ralph are Claude-only)
const isAltMode = session.mode === 'opencode' || session.mode === 'codex' || session.mode === 'gemini' || session.mode === 'antigravity' || session.mode === 'pi';
this.switchOptionsTab(isAltMode ? 'summary' : 'respawn');