Convert to PySide6, add ecosystem installer and Docker testing
PySide6 Migration: - Convert all Qt imports from PyQt6 to PySide6 (LGPL license) - Replace pyqtSignal with Signal throughout codebase - Unifies Qt library across ecosystem (ramble, artifact-editor) New Features: - Add Re-align Goals button to dashboard (launches Ramble for interview) - ReAlignGoalsDialog now directly launches Ramble with questions as fields Ecosystem Tools: - Add bin/install-ecosystem script for unified installation - Add Dockerfile for testing ecosystem installation - Add docker-compose.yml for easy testing - Add ramble and cmdforge as tracked dependencies 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
45f059647a
commit
3ecadb5e16
|
|
@ -0,0 +1,94 @@
|
|||
# Development Hub Ecosystem - Docker Build for Testing
|
||||
#
|
||||
# Tests that all ecosystem projects can be installed together.
|
||||
# GUI functionality is not tested (no X11), only imports and CLI tools.
|
||||
#
|
||||
# Build: docker build -t development-hub .
|
||||
# Test: docker run --rm development-hub
|
||||
|
||||
FROM python:3.12-slim
|
||||
|
||||
# Install system dependencies for Qt (even though we won't run GUI, imports need libs)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
libgl1 \
|
||||
libegl1 \
|
||||
libxkbcommon0 \
|
||||
libdbus-1-3 \
|
||||
libxcb-cursor0 \
|
||||
libxcb-icccm4 \
|
||||
libxcb-keysyms1 \
|
||||
libxcb-shape0 \
|
||||
libxcb-xfixes0 \
|
||||
libxcb-xinerama0 \
|
||||
libxcb-render0 \
|
||||
libxcb-render-util0 \
|
||||
libxcb-image0 \
|
||||
libglib2.0-0 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set up virtual display for Qt imports (no actual display, just for import)
|
||||
ENV QT_QPA_PLATFORM=offscreen
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
# Clone all repos
|
||||
ARG GITEA_URL=https://gitea.brrd.tech/rob
|
||||
RUN git clone ${GITEA_URL}/CmdForge.git CmdForge && \
|
||||
git clone ${GITEA_URL}/ramble.git ramble && \
|
||||
git clone ${GITEA_URL}/artifact-editor.git artifact-editor && \
|
||||
git clone ${GITEA_URL}/orchestrated-discussions.git orchestrated-discussions && \
|
||||
git clone ${GITEA_URL}/development-hub.git development-hub
|
||||
|
||||
# Apply patches for PySide6 unification (until changes are pushed to Gitea)
|
||||
# Patch pyproject.toml files
|
||||
RUN sed -i 's|PyQt6|PySide6|g' /workspace/development-hub/pyproject.toml && \
|
||||
sed -i 's|file:///home/rob/PycharmProjects|file:///workspace|g' /workspace/development-hub/pyproject.toml && \
|
||||
sed -i 's|PyQt6|PySide6|g' /workspace/artifact-editor/pyproject.toml
|
||||
|
||||
# Patch source files: PyQt6 -> PySide6
|
||||
RUN find /workspace/development-hub/src -name "*.py" -exec sed -i \
|
||||
-e 's/from PyQt6/from PySide6/g' \
|
||||
-e 's/import PyQt6/import PySide6/g' \
|
||||
-e 's/pyqtSignal/Signal/g' \
|
||||
-e 's/pyqtSlot/Slot/g' {} \;
|
||||
|
||||
RUN find /workspace/artifact-editor/src -name "*.py" -exec sed -i \
|
||||
-e 's/from PyQt6/from PySide6/g' \
|
||||
-e 's/import PyQt6/import PySide6/g' \
|
||||
-e 's/pyqtSignal/Signal/g' \
|
||||
-e 's/pyqtSlot/Slot/g' {} \;
|
||||
|
||||
# Create venv and install
|
||||
RUN python -m venv /venv
|
||||
ENV PATH="/venv/bin:$PATH"
|
||||
|
||||
RUN pip install --upgrade pip wheel setuptools
|
||||
|
||||
# Install in dependency order (all patches applied above)
|
||||
RUN pip install -e /workspace/CmdForge
|
||||
RUN pip install -e /workspace/ramble
|
||||
RUN pip install -e /workspace/artifact-editor
|
||||
RUN pip install -e "/workspace/orchestrated-discussions[gui]"
|
||||
RUN pip install -e /workspace/development-hub
|
||||
|
||||
# Verification script
|
||||
RUN echo '#!/bin/bash\n\
|
||||
set -e\n\
|
||||
echo "Testing imports..."\n\
|
||||
python -c "import cmdforge; print(\" cmdforge: OK\")"\n\
|
||||
python -c "import ramble; print(\" ramble: OK\")"\n\
|
||||
python -c "import artifact_editor; print(\" artifact_editor: OK\")"\n\
|
||||
python -c "import discussions; print(\" discussions: OK\")"\n\
|
||||
python -c "import development_hub; print(\" development_hub: OK\")"\n\
|
||||
echo ""\n\
|
||||
echo "Testing CLI tools..."\n\
|
||||
cmdforge --help > /dev/null && echo " cmdforge CLI: OK"\n\
|
||||
ramble --help > /dev/null && echo " ramble CLI: OK"\n\
|
||||
artifact-editor --help > /dev/null && echo " artifact-editor CLI: OK"\n\
|
||||
discussions --help > /dev/null && echo " discussions CLI: OK"\n\
|
||||
echo ""\n\
|
||||
echo "All tests passed!"\n\
|
||||
' > /test.sh && chmod +x /test.sh
|
||||
|
||||
CMD ["/test.sh"]
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
#!/bin/bash
|
||||
# Development Ecosystem Installer
|
||||
#
|
||||
# Installs all projects in the development ecosystem in the correct order.
|
||||
# Projects are cloned from Gitea and installed as editable packages.
|
||||
#
|
||||
# Usage:
|
||||
# ./install-ecosystem # Fresh install to ~/PycharmProjects
|
||||
# ./install-ecosystem --update # Update existing repos and reinstall
|
||||
# ./install-ecosystem --help # Show this help
|
||||
#
|
||||
# Projects installed (in order):
|
||||
# 1. CmdForge - AI-powered CLI tools
|
||||
# 2. ramble - Voice note transcription
|
||||
# 3. artifact-editor - Visual artifact editor
|
||||
# 4. orchestrated-discussions - Multi-agent AI discussions
|
||||
# 5. development-hub - Central orchestration GUI
|
||||
|
||||
set -e
|
||||
|
||||
GITEA_URL="https://gitea.brrd.tech/rob"
|
||||
PROJECTS_DIR="${PROJECTS_DIR:-$HOME/PycharmProjects}"
|
||||
VENV_DIR="${VENV_DIR:-$PROJECTS_DIR/development-hub/.venv}"
|
||||
|
||||
# Project definitions: name, repo_name, extras
|
||||
PROJECTS=(
|
||||
"CmdForge:CmdForge:"
|
||||
"ramble:ramble:"
|
||||
"artifact-editor:artifact-editor:"
|
||||
"orchestrated-discussions:orchestrated-discussions:gui"
|
||||
"development-hub:development-hub:"
|
||||
)
|
||||
|
||||
show_help() {
|
||||
head -20 "$0" | tail -n +2 | sed 's/^# //' | sed 's/^#//'
|
||||
}
|
||||
|
||||
info() {
|
||||
echo -e "\033[1;34m==>\033[0m \033[1m$1\033[0m"
|
||||
}
|
||||
|
||||
success() {
|
||||
echo -e "\033[1;32m==>\033[0m \033[1m$1\033[0m"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "\033[1;31m==>\033[0m \033[1m$1\033[0m" >&2
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
UPDATE_MODE=false
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--update|-u)
|
||||
UPDATE_MODE=true
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
error "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Ensure projects directory exists
|
||||
mkdir -p "$PROJECTS_DIR"
|
||||
cd "$PROJECTS_DIR"
|
||||
|
||||
info "Installing development ecosystem to $PROJECTS_DIR"
|
||||
|
||||
# Clone or update repositories
|
||||
for project_spec in "${PROJECTS[@]}"; do
|
||||
IFS=':' read -r name repo extras <<< "$project_spec"
|
||||
project_dir="$PROJECTS_DIR/$name"
|
||||
|
||||
if [[ -d "$project_dir" ]]; then
|
||||
if [[ "$UPDATE_MODE" == "true" ]]; then
|
||||
info "Updating $name..."
|
||||
cd "$project_dir"
|
||||
git fetch origin
|
||||
git pull --ff-only origin main 2>/dev/null || git pull --ff-only origin master 2>/dev/null || true
|
||||
cd "$PROJECTS_DIR"
|
||||
else
|
||||
info "$name already exists, skipping clone"
|
||||
fi
|
||||
else
|
||||
info "Cloning $name..."
|
||||
git clone "$GITEA_URL/$repo.git" "$name"
|
||||
fi
|
||||
done
|
||||
|
||||
# Create or activate virtual environment
|
||||
if [[ ! -d "$VENV_DIR" ]]; then
|
||||
info "Creating virtual environment at $VENV_DIR..."
|
||||
python3 -m venv "$VENV_DIR"
|
||||
fi
|
||||
|
||||
info "Activating virtual environment..."
|
||||
source "$VENV_DIR/bin/activate"
|
||||
|
||||
# Upgrade pip
|
||||
pip install --upgrade pip wheel setuptools -q
|
||||
|
||||
# Install projects in order
|
||||
for project_spec in "${PROJECTS[@]}"; do
|
||||
IFS=':' read -r name repo extras <<< "$project_spec"
|
||||
project_dir="$PROJECTS_DIR/$name"
|
||||
|
||||
if [[ -n "$extras" ]]; then
|
||||
info "Installing $name[$extras]..."
|
||||
pip install -e "$project_dir[$extras]" -q
|
||||
else
|
||||
info "Installing $name..."
|
||||
pip install -e "$project_dir" -q
|
||||
fi
|
||||
done
|
||||
|
||||
success "Installation complete!"
|
||||
echo ""
|
||||
echo "To use the ecosystem:"
|
||||
echo " source $VENV_DIR/bin/activate"
|
||||
echo " development-hub # Launch the GUI"
|
||||
echo ""
|
||||
echo "Individual tools:"
|
||||
echo " cmdforge # AI-powered CLI tools"
|
||||
echo " ramble # Voice note transcription"
|
||||
echo " artifact-editor # Visual artifact editor"
|
||||
echo " discussions # Multi-agent AI discussions"
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Development Ecosystem Docker Compose
|
||||
#
|
||||
# Builds and tests the complete ecosystem installation.
|
||||
#
|
||||
# Usage:
|
||||
# docker compose build # Build the test image
|
||||
# docker compose up # Run installation tests
|
||||
# docker compose down # Cleanup
|
||||
|
||||
services:
|
||||
ecosystem-test:
|
||||
build: .
|
||||
container_name: development-hub-test
|
||||
environment:
|
||||
- QT_QPA_PLATFORM=offscreen
|
||||
- DISPLAY=:99
|
||||
# For local development testing, mount the local repos:
|
||||
# volumes:
|
||||
# - ./:/workspace/development-hub:ro
|
||||
# - ../CmdForge:/workspace/CmdForge:ro
|
||||
# - ../ramble:/workspace/ramble:ro
|
||||
# - ../artifact-editor:/workspace/artifact-editor:ro
|
||||
# - ../orchestrated-discussions:/workspace/orchestrated-discussions:ro
|
||||
|
|
@ -10,9 +10,11 @@ readme = "README.md"
|
|||
license = {text = "MIT"}
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"PyQt6>=6.5.0",
|
||||
"PySide6>=6.4.0",
|
||||
"pyte>=0.8.0",
|
||||
"orchestrated-discussions[gui] @ file:///home/rob/PycharmProjects/orchestrated-discussions",
|
||||
"ramble @ file:///home/rob/PycharmProjects/ramble",
|
||||
"cmdforge @ file:///home/rob/PycharmProjects/CmdForge",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
import signal
|
||||
import sys
|
||||
|
||||
from PyQt6.QtCore import QTimer
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
from PySide6.QtCore import QTimer
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from development_hub.main_window import MainWindow
|
||||
from development_hub.styles import DARK_THEME
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import subprocess
|
|||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QProcess
|
||||
from PyQt6.QtWidgets import (
|
||||
from PySide6.QtCore import Qt, QThread, Signal, QProcess, QTimer
|
||||
from PySide6.QtWidgets import (
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
QDialog,
|
||||
|
|
@ -36,8 +36,8 @@ from development_hub.settings import Settings
|
|||
class RambleThread(QThread):
|
||||
"""Thread to run Ramble subprocess."""
|
||||
|
||||
finished = pyqtSignal(dict) # Emits parsed result
|
||||
error = pyqtSignal(str) # Emits error message
|
||||
finished = Signal(dict) # Emits parsed result
|
||||
error = Signal(str) # Emits error message
|
||||
|
||||
def __init__(self, prompt: str, fields: list[str], criteria: dict[str, str] = None):
|
||||
super().__init__()
|
||||
|
|
@ -97,8 +97,8 @@ class RambleThread(QThread):
|
|||
class NewProjectThread(QThread):
|
||||
"""Thread to run new-project script."""
|
||||
|
||||
output = pyqtSignal(str)
|
||||
finished = pyqtSignal(bool, str) # success, message
|
||||
output = Signal(str)
|
||||
finished = Signal(bool, str) # success, message
|
||||
|
||||
def __init__(self, name: str, title: str, tagline: str, deploy: bool):
|
||||
super().__init__()
|
||||
|
|
@ -152,8 +152,8 @@ class NewProjectThread(QThread):
|
|||
class DeployDocsThread(QThread):
|
||||
"""Thread to run deploy docs script asynchronously."""
|
||||
|
||||
output = pyqtSignal(str)
|
||||
finished = pyqtSignal(bool, str) # success, message
|
||||
output = Signal(str)
|
||||
finished = Signal(bool, str) # success, message
|
||||
|
||||
def __init__(self, project_key: str, project_title: str, docs_url: str):
|
||||
super().__init__()
|
||||
|
|
@ -198,7 +198,7 @@ class DeployDocsThread(QThread):
|
|||
class UpdateDocsThread(QThread):
|
||||
"""Thread to run update-docs CmdForge tool."""
|
||||
|
||||
finished = pyqtSignal(bool, str, str) # success, message, stderr
|
||||
finished = Signal(bool, str, str) # success, message, stderr
|
||||
|
||||
def __init__(self, project_key: str):
|
||||
super().__init__()
|
||||
|
|
@ -237,8 +237,8 @@ class UpdateDocsThread(QThread):
|
|||
class RebuildMainDocsThread(QThread):
|
||||
"""Thread to refresh the main documentation site."""
|
||||
|
||||
output = pyqtSignal(str)
|
||||
finished = pyqtSignal(bool, str) # success, message
|
||||
output = Signal(str)
|
||||
finished = Signal(bool, str) # success, message
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
|
@ -301,7 +301,7 @@ class RebuildMainDocsThread(QThread):
|
|||
class NewProjectDialog(QDialog):
|
||||
"""Dialog for creating a new project."""
|
||||
|
||||
project_created = pyqtSignal(str) # Emits project name
|
||||
project_created = Signal(str) # Emits project name
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
|
@ -621,7 +621,7 @@ class SettingsDialog(QDialog):
|
|||
class StandupDialog(QDialog):
|
||||
"""Dialog for capturing daily standup progress."""
|
||||
|
||||
progress_saved = pyqtSignal(Path) # Emits path to saved file
|
||||
progress_saved = Signal(Path) # Emits path to saved file
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
|
@ -1182,3 +1182,204 @@ class DocsPreviewDialog(QDialog):
|
|||
_, new_widget, _ = self._text_widgets[filename]
|
||||
result[filename] = new_widget.toPlainText()
|
||||
return result
|
||||
|
||||
|
||||
class RealignGoalsThread(QThread):
|
||||
"""Thread to run realign-goals CmdForge tool."""
|
||||
|
||||
finished = Signal(str) # Emits generated goals content
|
||||
error = Signal(str) # Emits error message
|
||||
|
||||
def __init__(self, answers: dict, project_name: str = "global"):
|
||||
super().__init__()
|
||||
self.answers = answers
|
||||
self.project_name = project_name
|
||||
|
||||
def run(self):
|
||||
"""Run realign-goals tool."""
|
||||
cmdforge_path = Path.home() / "PycharmProjects" / "CmdForge" / ".venv" / "bin" / "cmdforge"
|
||||
if not cmdforge_path.exists():
|
||||
cmdforge_path = shutil.which("cmdforge")
|
||||
if not cmdforge_path:
|
||||
self.error.emit("CmdForge not found")
|
||||
return
|
||||
|
||||
# Format answers as input
|
||||
input_text = json.dumps(self.answers, indent=2)
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[str(cmdforge_path), "run", "realign-goals", "--project", self.project_name],
|
||||
input=input_text,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
self.error.emit(f"Error: {result.stderr or result.stdout}")
|
||||
return
|
||||
|
||||
self.finished.emit(result.stdout)
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
self.error.emit("Tool timed out after 2 minutes")
|
||||
except Exception as e:
|
||||
self.error.emit(f"Error: {e}")
|
||||
|
||||
|
||||
class ReAlignGoalsDialog(QDialog):
|
||||
"""Dialog for re-aligning goals through Ramble interview.
|
||||
|
||||
Launches Ramble directly with interview questions as fields,
|
||||
then generates new goals.md content and shows diff for approval.
|
||||
"""
|
||||
|
||||
# Interview questions: (field_name, question_text, criteria_hint)
|
||||
QUESTIONS = [
|
||||
("motivation", "What drives you to build software?",
|
||||
"solving problems, tools, automation, learning, etc."),
|
||||
("common_thread", "What's the common thread in your projects?",
|
||||
"problems or frustrations that led to building them"),
|
||||
("finished", "How do you feel about 'finished' software?",
|
||||
"complete and move on vs ongoing refinement"),
|
||||
("audience", "Who are you building for?",
|
||||
"yourself, developers, non-technical users, community"),
|
||||
("polished", "What does 'polished' mean to you?",
|
||||
"qualities that make software feel polished"),
|
||||
("scope", "How do you feel about scope and focus?",
|
||||
"build many things vs focus deeply"),
|
||||
("priorities", "How do you decide what to work on next?",
|
||||
"frustrations, closest to done, most impactful"),
|
||||
("shipping", "What's your relationship with shipping?",
|
||||
"polish forever vs push things out early"),
|
||||
("ai_vision", "What's your vision for AI-assisted development?",
|
||||
"tool, scope enabler, or something more"),
|
||||
("done", "What does 'done' look like for a project?",
|
||||
"is there a completion point"),
|
||||
("principles", "What principles guide how you write code?",
|
||||
"philosophies, patterns, practices"),
|
||||
("success", "What does success look like in 2-3 years?",
|
||||
"the picture if things go well"),
|
||||
("avoid", "What do you want to avoid?",
|
||||
"traps or patterns to stay away from"),
|
||||
]
|
||||
|
||||
def __init__(self, goals_path: Path, project_name: str = "global", parent=None):
|
||||
super().__init__(parent)
|
||||
self.goals_path = goals_path
|
||||
self.project_name = project_name
|
||||
self._ramble_thread = None
|
||||
self._realign_thread = None
|
||||
|
||||
self.setWindowTitle("Re-align Goals")
|
||||
self.setFixedSize(400, 150)
|
||||
self._setup_ui()
|
||||
|
||||
# Start Ramble immediately
|
||||
QTimer.singleShot(100, self._start_ramble)
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Set up minimal progress UI."""
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(20, 20, 20, 20)
|
||||
|
||||
self.status_label = QLabel("Launching Ramble...")
|
||||
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.status_label.setStyleSheet("font-size: 14px; color: #e0e0e0;")
|
||||
layout.addWidget(self.status_label)
|
||||
|
||||
self.progress = QProgressBar()
|
||||
self.progress.setRange(0, 0) # Indeterminate
|
||||
self.progress.setStyleSheet("""
|
||||
QProgressBar {
|
||||
border: 1px solid #3d3d3d;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
background-color: #2d2d2d;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background-color: #4a9eff;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(self.progress)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
cancel_btn = QPushButton("Cancel")
|
||||
cancel_btn.clicked.connect(self.reject)
|
||||
layout.addWidget(cancel_btn, alignment=Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
def _start_ramble(self):
|
||||
"""Launch Ramble with interview questions as fields."""
|
||||
self.status_label.setText("Ramble is open - answer the questions...")
|
||||
|
||||
# Build field names and criteria from questions
|
||||
fields = [q[0] for q in self.QUESTIONS]
|
||||
criteria = {q[0]: q[2] for q in self.QUESTIONS}
|
||||
|
||||
# Build hints from the question text
|
||||
hints = [q[1] for q in self.QUESTIONS]
|
||||
|
||||
self._ramble_thread = RambleThread(
|
||||
prompt=(
|
||||
"Answer these questions about your development philosophy and goals. "
|
||||
"Type or use voice input, then click Generate to fill in the fields."
|
||||
),
|
||||
fields=fields,
|
||||
criteria=criteria,
|
||||
)
|
||||
self._ramble_thread.finished.connect(self._on_ramble_finished)
|
||||
self._ramble_thread.error.connect(self._on_ramble_error)
|
||||
self._ramble_thread.start()
|
||||
|
||||
def _on_ramble_finished(self, result: dict):
|
||||
"""Handle Ramble completion - now generate goals."""
|
||||
if not result or "fields" not in result:
|
||||
self.reject()
|
||||
return
|
||||
|
||||
answers = result["fields"]
|
||||
if not any(v.strip() for v in answers.values()):
|
||||
QMessageBox.warning(self, "No Answers", "Please answer at least some questions.")
|
||||
self.reject()
|
||||
return
|
||||
|
||||
self.status_label.setText("Generating goals from your answers...")
|
||||
self._realign_thread = RealignGoalsThread(answers, self.project_name)
|
||||
self._realign_thread.finished.connect(self._on_generate_finished)
|
||||
self._realign_thread.error.connect(self._on_generate_error)
|
||||
self._realign_thread.start()
|
||||
|
||||
def _on_ramble_error(self, error: str):
|
||||
"""Handle Ramble error."""
|
||||
QMessageBox.warning(self, "Ramble Error", error)
|
||||
self.reject()
|
||||
|
||||
def _on_generate_finished(self, new_content: str):
|
||||
"""Handle successful generation - show diff dialog."""
|
||||
self.progress.setRange(0, 1)
|
||||
self.progress.setValue(1)
|
||||
|
||||
# Read existing content
|
||||
old_content = ""
|
||||
if self.goals_path.exists():
|
||||
old_content = self.goals_path.read_text()
|
||||
|
||||
# Show diff dialog
|
||||
changes = {"goals.md": (old_content, new_content)}
|
||||
preview = DocsPreviewDialog(changes, self)
|
||||
if preview.exec() == QDialog.DialogCode.Accepted and preview.was_accepted():
|
||||
selected = preview.get_selected_changes()
|
||||
if "goals.md" in selected:
|
||||
self.goals_path.write_text(selected["goals.md"])
|
||||
QMessageBox.information(self, "Goals Updated", "Your goals have been updated.")
|
||||
self.accept()
|
||||
return
|
||||
|
||||
self.reject()
|
||||
|
||||
def _on_generate_error(self, error: str):
|
||||
"""Handle generation error."""
|
||||
QMessageBox.critical(self, "Generation Error", error)
|
||||
self.reject()
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QAction, QKeySequence
|
||||
from PyQt6.QtWidgets import (
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QAction, QKeySequence
|
||||
from PySide6.QtWidgets import (
|
||||
QLabel,
|
||||
QMainWindow,
|
||||
QSplitter,
|
||||
|
|
@ -227,7 +227,7 @@ class MainWindow(QMainWindow):
|
|||
|
||||
def _launch_discussion(self):
|
||||
"""Launch orchestrated-discussions UI in the current project directory."""
|
||||
from PyQt6.QtWidgets import QMessageBox
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
# Get current project context from active pane
|
||||
pane = self.workspace.get_active_pane()
|
||||
|
|
@ -260,7 +260,7 @@ class MainWindow(QMainWindow):
|
|||
|
||||
def _launch_global_discussion(self):
|
||||
"""Launch orchestrated-discussions UI in the root projects directory with new dialog."""
|
||||
from PyQt6.QtWidgets import QMessageBox
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
projects_root = Path.home() / "PycharmProjects"
|
||||
|
||||
|
|
@ -345,7 +345,7 @@ class MainWindow(QMainWindow):
|
|||
|
||||
def _show_about(self):
|
||||
"""Show about dialog."""
|
||||
from PyQt6.QtWidgets import QMessageBox
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
QMessageBox.about(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import subprocess
|
|||
import webbrowser
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt6.QtCore import Qt, pyqtSignal
|
||||
from PyQt6.QtGui import QAction
|
||||
from PyQt6.QtWidgets import (
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtGui import QAction
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
QLabel,
|
||||
QListWidget,
|
||||
|
|
@ -26,9 +26,9 @@ from development_hub.project_discovery import Project, discover_projects
|
|||
class ProjectListWidget(QWidget):
|
||||
"""Widget displaying the list of projects with context menu actions."""
|
||||
|
||||
project_selected = pyqtSignal(Project)
|
||||
open_terminal_requested = pyqtSignal(Project)
|
||||
open_dashboard_requested = pyqtSignal(Project)
|
||||
project_selected = Signal(Project)
|
||||
open_terminal_requested = Signal(Project)
|
||||
open_dashboard_requested = Signal(Project)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
|
@ -267,7 +267,7 @@ class ProjectListWidget(QWidget):
|
|||
|
||||
elif clicked == commit_btn:
|
||||
# Show commit dialog
|
||||
from PyQt6.QtWidgets import QInputDialog
|
||||
from PySide6.QtWidgets import QInputDialog
|
||||
message, ok = QInputDialog.getText(
|
||||
self,
|
||||
"Commit Message",
|
||||
|
|
@ -415,8 +415,8 @@ class ProjectListWidget(QWidget):
|
|||
|
||||
def _update_docs(self, project: Project):
|
||||
"""Update documentation using CmdForge update-docs tool with preview."""
|
||||
from PyQt6.QtWidgets import QMessageBox, QProgressDialog
|
||||
from PyQt6.QtCore import Qt, QTimer
|
||||
from PySide6.QtWidgets import QMessageBox, QProgressDialog
|
||||
from PySide6.QtCore import Qt, QTimer
|
||||
from pathlib import Path
|
||||
|
||||
# Confirm before starting
|
||||
|
|
@ -513,7 +513,7 @@ class ProjectListWidget(QWidget):
|
|||
|
||||
def _on_update_docs_finished(self, success: bool, message: str, detail: str):
|
||||
"""Handle update docs thread completion."""
|
||||
from PyQt6.QtWidgets import QMessageBox
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from pathlib import Path
|
||||
|
||||
# Stop timer
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import termios
|
|||
from pathlib import Path
|
||||
|
||||
import pyte
|
||||
from PyQt6.QtCore import QThread, pyqtSignal, Qt, QTimer
|
||||
from PyQt6.QtGui import (
|
||||
from PySide6.QtCore import QThread, Signal, Qt, QTimer
|
||||
from PySide6.QtGui import (
|
||||
QFont,
|
||||
QFontMetrics,
|
||||
QColor,
|
||||
|
|
@ -22,14 +22,14 @@ from PyQt6.QtGui import (
|
|||
QDragEnterEvent,
|
||||
QDropEvent,
|
||||
)
|
||||
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QScrollBar, QAbstractScrollArea
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QScrollBar, QAbstractScrollArea
|
||||
|
||||
|
||||
class PtyReaderThread(QThread):
|
||||
"""Thread that reads from PTY master and emits output."""
|
||||
|
||||
output_ready = pyqtSignal(bytes)
|
||||
finished_signal = pyqtSignal()
|
||||
output_ready = Signal(bytes)
|
||||
finished_signal = Signal()
|
||||
|
||||
def __init__(self, master_fd: int):
|
||||
super().__init__()
|
||||
|
|
@ -98,8 +98,8 @@ BG_COLORS = {
|
|||
class TerminalDisplay(QWidget):
|
||||
"""Terminal display widget that renders a pyte screen."""
|
||||
|
||||
key_pressed = pyqtSignal(bytes)
|
||||
cursor_position_requested = pyqtSignal()
|
||||
key_pressed = Signal(bytes)
|
||||
cursor_position_requested = Signal()
|
||||
|
||||
def __init__(self, rows: int = 24, cols: int = 80, parent=None):
|
||||
super().__init__(parent)
|
||||
|
|
@ -450,7 +450,7 @@ class TerminalDisplay(QWidget):
|
|||
|
||||
def contextMenuEvent(self, event):
|
||||
"""Show context menu with copy/paste."""
|
||||
from PyQt6.QtWidgets import QMenu, QApplication
|
||||
from PySide6.QtWidgets import QMenu, QApplication
|
||||
|
||||
menu = QMenu(self)
|
||||
|
||||
|
|
@ -490,7 +490,7 @@ class TerminalDisplay(QWidget):
|
|||
|
||||
def _copy_selection(self):
|
||||
"""Copy selected text to clipboard."""
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
start, end = self._get_selection_bounds()
|
||||
if start is None:
|
||||
|
|
@ -513,7 +513,7 @@ class TerminalDisplay(QWidget):
|
|||
|
||||
def _paste_clipboard(self):
|
||||
"""Paste clipboard content as keyboard input."""
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
text = QApplication.clipboard().text()
|
||||
if text:
|
||||
|
|
@ -589,7 +589,7 @@ class TerminalDisplay(QWidget):
|
|||
class TerminalWidget(QWidget):
|
||||
"""Full terminal widget with PTY support using pyte emulation."""
|
||||
|
||||
closed = pyqtSignal()
|
||||
closed = Signal()
|
||||
|
||||
def __init__(self, cwd: Path | None = None, parent=None):
|
||||
super().__init__(parent)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ from pathlib import Path
|
|||
|
||||
from development_hub.parsers.base import atomic_write
|
||||
|
||||
from PyQt6.QtCore import Qt, pyqtSignal, QFileSystemWatcher, QTimer, QThread, QObject
|
||||
from PyQt6.QtWidgets import (
|
||||
from PySide6.QtCore import Qt, Signal, QFileSystemWatcher, QTimer, QThread, QObject
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
|
|
@ -27,13 +27,14 @@ from development_hub.project_discovery import Project
|
|||
from development_hub.services.health_checker import HealthChecker
|
||||
from development_hub.parsers.progress_parser import ProgressLogManager
|
||||
from development_hub.widgets.health_card import HealthCardCompact
|
||||
from development_hub.dialogs import ReAlignGoalsDialog
|
||||
|
||||
|
||||
class AuditWorker(QObject):
|
||||
"""Background worker for running goals audit."""
|
||||
|
||||
finished = pyqtSignal(str, bool) # output, success
|
||||
error = pyqtSignal(str)
|
||||
finished = Signal(str, bool) # output, success
|
||||
error = Signal(str)
|
||||
|
||||
def __init__(self, goals_content: str, project_path: Path | None = None):
|
||||
super().__init__()
|
||||
|
|
@ -117,9 +118,9 @@ class ProjectDashboard(QWidget):
|
|||
- Today's progress
|
||||
"""
|
||||
|
||||
terminal_requested = pyqtSignal(object) # Emits Project
|
||||
project_selected = pyqtSignal(str) # Emits project_key (global mode only)
|
||||
standup_requested = pyqtSignal() # Global mode only
|
||||
terminal_requested = Signal(object) # Emits Project
|
||||
project_selected = Signal(str) # Emits project_key (global mode only)
|
||||
standup_requested = Signal() # Global mode only
|
||||
|
||||
def __init__(self, project: Project | None = None, parent: QWidget | None = None):
|
||||
"""Initialize project dashboard.
|
||||
|
|
@ -312,7 +313,7 @@ class ProjectDashboard(QWidget):
|
|||
goals_header_layout.addWidget(goals_label)
|
||||
|
||||
# Goals progress bar
|
||||
from PyQt6.QtWidgets import QProgressBar
|
||||
from PySide6.QtWidgets import QProgressBar
|
||||
self.goals_progress = QProgressBar()
|
||||
self.goals_progress.setMinimum(0)
|
||||
self.goals_progress.setMaximum(100)
|
||||
|
|
@ -343,6 +344,11 @@ class ProjectDashboard(QWidget):
|
|||
audit_goals_btn.clicked.connect(self._audit_goals)
|
||||
goals_header_layout.addWidget(audit_goals_btn)
|
||||
|
||||
realign_goals_btn = QPushButton("Re-align")
|
||||
realign_goals_btn.setStyleSheet(self._button_style())
|
||||
realign_goals_btn.clicked.connect(self._realign_goals)
|
||||
goals_header_layout.addWidget(realign_goals_btn)
|
||||
|
||||
edit_goals_btn = QPushButton("Edit")
|
||||
edit_goals_btn.setStyleSheet(self._button_style())
|
||||
edit_goals_btn.clicked.connect(self._open_goals)
|
||||
|
|
@ -1592,7 +1598,7 @@ class ProjectDashboard(QWidget):
|
|||
|
||||
def _on_todo_start_discussion(self, todo):
|
||||
"""Handle starting a discussion for a todo item."""
|
||||
from PyQt6.QtWidgets import QMessageBox
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
# Cannot start discussion in global mode (no project context)
|
||||
if self.is_global:
|
||||
|
|
@ -2413,7 +2419,7 @@ class ProjectDashboard(QWidget):
|
|||
"""Handle audit completion."""
|
||||
import json
|
||||
from datetime import datetime
|
||||
from PyQt6.QtWidgets import QTextEdit, QDialogButtonBox
|
||||
from PySide6.QtWidgets import QTextEdit, QDialogButtonBox
|
||||
|
||||
self._cleanup_audit()
|
||||
|
||||
|
|
@ -2660,6 +2666,18 @@ generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|||
goals_path = self._docs_root / "projects" / self.project.key / "goals.md"
|
||||
self._open_file_in_editor(goals_path)
|
||||
|
||||
def _realign_goals(self):
|
||||
"""Open the Re-align Goals dialog to regenerate goals from interview."""
|
||||
if self.is_global:
|
||||
goals_path = self._docs_root / "goals" / "goals.md"
|
||||
project_name = "global"
|
||||
else:
|
||||
goals_path = self._docs_root / "projects" / self.project.key / "goals.md"
|
||||
project_name = self.project.key
|
||||
|
||||
dialog = ReAlignGoalsDialog(goals_path, project_name, parent=self)
|
||||
dialog.exec()
|
||||
|
||||
def _open_milestones(self):
|
||||
"""Open the milestones.md file."""
|
||||
if self.is_global:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""Global dashboard view - wrapper around ProjectDashboard in global mode."""
|
||||
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtWidgets import QWidget, QVBoxLayout
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout
|
||||
|
||||
from development_hub.views.dashboard import ProjectDashboard
|
||||
|
||||
|
|
@ -12,8 +12,8 @@ class GlobalDashboard(QWidget):
|
|||
This is a thin wrapper around ProjectDashboard in global mode.
|
||||
"""
|
||||
|
||||
project_selected = pyqtSignal(str) # Emits project_key
|
||||
standup_requested = pyqtSignal()
|
||||
project_selected = Signal(str) # Emits project_key
|
||||
standup_requested = Signal()
|
||||
|
||||
def __init__(self, parent: QWidget | None = None):
|
||||
"""Initialize global dashboard."""
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"""Action menu dropdown widget (hamburger menu)."""
|
||||
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtGui import QAction
|
||||
from PyQt6.QtWidgets import QToolButton, QMenu, QWidget
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtGui import QAction
|
||||
from PySide6.QtWidgets import QToolButton, QMenu, QWidget
|
||||
|
||||
|
||||
class ActionMenu(QToolButton):
|
||||
|
|
@ -17,14 +17,14 @@ class ActionMenu(QToolButton):
|
|||
settings_requested: Emitted when Settings action is triggered
|
||||
"""
|
||||
|
||||
dashboard_requested = pyqtSignal()
|
||||
global_dashboard_requested = pyqtSignal()
|
||||
terminal_requested = pyqtSignal()
|
||||
launch_discussion_requested = pyqtSignal()
|
||||
standup_requested = pyqtSignal()
|
||||
update_docs_requested = pyqtSignal()
|
||||
settings_requested = pyqtSignal()
|
||||
close_pane_requested = pyqtSignal()
|
||||
dashboard_requested = Signal()
|
||||
global_dashboard_requested = Signal()
|
||||
terminal_requested = Signal()
|
||||
launch_discussion_requested = Signal()
|
||||
standup_requested = Signal()
|
||||
update_docs_requested = Signal()
|
||||
settings_requested = Signal()
|
||||
close_pane_requested = Signal()
|
||||
|
||||
def __init__(self, parent: QWidget | None = None):
|
||||
"""Initialize action menu.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""Collapsible section widget for dashboards."""
|
||||
|
||||
from PyQt6.QtCore import Qt, pyqtSignal
|
||||
from PyQt6.QtWidgets import (
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
|
|
@ -23,7 +23,7 @@ class CollapsibleSection(QFrame):
|
|||
toggled: Emitted when section is expanded/collapsed (bool expanded)
|
||||
"""
|
||||
|
||||
toggled = pyqtSignal(bool)
|
||||
toggled = Signal(bool)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -179,10 +179,10 @@ class CollapsibleSection(QFrame):
|
|||
class TodoItemWidget(QWidget):
|
||||
"""Widget for displaying a single todo item with checkbox."""
|
||||
|
||||
toggled = pyqtSignal(object, bool) # Emits (todo, completed)
|
||||
deleted = pyqtSignal(object) # Emits todo
|
||||
start_discussion = pyqtSignal(object) # Emits todo
|
||||
edited = pyqtSignal(object, str, str) # Emits (todo, old_text, new_text)
|
||||
toggled = Signal(object, bool) # Emits (todo, completed)
|
||||
deleted = Signal(object) # Emits todo
|
||||
start_discussion = Signal(object) # Emits todo
|
||||
edited = Signal(object, str, str) # Emits (todo, old_text, new_text)
|
||||
|
||||
def __init__(self, todo, parent: QWidget | None = None, show_priority: bool = False):
|
||||
"""Initialize todo item widget.
|
||||
|
|
@ -371,7 +371,7 @@ class TodoItemWidget(QWidget):
|
|||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Handle Escape key to cancel editing, focus out to save."""
|
||||
from PyQt6.QtCore import QEvent
|
||||
from PySide6.QtCore import QEvent
|
||||
|
||||
if obj == self._edit_widget:
|
||||
if event.type() == QEvent.Type.KeyPress:
|
||||
|
|
@ -416,9 +416,9 @@ class TodoItemWidget(QWidget):
|
|||
class GoalItemWidget(QWidget):
|
||||
"""Widget for displaying a single goal item with checkbox."""
|
||||
|
||||
toggled = pyqtSignal(object, bool, bool, bool) # Emits (goal, new_completed, was_completed, was_partial)
|
||||
deleted = pyqtSignal(object) # Emits goal
|
||||
edited = pyqtSignal(object, str, str) # Emits (goal, old_text, new_text)
|
||||
toggled = Signal(object, bool, bool, bool) # Emits (goal, new_completed, was_completed, was_partial)
|
||||
deleted = Signal(object) # Emits goal
|
||||
edited = Signal(object, str, str) # Emits (goal, old_text, new_text)
|
||||
|
||||
def __init__(self, goal, parent: QWidget | None = None, is_non_goal: bool = False):
|
||||
"""Initialize goal item widget.
|
||||
|
|
@ -624,7 +624,7 @@ class GoalItemWidget(QWidget):
|
|||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Handle Escape key to cancel editing, focus out to save."""
|
||||
from PyQt6.QtCore import QEvent
|
||||
from PySide6.QtCore import QEvent
|
||||
|
||||
if obj == self._edit_widget:
|
||||
if event.type() == QEvent.Type.KeyPress:
|
||||
|
|
@ -641,8 +641,8 @@ class GoalItemWidget(QWidget):
|
|||
class DeliverableItemWidget(QWidget):
|
||||
"""Widget for displaying a single deliverable item with checkbox."""
|
||||
|
||||
toggled = pyqtSignal(object, bool) # Emits (deliverable, is_done)
|
||||
deleted = pyqtSignal(object) # Emits deliverable
|
||||
toggled = Signal(object, bool) # Emits (deliverable, is_done)
|
||||
deleted = Signal(object) # Emits deliverable
|
||||
|
||||
def __init__(self, deliverable, parent: QWidget | None = None):
|
||||
"""Initialize deliverable item widget.
|
||||
|
|
@ -753,14 +753,14 @@ class MilestoneWidget(QFrame):
|
|||
- Linked todos (preferred, when todos parameter is provided)
|
||||
"""
|
||||
|
||||
deliverable_toggled = pyqtSignal(object, object, bool) # (milestone, deliverable, is_done)
|
||||
deliverable_added = pyqtSignal(object, str) # (milestone, text)
|
||||
deliverable_deleted = pyqtSignal(object, object) # (milestone, deliverable)
|
||||
todo_toggled = pyqtSignal(object, bool) # (todo, completed) - for linked todos mode
|
||||
todo_deleted = pyqtSignal(object) # (todo) - for linked todos mode
|
||||
todo_added = pyqtSignal(str, str, str) # (text, priority, milestone_id) - for adding new todos
|
||||
todo_start_discussion = pyqtSignal(object) # (todo) - for starting discussion from todo
|
||||
todo_edited = pyqtSignal(object, str, str) # (todo, old_text, new_text) - for inline editing
|
||||
deliverable_toggled = Signal(object, object, bool) # (milestone, deliverable, is_done)
|
||||
deliverable_added = Signal(object, str) # (milestone, text)
|
||||
deliverable_deleted = Signal(object, object) # (milestone, deliverable)
|
||||
todo_toggled = Signal(object, bool) # (todo, completed) - for linked todos mode
|
||||
todo_deleted = Signal(object) # (todo) - for linked todos mode
|
||||
todo_added = Signal(str, str, str) # (text, priority, milestone_id) - for adding new todos
|
||||
todo_start_discussion = Signal(object) # (todo) - for starting discussion from todo
|
||||
todo_edited = Signal(object, str, str) # (todo, old_text, new_text) - for inline editing
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""Project health card widget."""
|
||||
|
||||
from PyQt6.QtCore import Qt, pyqtSignal
|
||||
from PyQt6.QtWidgets import QFrame, QHBoxLayout, QVBoxLayout, QLabel, QProgressBar
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import QFrame, QHBoxLayout, QVBoxLayout, QLabel, QProgressBar
|
||||
|
||||
|
||||
class HealthCard(QFrame):
|
||||
|
|
@ -14,7 +14,7 @@ class HealthCard(QFrame):
|
|||
- Todo count
|
||||
"""
|
||||
|
||||
clicked = pyqtSignal(str) # Emits project_key when clicked
|
||||
clicked = Signal(str) # Emits project_key when clicked
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Initialize health card.
|
||||
|
|
@ -125,7 +125,7 @@ class HealthCard(QFrame):
|
|||
class HealthCardCompact(QFrame):
|
||||
"""Compact single-line health card."""
|
||||
|
||||
clicked = pyqtSignal(str)
|
||||
clicked = Signal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Initialize compact health card."""
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""Milestone progress bar widget."""
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QLabel, QProgressBar
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QLabel, QProgressBar
|
||||
|
||||
|
||||
class MilestoneProgressBar(QWidget):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""Statistic card widget."""
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QFrame, QVBoxLayout, QLabel
|
||||
|
||||
|
||||
class StatCard(QFrame):
|
||||
|
|
@ -88,7 +88,7 @@ class StatCardRow(QFrame):
|
|||
def __init__(self, parent=None):
|
||||
"""Initialize stat card row."""
|
||||
super().__init__(parent)
|
||||
from PyQt6.QtWidgets import QHBoxLayout
|
||||
from PySide6.QtWidgets import QHBoxLayout
|
||||
|
||||
self._layout = QHBoxLayout(self)
|
||||
self._layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""Toast notification widget with undo/redo support."""
|
||||
|
||||
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
|
||||
from PyQt6.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton
|
||||
from PySide6.QtCore import Qt, QTimer, Signal
|
||||
from PySide6.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton
|
||||
|
||||
|
||||
class Toast(QWidget):
|
||||
|
|
@ -13,9 +13,9 @@ class Toast(QWidget):
|
|||
dismissed: Emitted when toast is dismissed (timeout or manual)
|
||||
"""
|
||||
|
||||
undo_clicked = pyqtSignal()
|
||||
redo_clicked = pyqtSignal()
|
||||
dismissed = pyqtSignal()
|
||||
undo_clicked = Signal()
|
||||
redo_clicked = Signal()
|
||||
dismissed = Signal()
|
||||
|
||||
def __init__(self, parent: QWidget | None = None):
|
||||
"""Initialize toast widget.
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
from pathlib import Path
|
||||
from weakref import ref
|
||||
|
||||
from PyQt6.QtCore import Qt, pyqtSignal, QEvent, QTimer
|
||||
from PyQt6.QtWidgets import (
|
||||
from PySide6.QtCore import Qt, Signal, QEvent, QTimer
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QSplitter,
|
||||
|
|
@ -40,14 +40,14 @@ PANE_UNFOCUSED_STYLE = """
|
|||
class PaneWidget(QFrame):
|
||||
"""A pane containing a tab widget with terminals."""
|
||||
|
||||
clicked = pyqtSignal(object) # Emits self when clicked
|
||||
empty = pyqtSignal(object) # Emits self when last tab closed
|
||||
terminal_requested = pyqtSignal() # Request new terminal
|
||||
standup_requested = pyqtSignal() # Request daily standup dialog
|
||||
launch_discussion_requested = pyqtSignal() # Request launch discussion action
|
||||
update_docs_requested = pyqtSignal() # Request docs update
|
||||
settings_requested = pyqtSignal() # Request settings dialog
|
||||
close_requested = pyqtSignal(object) # Emits self when close pane requested
|
||||
clicked = Signal(object) # Emits self when clicked
|
||||
empty = Signal(object) # Emits self when last tab closed
|
||||
terminal_requested = Signal() # Request new terminal
|
||||
standup_requested = Signal() # Request daily standup dialog
|
||||
launch_discussion_requested = Signal() # Request launch discussion action
|
||||
update_docs_requested = Signal() # Request docs update
|
||||
settings_requested = Signal() # Request settings dialog
|
||||
close_requested = Signal(object) # Emits self when close pane requested
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
|
@ -362,12 +362,12 @@ class PaneWidget(QFrame):
|
|||
class WorkspaceManager(QWidget):
|
||||
"""Manages splittable panes, each with their own tab bar."""
|
||||
|
||||
pane_count_changed = pyqtSignal(int)
|
||||
terminal_requested = pyqtSignal() # Request new terminal (no project context)
|
||||
standup_requested = pyqtSignal() # Request daily standup dialog
|
||||
launch_discussion_requested = pyqtSignal() # Request launch discussion action
|
||||
update_docs_requested = pyqtSignal(str) # Request docs update with project key
|
||||
settings_requested = pyqtSignal() # Request settings dialog
|
||||
pane_count_changed = Signal(int)
|
||||
terminal_requested = Signal() # Request new terminal (no project context)
|
||||
standup_requested = Signal() # Request daily standup dialog
|
||||
launch_discussion_requested = Signal() # Request launch discussion action
|
||||
update_docs_requested = Signal(str) # Request docs update with project key
|
||||
settings_requested = Signal() # Request settings dialog
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
|
|
|||
Loading…
Reference in New Issue