Skip to content
Merged
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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`GLIBC_2.38 not found` on dlopen.

### Fixed
- **Task-type descriptions audited against the runner repos**: GREMLIN now
says sequence conservation (PSSM) plus co-evolutionary couplings (input is
a sequence; the MSA is an internal HHblits step, and no contact map is
produced). Pro-Prime is now
described as optimal growth temperature (OGT) prediction and moved from
`function` to `fitness`; ThermoMPNN-D is ΔΔG prediction for single/double
mutants (not sequence design); HyperMPNN is thermostable design from
hyperthermophile-trained weights; LASErMPNN is all-atom ligand-conditioned
design with sidechain packing for protonated structures; PLACER models
protein-ligand complexes from
an input structure (not structure prediction from sequence); OpenDDE is
all-atom prediction with MSA and template guidance.
- **Standalone manager Qt6 enum bridge**: the package manager's Qt5/Qt6 compat
layer no longer allowlists the enum members it happens to use. It now
mirrors every scoped-enum member of every QtCore/QtGui/QtWidgets class onto
the owning class at import, so any Qt5-style unscoped access
(`QMessageBox.Ok`, `QFileDialog.DontResolveSymlinks`, ...) resolves on Qt6
without per-API bookkeeping. Previously unmapped members raised
AttributeError on Qt6 PyMOL builds (e.g. macOS).
- **Mol* shell message dispatch**: load the isolated viewer shell script after
its status and host elements exist. Previously the shell received structure
messages but rejected before loading Mol* because its cached DOM nodes were
Expand Down
10 changes: 8 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,18 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- **Test-case-driven fixes**: For live/integration issues, first encode the observed behavior as the smallest test case or skip guard, then make the smallest production/test change, run the focused keyword gate (for example `make kw-test PYTEST_KW=openkinetics`), and update `CHANGELOG.md`. Treat environment-dependent live API responses such as expected HTTP `4xx`/`5xx` as explicit skips, while keeping non-HTTP client errors failing.
- **PR babysitting workflow** — after opening a PR, own it through the squash-merge marker:
1. Work on a fix branch off `main`; conventional commit messages; never push to `main` directly.
2. Babysit CI until green, then read every bot review comment (codex, coderabbit, codacy, deepsource) and decide per comment: fix or debate — reply with evidence (file:line) when the code already handles it.
2. Babysit CI until green, then read every bot review comment from codex and
coderabbit and decide per comment: fix or debate — reply with evidence
(file:line) when the code already handles it. **DeepSource and Codacy are
ignored** — their check statuses and comments are noise (stylistic lint
profiles configured opposite to project conventions): don't fix, don't
debate, don't treat them as blocking. No branch protection gates on them.
Their findings get handled periodically in dedicated batch-fix PRs.
3. **Server PRs** (`server/` — REvoCompute): deploy to the live SLURM server with `REVODESIGN_SERVER_ENV=/repo/REvoDesign/server/.env.production.v7-slurm bash server/run/restart.sh restart --use-proxy` (absolute env path, exactly ONE restart running at a time), then live-verify the affected pages on `https://revocompute.yaoyy.moe` and `https://revocompute-direct.yaoyy.moe` (auth-walled pages via guest login `group_users` through `/compute/api/auth/login`). Disk-full recovery: `docker buildx prune`, apptainer cache clean, remove obsolete SIFs under `/mnt/data/srv/revodesign/server-slurm/images/`. Submit living tests with real data files from `tests/data` when behavior changed. Check the fixed page behaves as designed in incognito (cache-free), not just that the served static files contain the change.
4. **Main program PRs** (PyMOL plugin): CI and review comments only — no server deploy. Run the relevant gates (`make kw-test PYTEST_KW='<keyword>'`); cross-Qt checks exist in `REvoDesignTestFlight` (PyQt5) and `REvoDesignTestFlightQt6`.
5. When CI is green and every comment is fixed or debated, push a final empty marker commit `chore: Done fixing — <what was live/CI verified>` as the branch head (the plain `Done fixing` prefix is an intentional exception to the conventional-commit rule, matching the squash-merge habit); the user squash-merges from there.
- **Standalone bootstrapper encoding**: Keep `src/REvoDesign/tools/package_manager.py` ASCII-only because it is published as `REvoDesign_PyMOL.py` and may be saved through locale-aware Windows tools. A UTF-8-to-GBK transcode turns characters such as `→` into bytes beginning with `0xA1`, which Python 3 rejects while parsing the file as UTF-8. Preserve both the GBK-compilation regression test and the `check-standalone-source-ascii` pre-commit guard; non-ASCII text remains acceptable in normal packaged modules.
- **Simplified-Chinese Windows living test**: After merging the installer change, republish both `REvoDesign_PyMOL.py` and `manifest.json` before testing so the Gist artifacts match the merged source. Test the documented first-install flow early on a Simplified-Chinese Windows machine with the system UTF-8 option **off** (the default CP936/GBK adverse path); record `chcp`, download the raw Gist through the normal user path, install it in PyMOL, and confirm bootstrap and installation succeed while the deferred UTF-8 guidance dialog appears once without blocking plugin registration. Repeat with **Region → Administrative language settings → Change system locale → “Beta: Use Unicode UTF-8 for worldwide language support”** enabled and the machine rebooted (CP65001); bootstrap and installation must also succeed, and the dialog must not appear. The toggle is a user workaround for non-English CMD/Windows PowerShell streams, not a prerequisite or substitute for CP936 compatibility.
- **Simplified-Chinese Windows living test**: After merging the installer change, republish both `REvoDesign_PyMOL.py` and `manifest.json` before testing so the Gist artifacts match the merged source. Test the documented first-install flow early on a Simplified-Chinese Windows machine with the system UTF-8 option **off** (the default CP936/GBK adverse path); record `chcp`, download the raw Gist through the normal user path, install it in PyMOL, and confirm bootstrap and installation succeed. Repeat with **Region → Administrative language settings → Change system locale → “Beta: Use Unicode UTF-8 for worldwide language support”** enabled and the machine rebooted (CP65001); bootstrap and installation must also succeed. The UTF-8 guidance lives in the docs only — the package manager no longer probes code pages or shows a dialog. The toggle is a user workaround for non-English CMD/Windows PowerShell streams, not a prerequisite or substitute for CP936 compatibility.
- **Version bumping**:
1. Update `__version__` in `src/REvoDesign/__init__.py` (validate format at https://regex101.com/r/6AoOI9/1).
2. Run `make tag` — it extracts old/new versions from the git diff, inserts a dated `[new_version]` section in `CHANGELOG.md`, commits `CHANGELOG.md` + `__init__.py`, creates an annotated tag with the changelog between versions, and pushes with `--tags`.
Expand Down
15 changes: 12 additions & 3 deletions docs/user-guide/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ Supported operating systems:
REvoDesign continues to test the default Simplified-Chinese Windows
CP936/GBK path. This setting is a recommended interoperability workaround,
not a prerequisite or a substitute for reporting encoding problems.
When the Package Manager detects CP936 during PyMOL startup, it displays
this procedure as a reminder. The reminder appears once per PyMOL session
and does not block the plugin from loading.

## Install PyMOL

Expand All @@ -60,6 +57,18 @@ pymol

If you already have a working PyMOL installation (2.5+), skip this step.

### macOS notes

- Use **PyMOL Open-Source on native Apple Silicon**. The conda-forge build
above installs the `osx-arm64` binary automatically on Apple Silicon — no
Rosetta required. The official **PyMOL bundle runs under Rosetta x86_64
emulation and is not recommended**; REvoDesign is tested against the native
conda-forge build, not the emulated bundle.
- **Xcode Command Line Tools** are required to compile dependencies that have
no prebuilt wheel. Install them once before the first installation:

xcode-select --install

## Install REvoDesign Package Manager

1. Open PyMOL.
Expand Down
16 changes: 8 additions & 8 deletions server/config/task_types.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ task_types:
gremlin:
display_name: "PSSM-GREMLIN"
category: evolution
intro: "Co-evolutionary contact and coupling prediction from a multiple sequence alignment (PSSM + GREMLIN)."
intro: "Sequence conservation (PSI-BLAST PSSM) and co-evolutionary couplings (GREMLIN) for a protein sequence."
runtime_family: "gremlin"
input_extension: ".fasta"
input_label: "FASTA file"
Expand Down Expand Up @@ -232,7 +232,7 @@ task_types:
opendde:
display_name: "OpenDDE"
category: structure
intro: "OpenDDE structure prediction with MSA-based guidance."
intro: "All-atom structure prediction with MSA and template guidance (OpenDDE)."
runtime_family: "opendde"
gpus: true
input_extension: ".json"
Expand Down Expand Up @@ -277,7 +277,7 @@ task_types:
hypermpnn:
display_name: "HyperMPNN"
category: inverse_folding
intro: "ProteinMPNN sequence design for the given backbone."
intro: "Thermostable sequence design using hyperthermophile-trained ProteinMPNN weights (HyperMPNN)."
runtime_family: "mpnn"
input_extension: ".pdb"
input_label: "PDB file"
Expand Down Expand Up @@ -415,7 +415,7 @@ task_types:
lasermpnn:
display_name: "LASErMPNN"
category: inverse_folding
intro: "Structure-conditioned sequence design (LASErMPNN)."
intro: "Ligand-conditioned sequence design and sidechain packing for protonated (all-atom) structures (LASErMPNN)."
runtime_family: "mpnn"
input_extension: ".pdb"
input_extensions: [".pdb", ".cif", ".mmcif"]
Expand Down Expand Up @@ -457,7 +457,7 @@ task_types:
thermompnn:
display_name: "ThermoMPNN-D"
category: fitness
intro: "Thermostability-tuned sequence design (ThermoMPNN-D)."
intro: "Stability change (ΔΔG) prediction for single and double mutants (ThermoMPNN-D)."
runtime_family: "mpnn"
input_extension: ".pdb"
input_label: "PDB file"
Expand All @@ -480,8 +480,8 @@ task_types:

prime:
display_name: "Pro-Prime"
category: function
intro: "Enzyme function (EC number) prediction from a sequence (Pro-Prime)."
category: fitness
intro: "Optimal growth temperature (OGT) prediction from a protein sequence (Pro-Prime)."
runtime_family: "prime"
runner_args: ["ogt"]
gpus: true
Expand Down Expand Up @@ -585,7 +585,7 @@ task_types:
placer:
display_name: "PLACER"
category: structure
intro: "Structure prediction with PLACER."
intro: "All-atom modeling of protein-ligand complexes from an input structure (PLACER)."
runtime_family: "placer-rfdiffusion"
runner_args: ["placer"]
gpus: true
Expand Down
2 changes: 1 addition & 1 deletion server/docker/runners/common/task_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@
elif command == "files":
print(json.dumps(manifest["files"]))
else:
sys.stderr.write("task_context.py: unknown command {!r}\n".format(command))
sys.stderr.write(f"task_context.py: unknown command {command!r}\n")
sys.exit(2)
2 changes: 1 addition & 1 deletion server/tests/test_input_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@

import pytest
from conftest import _load_pssm_module, _test_client_auth
from revocompute.input_validators import MAX_CIF_ATOMS # noqa: F401
from revocompute.input_validators import (
MAX_CIF_ATOMS, # noqa: F401
MAX_CIF_RECORD_LENGTH,
MAX_FASTA_SEQUENCES,
MAX_FASTA_TOTAL_RESIDUES,
Expand Down
99 changes: 31 additions & 68 deletions src/REvoDesign/Qt/qt_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,6 @@ def _install_scoped_alias(
setattr(container, member_name, getattr(owner, legacy_name))


def _install_flat_alias(owner: object, container_name: str, member_name: str, alias_name: str | None = None) -> None:
"""Install a flat Qt5-style alias from a scoped Qt6 enum member when missing."""

flat_name = alias_name or member_name
if hasattr(owner, flat_name):
return
container = getattr(owner, container_name, None)
if container is not None and hasattr(container, member_name):
setattr(owner, flat_name, getattr(container, member_name))


def _qt_enum(owner: Any, enum_name: str, member_name: str) -> Any:
"""Return a Qt enum member using Qt6 scoped lookup with Qt5 fallback."""

Expand Down Expand Up @@ -178,60 +167,8 @@ def _install_qtcore_scoped_aliases() -> None:
for member_name in member_names:
_install_scoped_alias(qt_namespace, container_name, member_name)

flat_aliases = (
("WidgetAttribute", "WA_DeleteOnClose"),
("WidgetAttribute", "WA_Hover"),
("WidgetAttribute", "WA_ShowWithoutActivating"),
("WidgetAttribute", "WA_TransparentForMouseEvents"),
("WidgetAttribute", "WA_TranslucentBackground"),
("ContextMenuPolicy", "CustomContextMenu"),
("TextFormat", "RichText"),
("TextFormat", "PlainText"),
("CheckState", "Checked"),
("CheckState", "Unchecked"),
("CheckState", "PartiallyChecked"),
("ItemFlag", "ItemIsUserCheckable"),
("ItemFlag", "ItemIsEnabled"),
("Orientation", "Horizontal"),
("Orientation", "Vertical"),
("ScrollBarPolicy", "ScrollBarAsNeeded"),
("ScrollBarPolicy", "ScrollBarAlwaysOff"),
("ScrollBarPolicy", "ScrollBarAlwaysOn"),
("GlobalColor", "yellow"),
("GlobalColor", "blue"),
("GlobalColor", "red"),
("GlobalColor", "green"),
("GlobalColor", "black"),
("GlobalColor", "white"),
("FocusPolicy", "NoFocus"),
("CursorShape", "PointingHandCursor"),
("WindowType", "Tool"),
("WindowType", "FramelessWindowHint"),
("WindowType", "WindowStaysOnTopHint"),
("WindowType", "WindowDoesNotAcceptFocus"),
("WindowType", "SplashScreen"),
("AlignmentFlag", "AlignLeft"),
("AlignmentFlag", "AlignRight"),
("AlignmentFlag", "AlignHCenter"),
("AlignmentFlag", "AlignJustify"),
("AlignmentFlag", "AlignTop"),
("AlignmentFlag", "AlignBottom"),
("AlignmentFlag", "AlignVCenter"),
("AlignmentFlag", "AlignCenter"),
("AlignmentFlag", "AlignLeading"),
("AlignmentFlag", "AlignTrailing"),
("BrushStyle", "NoBrush"),
("DropAction", "CopyAction"),
("DropAction", "MoveAction"),
("DropAction", "LinkAction"),
("DropAction", "IgnoreAction"),
)
for container_name, member_name in flat_aliases:
_install_flat_alias(qt_namespace, container_name, member_name)

for member_name in ("Linear", "InQuad", "OutQuad", "InOutQuad"):
_install_scoped_alias(QtCore.QEasingCurve, "Type", member_name)
_install_flat_alias(QtCore.QEasingCurve, "Type", member_name)


def _install_qtwidgets_scoped_aliases() -> None:
Expand Down Expand Up @@ -319,12 +256,8 @@ def _install_qtwidgets_scoped_aliases() -> None:
for member_name in member_names:
_install_scoped_alias(owner, container_name, member_name)

# QStackedLayout.StackingMode: scoped enum in Qt6 (StackAll, StackOne).
# Provide flat aliases (Qt5-style) so that code referencing
# QtWidgets.QStackedLayout.StackAll continues to work under Qt6.
for member_name in ("StackAll", "StackOne"):
_install_scoped_alias(getattr(QtWidgets, "QStackedLayout", None), "StackingMode", member_name)
_install_flat_alias(getattr(QtWidgets, "QStackedLayout", None), "StackingMode", member_name)


def _install_qtgui_scoped_aliases() -> None:
Expand Down Expand Up @@ -359,7 +292,6 @@ def _install_qtgui_scoped_aliases() -> None:
_install_scoped_alias(qpalette, "ColorRole", member_name)
if qpainter is not None:
_install_scoped_alias(qpainter, "RenderHint", "Antialiasing")
_install_flat_alias(qpainter, "RenderHint", "Antialiasing")


def _install_qtnetwork_scoped_aliases() -> None:
Expand Down Expand Up @@ -388,6 +320,36 @@ def _install_moved_class_aliases() -> None:
setattr(QtGui, attr_name, fallback)


def _install_unscoped_enum_bridge() -> None:
"""Alias every scoped-enum member onto its owning Qt class.

On Qt6 bindings enum members live under per-class enum types
(``QMessageBox.StandardButton.Ok``), so Qt5-style unscoped access
(``QMessageBox.Ok``) raises AttributeError. Mirror every member of
every enum type of every class in the loaded Qt modules onto the
class itself, skipping names that already exist, so any flat access
resolves on both Qt5 and Qt6 without per-API bookkeeping. On Qt5
and PySide bindings the flat names already resolve, so the bridge
aliases nothing and is a no-op.
"""

def _is_enum_type(value) -> bool:
return isinstance(value, type) and hasattr(value, "__members__")

for _qt_module in (QtCore, QtGui, QtWidgets, QtNetwork, QtWebSockets, QtSvg, QtUiTools):
if _qt_module is None:
continue
for _cls in list(vars(_qt_module).values()):
if not (isinstance(_cls, type) and getattr(_cls, "__module__", "") == _qt_module.__name__):
continue
for _enum_type in list(vars(_cls).values()):
if not _is_enum_type(_enum_type):
continue
for _member_name, _member in _enum_type.__members__.items():
if not hasattr(_cls, _member_name):
setattr(_cls, _member_name, _member)


_ALIAS_STATE = {"installed": False}


Expand All @@ -403,6 +365,7 @@ def install_qt6_aliases() -> None:
_install_qtgui_scoped_aliases()
_install_qtnetwork_scoped_aliases()
_install_qtwebsockets_scoped_aliases()
_install_unscoped_enum_bridge()
_ALIAS_STATE["installed"] = True


Expand Down
Loading
Loading