# Project export: Project No Cap

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: Project NoCap: Bringing clarity to the chaos of misinformation through real-time AI verification.
- Devpost: https://devpost.com/software/project-no-cap
- GitHub: https://github.com/aditya-nandyal1/nocaprepo
- Video: https://www.youtube.com/embed/QgJPSXN9QHw?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Cal Hacks: 3rd Overall)
- Team: 1 GitHub contributor(s) — aditya-nandyal1 (6 commits)

## Devpost submission (written by the team)

### Inspiration

Our team sees a significant issue in the increasing polarization of the United States populace and common media consumption. In hopes of tackling this problem, our team decided to explore the impacts of misinformation on these growing dynamics and find a means to mitigate any potential spread of false information. Throughout our research and personal experiences, we uncovered that misinformation is often being spread through biased/false publications and mismanaged word of mouth. From here, we determined that it would be far too difficult to ensure perfect accuracy on the publishing end so we settled for the next best thing which was a consumer centric implementation. Employing an AI-centered approach to information assurance, we were able to develop a convenient product that provides an additional layer of verification to any online readings or conversations. By creating an application that is multi-faceted and easy to apply, we’ve provided a new avenue by which individuals can access material on the internet without fear of misinformation or bias.

### What it does

This innovative project uses a three part solution to combat misinformation by checking for written bias, ensuring verbal accuracy, and providing a medium for productive debates. When the application is active on any website or article, any declarative statements are cross referenced with any other similar publications and any potential biases/incorrect information are listed to provide the reader more context on any content. Another use case of our project is when the user needs to verify information of a call/meeting in which case our tool will transform any speech into text to be analyzed and cross checked by various AI agents to determine how factual any declarative statements are. Finally, the application provides a medium by which individuals can engage in an AI moderated debate which can help people communicate their ideas effectively and without misleading or confusing others through false statements. In our unique combination of various future-forward technologies, users can be more confident of any information they receive regardless of the medium.

### How we built it

Our project required a diverse blend of different AI agents and tools to completely cover the complete breadth of different cases that we wanted to include. Our first task was to dissect the various technologies available and ensure that we could effectively fit together each piece of the puzzle without sacrificing efficiency or complexity of the solution. In order to organize our large tech stack, we spent a lot of time diagraming the interactions between different technologies and procuring necessary API credits or subscriptions. With the background work complete, we were able to begin leveraging Creao to put together a working back end and front end shell which were used to house all of our additional APIs and AI Agents. At this point we configured a speech to text then text to AI search agent pipeline which handled our entire fact checking work flow. At this point we spent some time maximizing efficiency with speech sensitivity and statement identification. Working with a large number of different implementation, we also had to split our program into a web app and phone application piece in order to create a fully comprehensive application from both mediums.

### Challenges we ran into

Our project was initially extremely ambitious and technically challenging which meant that we were plagued with various difficulties throughout the entire process. Since our project relied on the combined implementation of various different AI agents, we had to precisely navigate many different unique API’s and documentation which took a large amount of time. We originally found many of the technologies that we intended to implement were difficult to utilize in combination with other key technologies which led to many significant changes early on. Another difficulty that we had was ensuring a positive user experience while still maintaining reasonable computational complexity and effectively building out an MVP that still stayed true to our original expectations. With so many different moving parts in this project, we were working right up to the deadline and had to strategically cut some features in order to ensure that our product was reliable and consistent which led to various technical compromises. By making these decisions to cut back on certain technical aspects, we slightly decreased our project’s initial scope in exchange for time to further polishing our current application and ensure that all key features were sound. Another key component that we found difficult was our importance on underutilized and irregular applications of the various APIs and AI Agents which added further complexity to the final product. Regardless of all these hardships, we were able to persevere and achieve a satisfactory result.

### Accomplishments we're proud of

We are proud that throughout the competition we stayed well organized and maintained a solid work flow in spite of unexpected circumstances. Our team came into the competition with very ambitious expectations and we were able to adapt to new technologies while working under a severe time crunch. Throughout the entire event, our team communicated effectively and was able to make use of each member’s unique skills. At the end of the day, we stayed upbeat and effectively managed our time and energy to complete a technically complex product that upheld our original intentions.

### What we learned

Thanks to the incredible variety of innovative sponsors and the prominence of various emerging AI frameworks, our team gained hands-on experience with cutting-edge technologies and AI workflows. Due to strict time constraints, we needed to organize our thinking and leverage various generative AI agents while efficiently cutting unnecessary components in order to complete a fully integrated product. Additionally, we explored the various benefits of simultaneous use of different AI products to increase redundancy and accuracy through cross-examination. In the future we intend to expand upon these newfound skills that we acquired at Cal Hacks 12.0, growing into more complete engineers.

### What's next

In the future we would like to begin expanding our product to synthesize multiple different sources that the user is hoping to compare and allow for summarizing capabilities. Another feature that we believe could be practical is something along the lines of measuring confidence in a certain assurance and to potentially provide context for any situations with lower confidence. There are additionally many interesting avenues with regards to providing a backlog of alternate options of articles to expand the user’s perspective or background knowledge on a topic. In terms of technical refinements, we hope to expand to as many different platforms as possible to align with that goal of easy accessibility and to hopefully decrease the latency on some of the voice features.

