Skip to content

Commit ffd099e

Browse files
authored
feat: add feedback button to sidebar (#746)
* fix: self-host Boxicons to comply with strict CSP Replace the external CDN link to pro.boxicons.com with locally hosted font files, avoiding Content Security Policy violations from Helmet. * feat: add feedback button to sidebar Add a clickable feedback card pinned to the bottom of the sidebar that links to GitHub Discussions for feature requests.
1 parent e8e2ba5 commit ffd099e

File tree

7 files changed

+68
-2
lines changed

7 files changed

+68
-2
lines changed

CLAUDE.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,21 @@ See `packages/backend/.env.example` for all variables. Key ones:
272272
- **Tenant**: A user's data boundary. Created from `user.id` on first agent creation.
273273
- **Agent**: An AI agent owned by a tenant. Has a unique OTLP ingest key.
274274

275+
## Content Security Policy (CSP)
276+
277+
Helmet enforces a strict CSP in `main.ts`. The policy only allows `'self'` origins — **no external CDNs are permitted**.
278+
279+
**Rule: Never load external resources from CDNs.** All assets (fonts, icons, stylesheets) must be self-hosted under `packages/frontend/public/`. This keeps the CSP strict and avoids third-party dependencies at runtime.
280+
281+
Current self-hosted assets:
282+
- **Boxicons Duotone**`public/fonts/boxicons/` (CSS + woff/ttf font files)
283+
284+
To add a new font or icon library:
285+
1. Download the CSS and font files into `packages/frontend/public/`
286+
2. Rewrite any CDN URLs inside the CSS to use relative paths (`./filename.woff`)
287+
3. Reference the local CSS in `index.html` (e.g. `<link href="/fonts/..." />`)
288+
4. Do **not** add external domains to the CSP directives
289+
275290
## Architecture Notes
276291

277292
- **Single-service**: In production, `@nestjs/serve-static` serves `frontend/dist/` with SPA fallback. API routes (`/api/*`, `/otlp/*`) are excluded.

packages/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<link rel="icon" href="/favicon.ico" />
7-
<link href="https://pro.boxicons.com/fonts/3.0.8/duotone/regular/200/boxicons-duotone.min.css?sig=ccbb17a30bbd11285764bc03d85132b669dfe25ae1c170743de4040a31577782" rel="stylesheet" />
7+
<link href="/fonts/boxicons/boxicons-duotone.min.css" rel="stylesheet" />
88
<title>Manifest</title>
99
<meta name="description" content="AI agent observability platform — monitor costs, tokens, and performance." />
1010
<meta property="og:title" content="Manifest" />

packages/frontend/public/fonts/boxicons/boxicons-duotone.min.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
395 KB
Binary file not shown.
175 KB
Binary file not shown.

packages/frontend/src/components/Sidebar.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,23 @@ const Sidebar: Component = () => {
7979
Help
8080
</A>
8181
</Show>
82+
83+
<div class="sidebar__spacer" />
84+
85+
<a
86+
href="https://github.com/mnfst/manifest/discussions/new?category=feature-request"
87+
target="_blank"
88+
rel="noopener noreferrer"
89+
class="sidebar__feedback"
90+
>
91+
<span class="sidebar__feedback-title">
92+
<i class="bxd bx-message-bubble-detail" />
93+
Feedback
94+
</span>
95+
<p class="sidebar__feedback-hint">
96+
Help us improve Manifest by sharing your ideas and feature requests.
97+
</p>
98+
</a>
8299
</nav>
83100
);
84101
};

packages/frontend/src/styles/theme.css

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ h1, h2, h3, h4, h5, h6 {
713713
left: 0;
714714
background: hsl(var(--sidebar-background));
715715
border-right: 1px solid hsl(var(--sidebar-border));
716-
padding: var(--gap-lg) 0;
716+
padding: var(--gap-lg) 0 0 0;
717717
display: flex;
718718
flex-direction: column;
719719
z-index: 10;
@@ -772,6 +772,39 @@ h1, h2, h3, h4, h5, h6 {
772772
font-weight: 500;
773773
}
774774

775+
.sidebar__spacer {
776+
flex: 1;
777+
}
778+
779+
.sidebar__feedback {
780+
display: block;
781+
padding: var(--gap-md) var(--gap-lg);
782+
border-top: 1px solid hsl(var(--sidebar-border));
783+
text-decoration: none;
784+
color: hsl(var(--sidebar-foreground));
785+
transition: background var(--transition-fast);
786+
cursor: pointer;
787+
}
788+
789+
.sidebar__feedback:hover {
790+
background: hsl(var(--sidebar-accent));
791+
}
792+
793+
.sidebar__feedback-title {
794+
display: flex;
795+
align-items: center;
796+
gap: var(--gap-sm);
797+
font-size: var(--font-size-sm);
798+
font-weight: 500;
799+
}
800+
801+
.sidebar__feedback-hint {
802+
margin-top: var(--gap-xs);
803+
font-size: var(--font-size-xs);
804+
color: hsl(var(--muted-foreground));
805+
line-height: 1.4;
806+
}
807+
775808
.sidebar__footer {
776809
margin-top: auto;
777810
padding: var(--gap-md) var(--gap-lg);

0 commit comments

Comments
 (0)