diff --git a/src/cmdforge/gui/pages/tools_page.py b/src/cmdforge/gui/pages/tools_page.py index 054af15..74d5c5d 100644 --- a/src/cmdforge/gui/pages/tools_page.py +++ b/src/cmdforge/gui/pages/tools_page.py @@ -648,8 +648,11 @@ class ToolsPage(QWidget): tool_name = self._current_tool.name # Save before refresh clears it from ..dialogs.publish_dialog import PublishDialog dialog = PublishDialog(self, self._current_tool) - if dialog.exec(): - self.refresh() # Refresh to show updated publish state indicator + result = dialog.exec() + # Always refresh after dialog closes - tool config may have been updated + # even if user canceled (e.g., server processed request before client timeout) + self.refresh() + if result: self.main_window.show_status(f"Published '{tool_name}'") def _sync_tool_status(self): diff --git a/src/cmdforge/registry/app.py b/src/cmdforge/registry/app.py index a22bbea..4458f5d 100644 --- a/src/cmdforge/registry/app.py +++ b/src/cmdforge/registry/app.py @@ -1797,15 +1797,34 @@ def create_app() -> Flask: return error_response("VALIDATION_ERROR", "Tag exceeds 32 characters") owner = g.current_publisher["slug"] + + # Compute config hash early for idempotency check + config_hash = compute_yaml_hash(config_text) + existing = query_one( g.db, - "SELECT published_at FROM tools WHERE owner = ? AND name = ? AND version = ?", + "SELECT published_at, config_hash, moderation_status, visibility FROM tools WHERE owner = ? AND name = ? AND version = ?", [owner, name, version], ) if existing: + # If same content (config_hash matches), treat as idempotent retry - return success + if existing["config_hash"] == config_hash: + return jsonify({ + "data": { + "owner": owner, + "name": name, + "version": version, + "config_hash": config_hash, + "pr_url": "", + "status": existing["moderation_status"], + "visibility": existing["visibility"], + "already_published": True, + } + }) + # Different content - actual version conflict return error_response( "VERSION_EXISTS", - f"Version {version} already exists", + f"Version {version} already exists with different content. Use a new version number.", 409, details={"published_at": existing["published_at"]}, ) @@ -1895,8 +1914,7 @@ def create_app() -> Flask: tags_json = json.dumps(tags) - # Compute content hash for integrity verification - config_hash = compute_yaml_hash(config_text) + # config_hash already computed earlier for idempotency check # Determine status based on scrutiny if scrutiny_report and scrutiny_report.get("decision") == "approve": diff --git a/src/cmdforge/web/routes.py b/src/cmdforge/web/routes.py index 3c51313..e754c72 100644 --- a/src/cmdforge/web/routes.py +++ b/src/cmdforge/web/routes.py @@ -602,7 +602,32 @@ def dashboard_tools(): token = session.get("auth_token") user = _load_current_publisher() or session.get("user", {}) status, payload = _api_get("/api/v1/me/tools", token=token) - tools = payload.get("data", []) if status == 200 else [] + all_tools = payload.get("data", []) if status == 200 else [] + + # Consolidate multiple versions into single tool entries + # Group by tool name, keep latest version as main, collect all versions + tools_by_name = {} + for tool in all_tools: + name = tool.get("name") + if name not in tools_by_name: + tools_by_name[name] = { + **tool, + "versions": [tool.get("version")], + "all_downloads": tool.get("downloads", 0), + } + else: + # Add this version to the list + tools_by_name[name]["versions"].append(tool.get("version")) + tools_by_name[name]["all_downloads"] += tool.get("downloads", 0) + # Keep the latest version's data (assuming sorted by published_at desc) + # The first one we saw is the latest, so no need to update + + # Convert back to list and sort versions for display + tools = list(tools_by_name.values()) + for tool in tools: + tool["versions"].sort(key=lambda v: [int(x) for x in v.split(".")], reverse=True) + tool["version_count"] = len(tool["versions"]) + tool["downloads"] = tool["all_downloads"] # Use total across all versions # Load rating/issue data for each tool for tool in tools: diff --git a/src/cmdforge/web/templates/dashboard/tools.html b/src/cmdforge/web/templates/dashboard/tools.html index 3598379..87788fe 100644 --- a/src/cmdforge/web/templates/dashboard/tools.html +++ b/src/cmdforge/web/templates/dashboard/tools.html @@ -69,7 +69,14 @@
{{ tool.published_at|timeago }}
+ {% endif %}