Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/app.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { rubyHighRoutes } from "./app.js";
import { renderAppView, rubyHighRoutes } from "./app.js";

describe("Ruby High app routes", () => {
it("mounts every declared route at its exact public contract path", () => {
Expand Down Expand Up @@ -33,4 +33,18 @@ describe("Ruby High app routes", () => {
]),
);
});

it("renders a self-contained disconnected viewer without a broken asset request", () => {
const html = renderAppView({
connection: { connected: false, baseUrl: "", pending: null },
state: null,
autonomy: null,
error: "Ruby High service is unavailable.",
});

expect(html).toContain("<h1>Ruby High</h1>");
expect(html).toContain("Agents go to school here.");
expect(html).toContain('class="hero-mark"');
expect(html).not.toContain('src="/api/apps/ruby-high/');
});
});
11 changes: 7 additions & 4 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const rubyHighRoutes: RubyHighRoute[] = [
"Content-Type": "text/html; charset=utf-8",
"Cache-Control": "no-store",
"Content-Security-Policy":
"default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src 'self'; frame-ancestors 'self'",
"default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src 'self' https: data: http://localhost:* http://127.0.0.1:*; connect-src 'self'; frame-ancestors 'self'",
});
res.status(200).send(renderAppView(status));
},
Expand Down Expand Up @@ -93,7 +93,7 @@ function serviceOrThrow(runtime: IAgentRuntime): RubyHighAgentService {
return service;
}

function renderAppView(status: {
export function renderAppView(status: {
connection: {
connected: boolean;
baseUrl: string;
Expand All @@ -116,6 +116,9 @@ function renderAppView(status: {
const connected = status.connection.connected;
const student = status.state?.student;
const autonomy = status.autonomy;
const hero = connected && status.connection.baseUrl
? `<img src="${escapeHtml(status.connection.baseUrl)}/api/apps/ruby-high/assets/teachers/eliza-face.png" alt="Eliza">`
: '<div class="hero-mark" aria-hidden="true">RH</div>';
return `<!doctype html>
<html lang="en">
<head>
Expand All @@ -125,7 +128,7 @@ function renderAppView(status: {
<style>
:root{color-scheme:dark;font-family:Inter,ui-sans-serif,system-ui;background:#07111f;color:#f8fafc}
body{margin:0;padding:28px;background:radial-gradient(circle at 20% 0,#134e4a,#07111f 52%);min-height:100vh}
main{max-width:760px;margin:auto}.hero{display:flex;gap:22px;align-items:center}.hero img{width:112px;height:112px;object-fit:cover;border-radius:28px;background:#fce7e7}
main{max-width:760px;margin:auto}.hero{display:flex;gap:22px;align-items:center}.hero img,.hero-mark{width:112px;height:112px;border-radius:28px;background:#fce7e7}.hero img{object-fit:cover}.hero-mark{display:grid;place-items:center;color:#4c1d95;font-size:30px;font-weight:900;letter-spacing:.08em}
h1{margin:0;font-size:32px}.sub{color:#99f6e4}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin-top:24px}
article{padding:18px;border:1px solid #2dd4bf44;border-radius:16px;background:#0f172acc}dt{color:#94a3b8;font-size:12px;text-transform:uppercase}dd{margin:5px 0 14px;font-weight:700}
button{padding:11px 14px;border:0;border-radius:10px;background:#2dd4bf;color:#042f2e;font-weight:900;cursor:pointer}button.off{background:#fb7185;color:#4c0519}
Expand All @@ -135,7 +138,7 @@ function renderAppView(status: {
<body>
<main>
<section class="hero">
<img src="${escapeHtml(status.connection.baseUrl)}/api/apps/ruby-high/assets/teachers/eliza-face.png" alt="Eliza">
${hero}
<div><h1>Ruby High</h1><p class="sub">Agents go to school here.</p><p>${connected ? "Connected with scoped agent credentials." : "Not connected. Ask the agent to CONNECT_RUBY_HIGH."}</p></div>
</section>
${status.error ? `<p class="error">${escapeHtml(status.error)}</p>` : ""}
Expand Down
Loading