Use border-title panels (General, Email Notifications, SSH, Web
Dashboard) instead of TabbedContent for a cleaner visual layout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Organize settings into General, Email, SSH, and Web Dashboard tabs
using TabbedContent. Add test-email CLI command and button that
auto-saves settings then sends a test email via SMTP.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Full rename of all user-facing CLI command names and flags across
the entire codebase: bin/gniza, TUI screens, schedule cron generation,
README, and DOCUMENTATION.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace global last-run (based on log file mtime) with per-schedule
LAST_RUN field in the schedule config. New scheduled-run CLI command
runs backup and stamps LAST_RUN only on success.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename all user-facing strings: Target→Source, Remote→Destination across TUI, docs, web dashboard, bash scripts, config examples, and README
- Add go-to-path search input to both FolderPicker and RemoteFolderPicker
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename Targets → Sources, Remotes → Destinations across all screens
- Reorganize main menu with logical groupings and separators
- Add Browse button to Base path field in remote editor (local + SSH)
- Fix RichLog IndexError crash when compositor renders with y=-1
- Fix _sync_crontab accidentally wiping crontab via premature remove
- Per-target locking and auto-create local remote base directory
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Browse button now opens an SSH directory tree picker when source type is
SSH, using the configured credentials. Folders input and Browse button
placed on same row so they're visible without scrolling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rsync exit 23 (partial transfer) now triggers an automatic retry to recover
failed files before accepting with a warning. Also adds a task counter next
to the clock in the header bar showing running job count.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add source.sh for remote source backup support
- Add responsive DocsPanel with layout adaptations for narrow screens
- Running tasks log viewer now shows last 100 lines (tail -f style)
- Add incremental backup explanation to README
- Update backup, transfer, schedule, and snaplog modules
- Add MCP config and logo asset
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The app-level on_resize/on_screen_resume didn't fire on initial
screen push. Now DocsPanel handles its own responsive layout
via on_mount and on_resize, ensuring it works immediately.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The auto-hide threshold (100 cols) was hiding the panel before
the vertical layout logic could apply. Now:
- >= 80 cols: panel on the right (horizontal)
- 45-79 cols: panel at the bottom (vertical, max 40% height)
- < 45 cols: panel hidden
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On mobile/narrow terminals the .screen-with-docs container
switches from horizontal to vertical layout, placing the
help panel below the main content instead of beside it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Target the xterm canvas element with WheelEvent including proper
clientX/clientY coordinates so xterm.js forwards scroll events to
the Textual app for container-level scrolling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WheelEvent approach didn't work. Now dispatches ArrowUp/ArrowDown
KeyboardEvents to the xterm helper textarea, which xterm forwards
to the Textual app as actual key presses.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Translate touch swipe gestures into wheel events dispatched to
the xterm terminal, enabling scrolling on touch devices.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When in vertical layout (narrow screens), align content to top
and enable vertical scrolling so the menu list isn't cut off.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Below 100 cols: stack logo on top, menu below (vertical)
- Below 48 cols (~290px): hide logo entirely, show only menu
- 100+ cols: original side-by-side horizontal layout
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Custom textual-serve template with viewport meta tag, full-viewport
terminal sizing, and auto font-size for mobile (<768px)
- Fix web install-service/remove-service/status to handle user-mode
systemd (systemctl --user) alongside root mode
- Add 'gniza uninstall' command that runs the uninstall script
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Auth middleware now skips /static/ paths and WebSocket upgrades,
fixing the blank splash screen (xterm.css and textual.js were 401'd)
- Uninstall script now properly stops and removes the user systemd
service (~/.config/systemd/user/gniza-web.service) in user mode
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Auto-hide docs panel when terminal < 100 cols, respects manual F1 toggle
- Hide logo on main menu when terminal < 80 cols
- DataTable: flexible height (auto with min/max) instead of fixed 12
- Dialogs, pickers, wizard: percentage-based widths with max-width caps
- Menu list: flexible height and width constraints
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Let Rich handle word wrapping naturally instead of breaking lines
at fixed widths, which caused mid-word cuts in narrow panels.
Also use NoMatches instead of bare Exception in toggle handler.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split each screen into two columns: existing controls on the left,
a DocsPanel with Rich-markup documentation on the right (30% width).
Press F1 to toggle the help panel on/off from any screen.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rsync --info=progress2 uses \r without \n, so the entire progress
stream is one long line. Split on both \r and \n to parse each
progress update independently.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add --info=progress2 --no-inc-recursive to rsync for overall progress
- Parse rsync progress output (percentage, speed, ETA) from log file
- Show ProgressBar widget and progress label below buttons
- Progress bar auto-hides when job finishes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
View Log now displays the log in a RichLog panel below the buttons
instead of opening a modal screen. The log tails the file in real-time
with a 0.3s poll interval while the job is running.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The poll timer was reading from job.output in memory, which depended on
run_job's async task populating it. Now OperationLog reads new content
directly from the log file using seek, making it independent of the
async task. Also store the asyncio task reference to prevent GC.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detects status from log content: Success (backup completed, no errors),
Failed (ERROR/FATAL markers), OK (lock released), Interrupted (no clean exit).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
asyncio's SubprocessTransport sends SIGKILL to child processes during
event loop cleanup, killing the gniza bash wrapper when the TUI exits.
Switch to subprocess.Popen which has no such cleanup behavior, allowing
backup jobs to continue running after the TUI is closed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The settings screen wrote WORK_DIR="/usr/local/gniza/workdir" (root-mode
path) into gniza.conf. load_config then overrode the correct user-mode
path set by detect_mode, causing mktemp and rsync log redirects to fail
with Permission denied — crashing the bash script while rsync continued
as an orphan.
- Remove WORK_DIR from AppSettings model and settings screen
- Re-run detect_mode after config load to restore correct paths
- Removed stale WORK_DIR from user's gniza.conf
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Save finished jobs to registry (24h TTL) so they survive TUI restart
- Fix PermissionError in PID check incorrectly marking alive processes as dead
- Handle CancelledError explicitly to preserve running status on TUI exit
- Tail log files for reconnected running jobs instead of showing stale output
- Detect actual return code from log content; show "?" for unknown status
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The subprocess stdout was a pipe to the TUI. When the TUI exited, the
pipe broke (SIGPIPE) and killed the backup process. Now the subprocess
writes to a log file in WORK_DIR, and the TUI tails it for live
display. When the TUI exits, the subprocess keeps running because it
writes to a file, not a pipe. On restart, the log file is loaded to
show output for reconnected or finished background jobs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously _load_registry silently dropped dead PIDs, so jobs that
completed in the background were invisible on restart. Now dead PIDs
are loaded as 'success' status so the user sees they completed. The
registry is cleaned up after loading.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously action_quit() killed all running jobs before exiting,
making it impossible to reconnect to them on restart. Now shows a
confirmation dialog when jobs are running and lets them continue
in the background.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Jobs are now saved to gniza-jobs.json in WORK_DIR when they start and
finish. On TUI restart, the registry is loaded and PIDs are checked —
still-running jobs appear in the Running Tasks screen and can be
killed. Reconnected jobs are polled every second to detect completion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Jobs were tied to screen @work tasks, so switch_screen cancelled the
worker and lost the process reference. Now start_job uses
asyncio.create_task so jobs survive screen changes and can be killed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SIGTERM was being ignored by child processes. Use SIGKILL via the
actual process group ID, with proc.kill() as fallback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Start CLI subprocesses in their own session so SIGTERM via
os.killpg reaches child processes (rsync, etc.) not just the shell.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>