mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 00:19:42 +02:00
- Hide the new btn-session-manager header button on phones: add it to the @media (max-width: 430px) display:none block in mobile.css (next to .btn-away-digest) and to KNOWN_PHONE_HIDDEN in the mobile-header policy test, closing the recurring phone-header-leak regression that was PR #153's red CI job. - Put the session-manager header button on its own line in index.html (was crammed onto the away-digest line). - app.js: drop session:updated from the unified-list SSE refresh trigger — it is batch-broadcast ~every 500ms per active session and would turn an open modal / visible welcome list into a sustained ~1 Hz full projects rescan loop; created/deleted (structural changes) are sufficient. - terminal-ui.js _fetchUnifiedSessions: check the ApiResponse envelope and throw on failure so a 5xx surfaces via the caller's catch instead of rendering an empty history. - terminal-ui.js _openSessionRowMenu: on re-entry, invoke the previous menu's close fn (stored as _openRowMenuClose) so its document/window listeners are detached rather than leaked; use claudeSessionId || sessionId in the 'Resume session' menu item to match the main-row and Session Manager resume routing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1426,8 +1426,11 @@ class CodemanApp {
|
|||||||
// visible welcome list) on session structural changes. Extra listeners on the
|
// visible welcome list) on session structural changes. Extra listeners on the
|
||||||
// same EventSource — EventSource supports multiple listeners per event — so the
|
// same EventSource — EventSource supports multiple listeners per event — so the
|
||||||
// existing handlers above are untouched. Registered through addListener so they
|
// existing handlers above are untouched. Registered through addListener so they
|
||||||
// are torn down with the rest on reconnect.
|
// are torn down with the rest on reconnect. Only structural events (created /
|
||||||
for (const event of [SSE_EVENTS.SESSION_CREATED, SSE_EVENTS.SESSION_UPDATED, SSE_EVENTS.SESSION_DELETED]) {
|
// deleted) trigger a refetch: session:updated is batch-broadcast every ~500ms
|
||||||
|
// per active session, which would otherwise turn an open modal / visible welcome
|
||||||
|
// list into a sustained ~1 Hz full ~/.claude/projects rescan loop.
|
||||||
|
for (const event of [SSE_EVENTS.SESSION_CREATED, SSE_EVENTS.SESSION_DELETED]) {
|
||||||
addListener(event, () => this._onSessionListMaybeChanged());
|
addListener(event, () => this._onSessionListMaybeChanged());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<button class="btn-icon-header btn-redraw-terminal btn-redraw-terminal--hidden" onclick="app.restoreTerminalSize()" title="Redraw terminal to fit current screen (Ctrl+Shift+R)" aria-label="Redraw terminal"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"/><polyline points="23 20 23 14 17 14"/><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"/></svg></button>
|
<button class="btn-icon-header btn-redraw-terminal btn-redraw-terminal--hidden" onclick="app.restoreTerminalSize()" title="Redraw terminal to fit current screen (Ctrl+Shift+R)" aria-label="Redraw terminal"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="1 4 1 10 7 10"/><polyline points="23 20 23 14 17 14"/><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"/></svg></button>
|
||||||
<button class="btn-icon-header btn-response-viewer-header btn-response-viewer-header--hidden" onclick="app.toggleResponseViewer()" title="View last response" aria-label="View last response"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></button>
|
<button class="btn-icon-header btn-response-viewer-header btn-response-viewer-header--hidden" onclick="app.toggleResponseViewer()" title="View last response" aria-label="View last response"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></button>
|
||||||
<button class="btn-icon-header btn-away-digest" onclick="app.openAwayDigest()" title="Away Digest" aria-label="Open away digest"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M3 6h.01"/><path d="M3 12h.01"/><path d="M3 18h.01"/></svg></button> <button class="btn-icon-header btn-session-manager" onclick="app.openSessionManager()" title="Session Manager" aria-label="Open session manager"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></svg></button>
|
<button class="btn-icon-header btn-away-digest" onclick="app.openAwayDigest()" title="Away Digest" aria-label="Open away digest"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M3 6h.01"/><path d="M3 12h.01"/><path d="M3 18h.01"/></svg></button>
|
||||||
|
<button class="btn-icon-header btn-session-manager" onclick="app.openSessionManager()" title="Session Manager" aria-label="Open session manager"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></svg></button>
|
||||||
<button class="btn-icon-header btn-attachments-history btn-attachments-history--hidden" id="attachmentsHistoryBtn" onclick="app.toggleAttachmentHistory()" title="Attachments" aria-label="Open attachment history" aria-expanded="false">
|
<button class="btn-icon-header btn-attachments-history btn-attachments-history--hidden" id="attachmentsHistoryBtn" onclick="app.toggleAttachmentHistory()" title="Attachments" aria-label="Open attachment history" aria-expanded="false">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
|
||||||
<span class="attachment-history-badge" id="attachmentHistoryBadge" style="display:none;">0</span>
|
<span class="attachment-history-badge" id="attachmentHistoryBadge" style="display:none;">0</span>
|
||||||
|
|||||||
@@ -459,14 +459,16 @@ html.mobile-init .file-browser-panel {
|
|||||||
height: 12px;
|
height: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide header settings gear, lifecycle log, and away digest on mobile - settings
|
/* Hide header settings gear, lifecycle log, away digest, and session manager on
|
||||||
moved to toolbar; away digest is a secondary informational control that doesn't
|
mobile - settings moved to toolbar; away digest and the session manager are
|
||||||
belong on the cramped phone header.
|
secondary controls that don't belong on the cramped phone header (the session
|
||||||
|
manager stays reachable via the Ctrl+K palette's "Browse all sessions" item).
|
||||||
(The attachments button is opt-in / default-hidden everywhere via its own
|
(The attachments button is opt-in / default-hidden everywhere via its own
|
||||||
--hidden marker, so it needs no mobile-specific rule here.) */
|
--hidden marker, so it needs no mobile-specific rule here.) */
|
||||||
.btn-icon-header.btn-settings,
|
.btn-icon-header.btn-settings,
|
||||||
.btn-icon-header.btn-lifecycle-log,
|
.btn-icon-header.btn-lifecycle-log,
|
||||||
.btn-icon-header.btn-away-digest {
|
.btn-icon-header.btn-away-digest,
|
||||||
|
.btn-icon-header.btn-session-manager {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1152,8 +1152,14 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
*/
|
*/
|
||||||
async _fetchUnifiedSessions(limit = 60) {
|
async _fetchUnifiedSessions(limit = 60) {
|
||||||
const res = await fetch('/api/sessions/unified?limit=' + limit);
|
const res = await fetch('/api/sessions/unified?limit=' + limit);
|
||||||
const data = await res.json();
|
// ApiResponse envelope: { success, data: { sessions } }. Throw on failure so
|
||||||
return data.data?.sessions || [];
|
// callers (loadHistorySessions) hit their catch instead of rendering a 5xx as
|
||||||
|
// an empty history.
|
||||||
|
const data = await res.json().catch(() => null);
|
||||||
|
if (!res.ok || !data || data.success === false || !data.data) {
|
||||||
|
throw new Error(data?.error || `unified sessions request failed (HTTP ${res.status})`);
|
||||||
|
}
|
||||||
|
return data.data.sessions || [];
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1385,14 +1391,15 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
* @param {HTMLElement} detail the inline detail panel element
|
* @param {HTMLElement} detail the inline detail panel element
|
||||||
*/
|
*/
|
||||||
_openSessionRowMenu(anchorEl, s, cases, item, detail) {
|
_openSessionRowMenu(anchorEl, s, cases, item, detail) {
|
||||||
// Close any already-open row menu first.
|
// Close any already-open row menu first — call its own close fn so the
|
||||||
if (this._openRowMenuEl) {
|
// previous menu's document/window listeners are detached (a raw .remove()
|
||||||
|
// would leave them dangling until the next event self-cleans).
|
||||||
|
if (this._openRowMenuClose) {
|
||||||
try {
|
try {
|
||||||
this._openRowMenuEl.remove();
|
this._openRowMenuClose();
|
||||||
} catch {
|
} catch {
|
||||||
/* noop */
|
/* noop */
|
||||||
}
|
}
|
||||||
this._openRowMenuEl = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLiveOpen =
|
const isLiveOpen =
|
||||||
@@ -1416,7 +1423,10 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
} catch {
|
} catch {
|
||||||
/* noop */
|
/* noop */
|
||||||
}
|
}
|
||||||
if (this._openRowMenuEl === menu) this._openRowMenuEl = null;
|
if (this._openRowMenuEl === menu) {
|
||||||
|
this._openRowMenuEl = null;
|
||||||
|
this._openRowMenuClose = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper: build one menu item button.
|
// Helper: build one menu item button.
|
||||||
@@ -1451,7 +1461,9 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
if (isLiveOpen) {
|
if (isLiveOpen) {
|
||||||
this.selectSession(s.sessionId);
|
this.selectSession(s.sessionId);
|
||||||
} else {
|
} else {
|
||||||
this.resumeHistorySession(s.sessionId, s.workingDir || '');
|
// Resume by the Claude conversation UUID when present (resumed sessions
|
||||||
|
// carry theirs separately from their Codeman id).
|
||||||
|
this.resumeHistorySession(s.claudeSessionId || s.sessionId, s.workingDir || '');
|
||||||
}
|
}
|
||||||
this.closeSessionManager?.();
|
this.closeSessionManager?.();
|
||||||
closeMenu();
|
closeMenu();
|
||||||
@@ -1521,6 +1533,7 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
window.addEventListener('resize', onScrollResize, true);
|
window.addEventListener('resize', onScrollResize, true);
|
||||||
|
|
||||||
this._openRowMenuEl = menu;
|
this._openRowMenuEl = menu;
|
||||||
|
this._openRowMenuClose = closeMenu;
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Number of history items shown before "Show More" */
|
/** Number of history items shown before "Show More" */
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const MOBILE_VISIBLE_ALLOWLIST = new Set<string>([]);
|
|||||||
// that removes a hide rule fails loudly (not silently). The attachments button is
|
// that removes a hide rule fails loudly (not silently). The attachments button is
|
||||||
// NOT here: it's opt-in (default-hidden everywhere via its own --hidden marker), so
|
// NOT here: it's opt-in (default-hidden everywhere via its own --hidden marker), so
|
||||||
// it's excluded from the default-visible enumeration rather than mobile-hidden.
|
// it's excluded from the default-visible enumeration rather than mobile-hidden.
|
||||||
const KNOWN_PHONE_HIDDEN = ['btn-settings', 'btn-lifecycle-log'];
|
const KNOWN_PHONE_HIDDEN = ['btn-settings', 'btn-lifecycle-log', 'btn-session-manager'];
|
||||||
|
|
||||||
function attrOf(openTag: string, name: string): string {
|
function attrOf(openTag: string, name: string): string {
|
||||||
const m = openTag.match(new RegExp(`${name}="([^"]*)"`));
|
const m = openTag.match(new RegExp(`${name}="([^"]*)"`));
|
||||||
|
|||||||
Reference in New Issue
Block a user