Skip to content

Add Netlify static portfolio demo - #34

Closed
tomnguyen103 wants to merge 2 commits into
mainfrom
codex/netlify-static-demo
Closed

Add Netlify static portfolio demo#34
tomnguyen103 wants to merge 2 commits into
mainfrom
codex/netlify-static-demo

Conversation

@tomnguyen103

@tomnguyen103 tomnguyen103 commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • add a static Netlify portfolio-demo mode selected by NEXT_PUBLIC_SECOND_BRAIN_DEMO_MODE=static
  • add public-safe fixture data, browser-side static API adapter, and optional SHA-256 passcode gate
  • add Netlify config and setup docs for zero-dollar static hosting without backend secrets

Verification

  • npm run lint
  • npm run build
  • NEXT_PUBLIC_SECOND_BRAIN_DEMO_MODE=static NEXT_PUBLIC_AGENTIC_RAG_ENABLED=true npm run build
  • static preview via python -m http.server 4173 --directory out
  • headless Edge checks for /chat/?cid=9001, /search/?q=mcp, /sources/, /status/, and passcode gate with dummy hash
  • git diff --check

Draft only: do not request CodeRabbit yet.

Summary by CodeRabbit

  • New Features

    • Zero-cost static portfolio demo deployable on Netlify using public fixture data (read-only)
    • Home page now shows a landing UI with an "Open chat" link in static demo mode
    • Optional browser-side passcode gate for demo access
  • Documentation

    • Added a runbook with Netlify setup, build/publish instructions, and verification steps
  • Chores

    • Added demo mode env examples and Netlify build/publish configuration

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4d3cbc27-f3a3-4d89-a187-d13b67fab99c

📥 Commits

Reviewing files that changed from the base of the PR and between c538b7a and bf0f293.

📒 Files selected for processing (8)
  • docs/PROGRESS.md
  • docs/portfolio-demo-netlify.md
  • frontend/app/page.tsx
  • frontend/lib/api/client-types.ts
  • frontend/lib/api/client.ts
  • frontend/lib/api/demo-client.ts
  • frontend/next.config.ts
  • netlify.toml
✅ Files skipped from review due to trivial changes (2)
  • docs/portfolio-demo-netlify.md
  • docs/PROGRESS.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • netlify.toml
  • frontend/next.config.ts
  • frontend/lib/api/client.ts
  • frontend/lib/api/demo-client.ts

📝 Walkthrough

Walkthrough

Adds a zero-dollar static portfolio demo mode: static Netlify export, browser-side fixture-backed ApiClient and search, optional SHA-256 passcode gate, conditional frontend routing/UI for demo mode, and runbook + Netlify config for deployment.

Changes

Netlify Static Portfolio Demo

Layer / File(s) Summary
Demo configuration and environment setup
frontend/.env.example, frontend/lib/demo/config.ts, frontend/next.config.ts, netlify.toml
Environment variables and configuration flags enable demo mode and optional passcode gating; Next.js conditionally applies static export settings; Netlify build configuration targets the frontend with Node 22 and security headers.
Demo fixture corpus and metadata
frontend/lib/demo/public-demo-data.ts
Exports public-safe fixture data: demo source identity, corpus documents with feature descriptions, source summary, seeded tasks/research jobs/briefings, feedback analytics with per-model stats, and evaluation candidates.
In-memory static search and ranking
frontend/lib/demo/static-search.ts
Implements deterministic document ranking: query tokenization with stop-word filtering, term-based scoring in title/tags/content with keyword bonuses, ranked result retrieval, and conversion to SearchHit/Citation objects.
Full demo ApiClient implementation
frontend/lib/api/demo-client.ts
Implements the complete ApiClient interface: health/status responses, read-only rejection for mutations, chat/chatStream with generated responses from ranked fixtures and citations, search and read flows for conversations/feedback/tasks/research/sources/documents, and localStorage-backed conversation persistence.
API client switching and type definition
frontend/lib/api/client.ts, frontend/lib/api/client-types.ts
Adds typed ApiClient surface, exposes liveApi, and exports api as conditional between demoApi and liveApi based on STATIC_DEMO_MODE.
Browser-side passcode access control
frontend/components/DemoAccessGate.tsx, frontend/lib/demo/config.ts
Implements DemoAccessGate: state machine, localStorage persistence, browser SHA-256 verification of passcode hash, gated rendering when enabled, and locked-state UI with errors.
Frontend UI integration and landing page
frontend/app/page.tsx, frontend/components/Providers.tsx, frontend/components/ConversationSidebar.tsx
Changes home page from redirect to rendered landing page with description and chat link; wraps app children with DemoAccessGate; makes sidebar tagline and bottom panel conditional on demo mode, showing a read-only demo notice instead of API token form.
Demo setup and deployment documentation
README.md, docs/PROGRESS.md, docs/implementation-notes.md, docs/portfolio-demo-netlify.md
Adds README note linking to Netlify deployment; PROGRESS.md entry documenting the feature; implementation notes describing the architecture and affected files; comprehensive Netlify runbook covering scope, passcode setup, local verification, and portfolio linking.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • tomnguyen103/second-brain#6: Shares API surface work (chat/search/conversations/feedback) that this PR implements in a demo client; both change frontend API wiring.

