diff --git a/docs/web-ui.md b/docs/web-ui.md index 67b44b71..d2775493 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -143,6 +143,36 @@ Both bind their listeners with `SO_REUSEADDR` via exponential backoff capped at 2s) so an nspawn restart that races the previous process's socket release resolves itself. +### Per-agent relative paths + +The per-agent UI uses **document-relative paths everywhere** for +assets, API calls, form actions, and the screen WebSocket. Bare +references like `static/app.js`, `api/state`, `events/stream`, +`screen/ws` resolve against `document.baseURI` — the page's URL +without its last path segment. + +That makes the page work under any prefix the agent ends up mounted +at without rebuilding the dist. The cases that matter: + +| served at | `api/state` resolves to | +|---|---| +| `/` (own port, today's shape) | `/api/state` | +| `/agent/iris/` (gateway-prefixed) | `/agent/iris/api/state` | +| `/agent/iris/stats` (subpage, no trailing slash) | `/agent/iris/api/state` | + +The gateway upstream config strips the prefix before forwarding to +the per-agent server, so the agent's Rust routes (`api/state`, +`events/stream`, `screen/ws`, `login/start`, …) keep their absolute +paths server-side. Only the browser-facing URLs are gated on the +mount prefix. + +Subpages (`stats`, `screen`) are served without a trailing slash so +the relative-path resolution stays correct: `static/app.js` from +`/stats` becomes `/static/app.js` (last segment `stats` gets +replaced), not `/stats/static/app.js`. Adding a trailing slash to +those routes would break the resolution; either keep them +slash-less or use `` injection at serve time. + ## Dashboard layout The dashboard (`/`) has a fixed chrome header at the top and a diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index f490f649..c2c4805a 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -330,7 +330,7 @@ window.marked = marked; }), ); const start = el('form', { - action: '/login/start', method: 'POST', 'data-async': '', + action: 'login/start', method: 'POST', 'data-async': '', }); start.append( el('button', { type: 'submit', class: 'btn btn-login' }, '◆ ST4RT L0G1N'), @@ -358,7 +358,7 @@ window.marked = marked; } if (!s.finished) { const code = el('form', { - action: '/login/code', method: 'POST', class: 'loginform', 'data-async': '', + action: 'login/code', method: 'POST', class: 'loginform', 'data-async': '', }); // #568: OAuth code is a sensitive secret — mask the input with // type="password" so a shoulder-surfer / screenshot doesn't @@ -397,7 +397,7 @@ window.marked = marked; root.append(code); } const cancel = el('form', { - action: '/login/cancel', method: 'POST', 'data-async': '', + action: 'login/cancel', method: 'POST', 'data-async': '', style: 'margin-top: 0.4em;', }); cancel.append(el('button', { type: 'submit', class: 'btn btn-cancel' }, 'cancel + kill')); @@ -437,7 +437,7 @@ window.marked = marked; async function postModel(name) { try { - const resp = await fetch('/api/model', { + const resp = await fetch('api/model', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ model: name }), @@ -469,10 +469,12 @@ window.marked = marked; if (termAPI) termAPI.row('turn-end-fail', '✗ ' + label + ' failed: ' + err); } } - const postCancelTurn = () => postSimple('/api/cancel', '/cancel'); - const postCompact = () => postSimple('/api/compact', '/compact'); - const postNewSession = () => postSimple('/api/new-session', '/new-session'); - const postLogout = () => postSimple('/api/logout', '/logout'); + // First arg is the URL path (relative to document base, #14); + // second is the slash-command label rendered in the local note. + const postCancelTurn = () => postSimple('api/cancel', '/cancel'); + const postCompact = () => postSimple('api/compact', '/compact'); + const postNewSession = () => postSimple('api/new-session', '/new-session'); + const postLogout = () => postSimple('api/logout', '/logout'); function handleSlashCommand(line) { if (!termAPI) return false; @@ -548,7 +550,7 @@ window.marked = marked; if (!termInputRendered) { slot.innerHTML = ''; const form = el('form', { - action: '/send', method: 'POST', + action: 'send', method: 'POST', class: 'sendform-term', 'data-async': '', }); const ta = el('textarea', { @@ -668,7 +670,7 @@ window.marked = marked; // keeps the pill count at zero rather than surfacing a stale chrome. async function refreshLooseEnds() { try { - const resp = await fetch('/api/loose-ends'); + const resp = await fetch('api/loose-ends'); if (!resp.ok) { renderLooseEnds([]); return; @@ -1084,7 +1086,7 @@ window.marked = marked; async function refreshState() { try { - const resp = await fetch('/api/state'); + const resp = await fetch('api/state'); if (!resp.ok) throw new Error('http ' + resp.status); const s = await resp.json(); if (!headerSet) { setHeader(s.label, s.qualified_label, s.dashboard_port); headerSet = true; } @@ -1516,8 +1518,10 @@ window.marked = marked; // the composer. Geometry unchanged — `.agent-main` and // `.terminal-wrap` both `inset: 0` fill the same area. pillAnchor: $('agent-main'), - historyUrl: '/events/history', - streamUrl: '/events/stream', + // Path-relative so the page mounted under a nginx prefix + // (e.g. /agent//) still hits the right SSE upstream (#14). + historyUrl: 'events/history', + streamUrl: 'events/stream', renderers: { turn_start(ev, api) { if (api.fromHistory) openTurnsFromHistory += 1; diff --git a/frontend/packages/agent/src/index.html b/frontend/packages/agent/src/index.html index f39065cc..a437fab7 100644 --- a/frontend/packages/agent/src/index.html +++ b/frontend/packages/agent/src/index.html @@ -3,8 +3,8 @@ hyperhive agent - - + + @@ -14,9 +14,12 @@ height on the left as the identity anchor; flyout pills + an overflow menu trigger sit on the right. Frosted glass over the terminal — backdrop-filter blur shows the scrolled terminal - text behind. --> + text behind. + + All asset / API hrefs are relative — see docs/web-ui.md::Per-agent + relative paths for why (#14). -->
- +
@@ -104,6 +107,6 @@ - + diff --git a/frontend/packages/agent/src/screen.html b/frontend/packages/agent/src/screen.html index 2b91f126..702505d2 100644 --- a/frontend/packages/agent/src/screen.html +++ b/frontend/packages/agent/src/screen.html @@ -4,7 +4,7 @@ screen - +