@@ -102,6 +102,8 @@ export function wizardHtml(appName: string, reconfig = false): string {
102102 .btn-secondary:hover { background: #334155; }
103103 .btn-skip { background: transparent; color: #64748b; border: 1px solid #22314f; }
104104 .btn-skip:hover { background: #1e293b; }
105+ .btn-skip.ready { background: rgba(37, 99, 235, 0.2); color: #dbeafe; border-color: #3b82f6; box-shadow: inset 0 0 0 1px rgba(96, 165, 250, 0.25); }
106+ .btn-skip.ready:hover { background: rgba(37, 99, 235, 0.32); }
105107 .btn-validate { background: #0f766e; color: #fff; flex: none; width: auto; padding: 10px 18px; }
106108 .btn-validate:hover { background: #115e59; }
107109 .error { color: #ef4444; font-size: 13px; margin-bottom: 12px; min-height: 18px; }
@@ -205,7 +207,7 @@ export function wizardHtml(appName: string, reconfig = false): string {
205207 </div>
206208
207209 <div class="btn-row">
208- <button class="btn btn-skip" onclick="goStep(2)">Skip</button>
210+ <button class="btn btn-skip" id="gh-skip-btn" onclick="goStep(2)">Skip</button>
209211 <button class="btn btn-validate" id="gh-validate-btn" onclick="validateGithub()">Validate</button>
210212 <button class="btn btn-primary" onclick="saveGithub()">Save & Continue</button>
211213 </div>
@@ -234,7 +236,7 @@ export function wizardHtml(appName: string, reconfig = false): string {
234236
235237 <div class="btn-row">
236238 <button class="btn btn-secondary" onclick="goStep(1)">Back</button>
237- <button class="btn btn-skip" onclick="goStep(3)">Skip</button>
239+ <button class="btn btn-skip" id="llm-skip-btn" onclick="goStep(3)">Skip</button>
238240 <button class="btn btn-validate" id="llm-validate-btn" onclick="validateLlm()">Validate</button>
239241 <button class="btn btn-primary" onclick="saveLlm()">Save & Continue</button>
240242 </div>
@@ -288,7 +290,7 @@ export function wizardHtml(appName: string, reconfig = false): string {
288290
289291 <div class="btn-row">
290292 <button class="btn btn-secondary" onclick="goStep(2)">Back</button>
291- <button class="btn btn-skip" onclick="goStep(4)">Skip</button>
293+ <button class="btn btn-skip" id="slack-skip-btn" onclick="goStep(4)">Skip</button>
292294 <button class="btn btn-validate" id="slack-validate-btn" onclick="validateSlack()">Validate</button>
293295 <button class="btn btn-primary" onclick="saveSlack()">Save & Continue</button>
294296 </div>
@@ -310,6 +312,11 @@ export function wizardHtml(appName: string, reconfig = false): string {
310312<script>
311313let currentStep = 0;
312314const state = { password: false, github: false, llm: false, slack: false };
315+ const prefillState = {
316+ github: { token: false, appId: false, installationId: false, privateKey: false },
317+ llm: { apiKey: false },
318+ slack: { botToken: false, appToken: false }
319+ };
313320
314321function goStep(n) {
315322 document.querySelectorAll('.step').forEach(s => s.classList.remove('active'));
@@ -320,13 +327,62 @@ function goStep(n) {
320327 if (i === n) d.classList.add('active');
321328 });
322329 currentStep = n;
330+ syncSkipButtons();
323331 if (n === 4) renderReview();
324332}
325333
326334function toggleGhMode() {
327335 const mode = document.querySelector('input[name="gh-mode"]:checked').value;
328336 document.getElementById('gh-pat-fields').classList.toggle('hidden', mode !== 'pat');
329337 document.getElementById('gh-app-fields').classList.toggle('hidden', mode !== 'app');
338+ syncSkipButtons();
339+ }
340+
341+ function hasValue(id) {
342+ const el = document.getElementById(id);
343+ return !!el && typeof el.value === 'string' && el.value.trim().length > 0;
344+ }
345+
346+ function hasPrefillValue(field) {
347+ return !!field && !!field.source && field.source !== 'none';
348+ }
349+
350+ function hasGithubRequiredFields() {
351+ const mode = document.querySelector('input[name="gh-mode"]:checked')?.value;
352+ if (mode === 'app') {
353+ return hasValue('gh-app-id') && hasValue('gh-install-id') && hasValue('gh-key');
354+ }
355+ return hasValue('gh-token');
356+ }
357+
358+ function hasLlmRequiredFields() {
359+ return hasValue('llm-key');
360+ }
361+
362+ function hasSlackRequiredFields() {
363+ return hasValue('slack-bot-token') && hasValue('slack-app-token');
364+ }
365+
366+ function hasGithubReadyForSkip() {
367+ const mode = document.querySelector('input[name="gh-mode"]:checked')?.value;
368+ if (mode === 'app') {
369+ return hasGithubRequiredFields() || (prefillState.github.appId && prefillState.github.installationId && prefillState.github.privateKey);
370+ }
371+ return hasGithubRequiredFields() || prefillState.github.token;
372+ }
373+
374+ function hasLlmReadyForSkip() {
375+ return hasLlmRequiredFields() || prefillState.llm.apiKey;
376+ }
377+
378+ function hasSlackReadyForSkip() {
379+ return hasSlackRequiredFields() || (prefillState.slack.botToken && prefillState.slack.appToken);
380+ }
381+
382+ function syncSkipButtons() {
383+ document.getElementById('gh-skip-btn')?.classList.toggle('ready', hasGithubReadyForSkip() || state.github);
384+ document.getElementById('llm-skip-btn')?.classList.toggle('ready', hasLlmReadyForSkip() || state.llm);
385+ document.getElementById('slack-skip-btn')?.classList.toggle('ready', hasSlackReadyForSkip() || state.slack);
330386}
331387
332388function sourceLabel(source) {
@@ -366,6 +422,14 @@ function renderPrefillSummary(containerId, rows) {
366422function applySetupPrefill(prefill) {
367423 if (!prefill) return;
368424
425+ prefillState.github.token = hasPrefillValue(prefill.github?.token);
426+ prefillState.github.appId = hasPrefillValue(prefill.github?.appId);
427+ prefillState.github.installationId = hasPrefillValue(prefill.github?.installationId);
428+ prefillState.github.privateKey = hasPrefillValue(prefill.github?.privateKey);
429+ prefillState.llm.apiKey = hasPrefillValue(prefill.llm?.apiKey);
430+ prefillState.slack.botToken = hasPrefillValue(prefill.slack?.botToken);
431+ prefillState.slack.appToken = hasPrefillValue(prefill.slack?.appToken);
432+
369433 setGitHubAuthMode(prefill.github?.authMode);
370434 setInputValue('gh-owner', prefill.github?.defaultOwner);
371435 setInputValue('gh-app-id', prefill.github?.appId);
@@ -401,6 +465,8 @@ function applySetupPrefill(prefill) {
401465 { label: 'Client secret', source: prefill.slack?.clientSecret?.source },
402466 { label: 'Auth redirect URI', source: prefill.slack?.authRedirectUri?.source, value: prefill.slack?.authRedirectUri?.value },
403467 ]);
468+
469+ syncSkipButtons();
404470}
405471
406472async function post(url, body) {
@@ -483,6 +549,7 @@ async function saveGithub() {
483549 return;
484550 }
485551 state.github = true;
552+ syncSkipButtons();
486553 goStep(2);
487554}
488555
@@ -530,6 +597,7 @@ async function saveLlm() {
530597 return;
531598 }
532599 state.llm = true;
600+ syncSkipButtons();
533601 goStep(3); // → Slack step
534602}
535603
@@ -582,6 +650,7 @@ async function saveSlack() {
582650 return;
583651 }
584652 state.slack = true;
653+ syncSkipButtons();
585654 goStep(4);
586655}
587656
@@ -617,6 +686,11 @@ async function finishSetup() {
617686}
618687
619688// Check initial status
689+ ['gh-token', 'gh-owner', 'gh-app-id', 'gh-install-id', 'gh-key', 'llm-key', 'llm-model', 'slack-bot-token', 'slack-app-token', 'slack-signing-secret', 'slack-command', 'slack-client-id', 'slack-client-secret', 'slack-auth-redirect-uri']
690+ .forEach(id => document.getElementById(id)?.addEventListener('input', syncSkipButtons));
691+ document.querySelectorAll('input[name="gh-mode"]').forEach(el => el.addEventListener('change', syncSkipButtons));
692+ syncSkipButtons();
693+
620694fetch('/api/setup/status', { credentials: 'same-origin' })
621695 .then(r => r.json())
622696 .then(data => {
@@ -625,6 +699,7 @@ fetch('/api/setup/status', { credentials: 'same-origin' })
625699 if (data.hasLlm) state.llm = true;
626700 if (data.hasSlack) state.slack = true;
627701 applySetupPrefill(data.prefill);
702+ syncSkipButtons();
628703 ${ reconfig ? "// In reconfig mode, start from step 1 (skip password if already set)\n if (state.password) goStep(1);" : "" }
629704 })
630705 .catch(() => {});
0 commit comments