Skip to content
This repository was archived by the owner on Apr 5, 2026. It is now read-only.
Closed
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
2 changes: 2 additions & 0 deletions .raywonder-sync/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.local/*
!.local/.gitkeep
Empty file added .raywonder-sync/.local/.gitkeep
Empty file.
14 changes: 14 additions & 0 deletions .raywonder-sync/LAYOUT_STANDARD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Raywonder Repo Layout Standard

Use a simple top-level split:

- `apps/` for user-facing apps grouped by OS.
- `servers/` for API, signal, relay, and other backend services.

Required starter folders:

- `apps/macos/mac-app/`
- `apps/windows/windows-app/`
- `servers/api/`
- `servers/signal/`
- `servers/windows/` (if Windows hosts server-side roles)
20 changes: 20 additions & 0 deletions .raywonder-sync/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Raywonder Project Sync

This folder links this project to the shared private `.GITHUB` automation repo.

## Purpose
- Keep this project aligned with shared Raywonder governance/workflow tooling.
- Provide per-OS entrypoints for humans and agents.
- Keep machine-specific values local-only in `.local/`.

## Entrypoints
- Windows: `windows/sync-from-dotgithub.bat`
- macOS: `macos/sync-from-dotgithub.sh`
- WSL/Linux: `wsl/sync-from-dotgithub.sh`

## Local-only files
- `.local/*` is intentionally ignored by git.

## Layout helpers
- `LAYOUT_STANDARD.md` defines the OS/app/server folder standard.
- `apply-layout.sh` and `apply-layout.bat` create the standard starter folders in the repo root.
18 changes: 18 additions & 0 deletions .raywonder-sync/apply-layout.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@echo off
setlocal
set "REPO=%~dp0.."

mkdir "%REPO%\apps\macos\mac-app" 2>nul
mkdir "%REPO%\apps\windows\windows-app" 2>nul
mkdir "%REPO%\servers\api" 2>nul
mkdir "%REPO%\servers\signal" 2>nul
mkdir "%REPO%\servers\windows" 2>nul

type nul > "%REPO%\apps\macos\mac-app\.gitkeep"
type nul > "%REPO%\apps\windows\windows-app\.gitkeep"
type nul > "%REPO%\servers\api\.gitkeep"
type nul > "%REPO%\servers\signal\.gitkeep"
type nul > "%REPO%\servers\windows\.gitkeep"

echo Layout folders ensured in: %REPO%
exit /b 0
20 changes: 20 additions & 0 deletions .raywonder-sync/apply-layout.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

mkdir -p \
"$REPO_ROOT/apps/macos/mac-app" \
"$REPO_ROOT/apps/windows/windows-app" \
"$REPO_ROOT/servers/api" \
"$REPO_ROOT/servers/signal" \
"$REPO_ROOT/servers/windows"

touch \
"$REPO_ROOT/apps/macos/mac-app/.gitkeep" \
"$REPO_ROOT/apps/windows/windows-app/.gitkeep" \
"$REPO_ROOT/servers/api/.gitkeep" \
"$REPO_ROOT/servers/signal/.gitkeep" \
"$REPO_ROOT/servers/windows/.gitkeep"

echo "Layout folders ensured in: $REPO_ROOT"
26 changes: 26 additions & 0 deletions .raywonder-sync/macos/sync-from-dotgithub.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
TOOLS1="$HOME/DEV/APPS/.GITHUB/raywonder-repo-bootstrap"
TOOLS2="$HOME/dev/apps/.GITHUB/raywonder-repo-bootstrap"
TOOLS=""

if [[ -x "$TOOLS1/run-repo-bootstrap.bat" || -f "$TOOLS1/run-repo-bootstrap.bat" ]]; then
TOOLS="$TOOLS1"
elif [[ -x "$TOOLS2/run-repo-bootstrap.bat" || -f "$TOOLS2/run-repo-bootstrap.bat" ]]; then
TOOLS="$TOOLS2"
fi

if [[ -z "$TOOLS" ]]; then
echo "Could not find raywonder-repo-bootstrap tooling."
exit 1
fi

# On macOS call PowerShell updater directly if available; otherwise do report-only.
PS_SCRIPT="$TOOLS/scripts/pull_and_fix_repo.ps1"
if command -v pwsh >/dev/null 2>&1; then
pwsh -NoProfile -ExecutionPolicy Bypass -File "$PS_SCRIPT" -RepoRoot "$REPO_ROOT"
else
echo "pwsh not found; skipping PowerShell repo update."
fi
14 changes: 14 additions & 0 deletions .raywonder-sync/windows/sync-from-dotgithub.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@echo off
setlocal
set "REPO=%~dp0..\..\.."
set "TOOLS=%USERPROFILE%\git\raywonder\.github\raywonder-repo-bootstrap"
if not exist "%TOOLS%\run-repo-bootstrap.bat" set "TOOLS=%USERPROFILE%\dev\apps\.GITHUB\raywonder-repo-bootstrap"
if not exist "%TOOLS%\run-repo-bootstrap.bat" (
echo Could not find raywonder-repo-bootstrap tooling.
echo Expected in one of:
echo %USERPROFILE%\git\raywonder\.github\raywonder-repo-bootstrap
echo %USERPROFILE%\dev\apps\.GITHUB\raywonder-repo-bootstrap
exit /b 1
)
call "%TOOLS%\run-repo-bootstrap.bat" "%REPO%"
exit /b %errorlevel%
24 changes: 24 additions & 0 deletions .raywonder-sync/wsl/sync-from-dotgithub.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
TOOLS1="/mnt/c/Users/$USER/git/raywonder/.github/raywonder-repo-bootstrap"
TOOLS2="/mnt/c/Users/$USER/dev/apps/.GITHUB/raywonder-repo-bootstrap"
TOOLS=""

if [[ -f "$TOOLS1/run-repo-update.bat" ]]; then
TOOLS="$TOOLS1"
elif [[ -f "$TOOLS2/run-repo-update.bat" ]]; then
TOOLS="$TOOLS2"
fi

if [[ -z "$TOOLS" ]]; then
echo "Could not find raywonder-repo-bootstrap tooling."
exit 1
fi

if command -v powershell.exe >/dev/null 2>&1; then
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$(wslpath -w "$TOOLS/scripts/pull_and_fix_repo.ps1")" -RepoRoot "$(wslpath -w "$REPO_ROOT")"
else
echo "powershell.exe not found in WSL PATH; skipping update."
fi
30 changes: 27 additions & 3 deletions GUI/commits.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from models.repository import Repository
from models.commit import Commit
from . import theme
from .wx_safety import safe_raise


MAX_BRANCHES_DISPLAY = 50
Expand All @@ -27,6 +28,7 @@ def __init__(self, parent, repo: Repository):
self.filtered_branches = [] # Currently displayed branches
self.current_branch = None
self.initial_load = True # Track first load for focus
self._open_view_dialog = None

title = f"Commits - {repo.full_name}"
wx.Dialog.__init__(self, parent, title=title, size=(900, 600))
Expand Down Expand Up @@ -233,6 +235,8 @@ def update_list(self, commits):
else:
for commit in commits:
self.commits_list.Append(commit.format_display())
self.commits_list.SetSelection(0)
self.show_commit_preview(commits[0])

# Focus on commits list only on initial load
if self.initial_load:
Expand Down Expand Up @@ -274,9 +278,26 @@ def on_view(self, event):
"""View commit details in a dialog."""
commit = self.get_selected_commit()
if commit:
if self._open_view_dialog:
try:
if self._open_view_dialog.IsShown():
safe_raise(self._open_view_dialog)
return
except RuntimeError:
self._open_view_dialog = None
dlg = ViewCommitDialog(self, self.repo, commit)
dlg.ShowModal()
dlg.Destroy()
if platform.system() == "Darwin":
# Nested modal dialogs can crash wx on macOS; keep this modeless.
self._open_view_dialog = dlg
def _clear_ref(_event):
self._open_view_dialog = None
dlg.Destroy()
dlg.Bind(wx.EVT_CLOSE, _clear_ref)
dlg.Show()
wx.CallAfter(safe_raise, dlg)
else:
dlg.ShowModal()
dlg.Destroy()

def on_copy_sha(self, event):
"""Copy commit SHA to clipboard."""
Expand Down Expand Up @@ -317,7 +338,10 @@ def on_key(self, event):
"""Handle key events."""
key = event.GetKeyCode()
if key == wx.WXK_RETURN:
self.on_view(None)
if platform.system() == "Darwin":
wx.CallAfter(self.on_view, None)
else:
self.on_view(None)
else:
event.Skip()

Expand Down
63 changes: 57 additions & 6 deletions GUI/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from application import get_app
from models.repository import Repository
from version import APP_NAME
from .wx_safety import safe_raise

# Global hotkey support (Windows only)
if platform.system() != "Darwin":
Expand Down Expand Up @@ -279,6 +280,8 @@ def init_menu(self):
view_menu.AppendSeparator()
m_repo_sync_now = view_menu.Append(-1, self._menu_label("Run Repo Sync Now", "Ctrl+Shift+Y"), "Run configured repository sync now")
self.Bind(wx.EVT_MENU, self.on_repo_sync_now, m_repo_sync_now)
m_repo_sync_external = view_menu.Append(-1, self._menu_label("Sync External Local Repo...", "Ctrl+Shift+E"), "Sync any local git repository path")
self.Bind(wx.EVT_MENU, self.on_repo_sync_external, m_repo_sync_external)
m_mark_all_read = view_menu.Append(-1, self._menu_label("Mark All Notifications Read", "Ctrl+Shift+R"), "Mark all notifications as read")
self.Bind(wx.EVT_MENU, self.on_mark_all_notifications_read, m_mark_all_read)
menu_bar.Append(view_menu, "&View")
Expand Down Expand Up @@ -372,7 +375,12 @@ def on_list_key_hook(self, event):
"""Handle key events in list (using CHAR_HOOK for reliability)."""
key = event.GetKeyCode()
if key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER:
self.on_view_repo(None)
# On macOS, invoking modal dialogs directly from key hooks can crash
# in wxWidgets Cocoa. Defer to the next event-loop turn.
if platform.system() == "Darwin":
wx.CallAfter(self.on_view_repo, None)
else:
self.on_view_repo(None)
else:
event.Skip()

Expand All @@ -389,7 +397,10 @@ def on_feed_list_key_hook(self, event):
"""Handle key events in feed list."""
key = event.GetKeyCode()
if key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER:
self.on_open_feed_event(None)
if platform.system() == "Darwin":
wx.CallAfter(self.on_open_feed_event, None)
else:
self.on_open_feed_event(None)
else:
event.Skip()

Expand Down Expand Up @@ -780,6 +791,40 @@ def on_repo_sync_now(self, event):
"""Trigger repository sync immediately."""
self.run_repo_sync_background(manual=True)

def on_repo_sync_external(self, event):
"""Prompt for a local git repository path and sync it directly."""
default_path = self.app.prefs.git_path if os.path.isdir(self.app.prefs.git_path) else os.path.expanduser("~")
dlg = wx.DirDialog(
self._get_dialog_parent(),
"Select local git repository folder",
defaultPath=default_path,
style=wx.DD_DEFAULT_STYLE
)
if dlg.ShowModal() != wx.ID_OK:
dlg.Destroy()
return
repo_path = dlg.GetPath()
dlg.Destroy()

self.status_bar.SetStatusText(f"Syncing external repo: {repo_path}")

def do_sync():
result = self.app.repo_sync.sync_path(repo_path, repo_label=repo_path, auto_pull=True, auto_push=False)
if result.ok:
wx.CallAfter(self.status_bar.SetStatusText, f"External sync complete: {repo_path}")
if self.app.prefs.repo_sync_notify:
wx.CallAfter(self.show_notification, "External Repo Sync", result.message)
else:
wx.CallAfter(self.status_bar.SetStatusText, f"External sync failed: {repo_path}")
wx.CallAfter(
wx.MessageBox,
f"External sync failed:\n\n{result.message}",
"External Repo Sync",
wx.OK | wx.ICON_ERROR
)

threading.Thread(target=do_sync, daemon=True).start()

def show_notification(self, title: str, message: str):
"""Show an OS desktop notification."""
try:
Expand Down Expand Up @@ -1238,7 +1283,10 @@ def on_following_list_key_hook(self, event):
"""Handle key events in following list (using CHAR_HOOK for reliability)."""
key = event.GetKeyCode()
if key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER:
self.on_view_following_user(None)
if platform.system() == "Darwin":
wx.CallAfter(self.on_view_following_user, None)
else:
self.on_view_following_user(None)
else:
event.Skip()

Expand Down Expand Up @@ -1269,7 +1317,7 @@ def on_view_following_user(self, event):
user = self.get_selected_following_user()
if user:
from GUI.search import UserProfileDialog
dlg = UserProfileDialog(self, user.login)
dlg = UserProfileDialog(self._get_dialog_parent(), user.login)
dlg.ShowModal()
dlg.Destroy()

Expand Down Expand Up @@ -1310,7 +1358,10 @@ def on_notifications_list_key_hook(self, event):
"""Handle key events in notifications list."""
key = event.GetKeyCode()
if key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER:
self.on_open_notification(None)
if platform.system() == "Darwin":
wx.CallAfter(self.on_open_notification, None)
else:
self.on_open_notification(None)
elif key == wx.WXK_DELETE:
self.on_mark_notification_done(None)
else:
Expand Down Expand Up @@ -1717,7 +1768,7 @@ def toggle_visibility(self):
else:
self.app.prefs.window_shown = True
self.Show()
self.Raise()
safe_raise(self)
self._focus_current_list()

def _focus_current_list(self):
Expand Down
Loading