## README (from the GitHub repository)

No Cap! — Live Fact-Checking & Opinion Detector (SwiftUI)

A SwiftUI app that listens to live speech, segments it into statements, and classifies each as either:

Declarative (objective, verifiable) → labeled TRUE or FALSE, with an explanation if false

Opinion (subjective) → labeled OPINION

It supports single-speaker and multi-party modes, shows the live transcript, and logs results in a slick, dark UI.

Stack: SwiftUI · AVFoundation · Speech · URLSession
APIs: Anthropic Claude (primary), Janitor AI (optional first-try), Vapi (optional transcription assist)

✨ Features

🎙️ Live speech capture with partial results and auto-restart on errors

🧠 Unified fact-checker: tries Janitor AI once, permanently falls back to Claude on auth/parse failures

🔁 Silence-based segmentation (configurable) for natural, sentence-like chunks

👥 Multi-party mode (Speaker 1 / Speaker 2 toggling) or single-speaker mode

📋 Live analysis log with badges: TRUE / FALSE / OPINION and inline explanations

🛡️ Resilient networking with explicit error surfacing in the UI

🖼️ Screens

Mode Selector → Single vs Multi-party

Record/Stop → Big mic or stop button

Current Speech → Live transcript

Live Analysis Log → Reverse chronological cards with badges and timestamps


## Detected evidence (automated analysis)

Indexed codebase: 111 recognized source files, 514 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (120 of 136)

```
.DS_Store
.gitignore
LaunchTests
README.md
Tests
TruthCheckerApp
UITests
WebApp/.DS_Store
WebApp/creao-build-68fde00471e295f4c885fa10/.DS_Store
WebApp/creao-build-68fde00471e295f4c885fa10/API_SETUP.md
WebApp/creao-build-68fde00471e295f4c885fa10/biome.json
WebApp/creao-build-68fde00471e295f4c885fa10/components.json
WebApp/creao-build-68fde00471e295f4c885fa10/config/eslint/base.config.js
WebApp/creao-build-68fde00471e295f4c885fa10/config/eslint/eslint.mcp.config.js
WebApp/creao-build-68fde00471e295f4c885fa10/config/eslint/eslint.radix.config.js
WebApp/creao-build-68fde00471e295f4c885fa10/config/eslint/index.js
WebApp/creao-build-68fde00471e295f4c885fa10/config/eslint/rules/mcp-integration.js
WebApp/creao-build-68fde00471e295f4c885fa10/config/eslint/rules/radix-ui.js
WebApp/creao-build-68fde00471e295f4c885fa10/config/eslint/validate-configs.js
WebApp/creao-build-68fde00471e295f4c885fa10/config/hooks/display_hooks.sh
WebApp/creao-build-68fde00471e295f4c885fa10/config/hooks/dump/ensure_check_ok.sh
WebApp/creao-build-68fde00471e295f4c885fa10/config/hooks/ensure_build_ok.sh
WebApp/creao-build-68fde00471e295f4c885fa10/config/hooks/ensure_check_ok.sh
WebApp/creao-build-68fde00471e295f4c885fa10/config/hooks/file_tracker.sh
WebApp/creao-build-68fde00471e295f4c885fa10/config/hooks/git_diff.sh
WebApp/creao-build-68fde00471e295f4c885fa10/config/hooks/resume_prompt_injection.sh
WebApp/creao-build-68fde00471e295f4c885fa10/config/hooks/startup_cleanup.sh
WebApp/creao-build-68fde00471e295f4c885fa10/config/hooks/startup_prompt_injection.sh
WebApp/creao-build-68fde00471e295f4c885fa10/config/hooks/subagent_post_use.sh
WebApp/creao-build-68fde00471e295f4c885fa10/config/scripts/startup.sh
WebApp/creao-build-68fde00471e295f4c885fa10/config/vite/creao-plugin.mjs
WebApp/creao-build-68fde00471e295f4c885fa10/eslint.config.js
WebApp/creao-build-68fde00471e295f4c885fa10/IMPLEMENTATION_SUMMARY.md
WebApp/creao-build-68fde00471e295f4c885fa10/index.html
WebApp/creao-build-68fde00471e295f4c885fa10/MICROPHONE_FIX_EXPLAINED.md
WebApp/creao-build-68fde00471e295f4c885fa10/MICROPHONE_PERMISSION_FIX.md
WebApp/creao-build-68fde00471e295f4c885fa10/package.json
WebApp/creao-build-68fde00471e295f4c885fa10/public/manifest.json
WebApp/creao-build-68fde00471e295f4c885fa10/public/robots.txt
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/AppTest.test.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/orm/client.ts
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/orm/common.ts
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/orm/orm_declarative_statement.ts
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/orm/orm_misinformation_group.ts
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/orm/orm_speaker_queue.ts
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/orm/orm_user_session.ts
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/orm/orm_verification_result.ts
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/resource/declarative_statement.json
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/resource/misinformation_group.json
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/resource/speaker_queue.json
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/resource/user_session.json
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/resource/verification_result.json
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/schema/declarative_statement.json
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/schema/misinformation_group.json
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/schema/speaker_queue.json
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/schema/user_session.json
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/data/schema/verification_result.json
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ErrorBoundary.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/FloatingBanner.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/HomepagePlaceholder.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/MicrophonePermissionAlert.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/SpeakerQueuePanel.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/TranscriptionDisplay.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/accordion.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/alert-dialog.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/alert.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/aspect-ratio.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/avatar.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/badge.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/breadcrumb.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/button.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/calendar.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/card.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/carousel.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/chart.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/checkbox.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/collapsible.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/command.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/context-menu.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/dialog.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/drawer.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/dropdown-menu.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/form.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/hover-card.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/input-otp.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/input.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/label.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/menubar.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/navigation-menu.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/pagination.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/popover.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/progress.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/radio-group.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/resizable.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/scroll-area.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/select.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/separator.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/sheet.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/sidebar.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/skeleton.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/slider.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/sonner.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/switch.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/table.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/tabs.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/textarea.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/toggle-group.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/toggle.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/ui/tooltip.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/UnifiedTranscriptionPanel.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/VerificationResultsPanel.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/components/VoiceInputButton.tsx
WebApp/creao-build-68fde00471e295f4c885fa10/src/config/api-config.ts
WebApp/creao-build-68fde00471e295f4c885fa10/src/hooks/use-app-config.ts
WebApp/creao-build-68fde00471e295f4c885fa10/src/hooks/use-auth.ts
WebApp/creao-build-68fde00471e295f4c885fa10/src/hooks/use-mcp-client.ts
WebApp/creao-build-68fde00471e295f4c885fa10/src/hooks/use-mobile.ts
WebApp/creao-build-68fde00471e295f4c885fa10/src/lib/auth-integration.ts
WebApp/creao-build-68fde00471e295f4c885fa10/src/lib/jwt-utils.ts
WebApp/creao-build-68fde00471e295f4c885fa10/src/lib/react-jsx-dev.js
[16 more files omitted for size]
```

### Dependencies

- WebApp/creao-build-68fde00471e295f4c885fa10/package.json: @babel/plugin-syntax-typescript@^7.27.1, @babel/plugin-transform-react-jsx@^7.27.1, @babel/plugin-transform-react-jsx-development@^7.27.1, @biomejs/biome@1.9.4, @fortawesome/fontawesome-svg-core@^7.0.0, @fortawesome/free-brands-svg-icons@^7.0.0, @fortawesome/react-fontawesome@^0.2.3, @hookform/resolvers@^5.0.1, @protobuf-ts/runtime@^2.11.1, @radix-ui/react-accordion@^1.2.11, @radix-ui/react-alert-dialog@^1.1.14, @radix-ui/react-aspect-ratio@^1.1.7, @radix-ui/react-avatar@^1.1.10, @radix-ui/react-checkbox@^1.3.2, @radix-ui/react-collapsible@^1.1.11, @radix-ui/react-context-menu@^2.2.15, @radix-ui/react-dialog@^1.1.14, @radix-ui/react-dropdown-menu@^2.1.15, @radix-ui/react-hover-card@^1.1.14, @radix-ui/react-label@^2.1.7, @radix-ui/react-menubar@^1.1.15, @radix-ui/react-navigation-menu@^1.2.13, @radix-ui/react-popover@^1.1.14, @radix-ui/react-progress@^1.1.7, @radix-ui/react-radio-group@^1.3.7, @radix-ui/react-scroll-area@^1.2.9, @radix-ui/react-select@^2.2.5, @radix-ui/react-separator@^1.1.7, @radix-ui/react-slider@^1.3.5, @radix-ui/react-slot@^1.2.3, @radix-ui/react-switch@^1.2.5, @radix-ui/react-tabs@^1.1.12, @radix-ui/react-toggle@^1.1.9, @radix-ui/react-toggle-group@^1.1.10, @radix-ui/react-tooltip@^1.2.7, @tailwindcss/vite@^4.0.6, @tanstack/react-query@^5.80.3, @tanstack/react-router@^1.114.3, @tanstack/react-router-devtools@^1.114.3, @tanstack/router-plugin@^1.114.3, @testing-library/dom@^10.4.0, @testing-library/jest-dom@^6.6.3, @testing-library/react@^16.2.0, @types/lodash@^4.17.20, @types/react@^19.0.8, @types/react-dom@^19.0.3, @typescript-eslint/eslint-plugin@^8.38.0, @typescript-eslint/parser@^8.38.0, @typescript/native-preview@^7.0.0-dev.20250913.1, @vitejs/plugin-react@^4.3.4, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@^1.1.1, date-fns@^3.6.0, embla-carousel-react@^8.6.0, eslint@^9.32.0, eslint-plugin-react@^7.37.5, eslint-plugin-react-hooks@^5.2.0, input-otp@^1.4.2, jsdom@^26.0.0, lodash@^4.17.21, lucide-react@^0.476.0, next-themes@^0.4.6, openapi-fetch@^0.14.0, openapi-react-query@^0.5.0, openapi-typescript@^7.8.0, react@^19.0.0, react-day-picker@^9.8.0, react-dom@^19.0.0, react-hook-form@^7.57.0, react-resizable-panels@^3.0.2, recharts@^2.15.3, sonner@^2.0.6, tailwind-merge@^3.0.2, tailwindcss@^4.0.6, tailwindcss-animate@^1.0.7, typescript@~5.8.3, vaul@^1.1.2, vite@npm:rolldown-vite@^7.1.9, vite-plugin-babel@^1.3.2, vite-plugin-svgr@^4.5.0, vitest@^3.2.4, web-vitals@^4.2.4, zod@^3.25.50

### Recent commits (newest first)

- Full Web APP (Zip and Unzip)
- updated readme
- Implemented Launch Tests
- Implemented UI Tests
- Implemented Test File
- Implemented No Cap Main File
- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### WebApp/creao-build-68fde00471e295f4c885fa10/MICROPHONE_PERMISSION_FIX.md

```markdown
# Microphone Permission Fix Guide

## Problem
If you accidentally denied microphone access, the browser will remember that decision and block NoCap from accessing your microphone.

## Solution - I've Added Helpful Features!

Your NoCap application now has **automatic permission detection and helpful error messages**!

### What's New

1. **Automatic Permission Detection** - The app now checks your microphone permission status automatically
2. **Helpful Error Messages** - If permissions are denied, you'll see clear instructions on how to fix it
3. **Visual Indicators** - The voice input button shows an error state when permissions are blocked
4. **Step-by-Step Instructions** - The app displays exactly how to re-enable microphone access

## How to Fix Denied Permissions

### Option 1: Using the App's Built-in Guide (Easiest!)
1. Open NoCap in your browser
2. You'll see a **red alert box** with step-by-step instructions
3. Follow the instructions shown in the alert
4. Click "Check Again" once you've made the changes

### Option 2: Manual Browser Settings

#### Chrome/Edge/Brave
1. Click the **lock icon** 🔒 or **site info icon** in the address bar (left side)
2. Find **"Microphone"** in the permissions list
3. Change it from "Block" to **"Allow"**
4. Refresh the page (or click "Check Again" in the app)

#### Firefox
1. Click the **lock icon** 🔒 in the address bar
2. Click **"Connection secure"** or **"More information"**
3. Click **"More Information"** > **"Permissions"** tab
4. Find **"Use the Microphone"** and click **"Allow"**
5. Refresh the page

#### Safari
1. Go to **Safari menu** > **Settings** > **Websites** > **Microphone**
2. Find your NoCap site in the list
3. Change the permission to **"Allow"**
4. Refresh the page

## Features I Added

### 1. Permission Status Checker (`src/services/voice-service.ts`)
- `checkMicrophonePermission()` - Checks current permission status
- `requestMicrophonePermission()` - Explicitly requests permission
- Better error handling with detailed permission status

### 2. Permission Alert Component (`src/components/MicrophonePermissionAlert.tsx`)
Shows different alerts based on permission status:
- ✅ **Granted** - Green success message
- ❌ **Denied** - Red alert with step-by-step fix instructions
- 🔵 **Prompt** - Blue alert with "Allow Microphone" button

### 3. Enhanced Voice Button (`src/components/VoiceInputButton.tsx`)
- Shows error icon when permissions are blocked
- Disables the button until permissions are fixed
- Displays helpful error messages

### 4. Main App Integration (`src/routes/index.tsx`)
- Tracks permission status throughout the session
- Shows the permission alert automatically when needed
- Clears errors once permissions are granted

## Testing the Fix

1. Open NoCap in your browser
2. If you see the permission alert, follow the instructions
3. Click "Check Again" after fixing permissions
4. The alert should turn green ✅
5. Click "Start Session" to begin using voice input

## What Happens Now
[truncated — 615 more characters]
```

### WebApp/creao-build-68fde00471e295f4c885fa10/MICROPHONE_FIX_EXPLAINED.md

```markdown
# Microphone Permission Issue - Root Cause & Fix

## The Problem

You changed your browser settings to allow microphone access for the site, but NoCap was still showing "Microphone Denied" error.

## Root Cause

The issue was with **when** and **how** we were checking microphone permissions:

### Before (Problematic Approach)
```typescript
// src/services/voice-service.ts:208-216 (OLD)
const permissionStatus = await this.checkMicrophonePermission();

if (permissionStatus === "denied") {
    throw error; // Blocked BEFORE trying getUserMedia()
}

this.audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
```

**Why This Failed:**
1. `navigator.permissions.query({ name: "microphone" })` can return **stale** or **cached** permission status
2. The query API doesn't always immediately reflect changes you make in browser settings
3. We were blocking access based on this unreliable check BEFORE actually trying to use the microphone

### After (Fixed Approach)
```typescript
// src/services/voice-service.ts:207-250 (NEW)
try {
    // Try to get microphone access immediately
    this.audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
    // Success! Permission granted
} catch (error) {
    // Only NOW do we know the real permission status
    if (error.name === "NotAllowedError") {
        // Browser explicitly denied permission
    }
}
```

**Why This Works:**
1. `getUserMedia()` is the **authoritative** permission check - it reflects the actual browser state
2. No preemptive blocking based on potentially stale data
3. Error handling gives us specific, actionable error messages

## Changes Made

### 1. `src/services/voice-service.ts:205-251`
- **Removed** preemptive permission check before `getUserMedia()`
- **Added** detailed error handling based on actual `DOMException` types:
  - `NotAllowedError` → Permission denied
  - `NotFoundError` → No microphone found
  - `NotReadableError` → Microphone in use by another app
- Permission status now determined from **real** browser response, not cached API query

### 2. `src/routes/index.tsx:73-117`
- **Clears** permission state at the start of each listening attempt
- **Sets** `permissionStatus = "granted"` only when `startListening()` succeeds
- Prevents showing stale "denied" alerts when permission is actually granted

## Testing Instructions

1. **Reset your browser's microphone permission** (set to "Ask" or "Block")
2. **Open NoCap** and click the voice input button
3. **Observe:**
   - If blocked: Shows red alert with fix instructions
   - If browser prompts: Shows permission dialog
   - If allowed: Starts listening immediately ✅

4. **Change permission in settings** while app is open
5. **Click voice button again**
6. **Verify:** Should reflect your new permission setting immediately

## Technical Details

### Browser Permission States
- **"prompt"**: Browser will ask user when `getUserMedia()` is called
- **"granted"**: User previously allowed access
- **"denied"
[truncated — 799 more characters]
```

### WebApp/creao-build-68fde00471e295f4c885fa10/package.json

```
{
  "name": "basic_template",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite --port 3000",
    "start": "vite --port 3000",
    "build": "npm run check:safe && vite build",
    "serve": "vite preview",
    "test": "vitest run",
    "check": "tsgo --noEmit && npm run lint:radix -- --fix && npm run format",
    "check:safe": "timeout 20 npm run check || (echo 'Check timed out after 20 seconds' && exit 1)",
    "format": "biome check --write --unsafe",
    "format:quiet": "biome check --write --unsafe --log-level=warn",
    "lint:radix": "grep -l -r '<SelectItem' src | xargs eslint --config config/eslint/eslint.radix.config.js",
    "lint:mcp": "eslint src --config config/eslint/eslint.mcp.config.js",
    "lint:validate": "node config/eslint/validate-configs.js",
    "prepare": "if [ -f ./bin/setup-githooks.sh ]; then ./bin/setup-githooks.sh; fi"
  },
  "dependencies": {
    "@fortawesome/fontawesome-svg-core": "^7.0.0",
    "@fortawesome/free-brands-svg-icons": "^7.0.0",
    "@fortawesome/react-fontawesome": "^0.2.3",
    "@hookform/resolvers": "^5.0.1",
    "@protobuf-ts/runtime": "^2.11.1",
    "@radix-ui/react-accordion": "^1.2.11",
    "@radix-ui/react-alert-dialog": "^1.1.14",
    "@radix-ui/react-aspect-ratio": "^1.1.7",
    "@radix-ui/react-avatar": "^1.1.10",
    "@radix-ui/react-checkbox": "^1.3.2",
    "@radix-ui/react-collapsible": "^1.1.11",
    "@radix-ui/react-context-menu": "^2.2.15",
    "@radix-ui/react-dialog": "^1.1.14",
    "@radix-ui/react-dropdown-menu": "^2.1.15",
    "@radix-ui/react-hover-card": "^1.1.14",
    "@radix-ui/react-label": "^2.1.7",
    "@radix-ui/react-menubar": "^1.1.15",
    "@radix-ui/react-navigation-menu": "^1.2.13",
    "@radix-ui/react-popover": "^1.1.14",
    "@radix-ui/react-progress": "^1.1.7",
    "@radix-ui/react-radio-group": "^1.3.7",
    "@radix-ui/react-scroll-area": "^1.2.9",
    "@radix-ui/react-select": "^2.2.5",
    "@radix-ui/react-separator": "^1.1.7",
    "@radix-ui/react-slider": "^1.3.5",
    "@radix-ui/react-slot": "^1.2.3",
    "@radix-ui/react-switch": "^1.2.5",
    "@radix-ui/react-tabs": "^1.1.12",
    "@radix-ui/react-toggle": "^1.1.9",
    "@radix-ui/react-toggle-group": "^1.1.10",
    "@radix-ui/react-tooltip": "^1.2.7",
    "@tailwindcss/vite": "^4.0.6",
    "@tanstack/react-query": "^5.80.3",
    "@tanstack/react-router": "^1.114.3",
    "@tanstack/react-router-devtools": "^1.114.3",
    "@tanstack/router-plugin": "^1.114.3",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "^1.1.1",
    "date-fns": "^3.6.0",
    "embla-carousel-react": "^8.6.0",
    "input-otp": "^1.4.2",
    "lodash": "^4.17.21",
    "lucide-react": "^0.476.0",
    "next-themes": "^0.4.6",
    "openapi-fetch": "^0.14.0",
    "openapi-react-query": "^0.5.0",
    "react": "^19.0.0",
    "react-day-picker": "^9.8.0",
    "react-dom": "^19.0.0",
    "react-hook-form": "^7.57.0",
    "react-resizable-panels": "^3.0.2",
    "recharts": "^2.15.3",
    "sonner": "^2.0.6",
    "tailwind-merge": "^3.0.2",
    "tailwindcss": "^4.0.6",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "^1.1.2",
    "zod": "^3.25.50"
  },
  "devDependencies": {
    "@babel/plugin-syntax-typescript": "^7.27.1",
    "@babel/plugin-transform-react-jsx": "^7.27.1",
    "@babel/plugin-transform-react-jsx-development": "^7.27.1",
    "@biomejs/biome": "1.9.4",
    "@testing-library/dom": "^10.4.0",
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.2.0",
    "@types/lodash": "^4.17.20",
    "@types/react": "^19.0.8",
    "@types/react-dom": "^19.0.3",
    "@typescript-eslint/eslint-plugin": "^8.38.0",
    "@typescript-eslint/parser": "^8.38.0",
    "@typescript/native-preview": "^7.0.0-dev.20250913.1",
    "@vitejs/plugin-react": "^4.3.4",
    "eslint": "^9.32.0",
    "eslint-plugin-react": "^7.37.5",
    "eslint-plugin-react-hooks": "^5.2.0",
    "jsdom": "^26.0.0",
    "openapi-typescript": "^7.8.0",
    "typescript": "~5.8.3",
    "vite": "npm:rolldown-vite@^7.1.9",
    "vite-plugin-babel": "^1.3.2",
    "vite-plugin-svgr": "^4.5.0",
    "vitest": "^3.2.4",
    "web-vitals": "^4.2.4"
  },
  "overrides": {
    "vite": "npm:rolldown-vite@^7.1.9"
  }
}

```

