-
- {hint.title}
-
-
- {hint.description}
-
- {hint.command && (
-
- {hint.command}
-
- )}
- {hint.url && (
-
-
+ {viewModel.acceptsInput && (
+
+
+ {viewModel.inputSecret && (
+
+ Password input is sent directly to the installer terminal and is not added to
+ the log output.
)}
- ))}
-
- )}
+ )}
- {(viewModel.logs.length > 0 || viewModel.error) && (
-
-
- {viewModel.detailsOpen && (
-
- {[viewModel.error, ...viewModel.logs].filter(Boolean).join('\n')}
-
+ {manualHintsVisible && (
+
+ {viewModel.manualHints.map((hint) => (
+
+
+ {hint.title}
+
+
+ {hint.description}
+
+ {hint.command && (
+
+ {hint.command}
+
+ )}
+ {hint.url && (
+
+
+
+ )}
+
+ ))}
+
+ )}
+
+ {(viewModel.logs.length > 0 || viewModel.error) && (
+
+
+ {viewModel.detailsOpen && (
+
+ {[viewModel.error, ...viewModel.logs].filter(Boolean).join('\n')}
+
+ )}
+
)}
)}
diff --git a/src/features/tmux-installer/renderer/ui/__tests__/TmuxInstallerBannerView.test.tsx b/src/features/tmux-installer/renderer/ui/__tests__/TmuxInstallerBannerView.test.tsx
index 14e67a3f..109774c3 100644
--- a/src/features/tmux-installer/renderer/ui/__tests__/TmuxInstallerBannerView.test.tsx
+++ b/src/features/tmux-installer/renderer/ui/__tests__/TmuxInstallerBannerView.test.tsx
@@ -93,16 +93,32 @@ describe('TmuxInstallerBannerView', () => {
it('keeps Windows setup steps collapsed by default and expands them on demand', async () => {
const { host, root } = renderBanner(baseViewModel);
- expect(host.textContent).toContain('Show setup steps (2)');
+ expect(host.textContent).toContain('tmux is not installed');
+ expect(host.textContent).not.toContain('WSL is available, but no Linux distribution is installed yet.');
+ expect(host.textContent).not.toContain('Show setup steps (2)');
expect(host.textContent).not.toContain('wsl --install --no-distribution');
- const toggleButton = [...host.querySelectorAll('button')].find((button) =>
- button.textContent?.includes('Show setup steps')
+ const summaryButton = [...host.querySelectorAll('button')].find((button) =>
+ button.textContent?.includes('tmux is not installed')
);
- expect(toggleButton).toBeDefined();
+ expect(summaryButton).toBeDefined();
await act(async () => {
- toggleButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
+ summaryButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
+ await Promise.resolve();
+ });
+
+ expect(host.textContent).toContain('WSL is available, but no Linux distribution is installed yet.');
+ expect(host.textContent).toContain('Show setup steps (2)');
+ expect(host.textContent).not.toContain('Hide setup steps');
+
+ const setupToggle = [...host.querySelectorAll('button')].find((button) =>
+ button.textContent?.includes('Show setup steps')
+ );
+ expect(setupToggle).toBeDefined();
+
+ await act(async () => {
+ setupToggle?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
await Promise.resolve();
});
@@ -125,6 +141,15 @@ describe('TmuxInstallerBannerView', () => {
],
});
+ const summaryButton = [...host.querySelectorAll('button')].find((button) =>
+ button.textContent?.includes('tmux is not installed')
+ );
+ expect(summaryButton).toBeDefined();
+
+ act(() => {
+ summaryButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
+ });
+
expect(host.textContent).toContain('brew install tmux');
expect(host.textContent).not.toContain('Show setup steps');
@@ -132,4 +157,36 @@ describe('TmuxInstallerBannerView', () => {
root.unmount();
});
});
+
+ it('auto-expands when installer flow becomes active', async () => {
+ const { host, root } = renderBanner(baseViewModel);
+
+ mockUseTmuxInstallerBanner.mockReturnValue({
+ viewModel: {
+ ...baseViewModel,
+ title: 'tmux needs a restart',
+ body: 'Restart Windows before continuing.',
+ phase: 'needs_restart',
+ progressPercent: 96,
+ },
+ install: vi.fn(),
+ cancel: vi.fn(),
+ submitInput: vi.fn(),
+ refresh: vi.fn(),
+ toggleDetails: vi.fn(),
+ openExternal: vi.fn(),
+ });
+
+ await act(async () => {
+ root.render(React.createElement(TmuxInstallerBannerView));
+ await Promise.resolve();
+ });
+
+ expect(host.textContent).toContain('Restart Windows before continuing.');
+ expect(host.textContent).toContain('96%');
+
+ act(() => {
+ root.unmount();
+ });
+ });
});