feat: add favicon (Purple P + gold highlight) (#312)

* feat: add favicon (option 2 — purple P with gold highlight)

Adds the "Purple P + Highlight" favicon across all three Plannotator
servers (plan, review, annotate). The icon is a rounded purple square
with a bold white "P" and a semi-transparent gold stripe through the
middle — typographic, on-brand, and legible at 16px.

Implementation:
- shared-handlers.ts: handleFavicon() serves the inline SVG with a
  1-day cache header, shared by all servers
- index.ts, review.ts, annotate.ts: /favicon.svg route added before
  the SPA catch-all
- apps/hook/index.html, apps/review/index.html: <link rel="icon">
  added pointing to /favicon.svg

SVG favicons are the modern standard and render crisply at all sizes
without a separate .ico file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add favicon to marketing site

Use the same purple P + gold highlight SVG favicon on plannotator.ai.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
This commit is contained in:
dgrissen2
2026-03-16 15:45:00 +01:00
committed by GitHub
parent 93c0f035b3
commit 01d807787b
8 changed files with 33 additions and 3 deletions
+1
View File
@@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Plannotator</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="14" fill="#6B3FA0"/>
<rect x="12" y="28" width="40" height="14" rx="3" fill="#E0BA55" opacity="0.35"/>
<text x="32" y="46" text-anchor="middle" font-family="Inter,system-ui,sans-serif" font-weight="800" font-size="42" fill="white">P</text>
</svg>

After

Width:  |  Height:  |  Size: 347 B

+1
View File
@@ -23,6 +23,7 @@ import '../styles/global.css';
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title}</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<meta name="description" content={description} />
<meta property="og:type" content="website" />
+1
View File
@@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Code Review</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
+4 -1
View File
@@ -13,7 +13,7 @@
import { isRemoteSession, getServerPort } from "./remote";
import { getRepoInfo } from "./repo";
import { handleImage, handleUpload, handleServerReady, handleDraftSave, handleDraftLoad, handleDraftDelete } from "./shared-handlers";
import { handleImage, handleUpload, handleServerReady, handleDraftSave, handleDraftLoad, handleDraftDelete, handleFavicon } from "./shared-handlers";
import { handleDoc } from "./reference-handlers";
import { contentHash, deleteDraft } from "./draft";
import { dirname } from "path";
@@ -179,6 +179,9 @@ export async function startAnnotateServer(
}
}
// Favicon
if (url.pathname === "/favicon.svg") return handleFavicon();
// Serve embedded HTML for all other routes (SPA)
return new Response(htmlContent, {
headers: { "Content-Type": "text/html" },
+4 -1
View File
@@ -35,7 +35,7 @@ import {
} from "./storage";
import { getRepoInfo } from "./repo";
import { detectProjectName } from "./project";
import { handleImage, handleUpload, handleAgents, handleServerReady, handleDraftSave, handleDraftLoad, handleDraftDelete, type OpencodeClient } from "./shared-handlers";
import { handleImage, handleUpload, handleAgents, handleServerReady, handleDraftSave, handleDraftLoad, handleDraftDelete, handleFavicon, type OpencodeClient } from "./shared-handlers";
import { contentHash, deleteDraft } from "./draft";
import { handleDoc, handleObsidianVaults, handleObsidianFiles, handleObsidianDoc } from "./reference-handlers";
import { createEditorAnnotationHandler } from "./editor-annotations";
@@ -431,6 +431,9 @@ export async function startPlannotatorServer(
return Response.json({ ok: true, savedPath });
}
// Favicon
if (url.pathname === "/favicon.svg") return handleFavicon();
// Serve embedded HTML for all other routes (SPA)
return new Response(htmlContent, {
headers: { "Content-Type": "text/html" },
+4 -1
View File
@@ -12,7 +12,7 @@
import { isRemoteSession, getServerPort } from "./remote";
import { type DiffType, type GitContext, runGitDiff, getFileContentsForDiff, gitAddFile, gitResetFile, parseWorktreeDiffType, validateFilePath } from "./git";
import { getRepoInfo } from "./repo";
import { handleImage, handleUpload, handleAgents, handleServerReady, handleDraftSave, handleDraftLoad, handleDraftDelete, type OpencodeClient } from "./shared-handlers";
import { handleImage, handleUpload, handleAgents, handleServerReady, handleDraftSave, handleDraftLoad, handleDraftDelete, handleFavicon, type OpencodeClient } from "./shared-handlers";
import { contentHash, deleteDraft } from "./draft";
import { createEditorAnnotationHandler } from "./editor-annotations";
@@ -284,6 +284,9 @@ export async function startReviewServer(
}
}
// Favicon
if (url.pathname === "/favicon.svg") return handleFavicon();
// Serve embedded HTML for all other routes (SPA)
return new Response(htmlContent, {
headers: { "Content-Type": "text/html" },
+13
View File
@@ -125,6 +125,19 @@ export function handleDraftDelete(contentKey: string): Response {
return Response.json({ ok: true });
}
const FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="14" fill="#6B3FA0"/>
<rect x="12" y="28" width="40" height="14" rx="3" fill="#E0BA55" opacity="0.35"/>
<text x="32" y="46" text-anchor="middle" font-family="Inter,system-ui,sans-serif" font-weight="800" font-size="42" fill="white">P</text>
</svg>`;
/** Serve the app favicon. Used by all 3 servers. */
export function handleFavicon(): Response {
return new Response(FAVICON_SVG, {
headers: { "Content-Type": "image/svg+xml", "Cache-Control": "public, max-age=86400" },
});
}
/** Open browser for local sessions or when a custom handler (e.g. VS Code extension) is configured. */
export async function handleServerReady(
url: string,