Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 12 additions & 37 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -657,50 +657,25 @@ body {
border-radius: 10px;
}

/* What's New Section */
.whats-new-section {
padding: 0 20px 20px;
}

.changelog-list {
.home-links {
display: flex;
flex-direction: column;
gap: 2px;
background: var(--bg-card);
border-radius: 12px;
overflow: hidden;
}

.changelog-item {
display: flex;
gap: 16px;
padding: 14px 16px;
background: var(--bg-sidebar);
gap: 18px;
margin-top: 12px;
padding: 0 4px;
}

.changelog-date {
.home-link {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text-muted);
min-width: 70px;
flex-shrink: 0;
}

.changelog-content {
display: flex;
flex-direction: column;
gap: 4px;
}

.changelog-title {
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
text-decoration: none;
transition: color 0.15s;
}

.changelog-desc {
font-size: 12px;
color: var(--text-muted);
line-height: 1.4;
.home-link:hover {
color: var(--text-secondary);
}

.home-download {
Expand Down
21 changes: 20 additions & 1 deletion frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { act, fireEvent, render, screen, waitFor, within } from '@testing-librar
import { beforeEach, describe, expect, it, vi } from 'vitest';
import * as Api from '../wailsjs/go/main/App';
import App from './App';
import { EventsEmit, listenerCount, resetListeners } from './test/runtimeMock';
import { EventsEmit, listenerCount, openedUrls, resetListeners } from './test/runtimeMock';
import { MODELS, downloadState } from './test/fixtures';
import { IDLE_DOWNLOAD } from './modelTypes';

Expand Down Expand Up @@ -64,6 +64,25 @@ describe('App model integration', () => {
expect(listenerCount('downloadComplete')).toBe(0);
});

it('Home lists Browse models second and drops the changelog', async () => {
setup();
await screen.findByText('Get started');
const titles = Array.from(document.querySelectorAll('.action-list .action-title')).map((el) => el.textContent);
expect(titles).toEqual(['Start recording', 'Browse models', 'Settings', 'View history']);
expect(document.querySelector('.whats-new-section')).toBeNull();
expect(document.querySelector('.changelog-list')).toBeNull();
});

it('Home opens release notes and docs in the system browser', async () => {
setup();
fireEvent.click(await screen.findByRole('link', { name: /^What's new/ }));
fireEvent.click(screen.getByRole('link', { name: /^Docs/ }));
expect(openedUrls).toEqual([
'https://github.com/ApplauseLab/yap/releases',
'https://applauselab.ai/docs/yap/',
]);
});

it('Settings summarises the active model and links into the library', async () => {
setup();
fireEvent.click(await screen.findByRole('button', { name: /^Settings/ }));
Expand Down
75 changes: 37 additions & 38 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,29 @@ import {
Quit,
IsOnboardingCompleted,
} from '../wailsjs/go/main/App';
import { EventsOn, LogDebug, LogError, LogInfo } from '../wailsjs/runtime/runtime';
import { BrowserOpenURL, EventsOn, LogDebug, LogError, LogInfo } from '../wailsjs/runtime/runtime';

const RELEASES_URL = 'https://github.com/ApplauseLab/yap/releases';
const DOCS_URL = 'https://applauselab.ai/docs/yap/';

// Anchors keep the href for hover/keyboard affordances, but the webview must not
// navigate away from the app, so the click is handed to the system browser.
function HomeLink({ href, label }: { href: string; label: string }) {
return (
<a
className="home-link"
href={href}
onClick={(event) => { event.preventDefault(); BrowserOpenURL(href); }}
>
{label}
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
<polyline points="15 3 21 3 21 9"/>
<line x1="10" y1="14" x2="21" y2="3"/>
</svg>
</a>
);
}

interface AppState {
state: string;
Expand Down Expand Up @@ -907,30 +929,30 @@ function App() {
)}
</div>

<div className="action-item" onClick={() => setCurrentPage('settings')}>
<div className="action-item" onClick={() => openModelsLibrary()}>
<div className="action-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="3"/>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
<line x1="12" y1="22.08" x2="12" y2="12"/>
</svg>
</div>
<div className="action-content">
<span className="action-title">Settings</span>
<span className="action-desc">Configure provider, audio device, hotkeys, and more</span>
<span className="action-title">Browse models</span>
<span className="action-desc">Compare, download, and switch transcription models</span>
</div>
</div>

<div className="action-item" onClick={() => openModelsLibrary()}>
<div className="action-item" onClick={() => setCurrentPage('settings')}>
<div className="action-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
<line x1="12" y1="22.08" x2="12" y2="12"/>
<circle cx="12" cy="12" r="3"/>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>
</div>
<div className="action-content">
<span className="action-title">Browse models</span>
<span className="action-desc">Compare, download, and switch transcription models</span>
<span className="action-title">Settings</span>
<span className="action-desc">Configure provider, audio device, hotkeys, and more</span>
</div>
</div>

Expand All @@ -947,33 +969,10 @@ function App() {
</div>
</div>
</div>
</div>

{/* What's New Section */}
<div className="whats-new-section">
<h3 className="section-title">What's new</h3>
<div className="changelog-list">
<div className="changelog-item">
<span className="changelog-date">May 2026</span>
<div className="changelog-content">
<span className="changelog-title">Native overlay with waveform</span>
<span className="changelog-desc">Recording overlay now shows above all apps with animated waveform visualization.</span>
</div>
</div>
<div className="changelog-item">
<span className="changelog-date">May 2026</span>
<div className="changelog-content">
<span className="changelog-title">Auto-paste transcriptions</span>
<span className="changelog-desc">Transcribed text is automatically pasted into your active text field.</span>
</div>
</div>
<div className="changelog-item">
<span className="changelog-date">May 2026</span>
<div className="changelog-content">
<span className="changelog-title">Usage statistics</span>
<span className="changelog-desc">Track your words per minute, time saved, and weekly usage.</span>
</div>
</div>
<div className="home-links">
<HomeLink href={RELEASES_URL} label="What's new" />
<HomeLink href={DOCS_URL} label="Docs" />
</div>
</div>

Expand Down
7 changes: 7 additions & 0 deletions frontend/src/test/runtimeMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,15 @@ export function listenerCount(eventName: string): number {
return listeners.get(eventName)?.size ?? 0;
}

export const openedUrls: string[] = [];

export function BrowserOpenURL(url: string): void {
openedUrls.push(url);
}

export function resetListeners(): void {
listeners.clear();
openedUrls.length = 0;
}

export const LogInfo = (_message: string) => {};
Expand Down
Loading