feat: Initial commit introducing TUI - #29
Merged
Merged
Conversation
- Updated LibraryPanel to improve status display with icons and colors. - Enhanced duration formatting and date display in the library. - Introduced new methods for formatting status and tags. - Modified LibraryScreen to include a context hint and command bar. - Added LogsScreen for live server log tailing with color-coded log levels. - Created SearchScreen for full-text search results with improved UI. - Revamped SettingsPanel for better organization and added server settings. - Improved TranscriptScreen with enhanced metadata display and speaker colors. - Removed WelcomeScreen as it was deemed unnecessary. - Introduced shared chrome components: TitleBar, ContextHint, and CommandBar. - Updated SegmentList to include speaker colors and improved formatting. - Enhanced StatusBar for better connection status display and message handling.
… main Merges the Textual-based TUI (modeless, palette-driven interface for AmicoScript) into this branch and resolves it against main's meeting- capture / transcription-defaults work in backend/api/routes/settings.py, combining both settings sets rather than dropping either. Also fixes the TUI's API client, which predates this merge and was never exercised against the real backend: most write endpoints (folders, tags, recordings, analyses, LLM settings, app settings, transcribe-by-URL) are Form-encoded, but the client posted JSON bodies, and llm settings / transcribe-url used mismatched field names (base_url vs llm_base_url, url vs source_url). Fixed to use form encoding and matching field names throughout.
…ience
- Fix Space-q on 5 screens: was labeled "Back" but ran /quit, silently
exiting the whole app instead of just quitting as intended. Relabeled
to "Quit" everywhere for consistency, and added the README-promised
Space-h ("Welcome") and Space-? ("Help") chords app-wide so both are
finally real instead of just documented.
- Add bare l/j/s shortcuts on the welcome screen, matching the README.
- Add a reusable ConfirmDialog modal and wire it into every delete path
(/delete, the palette's delete mode, and Library's `d` key) instead of
deleting immediately or just telling the user to retype a command.
- Add a persistent "N jobs running" indicator to the status bar, polled
globally from the App so it survives navigating away from /jobs.
- Replace the 10s toast /help with a proper scrollable HelpScreen listing
every command and the leader-chord cheatsheet.
- Bring JobDetailScreen in line with every other screen's chrome
(TitleBar/ContextHint/CommandBar/leader_chords) instead of a bare
Textual Header with no leader support.
- Fix README: /models and /llm were documented backwards (Whisper vs LLM
model pickers were swapped), and it claimed no audio playback despite
playback.py being fully wired into the transcript screen.
- Remove dead per-line color computation in logs.py (Log widget has
highlight=False and can't render it).
Verified via a headless Textual Pilot smoke test exercising leader
navigation, the confirm-dialog flow, and JobDetailScreen mounting.
The welcome screen's hand-drawn ASCII logo was misaligned and rendered as an illegible block of characters, clipped at the right edge of its fixed-width container. Replaced it with a simple bordered, centered card (app name + tagline + command list) using Textual's own border/align primitives instead of hand-computed block characters, so it can't drift out of alignment again. TitleBar and StatusBar both right-aligned their trailing hint text by concatenating a fixed run of literal spaces before it — reliable only at one specific terminal width, clipping the hint everywhere narrower. Rebuilt both as two real widgets (1fr left / auto right) so the layout engine handles the alignment instead of a guessed space count. Verified by rendering the actual app (headless Textual Pilot -> export_screenshot -> Chromium) rather than judging from source alone.
Plain text reads better than a stylized wordmark here.
Rendered from the actual app (headless Textual Pilot + Chromium), not a mockup.
CLI: add ./tui.sh and tui.bat launchers that install tui/requirements.txt on first run and then exec the TUI, so starting it is one command instead of two manual steps. tui/__main__.py now catches a missing dependency and prints an actionable one-liner instead of a raw traceback. Root README gains a "Terminal (TUI)" quick-start section next to the existing Docker/Local ones — previously the TUI wasn't mentioned there at all. Web: the Help modal (frontend/index.html) gained a "Prefer the terminal?" card with a ready-to-run command and a copy-to-clipboard button. The command includes --api-url only when the page isn't served from the TUI's own default, so it works unmodified against Docker/remote/custom-port setups. A real browser tab can't spawn a terminal process directly, so this is a copy-paste hand-off rather than an in-page launch — verified with a headless Playwright run that the command text and clipboard write are both correct.
Wraps command execution (the single chokepoint every slash command goes through, whether triggered from the CommandBar, a leader chord, or the palette) with push_busy()/pop_busy(), and shows an animated spinner in the status bar while any command is in flight. Also wires up GET /api/watcher/status, polled globally, so an in-progress meeting auto-capture recording is visible in the TUI too — previously only the web UI's tray badge showed it. While building this, found that the status-bar updates were silently broken since the leader-chord hint display was first written: self.app.query(StatusBar) looks like it searches the active screen, but App._get_dom_base() roots App-level queries at the App's hidden default screen, not whatever's pushed on top — so it always returned nothing. This affected the leader-armed hint (never rendered) and the "N jobs running" indicator added in an earlier pass (silently never updated either). Fixed with a status_bars() helper that queries each screen in the stack directly, and pointed every call site at it. Verified by rendering the app (headless Textual Pilot -> export_screenshot -> Chromium): the busy spinner, recording badge, job count, and the previously-broken leader-armed hint all now show correctly.
…time Job screen: each segment's text now arrives over the same SSE stream as progress (backend/core/transcription.py already pushed it as data.segment.text — the web UI used it, the TUI didn't). The job log now shows the actual words as they're produced instead of a generic "Transcribing... 00:12 / 05:30" line, so you can read along instead of waiting for the job to finish. Transcript screen: "/" opens a find box. Typing text jumps to the first matching segment and Enter cycles to the next; typing a timestamp (83, 1:23, 1:02:03) jumps straight to the segment covering that time. Fixed a focus bug hit while building this: TranscriptScreen never explicitly focused anything on mount, relying on Textual's default focus order. Adding the (initially hidden) find Input earlier in the compose tree meant it silently grabbed initial focus instead of the segment list, which would have broken plain arrow-key navigation. Fixed by explicitly focusing the segment list on mount, matching every other screen's pattern. Verified with headless Pilot: text/timestamp find-and-jump, Escape closing find without popping the screen, normal j/k navigation still working after find closes, and a mocked SSE stream confirming segment text (not just the generic progress line) reaches the job log.
The web UI can rename/move/tag individual recordings and rename/delete folders and tags; the TUI's ApiClient already wrapped most of the backend calls for this (update_recording, update_folder, delete_folder, create_tag, add_tag/remove_tag) but nothing ever called them — no command, no keybinding. Tag rename/delete had no wrapper at all. Adds: - ApiClient.update_tag() / delete_tag() - /rename <id> <name>, /move <id> [folder_id], /tag-toggle <id> - /folder new|rename|delete and /tag new|rename|delete subcommands (mirrors the existing /folder new pattern) - Library screen keys: R rename (prompt), m move (folder picker), t tag (add/remove picker) - A new PromptDialog modal (mirrors ConfirmDialog) for single-line text input, reused by rename - Ad-hoc "move to folder" and "toggle tag" pickers in palette.py, reusing the same mechanism as the existing analysis-type picker Verified with headless Pilot against a mocked ApiClient: every new command and keybinding (including the ConfirmDialog-gated folder/tag deletes and the PromptDialog-gated rename, with its pre-filled current name) drives the right API call with the right arguments.
Library: multi-select (v to toggle a row, x for a bulk-action menu — delete / export as combined markdown / move to folder / tag) closes the last big library-management gap vs. the web UI. Refactored the single-recording move/tag pickers in palette.py into shared _folder_entries()/_tag_entries() builders so the new bulk pickers (open_bulk_move_picker, open_bulk_tag_picker) don't duplicate them. Transcript: e edits a segment's text, Ctrl+R resets it to the original, a sets the speaker on just that segment, S renames a speaker everywhere in the transcript — all four backend endpoints existed and were already used by the web UI but had no TUI wrapper at all. Bug found and fixed while building the bulk-action menu: Palette's MRU (recent-selection) ranking pushed every picked entry's key into a persistent, app-wide deque and re-sorted the list by it on next open. That's fine for the main command palette, but ad-hoc menus like the new bulk-action picker are rebuilt fresh each time and reuse the same keys (e.g. "bulk:delete") across unrelated invocations — so picking "export" once silently moved "export" to the top the next time the menu opened, making a bare Enter run the wrong action. MRU-boosting is now skipped for all ad-hoc pickers (analysis-type, move, tag-toggle, bulk), which only makes sense for the persistent palette anyway. Also confirmed (while chasing why 'space' wouldn't toggle a library row's selection) that the global Space-leader intercepts the key at the App level before it ever reaches a focused widget's own bindings on any screen with leader_chords — so multi-select uses 'v' instead. Noting this because it means TranscriptScreen's existing "Space = Play/Pause" binding has likely been dead code since it was written, for the same reason; left as-is since fixing it wasn't part of this change and touches established behavior. Verified with headless Pilot against mocked ApiClients: multi-select toggling, all four bulk actions (including the ConfirmDialog-gated bulk delete), and all four segment-editing actions (including the PromptDialog pre-fill and in-place SegmentList updates without a full reload).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.