Skip to content

demo hosting#16

Merged
hunterphillips merged 2 commits intomainfrom
hosting
Jan 8, 2026
Merged

demo hosting#16
hunterphillips merged 2 commits intomainfrom
hosting

Conversation

@hunterphillips
Copy link
Copy Markdown
Owner

@hunterphillips hunterphillips commented Jan 8, 2026

PR Type

Enhancement


Description

  • Add demo mode with simulated AI responses for hosted showcase

  • Implement fake streaming responses mimicking AI SDK format

  • Create demo banner component to indicate demo mode status

  • Add build configuration for Vercel deployment with demo mode

  • Export setMessages hook dependency for demo functionality


Diagram Walkthrough

flowchart LR
  A["User Input"] --> B["Demo Mode Check"]
  B -->|Demo Enabled| C["getDemoResponse"]
  C --> D["createFakeStream"]
  D --> E["Streaming Effect"]
  E --> F["Display in Chat"]
  B -->|Demo Disabled| G["Real API Call"]
  G --> F
  H["DemoBanner"] --> F
Loading

File Walkthrough

Relevant files
Enhancement
demo-stream.ts
Demo streaming response system implementation                       

client/src/lib/demo-stream.ts

  • New file implementing demo mode streaming responses with canned
    answers
  • getDemoResponse() function matches user input to predefined responses
  • createFakeStream() generates fake readable stream in AI SDK format
  • handleDemoSubmit() processes demo messages with simulated typing
    effect
+183/-0 
useChatSession.ts
Integrate demo mode into chat session hook                             

client/src/hooks/useChatSession.ts

  • Import handleDemoSubmit from demo-stream module
  • Add DEMO_MODE environment variable check
  • Route to demo handler when demo mode is enabled
  • Export setMessages in dependencies array for demo functionality
+10/-0   
DemoBanner.tsx
Demo mode banner component creation                                           

client/src/components/DemoBanner.tsx

  • New component displaying demo mode indicator banner
  • Shows yellow banner with demo mode notice and GitHub link
  • Only renders when VITE_DEMO_MODE environment variable is true
  • Includes call-to-action button linking to GitHub repository
+26/-0   
ChatView.tsx
Integrate demo banner into chat interface                               

client/src/components/chat/ChatView.tsx

  • Import and integrate DemoBanner component
  • Restructure layout to support banner placement
  • Add demo banner at bottom of chat area when in chat mode
  • Update flex layout structure for proper banner positioning
+47/-41 
Configuration changes
package.json
Add demo build script                                                                       

client/package.json

  • Add build:demo script that builds with demo mode enabled
  • Uses Vite --mode demo flag for environment configuration
+1/-0     
.env.demo
Demo environment configuration file                                           

client/.env.demo

  • New environment file for demo mode configuration
  • Sets VITE_DEMO_MODE=true to enable demo responses
  • Configures VITE_API_URL for local development fallback
+2/-0     
vercel.json
Vercel deployment configuration                                                   

vercel.json

  • New Vercel deployment configuration file
  • Specifies build command using demo build script
  • Sets output directory to client/dist
  • Configures Vite as framework with npm install
+6/-0     
Documentation
README.md
Add demo link to README                                                                   

README.md

  • Add demo link pointing to hosted demo at feather-demo.vercel.app
  • Note that demo uses simulated responses
+2/-0     

@qodo-code-review
Copy link
Copy Markdown

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Silent error handling: Demo streaming failures are only logged to the console without any user-visible fallback
state/message or cleanup, causing silent degradation in the UI if the stream breaks.