"I stitched a demo from fixtures bright,
A passcode gate to dim the night,
Browser search that ranks with care,
Chat streams trickle answers there,
Netlify hosts the public fair." 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 35.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Netlify static portfolio demo' accurately captures the primary change: introducing a static portfolio demo mode deployable on Netlify, which is the main objective of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/netlify-static-demo

Comment @coderabbitai help to get the list of available commands and usage tips.

@tomnguyen103
tomnguyen103 marked this pull request as ready for review June 8, 2026 02:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
docs/portfolio-demo-netlify.md (3)

31-35: ⚡ Quick win

Document the behavior when the passcode hash is omitted.

The runbook describes the passcode gate as "optional" (line 11) but doesn't clearly state what happens when NEXT_PUBLIC_DEMO_ACCESS_HASH is NOT set in Netlify. Based on the code context, omitting this variable disables the gate entirely and grants immediate access. Consider adding a note after line 35 to make this explicit.

📝 Suggested addition
 NEXT_PUBLIC_DEMO_ACCESS_HASH=<lowercase sha256 hash of your demo passcode>

+> Note: Omitting NEXT_PUBLIC_DEMO_ACCESS_HASH disables the passcode gate entirely and grants immediate access to all visitors. Only include this variable if you want casual access control for the static demo.

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/portfolio-demo-netlify.md around lines 31 - 35, Add an explicit note to
the demo Netlify docs stating the behavior when NEXT_PUBLIC_DEMO_ACCESS_HASH is
not set: explain that omitting NEXT_PUBLIC_DEMO_ACCESS_HASH disables the
passcode gate and grants immediate access to all visitors, and place this note
directly after the environment-variable instruction (the block that lists
NEXT_PUBLIC_DEMO_ACCESS_HASH) so readers know the default behavior when the
variable is absent.


</details>

<!-- cr-comment:v1:be600b87b9482d7b9681d47c -->

---

`73-78`: _⚡ Quick win_

**Clarify the purpose of the first build.**

The verification steps run `npm run build` twice (line 75 without demo env vars, line 78 with them). If the first build is intentional (e.g., to verify the normal build still works), add a comment explaining why. Otherwise, remove line 75 to avoid confusion.



<details>
<summary>📝 Suggested clarification</summary>

