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
4 changes: 3 additions & 1 deletion scripts/build-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ const sourceDirectory = `${projectRoot}src/miniapp`;
const outputDirectory = `${projectRoot}dist/miniapp/public`;

await mkdir(outputDirectory, { recursive: true });
await copyFile(`${sourceDirectory}/index.html`, `${outputDirectory}/index.html`);
for (const name of ["index.html", "favicon.ico"]) {
await copyFile(`${sourceDirectory}/${name}`, `${outputDirectory}/${name}`);
}

const result = await Bun.build({
entrypoints: [`${sourceDirectory}/client.tsx`],
Expand Down
36 changes: 23 additions & 13 deletions src/miniapp/app.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** Shared browser and Telegram application shell. */
import { CalendarClock, LogOut, SlidersHorizontal, Sparkles, Terminal } from "lucide-react";
import { CalendarClock, LogOut, SlidersHorizontal, Sparkles } from "lucide-react";
import { type ReactElement, useEffect, useState } from "react";
import { requestSnapshot } from "./api.js";
import { SchedulesManager } from "./schedules.js";
Expand Down Expand Up @@ -97,17 +97,7 @@ export function SettingsApp({
<SchedulesManager />
)}
</main>
<Tabbar
aria-label="Main navigation"
brand={
<>
<Terminal aria-hidden="true" />
<span>
Wirebot<small>Your Codex workspace</small>
</span>
</>
}
>
<Tabbar aria-label="Main navigation" brand={<WirebotLogo />}>
{tabs.map(({ id, label, icon: Icon }) => (
<Tabbar.Item
key={id}
Expand Down Expand Up @@ -168,7 +158,7 @@ export function SignIn({ error }: { readonly error: string | undefined }): React
</div>
}
>
<Terminal className="size-10 text-primary" aria-hidden="true" />
<WirebotLogo className="h-12 w-52 max-w-full" />
</Placeholder>
</main>
);
Expand Down Expand Up @@ -200,3 +190,23 @@ function SettingsLoading({
</div>
);
}

/** Full logo from https://wirebot.ai/. */
function WirebotLogo({ className }: { readonly className?: string }): ReactElement {
return (
<svg
className={className}
viewBox="0 0 428.7 100.7"
role="img"
aria-label="Wirebot"
focusable="false"
>
<g fill="currentColor" transform="translate(0 -174)">
<g transform="translate(0 173.97) scale(0.3158)">
<path d="M105 19 A19 19 0 1 1 143 19 A19 19 0 1 1 105 19 Z M117 31 H131 V79 H117 Z M0 195 A124 124 0 1 1 248 195 A124 124 0 1 1 0 195 Z M97 174 A15 15 0 0 0 67 174 V224 A15 15 0 0 0 97 224 Z M181 174 A15 15 0 0 0 151 174 V224 A15 15 0 0 0 181 224 Z" />
</g>
<path d="M122.2 260.0 107.3 211.1H120.2L130.6 248.4H126.7L138.7 211.1H149.3L161.3 248.4H157.5L167.9 211.1H180.4L165.4 260.0H153.8L142.2 225.0H145.8L134.1 260.0Z M198.7 260.0H185.6V211.1H198.7ZM184.2 195.9Q184.2 192.3 186.4 190.1Q188.5 187.8 192.1 187.8Q195.7 187.8 197.9 190.1Q200.0 192.3 200.0 195.9Q200.0 199.3 197.9 201.5Q195.7 203.7 192.1 203.7Q188.5 203.7 186.4 201.5Q184.2 199.3 184.2 195.9Z M218.4 211.1 221.7 224.6V260.0H208.6V211.1ZM219.0 231.3 216.6 230.2V220.2L217.5 219.1Q218.6 217.3 220.9 215.2Q223.1 213.1 226.1 211.6Q229.0 210.0 232.2 210.0Q233.8 210.0 235.1 210.2Q236.4 210.4 237.1 210.9V222.8H233.9Q227.4 222.8 223.9 224.9Q220.4 226.9 219.0 231.3Z M262.0 261.0Q254.7 261.0 249.2 257.9Q243.7 254.7 240.7 249.0Q237.7 243.3 237.7 235.6Q237.7 228.0 240.7 222.2Q243.7 216.5 249.2 213.2Q254.7 210.0 262.0 210.0Q269.4 210.0 274.9 213.3Q280.3 216.6 283.3 222.3Q286.3 228.1 286.3 235.5Q286.3 236.6 286.3 237.6Q286.2 238.7 286.0 239.4H248.4V229.6H276.1L274.1 234.3Q274.1 228.1 271.2 224.2Q268.2 220.3 262.0 220.3Q256.6 220.3 253.3 223.7Q250.0 227.0 250.0 232.6V237.9Q250.0 243.8 253.4 247.2Q256.7 250.5 262.5 250.5Q267.6 250.5 270.5 248.4Q273.4 246.3 275.5 243.2L284.6 248.4Q281.3 254.5 275.7 257.8Q270.0 261.0 262.0 261.0Z M319.1 261.2Q312.7 261.2 307.9 258.0Q303.0 254.8 300.3 249.1Q297.6 243.3 297.6 235.5Q297.6 227.4 300.3 221.7Q302.9 216.0 307.7 213.0Q312.5 210.0 319.0 210.0Q325.7 210.0 330.8 213.2Q335.9 216.4 338.8 222.2Q341.6 227.9 341.6 235.5Q341.6 243.0 338.8 248.8Q335.9 254.6 330.9 257.9Q325.8 261.2 319.1 261.2ZM292.8 260.0V191.9H305.9V223.9H304.9V248.4H305.9L302.6 260.0ZM316.8 249.1Q321.8 249.1 325.1 245.3Q328.3 241.5 328.3 235.5Q328.3 229.5 325.1 225.8Q321.8 222.0 316.8 222.0Q311.8 222.0 308.5 225.8Q305.2 229.5 305.2 235.6Q305.2 241.6 308.5 245.3Q311.8 249.1 316.8 249.1Z M369.8 261.2Q362.2 261.2 356.5 257.9Q350.7 254.7 347.5 248.9Q344.3 243.2 344.3 235.6Q344.3 228.0 347.5 222.2Q350.7 216.5 356.5 213.2Q362.2 210.0 369.8 210.0Q377.4 210.0 383.2 213.2Q388.9 216.5 392.1 222.2Q395.3 228.0 395.3 235.6Q395.3 243.2 392.1 248.9Q388.9 254.7 383.2 257.9Q377.4 261.2 369.8 261.2ZM369.8 248.5Q375.2 248.5 378.6 244.9Q382.0 241.3 382.0 235.6Q382.0 229.8 378.6 226.2Q375.2 222.6 369.8 222.6Q364.4 222.6 361.1 226.1Q357.7 229.7 357.7 235.5Q357.7 241.3 361.1 244.9Q364.4 248.5 369.8 248.5Z M421.1 260.9Q413.6 260.9 409.3 256.9Q404.9 253.0 404.9 244.3V211.7L404.7 211.1L407.4 199.6H418.0V242.2Q418.0 245.4 419.6 246.9Q421.1 248.3 423.5 248.3Q425.1 248.3 426.4 248.1Q427.7 247.8 428.7 247.5V259.7Q427.1 260.3 425.3 260.6Q423.5 260.9 421.1 260.9ZM397.3 223.2V211.1H428.7V223.2Z" />
</g>
</svg>
);
}
Binary file added src/miniapp/favicon.ico
Binary file not shown.
5 changes: 3 additions & 2 deletions src/miniapp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
content="width=device-width, initial-scale=1, viewport-fit=cover"
/>
<meta name="color-scheme" content="light dark" />
<meta name="theme-color" content="#f5f7fb" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#10141c" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#f5f2ec" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#171717" media="(prefers-color-scheme: dark)" />
<title>Wirebot</title>
<link rel="icon" href="/favicon.ico" sizes="48x48" />
<link rel="stylesheet" href="/miniapp/app.css" />
</head>
<body>
Expand Down
9 changes: 7 additions & 2 deletions src/miniapp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@ const MAX_REQUEST_BYTES = 32 * 1_024;
const MAX_AUTH_AGE_SECONDS = 60 * 60;
const JSON_CONTENT_TYPE = "application/json; charset=utf-8";

const staticAssets = new Map<string, readonly ["index.html" | "app.js" | "app.css", string]>([
const staticAssets = new Map<
string,
readonly ["index.html" | "app.js" | "app.css" | "favicon.ico", string]
>([
...["/", "/app", "/app/", "/app/settings", "/app/skills", "/app/schedules"].map(
(path): [string, readonly ["index.html", string]] => [
path,
["index.html", "text/html; charset=utf-8"],
],
),
["/favicon.ico", ["favicon.ico", "image/x-icon"]],
["/miniapp", ["index.html", "text/html; charset=utf-8"]],
["/miniapp/", ["index.html", "text/html; charset=utf-8"]],
["/miniapp/app.js", ["app.js", "text/javascript; charset=utf-8"]],
Expand Down Expand Up @@ -135,6 +139,7 @@ export class MiniAppServer {
access(join(this.#assetDirectory, "index.html")),
access(join(this.#assetDirectory, "app.js")),
access(join(this.#assetDirectory, "app.css")),
access(join(this.#assetDirectory, "favicon.ico")),
]);
await new Promise<void>((resolve, reject) => {
const onError = (error: Error): void => {
Expand Down Expand Up @@ -503,7 +508,7 @@ export class MiniAppServer {
private async sendAsset(
response: ServerResponse,
method: string,
name: "index.html" | "app.js" | "app.css",
name: "index.html" | "app.js" | "app.css" | "favicon.ico",
contentType: string,
): Promise<void> {
// Build outputs never change while the process runs; serve them from memory.
Expand Down
103 changes: 62 additions & 41 deletions src/miniapp/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,26 @@
:root {
color-scheme: light;
--radius: 0.875rem;
--background: #f5f7fb;
--foreground: #172337;
--background: #f5f2ec;
--foreground: #171717;
--card: #ffffff;
--card-foreground: var(--foreground);
--primary: #285bd4;
--primary: #126bdd;
--primary-foreground: #ffffff;
--secondary: #eaf0fa;
--secondary-foreground: #253b60;
--muted: #edf1f7;
--muted-foreground: #5d6c81;
--accent: #e9effb;
--secondary: #e8e5df;
--secondary-foreground: #242424;
--muted: #eae7e1;
--muted-foreground: #66635f;
--accent: #e8e5df;
--accent-foreground: var(--foreground);
--destructive: #c32e3d;
--destructive-foreground: #ffffff;
--switch-thumb: #ffffff;
--border: #dce3ee;
--input: #c7d2e2;
--border: #d7d3cc;
--input: #b8b3aa;
--ring: var(--primary);
--section-header: #5d6c81;
--tabbar-background: #ffffff;
--section-header: #66635f;
--tabbar-background: #efede7;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-synthesis: none;
text-rendering: optimizeLegibility;
Expand All @@ -54,22 +54,22 @@
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--background: #10141c;
--foreground: #e5ebf5;
--card: #191f2b;
--primary: #90b4ff;
--primary-foreground: #122544;
--secondary: #263248;
--secondary-foreground: #d9e5ff;
--muted: #242d3c;
--muted-foreground: #a0aec3;
--accent: #293650;
--background: #171717;
--foreground: #f5f2ec;
--card: #202020;
--primary: #1e83ff;
--primary-foreground: #ffffff;
--secondary: #2e2e2e;
--secondary-foreground: #f5f2ec;
--muted: #2a2a2a;
--muted-foreground: #b4b2ad;
--accent: #303030;
--destructive: #ff8793;
--destructive-foreground: #35151a;
--border: #303c50;
--input: #42516a;
--section-header: #a0aec3;
--tabbar-background: #191f2b;
--border: #4e4e4e;
--input: #5b5b5b;
--section-header: #b4b2ad;
--tabbar-background: #171717;
}
}

Expand Down Expand Up @@ -1542,6 +1542,39 @@ button {
min-height: 100dvh;
}

/* Website palette and shapes, with system typography and accessible button contrast. */
.browserApp .ui-button,
.browserApp .navItems a {
border-radius: 999px;
}
.browserApp .ui-button.bg-primary {
background: color-mix(in srgb, var(--primary) 85%, black);
}
.browserApp .navItems a:is(:hover, [aria-current="page"]) {
background: var(--secondary);
color: var(--foreground);
}
.browserApp .ui-section-body,
.browserApp .scheduleCard,
.browserApp .resetDialog,
.browserApp .loadingRoot > div {
border-radius: 9px;
box-shadow: none;
}
.browserApp .nativeControl {
border-radius: 9px;
}
.browserApp .pageHeader {
padding-bottom: 28px;
}
.browserApp .pageHeader h1 {
font-size: clamp(1.75rem, 4vw, 2.25rem);
letter-spacing: -0.035em;
}
.browserApp .sectionStack {
gap: 28px;
}

.browserHeader {
display: flex;
justify-content: space-between;
Expand Down Expand Up @@ -1602,24 +1635,12 @@ button {
border-right: 1px solid var(--border);
}
.browserApp .navBrand {
display: flex;
align-items: center;
gap: 12px;
display: block;
padding: 0 12px 36px;
font-size: 1.2rem;
font-weight: 700;
}
.browserApp .navBrand svg {
width: 32px;
height: 32px;
color: var(--primary);
}
.browserApp .navBrand small {
display: block;
margin-top: 3px;
font-size: 0.7rem;
font-weight: 400;
color: var(--muted-foreground);
width: 160px;
height: auto;
}
.browserApp .navItems {
display: flex;
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/web-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export async function startTestApp(assetDirectory?: string, includeTelegram = fa
const ownedDirectory = assetDirectory === undefined ? assets : undefined;
if (ownedDirectory !== undefined) {
await Promise.all(
["index.html", "app.js", "app.css"].map((name) =>
["index.html", "app.js", "app.css", "favicon.ico"].map((name) =>
Bun.write(
join(assets, name),
name === "index.html" ? "<!doctype html><title>Wirebot</title>" : "",
Expand Down
2 changes: 2 additions & 0 deletions test/miniapp-browser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ describe("Browser HTTP app", () => {
"/miniapp/",
"/miniapp/app.js",
"/miniapp/app.css",
"/favicon.ico",
]) {
expect((await request(path)).status).toBe(200);
expect((await request(path, { method: "HEAD" })).status).toBe(200);
}
expect((await request("/favicon.ico")).headers.get("content-type")).toBe("image/x-icon");
expect((await request("/app/unknown")).status).toBe(404);
expect((await request("/app", { method: "POST" })).status).toBe(405);
for (const path of [
Expand Down
Loading