Referred Code
try {
  let buffer = '';
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split('\n');
    buffer = lines.pop() || ''; // Keep incomplete line in buffer

    for (const line of lines) {
      if (line.startsWith('0:')) {
        // Extract text content and unescape
        try {
          const jsonString = line.slice(2); // Get the part after "0:"
          const text = JSON.parse(jsonString); // Parse as JSON string
          assistantMessage.content += text;
        } catch (e) {
          // Fallback if JSON.parse fails
          const text = line.slice(2).replace(/^"|"$/g, '');
          assistantMessage.content += text;


 ... (clipped 18 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Unstructured console logging: The demo stream error path logs raw error objects via console.error, which may include
sensitive runtime details and is not structured for auditing.

Referred Code
} catch (error) {
  console.error('Demo stream error:', error);
}

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@claude
Copy link
Copy Markdown

claude Bot commented Jan 8, 2026

Pull Request Review: Demo Mode Implementation

Overview

This PR adds a demo mode feature to allow hosting a public showcase with simulated AI responses. The implementation is well-structured and achieves its goal effectively.


Strengths

Code Quality

  • Clean separation of concerns: Demo logic is isolated in demo-stream.ts, making it easy to maintain and test
  • Good use of TypeScript: Type safety is maintained throughout with proper type annotations
  • Well-documented: JSDoc comments clearly explain function purposes
  • Minimal invasiveness: Demo mode integration doesn't clutter existing code paths

Implementation

  • Clever streaming simulation: The fake stream implementation (createFakeStream) accurately mimics the AI SDK format, providing realistic UX
  • Environment-based configuration: Using VITE_DEMO_MODE makes it easy to toggle between real and demo modes
  • Vercel deployment ready: The vercel.json configuration is properly set up

⚠️ Issues & Concerns

1. Type Safety Issues (Medium Priority)

Location: client/src/lib/demo-stream.ts:116

setMessages: (updater: (prev: any[]) => any[]) => void

Problem: Using any defeats TypeScript's purpose and hides potential type mismatches.

Recommendation: Import proper types from @ai-sdk/react:

import type { Message } from '@ai-sdk/react';

export async function handleDemoSubmit(
  input: string,
  setMessages: (updater: (prev: Message[]) => Message[]) => void,
  setInput: (value: string) => void
): Promise<void>

2. Missing Error Handling (Medium Priority)

Location: client/src/lib/demo-stream.ts:180-182

} catch (error) {
  console.error('Demo stream error:', error);
}

Problem: Errors are logged but not surfaced to the user. If the stream fails, the assistant message might be incomplete or missing.

Recommendation:

  • Add user-facing error handling
  • Consider showing a toast notification or error message
  • Set an error state on the message object

3. Unused Dependency Export (Low Priority)

Location: client/src/hooks/useChatSession.ts:178

The PR description mentions exporting setMessages for demo functionality, and it's added to the dependency array at line 178. However, this doesn't actually "export" it - the function is already returned by useChat and available in the hook's return value. The dependency array is for useCallback memoization only.

Impact: No functional issue, but the PR description might be misleading.


4. Commented-Out Code (Low Priority)

Location: client/src/components/chat/ChatView.tsx:75, 88

{/* <DemoBanner /> */}

Problem: Two commented-out DemoBanner instances suggest indecision about placement. The banner only renders at line 118 (inside the chat layout).

Recommendation: Remove commented code or add a comment explaining why these positions were rejected.


5. Potential Race Condition (Low Priority)

Location: client/src/lib/demo-stream.ts:167-176

The message update logic checks if the message exists before updating, but in the initial state it won't exist. The code handles this with an else branch that adds it, but this creates a brief moment where the user sees no assistant response.

Recommendation: Consider adding the assistant message with empty content immediately after the user message (similar to how the real API works).


6. Magic Numbers (Low Priority)

Location: client/src/lib/demo-stream.ts:86, 98-99

const chunkSize = Math.floor(Math.random() * 4) + 2;
setTimeout(resolve, Math.random() * 60 + 20)

Recommendation: Extract to named constants:

const CHUNK_SIZE_MIN = 2;
const CHUNK_SIZE_MAX = 6;
const TYPING_DELAY_MIN_MS = 20;
const TYPING_DELAY_MAX_MS = 80;

7. Security: Hardcoded URL in Banner (Low Priority)

Location: client/src/components/DemoBanner.tsx:16

The GitHub URL is hardcoded. Consider making it configurable via environment variable or pulling from package.json repository field.


🧪 Test Coverage

Major Gap: No tests for the new demo functionality.

Recommended tests:

  1. Unit test for getDemoResponse trigger matching
  2. Unit test for createFakeStream stream format
  3. Integration test for demo mode end-to-end flow
  4. Test that demo mode properly bypasses API calls

Note: The repository has server tests but no client tests. Consider adding Vitest or React Testing Library for frontend testing.


🚀 Performance Considerations

  • Minimal impact: Demo code only runs when DEMO_MODE=true
  • Stream efficiency: Character-by-character streaming is fine for demo purposes
  • No memory leaks detected: Stream cleanup looks correct

🔒 Security Considerations

Good:

  • No API keys or sensitive data in demo mode
  • Environment variables properly namespaced with VITE_ prefix
  • Demo responses are static/hardcoded (no injection risk)

⚠️ Minor concern:

  • The .env.demo file includes VITE_API_URL=http://localhost:3001. While this won't be used in demo mode, it could be confusing. Consider explicitly documenting that this value is ignored when VITE_DEMO_MODE=true.

📝 Documentation

Good:

  • README updated with demo link
  • JSDoc comments in demo-stream.ts

Could improve:

  • Add documentation explaining how to enable/disable demo mode locally
  • Document the trigger words for demo responses (or consider making them discoverable in the UI)

🎯 Recommendations Summary

High Priority

None - the code works as intended

Medium Priority

  1. Fix any types in demo-stream.ts
  2. Add user-facing error handling for stream failures

Low Priority

  1. Remove commented-out DemoBanner code
  2. Extract magic numbers to constants
  3. Add tests for demo functionality
  4. Consider improving initial message rendering

Overall Assessment

This is a well-executed feature that achieves its goal of enabling a hosted demo. The code is clean, maintainable, and properly isolated. The main improvements needed are around type safety and error handling - both fixable in a follow-up PR.

Recommendation: ✅ Approve with suggestions

The current implementation is production-ready for its intended use case (demo hosting). The issues identified are non-blocking but should be addressed to improve code quality and maintainability.


Great work on this feature! The demo mode will be valuable for showcasing Feather without requiring API keys. 🎉

Repository owner deleted a comment from qodo-code-review Bot Jan 8, 2026
@claude
Copy link
Copy Markdown

claude Bot commented Jan 8, 2026

PR Review: Demo Hosting Mode

Great work implementing the demo mode functionality! This is a well-thought-out solution for hosting a showcase version. Below is my detailed review:


Strengths

  1. Clean Architecture: The separation of demo logic into demo-stream.ts keeps concerns isolated and maintainable.
  2. Good UX: The streaming effect mimics real AI behavior, providing a realistic demo experience.
  3. Clear User Indication: The DemoBanner component clearly communicates demo mode status.
  4. Minimal Intrusion: Changes to existing files are well-contained, with demo mode as a conditional branch.

🐛 Potential Bugs & Issues

1. Non-deterministic Response Matching (client/src/lib/demo-stream.ts:61-73)

The getDemoResponse() function uses substring matching which can produce unpredictable results:

  • Multiple triggers can match the same input (e.g., "what can you do" matches both "what" and "can you")
  • First match wins, which may not be the most relevant response
  • Partial word matches could give false positives (e.g., "somewhere" contains "where")

Suggestion: Use more specific patterns or prioritize longer/more specific matches first.

2. Unsafe Non-null Assertion (client/src/lib/demo-stream.ts:72)

return DEMO_RESPONSES.find((r) => r.trigger.includes('default'))!.response;

The ! operator assumes the default response exists. If the array is accidentally modified, this will throw a runtime error.

Suggestion:

const defaultResponse = DEMO_RESPONSES.find((r) => r.trigger.includes('default'));
return defaultResponse?.response ?? 'Thanks for trying Feather!';

3. Commented Out Banner Placement (client/src/components/chat/ChatView.tsx:87)

There's a commented-out <DemoBanner /> at line 87, but it's active at line 117. This suggests uncertainty about placement or leftover debugging code.

Suggestion: Remove the commented line to avoid confusion.

4. Missing Error Type (client/src/lib/demo-stream.ts:180)

} catch (error) {
  console.error('Demo stream error:', error);

The error variable is implicitly any. Consider typing it for better error handling.

Suggestion:

} catch (error) {
  console.error('Demo stream error:', error instanceof Error ? error.message : error);

5. crypto.randomUUID() Browser Compatibility (client/src/lib/demo-stream.ts:122, 129)

crypto.randomUUID() requires HTTPS in production browsers. While likely fine for Vercel deployment, it could fail in some environments.

Suggestion: Consider adding a fallback or ensure the deployment is always HTTPS.


Performance Considerations

1. Unnecessary Re-renders During Streaming (client/src/lib/demo-stream.ts:167-176)

The code updates the entire message array on every chunk:

setMessages((prev) => {
  const existing = prev.find((m) => m.id === assistantMessage.id);
  if (existing) {
    return prev.map((m) =>
      m.id === assistantMessage.id ? { ...assistantMessage } : m
    );
  } else {
    return [...prev, assistantMessage];
  }
});

This creates a new array and searches through messages for every chunk (potentially 50+ times per response).

Impact: With longer responses or many messages, this could cause noticeable UI lag.

Suggestion: Track whether the message was added, then only update existing messages:

let messageAdded = false;
// ... in the streaming loop:
if (!messageAdded) {
  setMessages((prev) => [...prev, assistantMessage]);
  messageAdded = true;
} else {
  setMessages((prev) => 
    prev.map((m) => m.id === assistantMessage.id ? { ...assistantMessage } : m)
  );
}

2. Inconsistent Delay Calculation (client/src/lib/demo-stream.ts:97-100)

Comment says "20-80ms" but code implements Math.random() * 60 + 20 (20-80ms is correct).

Minor: This is accurate but the comment could be clearer about the formula.


🔒 Security Concerns

1. Environment Variable Validation (client/src/hooks/useChatSession.ts:28)

const DEMO_MODE = import.meta.env.VITE_DEMO_MODE === 'true';

This is good string comparison. However, consider if there's any risk of the environment variable being manipulated.

Status: ✅ Acceptable - build-time variable, no runtime security risk.

2. External Links (client/src/components/DemoBanner.tsx:15-22)

The GitHub link uses target="_blank" with rel="noopener noreferrer" - perfect! This prevents tabnapping attacks.

Status: ✅ Secure

3. No API Key Exposure

Demo mode bypasses the API entirely, so there's no risk of exposing API keys or credentials.

Status: ✅ Secure


🧪 Test Coverage

Issue: No tests found for the new functionality.

Missing Tests:

  1. getDemoResponse() - should test all trigger patterns
  2. createFakeStream() - should verify stream format matches AI SDK protocol
  3. handleDemoSubmit() - should test state updates and error handling
  4. DemoBanner - should test conditional rendering based on env var
  5. Integration test - verify demo mode flag properly routes to demo handler

Recommendation: Add unit tests for at least the core demo-stream functions to prevent regressions.


📝 Code Quality & Best Practices

1. Magic Numbers (client/src/lib/demo-stream.ts:88, 98-100)

const chunkSize = Math.floor(Math.random() * 4) + 2;
await new Promise((resolve) => setTimeout(resolve, Math.random() * 60 + 20));

Suggestion: Extract these as named constants:

const MIN_CHUNK_SIZE = 2;
const MAX_CHUNK_SIZE = 6; // Random range 2-5 becomes 2 + (0-4)
const MIN_DELAY_MS = 20;
const DELAY_RANGE_MS = 60;

2. Dependency Array Completeness (client/src/hooks/useChatSession.ts:169-181)

The handleSubmit dependency array is comprehensive and includes setMessages. Good! This ensures demo mode has access to the state setter.

Status: ✅ Correct

3. Type Safety

The types are generally good. The Message type from @ai-sdk/react is properly imported and used.

Status: ✅ Good


🎯 Additional Recommendations

  1. Environment File Security: Add .env.demo to .gitignore if it contains any non-public values (currently it only has VITE_DEMO_MODE=true, so this is okay).

  2. Banner Positioning: Consider whether the banner should also appear in empty chat state for consistency.

  3. Demo Response Extensibility: Consider moving DEMO_RESPONSES to a separate JSON file or config for easier content updates without code changes.

  4. Fallback Handling: In useChatSession.ts, demo mode doesn't create a chat ID. Verify this doesn't break any chat history expectations.

  5. Documentation: Consider adding a comment explaining the AI SDK stream format (0: prefix for content, d: for completion) for future maintainers.


📊 Summary

Category Status
Code Quality ⭐⭐⭐⭐ (4/5)
Security ⭐⭐⭐⭐⭐ (5/5)
Performance ⭐⭐⭐ (3/5) - Streaming optimization needed
Test Coverage ⭐ (1/5) - No tests
Overall ⭐⭐⭐⭐ (4/5)

Recommendation: Approve with minor changes. The core implementation is solid and secure. Address the performance issue with message updates during streaming, add the non-null assertion safety check, and consider adding basic tests before merging.

Great work on this feature! 🚀

@hunterphillips hunterphillips merged commit e3d171b into main Jan 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant