Generated: 2026-08-17 by automated analysis + manual review.
Scope: Core DualMind_UI application (/home/ubuntu/DualMind_UI), excluding node_modules, old-html-backup, next-dualmind, admin-email-system.
This is a living document. Update it as issues are fixed.
- Type: Vanilla-JS single-page application with Cloudflare Worker proxy + Supabase client-side auth.
- Size: ~22k logical non-comment code lines across 126 source files (excluding prototypes).
- Build: Custom Node copy scripts; no bundler.
- Test/CI: Broken out of the box (
npm run lintglob fails, Playwright not installed). - Critical Runtime Bugs:
login/auth-complete.jsreferences undefined variables. - Security: Open CORS on worker, localStorage tokens,
innerHTMLusage in 64 places. - Architecture Risk: Multiple overlapping prototypes in the same repo; globals-heavy dependency graph.
| Check | Result |
|---|---|
npm install |
OK (218 packages) |
npm audit |
0 vulnerabilities after npm audit fix |
npm run lint original |
FAIL — unquoted shell glob admin-email-system/** |
npm run lint (quoted glob) |
28 warnings, 0 errors |
npm run test |
FAIL — @playwright/test not installed |
npm run validate |
FAIL — depends on lint + test |
(From pygount, excluding prototypes)
| Language | Files | Code Lines |
|---|---|---|
| CSS | 18 | 8,485 |
| JavaScript | 39 | 7,059 |
| HTML | 17 | 3,353 |
| Other | 52 | 3,175 |
| Total | 126 | ~22,072 |
| File/Directory | Risk |
|---|---|
App.jsx |
React prototype; not imported by index.html |
next-dualmind/ |
Entire Next.js prototype |
old-html-backup/ |
Dead archive |
arena-core.js |
Older arena class; not imported in prod |
js/api-client.js |
Deprecated shim |
js/auth.js, js/auth/api-service.js |
Legacy auth wrappers |
build-deploy.js + wrangler-dist.toml |
Redundant build path |
login-modern.htmlvslogin/index.htmlvssignup-modern.html/verify.html/auth-verify.html- Multiple auth entry points increase maintenance and fragment analytics.
Undefined symbols (from ESLint):
emailInputlines 60, 63setLoadinglines 68, 79, 108, 123, 133, 158supabaseClientlines 69, 109, 136SITE_URLline 70
These will throw ReferenceError and break email/phone auth flows immediately.
Lines 119-120 attempt to read process.env, which does not exist in browsers. Fallbacks include a real Supabase project URL and partial anon key hardcoded in the repo. This makes local/back-end env substitution impossible.
Lines 7-11 set Access-Control-Allow-Origin: * for every /api/* response. Any website can invoke your backend through a user's browser.
- Line 40 forwards
request.headersunfiltered to the backend. fetch()to backend has no timeout or retry logic.- Backend errors (
error.toString()) are returned to the client around line 61.
- Classes: App
- Key methods: constructor, waitForAuth, init, checkBackendAvailability, showBackendUnavailableBanner, hideBackendUnavailableBanner, fetchModels, prettifyModelName, getModelIdByName, setup, showOfflineIndicator, attachGlobalListeners, _openExportDropdown, handleNavigation, handleChatSubmit, renderChat, cancelStreams, runArenaDemo, runArenaApi, runDirectDemo, runDirectApi, showFloatingVoting, hideFloatingVoting, handleVoteSubmit, updateTurnVisibilityAfterVote, cleanModelName, resetTtsButton, stopTextToSpeech, handleTextToSpeech, syncUserWithBackend
- Classes: ChatView
- Key methods: constructor, setupMarkdown, renderMarkdown, setState, appendTurn, clear, render, renderEmptyArena, renderArena, renderTurn, renderResponseCard, ensureResponseModal, openResponseModal, closeResponseModal, renderToggleButton, renderVoteBar, renderDirect, attach, attachModelSelectorListeners, updateDirectResponse, finishDirectResponse, updateResponse, finishResponse, attachScrollListener, attachResponseBodyScrollListeners, scrollToBottom
- Key methods:
- Classes: SupabaseAuthService
- Key methods: constructor, init, signup, signupWithPhone, login, logout, resetPassword, signupWithEmailOtp, loginWithEmailOtp, verifyEmailOtp, sendSmsOtp, verifySmsOtp, updatePhone, updateProfile, changePassword, getAccessToken, isAuthenticated, getUser, getUserEmail, getUserName, getUserInitials, getUserId, getSession, _saveSession, _clearSession, _parseError, signInWithOAuth
- Classes: Sidebar
- Key methods: constructor, init, scheduleLoadThreads, loadThreads, render, renderRecentChats, escapeHtml, getUserInitials, getUserName, getUserEmail, updateRecentChats, attachThreadClickHandlers, attachEventListeners, attachActionHandlers, handleRenameThread, handleDeleteThread, handleResize, updateClasses, toggle, updateSidebarState, open, close, lockScroll, unlockScroll, enableFocusTrap, disableFocusTrap, handleNavClick, addRecentChat, getState
- Classes: DualMindAPIService
- Key methods: constructor, chatNonStreaming, chatStreaming, dualChat, extractResponse, getModels, getMe, submitVote, getLeaderboard, getThreads, getThreadMessages, createThread, fetchThread, updateThreadVisibility, getFeatureFlag, healthCheck
- Key methods:
- Classes: SharedThreadView
- Key methods: constructor, init, loadThread, getBaseUrl, render, renderLoading, renderError, renderHeader, _modeLabel, renderMeta, renderMessages, renderTurn, renderFooter, prettifyModelName, renderMarkdown, attach
- Classes: HttpClient
- Key methods: constructor, buildUrl, buildHeaders, log, getRetryDelay, isRetryable, safeJsonParse, request, get, post, put, patch, delete, sleep
- Classes: ChatComponent
- Key methods: constructor, init, render, setupEventListeners, sendMessage, loadMessages
- Classes: ShareModal
- Key methods: constructor, init, render, renderShareIcon, renderCopyIcon, getVisibilityHint, attach, open, close, updateVisibility, copyLink, rerender
- Classes: ChatInput
- Key methods: constructor, init, render, escapeHtml, renderLoader, renderAttachments, attachEventListeners, submit, clear, setLoading, handleAdd, handleImageUpload, processFiles, addAttachment, removeAttachment, updateAttachments, handleWebSearch, handleCodeMode, focus, setValue, autoResize
- Classes: ChatExport
- Key methods: constructor, getFilename, _buildMetadata, toMarkdown, toJSON, toCSV, _csvEscape, toHTML, toPDF, download
- Key methods:
- Key methods: fetch
64 .innerHTML assignments were found across the code. High-risk locations:
arena-core.js:79—this.arenaGrid.innerHTML = cards;arena-core.js:103— `body.innerHTML = ``arena-core.js:111—meta.innerHTML = '<i class="ri-check-line"></i>';arena-core.js:116—meta.innerHTML = '<i class="ri-loader-4-line"></i>';arena-core.js:154—badge.innerHTML = '<i class="ri-award-line"></i> Winner';arena-core.js:201— `this.arenaGrid.innerHTML = ``arena-core.js:277—this.arenaGrid.innerHTML = '';login/auth-complete.js:118—submitBtn.innerHTML = '<span>Verify Code</span><i class="ri-check-line"></i>';login/auth-complete.js:192—submitBtn.innerHTML = '<span>Verify Code</span><i class="ri-check-line"></i>';login/auth-complete.js:194—submitBtn.innerHTML = '<span>Send Code</span><i class="ri-smartphone-line"></i>';login/auth-complete.js:197—submitBtn.innerHTML = isLogincomponents/ChatInput.js:26— `this.container.innerHTML = ``components/ChatInput.js:269—preview.innerHTML = this.renderAttachments();components/ShareModal.js:25—overlay.innerHTML = this.render();components/ShareModal.js:233—overlay.innerHTML = this.render();components/ShareModal.js:330—overlay.innerHTML = this.render();components/SharedThreadView.js:106—this.container.innerHTML = this.renderLoading();components/SharedThreadView.js:111—this.container.innerHTML = this.renderError();components/SharedThreadView.js:115— `this.container.innerHTML = ``components/Header.js:33— `this.container.innerHTML = ``components/Header.js:192—if (modeIcon) modeIcon.innerHTML = modeData.icon('white');components/Sidebar.js:78— `this.container.innerHTML = ``components/Sidebar.js:240—listContainer.innerHTML = this.renderRecentChats();components/CustomModal.js:22— `root.innerHTML = ``components/CustomModal.js:175— `toast.innerHTML = ``components/CustomModal.js:204—if (contentEl) contentEl.innerHTML = content;components/chat/ChatView.js:117—temp.innerHTML = turnHtml;components/chat/ChatView.js:152— `this.container.innerHTML = ``components/chat/ChatView.js:475— `modal.innerHTML = ``components/chat/ChatView.js:554— `body.innerHTML = ``components/chat/ChatView.js:563—body.innerHTML = this.renderMarkdown(data.text || '') + (data.streaming ? '<span class="stcomponents/chat/ChatView.js:871—el.innerHTML = this.renderMarkdown(fullText) + (streaming ? '<span class="stream-caret" arcomponents/chat/ChatView.js:898—el.innerHTML = this.renderMarkdown(fullText) + (streaming ? '<span class="stream-caret" arcomponents/chat/ChatView.js:909—modalBody.innerHTML = this.renderMarkdown(fullText) + (streaming ? '<span class="stream-cajs/chatExport.js:260—frame.innerHTML = this.toHTML();js/update-password.js:29—submitBtn.innerHTML = isLoadingjs/auth-examples.js:251— `this.container.innerHTML = ``js/auth-examples.js:266— `this.container.innerHTML = ``js/auth-examples.js:325— `messagesDiv.innerHTML = messages.map(msg => ``js/auth-modern.js:233— `confirmWrapper.innerHTML = ``js/auth-modern.js:249— `wrapper.innerHTML = ``js/auth-modern.js:440—if (btnIcon) btnIcon.innerHTML = '<div class="spinner"></div>';js/auth-modern.js:445—if (btnIcon) btnIcon.innerHTML = '<i class="ri-arrow-right-line"></i>';js/auth-modern.js:542—button.innerHTML = '<div class="spinner-small"></div> Connecting...';js/auth-modern.js:552—button.innerHTML = originalContent;js/forgot-password.js:28—submitBtn.innerHTML = isLoadingjs/leaderboardPage.js:12— `root.innerHTML = ``js/leaderboardPage.js:50— `root.innerHTML = ``js/leaderboardPage.js:90— `root.innerHTML = ``js/ui/leaderboardModal.js:58— `root.innerHTML = ``js/ui/leaderboardModal.js:127— `this._els.content.innerHTML = ``js/ui/leaderboardModal.js:139— `this._els.content.innerHTML = ``js/ui/leaderboardModal.js:187— `this._els.content.innerHTML = ``js/ui/app.js:382— `indicator.innerHTML = ``js/ui/app.js:918—if (leftEl) leftEl.innerHTML = '';js/ui/app.js:919—if (rightEl) rightEl.innerHTML = '';js/ui/app.js:1114— `container.innerHTML = ``js/ui/app.js:1144—container.innerHTML = '';js/ui/app.js:1345—buttonElement.innerHTML = originalContent;js/ui/app.js:1410—buttonElement.innerHTML = '⏳';js/ui/app.js:1423—buttonElement.innerHTML = '⏹';js/ui/app.js:1458—buttonElement.innerHTML = '⏹';js/ui/app.js:1857—iconEl.innerHTML = '<svg class="spinner" width="16" height="16" viewBox="0 0 24 24" fill="js/ui/app.js:1876—iconEl.innerHTML = originalIcon;
255 console usages found. Many are intentional debug guards, but any remaining in production cause noise and logging cost.
-
build-deploy.js:39—console.log(Skipping ${src} (not found)); -
build-deploy.js:55—console.log(Copied: ${src}); -
build-deploy.js:59—console.log('Building deployment package...\n'); -
build-deploy.js:67—console.log('\n✅ Build complete! Files copied to dist/'); -
build-deploy.js:68—console.log('Run: wrangler deploy --config wrangler-dist.toml'); -
build.js:83—console.log('Frontend built to /dist successfully!'); -
performance-monitor.js:40—console.log('Page Load Metrics:', this.metrics.pageLoad); -
performance-monitor.js:72—console.warn(Slow API call: ${url} took ${duration.toFixed(0)}ms); -
performance-monitor.js:109—console.warn('Performance Observer not supported:', e); -
performance-monitor.js:223—suggestions.forEach(s => console.log('•', s)); -
mock-api-server.js:62—console.log('🔐 Login attempt:', req.body); -
mock-api-server.js:93—console.log('✅ Login successful for:', user.email); -
mock-api-server.js:108—console.log('📝 Signup attempt:', req.body); -
mock-api-server.js:149—console.log('✅ Signup successful for:', newUser.email); -
mock-api-server.js:165—console.log('📊 Leaderboard requested by:', req.user.email); -
mock-api-server.js:175—console.log('🗳️ Vote submitted by:', req.user.email, 'Data:', req.body); -
mock-api-server.js:185—console.log('💬 Chat request by:', req.user.email, 'Data:', req.body); -
mock-api-server.js:248—console.log(🚀 DualMind Mock API Server running on http://localhost:${PORT}`);` -
mock-api-server.js:249—console.log('📋 Available endpoints:'); -
mock-api-server.js:250—console.log(' POST /api/auth/login'); -
mock-api-server.js:251—console.log(' POST /api/auth/signup'); -
mock-api-server.js:252—console.log(' GET /api/health'); -
mock-api-server.js:253—console.log(' GET /api/arena/model-stats (requires auth)'); -
mock-api-server.js:254—console.log(' POST /api/arena/vote (requires auth)'); -
mock-api-server.js:255—console.log(' POST /api/arena/dualchat (requires auth)'); -
mock-api-server.js:256—console.log('\n🔑 Test credentials:'); -
mock-api-server.js:257—console.log(' Email: test@example.com'); -
mock-api-server.js:258—console.log(' Password: password123'); -
mock-api-server.js:259—console.log(' Email: admin@dualmind.com'); -
mock-api-server.js:260—console.log(' Password: admin123');
... and 225 more.
37 window.* writes found. Key globals:
config.js:1—window.DUALMIND_CONFIGarena-core.js:291—window.ArenaModeperformance-monitor.js:49—window.fetchperformance-monitor.js:213—window.performanceMonitorlogin/js/supabase-init.js:2—window.supabaselogin/js/supabase-init.js:11—window.supabaseClientlogin/js/app-final.js:11—if (typeof window.supabaseClientlogin/js/app-final.js:167—window.dualMindAuthjs/auth.js:72—window.DUALMIND_AUTH_TOKENjs/auth.js:188—window.getSupabaseAccessTokenjs/api-client.js:255—window.APIClientjs/api-client.js:256—window.createAPIClientjs/api-client.js:257—window.getAPIClientjs/feature-flags.js:395—window._DUALMIND_TESTER_ENERGY_BALANCEjs/apiInstance.js:27—window.DualMindApiInstancejs/auth/api-service.js:610—window.DualMindAPIServicejs/auth/supabase-init.js:8—window.DualMindAuthReadyjs/auth/supabase-init.js:9—window._resolveDualMindAuthReadyjs/auth/supabase-init.js:18—window._SUPABASE_AUTH_INITIALIZEDjs/auth/supabase-init.js:49—window._DUALMIND_AUTHjs/auth/supabase-init.js:63—window.getAuthjs/auth/supabase-init.js:128—window.DualMindAuthjs/auth/supabase-init.js:265—window.authjs/auth/supabase-auth.js:694—window.SupabaseAuthServicejs/auth/supabase-auth.js:695—window.initializeSupabaseAuthjs/auth/supabase-auth.js:696—window.getSupabaseAuthServicejs/api/utils/authProvider.js:37—if (typeof window.getSupabaseAccessTokenjs/ui/app.js:68—window._DUALMIND_APIjs/ui/app.js:292—window._DUALMIND_MODELSjs/ui/app.js:332—window._APPjs/ui/app.js:373—window._DUALMIND_APP_READYjs/ui/app.js:2108—window.DualMindArena
| Item | Status | Notes |
|---|---|---|
| Input sanitization | Partial | DOMPurify used for AI markdown; thread titles escaped; but many innerHTML sites exist |
| Auth token storage | Risky | Supabase localStorage session; XSS exposure |
| CORS | Risky | Access-Control-Allow-Origin: * in worker |
| CSP header | Missing | No Content-Security-Policy |
| Secrets in repo | Risky | Supabase URL in config.js |
| API proxy filtering | Missing | Worker forwards headers unfiltered |
| Rate limiting | Missing | No worker or UI rate limiting |
| Item | Status | Notes |
|---|---|---|
| CSS bundling | Not done | 12 stylesheets loaded synchronously |
| JS bundling | Not done | ES modules + CDN scripts + inline scripts |
| Cache control | Wrong | Worker returns no-store for all statics from worker.js |
| Image optimization | Manual | assets/ PNG files not optimized/processed |
| Lazy loading | Partial | chatExport.js is lazy-loaded |
| DOM churn | Risky | ChatView full rerenders and appends |
- No unit tests.
- Playwright dependency missing.
- GitHub Actions / CI not present.
commit_and_push.shandcommit_and_push.ps1are manual wrappers that bypass review.
- Fix
login/auth-complete.jsundefined variables. - Quote lint glob in
package.json. - Install Playwright or remove test scripts.
- Restrict
worker.jsCORS to allowed origins.
- Remove orphan prototypes (
App.jsx,next-dualmind/,old-html-backup/, etc.). - Delete deprecated shims (
js/api-client.js,js/auth.js,js/auth/api-service.js). - Move Supabase URL/key to build-time env, remove from
config.js. - Add CSP + security headers in worker.
- Unify build/deploy scripts (
build.js+wrangler.jsonc).
- Replace 12 CSS files with one built bundle.
- Add Jest/Vitest unit tests for API client and authProvider.
- Add GitHub Actions CI for lint, audit, test, deploy.
- Migrate auth tokens from localStorage to httpOnly cookies (requires backend support).
- Add feature flags behind remote config.
End of audit.