### WebApp/creao-build-68fde00471e295f4c885fa10/src/main.tsx

```typescript
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";

// Import the generated route tree
import { routeTree } from "./routeTree.gen";

import reportWebVitals from "./reportWebVitals.ts";
import "./styles.css";

// Import and initialize auth integration
import { initializeAuthIntegration } from "@/lib/auth-integration";

// Global URL parsing and variable setup
interface GlobalAppConfig {
	userId: string | null;
	projectId: string | null;
	taskId: string | null;
	workspaceId: string | null; // Combined projectId-taskId
	uploadFolder: string | null; // Upload folder path
	baseUrl: string | null;
	isValidBuildUrl: boolean;
}

function parseCurrentUrl(): GlobalAppConfig {
	const currentUrl = window.location.href;

	// Pattern: {base_url}/builds/{userId}/{projectId}/{taskId}/dist
	const buildUrlRegex =
		/^(https?:\/\/[^\/]+)\/builds\/([^\/]+)\/([^\/]+)\/([^\/]+)\/dist/;
	const match = currentUrl.match(buildUrlRegex);

	if (match) {
		const [, baseUrl, userId, projectId, taskId] = match;
		const workspaceId = `${projectId}-${taskId}`;
		return {
			userId,
			projectId,
			taskId,
			workspaceId,
			uploadFolder: "resources",
			baseUrl,
			isValidBuildUrl: true,
		};
	}

	// If not a build URL, return nulls
	return {
		userId: null,
		projectId: null,
		taskId: null,
		workspaceId: null,
		uploadFolder: null,
		baseUrl: null,
		isValidBuildUrl: false,
	};
}

// Parse URL and set global variables
const appConfig = parseCurrentUrl();

// Add to window global object for easy access
declare global {
	interface Window {
		APP_CONFIG: GlobalAppConfig;
	}
}

window.APP_CONFIG = appConfig;

// Also export for module imports
export const APP_CONFIG = appConfig;

// Log the parsed configuration
console.log("App Configuration:", {
	userId: appConfig.userId,
	projectId: appConfig.projectId,
	taskId: appConfig.taskId,
	workspaceId: appConfig.workspaceId,
	uploadFolder: appConfig.uploadFolder,
	baseUrl: appConfig.baseUrl,
	isValidBuildUrl: appConfig.isValidBuildUrl,
	currentUrl: window.location.href,
});

// Initialize auth integration automatically when app starts
initializeAuthIntegration();

// Create a QueryClient instance
const queryClient = new QueryClient({
	defaultOptions: {
		queries: {
			staleTime: 5 * 60 * 1000, // 5 minutes
			gcTime: 10 * 60 * 1000, // 10 minutes (previously cacheTime)
			retry: 1,
			refetchOnWindowFocus: false,
		},
	},
});

// Create a new router instance
const router = createRouter({
	routeTree,
	context: {},
	defaultPreload: "intent",
	scrollRestoration: true,
	defaultStructuralSharing: true,
	defaultPreloadStaleTime: 0,
	basepath: import.meta.env.TENANT_ID ? `/${import.meta.env.TENANT_ID}` : "/",
});

// Register the router instance for type safety
declare module "@tanstack/react-router" {
	interface Register {
		router: typeof router;
	}
}

// Render the app
const rootElement = document.getElementById("app");
if (rootElement && !rootElement.innerHTML) {
	const root = ReactDOM.createRoot(rootElement);
	root.render(
		<StrictMode>
			<QueryClientProvider client={queryClient}>
				<RouterProvider router={router} />
			</QueryClientProvider>
		</StrictMode>,
	);
}

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

```