```diff
 npm ci
 npm run lint
-npm run build
+# Verify normal build still works
+npm run build
 $env:NEXT_PUBLIC_SECOND_BRAIN_DEMO_MODE="static"
 $env:NEXT_PUBLIC_AGENTIC_RAG_ENABLED="true"
+# Build static export for Netlify
 npm run build

Or remove the first build if it's not needed:

 npm ci
 npm run lint
-npm run build
 $env:NEXT_PUBLIC_SECOND_BRAIN_DEMO_MODE="static"
 $env:NEXT_PUBLIC_AGENTIC_RAG_ENABLED="true"
 npm run build
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/portfolio-demo-netlify.md` around lines 73 - 78, The docs currently run
"npm run build" twice: once before setting demo env vars and again after setting
NEXT_PUBLIC_SECOND_BRAIN_DEMO_MODE and NEXT_PUBLIC_AGENTIC_RAG_ENABLED; either
remove the first "npm run build" if it is redundant, or keep it but add a short
comment above the first "npm run build" explaining its purpose (e.g., "verify
standard build succeeds before enabling demo env vars") so readers understand
why the build is performed twice; update the lines referencing the two "npm run
build" commands and the demo env vars (NEXT_PUBLIC_SECOND_BRAIN_DEMO_MODE,
NEXT_PUBLIC_AGENTIC_RAG_ENABLED) accordingly.

96-97: 💤 Low value

Specify where to add the portfolio link.

