mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 23:49:41 +02:00
fix(web): address self-review findings on #103 (master-safe defaults + hardening)
Make the branch genuinely master-mergeable and fix several review findings: - Defaults are now prod-safe: CODEMAN_INSTANCE defaults to '' (→ ~/.codeman, -L codeman) and the web port back to 3000, so an existing install upgrades cleanly. Port also honors a new CODEMAN_PORT env var. Run the beta isolated alongside prod with scripts/run-beta.sh (CODEMAN_INSTANCE=beta + PORT 5000). - .gitignore: anchor the root `public` symlink rule to `/public` (a bare `public` also swallowed src/web/public, silently un-staging new web assets); ignore the gesture wasm/model binaries explicitly instead. - span-displays: add a macOS-only guard (400 elsewhere instead of spawning a bash that fails invisibly); extract resolveSpanUrl() for unit testing. - server.ts: memoize asset-version stat() calls (~1s TTL) so each index render doesn't re-stat every script/link tag. - styles.css: hide the multi-monitor button in solo (detached) windows. - app.js: require two consecutive unanswered roll-calls before redocking, so a timer-throttled background popup isn't wrongly un-marked. - index.html: make the "skip to terminal" link base-href-safe (onclick scroll) so it doesn't navigate to the dashboard from a /session/:id window. - Tests: test/config/instance.test.ts, test/routes/system-span-displays.test.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+13
-2
@@ -309,6 +309,7 @@ class CodemanApp {
|
||||
this._redockGrace = new Map(); // id -> timer: deferred redock (debounces popup reloads)
|
||||
this._detachPingPending = null; // Set of ids awaiting a liveness answer
|
||||
this._detachLivenessTimer = null; // periodic reconcile of channel-only detached windows
|
||||
this._detachOrphanStrikes = new Map(); // id -> consecutive unanswered roll-calls (redock at 2)
|
||||
|
||||
this._initGeneration = 0; // dedup concurrent handleInit calls
|
||||
this._initFallbackTimer = null; // fallback timer if SSE init doesn't arrive
|
||||
@@ -879,6 +880,7 @@ class CodemanApp {
|
||||
const t = this._detachWatchTimers.get(id);
|
||||
if (t) { clearInterval(t); this._detachWatchTimers.delete(id); }
|
||||
this._cancelPendingRedock(id);
|
||||
this._detachOrphanStrikes.delete(id);
|
||||
this.detachedWindows.delete(id);
|
||||
this._markDetached(id, false);
|
||||
}
|
||||
@@ -965,6 +967,7 @@ class CodemanApp {
|
||||
if (msg.type === 'detached' && msg.id) {
|
||||
this._cancelPendingRedock(msg.id); // a re-announce (e.g. popup reload) cancels a deferred redock
|
||||
this._detachPingPending?.delete(msg.id); // and proves liveness for this tick
|
||||
this._detachOrphanStrikes.delete(msg.id); // any answer clears accumulated misses
|
||||
this._markDetached(msg.id, true);
|
||||
} else if (msg.type === 'redocked' && msg.id) {
|
||||
this._scheduleRedock(msg.id); // defer: a popup reload fires redocked→detached; grace avoids a badge blip
|
||||
@@ -993,10 +996,18 @@ class CodemanApp {
|
||||
if (!orphans.length) return;
|
||||
this._detachPingPending = new Set(orphans);
|
||||
this._postWindowMessage({ type: 'roll-call' });
|
||||
// Live popups answer 'detached' (clearing themselves above); survivors are gone.
|
||||
// Live popups answer 'detached' (clearing themselves above); survivors stay in
|
||||
// the pending set. Redock only after TWO consecutive unanswered roll-calls — a
|
||||
// backgrounded popup is timer-throttled and may miss a single 1.2s window, and
|
||||
// we don't want to wrongly un-mark a still-open tab. A later answer resets the
|
||||
// strike count (see _onWindowMessage).
|
||||
setTimeout(() => {
|
||||
if (!this._detachPingPending) return;
|
||||
for (const id of this._detachPingPending) this._redock(id);
|
||||
for (const id of this._detachPingPending) {
|
||||
const strikes = (this._detachOrphanStrikes.get(id) || 0) + 1;
|
||||
if (strikes >= 2) { this._detachOrphanStrikes.delete(id); this._redock(id); }
|
||||
else this._detachOrphanStrikes.set(id, strikes);
|
||||
}
|
||||
this._detachPingPending = null;
|
||||
}, 1200);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,9 @@
|
||||
<div class="skeleton-toolbar"></div>
|
||||
</div>
|
||||
<!-- Skip link for keyboard users -->
|
||||
<a href="#terminalContainer" class="skip-link">Skip to terminal</a>
|
||||
<!-- onclick scrolls/focuses directly: with <base href="/"> a bare href="#..." would
|
||||
navigate to /#... (the dashboard) from a /session/:id solo window. -->
|
||||
<a href="#terminalContainer" class="skip-link" onclick="event.preventDefault(); var t=document.getElementById('terminalContainer'); if(t){t.scrollIntoView(); var f=t.querySelector('textarea,[tabindex]'); (f||t).focus&&(f||t).focus();}">Skip to terminal</a>
|
||||
<div class="app">
|
||||
<!-- Compact Header with Session Tabs -->
|
||||
<header class="header">
|
||||
|
||||
@@ -963,6 +963,7 @@ body.solo-mode .session-tabs,
|
||||
body.solo-mode .header-system-stats,
|
||||
body.solo-mode .header-tokens,
|
||||
body.solo-mode .btn-notifications,
|
||||
body.solo-mode .btn-multimonitor,
|
||||
body.solo-mode .btn-lifecycle-log {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user