### WebApp/creao-build-68fde00471e295f4c885fa10/config/eslint/index.js

```javascript
/**
 * ESLint Configuration Index
 * 
 * Centralized configuration management for different linting contexts.
 * This provides a single entry point for all ESLint configurations.
 */

import baseConfig from './base.config.js';
import mcpConfig from './eslint.mcp.config.js';
import radixConfig from './eslint.radix.config.js';
import radixRules from './rules/radix-ui.js';

export { baseConfig, mcpConfig, radixConfig };

// Default export combines base configuration with Radix validation
export default [
  ...baseConfig,
  {
    files: ['**/*.{ts,tsx}'],
    plugins: {
      'radix-custom': {
        rules: radixRules
      }
    },
    rules: {
      'radix-custom/no-empty-select-item': 'error'
    }
  }
]; 
```

### WebApp/creao-build-68fde00471e295f4c885fa10/src/routes/index.tsx

```typescript
import { MicrophonePermissionAlert } from "@/components/MicrophonePermissionAlert";
import { SpeakerQueuePanel } from "@/components/SpeakerQueuePanel";
import { UnifiedTranscriptionPanel } from "@/components/UnifiedTranscriptionPanel";
import { VoiceInputButton } from "@/components/VoiceInputButton";
import {
	type DeclarativeStatementModel,
	DeclarativeStatementORM,
	DeclarativeStatementStatementType,
	DeclarativeStatementVerificationStatus,
} from "@/components/data/orm/orm_declarative_statement";
import {
	type SpeakerQueueModel,
	SpeakerQueueORM,
	SpeakerQueueProcessingStatus,
} from "@/components/data/orm/orm_speaker_queue";
import {
	UserSessionMode,
	type UserSessionModel,
	UserSessionORM,
} from "@/components/data/orm/orm_user_session";
import { Badge } from "@/components/ui/badge";
import {
	Card,
	CardContent,
	CardDescription,
	CardHeader,
	CardTitle,
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import VerificationService, {
	type VerificationResult,
} from "@/services/verification-service";
import VoiceService, {
	type AudioLevelUpdate,
	type PermissionError,
	type PermissionStatus,
	type ProcessingStateUpdate,
	type TranscriptionResult,
} from "@/services/voice-service";
import { createFileRoute } from "@tanstack/react-router";
import { useCallback, useEffect, useState } from "react";

export const Route = createFileRoute("/")({
	component: App,
});

function App() {
	const [isListening, setIsListening] = useState(false);
	const [isProcessing, setIsProcessing] = useState(false);
	const [audioLevel, setAudioLevel] = useState(0);
	const [isMultiUserMode, setIsMultiUserMode] = useState(true);
	const [transcriptions, setTranscriptions] = useState<TranscriptionResult[]>(
		[],
	);
	const [speakerQueue, setSpeakerQueue] = useState<SpeakerQueueModel[]>([]);
	const [verificationResults, setVerificationResults] = useState<
		VerificationResult[]
	>([]);
	const [currentSession, setCurrentSession] = useState<UserSessionModel | null>(
		null,
	);
	const [processingStatement, setProcessingStatement] = useState<
		SpeakerQueueModel | undefined
	>();
	const [permissionStatus, setPermissionStatus] =
		useState<PermissionStatus>("unknown");
	const [permissionError, setPermissionError] = useState<string | null>(null);
	const [lastAudioTime, setLastAudioTime] = useState<number>(Date.now());
	const [isCurrentlySpeaking, setIsCurrentlySpeaking] = useState(false);

	// Initialize ORMs
	const userSessionORM = UserSessionORM.getInstance();
	const speakerQueueORM = SpeakerQueueORM.getInstance();
	const declarativeStatementORM = DeclarativeStatementORM.getInstance();

	const handleStartListening = useCallback(async () => {
		// Clear any previous errors
		setPermissionError(null);
		setPermissionStatus("unknown");

		// Create new session
		const newSession: Partial<UserSessionModel> = {
			mode: isMultiUserMode
				? UserSessionMode.MultiUser
				: UserSessionMode.SingleUser,
			start_time: new Date().toISOString(),
			is_active: true,
		};

		try {
			const sessions = await userSessionORM.insertUserSession([
				newSession as UserSessionModel,
			]);
			if (sessions.length > 0) {
				setCurrentSession(sessions[0]);
				// Set session ID for voice service context tracking
				VoiceService.setSessionId(sessions[0].id);
			}
		} catch (error) {
			console.error("Failed to create session:", error);
		}

		// Set up audio level callback with break detection
		VoiceService.setAudioLevelCallback((update: AudioLevelUpdate) => {
			setAudioLevel(update.level);

			// Detect audio breaks (silence after speech)
			if (update.level > 0.1) {
				// User is speaking
				setIsCurrentlySpeaking(true);
				setLastAudioTime(Date.now());
			} else {
				// Check if silence after speaking - use callback to get current state
				setIsCurrentlySpeaking((speaking) => {
					setLastAudioTime((lastTime) => {
						if (speaking && Date.now() - lastTime > 1000) {
							// Silence for more than 1 second after speaking - audio break detected
							setTimeout(() => setIsCurrentlySpeaking(false), 0);
						}
						return lastTime;
					});
					return speaking;
				});
			}
		});

		// Set up processing state callback
		VoiceService.setProcessingStateCallback((update: ProcessingStateUpdate) => {
			setIsProcessing(update.isProcessing);
		});

		// Start voice input
		try {
			await VoiceService.startListening(handleTranscription);
			setIsListening(true);
			setPermissionStatus("granted"); // If we got here, permission was granted
		} catch (error) {
			const permError = error as PermissionError;
			console.error("Failed to start listening:", permError);

			setPermissionError(
				permError.message ||
					"Failed to access microphone. Please check your browser permissions.",
			);

			// Update permission status
			if (permError.permissionStatus) {
				setPermissionStatus(permError.permissionStatus);
			}
		}
	}, [isMultiUserMode, userSessionORM]);

	const handleStopListening = useCallback(async () => {
		// Stop voice input
		await VoiceService.stopListening();
		setIsListening(false);
		setIsProcessing(false);
		setAudioLevel(0);

		// Update session
		if (currentSession) {
			const updatedSession: UserSessionModel = {
				...currentSession,
				end_time: new Date().toISOString(),
				is_active: false,
			};

			try {
				await userSessionORM.setUserSessionById(
					currentSession.id,
					updatedSession,
				);
				setCurrentSession(null);
			} catch (error) {
				console.error("Failed to update session:", error);
			}
		}
	}, [currentSession, userSessionORM]);

	const handleTranscription = useCallback(
		async (result: TranscriptionResult) => {
			// Add to transcriptions display
			setTranscriptions((prev) => {
				const newTranscriptions = [...prev, result];
				console.log(
					`📥 [UI RECEIVED] Transcription: "${result.text}" (type: ${result.statementType
[truncated — 10748 more characters]
```

### WebApp/creao-build-68fde00471e295f4c885fa10/eslint.config.js

```javascript
/**
 * Main ESLint Configuration
 * 
 * This is the primary ESLint configuration that combines base settings
 * with Radix UI validation. For specialized linting (like MCP validation),
 * use the specific configurations in config/eslint/
 */

import eslintConfig from './config/eslint/index.js';

export default eslintConfig;
```

### WebApp/creao-build-68fde00471e295f4c885fa10/index.html

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="icon" href="/favicon.ico" type="image/x-icon" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="NoCap - Multi-User AI Mediator for Truth Verification. Real-time voice monitoring with multi-agent consensus to identify and correct false declarative statements."
    />
    <link rel="apple-touch-icon" href="/creao_icon.svg" type="image/svg+xml" />
    <link rel="manifest" href="/manifest.json" />
    <title>NoCap - AI Truth Verification Mediator</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

### WebApp/creao-build-68fde00471e295f4c885fa10/vite.config.js

```javascript
import tailwindcss from "@tailwindcss/vite";
import viteReact from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import svgr from "vite-plugin-svgr";

// import { createRequire } from "node:module";
import { resolve } from "node:path";
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
import { creaoPlugins } from "./config/vite/creao-plugin.mjs";

// https://vitejs.dev/config/
export default defineConfig({
	base: process.env.TENANT_ID ? `/${process.env.TENANT_ID}/` : "/",
	define: {
		"import.meta.env.TENANT_ID": JSON.stringify(process.env.TENANT_ID || ""),
	},
	plugins: [
		...creaoPlugins(),
		TanStackRouterVite({
			autoCodeSplitting: false, // affects pick-n-edit feature. disabled for now.
		}),
		viteReact({
			jsxRuntime: "automatic",
		}),
		svgr(),
		tailwindcss(),
	],
	test: {
		globals: true,
		environment: "jsdom",
	},
	resolve: {
		alias: {
			"@": resolve(__dirname, "./src"),
		},
	},
	server: {
		host: true, // Listen on all addresses including localhost and network IPs
		port: 3000,
		strictPort: false, // Allow fallback to another port if 3000 is taken
		allowedHosts: true, // respond to *any* Host header
		watch: {
			usePolling: true,
			interval: 300, // ms; tune if CPU gets high
		},
	},
	build: {
		chunkSizeWarningLimit: 1500,
	},
});

```

### WebApp/creao-build-68fde00471e295f4c885fa10/src/vite-env.d.ts

```typescript
/// <reference types="vite/client" />
/// <reference types="vite-plugin-svgr/client" />

interface ImportMetaEnv {
	readonly TENANT_ID?: string;
	// add more env vars as needed
}

interface ImportMeta {
	readonly env: ImportMetaEnv;
}

```

[101 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]