Line 97 suggests adding "the Netlify URL to the portfolio" but doesn't clarify whether this means updating README.md, a personal portfolio site, or somewhere else. Consider being more specific about the intended location.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/portfolio-demo-netlify.md` around lines 96 - 97, The instruction "After
Netlify deploys successfully, add the Netlify URL to the portfolio as a live
demo link" is ambiguous; update the doc text that currently begins "After
Netlify deploys successfully" to explicitly state where to add the link: tell
the reader to add the Netlify live-demo URL to their personal portfolio site and
to the project's README.md under a clearly labeled "Live Demo" or "Deployment"
section (keeping the repository link immediately adjacent, e.g., "Live demo:
<Netlify URL> (Repo: <repo URL>)"), and mention to verify the live demo link is
reachable after deployment.
netlify.toml (1)

15-19: 💤 Low value

Consider adding Content-Security-Policy header.

The current security headers provide good baseline protection. For enhanced security, consider adding a Content-Security-Policy header to restrict resource loading and mitigate XSS risks.

🔒 Example CSP header for static demo
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
Permissions-Policy = "camera=(), microphone=(), geolocation=(), payment=()"
Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'"

Note: Adjust 'unsafe-inline' and 'unsafe-eval' based on Next.js requirements for the static export. Test thoroughly to ensure the app functions correctly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netlify.toml` around lines 15 - 19, Add a Content-Security-Policy header
under the existing [headers.values] block (where X-Frame-Options,
X-Content-Type-Options, Referrer-Policy, and Permissions-Policy are defined) by
adding a Content-Security-Policy entry that restricts resource origins (e.g.,
default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src
'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;
connect-src 'self'), then adjust/remove 'unsafe-inline'/'unsafe-eval' as needed
for your Next.js/static export requirements and thoroughly test the app to
ensure no functionality breaks.
frontend/lib/api/demo-client.ts (1)

328-344: ⚡ Quick win

Add SSR safety guard to delay() function.

The delay() function uses window.setTimeout without checking if window is defined. While the static export mode (output: "export") means this code likely won't run during build, adding a guard would make the code more robust and consistent with the SSR checks elsewhere in the file (e.g., line 242).

🛡️ Proposed fix to add SSR safety
 function delay(ms: number, signal?: AbortSignal): Promise<void> {
+  if (typeof window === "undefined") {
+    return Promise.resolve();
+  }
   return new Promise((resolve, reject) => {
     if (signal?.aborted) {
       reject(new DOMException("Aborted", "AbortError"));
       return;
     }
     const timer = window.setTimeout(resolve, ms);
     signal?.addEventListener(
       "abort",
       () => {
         window.clearTimeout(timer);
         reject(new DOMException("Aborted", "AbortError"));
       },
       { once: true },
     );
   });
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/lib/api/demo-client.ts` around lines 328 - 344, The delay function
uses window.setTimeout/clearTimeout which breaks SSR; update delay(ms: number,
signal?: AbortSignal) to use a runtime-safe timer by switching window.setTimeout
and window.clearTimeout to a safe fallback (e.g., use
globalThis.setTimeout/globalThis.clearTimeout or (typeof window !== "undefined"
? window.setTimeout : setTimeout) and similarly for clearTimeout) so timers
still work in SSR, and keep the existing AbortSignal handling and DOMException
usage unchanged.
frontend/lib/api/client.ts (1)

40-41: ⚡ Quick win

Consider extracting ApiClient type to avoid circular imports.

The current structure creates a circular dependency:

  • client.ts imports demoApi from demo-client.ts (line 40)
  • demo-client.ts imports ApiClient type from client.ts

While this works because the import is type-only (import type), the circular dependency can make the codebase harder to reason about and may cause issues with certain bundlers or refactoring tools.

♻️ Recommended refactor to eliminate circular dependency

Create a new file frontend/lib/api/client-types.ts:

import type {
  AppStatusResponse,
  CaptureRequest,
  CaptureResponse,
  ChatRequest,
  ChatResponse,
  // ... other imports
} from "./types";

export interface ChatStreamHandlers {
  onDelta: (delta: ChatStreamDelta) => void;
  onComplete: (complete: ChatStreamComplete) => void;
  signal?: AbortSignal;
}

export interface ApiClient {
  getHealth(): Promise<HealthResponse>;
  getStatus(): Promise<AppStatusResponse>;
  capture(req: CaptureRequest): Promise<CaptureResponse>;
  // ... other methods
}

Then update imports:

  • In demo-client.ts: import type { ApiClient, ChatStreamHandlers } from "./client-types"
  • In client.ts: import type { ApiClient, ChatStreamHandlers } from "./client-types"

This eliminates the circular dependency while maintaining full type safety.

Also applies to: 447-449

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/lib/api/client.ts` around lines 40 - 41, Extract the ApiClient and
ChatStreamHandlers type definitions into a new module (e.g.,
frontend/lib/api/client-types.ts) and move any related type imports (e.g.,
ChatStreamDelta, ChatStreamComplete, HealthResponse, AppStatusResponse,
CaptureRequest/Response, ChatRequest/Response, etc.) into that file; then update
demo-client.ts and client.ts to import those types via `import type { ApiClient,
ChatStreamHandlers } from "./client-types"` so demo-client.ts no longer imports
types from client.ts, eliminating the circular dependency while preserving type
safety.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/app/page.tsx`:
- Around line 4-24: The current page component always renders a static landing
card with a Link to "/chat" and ignores STATIC_DEMO_MODE; update the component
(page.tsx) to conditionally redirect users to "/chat" when STATIC_DEMO_MODE is
false (or keep the landing page when true) by checking the STATIC_DEMO_MODE flag
at render/server-side and performing a Next.js redirect (or documented
intentional behavior) instead of always rendering the landing UI — locate the
Link with href="/chat" and the page export to add the conditional redirect logic
tied to STATIC_DEMO_MODE.

In `@frontend/next.config.ts`:
- Around line 6-8: turbopack.root currently uses process.cwd() which can be
incorrect in monorepos; change turbopack.root to derive an absolute path
relative to the config file instead (use __dirname and path.resolve with the
project folder or '.'), so update the turbopack config entry (symbol:
turbopack.root in next.config.ts) to compute a stable absolute path rather than
relying on process.cwd().

---

Nitpick comments:
In `@docs/portfolio-demo-netlify.md`:
- Around line 31-35: Add an explicit note to the demo Netlify docs stating the
behavior when NEXT_PUBLIC_DEMO_ACCESS_HASH is not set: explain that omitting
NEXT_PUBLIC_DEMO_ACCESS_HASH disables the passcode gate and grants immediate
access to all visitors, and place this note directly after the
environment-variable instruction (the block that lists
NEXT_PUBLIC_DEMO_ACCESS_HASH) so readers know the default behavior when the
variable is absent.
- Around line 73-78: The docs currently run "npm run build" twice: once before
setting demo env vars and again after setting NEXT_PUBLIC_SECOND_BRAIN_DEMO_MODE
and NEXT_PUBLIC_AGENTIC_RAG_ENABLED; either remove the first "npm run build" if
it is redundant, or keep it but add a short comment above the first "npm run
build" explaining its purpose (e.g., "verify standard build succeeds before
enabling demo env vars") so readers understand why the build is performed twice;
update the lines referencing the two "npm run build" commands and the demo env
vars (NEXT_PUBLIC_SECOND_BRAIN_DEMO_MODE, NEXT_PUBLIC_AGENTIC_RAG_ENABLED)
accordingly.
- Around line 96-97: The instruction "After Netlify deploys successfully, add
the Netlify URL to the portfolio as a live demo link" is ambiguous; update the
doc text that currently begins "After Netlify deploys successfully" to
explicitly state where to add the link: tell the reader to add the Netlify
live-demo URL to their personal portfolio site and to the project's README.md
under a clearly labeled "Live Demo" or "Deployment" section (keeping the
repository link immediately adjacent, e.g., "Live demo: <Netlify URL> (Repo:
<repo URL>)"), and mention to verify the live demo link is reachable after
deployment.

In `@frontend/lib/api/client.ts`:
- Around line 40-41: Extract the ApiClient and ChatStreamHandlers type
definitions into a new module (e.g., frontend/lib/api/client-types.ts) and move
any related type imports (e.g., ChatStreamDelta, ChatStreamComplete,
HealthResponse, AppStatusResponse, CaptureRequest/Response,
ChatRequest/Response, etc.) into that file; then update demo-client.ts and
client.ts to import those types via `import type { ApiClient, ChatStreamHandlers
} from "./client-types"` so demo-client.ts no longer imports types from
client.ts, eliminating the circular dependency while preserving type safety.

In `@frontend/lib/api/demo-client.ts`:
- Around line 328-344: The delay function uses window.setTimeout/clearTimeout
which breaks SSR; update delay(ms: number, signal?: AbortSignal) to use a
runtime-safe timer by switching window.setTimeout and window.clearTimeout to a
safe fallback (e.g., use globalThis.setTimeout/globalThis.clearTimeout or
(typeof window !== "undefined" ? window.setTimeout : setTimeout) and similarly
for clearTimeout) so timers still work in SSR, and keep the existing AbortSignal
handling and DOMException usage unchanged.

In `@netlify.toml`:
- Around line 15-19: Add a Content-Security-Policy header under the existing
[headers.values] block (where X-Frame-Options, X-Content-Type-Options,
Referrer-Policy, and Permissions-Policy are defined) by adding a
Content-Security-Policy entry that restricts resource origins (e.g., default-src
'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self'
'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src
'self'), then adjust/remove 'unsafe-inline'/'unsafe-eval' as needed for your
Next.js/static export requirements and thoroughly test the app to ensure no
functionality breaks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bb12df22-bb51-4571-aed7-f9aa3aec2d4c

📥 Commits

Reviewing files that changed from the base of the PR and between 32242c9 and c538b7a.

📒 Files selected for processing (16)
  • README.md
  • docs/PROGRESS.md
  • docs/implementation-notes.md
  • docs/portfolio-demo-netlify.md
  • frontend/.env.example
  • frontend/app/page.tsx
  • frontend/components/ConversationSidebar.tsx
  • frontend/components/DemoAccessGate.tsx
  • frontend/components/Providers.tsx
  • frontend/lib/api/client.ts
  • frontend/lib/api/demo-client.ts
  • frontend/lib/demo/config.ts
  • frontend/lib/demo/public-demo-data.ts
  • frontend/lib/demo/static-search.ts
  • frontend/next.config.ts
  • netlify.toml

Comment thread frontend/app/page.tsx
Comment thread frontend/next.config.ts
@tomnguyen103

Copy link
Copy Markdown
Owner Author

Moved this Netlify static portfolio demo into a separate repository for safer deployment isolation: https://github.com/tomnguyen103/second-brain-portfolio-demo. Closing this PR so the demo changes do not merge into the main second-brain repo.

@tomnguyen103
tomnguyen103 deleted the codex/netlify-static-demo branch June 26, 2026 19:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant