mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 23:19:43 +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:
+27
-17
@@ -1021,37 +1021,47 @@ export class WebServer extends EventEmitter {
|
||||
return html;
|
||||
}
|
||||
|
||||
/** Cache-busting query for the gesture bundle: its mtime, re-read per render.
|
||||
* The bundle is served from /gesture/ with a 1-year cache, so without a
|
||||
* version that changes on redeploy the browser would keep running a stale
|
||||
* bundle forever. Re-stat'ing each render means a freshly copied-in bundle is
|
||||
* picked up with no server restart. Empty string if the file is missing. */
|
||||
private gestureBundleVersion(): string {
|
||||
/** mtime memo for asset cache-busting (keyed by absolute path). A full index
|
||||
* render does one stat per script/link tag (~25-30); without this each `/`,
|
||||
* `/index.html` and `/session/:id` hit would re-stat them all. A 1s TTL keeps
|
||||
* a burst of renders cheap while still picking up an edited/redeployed file
|
||||
* within a second (no server restart needed). */
|
||||
private _assetVersionMemo = new Map<string, { v: number; ts: number }>();
|
||||
private assetVersion(absPath: string): number | null {
|
||||
const now = Date.now();
|
||||
const hit = this._assetVersionMemo.get(absPath);
|
||||
if (hit && now - hit.ts < 1000) return hit.v;
|
||||
try {
|
||||
const p = join(__dirname, 'public', 'gesture', 'gesture-codeman.js');
|
||||
return `?v=${Math.floor(statSync(p).mtimeMs)}`;
|
||||
const v = Math.floor(statSync(absPath).mtimeMs);
|
||||
this._assetVersionMemo.set(absPath, { v, ts: now });
|
||||
return v;
|
||||
} catch {
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Cache-busting query for the gesture bundle: its mtime (memoized, see
|
||||
* assetVersion). The bundle is served from /gesture/ with a 1-year cache, so
|
||||
* without a version that changes on redeploy the browser would keep running a
|
||||
* stale bundle forever. Empty string if the file is missing. */
|
||||
private gestureBundleVersion(): string {
|
||||
const v = this.assetVersion(join(__dirname, 'public', 'gesture', 'gesture-codeman.js'));
|
||||
return v === null ? '' : `?v=${v}`;
|
||||
}
|
||||
|
||||
/** Append ?v=<mtime> to every same-origin .js/.css reference in the page so a
|
||||
* normal reload always serves the latest. Codeman's static assets are sent
|
||||
* with `Cache-Control: max-age=1y, immutable` and the script/link tags carry
|
||||
* no version, so without this an edited module (panels-ui.js, styles.css, …)
|
||||
* stays cached until a manual hard refresh. mtime is re-stat'd per render, so
|
||||
* a changed file is picked up with no server restart. External URLs (have a
|
||||
* stays cached until a manual hard refresh. mtime is memoized (1s TTL) so a
|
||||
* changed file is picked up with no server restart. External URLs (have a
|
||||
* `:` scheme), already-versioned refs (have a `?`), and refs with no matching
|
||||
* file on disk are left untouched. */
|
||||
private cacheBustAssets(html: string): string {
|
||||
const publicDir = join(__dirname, 'public');
|
||||
return html.replace(/(\s(?:src|href)=")([^"?:]+\.(?:js|css))(")/g, (full, pre, ref, post) => {
|
||||
try {
|
||||
const v = Math.floor(statSync(join(publicDir, ref)).mtimeMs);
|
||||
return `${pre}${ref}?v=${v}${post}`;
|
||||
} catch {
|
||||
return full;
|
||||
}
|
||||
const v = this.assetVersion(join(publicDir, ref));
|
||||
return v === null ? full : `${pre}${ref}?v=${v}${post}`;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user