agentvibes 4.4.1 → 4.5.0
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.
- package/.agentvibes/config.json +4 -4
- package/.claude/config/reverb-level.txt +1 -1
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks-windows/bmad-speak.ps1 +112 -0
- package/.claude/hooks-windows/play-tts-piper.ps1 +3 -4
- package/.claude/hooks-windows/play-tts-sapi.ps1 +3 -4
- package/.claude/hooks-windows/play-tts-soprano.ps1 +2 -3
- package/.claude/hooks-windows/play-tts-termux-ssh.ps1 +138 -0
- package/.claude/hooks-windows/play-tts.ps1 +14 -6
- package/.claude/hooks-windows/provider-manager.ps1 +16 -1
- package/CLAUDE.md +4 -0
- package/README.md +39 -9
- package/RELEASE_NOTES.md +39 -0
- package/bin/agent-vibes +1 -1
- package/bin/agentvibes-voice-browser.js +1 -1
- package/bin/bmad-speak.js +52 -0
- package/bin/mcp-server.js +1 -1
- package/bin/test-bmad-pr +1 -1
- package/package.json +1 -1
- package/setup-windows.ps1 +4 -4
- package/src/console/app.js +58 -11
- package/src/console/tabs/agents-tab.js +61 -65
- package/src/console/tabs/help-tab.js +107 -54
- package/src/console/tabs/install-tab.js +107 -47
- package/src/console/tabs/music-tab.js +1030 -1011
- package/src/console/tabs/placeholder-tab.js +27 -0
- package/src/console/tabs/readme-tab.js +9 -7
- package/src/console/tabs/receiver-tab.js +23 -12
- package/src/console/tabs/settings-tab.js +4001 -3783
- package/src/console/tabs/voices-tab.js +1680 -1653
- package/src/console/widgets/personality-picker.js +35 -7
- package/src/console/widgets/reverb-picker.js +9 -6
- package/src/console/widgets/track-picker.js +6 -1
- package/src/i18n/de.js +201 -0
- package/src/i18n/en.js +201 -0
- package/src/i18n/es.js +201 -0
- package/src/i18n/fr.js +201 -0
- package/src/i18n/hi.js +201 -0
- package/src/i18n/ja.js +201 -0
- package/src/i18n/ko.js +201 -0
- package/src/i18n/pt.js +201 -0
- package/src/i18n/strings.js +54 -0
- package/src/i18n/zh-CN.js +201 -0
- package/src/installer/language-screen.js +31 -0
- package/src/installer.js +79 -25
- package/src/services/language-service.js +47 -0
- package/src/utils/file-ownership-verifier.js +2 -2
- package/src/utils/provider-validator.js +9 -13
- package/.claude/hooks-windows/play-tts-windows-piper.ps1 +0 -209
- package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +0 -108
|
@@ -19,6 +19,7 @@ import { promisify } from 'node:util';
|
|
|
19
19
|
import fs from 'node:fs';
|
|
20
20
|
import { promises as _fsP } from 'node:fs';
|
|
21
21
|
import { buildAudioEnv } from '../audio-env.js';
|
|
22
|
+
import { SUPPORTED_LANGUAGES, t } from '../../i18n/strings.js';
|
|
22
23
|
import {
|
|
23
24
|
copyCommandFiles, copyHookFiles, copyPersonalityFiles,
|
|
24
25
|
copyPluginFiles, copyBmadConfigFiles, copyBackgroundMusicFiles,
|
|
@@ -140,7 +141,7 @@ function createTestStub() {
|
|
|
140
141
|
hide: () => {},
|
|
141
142
|
onFocus: () => {},
|
|
142
143
|
onBlur: () => {},
|
|
143
|
-
getFooterText: () =>
|
|
144
|
+
getFooterText: () => t('en', 'footerText'),
|
|
144
145
|
getFooterColor: () => COLORS.footerBg,
|
|
145
146
|
};
|
|
146
147
|
}
|
|
@@ -158,7 +159,7 @@ function createTestStub() {
|
|
|
158
159
|
export function createInstallTab(screen, services) {
|
|
159
160
|
if (IS_TEST) return createTestStub();
|
|
160
161
|
|
|
161
|
-
const { configService, providerService, navigationService, focusMainTabBar } = services;
|
|
162
|
+
const { configService, providerService, navigationService, focusMainTabBar, languageService } = services;
|
|
162
163
|
|
|
163
164
|
// -------------------------------------------------------------------------
|
|
164
165
|
// Container
|
|
@@ -178,8 +179,13 @@ export function createInstallTab(screen, services) {
|
|
|
178
179
|
// -------------------------------------------------------------------------
|
|
179
180
|
// Wizard state
|
|
180
181
|
|
|
181
|
-
let _screen =
|
|
182
|
-
let _lastScreen =
|
|
182
|
+
let _screen = 0;
|
|
183
|
+
let _lastScreen = -1;
|
|
184
|
+
// _lang is now owned by languageService; keep a local helper for convenience
|
|
185
|
+
// and a local _langIdx for the language-picker UI (Screen 0).
|
|
186
|
+
const _getLang = () => languageService?.getLang() ?? 'en';
|
|
187
|
+
const _tl = (key) => languageService?.t(key) ?? t('en', key);
|
|
188
|
+
let _langIdx = 0;
|
|
183
189
|
let _deps = null;
|
|
184
190
|
let _checking = false;
|
|
185
191
|
let _selectedProvider = null;
|
|
@@ -525,8 +531,8 @@ export function createInstallTab(screen, services) {
|
|
|
525
531
|
return btn;
|
|
526
532
|
}
|
|
527
533
|
|
|
528
|
-
const _editBtn = _createInstallBtn('
|
|
529
|
-
const _acceptBtn = _createInstallBtn('
|
|
534
|
+
const _editBtn = _createInstallBtn(_tl('editInstallBtn'), '#1565c0', _doEdit);
|
|
535
|
+
const _acceptBtn = _createInstallBtn(_tl('acceptInstallBtn'), COLORS.btnDefault, _doAccept);
|
|
530
536
|
|
|
531
537
|
// Edit sits inline with the intro text row; Accept & Install is below
|
|
532
538
|
_editBtn.top = 8; _editBtn.left = 36;
|
|
@@ -540,11 +546,11 @@ export function createInstallTab(screen, services) {
|
|
|
540
546
|
// -------------------------------------------------------------------------
|
|
541
547
|
// Screen 1 buttons — Begin (cyan) and Exit (grey)
|
|
542
548
|
|
|
543
|
-
const _s1BeginBtn = _createInstallBtn('
|
|
549
|
+
const _s1BeginBtn = _createInstallBtn(_tl('beginBtn'), '#00838f', () => {
|
|
544
550
|
_screen++;
|
|
545
551
|
_showCurrentScreen();
|
|
546
552
|
});
|
|
547
|
-
const _s1ExitBtn = _createInstallBtn('
|
|
553
|
+
const _s1ExitBtn = _createInstallBtn(_tl('exitBtn'), '#546e7a', () => {
|
|
548
554
|
box.hide();
|
|
549
555
|
screen.render();
|
|
550
556
|
if (typeof focusMainTabBar === 'function') focusMainTabBar();
|
|
@@ -562,7 +568,7 @@ export function createInstallTab(screen, services) {
|
|
|
562
568
|
// -------------------------------------------------------------------------
|
|
563
569
|
// Screen 2 button — Continue (shown after deps check passes)
|
|
564
570
|
|
|
565
|
-
const _s2ContinueBtn = _createInstallBtn('
|
|
571
|
+
const _s2ContinueBtn = _createInstallBtn(_tl('continueArrowBtn'), '#1565c0', () => {
|
|
566
572
|
_screen++;
|
|
567
573
|
_showCurrentScreen();
|
|
568
574
|
});
|
|
@@ -575,7 +581,7 @@ export function createInstallTab(screen, services) {
|
|
|
575
581
|
// -------------------------------------------------------------------------
|
|
576
582
|
// Screen 5 button — OK (summary page only, config already saved on screen 4)
|
|
577
583
|
|
|
578
|
-
const _s5OkBtn = _createInstallBtn('
|
|
584
|
+
const _s5OkBtn = _createInstallBtn(_tl('okDoneBtn'), '#1565c0', () => {
|
|
579
585
|
_dismissCompletionModal();
|
|
580
586
|
});
|
|
581
587
|
_s5OkBtn.bottom = 3; _s5OkBtn.left = 4; // bottom-anchored: sits above hintLine (bottom:2)
|
|
@@ -586,15 +592,35 @@ export function createInstallTab(screen, services) {
|
|
|
586
592
|
const _HDR = (emoji, label) =>
|
|
587
593
|
`{${COLORS.sectionHdr}-fg}${emoji} ${label} ${'─'.repeat(100)}{/${COLORS.sectionHdr}-fg}`;
|
|
588
594
|
|
|
595
|
+
function _renderScreen0() {
|
|
596
|
+
const lines = [
|
|
597
|
+
_HDR('🌐', 'Language / Idioma / Langue / Sprache / 言語 / भाषा / 语言 / 언어'),
|
|
598
|
+
'',
|
|
599
|
+
' Select your language:',
|
|
600
|
+
'',
|
|
601
|
+
...SUPPORTED_LANGUAGES.map((l, i) =>
|
|
602
|
+
i === _langIdx
|
|
603
|
+
? ` {green-fg}► ${l.name}{/green-fg}`
|
|
604
|
+
: ` ${l.name}`
|
|
605
|
+
),
|
|
606
|
+
];
|
|
607
|
+
contentBox.setContent(_c(lines));
|
|
608
|
+
hintLine.setContent(' Screen 0: Language | [↑/↓] Select | [Enter] Apply & Continue | [→] Skip (English)');
|
|
609
|
+
screen.render();
|
|
610
|
+
}
|
|
611
|
+
|
|
589
612
|
function _renderScreen1() {
|
|
613
|
+
// Update button labels to current language before focus triggers the decorator
|
|
614
|
+
_s1BeginBtn.setContent(t(_getLang(), 'beginBtn'));
|
|
615
|
+
_s1ExitBtn.setContent(t(_getLang(), 'exitBtn'));
|
|
590
616
|
contentBox.setContent(_c([
|
|
591
|
-
_HDR('🔧',
|
|
617
|
+
_HDR('🔧', t(_getLang(), 'setupWizard')),
|
|
592
618
|
'',
|
|
593
|
-
` {${COLORS.noticeFg}-fg}
|
|
619
|
+
` {${COLORS.noticeFg}-fg}${t(_getLang(), 'setupWizardSubtitle')}{/${COLORS.noticeFg}-fg}`,
|
|
594
620
|
'',
|
|
595
621
|
'', // ← [▶ Begin] [✗ Exit] buttons here (box row 5)
|
|
596
622
|
]));
|
|
597
|
-
hintLine.setContent(
|
|
623
|
+
hintLine.setContent(` ${t(_getLang(), 'screen1Hint')}`);
|
|
598
624
|
_s1BeginBtn.focus();
|
|
599
625
|
screen.render();
|
|
600
626
|
}
|
|
@@ -606,19 +632,19 @@ export function createInstallTab(screen, services) {
|
|
|
606
632
|
_s2ContinueBtn.hide(); // hidden during spinner
|
|
607
633
|
|
|
608
634
|
contentBox.setContent(_c([
|
|
609
|
-
_HDR('🔍',
|
|
635
|
+
_HDR('🔍', t(_getLang(), 'dependencyCheck')),
|
|
610
636
|
'',
|
|
611
|
-
` {${COLORS.noticeFg}-fg}${frames[0]}
|
|
637
|
+
` {${COLORS.noticeFg}-fg}${frames[0]} ${t(_getLang(), 'checkingDependencies')}{/${COLORS.noticeFg}-fg}`,
|
|
612
638
|
]));
|
|
613
|
-
hintLine.setContent(
|
|
639
|
+
hintLine.setContent(` ${t(_getLang(), 'screen2Hint')}`);
|
|
614
640
|
screen.render();
|
|
615
641
|
|
|
616
642
|
const spinInterval = setInterval(() => {
|
|
617
643
|
frameIdx = (frameIdx + 1) % frames.length;
|
|
618
644
|
contentBox.setContent(_c([
|
|
619
|
-
_HDR('🔍',
|
|
645
|
+
_HDR('🔍', t(_getLang(), 'dependencyCheck')),
|
|
620
646
|
'',
|
|
621
|
-
` {${COLORS.noticeFg}-fg}${frames[frameIdx]}
|
|
647
|
+
` {${COLORS.noticeFg}-fg}${frames[frameIdx]} ${t(_getLang(), 'checkingDependencies')}{/${COLORS.noticeFg}-fg}`,
|
|
622
648
|
]));
|
|
623
649
|
screen.render();
|
|
624
650
|
}, 100);
|
|
@@ -630,28 +656,29 @@ export function createInstallTab(screen, services) {
|
|
|
630
656
|
_checking = false;
|
|
631
657
|
}
|
|
632
658
|
|
|
633
|
-
const ok = () => `{${COLORS.successFg}-fg}✅
|
|
634
|
-
const bad = () => `{${COLORS.errorFg}-fg}❌
|
|
659
|
+
const ok = () => `{${COLORS.successFg}-fg}✅ ${t(_getLang(), 'installed')}{/${COLORS.successFg}-fg}`;
|
|
660
|
+
const bad = () => `{${COLORS.errorFg}-fg}❌ ${t(_getLang(), 'notFound')}{/${COLORS.errorFg}-fg}`;
|
|
635
661
|
|
|
636
662
|
const ttsOk = _deps.piper || _deps.soprano;
|
|
637
663
|
contentBox.setContent(_c([
|
|
638
|
-
_HDR('🔍',
|
|
664
|
+
_HDR('🔍', t(_getLang(), 'dependencyCheck')),
|
|
639
665
|
'',
|
|
640
|
-
` {${COLORS.noticeFg}-fg}${'
|
|
666
|
+
` {${COLORS.noticeFg}-fg}${t(_getLang(), 'depColumn').padEnd(14)}${t(_getLang(), 'statusColumn')}{/${COLORS.noticeFg}-fg}`,
|
|
641
667
|
` {${COLORS.noticeFg}-fg}${'─'.repeat(78)}{/${COLORS.noticeFg}-fg}`,
|
|
642
668
|
` {${COLORS.labelFg}-fg}${'Node.js'.padEnd(14)}{/${COLORS.labelFg}-fg}${_deps.node ? ok() : bad()}`,
|
|
643
669
|
` {${COLORS.labelFg}-fg}${'npm'.padEnd(14)}{/${COLORS.labelFg}-fg}${_deps.npm ? ok() : bad()}`,
|
|
644
670
|
` {${COLORS.labelFg}-fg}${'Piper TTS'.padEnd(14)}{/${COLORS.labelFg}-fg}${_deps.piper ? ok() : bad()}`,
|
|
645
671
|
` {${COLORS.labelFg}-fg}${'Soprano TTS'.padEnd(14)}{/${COLORS.labelFg}-fg}${_deps.soprano ? ok() : bad()}`,
|
|
646
|
-
` {${COLORS.labelFg}-fg}${'ffmpeg'.padEnd(14)}{/${COLORS.labelFg}-fg}${_deps.ffmpeg ? ok() : `{${COLORS.errorFg}-fg}⚠
|
|
672
|
+
` {${COLORS.labelFg}-fg}${'ffmpeg'.padEnd(14)}{/${COLORS.labelFg}-fg}${_deps.ffmpeg ? ok() : `{${COLORS.errorFg}-fg}⚠ ${t(_getLang(), 'ffmpegMissing')}{/${COLORS.errorFg}-fg}`}`,
|
|
647
673
|
'',
|
|
648
674
|
ttsOk
|
|
649
|
-
? ` {${COLORS.successFg}-fg}✅
|
|
650
|
-
: ` {${COLORS.errorFg}-fg}⚠
|
|
675
|
+
? ` {${COLORS.successFg}-fg}✅ ${t(_getLang(), 'ttsDetected')}{/${COLORS.successFg}-fg}`
|
|
676
|
+
: ` {${COLORS.errorFg}-fg}⚠ ${t(_getLang(), 'noTtsFound')}{/${COLORS.errorFg}-fg}`,
|
|
651
677
|
'', // blank separator
|
|
652
678
|
'', // ← [Continue →] button here (box row 12) when TTS detected
|
|
653
679
|
]));
|
|
654
680
|
if (ttsOk) {
|
|
681
|
+
_s2ContinueBtn.setContent(_tl('continueArrowBtn'));
|
|
655
682
|
_s2ContinueBtn.show();
|
|
656
683
|
_s2ContinueBtn.focus();
|
|
657
684
|
}
|
|
@@ -685,14 +712,14 @@ export function createInstallTab(screen, services) {
|
|
|
685
712
|
const _blank = ' '.repeat(120);
|
|
686
713
|
const _trail = Array(12).fill(_blank);
|
|
687
714
|
contentBox.setContent(_c([
|
|
688
|
-
_HDR('🎤',
|
|
715
|
+
_HDR('🎤', t(_getLang(), 'providerSelection')),
|
|
689
716
|
'',
|
|
690
|
-
` {${COLORS.noticeFg}-fg}${
|
|
717
|
+
` {${COLORS.noticeFg}-fg}${t(_getLang(), 'availableProviders').padEnd(94)}{/${COLORS.noticeFg}-fg}`,
|
|
691
718
|
'',
|
|
692
719
|
...paddedItems.map(i => ` ${i}`),
|
|
693
720
|
..._trail,
|
|
694
721
|
]));
|
|
695
|
-
hintLine.setContent(
|
|
722
|
+
hintLine.setContent(` ${t(_getLang(), 'screen3Hint')}`);
|
|
696
723
|
box.focus();
|
|
697
724
|
screen.render();
|
|
698
725
|
}
|
|
@@ -705,36 +732,38 @@ export function createInstallTab(screen, services) {
|
|
|
705
732
|
const voiceId = providerService?.getActiveVoiceId?.() ?? 'en_US-amy-medium';
|
|
706
733
|
|
|
707
734
|
contentBox.setContent(_c([
|
|
708
|
-
_HDR('🎤',
|
|
735
|
+
_HDR('🎤', t(_getLang(), 'providerAndVoice')),
|
|
709
736
|
'',
|
|
710
|
-
` {${COLORS.labelFg}-fg}${'
|
|
711
|
-
` {${COLORS.labelFg}-fg}${'
|
|
737
|
+
` {${COLORS.labelFg}-fg}${`${t(_getLang(), 'providerLabel')}:`.padEnd(14)}{/${COLORS.labelFg}-fg}{${COLORS.valueFg}-fg}${provider}{/${COLORS.valueFg}-fg}`,
|
|
738
|
+
` {${COLORS.labelFg}-fg}${`${t(_getLang(), 'voiceLabel')}:`.padEnd(14)}{/${COLORS.labelFg}-fg}{${COLORS.valueFg}-fg}${voiceId}{/${COLORS.valueFg}-fg} {${COLORS.noticeFg}-fg}${t(_getLang(), 'voiceChangeHint')}{/${COLORS.noticeFg}-fg}`,
|
|
712
739
|
'',
|
|
713
|
-
_HDR('✍️',
|
|
740
|
+
_HDR('✍️', t(_getLang(), 'introText')),
|
|
714
741
|
'',
|
|
715
|
-
` {${COLORS.labelFg}-fg}${
|
|
742
|
+
` {${COLORS.labelFg}-fg}${`${t(_getLang(), 'introTextLabel')}:`.padEnd(14)}{/${COLORS.labelFg}-fg}{${COLORS.valueFg}-fg}${intro || `(${t(_getLang(), 'none')})`}{/${COLORS.valueFg}-fg}`,
|
|
716
743
|
// ↑ [Edit] button rendered inline at box row 8, left=36
|
|
717
744
|
'',
|
|
718
|
-
` {${COLORS.noticeFg}-fg}
|
|
745
|
+
` {${COLORS.noticeFg}-fg}${t(_getLang(), 'example')}:{/${COLORS.noticeFg}-fg} {${COLORS.valueFg}-fg}"${example}"{/${COLORS.valueFg}-fg}`,
|
|
719
746
|
'',
|
|
720
747
|
'',
|
|
721
748
|
'', // ← [✓ Accept & Install] button rendered as real widget here (box row 13)
|
|
722
749
|
]));
|
|
723
|
-
hintLine.setContent(
|
|
750
|
+
hintLine.setContent(` ${t(_getLang(), 'screen4Hint')}`);
|
|
751
|
+
_editBtn.setContent(_tl('editInstallBtn'));
|
|
752
|
+
_acceptBtn.setContent(_tl('acceptInstallBtn'));
|
|
724
753
|
_acceptBtn.focus();
|
|
725
754
|
screen.render();
|
|
726
755
|
}
|
|
727
756
|
|
|
728
757
|
function _renderScreen5() {
|
|
729
758
|
const header = _installError
|
|
730
|
-
? _HDR('❌',
|
|
759
|
+
? _HDR('❌', t(_getLang(), 'installationFailed'))
|
|
731
760
|
: _installComplete
|
|
732
|
-
? _HDR('✅',
|
|
733
|
-
: _HDR('⚙️',
|
|
761
|
+
? _HDR('✅', t(_getLang(), 'installComplete'))
|
|
762
|
+
: _HDR('⚙️', t(_getLang(), 'installing'));
|
|
734
763
|
|
|
735
764
|
const hint = (_installComplete || _installError)
|
|
736
|
-
?
|
|
737
|
-
:
|
|
765
|
+
? ` ${t(_getLang(), 'screen5HintDone')}`
|
|
766
|
+
: ` ${t(_getLang(), 'screen5HintWait')}`;
|
|
738
767
|
|
|
739
768
|
// Show last 18 log lines so content fits in the box
|
|
740
769
|
const MAX_LINES = 18;
|
|
@@ -778,7 +807,7 @@ export function createInstallTab(screen, services) {
|
|
|
778
807
|
_completionModalBox = null;
|
|
779
808
|
}
|
|
780
809
|
_completionModalOpen = false;
|
|
781
|
-
_screen =
|
|
810
|
+
_screen = 0;
|
|
782
811
|
box.hide();
|
|
783
812
|
_showInstallNotice('Installation Complete — Settings Saved');
|
|
784
813
|
screen.render();
|
|
@@ -793,6 +822,8 @@ export function createInstallTab(screen, services) {
|
|
|
793
822
|
_s1BeginBtn.hide(); _s1ExitBtn.hide();
|
|
794
823
|
}
|
|
795
824
|
|
|
825
|
+
// Screen 0 has no button widgets — nav is handled via key handlers
|
|
826
|
+
|
|
796
827
|
// Screen 2 continue button: hidden on other screens; _renderScreen2 manages show/focus
|
|
797
828
|
if (_screen !== 2) _s2ContinueBtn.hide();
|
|
798
829
|
|
|
@@ -842,6 +873,7 @@ export function createInstallTab(screen, services) {
|
|
|
842
873
|
setTimeout(() => {
|
|
843
874
|
if (_screen !== targetScreen) return;
|
|
844
875
|
switch (_screen) {
|
|
876
|
+
case 0: _renderScreen0(); break;
|
|
845
877
|
case 1: _renderScreen1(); break;
|
|
846
878
|
case 2: _renderScreen2(); break;
|
|
847
879
|
case 3: _renderScreen3(); break;
|
|
@@ -852,6 +884,7 @@ export function createInstallTab(screen, services) {
|
|
|
852
884
|
return;
|
|
853
885
|
}
|
|
854
886
|
switch (_screen) {
|
|
887
|
+
case 0: _renderScreen0(); break;
|
|
855
888
|
case 1: _renderScreen1(); break;
|
|
856
889
|
case 2: _renderScreen2(); break;
|
|
857
890
|
case 3: _renderScreen3(); break;
|
|
@@ -870,6 +903,12 @@ export function createInstallTab(screen, services) {
|
|
|
870
903
|
screen.key(['enter'], () => {
|
|
871
904
|
if (box.hidden || _checking) return;
|
|
872
905
|
if (_completionModalOpen) { _dismissCompletionModal(); return; } // always first
|
|
906
|
+
if (_screen === 0) { // Screen 0: apply selected language and advance
|
|
907
|
+
if (languageService) languageService.setLang(SUPPORTED_LANGUAGES[_langIdx].value);
|
|
908
|
+
_screen = 1;
|
|
909
|
+
_showCurrentScreen();
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
873
912
|
if (_screen === 1) return; // Screen 1: Enter handled by Begin/Exit buttons
|
|
874
913
|
if (_screen === 2) return; // Screen 2: Enter handled by Continue button
|
|
875
914
|
if (_screen === 4) return; // Screen 4: Enter handled by the focused button
|
|
@@ -883,7 +922,7 @@ export function createInstallTab(screen, services) {
|
|
|
883
922
|
screen.key(['escape'], () => {
|
|
884
923
|
if (box.hidden || _checking) return;
|
|
885
924
|
if (_completionModalOpen) { _dismissCompletionModal(); return; }
|
|
886
|
-
if (_screen >
|
|
925
|
+
if (_screen > 0) {
|
|
887
926
|
_screen--;
|
|
888
927
|
_showCurrentScreen();
|
|
889
928
|
} else {
|
|
@@ -899,6 +938,11 @@ export function createInstallTab(screen, services) {
|
|
|
899
938
|
|
|
900
939
|
screen.key(['up'], () => {
|
|
901
940
|
if (box.hidden) return;
|
|
941
|
+
if (_screen === 0) {
|
|
942
|
+
_langIdx = Math.max(0, _langIdx - 1);
|
|
943
|
+
_renderScreen0();
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
902
946
|
if (_screen === 3 && _deps) {
|
|
903
947
|
const providers = [];
|
|
904
948
|
if (_deps.piper) providers.push('piper');
|
|
@@ -914,16 +958,23 @@ export function createInstallTab(screen, services) {
|
|
|
914
958
|
screen.key(['left'], () => {
|
|
915
959
|
if (box.hidden || _checking) return;
|
|
916
960
|
if (_screen === 4) return;
|
|
917
|
-
if (_screen >
|
|
961
|
+
if (_screen > 0) {
|
|
918
962
|
_screen--;
|
|
919
963
|
_showCurrentScreen();
|
|
920
964
|
}
|
|
921
965
|
});
|
|
922
966
|
|
|
923
967
|
// Right arrow = go forward (same logic as Enter, without save/finish side-effects)
|
|
968
|
+
// Screen 0: → skips language selection (keeps English)
|
|
924
969
|
// Screen 1: right arrow handled by button ←/→ navigation
|
|
925
970
|
screen.key(['right'], () => {
|
|
926
971
|
if (box.hidden || _checking) return;
|
|
972
|
+
if (_screen === 0) { // → skips: keep current _lang (default 'en') and advance
|
|
973
|
+
if (languageService) languageService.setLang(SUPPORTED_LANGUAGES[_langIdx].value);
|
|
974
|
+
_screen = 1;
|
|
975
|
+
_showCurrentScreen();
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
927
978
|
if (_screen === 1) return;
|
|
928
979
|
if (_screen === 2) return; // Screen 2: → handled by Continue button
|
|
929
980
|
if (_screen === 3) { _screen++; _showCurrentScreen(); return; } // → confirms provider and advances
|
|
@@ -931,10 +982,15 @@ export function createInstallTab(screen, services) {
|
|
|
931
982
|
if (_screen === 5) return; // Screen 5: → handled by button nav
|
|
932
983
|
});
|
|
933
984
|
|
|
934
|
-
// Down arrow: Screen 3 provider nav; Screen 1 ↓ is handled by button key handlers
|
|
985
|
+
// Down arrow: Screen 0 language nav; Screen 3 provider nav; Screen 1 ↓ is handled by button key handlers
|
|
935
986
|
// (tab bar's el.key(['down']) → onFocus() focuses Begin, then button ↓ → Exit)
|
|
936
987
|
screen.key(['down'], () => {
|
|
937
988
|
if (box.hidden) return;
|
|
989
|
+
if (_screen === 0) {
|
|
990
|
+
_langIdx = Math.min(SUPPORTED_LANGUAGES.length - 1, _langIdx + 1);
|
|
991
|
+
_renderScreen0();
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
938
994
|
if (_screen === 3 && _deps) {
|
|
939
995
|
const providers = [];
|
|
940
996
|
if (_deps.piper) providers.push('piper');
|
|
@@ -961,7 +1017,9 @@ export function createInstallTab(screen, services) {
|
|
|
961
1017
|
box,
|
|
962
1018
|
|
|
963
1019
|
show() {
|
|
964
|
-
_screen =
|
|
1020
|
+
_screen = 0;
|
|
1021
|
+
_langIdx = 0;
|
|
1022
|
+
// _lang now lives in languageService — don't reset it here
|
|
965
1023
|
_screen5Announced = false;
|
|
966
1024
|
_installLog = [];
|
|
967
1025
|
_installRunning = false;
|
|
@@ -982,7 +1040,9 @@ export function createInstallTab(screen, services) {
|
|
|
982
1040
|
|
|
983
1041
|
onFocus() {
|
|
984
1042
|
// Focus the active interactive element, not just the box container
|
|
985
|
-
if (_screen ===
|
|
1043
|
+
if (_screen === 0) {
|
|
1044
|
+
box.focus(); // Screen 0 uses key handlers, no button widgets
|
|
1045
|
+
} else if (_screen === 1) {
|
|
986
1046
|
_s1BeginBtn.focus();
|
|
987
1047
|
} else if (_screen === 4) {
|
|
988
1048
|
_editBtn.focus();
|
|
@@ -997,7 +1057,7 @@ export function createInstallTab(screen, services) {
|
|
|
997
1057
|
onBlur() {},
|
|
998
1058
|
|
|
999
1059
|
getFooterText() {
|
|
1000
|
-
return
|
|
1060
|
+
return _tl('footerText');
|
|
1001
1061
|
},
|
|
1002
1062
|
|
|
1003
1063
|
getFooterColor() {
|