@bridge_gpt/mcp-server 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +97 -15
  2. package/build/agent-config-credential-migration.js +272 -0
  3. package/build/agents.generated.js +1 -1
  4. package/build/chain-orchestrator.js +16 -1
  5. package/build/commands.generated.js +9 -7
  6. package/build/conductor/bridge-api-client.js +625 -0
  7. package/build/conductor/claude-hook.js +251 -0
  8. package/build/conductor/cli.js +1048 -0
  9. package/build/conductor/data-normalization.js +114 -0
  10. package/build/conductor/doctor.js +164 -0
  11. package/build/conductor/done-gate.js +325 -0
  12. package/build/conductor/epic-reconcile.js +139 -0
  13. package/build/conductor/epic-runtime.js +611 -0
  14. package/build/conductor/epic-state.js +125 -0
  15. package/build/conductor/errors.js +85 -0
  16. package/build/conductor/git-ci-types.js +129 -0
  17. package/build/conductor/git-hooks.js +218 -0
  18. package/build/conductor/git-inspection.js +185 -0
  19. package/build/conductor/git-producer.js +137 -0
  20. package/build/conductor/merge-ledger.js +198 -0
  21. package/build/conductor/paths.js +224 -0
  22. package/build/conductor/plan.js +77 -0
  23. package/build/conductor/pr-ci-producer.js +427 -0
  24. package/build/conductor/pr-discovery.js +135 -0
  25. package/build/conductor/producer-ledger.js +125 -0
  26. package/build/conductor/redaction.js +112 -0
  27. package/build/conductor/store.js +1156 -0
  28. package/build/conductor/supervisor-config.js +150 -0
  29. package/build/conductor/supervisor-escalation.js +244 -0
  30. package/build/conductor/supervisor-judgment-python.js +141 -0
  31. package/build/conductor/supervisor-judgment.js +215 -0
  32. package/build/conductor/supervisor-ledger.js +119 -0
  33. package/build/conductor/supervisor-merge.js +127 -0
  34. package/build/conductor/supervisor-message-relay.js +61 -0
  35. package/build/conductor/supervisor-notification.js +39 -0
  36. package/build/conductor/supervisor-runtime.js +351 -0
  37. package/build/conductor/supervisor-state.js +572 -0
  38. package/build/conductor/supervisor-types.js +16 -0
  39. package/build/conductor/taxonomy.js +58 -0
  40. package/build/conductor/tools.js +367 -0
  41. package/build/conductor/types.js +9 -0
  42. package/build/conductor-bin.js +21 -0
  43. package/build/conductor-claude-hook-bin.js +21 -0
  44. package/build/credential-store.js +175 -4
  45. package/build/credentials-cli.js +223 -0
  46. package/build/decision-page-schema.js +60 -0
  47. package/build/decision-page-template.js +262 -10
  48. package/build/doctor.js +5 -1
  49. package/build/index.js +554 -66
  50. package/build/pipeline-orchestrator.js +5 -1
  51. package/build/pipeline-utils.js +45 -5
  52. package/build/pipelines.generated.js +37 -9
  53. package/build/readme.generated.js +1 -1
  54. package/build/review-tickets.js +596 -0
  55. package/build/scheduled-prompt.js +16 -10
  56. package/build/start-tickets-conductor.js +496 -0
  57. package/build/start-tickets-prereqs.js +32 -23
  58. package/build/start-tickets-repo.js +49 -0
  59. package/build/start-tickets.js +682 -81
  60. package/build/version.generated.js +1 -1
  61. package/design-assets/favicon/android-chrome-192x192.png +0 -0
  62. package/design-assets/favicon/android-chrome-512x512.png +0 -0
  63. package/design-assets/favicon/apple-touch-icon.png +0 -0
  64. package/design-assets/favicon/favicon-16x16.png +0 -0
  65. package/design-assets/favicon/favicon-32x32.png +0 -0
  66. package/design-assets/favicon/favicon.ico +0 -0
  67. package/design-assets/favicon/site.webmanifest +1 -0
  68. package/design-assets/just-logo-rough-draft.png +0 -0
  69. package/package.json +17 -5
  70. package/pipelines/idea-to-ticket.json +5 -0
  71. package/pipelines/plan-epic.json +16 -1
  72. package/pipelines/review-ticket.json +2 -1
  73. package/public/css/main.min.css +2 -0
  74. package/public/css/main.min.css.map +1 -0
  75. package/public/fonts/OFL.txt +93 -0
  76. package/public/fonts/SourceSansPro-Black.ttf +0 -0
  77. package/public/fonts/SourceSansPro-BlackItalic.ttf +0 -0
  78. package/public/fonts/SourceSansPro-Bold.ttf +0 -0
  79. package/public/fonts/SourceSansPro-BoldItalic.ttf +0 -0
  80. package/public/fonts/SourceSansPro-ExtraLight.ttf +0 -0
  81. package/public/fonts/SourceSansPro-ExtraLightItalic.ttf +0 -0
  82. package/public/fonts/SourceSansPro-Italic.ttf +0 -0
  83. package/public/fonts/SourceSansPro-Light.ttf +0 -0
  84. package/public/fonts/SourceSansPro-LightItalic.ttf +0 -0
  85. package/public/fonts/SourceSansPro-Regular.ttf +0 -0
  86. package/public/fonts/SourceSansPro-SemiBold.ttf +0 -0
  87. package/public/fonts/SourceSansPro-SemiBoldItalic.ttf +0 -0
  88. package/public/img/bridge-logo-160x51.webp +0 -0
  89. package/public/img/bridge-logo-300x92.webp +0 -0
  90. package/public/img/favicon/android-chrome-192x192.png +0 -0
  91. package/public/img/favicon/android-chrome-512x512.png +0 -0
  92. package/public/img/favicon/apple-touch-icon.png +0 -0
  93. package/public/img/favicon/favicon-16x16.png +0 -0
  94. package/public/img/favicon/favicon-32x32.png +0 -0
  95. package/public/img/favicon/favicon.ico +0 -0
  96. package/public/img/favicon/site.webmanifest +1 -0
  97. package/public/img/installation/bitbucket/app-password-1.png +0 -0
  98. package/public/img/installation/bitbucket/app-password-2.png +0 -0
  99. package/public/img/installation/bitbucket/create-token-1.png +0 -0
  100. package/public/img/installation/bitbucket/create-token-2.png +0 -0
  101. package/public/img/installation/bitbucket/webhook-1.png +0 -0
  102. package/public/img/installation/github/github-review-webhook.png +0 -0
  103. package/public/img/installation/jira/credentials/api-key.png +0 -0
  104. package/public/img/installation/jira/webhook/create-rule.png +0 -0
  105. package/public/img/installation/jira/webhook/project-settings.png +0 -0
  106. package/public/img/installation/jira/webhook/rule-create-1.png +0 -0
  107. package/public/img/installation/jira/webhook/rule-create-2.png +0 -0
  108. package/public/img/installation/jira/webhook/rule-create-3.png +0 -0
  109. package/public/img/installation/pinecone/pinecone-api-key.png +0 -0
  110. package/public/img/installation/pinecone/pinecone-index.png +0 -0
  111. package/public/js/main.min.js +2 -0
  112. package/public/js/main.min.js.map +1 -0
  113. package/smoke-test/SMOKE-TEST.md +17 -9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"js/main.min.js","mappings":"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","sources":["webpack:///./src/js/auth.js","webpack:///./src/js/data_health.js","webpack:///./src/js/delete-modal.js","webpack:///./src/js/get-started.js","webpack:///./src/js/learn_repository.js","webpack:///./src/js/login.js","webpack:///./src/js/modal.js","webpack:///./src/js/monitoring.js","webpack:///./src/js/optimize.js","webpack:///./src/js/optimize/context.js","webpack:///./src/js/optimize/feedback.js","webpack:///./src/js/optimize/learn_repository_renderer.js","webpack:///./src/js/optimize/polling.js","webpack:///./src/js/optimize/status_adapters.js","webpack:///./src/js/optimize_analysis.js","webpack:///./src/js/project-detail.js","webpack:///./src/js/security.js","webpack:///./src/js/setup-detail.js","webpack:///./src/js/setup.js","webpack:///./src/js/utils.js","webpack:///webpack/bootstrap","webpack:///webpack/runtime/define property getters","webpack:///webpack/runtime/hasOwnProperty shorthand","webpack:///webpack/startup"],"sourcesContent":["import { showLoading, hideLoading, apiCall, getApiErrorMessage, TOKEN_STORAGE_KEY } from \"./utils\";\nimport { showModal } from './modal.js';\n// ==========================================\n// Authentication\n// ==========================================\n\n/**\n * Handles login form submission\n * @param {Event} e - Form submit event\n */\nexport async function handleLogin(e) {\n e.preventDefault(); // Prevent default form submission\n \n const clientId = document.getElementById('client-id').value;\n const authToken = document.getElementById('auth-token').value;\n \n if (!clientId || !authToken) {\n showModal('Missing Fields', 'Please enter both Client ID and Authentication Token.');\n return;\n }\n \n showLoading();\n let response = null; // Initialize response\n\n try {\n response = await apiCall('/auth/token', 'POST', {\n client_id: clientId,\n auth_token: authToken\n });\n } catch (error) {\n // /auth/token is exempt from apiCall's centralized 401 redirect, so bad\n // credentials (401) and other failures throw here. apiCall no longer\n // shows its own modal, so this handler owns the single login-failure\n // message. Returning here keeps it to exactly one modal (the post-try\n // else branch never runs on the throw path).\n console.error(\"Error during apiCall in handleLogin:\", error);\n showModal('Login Failed', getApiErrorMessage(error, 'Invalid Client ID or Authentication Token.'));\n return;\n } finally {\n hideLoading(); // Ensure loading is hidden regardless of success/failure\n }\n\n // Check the response AFTER hiding loading. Reaching here means no error was\n // thrown, so this is the success-or-empty-response path.\n if (response && response.session_token) {\n // Store token in localStorage as a backup (optional)\n localStorage.setItem(TOKEN_STORAGE_KEY, response.session_token);\n\n // Handle redirect\n if (response.redirect) {\n window.location.href = response.redirect;\n } else {\n // Just in case no redirect is provided, fallback\n window.location.href = '/setup';\n }\n } else {\n // A non-error response with no session_token means login did not succeed.\n showModal('Login Failed', 'Invalid Client ID or Authentication Token.');\n }\n}\n\n/**\n * Logs out the user\n */\nexport async function logout() {\n showLoading();\n let response = null;\n try {\n response = await apiCall('/auth/logout', 'POST');\n } catch (error) {\n // apiCall now rethrows non-401 failures; a failed logout still proceeds\n // to clear local state and return the user to the login page.\n console.error('Error during logout:', error);\n } finally {\n hideLoading();\n }\n\n localStorage.removeItem(TOKEN_STORAGE_KEY);\n\n // Handle redirect from server or default to login page\n if (response && response.redirect) {\n window.location.href = response.redirect;\n } else {\n window.location.href = '/setup/login';\n }\n}\n\n/**\n * Checks if the user is authenticated\n */\nexport async function checkAuthentication() {\n console.log('checkAuthentication')\n try {\n const response = await apiCall('/auth/check-auth', 'POST');\n\n // apiCall returns null on the centralized 401 redirect (Session Expired\n // modal + redirect already in progress); treat that as handled and do\n // nothing more here.\n if (response && !response.authenticated && response.redirect) {\n // User is not authenticated, redirect to login\n window.location.href = response.redirect;\n }\n } catch (error) {\n // Non-401 failure (401 is handled centrally by apiCall). Log without a\n // modal so an auth probe failure does not stack a second dialog.\n console.error('Error checking authentication:', error);\n }\n}","// src/js/data_health.js\n/**\n * Data Health Dashboard JavaScript\n * Handles data operations (Update Repository, Schedule Parsing, Update Labels)\n * and health checks (Data Integrity, Pinecone Health, Config Health)\n */\n\nimport { initModal, showModal } from './modal.js';\nimport { showLoading, hideLoading, apiCall, initDrawers, getApiErrorMessage } from './utils.js';\n\n// --- State ---\nlet currentProjectId = null;\nlet currentRepoName = null;\n\n// --- DOM Elements ---\nconst dataHealthContainer = document.getElementById('data-health-container');\n\n// --- Functions ---\n\n/**\n * Gets the project ID from the container data attribute.\n * @returns {string|null} The project ID or null if not found.\n */\nfunction getProjectId() {\n if (currentProjectId) return currentProjectId;\n \n if (dataHealthContainer) {\n currentProjectId = dataHealthContainer.dataset.projectId;\n return currentProjectId;\n }\n return null;\n}\n\n/**\n * Gets the repo name from the container data attribute.\n * @returns {string|null} The repo name or null if not found.\n */\nfunction getRepoName() {\n if (currentRepoName) return currentRepoName;\n \n if (dataHealthContainer) {\n currentRepoName = dataHealthContainer.dataset.repoName;\n return currentRepoName;\n }\n return null;\n}\n\n/**\n * Sets up the expandable drawer functionality for form sections.\n */\nfunction setupDrawers() {\n // Delegate to the shared drawer helper, scoped to this page's container.\n // It normalizes initial state (the Parse Repository Jobs drawer starts open)\n // and fixes the first-click bug for the collapsed drawers.\n initDrawers({ root: document, drawerSelector: '#data-health-container .drawer' });\n}\n\n/**\n * Populates the timezone dropdown with available timezones.\n *\n * UTC is always prepended as the first option: modern Chromium V8 omits\n * 'UTC' (and 'Etc/UTC') from `Intl.supportedValuesOf('timeZone')`, returning\n * only canonical IANA zones (e.g. 'Africa/Abidjan' ... 'Pacific/Wallis'),\n * while Firefox/WebKit still include 'UTC'. The backend accepts 'UTC' via\n * `zoneinfo.ZoneInfo('UTC')` and the scheduler stores it that way, so the\n * picker must offer it on every browser.\n */\nfunction _populateTimezoneSelectInto(timezoneSelect) {\n if (!timezoneSelect) return;\n try {\n const timezones = Intl.supportedValuesOf('timeZone');\n timezoneSelect.innerHTML = '';\n\n if (!timezones.includes('UTC')) {\n const utcOption = document.createElement('option');\n utcOption.value = 'UTC';\n utcOption.textContent = 'UTC';\n timezoneSelect.appendChild(utcOption);\n }\n\n timezones.forEach(timezone => {\n const option = document.createElement('option');\n option.value = timezone;\n option.textContent = timezone;\n timezoneSelect.appendChild(option);\n });\n\n const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;\n if (userTimezone === 'UTC' || timezones.includes(userTimezone)) {\n timezoneSelect.value = userTimezone;\n }\n } catch (error) {\n console.error('Error populating timezone dropdown:', error);\n const commonTimezones = [\n 'UTC',\n 'America/New_York',\n 'America/Chicago',\n 'America/Denver',\n 'America/Los_Angeles',\n 'Europe/London',\n 'Europe/Paris',\n 'Asia/Tokyo'\n ];\n\n commonTimezones.forEach(timezone => {\n const option = document.createElement('option');\n option.value = timezone;\n option.textContent = timezone;\n timezoneSelect.appendChild(option);\n });\n timezoneSelect.value = 'UTC';\n }\n}\n\n\nfunction populateTimezoneDropdown() {\n _populateTimezoneSelectInto(document.getElementById('schedule-timezone'));\n _populateTimezoneSelectInto(document.getElementById('learn-repo-timezone'));\n}\n\n\n// --- Phase 1b: Learn-repository auto-update schedule ---\n\nconst LEARN_REPO_ANALYSIS_TYPES = Object.freeze([\n 'architecture',\n 'frontend_correctness',\n 'backend_correctness',\n 'template_correctness',\n 'style_correctness',\n 'design_principles',\n]);\n\n\nfunction _setLearnRepoFormError(testid, message) {\n const el = document.querySelector(`[data-testid=\"${testid}\"]`);\n if (!el) return;\n if (message) {\n el.textContent = message;\n el.classList.remove('hidden');\n } else {\n el.textContent = '';\n el.classList.add('hidden');\n }\n}\n\n\nfunction _clearLearnRepoFormErrors() {\n [\n 'learn-repo-analysis-types-error',\n 'learn-repo-interval-weeks-error',\n 'learn-repo-timezone-error',\n 'learn-repo-months-back-error',\n ].forEach(id => _setLearnRepoFormError(id, ''));\n}\n\n\n// --- Parse scheduler inline validation (BAPI-306) ---\n// Mirrors the Learn helpers above so the Parse fields render client-side\n// validation inline (in the adjacent `.form-error` containers) instead of a\n// generic modal. Toggling the `.hidden` class matches the Learn pattern exactly.\n\nfunction _setParseRepoFormError(testid, message) {\n const el = document.querySelector(`[data-testid=\"${testid}\"]`);\n if (!el) return;\n if (message) {\n el.textContent = message;\n el.classList.remove('hidden');\n } else {\n el.textContent = '';\n el.classList.add('hidden');\n }\n}\n\n\nfunction _clearParseRepoFormErrors() {\n [\n 'schedule-interval-error',\n 'schedule-timezone-error',\n ].forEach(id => _setParseRepoFormError(id, ''));\n}\n\n\nfunction _renderLearnRepoMessage(text) {\n const msg = document.querySelector('[data-testid=\"learn-repo-schedule-message\"]');\n if (msg) msg.textContent = text || '';\n}\n\n\nasync function handleScheduleLearnRepository() {\n const projectId = getProjectId();\n const repoName = getRepoName();\n if (!projectId || !repoName) {\n showModal('Error', 'Project information not found.');\n return;\n }\n\n _clearLearnRepoFormErrors();\n\n const checkedTypes = LEARN_REPO_ANALYSIS_TYPES.filter(t =>\n document.querySelector(`[data-testid=\"learn-repo-analysis-type-${t}\"]`)?.checked\n );\n const intervalWeeks = parseInt(document.querySelector('[data-testid=\"learn-repo-interval-weeks-input\"]')?.value, 10);\n const timezone = document.querySelector('[data-testid=\"learn-repo-timezone-select\"]')?.value;\n const monthsBack = parseInt(document.querySelector('[data-testid=\"learn-repo-months-back-input\"]')?.value, 10);\n const prLimitRaw = document.querySelector('[data-testid=\"learn-repo-pr-limit-input\"]')?.value;\n\n let invalid = false;\n if (checkedTypes.length === 0) {\n _setLearnRepoFormError('learn-repo-analysis-types-error', 'Please select at least one analysis type.');\n invalid = true;\n }\n if (!Number.isFinite(intervalWeeks) || intervalWeeks < 1 || intervalWeeks > 52) {\n _setLearnRepoFormError('learn-repo-interval-weeks-error', 'Interval must be between 1 and 52 weeks.');\n invalid = true;\n }\n if (!timezone) {\n _setLearnRepoFormError('learn-repo-timezone-error', 'Please select a timezone.');\n invalid = true;\n }\n if (!Number.isFinite(monthsBack) || monthsBack < 1 || monthsBack > 60) {\n _setLearnRepoFormError('learn-repo-months-back-error', 'Months back must be between 1 and 60.');\n invalid = true;\n }\n // PR limit is optional. Empty string -> null (no cap). Any other value\n // must be a positive integer.\n let prLimit = null;\n if (prLimitRaw !== undefined && prLimitRaw !== null && String(prLimitRaw).trim() !== '') {\n const parsed = parseInt(prLimitRaw, 10);\n if (!Number.isFinite(parsed) || parsed < 1) {\n _setLearnRepoFormError('learn-repo-pr-limit-error', 'PR limit must be a positive integer or left blank.');\n invalid = true;\n } else {\n prLimit = parsed;\n }\n }\n if (invalid) return;\n\n const intervalDays = intervalWeeks * 7;\n const btn = document.querySelector('[data-testid=\"learn-repo-schedule-button\"]');\n if (btn) btn.disabled = true;\n showLoading();\n try {\n const response = await apiCall(\n `/setup/data/${projectId}/schedule-learn-repository`,\n 'POST',\n {\n repo_name: repoName,\n analysis_types: checkedTypes,\n interval_days: intervalDays,\n timezone,\n months_back: monthsBack,\n limit: prLimit,\n }\n );\n // apiCall() now rethrows HTTP failures (caught below) and returns null only\n // on the centralized 401 redirect, so reaching here with a truthy response\n // means the schedule succeeded.\n if (response) {\n // Only render the \"Scheduled for ...\" message when the response carries\n // a usable recommended_hour — this is the fix for the prior\n // \"Scheduled for undefined AM ...\" rendered on a failed/empty call.\n if (response.recommended_hour != null) {\n const recommendedHour = response.recommended_hour;\n const weeksPhrase = `${intervalWeeks} week${intervalWeeks !== 1 ? 's' : ''}`;\n _renderLearnRepoMessage(\n `Scheduled for ${recommendedHour} AM every ${weeksPhrase} (${timezone})`\n );\n }\n await loadLearnRepoScheduledJobs(projectId);\n }\n } catch (error) {\n console.error('Failed to schedule Learn Repository Job', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to schedule Learn Repository Job.'));\n } finally {\n if (btn) btn.disabled = false;\n hideLoading();\n }\n}\n\n\nfunction renderLearnRepositoryDiffSummaries(parent, diffSummary) {\n parent.textContent = '';\n if (!diffSummary || typeof diffSummary !== 'object') return;\n Object.entries(diffSummary).forEach(([analysisType, summaryText]) => {\n const wrapper = document.createElement('details');\n wrapper.dataset.testid = `learn-repo-diff-summary-${analysisType}`;\n const summary = document.createElement('summary');\n summary.dataset.testid = `learn-repo-diff-summary-toggle-${analysisType}`;\n summary.textContent = analysisType;\n const content = document.createElement('pre');\n content.dataset.testid = `learn-repo-diff-summary-content-${analysisType}`;\n content.textContent = summaryText == null ? '' : String(summaryText);\n wrapper.appendChild(summary);\n wrapper.appendChild(content);\n parent.appendChild(wrapper);\n });\n}\n\n\nfunction renderLearnRepositoryScheduleRow(container, job) {\n const row = document.createElement('div');\n row.className = 'learn-repo-schedule-row';\n row.dataset.testid = 'learn-repo-schedule-row';\n row.dataset.repoName = job.repo_name || '';\n row.dataset.jobType = job.job_type || '';\n\n const repoNameEl = document.createElement('strong');\n repoNameEl.textContent = job.repo_name || '';\n row.appendChild(repoNameEl);\n\n const types = Array.isArray(job.analysis_types) ? job.analysis_types.join(', ') : '';\n const meta = document.createElement('div');\n meta.className = 'learn-repo-schedule-meta';\n const intervalDays = job.interval_days || 0;\n const weeks = intervalDays % 7 === 0 ? intervalDays / 7 : null;\n const intervalText = weeks != null ? `Every ${weeks} weeks` : `Every ${intervalDays} days`;\n const limitText = job.limit != null ? `, PR limit ${job.limit}` : '';\n meta.textContent = `${types} — ${intervalText} — ${job.timezone || ''} — ${job.months_back || 0} months back${limitText}`;\n row.appendChild(meta);\n\n const lastRun = document.createElement('div');\n lastRun.dataset.testid = 'learn-repo-last-run-at';\n lastRun.textContent = `Last run: ${job.last_run_at || 'never'}`;\n row.appendChild(lastRun);\n\n const status = document.createElement('div');\n status.dataset.testid = 'learn-repo-last-status';\n const statusValue = job.last_status || 'idle';\n status.textContent = `Status: ${statusValue}`;\n status.classList.add(`learn-repo-status-${statusValue}`);\n if (job.paused) {\n status.textContent += ' (paused)';\n }\n if (job.scheduler_missing) {\n status.textContent += ' (scheduler missing)';\n }\n row.appendChild(status);\n\n const next = document.createElement('div');\n next.dataset.testid = 'learn-repo-next-run-time';\n next.textContent = `Next run: ${job.scheduler_missing ? 'not scheduled (scheduler missing)' : (job.next_run_time || 'none')}`;\n row.appendChild(next);\n\n if (job.last_diff_summary) {\n const diffWrapper = document.createElement('div');\n diffWrapper.dataset.testid = 'learn-repo-diff-summaries';\n renderLearnRepositoryDiffSummaries(diffWrapper, job.last_diff_summary);\n row.appendChild(diffWrapper);\n }\n\n const actions = document.createElement('div');\n actions.className = 'learn-repo-row-actions';\n if (!job.paused) {\n const pauseBtn = document.createElement('button');\n pauseBtn.type = 'button';\n pauseBtn.className = 'btn btn-sm btn-secondary';\n pauseBtn.textContent = 'Pause';\n pauseBtn.dataset.testid = 'learn-repo-pause-button';\n pauseBtn.addEventListener('click', () => handlePauseLearnRepositorySchedule(job.repo_name));\n actions.appendChild(pauseBtn);\n } else {\n const resumeBtn = document.createElement('button');\n resumeBtn.type = 'button';\n resumeBtn.className = 'btn btn-sm btn-secondary';\n resumeBtn.textContent = 'Resume';\n resumeBtn.dataset.testid = 'learn-repo-resume-button';\n resumeBtn.addEventListener('click', () => handleResumeLearnRepositorySchedule(job.repo_name));\n actions.appendChild(resumeBtn);\n }\n const deleteBtn = document.createElement('button');\n deleteBtn.type = 'button';\n deleteBtn.className = 'btn btn-sm btn-danger';\n deleteBtn.textContent = 'Delete';\n deleteBtn.dataset.testid = 'learn-repo-delete-button';\n deleteBtn.addEventListener('click', () => handleDeleteLearnRepositorySchedule(job.repo_name));\n actions.appendChild(deleteBtn);\n row.appendChild(actions);\n\n container.appendChild(row);\n}\n\n\nasync function loadLearnRepoScheduledJobs(projectId) {\n const container = document.querySelector('[data-testid=\"learn-repo-schedule-status\"]');\n if (!container) return;\n try {\n const data = await apiCall(`/setup/data/${projectId}/scheduled-jobs`, 'GET');\n container.textContent = '';\n const jobs = (data && data.jobs) || [];\n jobs.forEach(job => {\n if (job.job_type === 'learn_repo_auto') {\n renderLearnRepositoryScheduleRow(container, job);\n }\n });\n } catch (error) {\n console.error('Failed to load scheduled jobs', error);\n }\n}\n\n\nasync function handlePauseLearnRepositorySchedule(repoName) {\n const projectId = getProjectId();\n if (!projectId || !repoName) return;\n showLoading();\n try {\n await apiCall(`/setup/data/${projectId}/pause-learn-repository-job`, 'POST', { repo_name: repoName });\n await loadLearnRepoScheduledJobs(projectId);\n } catch (error) {\n console.error('Failed to pause Learn Repository Job', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to pause Learn Repository Job.'));\n } finally {\n hideLoading();\n }\n}\n\n\nasync function handleResumeLearnRepositorySchedule(repoName) {\n const projectId = getProjectId();\n if (!projectId || !repoName) return;\n showLoading();\n try {\n await apiCall(`/setup/data/${projectId}/resume-learn-repository-job`, 'POST', { repo_name: repoName });\n await loadLearnRepoScheduledJobs(projectId);\n } catch (error) {\n console.error('Failed to resume Learn Repository Job', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to resume Learn Repository Job.'));\n } finally {\n hideLoading();\n }\n}\n\n\nasync function handleDeleteLearnRepositorySchedule(repoName) {\n const projectId = getProjectId();\n if (!projectId || !repoName) return;\n showLoading();\n try {\n await apiCall(`/setup/data/${projectId}/scheduled-learn-repository-job`, 'DELETE', { repo_name: repoName });\n _renderLearnRepoMessage(`Schedule deleted for ${repoName}.`);\n await loadLearnRepoScheduledJobs(projectId);\n } catch (error) {\n console.error('Failed to delete Learn Repository Job schedule', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to delete Learn Repository Job schedule.'));\n } finally {\n hideLoading();\n }\n}\n\n// --- Data Operations Handlers ---\n\n/**\n * Handles the Update Repository button click.\n */\nasync function handleUpdateRepository() {\n const projectId = getProjectId();\n const repoName = getRepoName();\n \n if (!projectId || !repoName) {\n showModal('Error', 'Project information not found.');\n return;\n }\n \n const updateBtn = document.getElementById('update-repository-btn');\n if (updateBtn) {\n updateBtn.disabled = true;\n updateBtn.textContent = 'Processing...';\n }\n \n showLoading();\n \n try {\n const response = await apiCall(`/setup/data/${projectId}/parse-repository`, 'POST');\n \n hideLoading();\n \n if (response && response.status === 'success') {\n showModal('Job Started', response.message || 'Repository update started in the background.');\n }\n \n } catch (error) {\n console.error('Repository update error:', error);\n hideLoading();\n showModal('Error', getApiErrorMessage(error, 'Failed to start repository update. Please try again.'));\n } finally {\n if (updateBtn) {\n updateBtn.disabled = false;\n updateBtn.textContent = 'Update Repository';\n }\n }\n}\n\n/**\n * Handles the Schedule Repository Parsing button click.\n */\nasync function handleScheduleParseRepository() {\n const projectId = getProjectId();\n const repoName = getRepoName();\n \n if (!projectId || !repoName) {\n // Missing project/repo context is not a per-field problem, so keep it as a\n // modal rather than an inline field error.\n showModal('Error', 'Project information not found.');\n return;\n }\n\n // Clear → validate → return inline (mirrors handleScheduleLearnRepository).\n _clearParseRepoFormErrors();\n\n const intervalInput = document.getElementById('schedule-interval');\n const timezoneSelect = document.getElementById('schedule-timezone');\n const scheduleBtn = document.getElementById('schedule-repository-btn');\n\n // Do not default invalid input to 1 — let the range check render an inline\n // error instead of silently scheduling a bogus interval.\n const interval = parseInt(intervalInput?.value, 10);\n const timezone = timezoneSelect?.value;\n\n let invalid = false;\n if (!timezone) {\n _setParseRepoFormError('schedule-timezone-error', 'Please select a timezone.');\n invalid = true;\n }\n if (!Number.isFinite(interval) || interval < 1 || interval > 365) {\n _setParseRepoFormError('schedule-interval-error', 'Interval must be between 1 and 365 days.');\n invalid = true;\n }\n // Return before disabling the button, changing its text, showing the loader,\n // or firing the POST.\n if (invalid) return;\n\n if (scheduleBtn) {\n scheduleBtn.disabled = true;\n scheduleBtn.textContent = 'Scheduling...';\n }\n\n showLoading();\n\n try {\n const response = await apiCall(`/setup/data/${projectId}/schedule-parse-repository`, 'POST', {\n repo_name: repoName,\n interval: interval,\n timezone: timezone\n });\n\n hideLoading();\n\n if (response && response.status === 'scheduled') {\n const nextRun = response.next_run_time ? new Date(response.next_run_time).toLocaleString() : 'Not scheduled';\n showModal('Success', `Repository parsing scheduled successfully! Next run: ${nextRun}`);\n }\n\n } catch (error) {\n // Server/transport failures surface here because apiCall() now rethrows\n // non-401 HTTP errors instead of swallowing them (BAPI-306).\n console.error('Repository scheduling error:', error);\n hideLoading();\n showModal('Error', getApiErrorMessage(error, 'Failed to schedule repository parsing. Please try again.'));\n } finally {\n if (scheduleBtn) {\n scheduleBtn.disabled = false;\n scheduleBtn.textContent = 'Schedule Repository Parsing';\n }\n }\n}\n\n/**\n * Handles the Pause Schedule button click.\n */\nasync function handlePauseParseSchedule() {\n const projectId = getProjectId();\n const repoName = getRepoName();\n\n if (!projectId || !repoName) {\n showModal('Error', 'Project information not found.');\n return;\n }\n\n const btn = document.getElementById('pause-schedule-btn');\n if (btn) {\n btn.disabled = true;\n btn.textContent = 'Pausing...';\n }\n\n try {\n await apiCall(`/setup/data/${projectId}/pause-parse-schedule`, 'POST', { repo_name: repoName });\n window.location.reload();\n } catch (error) {\n console.error('Pause schedule error:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to pause scheduled parsing. Please try again.'));\n if (btn) {\n btn.disabled = false;\n btn.textContent = 'Pause';\n }\n }\n}\n\n/**\n * Handles the Resume Schedule button click.\n */\nasync function handleResumeParseSchedule() {\n const projectId = getProjectId();\n const repoName = getRepoName();\n\n if (!projectId || !repoName) {\n showModal('Error', 'Project information not found.');\n return;\n }\n\n const btn = document.getElementById('resume-schedule-btn');\n if (btn) {\n btn.disabled = true;\n btn.textContent = 'Resuming...';\n }\n\n try {\n await apiCall(`/setup/data/${projectId}/resume-parse-schedule`, 'POST', { repo_name: repoName });\n window.location.reload();\n } catch (error) {\n console.error('Resume schedule error:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to resume scheduled parsing. Please try again.'));\n if (btn) {\n btn.disabled = false;\n btn.textContent = 'Resume';\n }\n }\n}\n\n/**\n * Handles the Cancel Schedule button click.\n */\nasync function handleCancelParseSchedule() {\n const projectId = getProjectId();\n const repoName = getRepoName();\n\n if (!projectId || !repoName) {\n showModal('Error', 'Project information not found.');\n return;\n }\n\n showModal(\n 'Cancel Scheduled Parsing',\n `Are you sure you want to cancel the scheduled parsing job for <strong>${repoName}</strong>? This cannot be undone.`,\n async () => {\n const btn = document.getElementById('cancel-schedule-btn');\n if (btn) {\n btn.disabled = true;\n btn.textContent = 'Cancelling...';\n }\n try {\n await apiCall(`/setup/data/${projectId}/schedule-parse-repository`, 'DELETE', { repo_name: repoName });\n window.location.reload();\n } catch (error) {\n console.error('Cancel schedule error:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to cancel scheduled parsing. Please try again.'));\n if (btn) {\n btn.disabled = false;\n btn.textContent = 'Cancel';\n }\n }\n },\n true\n );\n}\n\n/**\n * Handles the Update File Labels button click.\n */\nasync function handleUpdateLabels() {\n const projectId = getProjectId();\n \n if (!projectId) {\n showModal('Error', 'Project information not found.');\n return;\n }\n \n const updateLabelsBtn = document.getElementById('update-labels-btn');\n if (updateLabelsBtn) {\n updateLabelsBtn.disabled = true;\n updateLabelsBtn.textContent = 'Processing...';\n }\n \n showLoading();\n \n try {\n const response = await apiCall(`/setup/data/${projectId}/update-labels`, 'POST');\n \n hideLoading();\n \n if (response && response.status === 'success') {\n showModal('Job Started', response.message || 'File label update started in the background.');\n }\n \n } catch (error) {\n console.error('Update labels error:', error);\n hideLoading();\n showModal('Error', getApiErrorMessage(error, 'Failed to start file label update. Please try again.'));\n } finally {\n if (updateLabelsBtn) {\n updateLabelsBtn.disabled = false;\n updateLabelsBtn.textContent = 'Update File Labels';\n }\n }\n}\n\n// --- Data Integrity Functions ---\n\n/**\n * Loads and displays data integrity health check results.\n * @param {string} projectId - The ID of the project to check.\n */\nasync function loadDataIntegrity(projectId) {\n const loadingEl = document.getElementById('integrity-loading');\n const errorEl = document.getElementById('integrity-error');\n const resultsEl = document.getElementById('integrity-results');\n const emptyStateEl = document.getElementById('integrity-empty-state');\n const issuesContainerEl = document.getElementById('integrity-issues-container');\n const issuesTbodyEl = document.getElementById('integrity-issues-tbody');\n const totalFilesEl = document.getElementById('integrity-total-files');\n const healthyFilesEl = document.getElementById('integrity-healthy-files');\n \n // Show loading, hide others\n if (loadingEl) loadingEl.classList.remove('hidden');\n if (errorEl) errorEl.classList.add('hidden');\n if (resultsEl) resultsEl.classList.add('hidden');\n \n try {\n const data = await apiCall(`/setup/data/${projectId}/integrity`, 'GET');\n \n // Hide loading\n if (loadingEl) loadingEl.classList.add('hidden');\n \n if (!data) {\n // Show error state\n if (errorEl) errorEl.classList.remove('hidden');\n return;\n }\n \n // Show results container\n if (resultsEl) resultsEl.classList.remove('hidden');\n \n // Update summary stats\n if (totalFilesEl) totalFilesEl.textContent = data.total_files || 0;\n if (healthyFilesEl) healthyFilesEl.textContent = data.healthy_files || 0;\n \n // Handle empty vs issues state\n if (!data.issues || data.issues.length === 0) {\n // Show empty state (all healthy)\n if (emptyStateEl) emptyStateEl.classList.remove('hidden');\n if (issuesContainerEl) issuesContainerEl.classList.add('hidden');\n } else {\n // Show issues table\n if (emptyStateEl) emptyStateEl.classList.add('hidden');\n if (issuesContainerEl) issuesContainerEl.classList.remove('hidden');\n \n // Populate issues table\n if (issuesTbodyEl) {\n issuesTbodyEl.innerHTML = '';\n \n data.issues.forEach(issue => {\n const row = document.createElement('tr');\n \n // Check name cell\n const checkNameCell = document.createElement('td');\n checkNameCell.textContent = issue.check_name;\n checkNameCell.style.fontWeight = '500';\n row.appendChild(checkNameCell);\n \n // Description cell\n const descCell = document.createElement('td');\n descCell.textContent = issue.description;\n row.appendChild(descCell);\n \n // Count cell\n const countCell = document.createElement('td');\n countCell.textContent = issue.affected_count;\n countCell.style.fontWeight = '600';\n countCell.style.color = '#dc2626';\n row.appendChild(countCell);\n \n // Affected files cell\n const filesCell = document.createElement('td');\n if (issue.affected_files && issue.affected_files.length > 0) {\n const fileList = document.createElement('ul');\n fileList.className = 'integrity-file-list';\n \n issue.affected_files.forEach(fileName => {\n const li = document.createElement('li');\n li.textContent = fileName;\n fileList.appendChild(li);\n });\n \n filesCell.appendChild(fileList);\n \n // Show \"and X more\" if there are more files than displayed\n if (issue.affected_count > issue.affected_files.length) {\n const moreText = document.createElement('div');\n moreText.className = 'integrity-more-files';\n moreText.textContent = `...and ${issue.affected_count - issue.affected_files.length} more`;\n filesCell.appendChild(moreText);\n }\n } else {\n filesCell.textContent = '-';\n }\n row.appendChild(filesCell);\n \n issuesTbodyEl.appendChild(row);\n });\n }\n }\n \n } catch (error) {\n console.error('Error loading data integrity:', error);\n if (loadingEl) loadingEl.classList.add('hidden');\n if (errorEl) errorEl.classList.remove('hidden');\n }\n}\n\n// --- Pinecone Health Functions ---\n\n/**\n * Loads and displays Pinecone data integrity health check results.\n * @param {string} projectId - The ID of the project to check.\n */\nasync function loadPineconeHealth(projectId) {\n const loadingEl = document.getElementById('pinecone-health-loading');\n const errorEl = document.getElementById('pinecone-health-error');\n const noResultsEl = document.getElementById('pinecone-health-no-results');\n const resultsEl = document.getElementById('pinecone-health-results');\n const allGoodEl = document.getElementById('pinecone-health-all-good');\n const namespaceContainerEl = document.getElementById('pinecone-namespace-container');\n const namespaceTbodyEl = document.getElementById('pinecone-namespace-tbody');\n const failuresContainerEl = document.getElementById('pinecone-failures-container');\n const failuresTbodyEl = document.getElementById('pinecone-failures-tbody');\n const statusEl = document.getElementById('pinecone-health-status');\n const lastRunEl = document.getElementById('pinecone-health-last-run');\n const expectedCountEl = document.getElementById('pinecone-expected-count');\n const actualCountEl = document.getElementById('pinecone-actual-count');\n const mismatchCountEl = document.getElementById('pinecone-mismatch-count');\n const sampleFailuresEl = document.getElementById('pinecone-sample-failures');\n \n try {\n const data = await apiCall(`/setup/setup-detail/${projectId}/pinecone-health-check`, 'GET');\n \n if (!data) {\n // No health check has been run yet\n if (noResultsEl) noResultsEl.classList.remove('hidden');\n if (resultsEl) resultsEl.classList.add('hidden');\n return;\n }\n \n // Hide no results, show results\n if (noResultsEl) noResultsEl.classList.add('hidden');\n if (resultsEl) resultsEl.classList.remove('hidden');\n \n // Update status badge\n if (statusEl) {\n statusEl.textContent = data.status.toUpperCase();\n statusEl.className = 'pinecone-health-status';\n if (data.status === 'healthy') {\n statusEl.classList.add('status-healthy');\n } else if (data.status === 'error') {\n statusEl.classList.add('status-error');\n } else if (data.status === 'warning') {\n statusEl.classList.add('status-warning');\n } else if (data.status === 'running') {\n statusEl.classList.add('status-running');\n }\n }\n \n // Update last run time\n if (lastRunEl && data.created_at) {\n const date = new Date(data.created_at);\n lastRunEl.textContent = `Last run: ${date.toLocaleString()}`;\n }\n \n // Update summary stats\n if (expectedCountEl) expectedCountEl.textContent = data.total_expected || 0;\n if (actualCountEl) actualCountEl.textContent = data.total_actual || 0;\n if (mismatchCountEl) {\n mismatchCountEl.textContent = data.total_mismatches || 0;\n if (data.total_mismatches > 0) {\n mismatchCountEl.style.color = '#dc2626';\n } else {\n mismatchCountEl.style.color = '#16a34a';\n }\n }\n if (sampleFailuresEl) {\n sampleFailuresEl.textContent = data.sample_failures || 0;\n if (data.sample_failures > 0) {\n sampleFailuresEl.style.color = '#dc2626';\n } else {\n sampleFailuresEl.style.color = '#16a34a';\n }\n }\n \n // Handle healthy vs issues state\n const hasIssues = (data.total_mismatches > 0) || (data.sample_failures > 0);\n \n if (!hasIssues) {\n if (allGoodEl) allGoodEl.classList.remove('hidden');\n if (namespaceContainerEl) namespaceContainerEl.classList.add('hidden');\n if (failuresContainerEl) failuresContainerEl.classList.add('hidden');\n } else {\n if (allGoodEl) allGoodEl.classList.add('hidden');\n \n // Populate namespace results table\n if (namespaceContainerEl && namespaceTbodyEl && data.namespace_results) {\n namespaceContainerEl.classList.remove('hidden');\n namespaceTbodyEl.innerHTML = '';\n \n for (const [namespace, result] of Object.entries(data.namespace_results)) {\n const row = document.createElement('tr');\n \n // Namespace cell\n const nsCell = document.createElement('td');\n nsCell.textContent = namespace;\n nsCell.style.fontWeight = '500';\n row.appendChild(nsCell);\n \n // Type cell\n const typeCell = document.createElement('td');\n typeCell.textContent = result.type.replace('_', ' ');\n row.appendChild(typeCell);\n \n // Expected cell\n const expectedCell = document.createElement('td');\n expectedCell.textContent = result.expected;\n row.appendChild(expectedCell);\n \n // Actual cell\n const actualCell = document.createElement('td');\n actualCell.textContent = result.actual;\n row.appendChild(actualCell);\n \n // Delta cell\n const deltaCell = document.createElement('td');\n deltaCell.textContent = result.delta;\n if (result.delta !== 0) {\n deltaCell.style.color = '#dc2626';\n deltaCell.style.fontWeight = '600';\n }\n row.appendChild(deltaCell);\n \n // Status cell\n const statusCell = document.createElement('td');\n if (result.match) {\n statusCell.textContent = '✅ Match';\n statusCell.style.color = '#16a34a';\n } else {\n statusCell.textContent = '❌ Mismatch';\n statusCell.style.color = '#dc2626';\n }\n row.appendChild(statusCell);\n \n namespaceTbodyEl.appendChild(row);\n }\n }\n \n // Populate sample failures table\n if (failuresContainerEl && failuresTbodyEl && data.sample_failure_details && data.sample_failure_details.length > 0) {\n failuresContainerEl.classList.remove('hidden');\n failuresTbodyEl.innerHTML = '';\n \n data.sample_failure_details.forEach(failure => {\n const row = document.createElement('tr');\n \n // Namespace cell\n const nsCell = document.createElement('td');\n nsCell.textContent = failure.namespace;\n row.appendChild(nsCell);\n \n // Type cell\n const typeCell = document.createElement('td');\n typeCell.textContent = failure.type;\n row.appendChild(typeCell);\n \n // File cell\n const fileCell = document.createElement('td');\n fileCell.textContent = failure.file_name || '-';\n row.appendChild(fileCell);\n \n // Details cell\n const detailsCell = document.createElement('td');\n if (failure.type === 'missing_record') {\n detailsCell.textContent = failure.message || 'Vector not found in Pinecone';\n } else if (failure.type === 'metadata_mismatch') {\n detailsCell.textContent = `${failure.field}: expected ${JSON.stringify(failure.expected)}, got ${JSON.stringify(failure.actual)}`;\n } else {\n detailsCell.textContent = JSON.stringify(failure);\n }\n row.appendChild(detailsCell);\n \n failuresTbodyEl.appendChild(row);\n });\n } else if (failuresContainerEl) {\n failuresContainerEl.classList.add('hidden');\n }\n }\n \n } catch (error) {\n console.error('Error loading Pinecone health:', error);\n // On error loading, just show no results state\n if (noResultsEl) noResultsEl.classList.remove('hidden');\n if (resultsEl) resultsEl.classList.add('hidden');\n }\n}\n\n/**\n * Triggers a Pinecone health check and updates the UI.\n * @param {string} projectId - The ID of the project to check.\n */\nasync function triggerPineconeHealthCheck(projectId) {\n const loadingEl = document.getElementById('pinecone-health-loading');\n const errorEl = document.getElementById('pinecone-health-error');\n const errorMsgEl = document.getElementById('pinecone-health-error-message');\n const noResultsEl = document.getElementById('pinecone-health-no-results');\n const resultsEl = document.getElementById('pinecone-health-results');\n const runBtn = document.getElementById('run-pinecone-health-check');\n \n // Disable button and show loading\n if (runBtn) {\n runBtn.disabled = true;\n runBtn.textContent = 'Running...';\n }\n if (loadingEl) loadingEl.classList.remove('hidden');\n if (errorEl) errorEl.classList.add('hidden');\n if (noResultsEl) noResultsEl.classList.add('hidden');\n if (resultsEl) resultsEl.classList.add('hidden');\n \n try {\n const data = await apiCall(`/setup/setup-detail/${projectId}/pinecone-health-check`, 'POST');\n \n // Hide loading\n if (loadingEl) loadingEl.classList.add('hidden');\n \n if (!data) {\n // Show error state\n if (errorEl) errorEl.classList.remove('hidden');\n if (errorMsgEl) errorMsgEl.textContent = 'Failed to run health check. Please try again.';\n return;\n }\n \n if (data.status === 'error' && data.message) {\n // Show error with specific message\n if (errorEl) errorEl.classList.remove('hidden');\n if (errorMsgEl) errorMsgEl.textContent = data.message;\n return;\n }\n \n // Reload the results\n await loadPineconeHealth(projectId);\n \n } catch (error) {\n console.error('Error triggering Pinecone health check:', error);\n if (loadingEl) loadingEl.classList.add('hidden');\n if (errorEl) errorEl.classList.remove('hidden');\n if (errorMsgEl) errorMsgEl.textContent = 'Failed to run health check. Please try again.';\n } finally {\n // Re-enable button\n if (runBtn) {\n runBtn.disabled = false;\n runBtn.textContent = 'Run Health Check';\n }\n }\n}\n\n// --- Pinecone Regeneration Functions ---\n\n/**\n * Gets the selected namespace checkboxes for regeneration.\n * @returns {string[]} Array of selected namespace types\n */\nfunction getSelectedNamespaces() {\n const checkboxes = document.querySelectorAll('input[name=\"regenerate-namespace\"]:checked');\n return Array.from(checkboxes).map(cb => cb.value);\n}\n\n/**\n * Handles the regenerate button click.\n * Shows confirmation modal and triggers API call.\n * @param {string} projectId - The ID of the project\n */\nasync function handleRegeneratePinecone(projectId) {\n const selectedNamespaces = getSelectedNamespaces();\n const regenerateBtn = document.getElementById('regenerate-pinecone-btn');\n \n // Validate at least one namespace is selected\n if (selectedNamespaces.length === 0) {\n showModal('Selection Required', 'Please select at least one namespace to regenerate.');\n return;\n }\n \n // Format namespace names for display\n const namespaceNames = selectedNamespaces.map(ns => {\n switch(ns) {\n case 'code_chunks': return 'Code Chunks';\n case 'summaries': return 'File Summaries';\n case 'metadata_summaries': return 'Metadata Summaries';\n default: return ns;\n }\n });\n \n // Show confirmation modal\n const confirmMessage = `\n <p><strong>Notice:</strong> This action will:</p>\n <ul>\n <li>Delete existing data in the selected Pinecone namespace(s)</li>\n <li>Re-upload all data from Postgres</li>\n </ul>\n <p><strong>Selected namespaces:</strong></p>\n <ul>\n ${namespaceNames.map(n => `<li>${n}</li>`).join('')}\n </ul>\n <p>This operation could take up to 30 minutes for large repositories.</p>\n <p>Do you want to proceed?</p>\n `;\n \n showModal('Confirm Regeneration', confirmMessage, async () => {\n // User confirmed - proceed with regeneration\n if (regenerateBtn) {\n regenerateBtn.disabled = true;\n regenerateBtn.textContent = 'Starting...';\n }\n \n try {\n const response = await apiCall(`/setup/setup-detail/${projectId}/pinecone-regenerate`, 'POST', {\n namespaces: selectedNamespaces\n });\n \n if (response && response.status === 'accepted') {\n showModal('Regeneration Started', \n `Regeneration has been started for ${selectedNamespaces.length} namespace(s). ` +\n 'This process runs in the background and may take several minutes. ' +\n 'Check Sentry logs or run a health check later to verify completion.');\n \n // Uncheck all checkboxes\n document.querySelectorAll('input[name=\"regenerate-namespace\"]:checked').forEach(cb => {\n cb.checked = false;\n });\n } else {\n showModal('Error', response?.message || 'Failed to start regeneration. Please try again.');\n }\n } catch (error) {\n console.error('Error triggering Pinecone regeneration:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to start regeneration. Please try again.'));\n } finally {\n if (regenerateBtn) {\n regenerateBtn.disabled = false;\n regenerateBtn.textContent = 'Regenerate Selected';\n }\n }\n }, true); // true = show cancel button\n}\n\n// --- Config Health Functions ---\n\n/**\n * Load configuration health check data for a project.\n * @param {string} projectId - The project ID.\n */\nasync function loadConfigHealth(projectId) {\n const loadingEl = document.getElementById('config-health-loading');\n const errorEl = document.getElementById('config-health-error');\n const resultsEl = document.getElementById('config-health-results');\n const allGoodEl = document.getElementById('config-health-all-good');\n const errorsContainerEl = document.getElementById('config-errors-container');\n const warningsContainerEl = document.getElementById('config-warnings-container');\n const errorsTbodyEl = document.getElementById('config-errors-tbody');\n const warningsTbodyEl = document.getElementById('config-warnings-tbody');\n const totalRequiredEl = document.getElementById('config-total-required');\n const configuredEl = document.getElementById('config-configured');\n const errorsCountEl = document.getElementById('config-errors-count');\n const warningsCountEl = document.getElementById('config-warnings-count');\n \n // Show loading, hide others\n if (loadingEl) loadingEl.classList.remove('hidden');\n if (errorEl) errorEl.classList.add('hidden');\n if (resultsEl) resultsEl.classList.add('hidden');\n \n try {\n const data = await apiCall(`/setup/data/${projectId}/config-health`, 'GET');\n \n // Hide loading\n if (loadingEl) loadingEl.classList.add('hidden');\n \n if (!data) {\n // Show error state\n if (errorEl) errorEl.classList.remove('hidden');\n return;\n }\n \n // Show results container\n if (resultsEl) resultsEl.classList.remove('hidden');\n \n // Update summary stats\n if (totalRequiredEl) totalRequiredEl.textContent = data.total_required_fields || 0;\n if (configuredEl) configuredEl.textContent = data.configured_fields || 0;\n if (errorsCountEl) errorsCountEl.textContent = data.error_count || 0;\n if (warningsCountEl) warningsCountEl.textContent = data.warning_count || 0;\n \n // Handle all good vs issues state\n if ((!data.errors || data.errors.length === 0) && (!data.warnings || data.warnings.length === 0)) {\n // Show all good state\n if (allGoodEl) allGoodEl.classList.remove('hidden');\n if (errorsContainerEl) errorsContainerEl.classList.add('hidden');\n if (warningsContainerEl) warningsContainerEl.classList.add('hidden');\n } else {\n // Hide all good state\n if (allGoodEl) allGoodEl.classList.add('hidden');\n \n // Handle errors table\n if (!data.errors || data.errors.length === 0) {\n if (errorsContainerEl) errorsContainerEl.classList.add('hidden');\n } else {\n if (errorsContainerEl) errorsContainerEl.classList.remove('hidden');\n \n // Populate errors table\n if (errorsTbodyEl) {\n errorsTbodyEl.innerHTML = '';\n \n data.errors.forEach(error => {\n const row = document.createElement('tr');\n \n // Field name cell\n const fieldCell = document.createElement('td');\n fieldCell.textContent = error.display_name;\n fieldCell.style.fontWeight = '500';\n row.appendChild(fieldCell);\n \n // Section cell\n const sectionCell = document.createElement('td');\n sectionCell.textContent = error.section;\n row.appendChild(sectionCell);\n \n // Status cell\n const statusCell = document.createElement('td');\n statusCell.textContent = error.status;\n statusCell.style.fontWeight = '600';\n statusCell.style.color = error.status === 'missing' ? '#dc2626' : '#ea580c';\n statusCell.style.textTransform = 'capitalize';\n row.appendChild(statusCell);\n \n // Description cell\n const descCell = document.createElement('td');\n descCell.textContent = error.description;\n row.appendChild(descCell);\n \n errorsTbodyEl.appendChild(row);\n });\n }\n }\n \n // Handle warnings table\n if (!data.warnings || data.warnings.length === 0) {\n if (warningsContainerEl) warningsContainerEl.classList.add('hidden');\n } else {\n if (warningsContainerEl) warningsContainerEl.classList.remove('hidden');\n \n // Populate warnings table\n if (warningsTbodyEl) {\n warningsTbodyEl.innerHTML = '';\n \n data.warnings.forEach(warning => {\n const row = document.createElement('tr');\n \n // Field name cell\n const fieldCell = document.createElement('td');\n fieldCell.textContent = warning.display_name;\n fieldCell.style.fontWeight = '500';\n row.appendChild(fieldCell);\n \n // Section cell\n const sectionCell = document.createElement('td');\n sectionCell.textContent = warning.section;\n row.appendChild(sectionCell);\n \n // Description cell\n const descCell = document.createElement('td');\n descCell.textContent = warning.description;\n row.appendChild(descCell);\n \n // Action cell (Dismiss button)\n const actionCell = document.createElement('td');\n const dismissBtn = document.createElement('button');\n dismissBtn.textContent = 'Dismiss';\n dismissBtn.className = 'btn-text';\n dismissBtn.dataset.fieldName = warning.field_name;\n dismissBtn.addEventListener('click', () => handleDismissWarning(projectId, warning.field_name));\n actionCell.appendChild(dismissBtn);\n row.appendChild(actionCell);\n \n warningsTbodyEl.appendChild(row);\n });\n }\n }\n }\n \n } catch (error) {\n console.error('[ERROR] Failed to load config health:', error);\n \n // Hide loading, show error\n if (loadingEl) loadingEl.classList.add('hidden');\n if (errorEl) errorEl.classList.remove('hidden');\n }\n}\n\n/**\n * Handle dismissing a config warning.\n * @param {string} projectId - The project ID.\n * @param {string} fieldName - The field name to dismiss.\n */\nasync function handleDismissWarning(projectId, fieldName) {\n try {\n await apiCall(`/setup/data/${projectId}/config-health/dismiss-warning`, 'POST', {\n field_name: fieldName\n });\n \n // Reload config health to refresh the list\n await loadConfigHealth(projectId);\n \n } catch (error) {\n console.error('[ERROR] Failed to dismiss warning:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to dismiss warning. Please try again.'));\n }\n}\n\n// --- Initialization ---\n\n/**\n * Loads all data health information for a project.\n * @param {string} projectId - The ID of the project.\n */\nasync function loadDataHealth(projectId) {\n // Load all health checks in parallel (non-blocking)\n loadDataIntegrity(projectId);\n loadConfigHealth(projectId);\n loadPineconeHealth(projectId);\n}\n\n/**\n * Initialize the data health page.\n */\nasync function init() {\n const currentPath = window.location.pathname;\n\n // Check if we're on the data health page\n if (currentPath.startsWith('/setup/data/')) {\n // Initialize modal system\n initModal();\n \n const projectId = getProjectId();\n \n if (!projectId) {\n showModal('Error', 'Project ID not found.');\n return;\n }\n \n // Setup drawers\n setupDrawers();\n \n // Populate timezone dropdown\n populateTimezoneDropdown();\n \n // Setup button event listeners\n const updateRepositoryBtn = document.getElementById('update-repository-btn');\n if (updateRepositoryBtn) {\n updateRepositoryBtn.addEventListener('click', handleUpdateRepository);\n }\n \n const scheduleRepositoryBtn = document.getElementById('schedule-repository-btn');\n if (scheduleRepositoryBtn) {\n scheduleRepositoryBtn.addEventListener('click', handleScheduleParseRepository);\n }\n\n const pauseScheduleBtn = document.getElementById('pause-schedule-btn');\n if (pauseScheduleBtn) {\n pauseScheduleBtn.addEventListener('click', handlePauseParseSchedule);\n }\n\n const resumeScheduleBtn = document.getElementById('resume-schedule-btn');\n if (resumeScheduleBtn) {\n resumeScheduleBtn.addEventListener('click', handleResumeParseSchedule);\n }\n\n const cancelScheduleBtn = document.getElementById('cancel-schedule-btn');\n if (cancelScheduleBtn) {\n cancelScheduleBtn.addEventListener('click', handleCancelParseSchedule);\n }\n\n const updateLabelsBtn = document.getElementById('update-labels-btn');\n if (updateLabelsBtn) {\n updateLabelsBtn.addEventListener('click', handleUpdateLabels);\n }\n\n // Phase 1b: Learn-repository auto-update schedule\n const learnRepoScheduleBtn = document.getElementById('learn-repo-schedule-btn');\n if (learnRepoScheduleBtn) {\n learnRepoScheduleBtn.addEventListener('click', handleScheduleLearnRepository);\n }\n loadLearnRepoScheduledJobs(projectId);\n \n // Setup Pinecone health check button\n const runPineconeHealthBtn = document.getElementById('run-pinecone-health-check');\n if (runPineconeHealthBtn) {\n runPineconeHealthBtn.addEventListener('click', () => triggerPineconeHealthCheck(projectId));\n }\n \n // Setup Pinecone regenerate button\n const regeneratePineconeBtn = document.getElementById('regenerate-pinecone-btn');\n if (regeneratePineconeBtn) {\n regeneratePineconeBtn.addEventListener('click', () => handleRegeneratePinecone(projectId));\n }\n \n // Setup namespace info tooltip buttons\n document.querySelectorAll('.info-tooltip-btn').forEach(btn => {\n btn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n const namespace = btn.dataset.namespace;\n const infoDiv = document.getElementById(`info-${namespace}`);\n if (infoDiv) {\n infoDiv.classList.toggle('hidden');\n }\n });\n });\n \n // Load all health data\n await loadDataHealth(projectId);\n }\n}\n\n// Initialize the page when DOM is fully loaded\ndocument.addEventListener('DOMContentLoaded', init);\n","// delete-modal.js\n\nexport async function deleteProjectById(projectId) {\n try {\n const response = await fetch(`/setup/projects/${projectId}`, {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n if (!response.ok) {\n throw new Error('Failed to delete project');\n }\n return true;\n } catch (error) {\n // Optionally, show a modal or log error\n return false;\n }\n}\n\n// Handles the special confirmation modal for permanent project deletion\n\nlet deleteModal = null;\nlet deleteModalOverlay = null;\nlet deleteModalProjectName = null;\nlet deleteModalInput = null;\nlet deleteModalError = null;\nlet deleteModalCancelBtn = null;\nlet deleteModalConfirmBtn = null;\n\nfunction createDeleteModalElements() {\n deleteModal = document.getElementById('delete-project-modal');\n deleteModalOverlay = document.getElementById('delete-project-modal-overlay');\n deleteModalProjectName = document.getElementById('delete-project-modal-project-name');\n deleteModalInput = document.getElementById('delete-project-modal-input');\n deleteModalError = document.getElementById('delete-project-modal-error');\n deleteModalCancelBtn = document.getElementById('delete-project-modal-cancel');\n deleteModalConfirmBtn = document.getElementById('delete-project-modal-confirm');\n}\n\nexport function showDeleteModal(projectName, onConfirm, onCancel) {\n if (!deleteModal) createDeleteModalElements();\n deleteModalInput.value = '';\n deleteModalError.textContent = '';\n deleteModalProjectName.textContent = projectName;\n // deleteModalConfirmBtn.disabled = true;\n\n function handleInput() {\n if (deleteModalInput.value === projectName) {\n // deleteModalConfirmBtn.disabled = false;\n deleteModalError.textContent = '';\n } else {\n // deleteModalConfirmBtn.disabled = true;\n if (deleteModalInput.value.length > 0)\n deleteModalError.textContent = 'Project name does not match.';\n else\n deleteModalError.textContent = '';\n }\n }\n\n function handleCancel() {\n hideDeleteModal();\n if (onCancel) onCancel();\n }\n\n function handleConfirm() {\n if (deleteModalInput.value === projectName) {\n hideDeleteModal();\n if (onConfirm) onConfirm();\n }\n }\n\n deleteModalInput.addEventListener('input', handleInput);\n deleteModalCancelBtn.addEventListener('click', handleCancel);\n deleteModalOverlay.addEventListener('click', handleCancel);\n deleteModalConfirmBtn.addEventListener('click', handleConfirm);\n\n deleteModal.style.display = 'block';\n deleteModalOverlay.style.display = 'block';\n\n // Focus input\n setTimeout(() => deleteModalInput.focus(), 100);\n\n // Remove listeners and hide modal on close\n function hideDeleteModal() {\n deleteModal.style.display = 'none';\n deleteModalOverlay.style.display = 'none';\n deleteModalInput.removeEventListener('input', handleInput);\n deleteModalCancelBtn.removeEventListener('click', handleCancel);\n deleteModalOverlay.removeEventListener('click', handleCancel);\n deleteModalConfirmBtn.removeEventListener('click', handleConfirm);\n }\n\n // Expose hide function for external use\n showDeleteModal.hide = hideDeleteModal;\n}\n","import { initModal } from './modal.js';\nimport { apiCall, getApiErrorMessage, initDrawers } from './utils.js';\n\n// ============================================================\n// Install-key constants\n// ============================================================\n\n// Onboarding-generated keys are always stamped as admin \"Easy Install\" keys so\n// they are recognizable on the Security page and authorized for /install-bridge.\nconst INSTALL_KEY_NAME = 'Easy Install';\nconst INSTALL_KEY_ROLE = 'admin';\nconst INSTALL_KEY_EMAIL_DOMAIN = 'bridgegpt-api.com';\n\n// Holds the plaintext minted key for the current page lifetime ONLY. Never\n// persisted to storage, cookies, URLs, datasets, or sent back to the server.\nlet generatedInstallKey = null;\n\n// ============================================================\n// Clipboard helper\n// ============================================================\n\n// Centralized, keyboard-accessible clipboard write used by snippet copy,\n// /install-bridge handoff copy, and the generated-key copy. Falls back to a\n// hidden textarea + execCommand when the async clipboard API is unavailable.\n// Does NOT log the copied text — it may be a plaintext API key.\nasync function copyTextToClipboard(text) {\n if (navigator.clipboard && navigator.clipboard.writeText) {\n await navigator.clipboard.writeText(text);\n return;\n }\n const ta = document.createElement('textarea');\n ta.value = text;\n ta.style.position = 'fixed';\n ta.style.opacity = '0';\n document.body.appendChild(ta);\n ta.focus();\n ta.select();\n try {\n document.execCommand('copy');\n } finally {\n document.body.removeChild(ta);\n }\n}\n\n// Derive a stable, non-empty synthetic email for the minted key. Setup sessions\n// expose client_id, not a user email, but the key-creation route only requires\n// a non-empty email — this address is repo-derived, stable, and recognizable in\n// the Security page key list.\nfunction deriveInstallKeyEmail(repoName) {\n let sanitized = String(repoName == null ? '' : repoName)\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9._+-]/g, '-')\n // Trim leading/trailing punctuation that would make the local part awkward.\n .replace(/^[._+-]+/, '')\n .replace(/[._+-]+$/, '');\n if (!sanitized) sanitized = 'project';\n return `install+${sanitized}@${INSTALL_KEY_EMAIL_DOMAIN}`;\n}\n\n// ============================================================\n// Snippet tabs\n// ============================================================\n\nfunction renderSnippetTabs(snippets) {\n const tabList = document.getElementById('editor-tabs');\n const panelContainer = document.getElementById('editor-tab-panels');\n if (!tabList || !panelContainer) return;\n\n tabList.innerHTML = '';\n panelContainer.innerHTML = '';\n\n snippets.forEach((snippet, idx) => {\n const panelId = `snippet-panel-${snippet.id}`;\n const tabId = `snippet-tab-${snippet.id}`;\n const isFirst = idx === 0;\n\n // Tab button\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.id = tabId;\n btn.className = 'snippet-tab-btn' + (isFirst ? ' active' : '');\n btn.setAttribute('role', 'tab');\n btn.setAttribute('aria-selected', isFirst ? 'true' : 'false');\n btn.setAttribute('aria-controls', panelId);\n btn.setAttribute('tabindex', isFirst ? '0' : '-1');\n btn.textContent = snippet.label;\n btn.addEventListener('click', () => activateTab(idx, snippets));\n tabList.appendChild(btn);\n\n // Panel\n const panel = document.createElement('div');\n panel.id = panelId;\n panel.className = 'snippet-panel' + (isFirst ? '' : ' hidden');\n panel.setAttribute('role', 'tabpanel');\n panel.setAttribute('aria-labelledby', tabId);\n if (!isFirst) panel.hidden = true;\n\n // Metadata row\n const meta = document.createElement('div');\n meta.className = 'snippet-meta';\n meta.textContent = `${snippet.target_path} · ${snippet.language}`;\n panel.appendChild(meta);\n\n if (snippet.notes) {\n const notes = document.createElement('p');\n notes.className = 'snippet-notes';\n notes.textContent = snippet.notes;\n panel.appendChild(notes);\n }\n\n // Code block built from parts\n const pre = document.createElement('pre');\n const code = document.createElement('code');\n code.className = `language-${snippet.language}`;\n\n (snippet.parts || []).forEach(part => {\n if (part.kind === 'api_key') {\n const span = document.createElement('span');\n span.setAttribute('data-bapi-api-key', '');\n span.className = 'bapi-api-key-slot';\n span.textContent = part.text;\n code.appendChild(span);\n } else {\n code.appendChild(document.createTextNode(part.text));\n }\n });\n pre.appendChild(code);\n panel.appendChild(pre);\n\n // Copy row\n const copyRow = document.createElement('div');\n copyRow.className = 'snippet-copy-row';\n\n const copyBtn = document.createElement('button');\n copyBtn.type = 'button';\n copyBtn.className = 'btn btn-secondary snippet-copy-btn';\n copyBtn.textContent = 'Copy';\n\n const copyStatus = document.createElement('span');\n copyStatus.className = 'copy-status';\n copyStatus.setAttribute('aria-live', 'polite');\n\n copyBtn.addEventListener('click', async () => {\n const text = code.textContent;\n try {\n await copyTextToClipboard(text);\n copyStatus.textContent = 'Copied!';\n setTimeout(() => { copyStatus.textContent = ''; }, 2000);\n } catch (_) {\n copyStatus.textContent = 'Copy failed';\n }\n });\n\n copyRow.appendChild(copyBtn);\n copyRow.appendChild(copyStatus);\n panel.appendChild(copyRow);\n\n panelContainer.appendChild(panel);\n });\n\n // If a key was already minted during this page lifetime, re-inject it so the\n // snippet contract stays intact across re-renders (e.g. a retry).\n if (generatedInstallKey) {\n injectInstallKeyIntoSnippets(generatedInstallKey);\n }\n}\n\nfunction activateTab(idx, snippets) {\n const tabList = document.getElementById('editor-tabs');\n const panelContainer = document.getElementById('editor-tab-panels');\n if (!tabList || !panelContainer) return;\n\n const tabs = tabList.querySelectorAll('[role=\"tab\"]');\n const panels = panelContainer.querySelectorAll('[role=\"tabpanel\"]');\n\n tabs.forEach((tab, i) => {\n const active = i === idx;\n tab.setAttribute('aria-selected', active ? 'true' : 'false');\n tab.setAttribute('tabindex', active ? '0' : '-1');\n tab.classList.toggle('active', active);\n });\n\n panels.forEach((panel, i) => {\n const active = i === idx;\n panel.hidden = !active;\n panel.classList.toggle('hidden', !active);\n });\n}\n\n// ============================================================\n// /install-bridge hand-off\n// ============================================================\n\nfunction initializeHandoffCopy() {\n const btn = document.getElementById('handoff-copy-btn');\n const status = document.getElementById('handoff-copy-status');\n if (!btn || !status) return;\n\n btn.addEventListener('click', async () => {\n try {\n await copyTextToClipboard('/install-bridge');\n status.textContent = 'Copied!';\n setTimeout(() => { status.textContent = ''; }, 2000);\n } catch (_) {\n status.textContent = 'Copy failed';\n }\n });\n}\n\n// ============================================================\n// Config-status readout\n// ============================================================\n\nfunction renderInstallStatus(installStatus) {\n const summaryEl = document.getElementById('install-status-summary');\n const groupsEl = document.getElementById('install-status-groups');\n if (!summaryEl || !groupsEl) return;\n\n if (!installStatus || !installStatus.available) {\n summaryEl.innerHTML = '<p class=\"status-empty\">No configuration found for this project.</p>';\n groupsEl.innerHTML = '';\n return;\n }\n\n const { summary, groups } = installStatus;\n if (summary) {\n const row = document.createElement('div');\n row.className = 'status-summary';\n\n const setChip = document.createElement('span');\n setChip.className = 'status-chip status-set';\n setChip.textContent = `${summary.set} Set`;\n\n const unsetChip = document.createElement('span');\n unsetChip.className = 'status-chip status-unset';\n unsetChip.textContent = `${summary.unset} Unset`;\n\n const total = document.createElement('span');\n total.className = 'status-total';\n total.textContent = `of ${summary.total} fields`;\n\n row.appendChild(setChip);\n row.appendChild(unsetChip);\n row.appendChild(total);\n summaryEl.appendChild(row);\n }\n\n if (!groups || groups.length === 0) {\n groupsEl.innerHTML = '<p class=\"status-empty\">No configuration fields found.</p>';\n return;\n }\n\n groupsEl.innerHTML = '';\n groups.forEach(group => {\n if (!group.fields || group.fields.length === 0) return;\n const section = document.createElement('div');\n section.className = 'status-group';\n\n const heading = document.createElement('h3');\n heading.className = 'status-group-title';\n heading.textContent = group.title || group.key;\n section.appendChild(heading);\n\n group.fields.forEach(field => {\n const row = document.createElement('div');\n row.className = 'status-field-row';\n\n const chip = document.createElement('span');\n chip.className = `status-chip ${field.is_set ? 'status-set' : 'status-unset'}`;\n chip.textContent = field.is_set ? 'Set' : 'Unset';\n\n const label = document.createElement('span');\n label.className = 'status-field-name';\n label.textContent = field.field_name;\n\n row.appendChild(chip);\n row.appendChild(label);\n\n if (field.current_value !== null && field.current_value !== undefined) {\n const val = document.createElement('span');\n val.className = 'status-field-value';\n const display =\n typeof field.current_value === 'object'\n ? JSON.stringify(field.current_value)\n : String(field.current_value);\n val.textContent = display;\n row.appendChild(val);\n }\n\n section.appendChild(row);\n });\n\n groupsEl.appendChild(section);\n });\n}\n\n// ============================================================\n// Data loading\n// ============================================================\n\nasync function loadInstallInstructions(projectId) {\n const loadingEl = document.getElementById('get-started-loading');\n const errorEl = document.getElementById('get-started-error');\n const contentEl = document.getElementById('get-started-content');\n\n if (loadingEl) loadingEl.classList.remove('hidden');\n if (errorEl) errorEl.classList.add('hidden');\n if (contentEl) contentEl.classList.add('hidden');\n\n try {\n const data = await apiCall(\n `/setup/install-instructions?projectId=${encodeURIComponent(projectId)}`,\n 'GET'\n );\n\n // apiCall returns null on centralized 401 redirect\n if (!data) return;\n\n if (loadingEl) loadingEl.classList.add('hidden');\n\n // Defensive sync: keep the container's repo dataset authoritative from\n // the install-instructions payload so the install-key click handler\n // reads the correct repo_name even if the server attribute was empty.\n const containerEl = document.getElementById('get-started-container');\n if (containerEl && data.repo_name) {\n containerEl.dataset.repoName = data.repo_name;\n }\n\n const repoLabel = document.getElementById('get-started-repo-label');\n if (repoLabel) {\n repoLabel.textContent = `Project: ${data.repo_name}`;\n }\n\n renderSnippetTabs(data.snippets || []);\n initializeHandoffCopy();\n renderInstallStatus(data.install_status);\n\n if (contentEl) contentEl.classList.remove('hidden');\n } catch (error) {\n if (loadingEl) loadingEl.classList.add('hidden');\n if (contentEl) contentEl.classList.add('hidden');\n\n const msgEl = document.getElementById('get-started-error-msg');\n if (msgEl) {\n msgEl.textContent = getApiErrorMessage(error, 'Failed to load install instructions.');\n }\n if (errorEl) errorEl.classList.remove('hidden');\n }\n}\n\n// ============================================================\n// Install-key generation\n// ============================================================\n\n// Replace every onboarding snippet's data-bapi-api-key placeholder with the\n// minted key, scoped strictly to the onboarding container. Returns the number\n// of updated elements so callers can detect an unexpected empty-snippet state.\nfunction injectInstallKeyIntoSnippets(apiKey) {\n const slots = document.querySelectorAll('#get-started-container [data-bapi-api-key]');\n slots.forEach(el => {\n el.textContent = apiKey;\n el.classList.add('bapi-api-key-slot-filled');\n });\n return slots.length;\n}\n\n// Toggle the loading state on the generate button. When loading ends without\n// success the caller passes loading=false to restore the idle affordance.\nfunction setInstallKeyLoading(loading) {\n const btn = document.getElementById('generate-install-key-btn');\n const panel = document.getElementById('install-key-panel');\n const errorEl = document.getElementById('install-key-error');\n if (loading) {\n if (errorEl) {\n errorEl.textContent = '';\n errorEl.classList.add('hidden');\n }\n if (btn) {\n btn.disabled = true;\n btn.textContent = 'Generating…';\n }\n if (panel) panel.setAttribute('aria-busy', 'true');\n } else {\n if (btn) {\n btn.disabled = false;\n btn.textContent = 'Generate install key';\n }\n if (panel) panel.removeAttribute('aria-busy');\n }\n}\n\n// Write a brief, non-leaking failure message near the action. Never surface raw\n// server error details, request body fields, or any plaintext key.\nfunction showInstallKeyError(message) {\n const errorEl = document.getElementById('install-key-error');\n if (!errorEl) return;\n errorEl.textContent = message || 'Could not generate the install key. Please try again.';\n errorEl.classList.remove('hidden');\n}\n\n// Mint an admin install key on explicit click via the existing POST /setup/keys\n// route, then reveal it once and inject it into every snippet.\nasync function handleGenerateInstallKeyClick() {\n const container = document.getElementById('get-started-container');\n if (!container) return;\n\n // Prevent silent repeat minting within the same page lifetime. By design\n // this guard is per-page-load only: a reload intentionally allows minting a\n // fresh on-demand key (BAPI-383). Unused keys are revocable from the\n // Security page, so we accept this over reusing/auto-revoking prior keys.\n if (generatedInstallKey) return;\n\n const repoName = container.dataset.repoName;\n if (!repoName) {\n showInstallKeyError('Could not generate the install key. Please try again.');\n return;\n }\n\n setInstallKeyLoading(true);\n\n const payload = {\n email: deriveInstallKeyEmail(repoName),\n repo_name: repoName,\n name: INSTALL_KEY_NAME,\n role: INSTALL_KEY_ROLE,\n };\n\n let response;\n try {\n response = await fetch('/setup/keys', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n });\n } catch (_) {\n setInstallKeyLoading(false);\n showInstallKeyError('Could not generate the install key. Please try again.');\n return;\n }\n\n // Only a 201 Created is success. Do not parse/display raw server details.\n if (response.status !== 201) {\n setInstallKeyLoading(false);\n showInstallKeyError('Could not generate the install key. Please try again.');\n return;\n }\n\n let data;\n try {\n data = await response.json();\n } catch (_) {\n setInstallKeyLoading(false);\n showInstallKeyError('Could not generate the install key. Please try again.');\n return;\n }\n\n const apiKey = data && data.api_key;\n if (!apiKey) {\n setInstallKeyLoading(false);\n showInstallKeyError('Could not generate the install key. Please try again.');\n return;\n }\n\n generatedInstallKey = apiKey;\n\n const keyInput = document.getElementById('generated-install-key');\n if (keyInput) keyInput.value = apiKey;\n\n const resultEl = document.getElementById('install-key-result');\n if (resultEl) resultEl.classList.remove('hidden');\n\n injectInstallKeyIntoSnippets(apiKey);\n\n const statusEl = document.getElementById('install-key-status');\n if (statusEl) statusEl.textContent = 'Admin install key generated and inserted into the snippets.';\n\n // Permanently disable the button after success to avoid repeated minting.\n const btn = document.getElementById('generate-install-key-btn');\n const panel = document.getElementById('install-key-panel');\n if (btn) {\n btn.disabled = true;\n btn.textContent = 'Install key generated';\n }\n if (panel) panel.removeAttribute('aria-busy');\n\n // Move focus to the revealed credential controls for keyboard/screen-reader users.\n const copyBtn = document.getElementById('copy-install-key-btn');\n if (copyBtn) copyBtn.focus();\n else if (keyInput) keyInput.focus();\n}\n\n// Page-scoped, click-driven initializer. Binds the generate and copy buttons.\n// Returns without side effects if the panel is absent. Does NOT mint on load.\nfunction initializeInstallKeyGeneration(container) {\n if (!container) return;\n\n const generateBtn = document.getElementById('generate-install-key-btn');\n if (!generateBtn) return;\n\n generateBtn.addEventListener('click', handleGenerateInstallKeyClick);\n\n const copyBtn = document.getElementById('copy-install-key-btn');\n const keyInput = document.getElementById('generated-install-key');\n const copyStatus = document.getElementById('copy-install-key-status');\n if (copyBtn && keyInput) {\n copyBtn.addEventListener('click', async () => {\n const value = keyInput.value;\n if (!value) return;\n try {\n await copyTextToClipboard(value);\n if (copyStatus) copyStatus.textContent = 'Copied!';\n copyBtn.textContent = 'Copied!';\n setTimeout(() => { copyBtn.textContent = 'Copy'; }, 2000);\n } catch (_) {\n if (copyStatus) copyStatus.textContent = 'Copy failed';\n }\n });\n }\n}\n\n// ============================================================\n// Entry point\n// ============================================================\n\nexport function init() {\n const container = document.getElementById('get-started-container');\n if (!container) return;\n\n initModal();\n\n // Click-driven install-key generation; does not mint a key on page load.\n initializeInstallKeyGeneration(container);\n\n // Bind keyboard navigation once — renderSnippetTabs may run multiple times (retry),\n // so binding here prevents the listener from stacking on the tabList element.\n const tabList = document.getElementById('editor-tabs');\n if (tabList) {\n tabList.addEventListener('keydown', (e) => {\n const tabs = Array.from(tabList.querySelectorAll('[role=\"tab\"]'));\n if (!tabs.length) return;\n const current = tabs.findIndex(t => t.getAttribute('aria-selected') === 'true');\n let next = current;\n if (e.key === 'ArrowRight') next = (current + 1) % tabs.length;\n else if (e.key === 'ArrowLeft') next = (current - 1 + tabs.length) % tabs.length;\n else if (e.key === 'Home') next = 0;\n else if (e.key === 'End') next = tabs.length - 1;\n else return;\n e.preventDefault();\n activateTab(next);\n tabs[next].focus();\n });\n }\n\n const params = new URLSearchParams(window.location.search);\n const projectId = params.get('projectId');\n\n // Wire the setup-detail link (FAQ drawer) and footer CTA separately\n const setupDetailLink = document.getElementById('setup-detail-link');\n if (setupDetailLink && projectId) {\n setupDetailLink.href = `/setup/setup-detail?projectId=${encodeURIComponent(projectId)}`;\n }\n const continueSetupLink = document.getElementById('continue-setup-link');\n if (continueSetupLink && projectId) {\n continueSetupLink.href = `/setup/setup-detail?projectId=${encodeURIComponent(projectId)}`;\n }\n\n // Initialize FAQ drawers scoped to this page\n initDrawers({ root: document, drawerSelector: '#get-started-container .drawer' });\n\n // Wire retry button\n const retryBtn = document.getElementById('get-started-retry-btn');\n if (retryBtn) {\n retryBtn.addEventListener('click', () => {\n if (projectId) loadInstallInstructions(projectId);\n });\n }\n\n if (!projectId) {\n const loadingEl = document.getElementById('get-started-loading');\n const errorEl = document.getElementById('get-started-error');\n const msgEl = document.getElementById('get-started-error-msg');\n if (loadingEl) loadingEl.classList.add('hidden');\n if (msgEl) msgEl.textContent = 'Missing projectId — please navigate here from your project dashboard.';\n if (errorEl) errorEl.classList.remove('hidden');\n return;\n }\n\n loadInstallInstructions(projectId);\n}\n\ninit();\n","/**\n * Learn Repository feature for the Optimize page.\n *\n * Routed through the shared Optimize plumbing (BAPI-307): canonical identity from\n * getOptimizeContext, status normalization via the pure adapters, declarative\n * per-section rendering via the renderer, polling via pollUntil, and inline\n * feedback via setInlineStatus. Initialization is driven by the single Optimize\n * page entrypoint (optimize.js) — this module no longer self-registers a\n * DOMContentLoaded listener. Phase 2 removed the global Begin/Resume CTA in\n * favor of the per-section \"Run Analysis\" model.\n */\n\nimport { apiCall, showLoading, hideLoading, getApiErrorMessage } from './utils.js';\nimport { showModal } from './modal.js';\nimport { getOptimizeContext, encodePathSegment } from './optimize/context.js';\nimport { ANALYSIS_TYPES, adaptLearnRepositoryStatus } from './optimize/status_adapters.js';\nimport { pollUntil, PollTimeoutError } from './optimize/polling.js';\nimport { setInlineStatus } from './optimize/feedback.js';\nimport {\n getSectionId,\n renderLearnRepositorySection,\n renderSectionRunning,\n renderSectionDraft,\n renderSectionComplete,\n} from './optimize/learn_repository_renderer.js';\n\n// Active polls keyed by analysis type (per-section) plus the global learning\n// poll. Each value is an AbortController so a new poll can cancel the prior one.\nconst activePolls = new Map();\n\nlet latestLearnRepositoryStatus = null;\nconst GLOBAL_LEARNING_POLL_KEY = 'global-learning';\nconst LEARNING_POLL_INTERVAL_MS = 30000;\nconst LEARNING_POLL_TIMEOUT_MS = 600000;\n\n// Canonical repository name, sourced once from the shared Optimize context. This\n// is the machine-readable identifier used in API URLs and POST payloads (never\n// the human-facing display name).\nlet currentRepoName = null;\n\n/**\n * Initialize the Learn Repository feature.\n * @param {{repoName: (string|null)}} [context] - Shared Optimize context.\n */\nexport async function initLearnRepository(context = getOptimizeContext()) {\n currentRepoName = context && context.repoName;\n if (!currentRepoName) {\n console.error('Repository name not available');\n return;\n }\n\n try {\n // Fetch initial status\n const data = await apiCall(\n `/optimize/learn-repository/${encodePathSegment(currentRepoName)}/status`,\n 'GET'\n );\n if (data) {\n renderUI(data);\n } else {\n showStatusMessage('Failed to load repository learning status', 'error');\n }\n } catch (error) {\n console.error('Error fetching learn repository status:', error);\n showStatusMessage('Error loading repository learning status', 'error');\n }\n\n // Set up event listeners\n setupEventListeners();\n}\n\nfunction updateLatestLearnRepositoryStatus(data) {\n if (!data) return;\n latestLearnRepositoryStatus = data;\n}\n\n/**\n * Render every Learn Repository section from a status payload. Each section is\n * normalized by the pure adapter and rendered declaratively by the renderer.\n * When the server reports a run in progress and no poll is active, a global poll\n * is started (idempotent — safe to call from re-renders inside the poll itself).\n * @param {Object} data\n */\nexport function renderUI(data) {\n updateLatestLearnRepositoryStatus(data);\n\n const normalized = adaptLearnRepositoryStatus(data);\n\n ANALYSIS_TYPES.forEach((type) => {\n renderLearnRepositorySection(type, data[type] || {});\n });\n\n if (normalized.learningInProgress && !activePolls.get(GLOBAL_LEARNING_POLL_KEY)) {\n pollLearningStatus();\n }\n}\n\nfunction setupEventListeners() {\n // Approve buttons for each analysis type\n ANALYSIS_TYPES.forEach((type) => {\n const sectionId = getSectionId(type);\n const approveBtn = document.getElementById(`lr-${sectionId}-approve`);\n if (approveBtn) {\n approveBtn.addEventListener('click', () => handleApprove(type));\n }\n\n // Save buttons\n const saveBtn = document.getElementById(`lr-${sectionId}-save`);\n if (saveBtn) {\n saveBtn.addEventListener('click', () => handleSaveInstructions(type));\n }\n });\n\n // Reanalyze controls — now <button>s; reveal options instead of direct-fire.\n document.querySelectorAll('.lr-reanalyze-link').forEach((link) => {\n link.addEventListener('click', () => {\n link.classList.add('hidden');\n const container = link.closest('.reanalyze-container');\n const optionsWrapper = container?.querySelector('.lr-reanalyze-options');\n optionsWrapper?.classList.remove('hidden');\n const defaultRadio = optionsWrapper?.querySelector('input[value=\"auto_complete\"]');\n defaultRadio?.focus();\n });\n });\n\n // Confirm buttons — invoke handleReanalyze\n document.querySelectorAll('.lr-reanalyze-confirm-btn').forEach((btn) => {\n btn.addEventListener('click', (e) => {\n e.preventDefault();\n handleReanalyze(btn.dataset.analysisType);\n });\n });\n\n // Cancel buttons — collapse options back to initial state\n document.querySelectorAll('.lr-reanalyze-cancel-btn').forEach((btn) => {\n btn.addEventListener('click', (e) => {\n e.preventDefault();\n const container = btn.closest('.reanalyze-container');\n const optionsWrapper = container?.querySelector('.lr-reanalyze-options');\n const reanalyzeLink = container?.querySelector('.lr-reanalyze-link');\n optionsWrapper?.classList.add('hidden');\n reanalyzeLink?.classList.remove('hidden');\n // Reset radio selection to auto_complete\n const defaultRadio = optionsWrapper?.querySelector('input[value=\"auto_complete\"]');\n if (defaultRadio) defaultRadio.checked = true;\n reanalyzeLink?.focus();\n });\n });\n\n // Accept/Discard draft buttons\n document.querySelectorAll('.lr-accept-draft-btn').forEach((btn) => {\n btn.addEventListener('click', () => handleAcceptDraft(btn.dataset.analysisType));\n });\n document.querySelectorAll('.lr-discard-draft-btn').forEach((btn) => {\n btn.addEventListener('click', () => handleDiscardDraft(btn.dataset.analysisType));\n });\n\n // Per-section \"Run Analysis\" buttons for empty sections (the single start path)\n document.querySelectorAll('.lr-initial-analyze-btn').forEach((btn) => {\n btn.addEventListener('click', () => handleInitialAnalyze(btn.dataset.analysisType));\n });\n}\n\nfunction stopGlobalLearningPoll() {\n const controller = activePolls.get(GLOBAL_LEARNING_POLL_KEY);\n if (controller) {\n controller.abort();\n activePolls.delete(GLOBAL_LEARNING_POLL_KEY);\n }\n}\n\nfunction pollLearningStatus() {\n stopGlobalLearningPoll();\n\n const controller = new AbortController();\n activePolls.set(GLOBAL_LEARNING_POLL_KEY, controller);\n const statusUrl = `/optimize/learn-repository/${encodePathSegment(currentRepoName)}/status`;\n\n // Note: a full 'all' run can take ~30 minutes, longer than\n // LEARNING_POLL_TIMEOUT_MS — once the timeout fires the user sees a refresh\n // prompt; on refresh, renderUI's learning_in_progress branch resumes polling.\n pollUntil(statusUrl, (data) => data != null && data.learning_in_progress !== true, {\n intervalMs: LEARNING_POLL_INTERVAL_MS,\n timeoutMs: LEARNING_POLL_TIMEOUT_MS,\n continueOnError: true,\n signal: controller.signal,\n onTick: (data) => {\n updateLatestLearnRepositoryStatus(data);\n renderUI(data);\n },\n })\n .then(() => {\n activePolls.delete(GLOBAL_LEARNING_POLL_KEY);\n showStatusMessage('Learning analysis complete. Review any generated clarifications below.', 'success');\n })\n .catch((error) => {\n if (error && error.name === 'AbortError') return;\n activePolls.delete(GLOBAL_LEARNING_POLL_KEY);\n if (error instanceof PollTimeoutError) {\n showStatusMessage(\n 'Learning is taking longer than expected. Please refresh the page to see the latest status.',\n 'error'\n );\n } else {\n console.error('Global learning poll error:', error);\n }\n });\n}\n\nasync function handleApprove(analysisType) {\n const sectionId = getSectionId(analysisType);\n\n const confirmText = 'Are you sure you want to approve these clarifications? (You will still be able to edit this later in the configuration panel if needed)';\n\n const confirmed = await showConfirmationModal('Confirm Approval', confirmText);\n\n if (!confirmed) {\n return;\n }\n\n const button = document.getElementById(`lr-${sectionId}-approve`);\n const textarea = document.getElementById(`lr-${sectionId}-textarea`);\n const originalText = button ? button.textContent : null;\n\n // Get the edited clarifications text from the textarea\n const editedClarifications = textarea ? textarea.value : '';\n\n try {\n // Disable button and show loading state\n if (button) {\n button.disabled = true;\n button.textContent = 'Processing...';\n }\n\n const result = await apiCall('/optimize/learn-repository/approve-instructions', 'POST', {\n repo_name: currentRepoName,\n analysis_type: analysisType,\n clarifications: editedClarifications,\n });\n\n if (!result) {\n // apiCall returns null only on the centralized 401 redirect (the\n // 'Session Expired' modal is already shown); non-401 failures throw\n // and are surfaced by the catch below.\n if (button) {\n button.disabled = false;\n button.textContent = originalText;\n }\n return;\n }\n\n // Transition to running state — the approve endpoint kicks off an\n // async instruction generation; start polling to catch the terminal state.\n const currentLiveInstructions = (latestLearnRepositoryStatus && latestLearnRepositoryStatus[analysisType] && latestLearnRepositoryStatus[analysisType].instructions) || '';\n renderSectionRunning(analysisType, { instructions: editedClarifications });\n pollReanalyzeStatus(analysisType, { mode: 'approve', priorInstructions: currentLiveInstructions });\n showModal('Success', 'Instructions approved. The AI is generating updated instructions — this section will refresh automatically when ready.');\n } catch (error) {\n console.error('Error approving instructions:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to approve instructions. Please try again.'));\n if (button) {\n button.disabled = false;\n button.textContent = originalText;\n }\n }\n}\n\nasync function handleSaveInstructions(analysisType) {\n const sectionId = getSectionId(analysisType);\n const saveBtn = document.getElementById(`lr-${sectionId}-save`);\n const textarea = document.getElementById(`lr-${sectionId}-textarea`);\n const originalText = saveBtn ? saveBtn.textContent : null;\n\n if (!textarea?.value.trim()) return;\n\n try {\n if (saveBtn) {\n saveBtn.disabled = true;\n saveBtn.textContent = 'Saving...';\n }\n\n const result = await apiCall('/optimize/learn-repository/save-instructions', 'POST', {\n repo_name: currentRepoName,\n analysis_type: analysisType,\n instructions_text: textarea ? textarea.value : ''\n });\n\n if (result) {\n showModal('Success', 'Instructions saved successfully.');\n }\n } catch (error) {\n console.error('Error saving instructions:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to save instructions. Please try again.'));\n } finally {\n if (saveBtn) {\n saveBtn.disabled = false;\n saveBtn.textContent = originalText;\n }\n }\n}\n\nasync function handleReanalyze(analysisType) {\n const sectionId = getSectionId(analysisType);\n const modeRadio = document.querySelector(`input[name=\"lr-${sectionId}-reanalyze-mode\"]:checked`);\n const mode = modeRadio ? modeRadio.value : 'auto_complete';\n const textarea = document.getElementById(`lr-${sectionId}-textarea`);\n const priorInstructions = textarea ? textarea.value : '';\n const confirmBtn = document.querySelector(\n `.lr-reanalyze-confirm-btn[data-analysis-type=\"${analysisType}\"]`\n );\n const originalConfirmText = confirmBtn ? confirmBtn.textContent : null;\n\n try {\n // Disable the confirm button so a rapid double-click cannot fire two\n // concurrent POSTs (the second would 409 on the per-type reanalyze lock).\n if (confirmBtn) {\n confirmBtn.disabled = true;\n confirmBtn.textContent = 'Confirming...';\n }\n\n const result = await apiCall('/optimize/learn-repository/reanalyze', 'POST', {\n repo_name: currentRepoName,\n analysis_type: analysisType,\n mode: mode,\n });\n\n if (result) {\n // Declaratively switch the section into its running state (hides the\n // save/reanalyze controls, shows the in-progress card, disables the\n // textarea while keeping the prior instructions visible).\n renderSectionRunning(analysisType, { instructions: priorInstructions });\n\n showStatusMessage('Reanalyzing... This may take several minutes.', 'info');\n\n // Start polling\n pollReanalyzeStatus(analysisType, { mode, priorInstructions });\n }\n } catch (error) {\n console.error('Error starting reanalyze:', error);\n // apiCall now rethrows non-401 failures (e.g. a 409 on the per-type\n // reanalyze lock), so surface the message here rather than relying on\n // apiCall to show its own modal.\n showModal('Error', getApiErrorMessage(error, 'Failed to start reanalysis. Please try again.'));\n } finally {\n if (confirmBtn) {\n confirmBtn.disabled = false;\n confirmBtn.textContent = originalConfirmText;\n }\n }\n}\n\n/**\n * Poll a single section's reanalyze/initial-analyze run to completion via the\n * shared pollUntil helper, then render the matching terminal state.\n * @param {string} analysisType\n * @param {Object} [options]\n * @param {string} [options.mode] - 'auto_complete' or 'review'.\n * @param {string} [options.priorInstructions] - Instructions before the run.\n * @param {boolean} [options.isInitialRun] - True for a per-section initial review\n * run; completion is \"no longer reanalyzing\" and re-renders from the payload.\n */\nfunction pollReanalyzeStatus(analysisType, { mode, priorInstructions = '', isInitialRun = false } = {}) {\n const existing = activePolls.get(analysisType);\n if (existing) existing.abort();\n const controller = new AbortController();\n activePolls.set(analysisType, controller);\n const statusUrl = `/optimize/learn-repository/${encodePathSegment(currentRepoName)}/status`;\n\n let sawReanalyzing = false;\n\n const isDone = (data) => {\n if (!data) return false;\n const typeData = data[analysisType];\n if (!typeData) return false;\n if (isInitialRun) {\n return typeData.reanalyzing !== true;\n }\n if (mode === 'approve') {\n if (typeData.reanalyzing === true) {\n sawReanalyzing = true;\n return false;\n }\n // Once we've observed the lock held, trust its release as the\n // terminal signal — covers the LLM-error path where instructions\n // stay unchanged but locks are released in finally.\n if (sawReanalyzing) return true;\n // Tick-1 defense: require content change before the lock is visible.\n return !!typeData.instructions && typeData.instructions !== priorInstructions;\n }\n if (mode === 'review') {\n if (typeData.draft_instructions) return true;\n return typeData.reanalyzing !== true;\n }\n // auto_complete\n const instructionsChanged = typeData.instructions && typeData.instructions !== priorInstructions;\n const freshCompletion = typeData.conclusions && typeData.instructions && !priorInstructions;\n if (instructionsChanged || freshCompletion) return true;\n return typeData.reanalyzing !== true;\n };\n\n pollUntil(statusUrl, isDone, {\n intervalMs: LEARNING_POLL_INTERVAL_MS,\n timeoutMs: LEARNING_POLL_TIMEOUT_MS,\n continueOnError: true,\n signal: controller.signal,\n onTick: (data) => {\n updateLatestLearnRepositoryStatus(data);\n },\n })\n .then((data) => {\n activePolls.delete(analysisType);\n const typeData = data[analysisType] || {};\n\n if (isInitialRun) {\n // Initial review run complete — re-render the section from the\n // payload so review output appears without a manual refresh.\n renderUI(data);\n showStatusMessage('Analysis complete. Review the generated clarifications below.', 'success');\n } else if (mode === 'approve') {\n renderSectionComplete(analysisType, typeData.instructions);\n if (typeData.instructions && typeData.instructions !== priorInstructions) {\n showStatusMessage('Instructions updated successfully.', 'success');\n } else {\n showStatusMessage('Reanalysis finished but instructions were not updated.', 'error');\n }\n } else if (mode === 'review') {\n if (typeData.draft_instructions) {\n renderSectionDraft(analysisType, typeData.draft_instructions, typeData.diff_summary);\n showStatusMessage('Draft ready for review.', 'success');\n } else {\n renderSectionComplete(analysisType, typeData.instructions || priorInstructions);\n showStatusMessage('Reanalysis completed but no draft was generated.', 'info');\n }\n } else {\n // auto_complete\n const instructionsChanged = typeData.instructions && typeData.instructions !== priorInstructions;\n const freshCompletion = typeData.conclusions && typeData.instructions && !priorInstructions;\n if (instructionsChanged || freshCompletion) {\n renderSectionComplete(analysisType, typeData.instructions);\n showStatusMessage('Reanalysis complete.', 'success');\n } else {\n renderSectionComplete(analysisType, typeData.instructions || priorInstructions);\n showStatusMessage('Reanalysis completed but no changes were generated.', 'info');\n }\n }\n })\n .catch((error) => {\n if (error && error.name === 'AbortError') return;\n activePolls.delete(analysisType);\n if (error instanceof PollTimeoutError) {\n const sectionId = getSectionId(analysisType);\n const textarea = document.getElementById(`lr-${sectionId}-textarea`);\n if (textarea) textarea.disabled = false;\n showStatusMessage('Reanalysis is taking longer than expected. Please refresh the page.', 'error');\n } else {\n console.error('Polling error:', error);\n }\n });\n}\n\nasync function handleAcceptDraft(analysisType) {\n const sectionId = getSectionId(analysisType);\n const textarea = document.getElementById(`lr-${sectionId}-textarea`);\n const acceptBtn = document.querySelector(`.lr-accept-draft-btn[data-analysis-type=\"${analysisType}\"]`);\n const originalText = acceptBtn ? acceptBtn.textContent : null;\n\n try {\n if (acceptBtn) {\n acceptBtn.disabled = true;\n acceptBtn.textContent = 'Accepting...';\n }\n\n const result = await apiCall('/optimize/learn-repository/accept-draft', 'POST', {\n repo_name: currentRepoName,\n analysis_type: analysisType,\n instructions_text: textarea ? textarea.value : ''\n });\n\n if (result) {\n showModal('Success', 'Draft accepted. Instructions updated.');\n // Refresh status to redraw UI\n const data = await apiCall(\n `/optimize/learn-repository/${encodePathSegment(currentRepoName)}/status`,\n 'GET'\n );\n if (data) renderUI(data);\n }\n } catch (error) {\n console.error('Error accepting draft:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to accept draft. Please try again.'));\n } finally {\n if (acceptBtn) {\n acceptBtn.disabled = false;\n acceptBtn.textContent = originalText;\n }\n }\n}\n\nasync function handleDiscardDraft(analysisType) {\n const discardBtn = document.querySelector(`.lr-discard-draft-btn[data-analysis-type=\"${analysisType}\"]`);\n const originalText = discardBtn ? discardBtn.textContent : null;\n\n try {\n if (discardBtn) {\n discardBtn.disabled = true;\n discardBtn.textContent = 'Discarding...';\n }\n\n const result = await apiCall('/optimize/learn-repository/discard-draft', 'POST', {\n repo_name: currentRepoName,\n analysis_type: analysisType\n });\n\n if (result) {\n showModal('Success', 'Draft discarded.');\n // Refresh status to redraw UI\n const data = await apiCall(\n `/optimize/learn-repository/${encodePathSegment(currentRepoName)}/status`,\n 'GET'\n );\n if (data) renderUI(data);\n }\n } catch (error) {\n console.error('Error discarding draft:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to discard draft. Please try again.'));\n } finally {\n if (discardBtn) {\n discardBtn.disabled = false;\n discardBtn.textContent = originalText;\n }\n }\n}\n\nasync function handleInitialAnalyze(analysisType) {\n const sectionId = getSectionId(analysisType);\n const modeRadio = document.querySelector(`input[name=\"lr-${sectionId}-initial-mode\"]:checked`);\n // Only the backend-supported modes are sent; there is no frontend-only\n // 'initial_review' string. The initial-review distinction is an internal\n // polling option (isInitialRun) instead.\n const selectedMode = modeRadio ? modeRadio.value : 'review';\n\n try {\n showLoading();\n\n const result = await apiCall('/optimize/learn-repository/start', 'POST', {\n repo_name: currentRepoName,\n analysis_type: analysisType,\n mode: selectedMode,\n });\n\n if (result) {\n const analysisLabel = analysisType.replace(/_/g, ' ');\n const statusMsg = selectedMode === 'auto_complete'\n ? `Analysis started for ${analysisLabel}. This section will update automatically when complete.`\n : `Analysis started for ${analysisLabel}. Check back later to review the generated clarifications.`;\n showStatusMessage(statusMsg, 'success');\n\n const modalMsg = selectedMode === 'auto_complete'\n ? `Analysis is running for ${analysisLabel}. This section will update automatically when complete — no need to do anything else.`\n : `Analysis is running for ${analysisLabel}. Come back later to review the generated clarifications.`;\n showModal('Analysis Started', modalMsg);\n\n // Declaratively switch the section to its running state.\n renderSectionRunning(analysisType, {});\n\n // Poll for completion\n if (selectedMode === 'auto_complete') {\n pollReanalyzeStatus(analysisType, { mode: 'auto_complete', priorInstructions: '' });\n } else {\n // review mode: an initial run completes when analysis stops, then\n // re-renders from the payload (clarification review).\n pollReanalyzeStatus(analysisType, { mode: 'review', priorInstructions: '', isInitialRun: true });\n }\n }\n } catch (error) {\n console.error('Error starting initial analysis:', error);\n showStatusMessage(getApiErrorMessage(error, 'Failed to start analysis. Please try again.'), 'error');\n } finally {\n hideLoading();\n }\n}\n\nfunction showStatusMessage(message, type = 'info') {\n // Auto-hide success messages after 10 seconds, matching prior behavior.\n const options = type === 'success' ? { autoHideMs: 10000 } : {};\n setInlineStatus('learn-repository-status', message, type, options);\n}\n\nfunction showConfirmationModal(title, message) {\n return new Promise((resolve) => {\n let resolved = false;\n showModal(title, message, () => { resolved = true; resolve(true); }, true, () => { if (!resolved) resolve(false); });\n });\n}\n","import { initModal, showModal } from './modal.js';\nimport { logout, handleLogin } from './auth.js';\n\n// ==========================================\n// Initialization\n// ==========================================\n\n/**\n * Initialize the page\n */\nasync function init() {\n // Attach login form listener only on the login page, after DOM is ready\n const loginForm = document.getElementById('login-form');\n\n if (loginForm) {\n loginForm.addEventListener('submit', handleLogin);\n }\n\n // Logout button might exist on other pages (like setup)\n // DOM elements - get elements based on the current page\n const logoutBtn = document.getElementById('logout-btn');\n \n // Set up logout button event listener (works on setup page)\n if (logoutBtn) {\n logoutBtn.addEventListener('click', logout);\n }\n\n initModal();\n\n // Show session expired modal if redirected due to expired session\n const urlParams = new URLSearchParams(window.location.search);\n if (urlParams.get('session_expired') === 'true') {\n showModal('Session Expired', 'Your session has expired. Please log in again.');\n window.history.replaceState({}, '', '/setup/login');\n }\n}\n\n// Initialize the page when DOM is fully loaded\ndocument.addEventListener('DOMContentLoaded', init);","// src/js/modal.js\n\n/**\n * Handles modal dialog interactions.\n */\n\n// DOM Elements for the modal (moved here)\nconst pageModal = document.getElementById('modal');\nconst pageModalTitle = document.getElementById('modal-title');\nconst pageModalBody = document.getElementById('modal-body');\nconst pageModalActions = document.getElementById('modal-actions');\nconst pageModalConfirmBtn = document.getElementById('modal-confirm-btn');\nconst pageModalCancelBtn = document.getElementById('modal-cancel-btn');\nconst pageCloseModalBtn = document.querySelector('.close-modal'); // Assuming there's only one\n\n// Store the current confirm callback\nlet currentConfirmCallback = null;\n// Store the current close callback\nlet currentCloseCallback = null;\n\n/**\n * Shows the modal with a specific title and message.\n * @param {string} title - The title to display in the modal header.\n * @param {string} message - The text content to display in the modal body (HTML is not rendered).\n * @param {Function|null} onConfirm - Optional callback function to execute on confirmation.\n * @param {boolean} showCancel - Whether to show cancel/confirm buttons (default: false).\n */\nexport function showModal(title, message, onConfirm = null, showCancel = false, onClose = null) {\n if (pageModalTitle) pageModalTitle.textContent = title;\n\n // Use textContent to prevent XSS from server-supplied error messages\n if (pageModalBody) pageModalBody.textContent = message;\n\n // Handle confirmation mode\n currentConfirmCallback = onConfirm;\n currentCloseCallback = onClose;\n \n if (pageModalActions) {\n if (onConfirm && showCancel) {\n pageModalActions.classList.remove('hidden');\n } else {\n pageModalActions.classList.add('hidden');\n }\n }\n \n if (pageModal) {\n pageModal.style.display = 'block';\n } else {\n console.error(\"Modal element not found.\");\n }\n}\n\n/**\n * Hides the modal dialog.\n */\nexport function hideModal() {\n if (pageModal) {\n pageModal.style.display = 'none';\n } else {\n console.warn(\"Modal element not found for hiding.\");\n }\n // Clear callbacks, capturing close callback before clearing\n currentConfirmCallback = null;\n const closeCallback = currentCloseCallback;\n currentCloseCallback = null;\n if (closeCallback) closeCallback();\n}\n\n/**\n * Handles confirm button click.\n */\nfunction handleConfirm() {\n if (currentConfirmCallback) {\n currentConfirmCallback();\n }\n hideModal();\n}\n\n/**\n * Initializes modal event listeners.\n */\nexport function initModal() {\n if (!pageModal || !pageCloseModalBtn) {\n console.warn(\"Modal elements not found for initialization.\");\n return;\n }\n \n // Add event listener for the close button\n pageCloseModalBtn.addEventListener('click', hideModal);\n \n // Add event listener for cancel button\n if (pageModalCancelBtn) {\n pageModalCancelBtn.addEventListener('click', hideModal);\n }\n \n // Add event listener for confirm button\n if (pageModalConfirmBtn) {\n pageModalConfirmBtn.addEventListener('click', handleConfirm);\n }\n\n // Add event listener to close modal when clicking outside of it\n window.addEventListener('click', (event) => {\n if (event.target === pageModal) {\n hideModal();\n }\n });\n}\n","// src/js/monitoring.js\n/**\n * Monitoring page module.\n *\n * Replaces the template's inline drawer-toggle implementation with the shared\n * drawer helper (BAPI-301). Webpack bundles every file under src/js/** into the\n * single main.min.js, so adding this module is enough to ship it — there is no\n * central entry file to register it in.\n */\nimport { initDrawers } from './utils.js';\n\nfunction init() {\n // Guard: only run on the Monitoring page (its root container is present).\n if (!document.getElementById('monitoring-container')) {\n return;\n }\n initDrawers({ root: document, drawerSelector: '#monitoring-container .drawer' });\n}\n\ndocument.addEventListener('DOMContentLoaded', init);\n","/**\n * Optimize page entrypoint (BAPI-307).\n *\n * This module owns the single `DOMContentLoaded` entrypoint for the Optimize\n * page. It reads the shared Optimize context once and passes that one object to\n * each feature initializer (Estimator here, plus Learn Repository and Code\n * Analysis from their modules), so identity is resolved in exactly one place.\n * It also hosts the Estimator feature, routed through the shared plumbing\n * (apiCall / context / status adapter / feedback) while preserving its\n * one-shot (non-polling) refresh behavior.\n */\n\nimport { showModal } from './modal.js';\nimport { initDrawers, apiCall, getApiErrorMessage } from './utils.js';\nimport { getOptimizeContext, encodePathSegment } from './optimize/context.js';\nimport { adaptEstimatorStatus } from './optimize/status_adapters.js';\nimport { withButtonPendingState } from './optimize/feedback.js';\nimport { initLearnRepository } from './learn_repository.js';\nimport { initializeCodeAnalysis } from './optimize_analysis.js';\n\n// Estimator sub-states, switched via the global `.hidden` class.\nconst ESTIMATOR_SECTIONS = ['loading', 'optimize', 'running', 'review'];\n\n// Canonical project id, sourced once from the shared Optimize context.\nlet currentProjectId = null;\n\n// ====================================================================\n// PAGE ENTRYPOINT\n// ====================================================================\n\n/**\n * Single Optimize page initializer. Reads the shared context once and fans it out\n * to every feature.\n */\nexport function init() {\n // Page-gate the entire module. The site-wide Webpack bundle loads optimize.js\n // on every page, so bail out unless we are on an Optimize project route with\n // the server-rendered container present — this keeps drawer-init and the\n // features from running anywhere else.\n if (!window.location.pathname.startsWith('/optimize')) {\n return;\n }\n const optimizeContainer = document.getElementById('optimize-container');\n if (!optimizeContainer) {\n return;\n }\n\n const context = getOptimizeContext();\n\n // Initialize drawers via the shared helper with the scoped selector used by\n // every page module (#<container> .drawer), matching the BAPI-301 convention.\n // Optimize keeps its UX of all drawers open by default (defaultExpanded), and\n // the first click correctly collapses an open drawer. No `.active` class is used.\n initDrawers({\n root: document,\n drawerSelector: '#optimize-container .drawer',\n defaultExpanded: true,\n });\n\n if (context.projectId) {\n initializeEstimator(context);\n }\n if (context.repoName) {\n initLearnRepository(context);\n }\n if (context.repoName) {\n initializeCodeAnalysis(context);\n }\n}\n\n// Initialize on DOM ready. init() is page-gated to the Optimize route, so this is\n// a no-op on every other page even though the bundle is loaded site-wide.\ndocument.addEventListener('DOMContentLoaded', init);\n\n// ====================================================================\n// ESTIMATOR FUNCTIONALITY\n// ====================================================================\n\n/**\n * Initialize the Estimator from the shared Optimize context.\n * @param {{projectId: (string|null)}} [context]\n */\nexport async function initializeEstimator(context = getOptimizeContext()) {\n currentProjectId = context && context.projectId;\n if (!currentProjectId) {\n return;\n }\n\n await checkEstimatorStatus();\n setupEstimatorEventListeners();\n}\n\nfunction setupEstimatorEventListeners() {\n // Optimize Estimator button\n document.getElementById('optimize-estimator-btn')?.addEventListener('click', startEstimatorOptimization);\n\n // Refresh Status button\n document.getElementById('refresh-estimator-btn')?.addEventListener('click', checkEstimatorStatus);\n\n // Save Ranges button\n document.getElementById('save-ranges-btn')?.addEventListener('click', saveEstimatorRanges);\n\n // Cancel Review button\n document.getElementById('cancel-review-btn')?.addEventListener('click', cancelReview);\n}\n\nexport async function checkEstimatorStatus() {\n showEstimatorSection('loading');\n\n let data;\n try {\n data = await apiCall(\n `/optimize/${encodePathSegment(currentProjectId)}/estimator/status`,\n 'GET',\n );\n } catch (error) {\n console.error('Error checking estimator status:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to check estimator status.'));\n showEstimatorSection('optimize');\n return;\n }\n\n if (data === null) return;\n\n const status = adaptEstimatorStatus(data);\n if (status.state === 'review') {\n showRangeReview(status.meta.rangeJson);\n } else if (status.state === 'running') {\n showEstimatorRunning(status.meta.message);\n } else {\n showEstimatorSection('optimize');\n }\n}\n\nexport async function startEstimatorOptimization() {\n showEstimatorSection('loading');\n\n let data;\n try {\n data = await apiCall(\n `/optimize/${encodePathSegment(currentProjectId)}/estimator/create-range`,\n 'POST',\n );\n } catch (error) {\n console.error('Error starting estimator optimization:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to start optimization.'));\n showEstimatorSection('optimize');\n return;\n }\n\n // apiCall returns null on the centralized 401 redirect — the session-expired\n // modal is already shown; bail out silently.\n if (data === null) return;\n\n if (data.accepted) {\n showEstimatorRunning('Estimator optimization started. This process takes about 30 minutes. Please check back later.');\n\n // One-shot refresh after 5 minutes. Estimator intentionally keeps its\n // delayed single refresh rather than converting to live polling.\n setTimeout(() => {\n checkEstimatorStatus();\n }, 5 * 60 * 1000);\n } else if (data.need_estimate_review) {\n // Already running, check status\n await checkEstimatorStatus();\n }\n}\n\nexport async function saveEstimatorRanges() {\n const rangeTextarea = document.getElementById('range-json');\n const rangeText = rangeTextarea ? rangeTextarea.value : '';\n\n if (!rangeText.trim()) {\n showModal('Error', 'Please provide range data to save.');\n return;\n }\n\n // Validate JSON\n let rangeData;\n try {\n rangeData = JSON.parse(rangeText);\n } catch (e) {\n showModal('Error', 'Invalid JSON format. Please check your range data.');\n return;\n }\n\n const saveBtn = document.getElementById('save-ranges-btn');\n try {\n const data = await withButtonPendingState(saveBtn, 'Saving...', () =>\n apiCall(\n `/optimize/${encodePathSegment(currentProjectId)}/estimator/save`,\n 'POST',\n { range: rangeData },\n )\n );\n\n // apiCall returns null on the centralized 401 redirect (Session\n // Expired modal already shown) — bail without a second modal.\n if (data === null) return;\n\n if (data.saved) {\n showModal('Success', 'Estimate ranges saved successfully!');\n showEstimatorSection('optimize');\n } else {\n showModal('Warning', 'Ranges could not be saved. Please try again.');\n }\n } catch (error) {\n console.error('Error saving estimator ranges:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to save ranges.'));\n }\n}\n\nfunction cancelReview() {\n showEstimatorSection('optimize');\n}\n\nfunction showEstimatorSection(section) {\n // Hide every estimator section, then reveal only the requested one,\n // using the global `.hidden` class instead of inline display mutation.\n ESTIMATOR_SECTIONS.forEach((name) => {\n document.getElementById(`estimator-${name}`)?.classList.add('hidden');\n });\n document.getElementById(`estimator-${section}`)?.classList.remove('hidden');\n}\n\nfunction showEstimatorRunning(message) {\n const messageElement = document.getElementById('estimator-message');\n if (messageElement && message) {\n messageElement.textContent = message;\n }\n showEstimatorSection('running');\n}\n\nfunction showRangeReview(rangeData) {\n const textarea = document.getElementById('range-json');\n if (textarea) {\n textarea.value = JSON.stringify(rangeData, null, 2);\n }\n showEstimatorSection('review');\n}\n","/**\n * Shared Optimize page identity source (BAPI-307).\n *\n * The Optimize page exposes its canonical identity on the server-rendered\n * `#optimize-container` element via `data-repo-name` and `data-project-id`\n * (see templates/optimize.html). This module is the single place that reads\n * them, so every Optimize feature resolves identity the same way — fixing the\n * BAPI-301 regression where one consumer still read a removed `window.repoName`\n * global and its \"Run Analysis\" buttons went dead.\n *\n * Identity is read ONLY from `#optimize-container`. Legacy sources\n * (`window.repoName`, `window.projectData`, URL query params, hidden inputs,\n * `document.body[data-repo-name]`) are intentionally not consulted.\n */\n\n/**\n * @typedef {Object} OptimizeContext\n * @property {string|null} repoName - Canonical repo name, or null when absent.\n * @property {string|null} projectId - Canonical project id, or null when absent.\n * @property {Element|null} container - The `#optimize-container` element, or null.\n */\n\n/**\n * Normalize a raw dataset value to a non-empty string or null. Missing or empty\n * attributes become null rather than the empty string so callers can guard with\n * a simple truthiness check.\n * @param {string|undefined|null} value\n * @returns {string|null}\n */\nfunction normalizeIdentityValue(value) {\n if (value === undefined || value === null) return null;\n return value === '' ? null : value;\n}\n\n/**\n * Read the canonical Optimize identity from the `#optimize-container` element.\n *\n * Returns a plain object; never throws when the container or attributes are\n * absent (e.g. during DOM-free unit execution), so callers can fail safe.\n *\n * @param {Document|Element} [root=document] - Root to resolve the container from.\n * @returns {OptimizeContext}\n */\nexport function getOptimizeContext(root = (typeof document !== 'undefined' ? document : null)) {\n const container = root && typeof root.getElementById === 'function'\n ? root.getElementById('optimize-container')\n : null;\n const dataset = (container && container.dataset) || {};\n return {\n repoName: normalizeIdentityValue(dataset.repoName),\n projectId: normalizeIdentityValue(dataset.projectId),\n container: container || null,\n };\n}\n\n/**\n * Encode a value for safe inclusion as a single URL path segment.\n *\n * Always stringifies first so numeric ids and other non-strings are handled,\n * then delegates to `encodeURIComponent` (so `/` becomes `%2F` and spaces\n * `%20`). Use this for repo names and project ids interpolated into API paths,\n * even when current identifiers are simple slugs.\n * @param {*} value\n * @returns {string}\n */\nexport function encodePathSegment(value) {\n return encodeURIComponent(String(value));\n}\n","/**\n * Shared Optimize inline feedback helpers (BAPI-307).\n *\n * One consistent way to render inline progress/success/error feedback and to\n * drive a button's pending state across the three Optimize features. Visibility\n * is controlled only by the global `.hidden` class — never inline\n * `style.display` — matching the project-wide convention.\n */\n\n/** Status type class applied alongside `status-message`. */\nconst STATUS_BASE_CLASS = 'status-message';\n\n/** Repeated Optimize feedback strings, centralized so call sites stay in sync. */\nexport const FEEDBACK_STRINGS = Object.freeze({\n SAVED: 'Saved successfully!',\n SAVING: 'Saving...',\n});\n\n/**\n * Resolve a target that may be an element or an element-id string.\n * @param {Element|string|null} target\n * @returns {Element|null}\n */\nfunction resolveElement(target) {\n if (!target) return null;\n if (typeof target === 'string') {\n return (typeof document !== 'undefined' && typeof document.getElementById === 'function')\n ? document.getElementById(target)\n : null;\n }\n return target;\n}\n\n/**\n * Render an inline status message: set text, apply `status-message <type>`\n * classes, and reveal the element by removing `.hidden`.\n *\n * @param {Element|string} target - Element or element id.\n * @param {string} message - Text content (set via textContent; never HTML).\n * @param {string} [type='info'] - Status type class (info/success/error/processing/warning).\n * @param {Object} [options]\n * @param {number} [options.autoHideMs] - When set, hide via `.hidden` after the delay.\n */\nexport function setInlineStatus(target, message, type = 'info', options = {}) {\n const el = resolveElement(target);\n if (!el) return;\n el.textContent = message;\n el.className = `${STATUS_BASE_CLASS} ${type}`.trim();\n el.classList.remove('hidden');\n\n const autoHideMs = options && options.autoHideMs;\n if (typeof autoHideMs === 'number' && autoHideMs > 0) {\n setTimeout(() => {\n el.classList.add('hidden');\n }, autoHideMs);\n }\n}\n\n/**\n * Clear an inline status: empty its text and hide it via `.hidden`. Safe when\n * the target is missing.\n * @param {Element|string} target - Element or element id.\n */\nexport function clearInlineStatus(target) {\n const el = resolveElement(target);\n if (!el) return;\n el.textContent = '';\n el.classList.add('hidden');\n}\n\n/**\n * Run an async action while a button shows a disabled \"pending\" state, restoring\n * the original text/disabled state in `finally` (so it recovers on both success\n * and failure). Returns the callback's resolved value; rethrows its error.\n *\n * @param {HTMLButtonElement|null} button - The triggering button (may be null).\n * @param {string} pendingText - Text shown while pending.\n * @param {function():Promise<*>} callback - The async work to await.\n * @returns {Promise<*>} The callback's resolved value.\n */\nexport async function withButtonPendingState(button, pendingText, callback) {\n const originalText = button ? button.textContent : null;\n const originalDisabled = button ? button.disabled : null;\n if (button) {\n button.disabled = true;\n if (typeof pendingText === 'string') {\n button.textContent = pendingText;\n }\n }\n try {\n return await callback();\n } finally {\n if (button) {\n button.disabled = originalDisabled;\n if (typeof pendingText === 'string') {\n button.textContent = originalText;\n }\n }\n }\n}\n","/**\n * Learn Repository per-section renderer (BAPI-307, Phase 2).\n *\n * Centralizes the per-section show/hide decisions that previously lived in the\n * five imperative `showSection*()` functions. Each section gets one declarative\n * `lr-state-*` class plus a `.hidden`-driven visibility map. Visibility is\n * controlled ONLY by toggling the global `.hidden` class — never inline\n * `style.display` — and dynamic diff-summary text is rendered with text nodes\n * (never `innerHTML` of user-supplied data) so it stays XSS-safe.\n */\n\nimport { adaptLearnRepositorySectionStatus } from './status_adapters.js';\n\n/** State classes managed on a `.learn-section` element. */\nconst LR_STATE_CLASSES = [\n 'lr-state-idle',\n 'lr-state-running',\n 'lr-state-review',\n 'lr-state-draft',\n 'lr-state-complete',\n];\n\n/** The section sub-elements whose visibility is toggled by the state map. */\nconst TOGGLEABLE_KEYS = [\n 'initialContainer',\n 'inProgressContainer',\n 'textarea',\n 'approveBtn',\n 'completeStatus',\n 'saveBtn',\n 'reanalyzeContainer',\n 'draftReview',\n];\n\n/**\n * Convert a backend analysis type (`frontend_correctness`) to its markup section\n * id fragment (`frontend-correctness`).\n * @param {string} analysisType\n * @returns {string}\n */\nexport function getSectionId(analysisType) {\n return analysisType.replace(/_/g, '-');\n}\n\n/**\n * Collect all DOM references for one Learn Repository section. Every property is\n * null-safe: a missing section (or missing root) yields an object of nulls\n * rather than throwing.\n * @param {string} analysisType\n * @param {Document|Element} [root=document]\n * @returns {Object}\n */\nexport function getLearnSectionElements(analysisType, root = (typeof document !== 'undefined' ? document : null)) {\n const sectionId = getSectionId(analysisType);\n const byId = (id) => (root && typeof root.getElementById === 'function') ? root.getElementById(id) : null;\n const section = byId(`lr-${sectionId}`);\n const q = (sel) => (section && typeof section.querySelector === 'function') ? section.querySelector(sel) : null;\n const reanalyzeContainer = byId(`lr-${sectionId}-reanalyze-container`);\n const qIn = (container, sel) => (container && typeof container.querySelector === 'function')\n ? container.querySelector(sel)\n : null;\n return {\n sectionId,\n section,\n initialContainer: q('.lr-initial-analyze-container'),\n inProgressContainer: q('.lr-in-progress-container'),\n textarea: byId(`lr-${sectionId}-textarea`),\n approveBtn: byId(`lr-${sectionId}-approve`),\n completeStatus: byId(`lr-${sectionId}-complete`),\n saveBtn: byId(`lr-${sectionId}-save`),\n reanalyzeContainer,\n draftReview: byId(`lr-${sectionId}-draft-review`),\n diffDisplay: byId(`lr-${sectionId}-diff-summary`),\n description: q('.section-description'),\n reanalyzeLink: qIn(reanalyzeContainer, '.lr-reanalyze-link'),\n reanalyzeOptions: qIn(reanalyzeContainer, '.lr-reanalyze-options'),\n };\n}\n\n/**\n * Remove any prior `lr-state-*` class and apply the current one. Unrelated\n * classes are preserved.\n * @param {Element|null} section\n * @param {string} state\n */\nexport function setLearnSectionStateClass(section, state) {\n if (!section || !section.classList) return;\n LR_STATE_CLASSES.forEach((cls) => section.classList.remove(cls));\n if (state) {\n section.classList.add(`lr-state-${state}`);\n }\n}\n\n/**\n * Toggle `.hidden` on each element of `elements` according to `visibleKeys`.\n * Elements whose key is in `visibleKeys` have `.hidden` removed; all others get\n * it added. Only `.hidden` is touched — no inline style changes.\n * @param {Object<string, (Element|null)>} elements\n * @param {Array<string>} visibleKeys\n */\nexport function applyVisibilityMap(elements, visibleKeys) {\n const visible = new Set(visibleKeys || []);\n Object.keys(elements || {}).forEach((key) => {\n const el = elements[key];\n if (!el || !el.classList) return;\n if (visible.has(key)) {\n el.classList.remove('hidden');\n } else {\n el.classList.add('hidden');\n }\n });\n}\n\n/**\n * Render a diff summary safely: clear the node, then append text nodes and\n * `<br>` elements so newlines are preserved and any HTML in the summary is\n * rendered as inert text (never executed).\n * @param {Element|null} diffDisplay\n * @param {string} diffSummary\n */\nexport function renderDiffSummarySafely(diffDisplay, diffSummary) {\n if (!diffDisplay) return;\n diffDisplay.innerHTML = '';\n const text = diffSummary || '';\n text.split('\\n').forEach((line, i, arr) => {\n diffDisplay.appendChild(document.createTextNode(line));\n if (i < arr.length - 1) {\n diffDisplay.appendChild(document.createElement('br'));\n }\n });\n}\n\n/** Build the toggleable subset of section elements for the visibility map. */\nfunction toggleableElements(els) {\n const out = {};\n TOGGLEABLE_KEYS.forEach((key) => { out[key] = els[key]; });\n return out;\n}\n\nfunction showSection(els) {\n if (els.section && els.section.classList) {\n els.section.classList.remove('hidden');\n }\n}\n\nfunction setDescription(els, text) {\n if (els.description) {\n els.description.textContent = text;\n }\n}\n\n/**\n * Render a section in its idle (no analysis yet) state: only the per-section\n * \"Run Analysis\" container is visible.\n */\nexport function renderSectionIdle(analysisType, root) {\n const els = getLearnSectionElements(analysisType, root);\n if (!els.section) return els;\n showSection(els);\n setLearnSectionStateClass(els.section, 'idle');\n applyVisibilityMap(toggleableElements(els), ['initialContainer']);\n return els;\n}\n\n/**\n * Render a section in its running state: the in-progress card is visible; if the\n * section already has data, the textarea stays visible but disabled.\n */\nexport function renderSectionRunning(analysisType, sectionData = {}, root) {\n const els = getLearnSectionElements(analysisType, root);\n if (!els.section) return els;\n showSection(els);\n setLearnSectionStateClass(els.section, 'running');\n\n const existing = sectionData && (\n sectionData.draft_instructions\n || sectionData.instructions\n || sectionData.clarifications\n );\n const visible = ['inProgressContainer'];\n if (existing) visible.push('textarea');\n applyVisibilityMap(toggleableElements(els), visible);\n\n if (els.textarea && existing) {\n els.textarea.value = sectionData.draft_instructions || sectionData.instructions || sectionData.clarifications || '';\n els.textarea.disabled = true;\n }\n setDescription(els, 'Analysis is running for this section. This card will update when complete.');\n return els;\n}\n\n/**\n * Render a section in its clarification-review state: an editable textarea\n * holding the clarifications plus the Approve action.\n */\nexport function renderSectionReview(analysisType, clarificationText = '', root) {\n const els = getLearnSectionElements(analysisType, root);\n if (!els.section) return els;\n showSection(els);\n setLearnSectionStateClass(els.section, 'review');\n applyVisibilityMap(toggleableElements(els), ['textarea', 'approveBtn']);\n if (els.textarea) {\n els.textarea.value = clarificationText || '';\n els.textarea.disabled = false;\n }\n setDescription(els, \"Review the AI's questions about how it should write code to the codebase.\");\n return els;\n}\n\n/**\n * Render a section in its draft-review state: an editable draft textarea, the\n * safely-rendered diff summary, and Accept/Discard actions.\n */\nexport function renderSectionDraft(analysisType, draftInstructions = '', diffSummary = '', root) {\n const els = getLearnSectionElements(analysisType, root);\n if (!els.section) return els;\n showSection(els);\n setLearnSectionStateClass(els.section, 'draft');\n applyVisibilityMap(toggleableElements(els), ['textarea', 'draftReview']);\n if (els.textarea) {\n els.textarea.value = draftInstructions || '';\n els.textarea.disabled = false;\n }\n renderDiffSummarySafely(els.diffDisplay, diffSummary);\n setDescription(els, 'Review the draft instructions below. Edit if needed, then Accept or Discard.');\n return els;\n}\n\n/**\n * Render a section in its complete state: an editable instructions textarea, the\n * Save action, and the Reanalyze control (with its options collapsed).\n */\nexport function renderSectionComplete(analysisType, instructions = '', root) {\n const els = getLearnSectionElements(analysisType, root);\n if (!els.section) return els;\n showSection(els);\n setLearnSectionStateClass(els.section, 'complete');\n applyVisibilityMap(toggleableElements(els), ['textarea', 'saveBtn', 'reanalyzeContainer']);\n if (els.textarea) {\n els.textarea.value = instructions || '';\n els.textarea.disabled = false;\n }\n // Inside the reanalyze container: collapse the options, expose the link/button.\n if (els.reanalyzeOptions && els.reanalyzeOptions.classList) {\n els.reanalyzeOptions.classList.add('hidden');\n }\n if (els.reanalyzeLink && els.reanalyzeLink.classList) {\n els.reanalyzeLink.classList.remove('hidden');\n }\n setDescription(els, 'Edit the instructions below and save, or reanalyze to regenerate them.');\n return els;\n}\n\n/**\n * Data-driven entry point: normalize a section payload via the pure adapter and\n * render the matching state. Used by the Learn Repository module's renderUI.\n * @param {string} analysisType\n * @param {Object} [sectionData={}]\n * @param {Document|Element} [root]\n * @returns {Object} The resolved section elements.\n */\nexport function renderLearnRepositorySection(analysisType, sectionData = {}, root) {\n const { state } = adaptLearnRepositorySectionStatus(sectionData);\n switch (state) {\n case 'running':\n return renderSectionRunning(analysisType, sectionData, root);\n case 'draft':\n return renderSectionDraft(analysisType, sectionData.draft_instructions, sectionData.diff_summary, root);\n case 'review':\n return renderSectionReview(analysisType, sectionData.clarifications || '', root);\n case 'complete':\n return renderSectionComplete(analysisType, sectionData.instructions, root);\n case 'idle':\n default:\n return renderSectionIdle(analysisType, root);\n }\n}\n","/**\n * Shared Optimize polling helper (BAPI-307).\n *\n * Replaces the three features' bespoke poll loops (recursive `setTimeout`,\n * `setInterval` + timeout maps) with one abortable helper. Each feature keeps\n * its own completion predicate and timing while sharing cancellation, timer\n * cleanup, and the `apiCall()`-backed fetch path.\n */\n\nimport { apiCall } from '../utils.js';\n\n/** Default poll interval when a caller does not specify one. */\nconst DEFAULT_INTERVAL_MS = 5000;\n\n/**\n * Rejection error used when an opt-in `timeoutMs` elapses before `isDone`\n * returns true. Callers detect it by `name === 'PollTimeoutError'`.\n */\nexport class PollTimeoutError extends Error {\n constructor(message = 'Polling timed out') {\n super(message);\n this.name = 'PollTimeoutError';\n }\n}\n\n/**\n * Poll `statusUrl` until `isDone(data)` returns true.\n *\n * @param {string} statusUrl - Status endpoint polled by the default fetcher.\n * @param {function(*):boolean} isDone - Predicate; resolve when it returns true.\n * @param {Object} [options]\n * @param {number} [options.intervalMs=5000] - Delay between polls.\n * @param {number|null} [options.timeoutMs=null] - Opt-in overall timeout; null\n * disables it (Code Analysis relies on unbounded polling in Phase 1).\n * @param {function(*):void} [options.onTick] - Called with each successful payload.\n * @param {function(Error):void} [options.onError] - Called on a fetch error when\n * `continueOnError` is true.\n * @param {boolean} [options.continueOnError=false] - Keep polling after a failed\n * fetch instead of rejecting.\n * @param {AbortSignal} [options.signal] - Abort to stop polling and reject.\n * @param {function():Promise<*>} [options.fetcher] - Override the fetch; defaults\n * to `apiCall(statusUrl, 'GET', null, { signal })`.\n * @param {boolean} [options.immediate=true] - Fetch immediately; when false the\n * first fetch waits one interval.\n * @returns {Promise<*>} Resolves with the final payload, rejects with\n * PollTimeoutError on timeout, the abort reason on abort, or the fetch error.\n */\nexport function pollUntil(statusUrl, isDone, options = {}) {\n const {\n intervalMs = DEFAULT_INTERVAL_MS,\n timeoutMs = null,\n onTick = null,\n onError = null,\n continueOnError = false,\n signal = null,\n fetcher = null,\n immediate = true,\n } = options || {};\n\n const doFetch = typeof fetcher === 'function'\n ? fetcher\n // apiCall throws on any non-401 failure (BAPI-306), which pollUntil\n // surfaces via its own reject path; pass the abort signal through.\n : () => apiCall(statusUrl, 'GET', null, { signal });\n\n return new Promise((resolve, reject) => {\n let intervalTimer = null;\n let timeoutTimer = null;\n let settled = false;\n\n const cleanup = () => {\n if (intervalTimer !== null) {\n clearTimeout(intervalTimer);\n intervalTimer = null;\n }\n if (timeoutTimer !== null) {\n clearTimeout(timeoutTimer);\n timeoutTimer = null;\n }\n if (signal && typeof signal.removeEventListener === 'function') {\n signal.removeEventListener('abort', onAbort);\n }\n };\n\n const finishResolve = (value) => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(value);\n };\n\n const finishReject = (error) => {\n if (settled) return;\n settled = true;\n cleanup();\n reject(error);\n };\n\n function onAbort() {\n const reason = (signal && signal.reason)\n || new DOMException('The poll was aborted.', 'AbortError');\n finishReject(reason);\n }\n\n const schedule = () => {\n if (settled) return;\n intervalTimer = setTimeout(runTick, intervalMs);\n };\n\n async function runTick() {\n if (settled) return;\n let data;\n try {\n data = await doFetch();\n } catch (error) {\n if (settled) return;\n if (continueOnError) {\n if (typeof onError === 'function') {\n try { onError(error); } catch (_) { /* never let a callback break the loop */ }\n }\n schedule();\n return;\n }\n finishReject(error);\n return;\n }\n if (settled) return;\n if (typeof onTick === 'function') {\n try { onTick(data); } catch (_) { /* tick callbacks must not break the loop */ }\n }\n if (settled) return;\n let done = false;\n try {\n done = isDone(data);\n } catch (error) {\n finishReject(error);\n return;\n }\n if (done) {\n finishResolve(data);\n } else {\n schedule();\n }\n }\n\n if (signal) {\n if (signal.aborted) {\n onAbort();\n return;\n }\n signal.addEventListener('abort', onAbort);\n }\n\n if (timeoutMs !== null && timeoutMs !== undefined) {\n timeoutTimer = setTimeout(() => {\n finishReject(new PollTimeoutError(`Polling timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n }\n\n if (immediate) {\n runTick();\n } else {\n schedule();\n }\n });\n}\n","/**\n * Pure status adapters for the three Optimize features (BAPI-307).\n *\n * Each backend feature returns a differently-shaped status payload. These\n * adapters normalize those shapes into a small shared vocabulary\n * (`{ state, text, meta }`) WITHOUT changing any backend contract. They are\n * pure: no DOM access, no API calls, no modal calls, no rendering side effects —\n * so they are trivially unit-testable and safe to import in DOM-free contexts.\n */\n\n/**\n * Canonical Learn Repository analysis types, in display order. Centralized here\n * so the adapter, the renderer, and the feature module all agree.\n * @type {ReadonlyArray<string>}\n */\nexport const ANALYSIS_TYPES = Object.freeze([\n 'architecture',\n 'frontend_correctness',\n 'backend_correctness',\n 'template_correctness',\n 'style_correctness',\n 'design_principles',\n]);\n\n/**\n * Allowed normalized states. Frozen so it cannot be mutated by callers.\n * @type {ReadonlyArray<string>}\n */\nexport const NORMALIZED_STATES = Object.freeze([\n 'idle',\n 'running',\n 'review',\n 'draft',\n 'complete',\n 'failed',\n 'unknown',\n]);\n\n/**\n * Normalize a Code Analysis status payload (`{ is_locked, complete,\n * analysis_text }`) into the shared shape.\n * @param {Object} [data={}]\n * @returns {{state: string, text: string, meta: Object}}\n */\nexport function adaptCodeAnalysisStatus(data = {}) {\n const payload = data || {};\n if (payload.is_locked) {\n return { state: 'running', text: 'Analysis in progress...', meta: {} };\n }\n const analysisText = payload.analysis_text;\n if (payload.complete && typeof analysisText === 'string' && analysisText.trim() !== '') {\n return { state: 'complete', text: 'Analysis completed!', meta: { analysisText } };\n }\n return { state: 'idle', text: '', meta: {} };\n}\n\n/**\n * Normalize a single Learn Repository section payload, preserving the existing\n * priority ladder exactly:\n * reanalyzing → running\n * draft_instructions → draft\n * clarifications && !conclusions → review\n * conclusions || instructions → complete\n * otherwise → idle\n * @param {Object} [sectionData={}]\n * @returns {{state: string, meta: Object}}\n */\nexport function adaptLearnRepositorySectionStatus(sectionData = {}) {\n const data = sectionData || {};\n let state;\n if (data.reanalyzing === true) {\n state = 'running';\n } else if (data.draft_instructions) {\n state = 'draft';\n } else if (data.clarifications && !data.conclusions) {\n state = 'review';\n } else if (data.conclusions || data.instructions) {\n state = 'complete';\n } else {\n state = 'idle';\n }\n return { state, meta: data };\n}\n\n/**\n * Normalize a full Learn Repository status payload into a global flag plus a map\n * of per-section normalized statuses keyed by analysis type.\n * @param {Object} [data={}]\n * @returns {{learningInProgress: boolean, sections: Object<string, {state: string, meta: Object}>}}\n */\nexport function adaptLearnRepositoryStatus(data = {}) {\n const payload = data || {};\n const sections = {};\n ANALYSIS_TYPES.forEach((type) => {\n sections[type] = adaptLearnRepositorySectionStatus(payload[type] || {});\n });\n return {\n learningInProgress: payload.learning_in_progress === true,\n sections,\n };\n}\n\n/**\n * Normalize an Estimator status payload (`{ need_estimate_review, range_json }`)\n * into the shared shape.\n * need_estimate_review && range_json → review\n * need_estimate_review && !range_json → running\n * otherwise → idle\n * @param {Object} [data={}]\n * @returns {{state: string, meta: Object}}\n */\nexport function adaptEstimatorStatus(data = {}) {\n const payload = data || {};\n if (payload.need_estimate_review) {\n if (payload.range_json) {\n return { state: 'review', meta: { rangeJson: payload.range_json } };\n }\n return { state: 'running', meta: { message: payload.message } };\n }\n return { state: 'idle', meta: {} };\n}\n","/**\n * Code Analysis functionality for the Optimize page.\n *\n * Routed through the shared Optimize plumbing (BAPI-307): canonical identity from\n * `#optimize-container` via getOptimizeContext, requests via apiCall(), polling\n * via pollUntil(), status normalization via adaptCodeAnalysisStatus, and inline\n * feedback via the shared feedback helpers. Initialization is driven by the\n * single Optimize page entrypoint in optimize.js — this module no longer\n * registers its own DOMContentLoaded listener.\n */\n\nimport { showModal } from './modal.js';\nimport { apiCall, ApiCallError, getApiErrorMessage } from './utils.js';\nimport { getOptimizeContext, encodePathSegment } from './optimize/context.js';\nimport { pollUntil } from './optimize/polling.js';\nimport { adaptCodeAnalysisStatus } from './optimize/status_adapters.js';\nimport { setInlineStatus, withButtonPendingState } from './optimize/feedback.js';\n\n// UI text constants\nconst UI_STRINGS = {\n ANALYSIS_DONE_LABEL: \"The AI analyzed your project and wrote the instructions below. Update and correct them as needed, and then click Save when done.\",\n};\n\n// Code Analysis polls indefinitely in Phase 1 (timeout is opt-in and disabled).\nconst ANALYSIS_POLL_INTERVAL_MS = 5000;\n\n// One AbortController per file label so starting a new poll cancels the prior\n// one for that section without touching the other sections' polls.\nconst analysisPollControllers = new Map();\n\n/**\n * Read the canonical repo name from the Optimize container's data attribute.\n * Mirrors learn_repository.js — the single source restored by the BAPI-307\n * Phase 0 hotfix (the legacy window/URL/body fallback chain is gone).\n * @returns {string|null|undefined}\n */\nexport function getRepoNameFromContext() {\n return document.getElementById('optimize-container')?.dataset.repoName;\n}\n\n/**\n * Initialize Code Analysis for the Optimize page.\n * @param {{repoName: (string|null)}} [context] - Shared Optimize context.\n */\nexport function initializeCodeAnalysis(context = getOptimizeContext()) {\n const repoName = context && context.repoName;\n if (!repoName) {\n console.error('Repository name not available for code analysis');\n return;\n }\n\n // Initialize each section\n setupSection('build', context);\n setupSection('unit_test', context);\n setupSection('e2e_test', context);\n}\n\n/**\n * Wire up a single Code Analysis section (build / unit_test / e2e_test).\n * @param {string} fileLabel - Backend file label.\n * @param {{repoName: (string|null)}} context - Shared Optimize context.\n */\nexport function setupSection(fileLabel, context) {\n const repoName = context && context.repoName;\n // Map backend format (unit_test, e2e_test) to frontend DOM IDs (unit-test, e2e-test)\n const prefix = fileLabel.replace(/_/g, '-');\n\n // Guard against a missing section so this page-specific module tolerates\n // absent DOM safely.\n const sectionContainer = document.getElementById(`${prefix}-section`);\n if (!sectionContainer) {\n console.log(`Section ${prefix} not found, skipping initialization`);\n return;\n }\n\n // Get references to all elements\n const userGuidanceTextarea = document.getElementById(`${prefix}-user-guidance`);\n const analyzeBtn = document.getElementById(`analyze-${prefix}-btn`);\n const outputTextarea = document.getElementById(`${prefix}-output`);\n const saveBtn = document.getElementById(`save-${prefix}-btn`);\n const infoLabel = document.getElementById(`${prefix}-info-label`);\n\n // Set the info label text\n if (infoLabel) {\n infoLabel.textContent = UI_STRINGS.ANALYSIS_DONE_LABEL;\n }\n\n // Check initial status\n checkStatus(fileLabel, prefix, context);\n\n // Run Analysis button handler\n if (analyzeBtn) {\n analyzeBtn.addEventListener('click', async () => {\n try {\n analyzeBtn.disabled = true;\n if (userGuidanceTextarea) userGuidanceTextarea.disabled = true;\n\n setInlineStatus(`${prefix}-status`, 'Starting analysis...', 'processing');\n\n const payload = {\n file_label: fileLabel,\n repo_name: repoName,\n custom_only: true,\n user_guidance: userGuidanceTextarea ? userGuidanceTextarea.value : null\n };\n\n await apiCall('/optimize/analysis/generate', 'POST', payload);\n\n setInlineStatus(`${prefix}-status`, 'Analysis running...', 'processing');\n\n // Start polling\n pollForCompletion(fileLabel, prefix, context);\n } catch (error) {\n // 409: the analysis lock already exists. Preserve the existing UX —\n // an inline \"already in progress\" message, NO modal, and continue\n // into polling so the user still sees completion.\n if (error instanceof ApiCallError && error.status === 409) {\n setInlineStatus(`${prefix}-status`, 'Analysis already in progress...', 'processing');\n pollForCompletion(fileLabel, prefix, context);\n return;\n }\n\n // 400 and other failures: inline error AND the shared modal with the\n // backend detail (getApiErrorMessage keeps the readable message and\n // avoids leaking the bare \"HTTP error! status: N\" sentinel), and\n // re-enable the controls.\n console.error(`Error starting ${fileLabel} analysis:`, error);\n const detail = getApiErrorMessage(error, 'Failed to start analysis. Please try again.');\n setInlineStatus(`${prefix}-status`, `Error: ${detail}`, 'error');\n showModal('Error', detail);\n\n analyzeBtn.disabled = false;\n if (userGuidanceTextarea) userGuidanceTextarea.disabled = false;\n }\n });\n }\n\n // Save button handler\n if (saveBtn) {\n saveBtn.addEventListener('click', async () => {\n const payload = {\n file_label: fileLabel,\n repo_name: repoName,\n analysis_text: outputTextarea ? outputTextarea.value : ''\n };\n try {\n await withButtonPendingState(saveBtn, 'Saving...', () =>\n apiCall('/optimize/analysis/persist', 'POST', payload)\n );\n\n setInlineStatus(`${prefix}-status`, 'Saved successfully!', 'success', { autoHideMs: 3000 });\n } catch (error) {\n console.error(`Error saving ${fileLabel} analysis:`, error);\n const detail = getApiErrorMessage(error, 'Failed to save analysis. Please try again.');\n setInlineStatus(`${prefix}-status`, `Error saving: ${detail}`, 'error');\n showModal('Error', detail);\n }\n });\n }\n}\n\n/**\n * Read the section's current status and render the matching state.\n * @param {string} fileLabel\n * @param {string} prefix\n * @param {{repoName: (string|null)}} context\n */\nexport async function checkStatus(fileLabel, prefix, context) {\n const repoName = context && context.repoName;\n const statusUrl = `/optimize/analysis/status/${encodePathSegment(repoName)}/${fileLabel}`;\n\n let data;\n try {\n data = await apiCall(statusUrl, 'GET');\n } catch (error) {\n console.error(`Error checking status for ${fileLabel}:`, error);\n // Default to showing no analysis state on error\n showNoAnalysisState(prefix);\n return;\n }\n\n const status = adaptCodeAnalysisStatus(data);\n if (status.state === 'running') {\n setInlineStatus(`${prefix}-status`, 'Analysis in progress...', 'processing');\n pollForCompletion(fileLabel, prefix, context);\n } else if (status.state === 'complete') {\n showAnalysisDoneState(prefix, status.meta.analysisText);\n } else {\n showNoAnalysisState(prefix);\n }\n}\n\nfunction showNoAnalysisState(prefix) {\n const noAnalysisContainer = document.getElementById(`${prefix}-state-no-analysis`);\n const analysisDoneContainer = document.getElementById(`${prefix}-state-analysis-done`);\n\n if (analysisDoneContainer) {\n analysisDoneContainer.classList.add('hidden');\n }\n if (noAnalysisContainer) {\n noAnalysisContainer.classList.remove('hidden');\n }\n}\n\nfunction showAnalysisDoneState(prefix, text) {\n const noAnalysisContainer = document.getElementById(`${prefix}-state-no-analysis`);\n const analysisDoneContainer = document.getElementById(`${prefix}-state-analysis-done`);\n const outputTextarea = document.getElementById(`${prefix}-output`);\n\n if (outputTextarea) {\n outputTextarea.value = text;\n }\n if (noAnalysisContainer) {\n noAnalysisContainer.classList.add('hidden');\n }\n if (analysisDoneContainer) {\n analysisDoneContainer.classList.remove('hidden');\n }\n}\n\n/**\n * Poll the section status until the lock releases, then render completion.\n * Uses pollUntil with an opt-in timeout left disabled (timeoutMs: null) so Code\n * Analysis keeps its Phase-1 unbounded polling. A per-file-label AbortController\n * cancels any in-flight poll for the same section before a new one starts.\n * @param {string} fileLabel\n * @param {string} prefix\n * @param {{repoName: (string|null)}} context\n */\nexport function pollForCompletion(fileLabel, prefix, context) {\n const repoName = context && context.repoName;\n const statusUrl = `/optimize/analysis/status/${encodePathSegment(repoName)}/${fileLabel}`;\n const analyzeBtn = document.getElementById(`analyze-${prefix}-btn`);\n const userGuidanceTextarea = document.getElementById(`${prefix}-user-guidance`);\n\n // Abort any existing poll for this section before starting a new one.\n const existing = analysisPollControllers.get(fileLabel);\n if (existing) existing.abort();\n const controller = new AbortController();\n analysisPollControllers.set(fileLabel, controller);\n\n const reEnableControls = () => {\n if (analyzeBtn) analyzeBtn.disabled = false;\n if (userGuidanceTextarea) userGuidanceTextarea.disabled = false;\n };\n\n pollUntil(statusUrl, (data) => data != null && !data.is_locked, {\n intervalMs: ANALYSIS_POLL_INTERVAL_MS,\n timeoutMs: null,\n signal: controller.signal,\n // Preserve the original \"wait one interval before the first poll\" timing.\n immediate: false,\n })\n .then((data) => {\n analysisPollControllers.delete(fileLabel);\n reEnableControls();\n if (data.complete && data.analysis_text) {\n setInlineStatus(`${prefix}-status`, 'Analysis completed!', 'success');\n showAnalysisDoneState(prefix, data.analysis_text);\n } else {\n setInlineStatus(`${prefix}-status`, 'Analysis finished. Check results.', 'warning');\n }\n })\n .catch((error) => {\n // A newer poll for the same section aborted this one — stay silent.\n if (error && error.name === 'AbortError') {\n return;\n }\n analysisPollControllers.delete(fileLabel);\n console.error(`Polling error for ${fileLabel}:`, error);\n setInlineStatus(`${prefix}-status`, 'Error checking status. Please refresh.', 'error');\n reEnableControls();\n });\n}\n","// src/js/project-detail.js\n\nimport { initModal, showModal, hideModal } from './modal.js';\nimport {\n showLoading, hideLoading, apiCall, getApiErrorMessage, arrayToString,\n captureBaseline, diff, resetBaseline,\n initDrawers, setDrawerExpanded,\n} from './utils.js';\n\n// --- Constants & State ---\nlet currentProjectId = null;\n\n// --- DOM Elements ---\nconst projectDetailContainer = document.getElementById('project-detail-container');\nconst projectDetailTitle = document.getElementById('project-detail-title');\nconst projectInfoForm = document.getElementById('project-info-form');\nconst estimatorForm = document.getElementById('estimator-form');\nconst codeWriterForm = document.getElementById('code-writer-form');\nconst codeReviewForm = document.getElementById('code-review-form');\nconst saveChangesBtn = document.getElementById('save-project-btn');\nconst estimateScaleJsonTextarea = document.getElementById('estimate_scale');\nconst estimateScaleJsonErrorDiv = document.getElementById('estimate_scale-error');\n\n// Normalized baseline snapshot of every persisted field, captured after the\n// forms are populated. Field-granular sparse saves diff against this instead of\n// tracking interactions, so only fields whose normalized value actually changed\n// are sent to the backend.\nlet projectDetailBaseline = null;\n\n// --- Custom DOM value readers for fields the shared diff util cannot read by id ---\n\n/**\n * Coerce a raw array into trimmed, non-empty strings without sorting, diffing,\n * or otherwise mutating the input. Used by the custom checkbox readers below.\n * @param {Array<*>} values\n * @returns {Array<string>}\n */\nfunction normalizeStringArrayValue(values) {\n if (!Array.isArray(values)) return [];\n return values\n .filter(value => value !== null && value !== undefined)\n .map(value => (typeof value === 'string' ? value.trim() : String(value)))\n .filter(value => value !== '');\n}\n\n/**\n * Read the currently-checked MCP manual slugs. These checkboxes are rendered\n * dynamically by populateMcpDocSelector(), so the baseline must be captured\n * after that render (see captureProjectDetailBaseline()).\n * @returns {Array<string>}\n */\nfunction getSelectedMcpSlugs() {\n return normalizeStringArrayValue(\n Array.from(\n document.querySelectorAll('#selected_mcp_slugs_list input[type=\"checkbox\"]:checked')\n ).map(cb => cb.value)\n );\n}\n\n/**\n * Read the allowed-provider checkbox values, preserving the existing save\n * behavior that includes the disabled primary-provider checkbox.\n * @returns {Array<string>}\n */\nfunction getAllowedProviderValues() {\n return normalizeStringArrayValue(\n Array.from(\n document.querySelectorAll('#allowed_providers_group .provider-checkbox')\n ).filter(cb => cb.checked || cb.disabled).map(cb => cb.value)\n );\n}\n\n// --- Hidden config field helpers (BAPI-308) ---\n// Second-opinion provider, deep-research stall timeout, story points field,\n// documentation instructions, and the structured CI Follow-up sub-section. These\n// surface five pre-existing DB columns on the Configure page; the CI Follow-up\n// control diffs as a stable canonical-JSON-string text descriptor.\n\nconst CI_FOLLOWUP_INSTRUCTIONS_MAX_BYTES = 8192;\nconst CI_FOLLOWUP_DEFAULTS = Object.freeze({\n strategy: 'poll_only',\n max_iterations: 1,\n max_minutes: 10,\n instructions: '',\n});\n\n/**\n * UTF-8 byte length of a string. HTML `maxlength` counts UTF-16 code units, so the\n * 8192-byte ci_followup instructions limit is enforced in bytes via TextEncoder to\n * match the backend's byte rule.\n * @param {string} value\n * @returns {number}\n */\nfunction getUtf8ByteLength(value) {\n return new TextEncoder().encode(value || '').length;\n}\n\n/**\n * The checked CI follow-up strategy radio value, defaulting to 'poll_only'.\n * @returns {string}\n */\nfunction getSelectedCiFollowupStrategy() {\n const checked = document.querySelector('input[name=\"ci_followup_strategy\"]:checked');\n return checked ? checked.value : 'poll_only';\n}\n\n/**\n * Canonical CI follow-up value as a stable JSON string for sparse diffing, or ''\n * when the feature is disabled. Keys are emitted in the canonical order the backend\n * validator expects (strategy, max_iterations, max_minutes, instructions) so a\n * stable string diffs deterministically as a `type:'text'` descriptor without\n * extending the shared diff util.\n * @returns {string}\n */\nfunction getCiFollowupConfigCanonicalString() {\n const enabled = document.getElementById('ci_followup_enabled');\n if (!enabled || !enabled.checked) return '';\n const maxIterationsEl = document.getElementById('ci_followup_max_iterations');\n const maxMinutesEl = document.getElementById('ci_followup_max_minutes');\n const instructionsEl = document.getElementById('ci_followup_instructions');\n const config = {\n strategy: getSelectedCiFollowupStrategy(),\n max_iterations: parseInt(maxIterationsEl ? maxIterationsEl.value : '', 10),\n max_minutes: parseInt(maxMinutesEl ? maxMinutesEl.value : '', 10),\n instructions: instructionsEl ? instructionsEl.value : '',\n };\n return JSON.stringify(config);\n}\n\n/**\n * Apply the strategy radio + numeric + instructions controls from a config object.\n * @param {{strategy:string, max_iterations:(number|string), max_minutes:(number|string), instructions:string}} config\n */\nfunction setCiFollowupControlValues(config) {\n document.querySelectorAll('input[name=\"ci_followup_strategy\"]').forEach(radio => {\n radio.checked = radio.value === config.strategy;\n });\n const maxIterationsEl = document.getElementById('ci_followup_max_iterations');\n if (maxIterationsEl) maxIterationsEl.value = config.max_iterations;\n const maxMinutesEl = document.getElementById('ci_followup_max_minutes');\n if (maxMinutesEl) maxMinutesEl.value = config.max_minutes;\n const instructionsEl = document.getElementById('ci_followup_instructions');\n if (instructionsEl) instructionsEl.value = config.instructions;\n}\n\n/**\n * Update the byte counter text and over-limit class for the ci_followup\n * instructions textarea.\n */\nfunction updateCiFollowupInstructionsByteCounter() {\n const counter = document.getElementById('ci_followup_instructions_byte_count');\n if (!counter) return;\n const instructionsEl = document.getElementById('ci_followup_instructions');\n const byteLength = getUtf8ByteLength(instructionsEl ? instructionsEl.value : '');\n counter.textContent = `${byteLength}/${CI_FOLLOWUP_INSTRUCTIONS_MAX_BYTES} bytes`;\n counter.classList.toggle('over-limit', byteLength > CI_FOLLOWUP_INSTRUCTIONS_MAX_BYTES);\n}\n\n/**\n * Apply strategy-specific state to the instructions textarea: poll_only disables\n * (and, for a user-initiated change, clears) instructions; custom makes them\n * required; fix_and_iterate makes them optional. Initial population passes\n * `clearInstructionsOnPollOnly: false` so MCP-authored poll_only instructions\n * survive page load.\n * @param {{clearInstructionsOnPollOnly?: boolean}} [options]\n */\nfunction applyCiFollowupStrategyState(options = {}) {\n const { clearInstructionsOnPollOnly = true } = options;\n const strategy = getSelectedCiFollowupStrategy();\n const instructionsEl = document.getElementById('ci_followup_instructions');\n if (instructionsEl) {\n if (strategy === 'poll_only') {\n if (clearInstructionsOnPollOnly) instructionsEl.value = '';\n instructionsEl.disabled = true;\n instructionsEl.required = false;\n } else if (strategy === 'custom') {\n instructionsEl.disabled = false;\n instructionsEl.required = true;\n } else { // fix_and_iterate\n instructionsEl.disabled = false;\n instructionsEl.required = false;\n }\n }\n updateCiFollowupInstructionsByteCounter();\n}\n\n/**\n * Show/hide and enable/disable the CI Follow-up inner controls based on the enable\n * checkbox. Off: hide via `.hidden` and disable every inner input. On: reveal and\n * re-enable, then apply strategy-specific instruction state (without clearing).\n */\nfunction toggleCiFollowupControlsVisibility() {\n const enabled = document.getElementById('ci_followup_enabled');\n const controls = document.getElementById('ci_followup_controls');\n if (!controls) return;\n const isOn = !!(enabled && enabled.checked);\n const innerInputs = controls.querySelectorAll('input, textarea, select');\n if (isOn) {\n controls.classList.remove('hidden');\n innerInputs.forEach(el => { el.disabled = false; });\n applyCiFollowupStrategyState({ clearInstructionsOnPollOnly: false });\n } else {\n controls.classList.add('hidden');\n innerInputs.forEach(el => { el.disabled = true; });\n }\n}\n\n/**\n * Initialize the CI Follow-up sub-section from the GET value. Accepts the raw DB\n * value (canonical JSON string), an already-parsed object, or null/empty/invalid.\n * Invalid or empty values disable the feature and reset inner controls to defaults;\n * a valid config enables it and populates every control, preserving stored\n * instructions even for poll_only so MCP-authored values are not silently altered.\n * @param {string|Object|null} rawValue\n */\nfunction populateCiFollowupConfig(rawValue) {\n const enabledEl = document.getElementById('ci_followup_enabled');\n let config = null;\n if (rawValue && typeof rawValue === 'object') {\n config = rawValue;\n } else if (typeof rawValue === 'string' && rawValue.trim() !== '') {\n try {\n config = JSON.parse(rawValue);\n } catch (e) {\n config = null;\n }\n }\n\n if (!config || Array.isArray(config) || typeof config !== 'object') {\n if (enabledEl) enabledEl.checked = false;\n setCiFollowupControlValues(CI_FOLLOWUP_DEFAULTS);\n toggleCiFollowupControlsVisibility();\n return;\n }\n\n if (enabledEl) enabledEl.checked = true;\n setCiFollowupControlValues({\n strategy: config.strategy || CI_FOLLOWUP_DEFAULTS.strategy,\n max_iterations: config.max_iterations != null ? config.max_iterations : CI_FOLLOWUP_DEFAULTS.max_iterations,\n max_minutes: config.max_minutes != null ? config.max_minutes : CI_FOLLOWUP_DEFAULTS.max_minutes,\n instructions: typeof config.instructions === 'string' ? config.instructions : CI_FOLLOWUP_DEFAULTS.instructions,\n });\n // Reveals the controls and applies strategy state without clearing instructions.\n toggleCiFollowupControlsVisibility();\n}\n\n/**\n * Rebuild the second-opinion provider <select> so its options are exactly the\n * currently-allowed providers plus a blank \"Use default\". Preserves the current (or\n * a preferred) selection when it is still allowed; otherwise resets it to blank.\n * @param {string} [preferredValue] - value to select if allowed (used on initial load)\n */\nfunction rebuildSecondOpinionProviderOptions(preferredValue) {\n const select = document.getElementById('second_opinion_provider');\n if (!select) return;\n const desiredValue = preferredValue !== undefined ? preferredValue : select.value;\n const allowed = getAllowedProviderValues();\n\n select.textContent = '';\n const blank = document.createElement('option');\n blank.value = '';\n blank.textContent = 'Use default';\n select.appendChild(blank);\n\n allowed.forEach(provider => {\n const option = document.createElement('option');\n option.value = provider;\n option.textContent = PROVIDER_DISPLAY_NAMES[provider] || provider;\n select.appendChild(option);\n });\n\n select.value = allowed.includes(desiredValue) ? desiredValue : '';\n}\n\n/**\n * Show the deep-research stall timeout group only when Deep Research is enabled;\n * disable the input while hidden so browser validation never blocks save.\n */\nfunction toggleDeepResearchTimeoutVisibility() {\n const enabled = document.getElementById('deep_research_enabled');\n const group = document.getElementById('deep_research_stall_timeout_minutes_group');\n const input = document.getElementById('deep_research_stall_timeout_minutes');\n if (!group) return;\n const isOn = !!(enabled && enabled.checked);\n group.classList.toggle('hidden', !isOn);\n if (input) input.disabled = !isOn;\n}\n\n/**\n * Show the story points field group only when Update JIRA is enabled; disable the\n * input while hidden.\n */\nfunction toggleStoryPointsFieldVisibility() {\n const updateJira = document.getElementById('update_jira');\n const group = document.getElementById('story_points_field_group');\n const input = document.getElementById('story_points_field');\n if (!group) return;\n const isOn = !!(updateJira && updateJira.checked);\n group.classList.toggle('hidden', !isOn);\n if (input) input.disabled = !isOn;\n}\n\n/**\n * Wire the three conditional-visibility toggles and CI strategy/byte listeners,\n * then apply all initial states once. Must run after the forms are populated and\n * before captureProjectDetailBaseline() so hidden/disabled state is correct before\n * the baseline snapshot.\n */\nfunction setupConditionalVisibilityListeners() {\n const deepResearchEnabled = document.getElementById('deep_research_enabled');\n if (deepResearchEnabled) {\n deepResearchEnabled.addEventListener('change', toggleDeepResearchTimeoutVisibility);\n }\n const updateJira = document.getElementById('update_jira');\n if (updateJira) {\n updateJira.addEventListener('change', toggleStoryPointsFieldVisibility);\n }\n const ciFollowupEnabled = document.getElementById('ci_followup_enabled');\n if (ciFollowupEnabled) {\n ciFollowupEnabled.addEventListener('change', toggleCiFollowupControlsVisibility);\n }\n document.querySelectorAll('input[name=\"ci_followup_strategy\"]').forEach(radio => {\n radio.addEventListener('change', () => applyCiFollowupStrategyState());\n });\n const ciInstructions = document.getElementById('ci_followup_instructions');\n if (ciInstructions) {\n ciInstructions.addEventListener('input', updateCiFollowupInstructionsByteCounter);\n }\n\n // Apply initial state for all three dependent regions before baseline capture.\n toggleDeepResearchTimeoutVisibility();\n toggleStoryPointsFieldVisibility();\n toggleCiFollowupControlsVisibility();\n}\n\n// Canonical descriptor list driving baseline capture, sparse diffing, section\n// routing, and payload shaping. One descriptor per persisted field. `field` is\n// the payload key; `type` is 'text' | 'array' | 'boolean'. Fields without a\n// custom reader use their matching DOM `id`.\nconst PROJECT_DETAIL_FIELD_DESCRIPTORS = [\n // General (config_projects, plus testing stacks routed to code_repositories)\n { field: 'project_description', type: 'text', id: 'project_description' },\n { field: 'confluence_pattern', type: 'text', id: 'confluence_pattern' },\n { field: 'ignored_domains', type: 'array', delimiter: ',', id: 'ignored_domains' },\n { field: 'deep_research_enabled', type: 'boolean', id: 'deep_research_enabled' },\n { field: 'deep_research_stall_timeout_minutes', type: 'text', id: 'deep_research_stall_timeout_minutes' },\n { field: 'allowed_providers', type: 'array', getValue: getAllowedProviderValues, orderInsensitive: true },\n { field: 'second_opinion_provider', type: 'text', id: 'second_opinion_provider' },\n { field: 'unit_testing_stack', type: 'text', id: 'unit_testing_stack' },\n { field: 'e2e_testing_stack', type: 'text', id: 'e2e_testing_stack' },\n\n // Estimator\n { field: 'estimate_qa', type: 'boolean', id: 'estimate_qa' },\n { field: 'process_estimate_instructions', type: 'text', id: 'process_estimate_instructions' },\n { field: 'estimate_instructions', type: 'text', id: 'estimate_instructions' },\n { field: 'update_jira', type: 'boolean', id: 'update_jira' },\n { field: 'estimate_scale', type: 'text', id: 'estimate_scale' },\n { field: 'story_points_field', type: 'text', id: 'story_points_field' },\n\n // Code Writer\n { field: 'provider', type: 'text', id: 'provider' },\n { field: 'architecture_instructions', type: 'text', id: 'architecture_instructions' },\n { field: 'tdd_document_instructions', type: 'text', id: 'tdd_document_instructions' },\n { field: 'fsd_document_instructions', type: 'text', id: 'fsd_document_instructions' },\n { field: 'design_principles', type: 'text', id: 'design_principles' },\n { field: 'unit_testing_instructions', type: 'text', id: 'unit_testing_instructions' },\n { field: 'e2e_testing_instructions', type: 'text', id: 'e2e_testing_instructions' },\n { field: 'review_instructions', type: 'text', id: 'review_instructions' },\n { field: 'documentation_instructions', type: 'text', id: 'documentation_instructions' },\n { field: 'allow_mutating_smoke_ops', type: 'boolean', id: 'allow_mutating_smoke_ops' },\n { field: 'selected_mcp_slugs', type: 'array', getValue: getSelectedMcpSlugs, orderInsensitive: true },\n // CI Follow-up: a structured sub-section that diffs as a stable canonical JSON\n // string ('' when disabled). getPayloadValueForField parses it back to an object.\n { field: 'ci_followup_config', type: 'text', getValue: getCiFollowupConfigCanonicalString },\n\n // Code Reviewer (the four *_correctness_standards render in the Code Writer\n // drawer but persist via the code_reviewer payload — see FIELD_TO_SECTION)\n { field: 'frontend_correctness_standards', type: 'text', id: 'frontend_correctness_standards' },\n { field: 'backend_correctness_standards', type: 'text', id: 'backend_correctness_standards' },\n { field: 'template_correctness_standards', type: 'text', id: 'template_correctness_standards' },\n { field: 'style_correctness_standards', type: 'text', id: 'style_correctness_standards' },\n { field: 'reviewable_file_types', type: 'array', delimiter: ',', id: 'reviewable_file_types' },\n { field: 'include_path', type: 'array', delimiter: '\\n', id: 'include_path' },\n { field: 'exclude_path', type: 'array', delimiter: '\\n', id: 'exclude_path' },\n { field: 'frontend_styleguide', type: 'text', id: 'frontend_styleguide' },\n { field: 'backend_styleguide', type: 'text', id: 'backend_styleguide' },\n { field: 'css_styleguide', type: 'text', id: 'css_styleguide' },\n { field: 'review_correctness', type: 'boolean', id: 'review_correctness' },\n { field: 'review_style', type: 'boolean', id: 'review_style' },\n { field: 'review_requirements', type: 'boolean', id: 'review_requirements' },\n { field: 'review_architecture', type: 'boolean', id: 'review_architecture' },\n { field: 'create_tests', type: 'boolean', id: 'create_tests' },\n { field: 'create_documentation', type: 'boolean', id: 'create_documentation' },\n { field: 'documentation_standards', type: 'text', id: 'documentation_standards' },\n { field: 'score_threshold_for_review', type: 'text', id: 'score_threshold_for_review' },\n { field: 'no_comment_on_passing_score', type: 'boolean', id: 'no_comment_on_passing_score' },\n];\n\n// Maps each descriptor field to exactly one backend payload section. The four\n// correctness-standard fields route to code_reviewer even though their controls\n// render inside the Code Writer drawer.\nconst FIELD_TO_SECTION = {\n // general\n project_description: 'general',\n confluence_pattern: 'general',\n ignored_domains: 'general',\n deep_research_enabled: 'general',\n deep_research_stall_timeout_minutes: 'general',\n allowed_providers: 'general',\n second_opinion_provider: 'general',\n unit_testing_stack: 'general',\n e2e_testing_stack: 'general',\n // estimator\n estimate_qa: 'estimator',\n process_estimate_instructions: 'estimator',\n estimate_instructions: 'estimator',\n update_jira: 'estimator',\n estimate_scale: 'estimator',\n story_points_field: 'estimator',\n // code_writer\n provider: 'code_writer',\n architecture_instructions: 'code_writer',\n tdd_document_instructions: 'code_writer',\n fsd_document_instructions: 'code_writer',\n design_principles: 'code_writer',\n unit_testing_instructions: 'code_writer',\n e2e_testing_instructions: 'code_writer',\n review_instructions: 'code_writer',\n documentation_instructions: 'code_writer',\n ci_followup_config: 'code_writer',\n allow_mutating_smoke_ops: 'code_writer',\n selected_mcp_slugs: 'code_writer',\n // code_reviewer\n frontend_correctness_standards: 'code_reviewer',\n backend_correctness_standards: 'code_reviewer',\n template_correctness_standards: 'code_reviewer',\n style_correctness_standards: 'code_reviewer',\n reviewable_file_types: 'code_reviewer',\n include_path: 'code_reviewer',\n exclude_path: 'code_reviewer',\n frontend_styleguide: 'code_reviewer',\n backend_styleguide: 'code_reviewer',\n css_styleguide: 'code_reviewer',\n review_correctness: 'code_reviewer',\n review_style: 'code_reviewer',\n review_requirements: 'code_reviewer',\n review_architecture: 'code_reviewer',\n create_tests: 'code_reviewer',\n create_documentation: 'code_reviewer',\n documentation_standards: 'code_reviewer',\n score_threshold_for_review: 'code_reviewer',\n no_comment_on_passing_score: 'code_reviewer',\n};\n\n/**\n * Capture the normalized baseline of every descriptor. Must run only after the\n * forms are populated AND the dynamic MCP checkboxes are rendered, otherwise\n * dynamically-rendered fields (selected_mcp_slugs) would look permanently dirty.\n */\nfunction captureProjectDetailBaseline() {\n projectDetailBaseline = captureBaseline(PROJECT_DETAIL_FIELD_DESCRIPTORS);\n}\n\n/**\n * Re-snapshot the baseline after a successful save so an immediate second save\n * with no further edits is clean.\n */\nfunction resetProjectDetailBaseline() {\n projectDetailBaseline = resetBaseline(PROJECT_DETAIL_FIELD_DESCRIPTORS);\n}\n\n/**\n * Thin adapter over the shared diff utility. Returns a field-keyed object of the\n * fields whose normalized value changed since the baseline. Performs no value\n * comparison of its own — it only interprets the util's diff output.\n * @returns {Object<string, *>}\n */\nfunction getChangedFieldEntries() {\n return diff(PROJECT_DETAIL_FIELD_DESCRIPTORS, projectDetailBaseline);\n}\n\n/**\n * Final payload value for a changed field. Most fields use their normalized diff\n * value as-is; estimate_scale and score_threshold_for_review keep their existing\n * save shaping.\n * @param {string} fieldName\n * @param {*} changedValue - normalized value from the diff result\n * @param {string|null} estimateScaleValue - validated estimate-scale string\n * @returns {*}\n */\nfunction getPayloadValueForField(fieldName, changedValue, estimateScaleValue = null) {\n if (fieldName === 'estimate_scale') {\n return estimateScaleValue;\n }\n if (fieldName === 'score_threshold_for_review') {\n return parseInt(changedValue, 10) || 85;\n }\n if (fieldName === 'deep_research_stall_timeout_minutes') {\n return changedValue === '' ? null : parseInt(changedValue, 10);\n }\n if (fieldName === 'second_opinion_provider') {\n return changedValue === '' ? null : changedValue;\n }\n if (fieldName === 'ci_followup_config') {\n // changedValue is the canonical JSON string ('' when disabled). Send null\n // when off; otherwise an object the backend's shared validator canonicalizes.\n return changedValue === '' ? null : JSON.parse(changedValue);\n }\n return changedValue;\n}\n\n/**\n * Build the sparse PUT body from a changed-field map. Section objects are created\n * lazily and contain only changed fields; correctness standards route to\n * code_reviewer via FIELD_TO_SECTION. `touched_fields` is always present and is\n * the changed field-name list (`[]` when nothing changed), so the backend\n * clearing allowlists fire only for fields the user intentionally cleared and the\n * \"No updates provided or payload empty\" path is preserved.\n * @param {Object<string, *>} changedFieldEntries - field-keyed normalized diff values\n * @param {string|null} estimateScaleValue - validated estimate-scale string\n * @returns {Object}\n */\nfunction buildSparseProjectPayload(changedFieldEntries, estimateScaleValue = null) {\n const projectData = {};\n for (const [fieldName, changedValue] of Object.entries(changedFieldEntries || {})) {\n const section = FIELD_TO_SECTION[fieldName];\n if (!section) continue;\n if (!projectData[section]) projectData[section] = {};\n projectData[section][fieldName] = getPayloadValueForField(fieldName, changedValue, estimateScaleValue);\n }\n projectData.touched_fields = Object.keys(changedFieldEntries || {});\n return projectData;\n}\n\n// --- Explanation Texts ---\nconst EXPLANATION_TEXTS = {\n 'project_description': 'Describe your project for the AI. Be sure to include your tech stack, Commerce Cloud version, and any other crucial architectural details here.',\n 'confluence_pattern': 'Optional: A URL pattern or prefix used to identify Confluence documentation links relevant to this project (e.g., bridge-test.atlassian.net/wiki).',\n 'unit_testing_stack': 'Optional: Describe the frameworks or libraries used for unit testing in this project (e.g., Jest, Mocha, Chai, Sinon, and Proxyquire). Leave blank if you don\\'t want unit tests.',\n 'e2e_testing_stack': 'Optional: Describe the frameworks or tools used for end-to-end testing (e.g., Playwright, Selenium, Cypress). Leave blank if you don\\'t want E2E tests.',\n 'ignored_domains': 'Optional: A comma-separated list of domain names that you want the AI to ignore. For example, links to a sandbox environment.',\n 'estimate_qa': 'When enabled, the Estimator will also add an estimate for Quality Assurance (QA) activities.',\n 'process_estimate_instructions': 'Give the AI any background it needs to accurately estimate QA tasks on your project.',\n 'estimate_scale': 'Enter a valid JSON array defining your custom estimate scale. Each object in the array needs \\'min\\', \\'max\\', and \\'label\\' keys. Refer to the project README for detailed formatting instructions and examples.',\n 'update_jira': 'If enabled, the Estimator will automatically update the story points field on the corresponding JIRA ticket after generating an estimate.',\n 'estimate_instructions': 'Optional: Custom instructions that will be appended to the AI estimation prompt to guide estimates.',\n 'custom_directories': 'Specify which directories within your codebase are custom and modifiable by the AI. Enter one directory path per line (e.g., `/custom-cartridge/`). Required.',\n 'exclude_file_extensions': 'Specify file extensions to exclude from processing. Enter one extension per line (e.g., `.min.js`, `.map`). Optional.',\n 'exclude_directories': 'Specify directories to exclude from processing. Enter one directory path per line (e.g., `/node_modules/`, `/dist/`). Optional.',\n 'architecture_instructions': 'Provide specific instructions for the AI about your project\\'s architecture. Include patterns, best practices, and architectural constraints that the AI should follow when generating plans or TDDs.',\n 'tdd_document_instructions': 'Optional: Define the STRUCTURE and FORMAT of the generated Technical Design Document (TDD) — the sections, ordering, and writing style. This replaces the default TDD format verbatim. It does NOT set architecture policy (use Architecture Instructions for that). Leave blank to use the built-in default TDD format.',\n 'fsd_document_instructions': 'Optional: Define the STRUCTURE and FORMAT of the generated Functional Specification Document (FSD), which targets a product/functional audience (user flows, functional requirements, acceptance criteria). This replaces the default FSD format verbatim and is distinct from the technical implementation design. Leave blank to use the built-in default FSD format.',\n 'design_principles': 'Frontend design principles and visual language for this project. Describe your design tokens (color, spacing, typography), layout patterns, component inventory, and composition rules so the AI generates UI that matches your design system. This field is shared with the learn-repository / Optimize flow, which can also write it — there is no locking or merge, so it is last-write-wins (the most recent writer wins).',\n 'provider': 'Choose the AI provider to use for this project (OpenAI, Google Gemini, or Anthropic). This controls which LLM is used for planning, coding, and analysis.',\n 'allowed_providers': 'The set of AI providers this project is permitted to use. The selected primary AI provider is always included and locked, because it must remain in Allowed Providers. Enable additional providers here to allow them as alternates.',\n 'unit_testing_instructions': 'Detailed instructions for how the AI should write unit tests. Include test frameworks, mocking patterns, naming conventions, and project-specific testing requirements.',\n 'e2e_testing_instructions': 'Detailed instructions for how the AI should write end-to-end tests. Include test frameworks, selectors, setup/teardown patterns, and any project-specific E2E testing requirements.',\n 'version': 'The specific version of Salesforce Commerce Cloud (SFCC) your project is using. Select \"Hybrid\" if your project combines any two distinct standalone SFCC versions in one codebase. Required.',\n 'base_version': 'For a Hybrid project, the standalone SFCC version (SFRA, PWA Kit, SiteGenesis, or Storefront Next) that encompasses the majority of your codebase.',\n 'alternate_version_directories': 'Which directories hold the alternate SFCC version (the one that isn\\'t your base version)? Input partial directory paths that can identify them. Enter one directory path per line.',\n 'write_tests': 'If enabled, the Code Writer will attempt to generate unit tests alongside the code draft. Optional, defaults to true.',\n 'allow_mutating_smoke_ops': 'If selected, the AI will work to genuinely test functionality through real operations, executed against the local environment. This will provide greater quality assurance, but requires greater trust. Only runs on the sandbox.',\n 'review_instructions': 'Instructions that guide how the AI validates that a plan was implemented correctly. Use this to define the specific verification steps the AI should follow during its review — such as which tools to invoke, what checks to run, and how to confirm correctness.',\n 'selected_mcp_slugs': 'MCP Validation Manuals: choose which canonical MCP manuals are supplied to the final plan reviewer when it generates review, smoke, integration, and documentation steps. The available choices are loaded from the backend MCP docs catalog.',\n 'pwa_overrides_directories': 'Specify the directories that contain your PWA Kit template overrides. Enter one directory path per line (e.g., `overrides`, `app/templates`, `custom-overrides`). These directories will be given special consideration when the AI generates or modifies code.',\n 'frontend_correctness_standards': 'Describe how the AI should review frontend Javascript. Consider project standards, error handling, data validation, etc. Required.',\n 'backend_correctness_standards': 'Describe how the AI should review backend code. Consider project standards, error handling, data validation, security, best practices',\n 'reviewable_file_types': 'List the file suffixes (e.g., `js, isml, scss`) the AI should review. Required.',\n 'include_path': 'Specify directory paths (one per line) to include in reviews. Prevents reviewing third-party code. Recommended.',\n 'exclude_path': 'Specify directory paths (one per line) to exclude from reviews, even if they are within an included path. Recommended.',\n 'template_correctness_standards': 'Define standards for reviewing template code (e.g., HTML, ISML), focusing on issues like accessibility. Recommended. If blank, template correctness is skipped.',\n 'style_correctness_standards': 'Define standards for reviewing CSS/SASS/LESS code. Optional. If blank, style correctness for these files is skipped.',\n 'frontend_styleguide': 'Describe style conventions for frontend Javascript. Optional. If blank, frontend style reviews are skipped.',\n 'backend_styleguide': 'Describe style conventions for backend code (e.g., JSDoc, function size). Optional. If blank, backend style reviews are skipped.',\n 'css_styleguide': 'Describe style conventions for CSS/SASS/LESS code. Optional. If blank, CSS style reviews are skipped.',\n 'review_correctness': 'Enable/disable checking code for bugs and errors. Defaults to true. Optional.',\n 'review_style': 'Enable/disable checking code for style guide adherence. Defaults to true. Optional.',\n 'review_requirements': 'Enable/disable checking if the PR fulfills ticket requirements (requires ticket ID in branch/commit). Best for initial commits. Defaults to true. Optional.',\n 'review_architecture': 'Enable/disable checking for integration errors and correctness (if correctness review is off). Defaults to true. Optional.',\n 'create_tests': 'Enable/disable AI generation of unit/integration tests for the PR. Defaults to false. Optional.',\n 'create_documentation': 'Enable/disable AI generation of documentation for the PR. Defaults to false. Optional.',\n 'documentation_standards': 'If `Create Documentation` is enabled, describe the desired documentation format and standards here. Optional.',\n 'score_threshold_for_review': 'Set a score (0-100) below which the AI will perform a review. Files scoring at or above this threshold are considered passing. Defaults to 85. Optional.',\n 'no_comment_on_passing_score': 'If enabled, the AI will not leave a comment on files with a passing score. If disabled, it leaves a positive comment (e.g., \"Looks good!\"). Defaults to true (no comment). Optional.',\n 'schedule_interval': 'How frequently should the repository parsing job run? This is measured in days (e.g., every 1 day, 2 days, etc.).',\n 'schedule_timezone': 'The timezone for scheduling repository parsing jobs. We will run this job daily at 1:00 AM according to the timezone you select.',\n 'deep_research_enabled': 'Enable or disable AI deep research for this project. When enabled, the MCP tool can perform in-depth web research on technical topics using the default Deep Research provider. Defaults to enabled.',\n 'deep_research_stall_timeout_minutes': 'How long (in minutes) deep research may stall before it is abandoned. Defaults to 20 minutes. Only applies when Deep Research is enabled.',\n 'second_opinion_provider': 'Optional alternate provider used for second-opinion passes. Leave blank to \"Use default\" (the backend resolver decides). Options are limited to this project\\'s Allowed Providers, so changing Allowed Providers may reset this selection.',\n 'story_points_field': 'The Jira field key / custom field identifier (e.g., customfield_10016) the Estimator writes story points to. Only used when Update JIRA Issue is enabled; leave blank to let the Estimator auto-detect the field.',\n 'documentation_instructions': 'Sections and formatting for AI-generated documentation written on pull requests. This guides the documentation the Code Writer produces — distinct from the Code Reviewer\\'s Documentation Standards, which govern review-time documentation behavior.',\n 'ci_followup_config': 'Controls the CI follow-up step after a PR. Off (default) uses baseline poll-only behavior. When on, choose a strategy: \"poll_only\" just watches CI; \"fix_and_iterate\" runs a self-contained correction loop (instructions optional); \"custom\" uses your instructions as the complete CI follow-up instruction set (instructions required). Max Attempts is 1-10 and Max Minutes is 1-120. Instructions are limited to 8192 UTF-8 bytes (byte count, not characters).'\n};\n\n// Human-readable provider names for tooltips and labels. Keep in sync with the\n// provider <select> options and the allowed-provider checkboxes.\nconst PROVIDER_DISPLAY_NAMES = {\n openai: 'OpenAI',\n anthropic: 'Anthropic',\n gemini: 'Google Gemini',\n};\n\n// Frontend-only display labels for known MCP validation manual slugs. Unknown\n// slugs fall back to the catalog label (see getMcpDocDisplayLabel). This is a\n// display-only transform — the backend catalog label/slug are left untouched so\n// the reviewer prompt block is unaffected.\nconst MCP_DOC_DISPLAY_LABELS = {\n 'playwright-mcp': 'Playwright MCP',\n 'pwa-kit-mcp': 'PWA Kit MCP',\n};\n\n/**\n * Tooltip text explaining why the primary-provider allowed-provider checkbox is\n * locked. Tracks the currently-selected primary provider so it never goes stale.\n * @param {string} primaryProvider - the selected primary provider value\n * @returns {string}\n */\nfunction getPrimaryProviderLockTooltip(primaryProvider) {\n const displayName = PROVIDER_DISPLAY_NAMES[primaryProvider] || primaryProvider;\n return `${displayName} is locked because it is the selected primary AI provider and must remain in Allowed Providers. Select a different primary provider to unlock it.`;\n}\n\n/**\n * Apply the locked state for the primary provider's allowed-provider checkbox and\n * clear any stale locked state from the other checkboxes. The primary checkbox is\n * forced checked + disabled with an explanatory tooltip on both the input and its\n * wrapping label; non-primary checkboxes are re-enabled and have their tooltips\n * removed so a previous primary provider never keeps a stale lock tooltip.\n * @param {Iterable<HTMLInputElement>} checkboxes - the provider checkboxes\n * @param {string} primaryProvider - the selected primary provider value\n */\nfunction applyPrimaryProviderLockState(checkboxes, primaryProvider) {\n const tooltip = getPrimaryProviderLockTooltip(primaryProvider);\n Array.from(checkboxes || []).forEach(cb => {\n const label = cb.closest('.checkbox-label');\n if (cb.value === primaryProvider) {\n cb.checked = true;\n cb.disabled = true;\n cb.title = tooltip;\n if (label) label.title = tooltip;\n } else {\n cb.disabled = false;\n cb.removeAttribute('title');\n if (label) label.removeAttribute('title');\n }\n });\n}\n\n/**\n * Map a backend MCP doc to its concise display label. Known slugs use a short\n * frontend label; unknown future slugs fall back to the catalog label or slug.\n * @param {{slug:string,label?:string}} doc\n * @returns {string}\n */\nfunction getMcpDocDisplayLabel(doc) {\n if (!doc) return '';\n return MCP_DOC_DISPLAY_LABELS[doc.slug] || doc.label || doc.slug;\n}\n\n// --- Functions ---\n\n/**\n * Loads project configuration for a specific ID.\n * @param {string} projectId - The ID of the project to load.\n */\nasync function loadProjectDetail(projectId) {\n currentProjectId = projectId;\n\n showLoading();\n\n try {\n const projectData = await apiCall(`/setup/project-detail/${projectId}`, 'GET');\n\n // apiCall returns null only on the centralized 401 redirect, which already\n // shows the Session Expired modal and redirects — return quietly without a\n // second modal.\n if (!projectData) {\n currentProjectId = null;\n return;\n }\n\n if (projectDetailTitle) projectDetailTitle.textContent = `Configuration: ${projectData.repo_display_name?.trim() || projectData.repo_name || 'Project'}`;\n if (projectDetailContainer) {\n projectDetailContainer.dataset.repoName = projectData.repo_name || '';\n }\n populateProjectForms(projectData);\n if (projectDetailContainer) projectDetailContainer.classList.remove('hidden');\n setupDrawers();\n } catch (error) {\n // Non-401 load failures now throw (apiCall rethrows); own the error UI here.\n currentProjectId = null;\n console.error('Error loading project configuration:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to load project configuration.'));\n } finally {\n hideLoading();\n }\n}\n\n/**\n * Populates all project configuration forms with data.\n * @param {Object} data - Project data object from API.\n */\n/**\n * Render the MCP validation manual selector from backend-provided choices.\n *\n * The selector is fully backend-driven: there is no hardcoded slug catalog in\n * the frontend. Each available doc becomes a checkbox keyed on its slug, with a\n * concise display label as visible text (see getMcpDocDisplayLabel). The source\n * file path is intentionally not rendered. When no docs are available an\n * informational empty state is shown.\n *\n * @param {Array<{slug:string,label:string,source_file_path?:string,updated_at?:string}>} availableMcpDocs\n * @param {Array<string>} selectedMcpSlugs - currently selected slugs for this repo\n */\nfunction populateMcpDocSelector(availableMcpDocs, selectedMcpSlugs) {\n const list = document.getElementById('selected_mcp_slugs_list');\n const emptyState = document.getElementById('selected_mcp_slugs_empty');\n if (!list) return;\n\n list.textContent = '';\n const docs = Array.isArray(availableMcpDocs) ? availableMcpDocs : [];\n const selected = new Set(Array.isArray(selectedMcpSlugs) ? selectedMcpSlugs : []);\n\n if (docs.length === 0) {\n if (emptyState) emptyState.classList.remove('hidden');\n return;\n }\n if (emptyState) emptyState.classList.add('hidden');\n\n docs.forEach(doc => {\n if (!doc || !doc.slug) return;\n\n const option = document.createElement('label');\n option.className = 'mcp-doc-option';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'mcp-doc-checkbox';\n checkbox.value = doc.slug;\n checkbox.checked = selected.has(doc.slug);\n // No interaction listener is needed: the value diff (not an interaction\n // flag) decides whether selected_mcp_slugs is sent on save.\n\n const labelText = document.createElement('span');\n labelText.className = 'mcp-doc-label';\n labelText.textContent = getMcpDocDisplayLabel(doc);\n\n option.appendChild(checkbox);\n option.appendChild(labelText);\n\n list.appendChild(option);\n });\n}\n\nfunction populateProjectForms(data) {\n if (!data) return;\n\n // Project Info Form\n if (projectInfoForm) {\n projectInfoForm.elements.project_description.value = data.project_description || '';\n projectInfoForm.elements.confluence_pattern.value = data.confluence_pattern || '';\n projectInfoForm.elements.ignored_domains.value = arrayToString(data.ignored_domains || []);\n projectInfoForm.elements.provider.value = (data?.code_writer?.provider || 'openai');\n projectInfoForm.elements.deep_research_enabled.checked = data.deep_research_enabled !== undefined ? data.deep_research_enabled : true;\n if (projectInfoForm.elements.deep_research_stall_timeout_minutes) {\n projectInfoForm.elements.deep_research_stall_timeout_minutes.value = data.deep_research_stall_timeout_minutes || 20;\n }\n\n // Initialize allowed providers checkboxes\n const allowedProvidersGroup = document.getElementById('allowed_providers_group');\n if (allowedProvidersGroup) {\n const allowedProviders = data.allowed_providers || ['openai', 'anthropic', 'gemini'];\n const checkboxes = allowedProvidersGroup.querySelectorAll('.provider-checkbox');\n checkboxes.forEach(cb => {\n cb.checked = allowedProviders.includes(cb.value);\n });\n // Lock the current primary provider's checkbox (checked + disabled)\n // and add an explanatory tooltip that tracks the primary provider.\n const primaryProvider = projectInfoForm.elements.provider.value;\n applyPrimaryProviderLockState(checkboxes, primaryProvider);\n\n // Build the second-opinion options from the now-initialized allowed\n // providers, honoring the stored value when it is still allowed.\n rebuildSecondOpinionProviderOptions(data.second_opinion_provider || '');\n\n // Any Allowed Providers change re-derives the second-opinion options\n // (and resets a now-disallowed selection to blank).\n checkboxes.forEach(cb => {\n cb.addEventListener('change', () => rebuildSecondOpinionProviderOptions());\n });\n }\n\n // When primary provider changes, move the lock + tooltip to the newly\n // selected provider, clear the stale lock from the previous one, and\n // rebuild the second-opinion options (the primary is always allowed).\n projectInfoForm.elements.provider.addEventListener('change', function() {\n const allowedProvidersGroup = document.getElementById('allowed_providers_group');\n if (!allowedProvidersGroup) return;\n const checkboxes = allowedProvidersGroup.querySelectorAll('.provider-checkbox');\n applyPrimaryProviderLockState(checkboxes, this.value);\n rebuildSecondOpinionProviderOptions();\n });\n }\n\n // Estimator Form\n const estimator = data?.estimator || {};\n if (estimatorForm) {\n estimatorForm.elements.estimate_qa.checked = estimator.estimate_qa || false;\n estimatorForm.elements.process_estimate_instructions.value = estimator.process_estimate_instructions || '';\n estimatorForm.elements.estimate_instructions.value = estimator.estimate_instructions || '';\n estimatorForm.elements.update_jira.checked = estimator.update_jira || false;\n if (estimatorForm.elements.story_points_field) {\n estimatorForm.elements.story_points_field.value = estimator.story_points_field || '';\n }\n if (estimateScaleJsonTextarea) {\n let scaleJsonString = estimator.estimate_scale || '';\n if (typeof scaleJsonString === 'object') {\n try {\n scaleJsonString = JSON.stringify(scaleJsonString, null, 2);\n } catch (e) {\n console.error('Error stringifying existing estimate scale:', e);\n scaleJsonString = '';\n }\n }\n estimateScaleJsonTextarea.value = scaleJsonString;\n estimateScaleJsonTextarea.setAttribute('data-original-value', scaleJsonString);\n }\n }\n\n // Code Writer Form (platform fields moved to Setup page)\n const writer = data?.code_writer || {};\n if (codeWriterForm) {\n codeWriterForm.elements.architecture_instructions.value = writer.architecture_instructions || '';\n if (codeWriterForm.elements.tdd_document_instructions) {\n codeWriterForm.elements.tdd_document_instructions.value = writer.tdd_document_instructions || '';\n }\n if (codeWriterForm.elements.fsd_document_instructions) {\n codeWriterForm.elements.fsd_document_instructions.value = writer.fsd_document_instructions || '';\n }\n if (codeWriterForm.elements.design_principles) {\n codeWriterForm.elements.design_principles.value = writer.design_principles || '';\n }\n codeWriterForm.elements.unit_testing_stack.value = data.unit_testing_stack || '';\n codeWriterForm.elements.unit_testing_instructions.value = writer.unit_testing_instructions || '';\n codeWriterForm.elements.e2e_testing_stack.value = data.e2e_testing_stack || '';\n codeWriterForm.elements.e2e_testing_instructions.value = writer.e2e_testing_instructions || '';\n codeWriterForm.elements.review_instructions.value = writer.review_instructions || '';\n if (codeWriterForm.elements.documentation_instructions) {\n codeWriterForm.elements.documentation_instructions.value = writer.documentation_instructions || '';\n }\n if (codeWriterForm.elements.allow_mutating_smoke_ops) {\n codeWriterForm.elements.allow_mutating_smoke_ops.checked = !!writer.allow_mutating_smoke_ops;\n }\n populateMcpDocSelector(data.available_mcp_docs || [], writer.selected_mcp_slugs || []);\n // CI Follow-up: parse the raw DB text into the structured controls (OFF when null).\n populateCiFollowupConfig(writer.ci_followup_config);\n }\n\n // Correctness standards (moved to Code Writer drawer, still saved via code_reviewer payload)\n const reviewer = data?.code_reviewer || {};\n const correctnessFields = ['frontend_correctness_standards', 'backend_correctness_standards', 'template_correctness_standards', 'style_correctness_standards'];\n correctnessFields.forEach(field => {\n const el = document.getElementById(field);\n if (el) el.value = reviewer[field] || '';\n });\n\n // Code Reviewer Form (hidden drawer - still populated to preserve values on save)\n if (codeReviewForm) {\n codeReviewForm.elements.reviewable_file_types.value = arrayToString(reviewer.reviewable_file_types || []);\n codeReviewForm.elements.include_path.value = (reviewer.include_path || []).join('\\n');\n codeReviewForm.elements.exclude_path.value = (reviewer.exclude_path || []).join('\\n');\n codeReviewForm.elements.frontend_styleguide.value = reviewer.frontend_styleguide || '';\n codeReviewForm.elements.backend_styleguide.value = reviewer.backend_styleguide || '';\n codeReviewForm.elements.css_styleguide.value = reviewer.css_styleguide || '';\n codeReviewForm.elements.review_correctness.checked = reviewer.review_correctness !== undefined ? reviewer.review_correctness : true;\n codeReviewForm.elements.review_style.checked = reviewer.review_style !== undefined ? reviewer.review_style : true;\n codeReviewForm.elements.review_requirements.checked = reviewer.review_requirements !== undefined ? reviewer.review_requirements : true;\n codeReviewForm.elements.review_architecture.checked = reviewer.review_architecture !== undefined ? reviewer.review_architecture : true;\n codeReviewForm.elements.create_tests.checked = reviewer.create_tests || false;\n codeReviewForm.elements.create_documentation.checked = reviewer.create_documentation || false;\n codeReviewForm.elements.documentation_standards.value = reviewer.documentation_standards || '';\n codeReviewForm.elements.score_threshold_for_review.value = reviewer.score_threshold_for_review !== undefined ? reviewer.score_threshold_for_review : 85;\n codeReviewForm.elements.no_comment_on_passing_score.checked = reviewer.no_comment_on_passing_score !== undefined ? reviewer.no_comment_on_passing_score : true;\n }\n}\n\n/**\n * Handles saving the project configuration changes.\n */\nasync function handleSaveProject() {\n if (!saveChangesBtn) return;\n\n saveChangesBtn.disabled = true;\n saveChangesBtn.textContent = 'Saving...';\n\n // Validate all forms before saving\n if (!validateAllForms()) {\n showModal('Error', 'Please fix the errors marked in red before saving.');\n saveChangesBtn.disabled = false;\n saveChangesBtn.textContent = 'Save Configuration';\n\n const firstErrorInput = document.querySelector('.input-error');\n if (firstErrorInput) {\n const drawer = firstErrorInput.closest('.drawer');\n if (drawer) {\n // Open the containing drawer directly via the shared helper\n // (no synthetic header click, no stale `.open` check).\n setDrawerExpanded(drawer, true);\n }\n setTimeout(() => {\n firstErrorInput.scrollIntoView({ behavior: 'smooth', block: 'center' });\n firstErrorInput.focus();\n }, 150);\n\n }\n return;\n }\n\n let isValid = true;\n let estimateScaleValue = null;\n\n // Validate JSON for Estimate Scale\n if (estimateScaleJsonTextarea) {\n const rawJson = estimateScaleJsonTextarea.value.trim();\n estimateScaleJsonErrorDiv.classList.remove('visible');\n estimateScaleJsonTextarea.style.borderColor = '';\n\n if (rawJson) {\n try {\n const parsedJson = JSON.parse(rawJson);\n if (!Array.isArray(parsedJson)) {\n throw new Error('Input must be a JSON array.');\n }\n // Note: server also enforces min >= 0 and min <= max per element\n estimateScaleValue = rawJson;\n } catch (e) {\n console.error('Invalid JSON in Estimate Scale:', e);\n estimateScaleJsonErrorDiv.textContent = `Invalid JSON: ${e.message}`; \n estimateScaleJsonErrorDiv.classList.add('visible');\n estimateScaleJsonTextarea.style.borderColor = 'var(--error-color)';\n isValid = false;\n }\n } else {\n estimateScaleValue = \"\";\n }\n }\n\n if (!isValid) {\n showModal('Validation Error', 'Please fix the errors in the form before saving. Check the Estimate Scale JSON format.');\n saveChangesBtn.disabled = false;\n saveChangesBtn.textContent = 'Save Configuration';\n return; \n }\n\n // Field-granular sparse save: diff every descriptor against the baseline and\n // send only the fields whose normalized value changed. Untouched fields and\n // sections are structurally absent from the payload.\n const changedFieldEntries = getChangedFieldEntries();\n const projectData = buildSparseProjectPayload(changedFieldEntries, estimateScaleValue);\n\n try {\n const response = await apiCall(`/setup/project-detail/${currentProjectId}`, 'PUT', projectData);\n\n if (response) {\n showModal('Success', 'Project configuration saved successfully!');\n // Re-snapshot from the saved values so an immediate second save with\n // no further edits is clean.\n resetProjectDetailBaseline();\n }\n // A null response is the centralized 401 redirect (handled by apiCall);\n // every other failure throws and is handled by the catch below.\n } catch (error) {\n console.error('Error saving project configuration:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to save project configuration.'));\n } finally {\n saveChangesBtn.disabled = false;\n saveChangesBtn.textContent = 'Save Configuration';\n }\n}\n\n/**\n * Sets up the expandable drawer functionality for form sections.\n */\nfunction setupDrawers() {\n // Drawer open/close behavior is delegated to the shared helper, which\n // normalizes state, fixes the first-click bug, and ignores clicks on\n // explanation icons (so they open the explanation modal instead of toggling).\n initDrawers({\n root: document,\n drawerSelector: '#project-detail-container .drawer',\n });\n\n // Wire each drawer's explanation icon to its modal. The shared helper's\n // ignoreSelector keeps these clicks from toggling the drawer.\n document.querySelectorAll('#project-detail-container .drawer').forEach(drawer => {\n const header = drawer.querySelector('.drawer-header');\n const explanationIcon = header?.querySelector('.explanation-icon');\n if (explanationIcon) {\n explanationIcon.addEventListener('click', () => {\n const fieldId = explanationIcon.getAttribute('data-field-id');\n const title = header.querySelector('h3')?.textContent || 'Explanation';\n const message = EXPLANATION_TEXTS[fieldId] || 'No explanation available for this field.';\n showModal(title, message);\n });\n }\n });\n}\n\n/**\n * Validates a single input field based on specified rules.\n */\nfunction validateInput(element) {\n if (!element || element.type === 'hidden' || element.type === 'button' || element.type === 'submit' || element.offsetParent === null) {\n return true;\n }\n\n const errorElement = document.getElementById(`${element.id}-error`);\n let isValid = true;\n let errorMessage = '';\n\n element.setCustomValidity('');\n\n // CI Follow-up instructions: enforce the custom-required and 8192 UTF-8 byte\n // rules via custom validity so checkValidity() below surfaces them. Only when CI\n // follow-up is enabled; hidden/disabled controls are skipped by the offsetParent\n // guard above and by the disabled-element constraint-validation barring.\n if (element.id === 'ci_followup_instructions') {\n const ciEnabled = document.getElementById('ci_followup_enabled');\n if (ciEnabled && ciEnabled.checked) {\n const strategy = getSelectedCiFollowupStrategy();\n const value = element.value || '';\n if (strategy === 'custom' && value.trim() === '') {\n element.setCustomValidity('Custom CI follow-up instructions are required.');\n } else if (getUtf8ByteLength(value) > CI_FOLLOWUP_INSTRUCTIONS_MAX_BYTES) {\n element.setCustomValidity('Instructions must be at most 8192 UTF-8 bytes.');\n }\n }\n }\n\n if (!element.checkValidity()) {\n isValid = false;\n if (element.validity.valueMissing) {\n errorMessage = 'This field is required.';\n } else if (element.validity.typeMismatch) {\n if (element.type === 'url' && element.value.trim() !== '') {\n errorMessage = 'Please enter a valid URL (e.g., https://example.com).';\n } else if (element.type === 'email' && element.value.trim() !== '') {\n errorMessage = 'Please enter a valid email address.';\n } else {\n errorMessage = 'Please enter a valid value.';\n }\n } else if (element.validity.tooLong) {\n errorMessage = `Maximum length is ${element.maxLength} characters.`;\n } else if (element.validity.rangeUnderflow) {\n errorMessage = `Value must be at least ${element.min}.`;\n } else if (element.validity.rangeOverflow) {\n errorMessage = `Value cannot be more than ${element.max}.`;\n } else {\n errorMessage = element.validationMessage;\n }\n }\n\n // Specific validation for Estimate Scale JSON\n if (element.id === 'estimate_scale') {\n const jsonString = element.value.trim();\n if (jsonString !== '') {\n try {\n const parsedJson = JSON.parse(jsonString);\n if (!Array.isArray(parsedJson) || \n parsedJson.some(item => typeof item !== 'object' || item === null || !('min' in item) || !('max' in item) || !('label' in item))) {\n throw new Error('JSON must be an array of objects with min, max, and label keys.');\n }\n } catch (e) {\n isValid = false;\n errorMessage = `Invalid JSON format or structure: ${e.message}`;\n }\n }\n }\n\n // Update UI (visibility via the `.visible` class, not inline display).\n if (errorElement) {\n errorElement.textContent = errorMessage;\n if (!isValid) {\n errorElement.classList.add('visible');\n element.classList.add('input-error');\n } else {\n errorElement.classList.remove('visible');\n element.classList.remove('input-error');\n }\n } else if (!isValid) {\n element.classList.add('input-error');\n console.warn(`No error message element found for ID: ${element.id}-error`);\n } else {\n element.classList.remove('input-error');\n }\n\n return isValid;\n}\n\n/**\n * Validates all relevant input fields across the forms.\n */\nfunction validateAllForms() {\n let allValid = true;\n const fieldsToValidate = document.querySelectorAll(\n '.drawer-content input, .drawer-content textarea, .drawer-content select'\n );\n\n fieldsToValidate.forEach(field => {\n if (!validateInput(field)) {\n allValid = false;\n }\n });\n\n return allValid;\n}\n\n/**\n * Setup validation listeners\n */\nfunction setupValidationListeners() {\n const fieldsToValidate = document.querySelectorAll(\n '.drawer-content input, .drawer-content textarea, .drawer-content select'\n );\n\n fieldsToValidate.forEach(field => {\n if (field.type !== 'button' && field.type !== 'submit' && field.type !== 'hidden' && field.type !== 'checkbox' && field.type !== 'radio') {\n field.removeEventListener('blur', handleBlurValidation);\n field.addEventListener('blur', handleBlurValidation);\n }\n if (field.type !== 'checkbox' && field.type !== 'radio') {\n field.removeEventListener('input', handleInputClearError);\n field.addEventListener('input', handleInputClearError);\n }\n });\n}\n\nfunction handleBlurValidation(event) {\n validateInput(event.target);\n}\n\nfunction handleInputClearError(event) {\n const element = event.target;\n const errorElement = document.getElementById(`${element.id}-error`);\n if (element.classList.contains('input-error')) {\n element.classList.remove('input-error');\n if (errorElement) {\n errorElement.textContent = '';\n errorElement.classList.remove('visible');\n }\n }\n if (element.id === 'ci_followup_instructions') {\n updateCiFollowupInstructionsByteCounter();\n }\n}\n\n/**\n * Initialize the explain link functionality\n */\nfunction initializeExplainLinkFunctionality() {\n document.querySelectorAll('.explain-link').forEach(link => {\n link.addEventListener('click', async (event) => {\n event.preventDefault();\n const sectionKey = link.dataset.section;\n const explanationDivId = `${sectionKey}-explanation`;\n const explanationDiv = document.getElementById(explanationDivId);\n\n if (!explanationDiv) {\n console.error(`Explanation div not found for ID: ${explanationDivId}`);\n return;\n }\n\n const isHidden = explanationDiv.classList.contains('hidden');\n\n if (isHidden) {\n const explanation = EXPLANATION_TEXTS[sectionKey] || 'Explanation not found.';\n explanationDiv.textContent = explanation;\n explanationDiv.classList.remove('hidden');\n } else {\n explanationDiv.classList.add('hidden');\n }\n });\n }); \n}\n\n/**\n * Initialize the project detail page.\n */\nasync function init() {\n const currentPath = window.location.pathname;\n\n if (currentPath === '/setup/project-detail') {\n initModal();\n\n const urlParams = new URLSearchParams(window.location.search);\n const projectId = urlParams.get('projectId');\n\n if (!projectId) {\n showModal('Error', 'Project ID not found in URL.');\n return;\n }\n\n await loadProjectDetail(projectId);\n\n if (saveChangesBtn) {\n saveChangesBtn.addEventListener('click', handleSaveProject);\n }\n\n if (saveChangesBtn) {\n saveChangesBtn.textContent = 'Save Configuration';\n saveChangesBtn.style.position = 'fixed';\n saveChangesBtn.style.bottom = '20px';\n saveChangesBtn.style.left = '50%';\n saveChangesBtn.style.transform = 'translateX(-50%)';\n saveChangesBtn.style.zIndex = '1000';\n saveChangesBtn.style.padding = '10px 20px';\n saveChangesBtn.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';\n }\n\n initializeExplainLinkFunctionality();\n \n const allSaveButtons = document.querySelectorAll('button[type=\"submit\"]');\n allSaveButtons.forEach(button => {\n if (button !== saveChangesBtn) {\n button.style.display = 'none';\n }\n });\n\n if (estimateScaleJsonTextarea) {\n estimateScaleJsonTextarea.addEventListener('input', () => {\n try {\n if (estimateScaleJsonTextarea.value.trim()) {\n JSON.parse(estimateScaleJsonTextarea.value);\n }\n estimateScaleJsonErrorDiv.classList.remove('visible');\n estimateScaleJsonTextarea.style.borderColor = '';\n } catch (e) {\n estimateScaleJsonErrorDiv.textContent = 'Invalid JSON format.';\n estimateScaleJsonErrorDiv.classList.add('visible');\n estimateScaleJsonTextarea.style.borderColor = 'var(--error-color)';\n }\n });\n }\n\n setupValidationListeners();\n\n // Wire conditional visibility (deep-research timeout, story points field, CI\n // follow-up controls) and apply initial hidden/disabled state BEFORE the\n // baseline snapshot so dependent fields are not flagged permanently dirty.\n setupConditionalVisibilityListeners();\n\n // Capture the baseline only after the forms are populated, the dynamic\n // MCP checkboxes are rendered (in loadProjectDetail -> populateProjectForms),\n // the explain links are wired, the estimate-scale live-validation listener\n // is attached, the validation listeners are set up, and conditional\n // visibility has been applied — otherwise async-rendered or dependent fields\n // would appear permanently dirty.\n captureProjectDetailBaseline();\n }\n}\n\ndocument.addEventListener('DOMContentLoaded', init);\n\n// Named exports for unit testing (DOM-free where possible). Exporting does not\n// change browser behavior — the page still runs via the DOMContentLoaded hook.\nexport {\n PROJECT_DETAIL_FIELD_DESCRIPTORS,\n FIELD_TO_SECTION,\n normalizeStringArrayValue,\n getSelectedMcpSlugs,\n getAllowedProviderValues,\n captureProjectDetailBaseline,\n resetProjectDetailBaseline,\n getChangedFieldEntries,\n getPayloadValueForField,\n buildSparseProjectPayload,\n handleSaveProject,\n validateInput,\n setupValidationListeners,\n handleBlurValidation,\n handleInputClearError,\n populateMcpDocSelector,\n populateProjectForms,\n getUtf8ByteLength,\n getSelectedCiFollowupStrategy,\n getCiFollowupConfigCanonicalString,\n populateCiFollowupConfig,\n updateCiFollowupInstructionsByteCounter,\n toggleCiFollowupControlsVisibility,\n applyCiFollowupStrategyState,\n toggleDeepResearchTimeoutVisibility,\n toggleStoryPointsFieldVisibility,\n rebuildSecondOpinionProviderOptions,\n setupConditionalVisibilityListeners,\n};\n","// src/js/security.js\n/**\n * Security page module.\n *\n * Replaces the template's inline drawer-toggle implementation with the shared\n * drawer helper (BAPI-301). Only drawer behavior lives here; the page's API-key\n * management UI (create / revoke / rotate / copy) remains in the page template's\n * own script. Webpack bundles every file under src/js/** into the single\n * main.min.js, so adding this module is enough to ship it — there is no central\n * entry file to register it in.\n */\nimport { initDrawers } from './utils.js';\n\nfunction init() {\n // Guard: only run on the Security page (its root container is present).\n if (!document.getElementById('security-container')) {\n return;\n }\n initDrawers({ root: document, drawerSelector: '#security-container .drawer' });\n}\n\ndocument.addEventListener('DOMContentLoaded', init);\n","// src/js/setup-detail.js\n\nimport { initModal, showModal } from './modal.js';\nimport {\n showLoading,\n hideLoading,\n apiCall,\n getApiErrorMessage,\n captureBaseline,\n diff as diffFields,\n resetBaseline,\n initDrawers,\n setDrawerExpanded,\n} from './utils.js';\n\n// --- Constants & State ---\nlet currentProjectId = null;\nlet currentVersionControlSystem = null; // Track current VCS to detect changes\n\n// Limit for displaying affected files before showing \"and X more\"\nconst AFFECTED_FILES_DISPLAY_LIMIT = 50;\n\n// --- DOM Elements ---\nconst setupDetailContainer = document.getElementById('setup-detail-container');\nconst setupDetailTitle = document.getElementById('setup-detail-title');\nconst identityForm = document.getElementById('identity-form');\nconst jiraForm = document.getElementById('jira-form');\nconst vcsForm = document.getElementById('vcs-form');\nconst githubForm = document.getElementById('github-form');\nconst bitbucketForm = document.getElementById('bitbucket-form');\nconst pineconeForm = document.getElementById('pinecone-form');\nconst platformForm = document.getElementById('platform-form');\nconst saveSetupBtn = document.getElementById('save-setup-btn');\n\n// Platform field DOM elements\nconst versionSelect = document.getElementById('version');\nconst baseVersionGroup = document.getElementById('base_version_group');\nconst baseVersionSelect = document.getElementById('base_version');\nconst alternateVersionGroup = document.getElementById('alternate_version_group');\nconst alternateVersionSelect = document.getElementById('alternate_version');\nconst alternateVersionDirectoriesGroup = document.getElementById('alternate_version_directories_group');\nconst alternateVersionDirectoriesTextarea = document.getElementById('alternate_version_directories');\nconst pwaOverridesGroup = document.getElementById('pwa_overrides_group');\nconst pwaOverridesCheckbox = document.getElementById('pwa_overrides');\nconst pwaOverridesDirectoriesGroup = document.getElementById('pwa_overrides_directories_group');\nconst pwaOverridesDirectoriesTextarea = document.getElementById('pwa_overrides_directories');\n\n// Baseline snapshot of every Setup Details field, captured once after the load\n// handler fully populates the forms. Sparse saves diff the current DOM against\n// this snapshot via the shared BAPI-295 utility.\nlet setupBaseline = null;\n\n// Setup Details array fields (custom_directories, exclude_directories, etc.) are\n// newline-delimited in the UI. The shared diff util refuses to assume a\n// delimiter for string array values, so every array descriptor declares this.\nconst ARRAY_FIELD_DELIMITER = '\\n';\n\n// --- Explanation Texts ---\nconst EXPLANATION_TEXTS = {\n 'repo_name': 'The unique identifier for your repository. This is typically set during project creation and cannot be changed.',\n 'repo_display_name': 'A friendly display name for your repository. This is shown in the project list and can be changed at any time.',\n 'jira_ticket_key': \"The key that identifies the JIRA tickets on your project. For example, if your ticket number is 'SCRUM-1', then you should enter 'SCRUM'.\",\n 'ai_author': 'The username the AI uses for comments in Bitbucket. Should match the name given to the Bitbucket access token. Required for Bitbucket.',\n 'jira_server_url': 'The root url for your JIRA instance. For example, if the URL for your JIRA ticket was: https://bridge-test.atlassian.net/browse/BAPI-1 then the root url would be: https://bridge-test.atlassian.net.',\n 'jira_email': 'The email address associated with your JIRA account used for the API token.',\n 'jira_api_token': 'Your JIRA API token. Used for fetching issue details. See the README file for how to get this.',\n 'vcs_webhook_secret': 'A secret string used to verify webhook payloads sent from your version control system (Github & Bitbucket). Create this secret when setting up your webhook and then enter it here.',\n 'repo_id': 'Your repository ID for GitHub or Bitbucket. Required for version control integration. See the README for how to locate this.',\n 'git_app_installation_id': 'The Installation ID for the Bridge GPT GitHub App installed on your repository. Find this in your GitHub organization/repository settings under Installed GitHub Apps. See README for details.',\n 'version_control_access_token': 'A Bitbucket Access Token with Read access to repositories and pull requests. Create this in your Bitbucket account settings under Access tokens.',\n 'workspace': 'Your Bitbucket workspace ID (e.g., bitbucket.org/workspace/{workspace-id}). See the README for details.',\n 'bitbucket_app_username': 'Your Bitbucket username used for app password authentication.',\n 'bitbucket_app_password': 'Your Bitbucket app password. Create this in your Bitbucket account settings under App passwords with appropriate repository access permissions.',\n 'pinecone_index_name': 'The name of the Pinecone index you created for storing vector embeddings for this project. Refer to the README for Pinecone setup instructions.',\n 'base_branch': 'Optional: Specify a custom base branch (e.g., \"develop\") to override the VCS-reported default branch. When left blank, the system automatically uses the default branch from your VCS provider (e.g., \"main\" or \"master\").',\n 'post_pr_target_status': 'The Jira workflow status that tickets transition to after code is committed via pull request. Automatically resolved by the LLM status resolver agent if left blank. Set manually to override the auto-resolved value (e.g., \"Ready for Testing\", \"In Review\").',\n 'working_in': 'Required: enter the platform you are working in, such as \"Salesforce Commerce Cloud\", \"Scayle\", or \"Shopify\".',\n 'version': 'The specific version of Salesforce Commerce Cloud (SFCC) your project is using — SFRA, PWA Kit, Hybrid, SiteGenesis, or Storefront Next. Select \"Hybrid\" if your project combines any two distinct standalone SFCC versions in one codebase. Select \"SiteGenesis\" for legacy multi-cartridge SG projects (pipelines, ISML, Demandware Script). Select \"Storefront Next\" for the React 19 / React Router 7 / Vite SFCC storefront (TypeScript, SCAPI/SLAS, Page Designer metadata cartridges). Required.',\n 'base_version': 'For a Hybrid project, the standalone SFCC version (SFRA, PWA Kit, SiteGenesis, or Storefront Next) that encompasses the majority of your codebase.',\n 'alternate_version': 'For a Hybrid project, the second standalone SFCC version used by the alternate-version directories. Must be a different standalone SFCC version from your Base Version.',\n 'alternate_version_directories': 'Which directories hold the alternate SFCC version (the one that isn\\'t your base version)? Input partial directory paths that can identify them. Enter one directory path per line.',\n 'custom_directories': 'Specify which directories within your codebase are custom and modifiable by the AI. Enter one directory path per line (e.g., `/custom-cartridge/`). Required.',\n 'exclude_file_extensions': 'Specify file extensions to exclude from processing. Enter one extension per line (e.g., `.min.js`, `.map`). Optional.',\n 'exclude_directories': 'Specify directories to exclude from processing. Enter one directory path per line (e.g., `/node_modules/`, `/dist/`). Optional.',\n 'pwa_overrides_directories': 'Specify the directories that contain your PWA Kit template overrides. Enter one directory path per line (e.g., `overrides`, `app/templates`, `custom-overrides`).'\n};\n\n// --- Validation & Save UI Strings ---\nexport const UI_STRINGS = {\n FIELD_REQUIRED: 'This field is required.',\n INVALID_URL: 'Please enter a valid URL (e.g., https://example.com).',\n INVALID_EMAIL: 'Please enter a valid email address.',\n INVALID_VALUE: 'Please enter a valid value.',\n MAX_LENGTH: (max) => `Maximum length is ${max} characters.`,\n RANGE_UNDERFLOW: (min) => `Value must be at least ${min}.`,\n RANGE_OVERFLOW: (max) => `Value cannot be more than ${max}.`,\n BASE_VERSION_REQUIRED: 'Base Version is required when Hybrid is selected.',\n ALTERNATE_VERSION_REQUIRED: 'Alternate Version is required when Hybrid is selected.',\n BASE_ALTERNATE_DISTINCT: 'Base Version and Alternate Version must be different.',\n SAVE_VALIDATION_ERROR: 'Please fix the errors marked in red before saving.',\n // Centralized save copy (BAPI-296 sparse save).\n SAVE_IN_PROGRESS: 'Saving...',\n SAVE_BUTTON_LABEL: 'Save Setup',\n SAVE_SUCCESS: 'Setup configuration saved successfully!',\n NO_CHANGES_TITLE: 'No Changes',\n NO_CHANGES_TO_SAVE: 'No changes to save.',\n // Install status review surface (BAPI-384).\n INSTALL_STATUS_SET: 'Set by agent',\n INSTALL_STATUS_UNSET: 'Unset',\n INSTALL_STATUS_EDIT: 'Edit',\n INSTALL_STATUS_LOADING: 'Loading status…',\n INSTALL_STATUS_UNAVAILABLE: 'Install status unavailable',\n};\n\n/**\n * Page-specific FieldDescriptor list consumed by the shared BAPI-295 value-diff\n * utility (src/js/utils.js). Every Setup Details field across the seven forms\n * appears exactly once. Each descriptor's `id` matches both the DOM element id\n * and the payload key, so the util reads the live DOM through the descriptor and\n * the page no longer hand-collects values.\n *\n * Secret fields (jira_api_token, vcs_webhook_secret, version_control_access_token,\n * bitbucket_app_password) are plain `text` descriptors with no secret-specific\n * logic: the GET response populates the mask, so the baseline holds the mask and\n * an untouched secret stays equal to it (omitted from the diff), while a changed\n * secret differs and is included. Array descriptors declare the mandatory\n * newline delimiter.\n */\nexport const SETUP_FIELD_DESCRIPTORS = Object.freeze([\n // Project Identity\n { field: 'repo_name', type: 'text', id: 'repo_name' },\n { field: 'repo_display_name', type: 'text', id: 'repo_display_name' },\n { field: 'jira_ticket_key', type: 'text', id: 'jira_ticket_key' },\n // JIRA Integration\n { field: 'jira_server_url', type: 'text', id: 'jira_server_url' },\n { field: 'jira_email', type: 'text', id: 'jira_email' },\n { field: 'jira_api_token', type: 'text', id: 'jira_api_token' },\n // Version Control\n { field: 'version_control_system', type: 'text', id: 'version_control_system' },\n { field: 'vcs_webhook_secret', type: 'text', id: 'vcs_webhook_secret' },\n { field: 'repo_id', type: 'text', id: 'repo_id' },\n { field: 'base_branch', type: 'text', id: 'base_branch' },\n { field: 'post_pr_target_status', type: 'text', id: 'post_pr_target_status' },\n // GitHub Access\n { field: 'git_app_installation_id', type: 'text', id: 'git_app_installation_id' },\n // Bitbucket Access\n { field: 'ai_author', type: 'text', id: 'ai_author' },\n { field: 'version_control_access_token', type: 'text', id: 'version_control_access_token' },\n { field: 'workspace', type: 'text', id: 'workspace' },\n { field: 'bitbucket_app_username', type: 'text', id: 'bitbucket_app_username' },\n { field: 'bitbucket_app_password', type: 'text', id: 'bitbucket_app_password' },\n // Pinecone\n { field: 'pinecone_index_name', type: 'text', id: 'pinecone_index_name' },\n // Project Platform\n { field: 'working_in', type: 'text', id: 'working_in' },\n { field: 'version', type: 'text', id: 'version' },\n { field: 'base_version', type: 'text', id: 'base_version' },\n { field: 'alternate_version', type: 'text', id: 'alternate_version' },\n { field: 'pwa_overrides', type: 'boolean', id: 'pwa_overrides' },\n { field: 'alternate_version_directories', type: 'array', id: 'alternate_version_directories', delimiter: ARRAY_FIELD_DELIMITER },\n { field: 'pwa_overrides_directories', type: 'array', id: 'pwa_overrides_directories', delimiter: ARRAY_FIELD_DELIMITER },\n { field: 'custom_directories', type: 'array', id: 'custom_directories', delimiter: ARRAY_FIELD_DELIMITER },\n { field: 'exclude_file_extensions', type: 'array', id: 'exclude_file_extensions', delimiter: ARRAY_FIELD_DELIMITER },\n { field: 'exclude_directories', type: 'array', id: 'exclude_directories', delimiter: ARRAY_FIELD_DELIMITER },\n]);\n\n/**\n * Capture the Setup Details diff baseline from the fully populated DOM. Call this\n * once after the load handler finishes setting every `.value` / `.checked` and\n * after all visibility toggles run, so the snapshot reflects the hydrated forms.\n */\nexport function captureSetupBaseline() {\n setupBaseline = captureBaseline(SETUP_FIELD_DESCRIPTORS);\n}\n\n/**\n * Build a sparse PUT payload containing only the fields whose current DOM value\n * differs from the captured baseline, plus `touched_fields` listing exactly\n * those changed field names (the backend uses it to clear allowed fields).\n * Captures a baseline first if one was never taken. Returns null when nothing\n * changed so the caller can show explicit no-change UX instead of a silent PUT.\n * @returns {Object|null}\n */\nexport function buildSparseSetupPayload() {\n if (!setupBaseline) {\n captureSetupBaseline();\n }\n const changedFields = diffFields(SETUP_FIELD_DESCRIPTORS, setupBaseline) || {};\n if (Object.keys(changedFields).length === 0) {\n return null;\n }\n return { ...changedFields, touched_fields: Object.keys(changedFields) };\n}\n\n/**\n * Re-snapshot the baseline after a successful save so the next diff compares\n * against the just-saved state rather than the original load. Falls back to a\n * fresh capture if the utility returns a falsy value.\n */\nexport function resetSetupBaselineAfterSave() {\n setupBaseline = resetBaseline(SETUP_FIELD_DESCRIPTORS) || captureBaseline(SETUP_FIELD_DESCRIPTORS);\n}\n\n// --- Install Status Review (BAPI-384) ---\n\n/**\n * Flatten the install-status manifest into a Map keyed by `field_name` for O(1)\n * inline-slot lookups. Tolerates null/missing/malformed groups or fields by\n * skipping them — never throws.\n * @param {Object} status - The /setup/install-status response.\n * @returns {Map<string, Object>} field_name -> field descriptor object.\n */\nexport function flattenInstallStatusFields(status) {\n const map = new Map();\n if (!status || !Array.isArray(status.groups)) {\n return map;\n }\n status.groups.forEach((group) => {\n if (!group || !Array.isArray(group.fields)) {\n return;\n }\n group.fields.forEach((field) => {\n if (field && typeof field.field_name === 'string') {\n map.set(field.field_name, field);\n }\n });\n });\n return map;\n}\n\n/**\n * Produce a safe, human-readable display string for any manifest value. Handles\n * strings, arrays (comma-separated, stable order), plain objects (readable\n * JSON-ish, never `[object Object]`), booleans, and null/undefined/'' (empty).\n * The returned value is always assigned via textContent by callers, so no HTML\n * escaping is needed here, but the function itself never builds markup.\n * @param {*} value\n * @returns {string}\n */\nexport function formatInstallStatusValue(value) {\n if (value === null || value === undefined || value === '') {\n return '';\n }\n if (typeof value === 'boolean') {\n return value ? 'Yes' : 'No';\n }\n if (Array.isArray(value)) {\n return value\n .map((item) => formatInstallStatusValue(item))\n .filter((item) => item !== '')\n .join(', ');\n }\n if (typeof value === 'object') {\n try {\n return JSON.stringify(value);\n } catch (error) {\n return String(value);\n }\n }\n return String(value);\n}\n\n/**\n * Fill every inline badge slot with a loading badge before the endpoint returns,\n * preserving layout. Safe to call when no slots exist.\n */\nexport function setInstallStatusLoading() {\n document.querySelectorAll('[data-install-field]').forEach((slot) => {\n slot.textContent = '';\n const badge = document.createElement('span');\n badge.className = 'install-status-badge status-loading';\n badge.textContent = UI_STRINGS.INSTALL_STATUS_LOADING;\n slot.appendChild(badge);\n });\n}\n\n/**\n * Render the inline status badge inside each `[data-install-field]` slot from the\n * flattened manifest map. \"Set by agent\" + value when is_set, otherwise \"Unset\"\n * + guidance. Set/unset is read ONLY from the manifest, never inferred from the\n * current form control value. Unknown fields fall back to a calm unavailable\n * state. Never throws on a missing slot or field.\n * @param {Map<string, Object>} fieldMap\n */\nexport function renderInlineInstallStatusBadges(fieldMap) {\n const map = fieldMap instanceof Map ? fieldMap : new Map();\n document.querySelectorAll('[data-install-field]').forEach((slot) => {\n const fieldName = slot.dataset.installField;\n const field = map.get(fieldName);\n slot.textContent = '';\n\n const badge = document.createElement('span');\n if (!field) {\n badge.className = 'install-status-badge status-unavailable';\n badge.textContent = UI_STRINGS.INSTALL_STATUS_UNAVAILABLE;\n slot.appendChild(badge);\n return;\n }\n\n if (field.is_set) {\n badge.className = 'install-status-badge status-set';\n const value = formatInstallStatusValue(field.current_value);\n badge.textContent = value\n ? `${UI_STRINGS.INSTALL_STATUS_SET}: ${value}`\n : UI_STRINGS.INSTALL_STATUS_SET;\n } else {\n badge.className = 'install-status-badge status-unset';\n badge.textContent = UI_STRINGS.INSTALL_STATUS_UNSET;\n if (field.guidance) {\n badge.title = field.guidance;\n }\n }\n if (field.requires_confirmation) {\n badge.title = field.guidance\n ? `${field.guidance} (Requires confirmation)`\n : 'Requires confirmation';\n }\n slot.appendChild(badge);\n });\n}\n\n/**\n * Returns true when an editable form control for `fieldName` exists on this page.\n * Used to decide whether to show an inline \"Edit\" affordance in the panel.\n * @param {string} fieldName\n * @returns {boolean}\n */\nfunction hasInstallStatusFormControl(fieldName) {\n if (!fieldName) return false;\n const element = document.getElementById(fieldName);\n return Boolean(element && setupDetailContainer && setupDetailContainer.contains(element));\n}\n\n/**\n * Render the top-level review panel: the summary counts (read directly from\n * status.summary — never recomputed from the DOM) and one row per field across\n * every group, including fields with no inline form control. An Edit button is\n * shown only when a matching editable control exists on the page.\n * @param {Object} status - The /setup/install-status response.\n */\nexport function renderInstallStatusPanel(status) {\n const summaryEl = document.getElementById('setup-install-status-summary');\n const groupsEl = document.getElementById('setup-install-status-groups');\n const errorEl = document.getElementById('setup-install-status-error');\n\n if (errorEl) {\n errorEl.textContent = '';\n errorEl.hidden = true;\n }\n\n if (summaryEl) {\n summaryEl.textContent = '';\n const summary = (status && status.summary) || {};\n const total = summary.total ?? 0;\n const set = summary.set ?? 0;\n const unset = summary.unset ?? 0;\n const parts = [\n { label: 'Set by agent', value: set },\n { label: 'Unset', value: unset },\n { label: 'Total', value: total },\n ];\n parts.forEach((part) => {\n const span = document.createElement('span');\n span.className = 'install-status-summary-item';\n const count = document.createElement('span');\n count.className = 'install-status-summary-count';\n count.textContent = String(part.value);\n span.appendChild(count);\n span.appendChild(document.createTextNode(` ${part.label}`));\n summaryEl.appendChild(span);\n });\n }\n\n if (!groupsEl) return;\n groupsEl.textContent = '';\n\n const groups = (status && Array.isArray(status.groups)) ? status.groups : [];\n groups.forEach((group) => {\n if (!group || !Array.isArray(group.fields)) return;\n const groupEl = document.createElement('div');\n groupEl.className = 'install-status-group';\n\n const titleEl = document.createElement('div');\n titleEl.className = 'install-status-group-title';\n titleEl.textContent = group.title || group.key || '';\n groupEl.appendChild(titleEl);\n\n group.fields.forEach((field) => {\n if (!field || typeof field.field_name !== 'string') return;\n const row = document.createElement('div');\n row.className = 'install-status-field-row';\n\n const nameEl = document.createElement('span');\n nameEl.className = 'install-status-field-name';\n nameEl.textContent = field.field_name;\n row.appendChild(nameEl);\n\n const badge = document.createElement('span');\n if (field.is_set) {\n badge.className = 'install-status-badge status-set';\n badge.textContent = UI_STRINGS.INSTALL_STATUS_SET;\n } else {\n badge.className = 'install-status-badge status-unset';\n badge.textContent = UI_STRINGS.INSTALL_STATUS_UNSET;\n }\n row.appendChild(badge);\n\n const valueText = field.is_set\n ? formatInstallStatusValue(field.current_value)\n : formatInstallStatusValue(field.guidance);\n if (valueText) {\n const valueEl = document.createElement('span');\n valueEl.className = 'install-status-field-value';\n valueEl.textContent = valueText;\n row.appendChild(valueEl);\n }\n\n if (field.requires_confirmation) {\n const confirmEl = document.createElement('span');\n confirmEl.className = 'install-status-field-confirm';\n confirmEl.textContent = 'Requires confirmation';\n if (field.guidance) confirmEl.title = field.guidance;\n row.appendChild(confirmEl);\n }\n\n if (hasInstallStatusFormControl(field.field_name)) {\n const editBtn = document.createElement('button');\n editBtn.type = 'button';\n editBtn.className = 'install-status-edit';\n editBtn.textContent = UI_STRINGS.INSTALL_STATUS_EDIT;\n editBtn.addEventListener('click', () => focusInstallStatusField(field.field_name));\n row.appendChild(editBtn);\n }\n\n groupEl.appendChild(row);\n });\n\n groupsEl.appendChild(groupEl);\n });\n}\n\n/**\n * Render a calm unavailable state: unavailable badges in every inline slot, a\n * cleared panel summary/groups, and an \"Install status unavailable\" message in\n * the panel error area. Used on endpoint failure, available:false, or empty\n * groups. Never throws.\n */\nexport function renderInstallStatusUnavailable() {\n document.querySelectorAll('[data-install-field]').forEach((slot) => {\n slot.textContent = '';\n const badge = document.createElement('span');\n badge.className = 'install-status-badge status-unavailable';\n badge.textContent = UI_STRINGS.INSTALL_STATUS_UNAVAILABLE;\n slot.appendChild(badge);\n });\n\n const summaryEl = document.getElementById('setup-install-status-summary');\n if (summaryEl) summaryEl.textContent = '';\n const groupsEl = document.getElementById('setup-install-status-groups');\n if (groupsEl) groupsEl.textContent = '';\n const errorEl = document.getElementById('setup-install-status-error');\n if (errorEl) {\n errorEl.textContent = UI_STRINGS.INSTALL_STATUS_UNAVAILABLE;\n errorEl.hidden = false;\n }\n}\n\n/**\n * Focus the editable form control for `fieldName`. If it sits inside a collapsed\n * drawer, open the drawer first (shared `setDrawerExpanded` contract) before\n * focusing. A missing control is a no-op — never throws. Deeper/non-bootstrap\n * fields remain editable exactly as today.\n * @param {string} fieldName\n */\nexport function focusInstallStatusField(fieldName) {\n if (!fieldName) return;\n const element = document.getElementById(fieldName);\n if (!element) return;\n\n const drawer = element.closest('.drawer');\n if (drawer) {\n setDrawerExpanded(drawer, true);\n }\n // Defer focus so the drawer-expand DOM/class change settles first.\n setTimeout(() => {\n try {\n element.scrollIntoView({ behavior: 'smooth', block: 'center' });\n element.focus();\n } catch (error) {\n // Focus failures are non-fatal.\n }\n }, 150);\n}\n\n/**\n * Fetch the install status for a project via the existing session-authed\n * endpoint and render the panel + inline badges. On any failure, available:false,\n * or an empty/unusable manifest, render the calm unavailable state instead of\n * propagating the error (form loading must not depend on status).\n * @param {string} projectId\n */\nexport async function loadInstallStatus(projectId) {\n try {\n const status = await apiCall(\n `/setup/install-status?projectId=${encodeURIComponent(projectId)}`,\n 'GET'\n );\n // A null response is the centralized 401 redirect handled by apiCall.\n if (!status) {\n return;\n }\n const hasGroups = Array.isArray(status.groups) && status.groups.length > 0;\n if (status.available === false || !hasGroups) {\n renderInstallStatusUnavailable();\n return;\n }\n renderInstallStatusPanel(status);\n renderInlineInstallStatusBadges(flattenInstallStatusFields(status));\n } catch (error) {\n console.error('Error loading install status:', error);\n renderInstallStatusUnavailable();\n }\n}\n\n// --- Functions ---\n\n/**\n * Shows a confirmation modal with Confirm/Cancel buttons.\n * @param {string} title - The modal title.\n * @param {string} message - The modal message.\n * @param {Function} onConfirm - Callback function when user confirms.\n * @param {Function} onCancel - Callback function when user cancels.\n */\nfunction showConfirmationModal(title, message, onConfirm, onCancel) {\n const modal = document.getElementById('confirmation-modal');\n const modalTitle = document.getElementById('confirmation-modal-title');\n const modalMessage = document.getElementById('confirmation-modal-message');\n const confirmBtn = document.getElementById('confirmation-modal-confirm');\n const cancelBtn = document.getElementById('confirmation-modal-cancel');\n \n if (!modal || !modalTitle || !modalMessage || !confirmBtn || !cancelBtn) {\n console.error('[ERROR] Confirmation modal elements not found');\n return;\n }\n \n // Set title and message\n modalTitle.textContent = title;\n modalMessage.textContent = message;\n \n // Remove any existing event listeners by cloning and replacing\n const newConfirmBtn = confirmBtn.cloneNode(true);\n const newCancelBtn = cancelBtn.cloneNode(true);\n confirmBtn.parentNode.replaceChild(newConfirmBtn, confirmBtn);\n cancelBtn.parentNode.replaceChild(newCancelBtn, cancelBtn);\n \n // Add new event listeners\n newCancelBtn.addEventListener('click', () => {\n modal.style.display = 'none';\n if (onCancel) onCancel();\n });\n \n newConfirmBtn.addEventListener('click', () => {\n modal.style.display = 'none';\n if (onConfirm) onConfirm();\n });\n \n // Show modal\n modal.style.display = 'flex';\n}\n\n/**\n * Shows/hides drawers based on version control system.\n * @param {string} versionControlSystem - The version control system ('github' or 'bitbucket').\n */\nfunction handleVersionControlVisibility(versionControlSystem) {\n const githubDrawer = document.querySelector('.drawer:has(#github-form)');\n const bitbucketDrawer = document.querySelector('.drawer:has(#bitbucket-form)');\n \n if (versionControlSystem === 'github') {\n // Show GitHub, hide Bitbucket\n if (githubDrawer) {\n githubDrawer.classList.remove('hidden');\n }\n if (bitbucketDrawer) {\n bitbucketDrawer.classList.add('hidden');\n }\n } else if (versionControlSystem === 'bitbucket') {\n // Show Bitbucket, hide GitHub\n if (githubDrawer) {\n githubDrawer.classList.add('hidden');\n }\n if (bitbucketDrawer) {\n bitbucketDrawer.classList.remove('hidden');\n }\n } else {\n // If not set or unknown value, show both\n if (githubDrawer) {\n githubDrawer.classList.remove('hidden');\n }\n if (bitbucketDrawer) {\n bitbucketDrawer.classList.remove('hidden');\n }\n }\n}\n\n/**\n * Toggles visibility of Hybrid version fields based on version selection.\n */\nfunction toggleHybridFields() {\n if (versionSelect && baseVersionGroup && alternateVersionDirectoriesGroup) {\n const isHybrid = versionSelect.value === 'hybrid';\n if (isHybrid) {\n baseVersionGroup.classList.remove('hidden');\n if (alternateVersionGroup) alternateVersionGroup.classList.remove('hidden');\n alternateVersionDirectoriesGroup.classList.remove('hidden');\n } else {\n baseVersionGroup.classList.add('hidden');\n if (alternateVersionGroup) alternateVersionGroup.classList.add('hidden');\n alternateVersionDirectoriesGroup.classList.add('hidden');\n }\n }\n}\n\n/**\n * Returns true when PWA Kit override controls are relevant for the current\n * version selection: standalone PWA Kit, or a Hybrid pair where either the base\n * or alternate version is PWA Kit.\n */\nfunction isPwaOverrideRelevant() {\n const version = versionSelect ? versionSelect.value : '';\n if (version === 'pwakit') return true;\n if (version === 'hybrid') {\n const base = baseVersionSelect ? baseVersionSelect.value : '';\n const alternate = alternateVersionSelect ? alternateVersionSelect.value : '';\n return base === 'pwakit' || alternate === 'pwakit';\n }\n return false;\n}\n\n/**\n * Toggles visibility of PWA overrides group based on version selection.\n */\nfunction togglePwaOverridesVisibility() {\n if (versionSelect && pwaOverridesGroup) {\n if (isPwaOverrideRelevant()) {\n pwaOverridesGroup.classList.remove('hidden');\n togglePwaOverridesDirectoriesVisibility();\n } else {\n pwaOverridesGroup.classList.add('hidden');\n if (pwaOverridesDirectoriesGroup) {\n pwaOverridesDirectoriesGroup.classList.add('hidden');\n }\n }\n }\n}\n\n/**\n * Toggles visibility of PWA overrides directories based on checkbox state.\n */\nfunction togglePwaOverridesDirectoriesVisibility() {\n if (pwaOverridesCheckbox && pwaOverridesDirectoriesGroup) {\n const isChecked = pwaOverridesCheckbox.checked;\n\n if (isPwaOverrideRelevant() && isChecked) {\n pwaOverridesDirectoriesGroup.classList.remove('hidden');\n } else {\n pwaOverridesDirectoriesGroup.classList.add('hidden');\n }\n }\n}\n\n/**\n * Loads setup details for a specific project ID.\n * @param {string} projectId - The ID of the project to load.\n */\nasync function loadSetupDetail(projectId) {\n currentProjectId = projectId;\n\n showLoading();\n\n // Paint loading badges in every inline slot before any request returns so the\n // status surface preserves layout (additive — independent of form load).\n setInstallStatusLoading();\n\n try {\n const setupData = await apiCall(`/setup/setup-detail/${projectId}`, 'GET');\n\n // apiCall returns null only on the centralized 401 redirect, which already\n // shows the Session Expired modal and redirects — return quietly without a\n // second modal.\n if (!setupData) {\n currentProjectId = null;\n return;\n }\n\n if (setupDetailTitle) {\n setupDetailTitle.textContent = `Setup: ${setupData.repo_display_name?.trim() || setupData.repo_name || 'Project'}`;\n }\n populateSetupForms(setupData);\n if (setupDetailContainer) {\n setupDetailContainer.classList.remove('hidden');\n }\n\n setupDrawers();\n\n // Request install status additively. Its own try/catch renders the calm\n // unavailable state on failure, so a status error never fails the page or\n // overwrites the successfully loaded forms. Not awaited so it cannot block\n // or break the form-load flow.\n loadInstallStatus(projectId);\n } catch (error) {\n // Non-401 load failures now throw (apiCall rethrows); own the error UI here.\n currentProjectId = null;\n console.error('Error loading setup details:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to load setup details.'));\n } finally {\n hideLoading();\n }\n}\n\n/**\n * Populates all setup configuration forms with data.\n * @param {Object} data - Setup data object from API.\n */\nexport function populateSetupForms(data) {\n if (!data) return;\n\n // Identity Form\n if (identityForm) {\n const repoNameInput = identityForm.elements.repo_name;\n if (repoNameInput) {\n repoNameInput.value = data.repo_name || '';\n }\n const repoDisplayNameInput = identityForm.elements.repo_display_name;\n if (repoDisplayNameInput) {\n repoDisplayNameInput.value = data.repo_display_name || '';\n }\n const jiraTicketKeyInput = identityForm.elements.jira_ticket_key;\n if (jiraTicketKeyInput) {\n jiraTicketKeyInput.value = data.jira_ticket_key || '';\n }\n }\n\n // JIRA Form\n if (jiraForm) {\n jiraForm.elements.jira_server_url.value = data.jira_server_url || '';\n jiraForm.elements.jira_email.value = data.jira_email || '';\n jiraForm.elements.jira_api_token.value = data.jira_api_token || '';\n }\n\n // VCS Form\n if (vcsForm) {\n const vcsSelect = vcsForm.elements.version_control_system;\n if (vcsSelect) {\n // Set '' when unset so the blank \"Agent will detect\" option shows\n // rather than a false default. currentVersionControlSystem mirrors it.\n vcsSelect.value = data.version_control_system || '';\n currentVersionControlSystem = data.version_control_system || null;\n }\n vcsForm.elements.vcs_webhook_secret.value = data.vcs_webhook_secret || '';\n vcsForm.elements.repo_id.value = data.repo_id || '';\n const baseBranchInput = document.getElementById('base_branch');\n if (baseBranchInput) {\n baseBranchInput.value = data.base_branch || '';\n }\n const postPrTargetStatusInput = document.getElementById('post_pr_target_status');\n if (postPrTargetStatusInput) {\n postPrTargetStatusInput.value = data.post_pr_target_status || '';\n }\n }\n\n // GitHub Form\n if (githubForm) {\n githubForm.elements.git_app_installation_id.value = data.git_app_installation_id || '';\n }\n\n // Bitbucket Form\n if (bitbucketForm) {\n bitbucketForm.elements.ai_author.value = data.ai_author || '';\n bitbucketForm.elements.version_control_access_token.value = data.version_control_access_token || '';\n bitbucketForm.elements.workspace.value = data.workspace || '';\n bitbucketForm.elements.bitbucket_app_username.value = data.bitbucket_app_username || '';\n bitbucketForm.elements.bitbucket_app_password.value = data.bitbucket_app_password || '';\n }\n\n // Handle conditional drawer visibility based on version_control_system\n handleVersionControlVisibility(data.version_control_system);\n\n // Pinecone Form\n if (pineconeForm) {\n pineconeForm.elements.pinecone_index_name.value = data.pinecone_index_name || '';\n }\n\n // Platform Form\n if (platformForm) {\n // working_in / version default to '' (blank \"Agent will detect\" option)\n // when the backend value is null/empty, so a missing value never shows as\n // a false default selection.\n const workingInSelect = platformForm.elements.working_in;\n if (workingInSelect) workingInSelect.value = data.working_in || '';\n if (versionSelect) versionSelect.value = data.version || '';\n if (baseVersionSelect) baseVersionSelect.value = data.base_version || '';\n if (alternateVersionSelect) alternateVersionSelect.value = data.alternate_version || '';\n if (alternateVersionDirectoriesTextarea) {\n alternateVersionDirectoriesTextarea.value = (data.alternate_version_directories || []).join('\\n');\n }\n if (pwaOverridesCheckbox) pwaOverridesCheckbox.checked = data.pwa_overrides || false;\n if (pwaOverridesDirectoriesTextarea) {\n pwaOverridesDirectoriesTextarea.value = (data.pwa_overrides_directories || []).join('\\n');\n }\n const customDirs = document.getElementById('custom_directories');\n if (customDirs) customDirs.value = (data.custom_directories || []).join('\\n');\n const excludeExts = document.getElementById('exclude_file_extensions');\n if (excludeExts) excludeExts.value = (data.exclude_file_extensions || []).join('\\n');\n const excludeDirs = document.getElementById('exclude_directories');\n if (excludeDirs) excludeDirs.value = (data.exclude_directories || []).join('\\n');\n\n // Toggle visibility of version-dependent fields\n toggleHybridFields();\n togglePwaOverridesVisibility();\n }\n\n // Capture the diff baseline now that every field value, checked state, and\n // visibility toggle has been applied. Sparse saves compare against this.\n captureSetupBaseline();\n}\n\n/** Platform form groups hidden when SFCC version/PWA options do not apply. */\nconst CONDITIONAL_VALIDATION_SKIP_GROUPS =\n '#base_version_group, #alternate_version_group, #alternate_version_directories_group, #pwa_overrides_group, #pwa_overrides_directories_group';\n\n/**\n * Returns true when a field should be excluded from client-side validation.\n * Drawer collapse (`.drawer-content.hidden`, BAPI-301) is intentionally NOT\n * skipped — invalid required fields must still block save even when their\n * section is collapsed. Only VCS-inapplicable drawers and conditionally hidden\n * platform groups are exempt.\n * @param {HTMLElement} element\n * @returns {boolean}\n */\nfunction shouldSkipValidation(element) {\n if (element.closest('.drawer.hidden')) {\n return true;\n }\n const conditionalGroup = element.closest(CONDITIONAL_VALIDATION_SKIP_GROUPS);\n if (conditionalGroup && conditionalGroup.classList.contains('hidden')) {\n return true;\n }\n return false;\n}\n\n/**\n * Validates a single input element using the Constraint Validation API.\n * @param {HTMLElement} element - The form element to validate.\n * @returns {boolean} True if valid, false otherwise.\n */\nfunction validateInput(element) {\n if (!element || element.type === 'hidden' || element.type === 'button' || element.type === 'submit' || element.readOnly) {\n return true;\n }\n if (shouldSkipValidation(element)) {\n return true;\n }\n\n const errorElement = document.getElementById(`${element.id}-error`);\n let isValid = true;\n let errorMessage = '';\n\n element.setCustomValidity('');\n if (!element.checkValidity()) {\n isValid = false;\n if (element.validity.valueMissing) {\n errorMessage = UI_STRINGS.FIELD_REQUIRED;\n } else if (element.validity.typeMismatch) {\n if (element.type === 'url' && element.value.trim() !== '') {\n errorMessage = UI_STRINGS.INVALID_URL;\n } else if (element.type === 'email' && element.value.trim() !== '') {\n errorMessage = UI_STRINGS.INVALID_EMAIL;\n } else {\n errorMessage = UI_STRINGS.INVALID_VALUE;\n }\n } else if (element.validity.tooLong) {\n errorMessage = UI_STRINGS.MAX_LENGTH(element.maxLength);\n } else if (element.validity.rangeUnderflow) {\n errorMessage = UI_STRINGS.RANGE_UNDERFLOW(element.min);\n } else if (element.validity.rangeOverflow) {\n errorMessage = UI_STRINGS.RANGE_OVERFLOW(element.max);\n } else {\n errorMessage = element.validationMessage;\n }\n }\n\n // Custom validation for hybrid base/alternate version pair.\n const isHybrid = versionSelect && versionSelect.value === 'hybrid';\n if (isHybrid && element.id === 'base_version' && !element.value) {\n isValid = false;\n errorMessage = UI_STRINGS.BASE_VERSION_REQUIRED;\n }\n if (isHybrid && element.id === 'alternate_version') {\n const baseValue = baseVersionSelect ? baseVersionSelect.value : '';\n if (!element.value) {\n isValid = false;\n errorMessage = UI_STRINGS.ALTERNATE_VERSION_REQUIRED;\n } else if (baseValue && element.value === baseValue) {\n isValid = false;\n errorMessage = UI_STRINGS.BASE_ALTERNATE_DISTINCT;\n }\n }\n\n // Update UI using CSS class toggling\n if (errorElement) {\n errorElement.textContent = errorMessage;\n if (!isValid) {\n errorElement.classList.add('visible');\n element.classList.add('input-error');\n } else {\n errorElement.classList.remove('visible');\n element.classList.remove('input-error');\n }\n } else if (!isValid) {\n element.classList.add('input-error');\n console.warn(`No error message element found for ID: ${element.id}-error`);\n } else {\n element.classList.remove('input-error');\n }\n\n return isValid;\n}\n\n/**\n * Validates all relevant input fields across the forms.\n * @returns {boolean} True if all fields are valid, false otherwise.\n */\nfunction validateAllForms() {\n let allValid = true;\n const fieldsToValidate = document.querySelectorAll(\n '.drawer-content input, .drawer-content textarea, .drawer-content select'\n );\n\n fieldsToValidate.forEach(field => {\n if (!validateInput(field)) {\n allValid = false;\n }\n });\n\n return allValid;\n}\n\n/**\n * Handles blur event to trigger validation on the blurred element.\n * @param {Event} event - The blur event.\n */\nfunction handleBlurValidation(event) {\n validateInput(event.target);\n}\n\n/**\n * Clears validation error state when the user starts typing or changes a value.\n * @param {Event} event - The input or change event.\n */\nfunction handleInputClearError(event) {\n const element = event.target;\n const errorElement = document.getElementById(`${element.id}-error`);\n if (element.classList.contains('input-error')) {\n element.classList.remove('input-error');\n if (errorElement) {\n errorElement.textContent = '';\n errorElement.classList.remove('visible');\n }\n }\n}\n\n/**\n * Sets up blur validation and input/change error clearing listeners on all form fields.\n */\nfunction setupValidationListeners() {\n const fieldsToValidate = document.querySelectorAll(\n '.drawer-content input, .drawer-content textarea, .drawer-content select'\n );\n\n fieldsToValidate.forEach(field => {\n if (field.type !== 'button' && field.type !== 'submit' && field.type !== 'hidden' && field.type !== 'checkbox' && field.type !== 'radio') {\n field.removeEventListener('blur', handleBlurValidation);\n field.addEventListener('blur', handleBlurValidation);\n }\n if (field.type !== 'checkbox' && field.type !== 'radio') {\n // Use 'change' for select elements, 'input' for text inputs/textareas\n if (field.tagName === 'SELECT') {\n field.removeEventListener('change', handleInputClearError);\n field.addEventListener('change', handleInputClearError);\n } else {\n field.removeEventListener('input', handleInputClearError);\n field.addEventListener('input', handleInputClearError);\n }\n }\n });\n}\n\n/**\n * Handles saving the setup configuration changes.\n */\nexport async function handleSaveSetup() {\n if (!saveSetupBtn) return;\n\n // Validate all forms before saving (first save gate).\n if (!validateAllForms()) {\n const scrollToFirstError = () => {\n const firstErrorInput = document.querySelector('.input-error');\n if (firstErrorInput) {\n const drawer = firstErrorInput.closest('.drawer');\n if (drawer) {\n // Open the containing drawer directly via the shared helper\n // rather than synthesizing a header click.\n setDrawerExpanded(drawer, true);\n }\n setTimeout(() => {\n firstErrorInput.scrollIntoView({ behavior: 'smooth', block: 'center' });\n firstErrorInput.focus();\n }, 150);\n }\n };\n showModal('Error', UI_STRINGS.SAVE_VALIDATION_ERROR, null, false, scrollToFirstError);\n return;\n }\n\n // Build a sparse payload of only the changed fields (diffed against the\n // baseline captured at load). Unchanged keys — including unchanged masked\n // secrets — are structurally absent. Changed-to-blank values are kept so the\n // backend can clear the allowlisted code-repository fields via touched_fields.\n const setupData = buildSparseSetupPayload();\n if (!setupData) {\n // Explicit no-change UX instead of a silent no-op that looks like a save.\n showModal(UI_STRINGS.NO_CHANGES_TITLE, UI_STRINGS.NO_CHANGES_TO_SAVE);\n return;\n }\n\n // Loading state wraps only the actual PUT request.\n saveSetupBtn.disabled = true;\n saveSetupBtn.textContent = UI_STRINGS.SAVE_IN_PROGRESS;\n\n try {\n const response = await apiCall(`/setup/setup-detail/${currentProjectId}`, 'PUT', setupData);\n\n if (response) {\n showModal('Success', UI_STRINGS.SAVE_SUCCESS);\n // Reset the baseline only after a confirmed success so the next diff\n // compares against the just-saved state.\n resetSetupBaselineAfterSave();\n // Refresh the install-status surface so badges/panel reflect the\n // just-saved values. Not awaited; failure renders the unavailable\n // state and never disrupts the save-success UX.\n if (currentProjectId) {\n loadInstallStatus(currentProjectId);\n }\n }\n // A null response is the centralized 401 redirect (handled by apiCall);\n // every other failure throws and is handled by the catch below.\n } catch (error) {\n console.error('Error saving setup configuration:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to save setup configuration.'));\n } finally {\n saveSetupBtn.disabled = false;\n saveSetupBtn.textContent = UI_STRINGS.SAVE_BUTTON_LABEL;\n }\n}\n\n/**\n * Sets up the expandable drawer functionality for form sections.\n */\nfunction setupDrawers() {\n // Delegate to the shared helper, which normalizes state, fixes the\n // first-click bug, and (via the default ignoreSelector) leaves `.explain-link`\n // clicks to the explain-link handler instead of toggling the drawer.\n initDrawers({\n root: document,\n drawerSelector: '#setup-detail-container .drawer',\n });\n}\n\n/**\n * Initializes the explain link functionality.\n */\nfunction initializeExplainLinks() {\n document.querySelectorAll('.explain-link').forEach(link => {\n link.addEventListener('click', async (event) => {\n event.preventDefault();\n event.stopPropagation();\n \n const sectionKey = link.dataset.section;\n const explanationDivId = `${sectionKey}-explanation`;\n const explanationDiv = document.getElementById(explanationDivId);\n\n if (!explanationDiv) {\n console.error(`Explanation div not found for ID: ${explanationDivId}`);\n return;\n }\n\n const isHidden = explanationDiv.classList.contains('hidden');\n\n if (isHidden) {\n const explanation = EXPLANATION_TEXTS[sectionKey] || 'Explanation not found.';\n explanationDiv.textContent = explanation;\n explanationDiv.classList.remove('hidden');\n } else {\n explanationDiv.classList.add('hidden');\n }\n });\n });\n}\n\n/**\n * Initialize the setup detail page.\n */\nexport async function init() {\n const currentPath = window.location.pathname;\n\n if (currentPath === '/setup/setup-detail') {\n // Initialize modal system first\n initModal();\n\n // Get project ID from URL query parameter\n const urlParams = new URLSearchParams(window.location.search);\n const projectId = urlParams.get('projectId');\n\n if (!projectId) {\n showModal('Error', 'Project ID not found in URL.');\n return;\n }\n\n // Load setup details\n await loadSetupDetail(projectId);\n\n // Add event listener for save button\n if (saveSetupBtn) {\n saveSetupBtn.addEventListener('click', handleSaveSetup);\n }\n \n // Add event listener for version control system changes\n const vcsSelect = vcsForm?.elements.version_control_system;\n \n if (vcsSelect) {\n vcsSelect.addEventListener('change', (e) => {\n const newValue = e.target.value;\n const previousValue = currentVersionControlSystem;\n \n // If value actually changed and there was a previous value, show confirmation\n if (previousValue && newValue !== previousValue) {\n // Immediately revert the select to previous value\n e.target.value = previousValue;\n \n showConfirmationModal(\n 'Confirm Version Control System Change',\n 'Warning: Changing this setting will break the system until the appropriate version control system settings are configured. Are you sure you want to proceed?',\n () => {\n // User confirmed - apply the change\n e.target.value = newValue;\n currentVersionControlSystem = newValue;\n handleVersionControlVisibility(newValue);\n },\n () => {\n // User cancelled - value already reverted, nothing to do\n }\n );\n } else {\n // No previous value or same value - just apply\n currentVersionControlSystem = newValue;\n handleVersionControlVisibility(newValue);\n }\n });\n }\n\n // Set up platform field visibility toggles\n if (versionSelect) {\n versionSelect.addEventListener('change', () => {\n toggleHybridFields();\n togglePwaOverridesVisibility();\n });\n }\n // Base/alternate selects affect both hybrid validation (distinctness)\n // and PWA override visibility (either side may be PWA Kit).\n if (baseVersionSelect) {\n baseVersionSelect.addEventListener('change', () => {\n togglePwaOverridesVisibility();\n if (alternateVersionSelect) validateInput(alternateVersionSelect);\n });\n }\n if (alternateVersionSelect) {\n alternateVersionSelect.addEventListener('change', () => {\n togglePwaOverridesVisibility();\n validateInput(alternateVersionSelect);\n });\n }\n if (pwaOverridesCheckbox) {\n pwaOverridesCheckbox.addEventListener('change', togglePwaOverridesDirectoriesVisibility);\n }\n\n // Set up validation listeners for blur validation and error clearing\n setupValidationListeners();\n\n // Initialize explain link functionality\n initializeExplainLinks();\n\n // Style the save button to be fixed at bottom\n if (saveSetupBtn) {\n saveSetupBtn.style.position = 'fixed';\n saveSetupBtn.style.bottom = '20px';\n saveSetupBtn.style.left = '50%';\n saveSetupBtn.style.transform = 'translateX(-50%)';\n saveSetupBtn.style.zIndex = '1000';\n saveSetupBtn.style.padding = '10px 20px';\n saveSetupBtn.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';\n }\n }\n}\n\n// Initialize the page when DOM is fully loaded\ndocument.addEventListener('DOMContentLoaded', init);\n\n\n\n\n\n\n\n\n\n","/**\n * Setup page JavaScript\n * Handles authentication, project management, and configuration updates\n */\nimport { checkAuthentication } from './auth.js';\nimport { initModal, showModal } from './modal.js';\nimport { showLoading, hideLoading, apiCall, getApiErrorMessage } from './utils.js';\nimport { showDeleteModal, deleteProjectById } from './delete-modal.js';\n\n// Module-scoped variables for DOM elements needed across functions\nlet createProjectForm;\nlet projectsContainer, createProjectContainer;\n\n// ==========================================\n// Project Management (Setup Page Only)\n// ==========================================\n\n/**\n * Loads the list of projects\n */\nasync function loadProjects() {\n showLoading();\n\n try {\n const projects = await apiCall('/setup/projects/', 'GET');\n // apiCall returns null only on the centralized 401 redirect; nothing to render.\n if (projects) {\n renderProjectsList(projects);\n }\n } catch (error) {\n console.error('Error loading projects:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to load projects.'));\n } finally {\n hideLoading();\n }\n}\n\n/**\n * Renders the list of projects in the projects grid\n * @param {Array} projects - List of projects\n */\nfunction renderProjectsList(projects) {\n const projectsList = document.getElementById('projects-list');\n projectsList.innerHTML = ''; // Clear existing projects\n \n if (projects.length === 0) {\n projectsList.innerHTML = '<p>No projects found. Create one!</p>';\n return;\n }\n \n projects.forEach(project => {\n const displayName = project.repo_display_name || project.repo_name;\n const projectCard = document.createElement('div');\n projectCard.className = 'project-card';\n projectCard.setAttribute('data-project-id', project.id);\n projectCard.setAttribute('data-project-name', project.repo_name);\n projectCard.innerHTML = `\n <h3>${displayName}</h3>\n <p>${project.project_description || 'No description provided.'}</p>\n <div class=\"project-card-actions\">\n <div class=\"project-card-actions-row\">\n <button class=\"btn btn-secondary btn-setup\" data-project-id=\"${project.id}\">Setup</button>\n <button class=\"btn btn-secondary btn-config\" data-project-id=\"${project.id}\">Configure</button>\n <button class=\"btn btn-secondary btn-optimize\" data-project-id=\"${project.id}\">Optimize</button>\n <button class=\"btn btn-secondary btn-health\">Data</button>\n <button class=\"btn btn-secondary btn-monitoring\">Monitoring</button>\n <button class=\"btn btn-secondary btn-security\" data-project-id=\"${project.id}\">Security</button>\n <button class=\"btn btn-secondary btn-connect-editor\" data-project-id=\"${project.id}\">Connect editor</button>\n </div>\n <div class=\"project-card-actions-row project-card-actions-secondary\">\n <a href=\"#\" class=\"delete-link\" data-project-id=\"${project.id}\">Delete</a>\n </div>\n </div>\n `;\n \n // Add event listener for the setup button\n const setupButton = projectCard.querySelector('.btn-setup');\n if (setupButton) {\n setupButton.addEventListener('click', () => {\n window.location.href = `/setup/setup-detail?projectId=${project.id}`;\n });\n }\n \n // Add event listener for the config button\n const configButton = projectCard.querySelector('.btn-config');\n if (configButton) {\n configButton.addEventListener('click', () => {\n window.location.href = `/setup/project-detail?projectId=${project.id}`;\n });\n }\n \n // Add event listener for the optimize button\n const optimizeButton = projectCard.querySelector('.btn-optimize');\n if (optimizeButton) {\n optimizeButton.addEventListener('click', () => {\n window.location.href = `/optimize/${project.id}`;\n });\n }\n \n // Add event listener for the data health button\n const healthButton = projectCard.querySelector('.btn-health');\n if (healthButton) {\n healthButton.addEventListener('click', () => {\n window.location.href = `/setup/data/${project.id}`;\n });\n }\n \n // Add event listener for the monitoring button\n const monitoringButton = projectCard.querySelector('.btn-monitoring');\n if (monitoringButton) {\n monitoringButton.addEventListener('click', () => {\n window.location.href = `/setup/monitoring/${project.id}`;\n });\n }\n \n // Add event listener for the security button\n const securityButton = projectCard.querySelector('.btn-security');\n if (securityButton) {\n securityButton.addEventListener('click', () => {\n window.location.href = `/setup/security/${project.id}`;\n });\n }\n\n // Add event listener for the connect-editor button\n const connectEditorButton = projectCard.querySelector('.btn-connect-editor');\n if (connectEditorButton) {\n connectEditorButton.addEventListener('click', () => {\n window.location.href = `/setup/get-started?projectId=${project.id}`;\n });\n }\n\n // Add event listener for the delete link\n const deleteLink = projectCard.querySelector('.delete-link');\n if (deleteLink) {\n deleteLink.addEventListener('click', (e) => {\n e.preventDefault();\n const projectName = project.repo_name;\n if (projectName && typeof projectName === 'string' && projectName.trim() !== '') {\n showDeleteModal(projectName, async () => {\n // Call API to delete the project\n const result = await deleteProjectById(project.id);\n if (result) {\n // Remove the project card from the DOM\n const card = document.querySelector(`.project-card[data-project-id=\"${project.id}\"]`);\n if (card) {\n card.remove();\n }\n } else {\n showModal('Error', 'Failed to delete project.');\n }\n }, () => {\n // Cancelled\n });\n } else {\n showModal('Error', 'Could not delete your project. Try deleting from the database directly.');\n }\n });\n }\n \n projectsList.appendChild(projectCard);\n });\n}\n\n/**\n * Handles create project form submission\n * @param {Event} e - Form submit event\n */\nasync function handleCreateProject(e) {\n e.preventDefault();\n \n const formData = new FormData(createProjectForm);\n const projectData = {\n repo_name: formData.get('repo_name')\n };\n\n // Include the display name only when the user provided one.\n const repoDisplayName = (formData.get('repo_display_name') || '').trim();\n if (repoDisplayName) {\n projectData.repo_display_name = repoDisplayName;\n }\n\n // Validate required fields\n if (!projectData.repo_name) {\n showModal('Missing Fields', 'Please fill in all required fields.');\n return;\n }\n\n // Validate repo_name: only alphanumeric and underscores\n if (\n typeof projectData.repo_name !== 'string' ||\n !/^[a-zA-Z0-9_-]+$/.test(projectData.repo_name)\n ) {\n showModal('Invalid Repository Name', 'Repository name must contain only letters, numbers, underscores, and dashes (no spaces or other special characters).');\n return;\n }\n \n showLoading();\n\n try {\n const response = await apiCall('/setup/', 'POST', projectData);\n\n if (response && response.id) {\n createProjectForm.reset(); // Clear the form\n // Redirect to the onboarding page for the new project\n window.location.href = `/setup/get-started?projectId=${response.id}`;\n }\n // A non-error response without an id (or the null centralized-401 return)\n // means creation did not complete — surface nothing extra here.\n } catch (error) {\n console.error('Failed to create project:', error);\n showModal('Error', getApiErrorMessage(error, 'Failed to create project.'));\n } finally {\n hideLoading();\n }\n}\n\n// ==========================================\n// Event Listeners\n// ==========================================\n// Main setup page elements\nfunction addEventListeners() {\n let projectsList, loadingOverlay, \n createProjectBtn, backToProjectsBtn;\n\n // Initialize elements - Assign to module-scoped vars\n projectsContainer = document.getElementById('projects-container');\n createProjectContainer = document.getElementById('create-project-container'); \n projectsList = document.getElementById('projects-list');\n loadingOverlay = document.getElementById('loading-overlay');\n\n // Forms - Assign to module-scoped variable\n createProjectForm = document.getElementById('create-project-form');\n\n // Buttons\n createProjectBtn = document.getElementById('create-project-btn');\n backToProjectsBtn = document.getElementById('back-to-projects-btn');\n\n // Project Management (setup page only)\n // Add listener to show create form and hide project list\n if (createProjectBtn && projectsContainer && createProjectContainer) {\n createProjectBtn.addEventListener('click', () => {\n projectsContainer.classList.add('hidden');\n createProjectContainer.classList.remove('hidden');\n });\n }\n \n // Add listener for the 'Back to Projects' button\n if (backToProjectsBtn && projectsContainer && createProjectContainer) {\n backToProjectsBtn.addEventListener('click', () => {\n createProjectContainer.classList.add('hidden');\n projectsContainer.classList.remove('hidden');\n });\n }\n \n createProjectForm.addEventListener('submit', handleCreateProject);\n\n // Explain Link Functionality\n document.querySelectorAll('.explain-link').forEach(link => {\n link.addEventListener('click', async (event) => {\n event.preventDefault(); // Prevent default anchor behavior\n const sectionKey = link.dataset.section;\n const explanationDivId = `${sectionKey}-explanation`;\n const explanationDiv = document.getElementById(explanationDivId);\n\n if (!explanationDiv) {\n console.error(`Explanation div not found for ID: ${explanationDivId}`);\n return;\n }\n\n // Toggle visibility\n const isHidden = explanationDiv.classList.contains('hidden');\n\n if (isHidden) {\n // Get text from local constant\n const explanation = EXPLANATION_TEXTS[sectionKey] || 'Explanation not found.';\n explanationDiv.textContent = explanation; // Use textContent for security\n \n explanationDiv.classList.remove('hidden'); // Show the div\n explanationDiv.style.display = 'block'; // Explicitly set display\n } else {\n explanationDiv.classList.add('hidden'); // Hide the div\n explanationDiv.style.display = 'none'; // Explicitly set display\n }\n });\n }); \n}\n\n// ==========================================\n// Initialization\n// ==========================================\n\n/**\n * Initialize the page\n */\nasync function init() {\n const currentPath = window.location.pathname;\n\n if (currentPath === '/setup') {\n // Check authentication status - this might redirect\n // Await the check to ensure it completes before hiding the loader\n await checkAuthentication();\n\n // Initialize modal listeners using the imported function\n initModal(); \n\n // Setup page specific initialization\n loadProjects(); // Attempt to load projects (will redirect if not authenticated)\n \n addEventListeners();\n }\n}\n\n// Initialize the page when DOM is fully loaded\ndocument.addEventListener('DOMContentLoaded', init);\n","/**\n * Utility functions shared across the application.\n */\n\nexport const TOKEN_STORAGE_KEY = 'bridge_setup_token'; \n// Import modal functions needed by apiCall for error handling/session expiry\nimport { showModal } from './modal.js'; \n\n// DOM Elements needed by loading functions\nconst pageLoadingOverlay = document.getElementById('loading-overlay');\n\n/**\n * Shows the loading overlay\n */\nexport function showLoading() {\n if (pageLoadingOverlay) pageLoadingOverlay.classList.remove('hidden');\n else console.warn(\"Loading overlay element not found.\");\n}\n\n/**\n * Hides the loading overlay\n */\nexport function hideLoading() {\n if (pageLoadingOverlay) pageLoadingOverlay.classList.add('hidden');\n else console.warn(\"Loading overlay element not found.\");\n}\n\n\n/**\n * Structured error thrown by {@link apiCall} on an HTTP failure (`!response.ok`).\n * Extends `Error` — so `error.message` and {@link getApiErrorMessage} keep\n * working for every caller — and additionally carries the HTTP `status`, the\n * parsed response `data`, and the extracted `detail`, so status-aware callers\n * (e.g. Code Analysis branching on 400/409) can react without re-parsing.\n */\nexport class ApiCallError extends Error {\n /**\n * @param {string} message - Human-readable message (already formatted).\n * @param {Object} [info]\n * @param {number} [info.status] - HTTP status code of the failed response.\n * @param {*} [info.data] - Parsed JSON response body.\n * @param {*} [info.detail] - The response `detail` field (string or array).\n */\n constructor(message, { status, data, detail } = {}) {\n super(message);\n this.name = 'ApiCallError';\n this.status = status;\n this.data = data;\n this.detail = detail;\n }\n}\n\n/**\n * Format a non-OK response body into a readable message, formatting Pydantic 422\n * validation-error arrays the same way the original implementation did.\n * @param {*} data - Parsed JSON response body.\n * @param {number} status - HTTP status code.\n * @returns {string}\n */\nfunction formatApiErrorMessage(data, status) {\n if (Array.isArray(data?.detail)) {\n const firstErr = data.detail[0];\n const field = firstErr?.loc?.[firstErr.loc.length - 1] || 'unknown field';\n let message = `Validation error on '${field}': ${firstErr?.msg || 'invalid value'}`;\n if (data.detail.length > 1) {\n message += ` (and ${data.detail.length - 1} more error${data.detail.length > 2 ? 's' : ''})`;\n }\n return message;\n }\n return data?.detail || `HTTP error! status: ${status}`;\n}\n\n/**\n * Makes an API call with authentication and centralized session handling.\n *\n * Error contract (BAPI-306): a successful response returns the parsed JSON data.\n * A 401 on any URL except `/auth/token` is handled centrally — it shows the\n * single `'Session Expired'` modal, clears the stored token, redirects to\n * `/setup/login`, and returns `null` without throwing. Every other failure\n * (`!response.ok`, a JSON parse error, or a transport/network error) THROWS;\n * this function shows no generic modal, so callers own the error UI and surface\n * it via their own `try/catch` (use {@link getApiErrorMessage}). HTTP failures\n * throw a structured {@link ApiCallError} carrying `status`/`data`/`detail`\n * (and an `error.message` that preserves the API `detail`, the Pydantic-422\n * formatting, and the `HTTP error! status: <code>` fallback).\n *\n * The optional `options` object (BAPI-307) carries:\n * - `headers` — caller headers merged over the defaults (Content-Type / Bearer).\n * - `signal` — an `AbortSignal` forwarded to `fetch`, so pollers can cancel.\n *\n * @param {string} url - API endpoint URL.\n * @param {string} [method='GET'] - HTTP method (GET, POST, PUT, DELETE).\n * @param {Object} [body=null] - Request body for POST/PUT/DELETE requests.\n * @param {Object} [options={}] - Optional `{ headers, signal }`.\n * @returns {Promise<Object|null>} - Parsed data on success; `null` on the\n * centralized 401 redirect. Throws on all other failures.\n */\nexport async function apiCall(url, method = 'GET', body = null, options = {}) {\n const {\n headers: callerHeaders = null,\n signal = undefined,\n } = options || {};\n\n const token = localStorage.getItem(TOKEN_STORAGE_KEY);\n\n const headers = {\n 'Content-Type': 'application/json'\n };\n\n // Check if token exists before adding Authorization header\n if (token) {\n headers['Authorization'] = `Bearer ${token}`;\n }\n\n // Merge caller-supplied headers last so they can override defaults (but the\n // auth/session behavior below is driven by the response, not these headers).\n if (callerHeaders) {\n Object.assign(headers, callerHeaders);\n }\n\n try {\n const fetchOptions = {\n method: method,\n headers: headers,\n credentials: 'include' // Ensure cookies (like session) are sent\n };\n\n if (signal) {\n fetchOptions.signal = signal;\n }\n\n if (body && (method === 'POST' || method === 'PUT' || method === 'DELETE')) {\n fetchOptions.body = JSON.stringify(body);\n }\n\n const response = await fetch(url, fetchOptions);\n\n // Immediately check for 401 Unauthorized before parsing JSON. This\n // session/auth handling is centralized and always runs — it is never\n // bypassed by the rethrow contract below.\n if (response.status === 401 && url !== '/auth/token') { // Don't redirect on the token endpoint itself\n // Use the imported showModal\n showModal('Session Expired', 'Your session has expired. Please log in again.');\n localStorage.removeItem(TOKEN_STORAGE_KEY); // Clear potentially invalid token\n // Redirect to login - adjust path if necessary\n window.location.href = '/setup/login'; // Assuming login is at /setup/login\n return null; // Stop processing\n }\n\n // Attempt to parse JSON, handle potential errors if response isn't JSON\n let data;\n try {\n // Handle potential empty responses (e.g., 204 No Content)\n if (response.status === 204 || response.headers.get('content-length') === '0') {\n data = {}; // Treat as success with no data\n } else {\n data = await response.json();\n }\n } catch (e) {\n // If JSON parsing fails, throw error\n throw new Error(`Failed to parse JSON response. Status: ${response.status}`);\n }\n\n\n if (!response.ok) {\n // Rethrow on every non-401 failure (BAPI-306). The error is a\n // structured ApiCallError carrying status/data/detail so status-aware\n // callers can branch; it extends Error so getApiErrorMessage() and\n // error.message keep working for everyone else.\n throw new ApiCallError(formatApiErrorMessage(data, response.status), {\n status: response.status,\n data,\n detail: data?.detail,\n });\n }\n\n // Handle explicit redirects from backend (e.g., after successful login/logout)\n if (data && data.redirect) {\n // Don't redirect immediately, let the calling function handle it\n // This allows the calling function to access response data before redirect\n return data;\n }\n\n return data; // Return the parsed JSON data (may be null for endpoints that return null)\n } catch (error) {\n // Log for diagnostics, then rethrow so the caller owns the error UI. The\n // centralized 401 path returns above without throwing, so it never reaches\n // here — this catch only sees !response.ok, JSON-parse, and transport\n // errors, all of which callers surface via their own try/catch.\n console.error(`API call error to ${url}:`, error);\n // Rethrow so the caller owns the error UI (BAPI-306). HTTP failures are\n // already an ApiCallError; parse/transport errors pass through as-is.\n throw error instanceof Error\n ? error\n : new Error(error?.message || 'An error occurred while communicating with the server.');\n }\n}\n\n/**\n * Resolve a user-facing message from a thrown apiCall() error (BAPI-306).\n *\n * apiCall() rethrows on HTTP failure with error.message preserving the API\n * `detail` (and Pydantic-422 formatting). When the server returned no detail,\n * error.message is the generic `HTTP error! status: <code>` sentinel — which is\n * truthy, so a plain `error.message || 'fallback'` would leak that dev-facing\n * string to users. This helper detects the sentinel (and empty messages) and\n * prefers the page-owned fallback, otherwise returns the real detail message.\n * Centralized here so every page shares the same fallback contract.\n * @param {Error} error - The error thrown by apiCall().\n * @param {string} fallback - Page-specific fallback message.\n * @returns {string} The detail message when present, otherwise the fallback.\n */\nexport function getApiErrorMessage(error, fallback) {\n const message = error && error.message ? String(error.message) : '';\n if (!message || /^HTTP error! status: \\d+$/.test(message)) {\n return fallback;\n }\n return message;\n}\n\n/**\n * Formats a comma-separated string into an array of trimmed strings.\n * @param {string} str - The comma-separated input string.\n * @returns {Array<string>} - An array of strings, empty if input is invalid/empty.\n */\nexport function stringToArray(str) {\n if (!str || typeof str !== 'string' || str.trim() === '') return [];\n return str.split(',').map(item => item.trim()).filter(item => item !== ''); // Filter out empty strings\n}\n\n/**\n * Formats an array of strings into a comma-separated string.\n * @param {Array<string>} arr - The array of strings.\n * @returns {string} - A comma-separated string, empty if input is invalid/empty.\n */\nexport function arrayToString(arr) {\n if (!arr || !Array.isArray(arr) || arr.length === 0) return '';\n return arr.join(', ');\n}\n\n/* -------------------------------------------------------------------------- *\n * Shared config-save value-diff utility (BAPI-295)\n *\n * A small, data-driven, page-agnostic diff utility. It knows nothing about\n * Jira / Bitbucket / Pinecone, any specific form, or any specific field name —\n * each consuming page owns its own descriptor list and special cases. The pure\n * normalize/compare logic (normalizeDescriptorValue, diffSnapshots, and the\n * private equality helper) is DOM-free so it can run outside a browser; only\n * captureBaseline / diff / resetBaseline touch the DOM, via the single\n * readDescriptorRawValue seam.\n *\n * @typedef {Object} FieldDescriptor\n * @property {string} [field] - Payload field name (preferred). Falls back to `name`.\n * @property {string} [name] - Legacy payload field name used when `field` is absent.\n * @property {'text'|'array'|'boolean'} type - Value type controlling normalization.\n * @property {HTMLElement|{value?:*,checked?:*}} [element] - Pre-resolved DOM element.\n * @property {string} [selector] - CSS selector resolved via `document.querySelector`.\n * @property {string} [id] - Element id resolved via `document.getElementById`.\n * @property {function(FieldDescriptor):*} [getValue] - Highest-priority value reader.\n * @property {function(FieldDescriptor):*} [read] - Value reader used when `getValue` is absent.\n * @property {function(*, FieldDescriptor):Array<string>} [normalizer] - Custom array normalizer.\n * @property {string} [delimiter] - Delimiter used to split string array values.\n * @property {boolean} [orderInsensitive] - Compare arrays as sets (sorted copies).\n * Requires string elements: the set comparison sorts with JavaScript's default\n * (lexicographic) comparator, which is correct for the string arrays this util\n * normalizes to. Numeric arrays are not ordered numerically — string-coerce\n * them via a `normalizer` if needed.\n * -------------------------------------------------------------------------- */\n\n/**\n * Resolve the payload field name for a descriptor.\n *\n * Prefers `descriptor.field`, then falls back to `descriptor.name`. Throws when\n * neither is present so a malformed descriptor fails fast rather than producing\n * an `undefined`-keyed payload.\n * @param {FieldDescriptor} descriptor\n * @returns {string}\n */\nfunction getDescriptorFieldName(descriptor) {\n const fieldName = descriptor && (descriptor.field || descriptor.name);\n if (!fieldName) {\n throw new Error('Descriptor is missing a field name: provide `field` or `name`.');\n }\n return fieldName;\n}\n\n/**\n * Normalize a single raw field value based only on descriptor metadata and the\n * passed value. DOM-free and page-agnostic — safe to run without a browser.\n *\n * - `text`: nullish becomes `''`, otherwise coerce to string and trim.\n * - `boolean`: returns `true` only for the strict boolean `true`; everything\n * else (including missing/undefined) becomes `false`.\n * - `array`: a `descriptor.normalizer(rawValue, descriptor)` wins when supplied;\n * array raw values are trimmed/filtered directly; string raw values require an\n * explicit `descriptor.delimiter` (comma behavior is never implicitly assumed).\n *\n * @param {FieldDescriptor} descriptor\n * @param {*} rawValue\n * @returns {string|boolean|Array<string>}\n */\nexport function normalizeDescriptorValue(descriptor, rawValue) {\n const type = descriptor && descriptor.type;\n switch (type) {\n case 'text':\n if (rawValue === null || rawValue === undefined) return '';\n return String(rawValue).trim();\n case 'boolean':\n // Strict: only a real boolean `true` counts as checked, so missing\n // or coerced values normalize to `false`.\n return rawValue === true;\n case 'array':\n return normalizeArrayValue(descriptor, rawValue);\n default: {\n const fieldName = (descriptor && (descriptor.field || descriptor.name)) || 'unknown';\n throw new Error(`Unsupported descriptor type '${type}' for field '${fieldName}'.`);\n }\n }\n}\n\n/**\n * Normalize an `array`-typed descriptor value. Private to the module — callers\n * use {@link normalizeDescriptorValue}.\n * @param {FieldDescriptor} descriptor\n * @param {*} rawValue\n * @returns {Array<string>}\n */\nfunction normalizeArrayValue(descriptor, rawValue) {\n // A custom normalizer wins so a page can choose exactly how its array field\n // is parsed; it receives both the raw value and the descriptor.\n if (typeof descriptor.normalizer === 'function') {\n return descriptor.normalizer(rawValue, descriptor);\n }\n // Nullish becomes an empty array so sparse-save consumers never crash on a\n // missing DOM hook or absent value.\n if (rawValue === null || rawValue === undefined) return [];\n // Array raw values (e.g. checkbox-group readers) are normalized directly:\n // trim string items and drop blank/nullish entries.\n if (Array.isArray(rawValue)) {\n return rawValue\n .map(item => (typeof item === 'string' ? item.trim() : item))\n .filter(item => item !== '' && item !== null && item !== undefined);\n }\n if (typeof rawValue === 'string') {\n // String array values require an explicit delimiter; the util refuses to\n // assume comma vs newline so each page chooses deliberately.\n if (typeof descriptor.delimiter === 'string') {\n return rawValue\n .split(descriptor.delimiter)\n .map(item => item.trim())\n .filter(item => item !== '');\n }\n const fieldName = (descriptor.field || descriptor.name) || 'unknown';\n throw new Error(\n `Array descriptor '${fieldName}' received a string value but defines neither `\n + `a 'normalizer' nor a 'delimiter'; choose one explicitly.`\n );\n }\n const fieldName = (descriptor.field || descriptor.name) || 'unknown';\n throw new Error(\n `Array descriptor '${fieldName}' received an unsupported value of type '${typeof rawValue}'.`\n );\n}\n\n/**\n * Pure equality for two already-normalized descriptor values. Scalars compare\n * with strict equality; arrays compare element-by-element. When\n * `descriptor.orderInsensitive === true`, arrays compare sorted copies so member\n * order never registers as a change — without mutating either input.\n * @param {FieldDescriptor} descriptor\n * @param {*} a\n * @param {*} b\n * @returns {boolean}\n */\nfunction areDescriptorValuesEqual(descriptor, a, b) {\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n let left = a;\n let right = b;\n if (descriptor && descriptor.orderInsensitive === true) {\n // slice() so the normalized arrays returned in payloads keep their\n // original order; only these throwaway copies are sorted. The default\n // lexicographic comparator is sufficient here because both arrays are\n // sorted identically, so equal string multisets compare equal.\n left = a.slice().sort();\n right = b.slice().sort();\n }\n for (let i = 0; i < left.length; i++) {\n if (left[i] !== right[i]) return false;\n }\n return true;\n }\n return a === b;\n}\n\n/**\n * Read a descriptor's current raw value from its configured source. This is the\n * module's single DOM-touching seam. Resolution order:\n * `getValue` → `read` → `element` → `selector` → `id`. Missing boolean elements\n * resolve to `false` and missing text/array elements to `''`, so sparse-save\n * consumers never crash on an absent DOM hook.\n * @param {FieldDescriptor} descriptor\n * @returns {*}\n */\nfunction readDescriptorRawValue(descriptor) {\n if (typeof descriptor.getValue === 'function') {\n return descriptor.getValue(descriptor);\n }\n if (typeof descriptor.read === 'function') {\n return descriptor.read(descriptor);\n }\n let element = descriptor.element || null;\n if (!element && descriptor.selector && typeof document !== 'undefined') {\n element = document.querySelector(descriptor.selector);\n }\n if (!element && descriptor.id && typeof document !== 'undefined') {\n element = document.getElementById(descriptor.id);\n }\n if (descriptor.type === 'boolean') {\n return element ? element.checked : false;\n }\n return element ? element.value : '';\n}\n\n/**\n * Read each descriptor's current value, normalize it, and return a snapshot\n * object keyed by descriptor field name. This is the DOM-reading entry point;\n * the pure normalize/diff logic stays DOM-free.\n *\n * Masked-secret behavior (relied on by consuming pages): a secret field's GET\n * response populates a mask (e.g. `••••••••`). If the user leaves it untouched,\n * baseline and current both equal the mask, so {@link diffSnapshots} omits it;\n * if the user edits it, the new value differs from the mask and is included.\n * The util needs no secret-field list for this to work.\n * @param {Array<FieldDescriptor>} descriptors\n * @returns {Object<string, *>} Normalized snapshot keyed by field name.\n */\nexport function captureBaseline(descriptors) {\n const snapshot = {};\n for (const descriptor of descriptors) {\n const fieldName = getDescriptorFieldName(descriptor);\n const rawValue = readDescriptorRawValue(descriptor);\n snapshot[fieldName] = normalizeDescriptorValue(descriptor, rawValue);\n }\n return snapshot;\n}\n\n/**\n * Normalize a snapshot value for diffing. Snapshots produced by\n * captureBaseline/resetBaseline are already normalized, so an array value for an\n * array descriptor is passed through unchanged. This avoids invoking a custom\n * `descriptor.normalizer` a second time on its own (already-normalized) array\n * output — a normalizer such as `(raw) => raw.split('|')` assumes a string and\n * would throw on an array during the baseline-replay path. Raw string values\n * (e.g. a delimiter-joined snapshot or a value read straight from a form) are\n * still normalized normally.\n * @param {FieldDescriptor} descriptor\n * @param {*} value\n * @returns {*}\n */\nfunction normalizeSnapshotValue(descriptor, value) {\n if (descriptor && descriptor.type === 'array' && Array.isArray(value)) {\n return value;\n }\n return normalizeDescriptorValue(descriptor, value);\n}\n\n/**\n * DOM-free. Return an object containing only the fields whose normalized current\n * snapshot value differs from the normalized baseline value. Snapshot values are\n * normalized via {@link normalizeSnapshotValue}: already-normalized array values\n * are passed through (so custom normalizers are never re-applied to their own\n * output), while raw string/scalar values are normalized through the descriptor.\n * A field changed and then reverted to its baseline value is omitted (reverted\n * edits are clean). Suitable for pure normalization/comparison usage outside a\n * browser.\n * @param {Array<FieldDescriptor>} descriptors\n * @param {Object<string, *>} baselineSnapshot\n * @param {Object<string, *>} currentSnapshot\n * @returns {Object<string, *>} Changed fields keyed by name, normalized values.\n */\nexport function diffSnapshots(descriptors, baselineSnapshot, currentSnapshot) {\n const changed = {};\n const baseline = baselineSnapshot || {};\n const current = currentSnapshot || {};\n for (const descriptor of descriptors) {\n const fieldName = getDescriptorFieldName(descriptor);\n const baselineValue = normalizeSnapshotValue(descriptor, baseline[fieldName]);\n const currentValue = normalizeSnapshotValue(descriptor, current[fieldName]);\n if (!areDescriptorValuesEqual(descriptor, baselineValue, currentValue)) {\n changed[fieldName] = currentValue;\n }\n }\n return changed;\n}\n\n/**\n * Capture the current normalized snapshot from the DOM and diff it against a\n * previously captured baseline snapshot. Returns only changed fields.\n * @param {Array<FieldDescriptor>} descriptors\n * @param {Object<string, *>} baseline - A snapshot from a prior captureBaseline/resetBaseline.\n * @returns {Object<string, *>} Changed fields keyed by name, normalized values.\n */\nexport function diff(descriptors, baseline) {\n const current = captureBaseline(descriptors);\n return diffSnapshots(descriptors, baseline, current);\n}\n\n/**\n * Re-snapshot the current normalized values after a successful save so a\n * subsequent diff compares against the just-saved state rather than the\n * original baseline.\n * @param {Array<FieldDescriptor>} descriptors\n * @returns {Object<string, *>} Fresh normalized baseline snapshot.\n */\nexport function resetBaseline(descriptors) {\n return captureBaseline(descriptors);\n}\n\n/* -------------------------------------------------------------------------- *\n * Shared drawer utility (BAPI-301)\n *\n * One implementation of collapsible-drawer behavior for every Setup/admin page\n * (Project Detail, Setup Detail, Data, Optimize, Security, Monitoring), replacing\n * six duplicated toggles that disagreed about open-state conventions.\n *\n * The contract is intentionally simple and consistent:\n * - OPEN: drawer has `.expanded`, `.drawer-content` lacks `.hidden`,\n * toggle glyph is `▲`, header `aria-expanded=\"true\"`.\n * - CLOSED: drawer lacks `.expanded`,`.drawer-content` has `.hidden`,\n * toggle glyph is `▼`, header `aria-expanded=\"false\"`.\n *\n * Visibility is driven solely by the global `.hidden` class — never inline\n * `style.display` — and `.expanded` is reserved for glyph/header styling. On\n * initialization each drawer is normalized so the class, glyph, ARIA, and real\n * content visibility all agree, which is the fix for the \"first click doesn't\n * collapse a visibly-open drawer\" bug: clicks toggle the REAL current state\n * (derived from content visibility) rather than blindly flipping a stale class.\n * -------------------------------------------------------------------------- */\n\nconst DRAWER_DEFAULTS = {\n // `root` defaults to `document` at call time so callers can scope a drawer\n // init to a subtree (or, in tests, a fake document).\n root: null,\n drawerSelector: '.drawer',\n headerSelector: '.drawer-header',\n contentSelector: '.drawer-content',\n toggleSelector: '.toggle-drawer, .drawer-toggle',\n expandedClass: 'expanded',\n hiddenClass: 'hidden',\n expandedGlyph: '▲',\n collapsedGlyph: '▼',\n // Interactive elements inside a header that must NOT toggle the drawer\n // (e.g. explain links / explanation icons, or any opt-out marker).\n ignoreSelector: '.explain-link, .explanation-icon, [data-drawer-ignore]',\n // Fallback open-state used only when a drawer has no `.drawer-content` to\n // derive visibility from.\n defaultExpanded: false,\n};\n\n/**\n * Merge caller options over the drawer defaults and resolve `root` to the live\n * document when not supplied.\n * @param {Object} options\n * @returns {Object} Fully resolved option set.\n */\nfunction resolveDrawerOptions(options) {\n const merged = { ...DRAWER_DEFAULTS, ...(options || {}) };\n if (!merged.root) {\n merged.root = (typeof document !== 'undefined') ? document : null;\n }\n return merged;\n}\n\n/**\n * Set a single drawer to the given expanded/collapsed state, synchronizing the\n * `.expanded` class, `.hidden` on the content, the toggle glyph, and the\n * header's `aria-expanded`. Only classes/glyph/ARIA are touched — never inline\n * `style.display` — so visibility stays under the global `.hidden` utility.\n * @param {Element} drawer - The drawer element.\n * @param {boolean} expanded - Desired open (true) / closed (false) state.\n * @param {Object} [options] - Same selector/glyph/class options as initDrawers.\n */\nexport function setDrawerExpanded(drawer, expanded, options = {}) {\n if (!drawer) return;\n const opts = resolveDrawerOptions(options);\n const header = drawer.querySelector(opts.headerSelector);\n const content = drawer.querySelector(opts.contentSelector);\n const toggle = drawer.querySelector(opts.toggleSelector);\n\n if (expanded) {\n drawer.classList.add(opts.expandedClass);\n if (content) content.classList.remove(opts.hiddenClass);\n if (toggle) toggle.textContent = opts.expandedGlyph;\n } else {\n drawer.classList.remove(opts.expandedClass);\n if (content) content.classList.add(opts.hiddenClass);\n if (toggle) toggle.textContent = opts.collapsedGlyph;\n }\n\n if (header) {\n header.setAttribute('aria-expanded', expanded ? 'true' : 'false');\n }\n}\n\n/**\n * Derive a drawer's true open state. Content visibility (`.hidden`) is the\n * source of truth so a drawer that is visually open but missing `.expanded`\n * normalizes to open, and one that is class-expanded but content-hidden\n * normalizes to closed. When there is no content element to inspect, fall back\n * to `defaultExpanded`.\n * @param {Element} drawer\n * @param {Element|null} content\n * @param {Object} opts\n * @returns {boolean}\n */\nfunction deriveDrawerExpandedState(drawer, content, opts) {\n if (!content) {\n return !!opts.defaultExpanded;\n }\n return !content.classList.contains(opts.hiddenClass);\n}\n\n/**\n * Initialize (and normalize) every drawer under `options.root`.\n *\n * On each call every matched drawer is re-normalized so its class, glyph, ARIA,\n * and content visibility agree with the derived true state. The click listener\n * is attached only once per drawer (guarded by `data-drawer-initialized`), so\n * calling this again after a re-render re-normalizes without double-binding.\n * Header clicks toggle the real current state (visibility-derived), which is the\n * first-click bug fix; clicks originating inside `ignoreSelector` are ignored.\n *\n * @param {Object} [options]\n * @param {Document|Element} [options.root=document]\n * @param {string} [options.drawerSelector='.drawer']\n * @param {string} [options.headerSelector='.drawer-header']\n * @param {string} [options.contentSelector='.drawer-content']\n * @param {string} [options.toggleSelector='.toggle-drawer, .drawer-toggle']\n * @param {string} [options.expandedClass='expanded']\n * @param {string} [options.hiddenClass='hidden']\n * @param {string} [options.expandedGlyph='▲']\n * @param {string} [options.collapsedGlyph='▼']\n * @param {string} [options.ignoreSelector]\n * @param {boolean} [options.defaultExpanded=false]\n */\nexport function initDrawers(options = {}) {\n const opts = resolveDrawerOptions(options);\n if (!opts.root || typeof opts.root.querySelectorAll !== 'function') {\n return;\n }\n\n const drawers = opts.root.querySelectorAll(opts.drawerSelector);\n drawers.forEach((drawer) => {\n const content = drawer.querySelector(opts.contentSelector);\n\n // Normalize on every call so a re-render can re-sync state cheaply.\n setDrawerExpanded(drawer, deriveDrawerExpandedState(drawer, content, opts), opts);\n\n // Attach the toggle listener exactly once per drawer.\n if (drawer.dataset && drawer.dataset.drawerInitialized === 'true') {\n return;\n }\n\n const header = drawer.querySelector(opts.headerSelector);\n if (!header) {\n return;\n }\n\n header.addEventListener('click', (event) => {\n // Ignore opt-out targets (explain links/icons) inside the header.\n const target = event && event.target;\n if (target && opts.ignoreSelector && typeof target.closest === 'function'\n && target.closest(opts.ignoreSelector)) {\n return;\n }\n\n // Toggle based on the REAL current state, derived from content\n // visibility rather than a possibly-stale class.\n const currentContent = drawer.querySelector(opts.contentSelector);\n const isOpen = currentContent\n ? !currentContent.classList.contains(opts.hiddenClass)\n : drawer.classList.contains(opts.expandedClass);\n setDrawerExpanded(drawer, !isOpen, opts);\n });\n\n if (drawer.dataset) {\n drawer.dataset.drawerInitialized = 'true';\n }\n });\n}","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\n__webpack_require__(723);\n__webpack_require__(923);\n__webpack_require__(589);\n__webpack_require__(118);\n__webpack_require__(889);\n__webpack_require__(60);\n__webpack_require__(307);\n__webpack_require__(470);\n__webpack_require__(579);\n__webpack_require__(527);\n__webpack_require__(523);\n__webpack_require__(302);\n__webpack_require__(567);\n__webpack_require__(221);\n__webpack_require__(246);\n__webpack_require__(455);\n__webpack_require__(995);\n__webpack_require__(87);\n__webpack_require__(225);\n__webpack_require__(575);\n__webpack_require__(328);\n__webpack_require__(436);\n__webpack_require__(619);\n__webpack_require__(764);\n__webpack_require__(941);\n__webpack_require__(310);\n__webpack_require__(883);\n__webpack_require__(743);\nvar __webpack_exports__ = __webpack_require__(406);\n"],"names":["async","handleLogin","e","preventDefault","clientId","document","getElementById","value","authToken","response","client_id","auth_token","error","console","session_token","localStorage","setItem","redirect","window","location","href","logout","removeItem","checkAuthentication","log","authenticated","currentProjectId","currentRepoName","dataHealthContainer","getProjectId","dataset","projectId","getRepoName","repoName","_populateTimezoneSelectInto","timezoneSelect","timezones","Intl","supportedValuesOf","innerHTML","includes","utcOption","createElement","textContent","appendChild","forEach","timezone","option","userTimezone","DateTimeFormat","resolvedOptions","timeZone","LEARN_REPO_ANALYSIS_TYPES","Object","freeze","_setLearnRepoFormError","testid","message","el","querySelector","classList","remove","add","_setParseRepoFormError","_renderLearnRepoMessage","text","msg","handleScheduleLearnRepository","id","checkedTypes","filter","t","checked","intervalWeeks","parseInt","monthsBack","prLimitRaw","invalid","length","Number","isFinite","prLimit","String","trim","parsed","intervalDays","btn","disabled","repo_name","analysis_types","interval_days","months_back","limit","recommended_hour","recommendedHour","loadLearnRepoScheduledJobs","renderLearnRepositoryScheduleRow","container","job","row","className","jobType","job_type","repoNameEl","types","Array","isArray","join","meta","weeks","intervalText","limitText","lastRun","last_run_at","status","statusValue","last_status","paused","scheduler_missing","next","next_run_time","last_diff_summary","diffWrapper","parent","diffSummary","entries","analysisType","summaryText","wrapper","summary","content","actions","resumeBtn","type","addEventListener","handleResumeLearnRepositorySchedule","pauseBtn","handlePauseLearnRepositorySchedule","deleteBtn","handleDeleteLearnRepositorySchedule","data","jobs","handleUpdateRepository","updateBtn","handleScheduleParseRepository","intervalInput","scheduleBtn","interval","nextRun","Date","toLocaleString","handlePauseParseSchedule","reload","handleResumeParseSchedule","handleCancelParseSchedule","handleUpdateLabels","updateLabelsBtn","loadPineconeHealth","noResultsEl","resultsEl","allGoodEl","namespaceContainerEl","namespaceTbodyEl","failuresContainerEl","failuresTbodyEl","statusEl","lastRunEl","expectedCountEl","actualCountEl","mismatchCountEl","sampleFailuresEl","toUpperCase","created_at","date","total_expected","total_actual","total_mismatches","style","color","sample_failures","namespace_results","namespace","result","nsCell","fontWeight","typeCell","replace","expectedCell","expected","actualCell","actual","deltaCell","delta","statusCell","match","sample_failure_details","failure","fileCell","file_name","detailsCell","field","JSON","stringify","handleRegeneratePinecone","selectedNamespaces","checkboxes","querySelectorAll","from","map","cb","getSelectedNamespaces","regenerateBtn","confirmMessage","ns","n","namespaces","loadConfigHealth","loadingEl","errorEl","errorsContainerEl","warningsContainerEl","errorsTbodyEl","warningsTbodyEl","totalRequiredEl","configuredEl","errorsCountEl","warningsCountEl","total_required_fields","configured_fields","error_count","warning_count","errors","warnings","fieldCell","display_name","sectionCell","section","textTransform","descCell","description","warning","actionCell","dismissBtn","fieldName","field_name","handleDismissWarning","loadDataHealth","emptyStateEl","issuesContainerEl","issuesTbodyEl","totalFilesEl","healthyFilesEl","total_files","healthy_files","issues","issue","checkNameCell","check_name","countCell","affected_count","filesCell","affected_files","fileList","fileName","li","moreText","loadDataIntegrity","pathname","startsWith","root","drawerSelector","updateRepositoryBtn","scheduleRepositoryBtn","pauseScheduleBtn","resumeScheduleBtn","cancelScheduleBtn","learnRepoScheduleBtn","runPineconeHealthBtn","errorMsgEl","runBtn","triggerPineconeHealthCheck","regeneratePineconeBtn","stopPropagation","infoDiv","toggle","deleteProjectById","fetch","method","headers","ok","Error","deleteModal","deleteModalOverlay","deleteModalProjectName","deleteModalInput","deleteModalError","deleteModalCancelBtn","deleteModalConfirmBtn","showDeleteModal","projectName","onConfirm","onCancel","handleInput","handleCancel","hideDeleteModal","handleConfirm","display","removeEventListener","setTimeout","focus","hide","generatedInstallKey","copyTextToClipboard","navigator","clipboard","writeText","ta","position","opacity","body","select","execCommand","removeChild","deriveInstallKeyEmail","sanitized","toLowerCase","activateTab","idx","snippets","tabList","panelContainer","tabs","panels","tab","i","active","setAttribute","panel","hidden","loadInstallInstructions","contentEl","encodeURIComponent","containerEl","repoLabel","snippet","panelId","tabId","isFirst","label","target_path","language","notes","pre","code","parts","part","kind","span","createTextNode","copyRow","copyBtn","copyStatus","_","injectInstallKeyIntoSnippets","renderSnippetTabs","initializeHandoffCopy","installStatus","summaryEl","groupsEl","available","groups","setChip","set","unsetChip","unset","total","group","fields","heading","title","key","chip","is_set","current_value","undefined","val","renderInstallStatus","install_status","msgEl","apiKey","slots","setInstallKeyLoading","loading","removeAttribute","showInstallKeyError","handleGenerateInstallKeyClick","payload","email","name","role","json","api_key","keyInput","resultEl","generateBtn","initializeInstallKeyGeneration","current","findIndex","getAttribute","URLSearchParams","search","get","setupDetailLink","continueSetupLink","retryBtn","init","activePolls","Map","latestLearnRepositoryStatus","GLOBAL_LEARNING_POLL_KEY","LEARNING_POLL_INTERVAL_MS","LEARNING_POLL_TIMEOUT_MS","initLearnRepository","context","renderUI","showStatusMessage","sectionId","approveBtn","confirmText","Promise","resolve","resolved","button","textarea","originalText","editedClarifications","analysis_type","clarifications","currentLiveInstructions","instructions","pollReanalyzeStatus","mode","priorInstructions","handleApprove","saveBtn","instructions_text","handleSaveInstructions","link","closest","optionsWrapper","defaultRadio","modeRadio","confirmBtn","originalConfirmText","handleReanalyze","reanalyzeLink","acceptBtn","handleAcceptDraft","discardBtn","handleDiscardDraft","selectedMode","analysisLabel","modalMsg","isInitialRun","handleInitialAnalyze","updateLatestLearnRepositoryStatus","normalized","learningInProgress","controller","abort","delete","stopGlobalLearningPoll","AbortController","statusUrl","learning_in_progress","intervalMs","timeoutMs","continueOnError","signal","onTick","then","catch","pollLearningStatus","existing","sawReanalyzing","typeData","reanalyzing","draft_instructions","instructionsChanged","freshCompletion","conclusions","diff_summary","options","autoHideMs","loginForm","logoutBtn","history","replaceState","pageModal","pageModalTitle","pageModalBody","pageModalActions","pageModalConfirmBtn","pageModalCancelBtn","pageCloseModalBtn","currentConfirmCallback","currentCloseCallback","showModal","showCancel","onClose","hideModal","warn","closeCallback","initModal","event","target","ESTIMATOR_SECTIONS","checkEstimatorStatus","showEstimatorSection","state","rangeData","showRangeReview","rangeJson","showEstimatorRunning","startEstimatorOptimization","accepted","need_estimate_review","saveEstimatorRanges","rangeTextarea","rangeText","parse","range","saved","cancelReview","messageElement","defaultExpanded","initializeEstimator","normalizeIdentityValue","getOptimizeContext","encodePathSegment","SAVED","SAVING","resolveElement","setInlineStatus","withButtonPendingState","pendingText","callback","originalDisabled","LR_STATE_CLASSES","TOGGLEABLE_KEYS","getSectionId","getLearnSectionElements","byId","q","sel","reanalyzeContainer","qIn","initialContainer","inProgressContainer","completeStatus","draftReview","diffDisplay","reanalyzeOptions","setLearnSectionStateClass","cls","applyVisibilityMap","elements","visibleKeys","visible","Set","keys","has","toggleableElements","els","out","showSection","setDescription","renderSectionRunning","sectionData","push","renderSectionDraft","draftInstructions","split","line","arr","renderDiffSummarySafely","renderSectionComplete","renderLearnRepositorySection","clarificationText","renderSectionReview","renderSectionIdle","DEFAULT_INTERVAL_MS","PollTimeoutError","constructor","super","this","pollUntil","isDone","onError","fetcher","immediate","doFetch","reject","intervalTimer","timeoutTimer","settled","cleanup","clearTimeout","onAbort","finishReject","reason","DOMException","schedule","runTick","done","aborted","ANALYSIS_TYPES","adaptCodeAnalysisStatus","is_locked","analysisText","analysis_text","complete","adaptLearnRepositorySectionStatus","adaptLearnRepositoryStatus","sections","adaptEstimatorStatus","range_json","UI_STRINGS","analysisPollControllers","initializeCodeAnalysis","setupSection","fileLabel","prefix","userGuidanceTextarea","analyzeBtn","outputTextarea","infoLabel","showNoAnalysisState","pollForCompletion","showAnalysisDoneState","checkStatus","file_label","custom_only","user_guidance","detail","noAnalysisContainer","analysisDoneContainer","reEnableControls","projectDetailContainer","projectDetailTitle","projectInfoForm","estimatorForm","codeWriterForm","codeReviewForm","saveChangesBtn","estimateScaleJsonTextarea","estimateScaleJsonErrorDiv","projectDetailBaseline","normalizeStringArrayValue","values","getAllowedProviderValues","CI_FOLLOWUP_INSTRUCTIONS_MAX_BYTES","CI_FOLLOWUP_DEFAULTS","strategy","max_iterations","max_minutes","getUtf8ByteLength","TextEncoder","encode","getSelectedCiFollowupStrategy","setCiFollowupControlValues","config","radio","maxIterationsEl","maxMinutesEl","instructionsEl","updateCiFollowupInstructionsByteCounter","counter","byteLength","applyCiFollowupStrategyState","clearInstructionsOnPollOnly","required","toggleCiFollowupControlsVisibility","enabled","controls","isOn","innerInputs","rebuildSecondOpinionProviderOptions","preferredValue","desiredValue","allowed","blank","provider","PROVIDER_DISPLAY_NAMES","toggleDeepResearchTimeoutVisibility","input","toggleStoryPointsFieldVisibility","updateJira","PROJECT_DETAIL_FIELD_DESCRIPTORS","delimiter","getValue","orderInsensitive","FIELD_TO_SECTION","project_description","confluence_pattern","ignored_domains","deep_research_enabled","deep_research_stall_timeout_minutes","allowed_providers","second_opinion_provider","unit_testing_stack","e2e_testing_stack","estimate_qa","process_estimate_instructions","estimate_instructions","update_jira","estimate_scale","story_points_field","architecture_instructions","tdd_document_instructions","fsd_document_instructions","design_principles","unit_testing_instructions","e2e_testing_instructions","review_instructions","documentation_instructions","ci_followup_config","allow_mutating_smoke_ops","selected_mcp_slugs","frontend_correctness_standards","backend_correctness_standards","template_correctness_standards","style_correctness_standards","reviewable_file_types","include_path","exclude_path","frontend_styleguide","backend_styleguide","css_styleguide","review_correctness","review_style","review_requirements","review_architecture","create_tests","create_documentation","documentation_standards","score_threshold_for_review","no_comment_on_passing_score","getPayloadValueForField","changedValue","estimateScaleValue","EXPLANATION_TEXTS","openai","anthropic","gemini","MCP_DOC_DISPLAY_LABELS","applyPrimaryProviderLockState","primaryProvider","tooltip","getPrimaryProviderLockTooltip","loadProjectDetail","projectData","repo_display_name","code_writer","allowedProvidersGroup","allowedProviders","estimator","scaleJsonString","writer","availableMcpDocs","selectedMcpSlugs","list","emptyState","docs","selected","doc","slug","checkbox","labelText","getMcpDocDisplayLabel","populateMcpDocSelector","available_mcp_docs","rawValue","enabledEl","populateCiFollowupConfig","reviewer","code_reviewer","populateProjectForms","drawer","header","explanationIcon","fieldId","handleSaveProject","allValid","validateInput","validateAllForms","firstErrorInput","scrollIntoView","behavior","block","isValid","rawJson","borderColor","parsedJson","changedFieldEntries","touched_fields","buildSparseProjectPayload","element","offsetParent","errorElement","errorMessage","setCustomValidity","ciEnabled","checkValidity","validity","valueMissing","typeMismatch","tooLong","maxLength","rangeUnderflow","min","rangeOverflow","max","validationMessage","jsonString","some","item","handleBlurValidation","handleInputClearError","contains","bottom","left","transform","zIndex","padding","boxShadow","sectionKey","explanationDivId","explanationDiv","explanation","deepResearchEnabled","ciFollowupEnabled","ciInstructions","setupConditionalVisibilityListeners","currentVersionControlSystem","setupDetailContainer","setupDetailTitle","identityForm","jiraForm","vcsForm","githubForm","bitbucketForm","pineconeForm","platformForm","saveSetupBtn","versionSelect","baseVersionGroup","baseVersionSelect","alternateVersionGroup","alternateVersionSelect","alternateVersionDirectoriesGroup","alternateVersionDirectoriesTextarea","pwaOverridesGroup","pwaOverridesCheckbox","pwaOverridesDirectoriesGroup","pwaOverridesDirectoriesTextarea","setupBaseline","ARRAY_FIELD_DELIMITER","SETUP_FIELD_DESCRIPTORS","captureSetupBaseline","formatInstallStatusValue","renderInstallStatusPanel","count","groupEl","titleEl","nameEl","badge","valueText","guidance","valueEl","requires_confirmation","confirmEl","Boolean","hasInstallStatusFormControl","editBtn","focusInstallStatusField","renderInstallStatusUnavailable","slot","loadInstallStatus","hasGroups","fieldMap","installField","renderInlineInstallStatusBadges","flattenInstallStatusFields","handleVersionControlVisibility","versionControlSystem","githubDrawer","bitbucketDrawer","toggleHybridFields","isPwaOverrideRelevant","version","base","alternate","togglePwaOverridesVisibility","togglePwaOverridesDirectoriesVisibility","isChecked","loadSetupDetail","setupData","repoNameInput","repoDisplayNameInput","jiraTicketKeyInput","jira_ticket_key","jira_server_url","jira_email","jira_api_token","vcsSelect","version_control_system","vcs_webhook_secret","repo_id","baseBranchInput","base_branch","postPrTargetStatusInput","post_pr_target_status","git_app_installation_id","ai_author","version_control_access_token","workspace","bitbucket_app_username","bitbucket_app_password","pinecone_index_name","workingInSelect","working_in","base_version","alternate_version","alternate_version_directories","pwa_overrides","pwa_overrides_directories","customDirs","custom_directories","excludeExts","exclude_file_extensions","excludeDirs","exclude_directories","populateSetupForms","readOnly","conditionalGroup","shouldSkipValidation","isHybrid","baseValue","handleSaveSetup","scrollToFirstError","changedFields","buildSparseSetupPayload","newValue","previousValue","modal","modalTitle","modalMessage","cancelBtn","newConfirmBtn","cloneNode","newCancelBtn","parentNode","replaceChild","showConfirmationModal","tagName","createProjectForm","projectsContainer","createProjectContainer","loadProjects","projects","projectsList","project","displayName","projectCard","setupButton","configButton","optimizeButton","healthButton","monitoringButton","securityButton","connectEditorButton","deleteLink","card","renderProjectsList","handleCreateProject","formData","FormData","repoDisplayName","test","reset","loadingOverlay","createProjectBtn","backToProjectsBtn","addEventListeners","TOKEN_STORAGE_KEY","pageLoadingOverlay","showLoading","hideLoading","ApiCallError","apiCall","url","callerHeaders","token","getItem","assign","fetchOptions","credentials","firstErr","loc","formatApiErrorMessage","getApiErrorMessage","fallback","arrayToString","getDescriptorFieldName","descriptor","normalizeDescriptorValue","normalizer","normalizeArrayValue","areDescriptorValuesEqual","a","b","right","slice","sort","readDescriptorRawValue","read","selector","captureBaseline","descriptors","snapshot","normalizeSnapshotValue","diff","baseline","baselineSnapshot","currentSnapshot","changed","baselineValue","currentValue","diffSnapshots","resetBaseline","DRAWER_DEFAULTS","headerSelector","contentSelector","toggleSelector","expandedClass","hiddenClass","expandedGlyph","collapsedGlyph","ignoreSelector","resolveDrawerOptions","merged","setDrawerExpanded","expanded","opts","initDrawers","deriveDrawerExpandedState","drawerInitialized","currentContent","isOpen","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","exports","module","__webpack_modules__","d","definition","o","defineProperty","enumerable","obj","prop","prototype","hasOwnProperty","call"],"sourceRoot":""}