diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index dd6e584..7401e38 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -183,7 +183,14 @@ jobs:
Automated release for ${{ env.RELEASE_TAG }}.
This release publishes native Tauri bundles built on macOS, Windows, and Linux runners.
- Windows/Linux builds are unsigned in this workflow.
+
+ ### Первый запуск / First launch
+
+ Сборки пока без платной подписи кода — это **не вирус** (исходники открыты, AGPL-3.0). Если ОС блокирует запуск:
+ The builds are not code-signed yet — this is **not malware** (open source, AGPL-3.0). If your OS blocks it:
+
+ - **macOS** — «не удалось проверить» / "could not verify": нажмите **Готово/Done**, затем **Системные настройки → Конфиденциальность и безопасность → Всё равно открыть** (System Settings → Privacy & Security → Open Anyway). Или: `xattr -dr com.apple.quarantine /Applications/Talkis.app`
+ - **Windows** — SmartScreen «защитил ПК» / "protected your PC": **Подробнее → Выполнить в любом случае** (More info → Run anyway).
draft: false
prerelease: ${{ contains(env.RELEASE_TAG, '-') }}
files: |
diff --git a/README.md b/README.md
index b3a5bfa..18a55fb 100644
--- a/README.md
+++ b/README.md
@@ -123,6 +123,27 @@ Download the latest build from GitHub Releases:
- [Windows x64 installer](https://github.com/SerTimBerrners-Lee/talkis/releases/latest/download/Talkis-windows-x64-setup.exe)
- [Linux x64 AppImage](https://github.com/SerTimBerrners-Lee/talkis/releases/latest/download/Talkis-linux-x64.AppImage)
+### If your OS blocks the first launch
+
+Talkis is currently distributed **without paid code signing**, so Gatekeeper (macOS) and SmartScreen (Windows) show an "unverified app" warning. This is **not malware**: the source is open (AGPL-3.0) and the builds are produced by the public GitHub Actions workflow in this repo. How to open it:
+
+**macOS** — "Apple could not verify Talkis is free of malware":
+
+1. In the dialog click **Done** (not "Move to Trash").
+2. Open **System Settings → Privacy & Security**, scroll down to *"Talkis was blocked…"* and click **Open Anyway**.
+3. Confirm with your password or Touch ID and click **Open** again.
+
+Or, in one command in Terminal (after moving it to Applications):
+
+```bash
+xattr -dr com.apple.quarantine /Applications/Talkis.app
+```
+
+**Windows** — the blue *"Windows protected your PC"* dialog:
+
+1. Click **More info**.
+2. Click **Run anyway**.
+
On first launch, grant the permissions Talkis needs:
- Microphone access for recording.
diff --git a/README.ru.md b/README.ru.md
index b6e23d1..68b384d 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -123,6 +123,27 @@ Talkis сделан для повседневной работы: IDE, чаты,
- [Windows x64 installer](https://github.com/SerTimBerrners-Lee/talkis/releases/latest/download/Talkis-windows-x64-setup.exe)
- [Linux x64 AppImage](https://github.com/SerTimBerrners-Lee/talkis/releases/latest/download/Talkis-linux-x64.AppImage)
+### Если ОС блокирует первый запуск
+
+Talkis пока распространяется **без платной подписи кода**, поэтому Gatekeeper (macOS) и SmartScreen (Windows) показывают предупреждение о «непроверённом» приложении. Это **не вирус**: исходный код открыт (AGPL-3.0), сборки собираются публичным GitHub Actions из этого репозитория. Как открыть:
+
+**macOS** — «Не удалось проверить, что приложение Talkis не содержит вредоносного ПО»:
+
+1. В диалоге нажмите **«Готово»** (не «Переместить в Корзину»).
+2. Откройте **Системные настройки → Конфиденциальность и безопасность**, пролистайте вниз до строки *«Talkis заблокирован…»* и нажмите **«Всё равно открыть»**.
+3. Подтвердите паролем или Touch ID и ещё раз нажмите **«Открыть»**.
+
+Либо одной командой в Терминале (после переноса в «Программы»):
+
+```bash
+xattr -dr com.apple.quarantine /Applications/Talkis.app
+```
+
+**Windows** — синее окно *«Система Windows защитила ваш компьютер»*:
+
+1. Нажмите **«Подробнее»**.
+2. Нажмите **«Выполнить в любом случае»**.
+
При первом запуске выдайте нужные разрешения:
- доступ к микрофону для записи;
diff --git a/docs/release/review-v0.3.2.md b/docs/release/review-v0.3.2.md
new file mode 100644
index 0000000..4f9cd4c
--- /dev/null
+++ b/docs/release/review-v0.3.2.md
@@ -0,0 +1,43 @@
+# Release Review
+
+## Release
+
+- Version: 0.3.2
+- Release branch: release/v0.3.2
+- Target tag: v0.3.2
+- Reviewer: Codex
+- Date: 2026-06-30
+
+## Scope
+
+- Key changes included in this release: single-instance focus fix for Windows/Linux, auth flow cancellation guard, subscription/account card cleanup, main-page stats glass card with rotating help/support hint, settings navigation order update, first-launch signing guidance in README/site/release notes.
+- User-facing changes: widget should no longer duplicate on repeated launch on Windows/Linux; logout should not immediately re-authenticate from stale auth flow; subscription CTA/status states are clearer; the settings menu puts general settings at the bottom.
+- Risky areas: cloud auth deep-link/polling flow, settings model tab conditional rendering, release artifact workflow text, UI layout around the main stats panel.
+
+## Checks run
+
+- `git diff --check`: passed
+- `bun run check:release`: passed
+- `TAURI_SIGNING_PRIVATE_KEY_PATH=~/.tauri/talkis-updater.key bun run build:release:macos`: failed at DMG bundling in this sandboxed environment after `.app` build; `hdiutil` reports `Cannot start hdiejectd because app is sandboxed` / `Device not configured`.
+- Native/GitHub Windows build: expected via GitHub Actions after tag
+- Native/GitHub Linux build: expected via GitHub Actions after tag
+- Additional manual checks: release diff reviewed statically; no browser/Playwright pass requested.
+
+## Manual review
+
+- Hotkey flow: no hotkey registration code changed; main screen still renders the configured hotkey on the first hint slide.
+- Onboarding permissions: no permission/onboarding flow changes in this release.
+- Widget position and notice behavior: single-instance handler now unminimizes/shows/focuses the existing widget on Windows/Linux.
+- Transcription quality and short-utterance handling: no transcription pipeline or hallucination-filter changes in this release.
+- README refreshed: README.md and README.ru.md include first-launch instructions for unsigned builds.
+
+## Findings
+
+- Blockers: local macOS DMG packaging could not be completed in the current sandboxed environment.
+- Non-blocking issues: Windows/Linux release artifacts are validated by GitHub Actions runners, not locally on this macOS machine.
+- Follow-ups after release: continue paid code-signing/certification work to reduce first-launch OS warnings.
+
+## Decision
+
+- Ready for `main` merge: no, blocked until DMG build is validated outside this sandbox or by CI.
+- Ready for tag publish: no, blocked until release artifacts are validated.
diff --git a/docs/release/trust-and-signing.md b/docs/release/trust-and-signing.md
new file mode 100644
index 0000000..89f247e
--- /dev/null
+++ b/docs/release/trust-and-signing.md
@@ -0,0 +1,87 @@
+# Доверие ОС, подпись кода и нотаризация
+
+Runbook по проблеме «после релиза все платформы расценивают Talkis как вредоносное ПО».
+
+## Диагноз (корень проблемы)
+
+Сборки Talkis **не подписаны платными сертификатами и не нотаризованы** ни под одну платформу.
+ОС реагируют на это предупреждениями о «непроверённом» приложении — это **не** реальная
+детекция вируса и **не** регрессия конкретного релиза.
+
+- Единственная «подпись» в пайплайне — ключ апдейтера `TAURI_SIGNING_PRIVATE_KEY` (minisign).
+ Он нужен только авто-апдейтеру для проверки подлинности обновлений и **не влияет на доверие ОС**.
+- **macOS** (`scripts/postprocess-macos-release.sh`): `codesign --force --deep --sign -` — это
+ **ad-hoc** подпись без Apple Developer ID и **без нотаризации**. Скачанный `.dmg` получает
+ атрибут `com.apple.quarantine`, и Gatekeeper показывает «Не удалось проверить, что приложение
+ не содержит вредоносного ПО».
+- **Windows** (`.github/workflows/release.yml`): NSIS `.exe` **полностью без подписи** → SmartScreen
+ «Система Windows защитила ваш компьютер». Бандл из нативных бинарей (`talkis-llm` = llama.cpp,
+ `talkis-ffmpeg`, whisper-STT, открывают локальные порты 8011/18200) повышает шанс эвристического
+ флага у Defender/сторонних AV.
+- **Linux**: AppImage/deb без подписи; обычно «вредоносом» не зовётся.
+
+**Почему «после нового релиза».** Пайплайн никогда не подписывал нормально (v0.3.0 был таким же).
+Каждая новая неподписанная сборка — это новый неизвестный хеш: репутация в SmartScreen/Defender
+обнуляется, а релиз пошёл к реальным пользователям, которые скачивают файл (с quarantine-битом),
+а не собирают локально.
+
+## Фаза 1 — бесплатные меры (сделано / делается)
+
+Не убирают предупреждение полностью, но снимают трение и страх у пользователей.
+
+- [x] Инструкция «как открыть» в `README.ru.md` / `README.md` (раздел «Если ОС блокирует первый запуск»).
+- [x] Та же инструкция в теле GitHub-релиза (`release.yml`, шаг *Publish GitHub release assets*).
+- [x] Блок «Первый запуск» на статической странице сайта (`site/index.html`) рядом со ссылками на загрузку.
+- [ ] Проверить сборку на [VirusTotal](https://www.virustotal.com/) — убедиться, что нет **реальных**
+ детекций (а только репутационные предупреждения). Если конкретный движок флагит — отправить
+ false-positive репорт (см. ниже).
+
+### Инструкции для пользователей
+
+**macOS** («не удалось проверить»):
+1. В диалоге — **«Готово»** (не «Переместить в Корзину»).
+2. **Системные настройки → Конфиденциальность и безопасность → «Всё равно открыть»**.
+3. Подтвердить паролем/Touch ID, ещё раз **«Открыть»**.
+ Терминал-альтернатива: `xattr -dr com.apple.quarantine /Applications/Talkis.app`
+
+> На macOS 15 Sequoia правый клик → «Открыть» больше не обходит Gatekeeper — нужен путь через
+> «Системные настройки». На Sonoma и раньше правый клик → «Открыть» ещё работает.
+
+**Windows** (SmartScreen): **«Подробнее» → «Выполнить в любом случае»**.
+Либо ПКМ по `.exe` → Свойства → галочка **«Разблокировать»** → Применить.
+
+### False-positive репорты (если есть реальная детекция)
+
+- Microsoft Defender: https://www.microsoft.com/en-us/wdsi/filesubmission (как разработчик ПО).
+- VirusTotal: открыть отчёт по файлу, у флагнувших движков использовать форму «contact / false positive».
+
+## Фаза 2 — настоящая подпись (когда будет бюджет)
+
+Полностью убирает предупреждения. Здесь же — конкретные шаги, чтобы не вспоминать заново.
+
+### macOS — Developer ID + нотаризация (~$99/год)
+
+1. Apple Developer Program → сертификат **Developer ID Application**.
+2. В CI (`release.yml`, шаг сборки macOS) задать Tauri-переменные:
+ - `APPLE_CERTIFICATE` (base64 .p12), `APPLE_CERTIFICATE_PASSWORD`
+ - `APPLE_SIGNING_IDENTITY` (`Developer ID Application: … (TEAMID)`)
+ - `APPLE_ID`, `APPLE_PASSWORD` (app-specific), `APPLE_TEAM_ID`
+ Tauri тогда сам подпишет Developer ID и **нотаризует** бандл.
+3. Убрать/заменить ad-hoc `postprocess-macos-release.sh`. Если оставляем ручную подпись —
+ **не использовать `--deep`** (deprecated, ломает подпись бандла с сайдкарами): подписывать
+ вложенные бинарники по отдельности (inside-out), приложение — последним, с
+ `--options runtime --timestamp --entitlements entitlements.plist`, затем `xcrun notarytool submit`
+ и `xcrun stapler staple`.
+
+### Windows — подпись installer'а
+
+- Варианты: **EV-сертификат** (мгновенная репутация SmartScreen, дорого), обычный **OV-сертификат**
+ (дешевле, но репутация копится), либо **Azure Trusted Signing** (~$10/мес, нужна верифицированная
+ организация/личность).
+- В Tauri NSIS прописать `signCommand` (signtool / Azure) — подписывать `.exe`/`.msi`.
+
+### Линки
+
+- Tauri code signing: https://v2.tauri.app/distribute/sign/
+- Apple notarization: https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution
+- Azure Trusted Signing: https://learn.microsoft.com/azure/trusted-signing/
diff --git a/package.json b/package.json
index 3399091..833d5f0 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "talkis",
"private": true,
- "version": "0.3.1",
+ "version": "0.3.2",
"license": "AGPL-3.0-or-later",
"type": "module",
"packageManager": "bun@1.2.13",
diff --git a/site/index.html b/site/index.html
index 4bad313..9a526ac 100644
--- a/site/index.html
+++ b/site/index.html
@@ -269,6 +269,27 @@
left: 0;
color: rgba(255,255,255,0.4);
}
+
+ .first-launch-link {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 16px 0;
+ border-bottom: 1px solid rgba(0,0,0,0.08);
+ font-size: 0.72rem;
+ font-weight: 800;
+ text-transform: uppercase;
+ letter-spacing: 0.14em;
+ color: #000;
+ }
+ .first-launch-link:hover { opacity: 0.55; }
+ .first-launch-copy {
+ font-size: 0.85rem;
+ line-height: 1.75;
+ color: #555;
+ }
+ .first-launch-copy strong { color: #000; }
@@ -288,7 +309,7 @@
СкоростьВарианты
-
+ скачать
@@ -306,16 +327,56 @@
Мгновенная трансформация ваших мыслей в структурированный текст. Работает везде, где есть курсор.
+ Talkis пока распространяется без платной подписи кода, поэтому Gatekeeper и SmartScreen могут показать предупреждение о непроверенном приложении. Это репутационная проверка нового файла, а не признак вируса: исходный код открыт, а сборки создаются публичным GitHub Actions.
+
+
+
+
Как открыть
+
+ macOS: нажмите «Готово», затем откройте Системные настройки → Конфиденциальность и безопасность → «Всё равно открыть».
+ Windows: в окне SmartScreen нажмите «Подробнее» → «Выполнить в любом случае».
+
+
+
+
+
+
@@ -429,7 +490,7 @@
-
+ Скачать приложение
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 33762fc..ffae187 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -4969,7 +4969,7 @@ dependencies = [
[[package]]
name = "talkis"
-version = "0.3.1"
+version = "0.3.2"
dependencies = [
"base64 0.22.1",
"block2 0.6.2",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index a85daf0..81932fc 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "talkis"
-version = "0.3.1"
+version = "0.3.2"
description = "Talkis - Voice to Text Desktop Widget"
authors = ["trixter"]
license = "AGPL-3.0-or-later"
diff --git a/src-tauri/binaries/talkis-llm-aarch64-apple-darwin b/src-tauri/binaries/talkis-llm-aarch64-apple-darwin
index 222796d..dfa32fd 100644
Binary files a/src-tauri/binaries/talkis-llm-aarch64-apple-darwin and b/src-tauri/binaries/talkis-llm-aarch64-apple-darwin differ
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 3a4aad3..0381abc 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -21,7 +21,6 @@ use commands::{
use tauri::{Emitter, Manager};
use tauri_plugin_deep_link::DeepLinkExt;
-#[cfg_attr(mobile, tauri::mobile_entry_point)]
/// Bring the already-running instance to front when a second launch is blocked
/// by the single-instance plugin (Windows/Linux only).
#[cfg(any(windows, target_os = "linux"))]
@@ -36,11 +35,13 @@ fn focus_existing_instance(app: &tauri::AppHandle) {
}
if let Some(win) = app.get_webview_window("widget") {
+ let _ = win.unminimize();
let _ = win.show();
let _ = win.set_focus();
}
}
+#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
#[allow(unused_mut)]
let mut builder = tauri::Builder::default();
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 3da5ea2..7197b7e 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -2,7 +2,7 @@
"$schema": "https://schema.tauri.app/config/2",
"productName": "Talkis",
"mainBinaryName": "Talkis",
- "version": "0.3.1",
+ "version": "0.3.2",
"identifier": "com.trixter.talkis",
"build": {
"beforeDevCommand": "bun run prepare:sidecars && bun run dev",
diff --git a/src/components/TranscriptionStatsPanel.tsx b/src/components/TranscriptionStatsPanel.tsx
index ec1d419..7465bbc 100644
--- a/src/components/TranscriptionStatsPanel.tsx
+++ b/src/components/TranscriptionStatsPanel.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
-import type { ReactElement } from "react";
+import type { ReactElement, ReactNode } from "react";
import { listen } from "@tauri-apps/api/event";
import { logError } from "../lib/logger";
@@ -39,6 +39,10 @@ interface StatItem {
value: string;
}
+interface TranscriptionStatsPanelProps {
+ footer?: ReactNode;
+}
+
function buildStats(view: TranscriptionStatsView, t: TranslateFn): StatItem[] {
return [
{
@@ -59,7 +63,9 @@ function buildStats(view: TranscriptionStatsView, t: TranslateFn): StatItem[] {
];
}
-export function TranscriptionStatsPanel(): ReactElement {
+export function TranscriptionStatsPanel({
+ footer,
+}: TranscriptionStatsPanelProps): ReactElement {
const { t } = useI18n();
const [view, setView] = useState(() => getEmptyView());
@@ -95,31 +101,59 @@ export function TranscriptionStatsPanel(): ReactElement {
return (
- {stats.map(({ key, label, value }) => (
+