diff --git a/README.md b/README.md index 2a2571d..04c2d33 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ The command needs the `issue_comment` trigger and runs from your default branch | `parsing_model` | `google/gemini-3.1-flash-lite-preview` | Parsing model. OpenRouter default shown; other providers use their own engine default. | | `comment_header` | `Architecture review` | Heading for the PR comment. | | `trigger_command` | `/codeboarding` | Slash command for trusted on-demand runs. | -| `cta_base_url` | empty | Optional click-proxy base URL for editor and extension links. | +| `cta_base_url` | empty | Click-proxy base URL for the editor/extension links (tracks owner/repo/pr). Empty links straight to the editor deep link and Marketplace. | ## Outputs diff --git a/action.yml b/action.yml index 7d9c728..5badad0 100644 --- a/action.yml +++ b/action.yml @@ -51,7 +51,7 @@ inputs: required: false default: '1' cta_base_url: - description: 'Base URL of the click proxy (e.g. https://go.codeboarding.org). When set, the comment adds "open in VS Code/Cursor" / "get the extension" links with owner/repo/pr appended. Empty disables the CTA.' + description: 'Base URL of the click proxy (e.g. https://go.codeboarding.org) for the "open in VS Code/Cursor" / "get the extension" links, so owner/repo/pr are tracked. Empty (default) links straight to the editor deep link and Marketplace instead.' required: false default: '' trigger_command: @@ -573,8 +573,8 @@ runs: else echo "$N components changed"; fi } - # CTA footer (editor + extension links via the click proxy, warning banner - # on real health findings). build_cta also emits the ⚠️ banner with no proxy. + # CTA footer: editor + extension links (via the click proxy when CTA_BASE is + # set, else straight to the editor deep link / Marketplace) plus the ⚠️ banner. cta() { python3 "$ACTION_PATH/scripts/build_cta.py" \ --cta-base "$CTA_BASE" --owner "$OWNER" --repo "$REPO" --pr "$PR" \ diff --git a/scripts/build_cta.py b/scripts/build_cta.py index 553c06b..070d27d 100644 --- a/scripts/build_cta.py +++ b/scripts/build_cta.py @@ -1,9 +1,11 @@ """Build the call-to-action footer appended to the architecture-diff PR comment. -The footer links into CodeBoarding's click proxy (so owner/repo/pr are tracked) -and currently drives straight to the VS Code/Cursor **extension**: an "open this +The footer drives straight to the VS Code/Cursor **extension**: an "open this architecture in your editor" link (editor-specific) plus an "install the -extension" link, and a warning banner when real health findings exist. A +extension" link, and a warning banner when real health findings exist. When a +click proxy (``cta_base``) is set the links route through it so owner/repo/pr are +tracked; otherwise they point at the final destinations directly — the editor +``:extension/...`` deep link and the Marketplace listing. A no-install hosted-webview ("explore in browser") tier is intentionally deferred (see docs/COMMIT_STRATEGY.md) — the committed analysis already supports it later. @@ -45,33 +47,42 @@ def detect_editors(repo_path: Path) -> list[str]: _EDITOR_LABEL = {"vscode": "VS Code", "cursor": "Cursor"} +# No-proxy fallback targets: the final destinations the click proxy would route to. +_EXTENSION_ID = "Codeboarding.codeboarding" +_EDITOR_DEEPLINK = {e: f"{e}:extension/{_EXTENSION_ID}" for e in _EDITOR_LABEL} +_MARKETPLACE_URL = f"https://marketplace.visualstudio.com/items?itemName={_EXTENSION_ID}" + def build_cta(cta_base: str, owner: str, repo: str, pr: str, repo_path: Path, issues: int = 0) -> str: - """Return the markdown CTA footer (the warning banner shows even without a proxy URL). + """Return the markdown CTA footer: a health-warning banner plus editor/extension links. - The ⚠️ health banner is informational and needs no proxy, so it renders - whenever ``issues > 0``; the editor/marketplace links require ``cta_base``. - Returns '' only when there's nothing to show. + With a ``cta_base`` proxy the links route through it (owner/repo/pr tracked); + without one they point straight to the destinations the proxy would route to — + the editor's ``:extension/...`` deep link and the Marketplace listing. + The ⚠️ banner shows whenever ``issues > 0``. """ parts: list[str] = [] if issues > 0: noun = "issue" if issues == 1 else "issues" parts.append(f"⚠️ **{issues} architecture {noun} found** — open CodeBoarding to explore them.") + editors = detect_editors(repo_path) if cta_base: base = cta_base.rstrip("/") def link(path: str, **extra: str) -> str: return f"{base}/{path}?" + urlencode({"owner": owner, "repo": repo, "pr": pr, **extra}) - editor_links = " · ".join( - f"[**Open in {_EDITOR_LABEL[e]} →**]({link('open-in-editor', editor=e)})" for e in detect_editors(repo_path) - ) - parts.append(f"See this architecture in your editor: {editor_links}") - parts.append(f"💡 New to CodeBoarding? [**Get the extension →**]({link('use-marketplace')})") + editor_href = {e: link("open-in-editor", editor=e) for e in editors} + extension_href = link("use-marketplace") + else: + editor_href = {e: _EDITOR_DEEPLINK[e] for e in editors} + extension_href = _MARKETPLACE_URL + + editor_links = " · ".join(f"[**Open in {_EDITOR_LABEL[e]} →**]({editor_href[e]})" for e in editors) + parts.append(f"See this architecture in your editor: {editor_links}") + parts.append(f"💡 New to CodeBoarding? [**Get the extension →**]({extension_href})") - if not parts: - return "" lines = ["", "---"] for p in parts: lines += ["", p] diff --git a/tests/test_build_cta.py b/tests/test_build_cta.py index acb5580..38ea171 100644 --- a/tests/test_build_cta.py +++ b/tests/test_build_cta.py @@ -31,13 +31,18 @@ def test_both_vscode_first(self): class TestBuildCta(unittest.TestCase): - def test_empty_base_yields_no_footer(self): - self.assertEqual(bc.build_cta("", "o", "r", "1", repo_with()), "") - - def test_warning_shows_without_cta_base(self): - out = bc.build_cta("", "o", "r", "1", repo_with(), issues=3) + def test_no_proxy_falls_back_to_editor_deeplink_and_marketplace(self): + out = bc.build_cta("", "o", "r", "1", repo_with(".cursor"), issues=3) self.assertIn("3 architecture issues found", out) - self.assertNotIn("http", out) # no links without a proxy base + self.assertIn("[**Open in Cursor →**](cursor:extension/Codeboarding.codeboarding)", out) + self.assertIn("marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding", out) + self.assertNotIn("open-in-editor", out) # no proxy routing + self.assertNotIn("Open in VS Code", out) # cursor-only repo + + def test_no_proxy_default_vscode_deeplink_no_banner_at_zero(self): + out = bc.build_cta("", "o", "r", "1", repo_with()) # neither dir, no issues + self.assertIn("[**Open in VS Code →**](vscode:extension/Codeboarding.codeboarding)", out) + self.assertNotIn("architecture issue", out) # banner suppressed at 0 issues def test_links_banner_and_cursor_only(self): out = bc.build_cta("https://x.dev/", "Org", "Repo", "9", repo_with(".cursor"), issues=2)