From ffb4428e52400ea1d0aef9c3e3b3d84ccca49d3c Mon Sep 17 00:00:00 2001 From: Trevin Chow Date: Sun, 19 Apr 2026 01:23:05 -0700 Subject: [PATCH] feat(windows): add Open data folder button to General tab (#6) Users asked for a one-click way to grab `settings.json`, audio recordings, and the history DB out of `%APPDATA%\FreeFlow` when sharing troubleshooting evidence with support. A new **Data Folder** section at the bottom of the General tab (below Custom Vocabulary) has a short label telling the reader where the files live and an *Open data folder* button that opens the folder in Explorer. Implementation: - New `_open_data_folder` handler calls `os.startfile(str(Config.app_ data_dir()))`. `Config._app_data_dir()` already mkdir's the folder at import time, so the path always exists by the time the button is clickable. `os.startfile` hands the path to the shell and returns, so the settings window stays responsive per the acceptance criteria. - Failure path: if `os.startfile` raises `OSError` (e.g. shell can't open the path), a `messagebox.showerror` with the settings window as parent reports the error without crashing the app. Acceptance criteria from #6: - [x] Button opens Explorer at the FreeFlow data folder - [x] Works when the folder already exists (always, per `_app_data_dir`) - [x] Does not block the settings window while Explorer is open (`os.startfile` is non-blocking) Closes #6 --- windows/freeflow/settings_window.py | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/windows/freeflow/settings_window.py b/windows/freeflow/settings_window.py index d9a5894e..66b8f19f 100644 --- a/windows/freeflow/settings_window.py +++ b/windows/freeflow/settings_window.py @@ -1,4 +1,5 @@ """Settings window with General and Run Log tabs (tkinter).""" +import os import threading import tkinter as tk from tkinter import ttk, messagebox @@ -7,6 +8,7 @@ import sounddevice as sd +from .config import Config from .transcription_service import TranscriptionService from .models import HotkeyOption @@ -191,6 +193,40 @@ def _on_mousewheel(event: tk.Event) -> None: foreground="#9ca3af", ).pack(anchor=tk.W, padx=10, pady=(0, 8)) + # --- Data Folder --- + data_frame = ttk.LabelFrame(scroll_frame, text="Data Folder") + data_frame.pack(fill=tk.X, **pad) + + ttk.Label( + data_frame, + text="Settings, audio recordings, and history DB live in %APPDATA%\\FreeFlow.", + font=("Segoe UI", 9), + foreground="#6b7280", + ).pack(anchor=tk.W, padx=10, pady=(5, 5)) + + ttk.Button( + data_frame, + text="Open data folder", + command=self._open_data_folder, + ).pack(anchor=tk.W, padx=10, pady=(0, 8)) + + def _open_data_folder(self) -> None: + """Open the FreeFlow data folder in Explorer. + + Config._app_data_dir() mkdir's the folder at import time, so it + always exists by the time the user clicks this button. os.startfile + returns immediately after handing the path to the shell, so the + settings window stays responsive while Explorer is open. + """ + try: + os.startfile(str(Config.app_data_dir())) + except OSError as e: + messagebox.showerror( + "Open data folder", + f"Could not open the data folder:\n{e}", + parent=self._root, + ) + def _build_snippets_tab(self) -> None: """Build the Snippets tab for managing voice shortcuts.""" # Header