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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | Click-proxy base URL for the editor/extension links (tracks owner/repo/pr). Empty links straight to the editor deep link and Marketplace. |
| `cta_base_url` | empty | Click-proxy base URL: deep-links the editor link into VS Code/Cursor and adds a "get the extension" link (tracks owner/repo/pr). Empty links to the extension listing instead (GitHub strips `vscode:`/`cursor:` from comments). |

## Outputs

Expand Down
6 changes: 3 additions & 3 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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) 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.'
description: 'Base URL of the click proxy (e.g. https://go.codeboarding.org). When set, the editor link deep-links into VS Code/Cursor via the proxy and a "get the extension" link is added (owner/repo/pr tracked). Empty (default) links to the extension listing instead, since GitHub strips vscode:/cursor: schemes from comment links.'
required: false
default: ''
trigger_command:
Expand Down Expand Up @@ -573,8 +573,8 @@ runs:
else echo "$N components changed"; fi
}

# 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 footer: an editor link (proxy deep-link when CTA_BASE is set, else the
# extension's https listing — GitHub strips vscode:/cursor:) plus the ⚠️ banner.
cta() {
python3 "$ACTION_PATH/scripts/build_cta.py" \
--cta-base "$CTA_BASE" --owner "$OWNER" --repo "$REPO" --pr "$PR" \
Expand Down
47 changes: 27 additions & 20 deletions scripts/build_cta.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"""Build the call-to-action footer appended to the architecture-diff PR comment.

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. 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
``<scheme>: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.
The footer drives to the VS Code/Cursor **extension** with an editor link, and a
warning banner when real health findings exist. With a click proxy (``cta_base``)
the links route through it (owner/repo/pr tracked) and can deep-link into the
editor (the proxy redirects to a ``vscode:``/``cursor:`` URL) plus a separate
"install the extension" link. Without a proxy GitHub's comment sanitizer strips
custom ``vscode:``/``cursor:`` schemes — a deep link would render as dead text —
so the editor link points at the extension's plain-https listing instead (VS Code
Marketplace, Cursor via Open VSX), which is the only clickable option. A no-install
hosted-webview ("explore in browser") tier is intentionally deferred (see
docs/COMMIT_STRATEGY.md) — the committed analysis already supports it later.

Editor coverage is deliberately limited to **VS Code and Cursor**. Per the 2025
Stack Overflow Developer Survey (https://survey.stackoverflow.co/2025/technology/),
Expand Down Expand Up @@ -47,18 +49,22 @@ 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}"
# No-proxy editor targets. Must be plain https: GitHub strips custom URI schemes
# (vscode:/cursor:) from comment links, so a deep link renders as dead text. Each
# editor points at its extension listing instead — clickable, and installs there.
_EDITOR_MARKETPLACE = {
"vscode": "https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding",
"cursor": "https://open-vsx.org/extension/CodeBoarding/codeboarding",
}


def build_cta(cta_base: str, owner: str, repo: str, pr: str, repo_path: Path, issues: int = 0) -> str:
"""Return the markdown CTA footer: a health-warning banner plus editor/extension links.
"""Return the markdown CTA footer: a health-warning banner plus an editor link.

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 ``<scheme>:extension/...`` deep link and the Marketplace listing.
With a ``cta_base`` proxy the links route through it (owner/repo/pr tracked),
deep-link into the editor, and add a separate "get the extension" link. Without
a proxy the editor link is the extension's https listing (GitHub strips custom
``vscode:``/``cursor:`` schemes), and the redundant install link is dropped.
The ⚠️ banner shows whenever ``issues > 0``.
"""
parts: list[str] = []
Expand All @@ -74,14 +80,15 @@ def link(path: str, **extra: str) -> str:
return f"{base}/{path}?" + urlencode({"owner": owner, "repo": repo, "pr": pr, **extra})

editor_href = {e: link("open-in-editor", editor=e) for e in editors}
extension_href = link("use-marketplace")
extension_href: str | None = link("use-marketplace")
else:
editor_href = {e: _EDITOR_DEEPLINK[e] for e in editors}
extension_href = _MARKETPLACE_URL
editor_href = {e: _EDITOR_MARKETPLACE[e] for e in editors}
extension_href = None

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 extension_href:
parts.append(f"💡 New to CodeBoarding? [**Get the extension →**]({extension_href})")

lines = ["", "---"]
for p in parts:
Expand Down
17 changes: 11 additions & 6 deletions tests/test_build_cta.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,22 @@ def test_both_vscode_first(self):


class TestBuildCta(unittest.TestCase):
def test_no_proxy_falls_back_to_editor_deeplink_and_marketplace(self):
def test_no_proxy_links_editor_to_https_listing_no_get_extension(self):
out = bc.build_cta("", "o", "r", "1", repo_with(".cursor"), issues=3)
self.assertIn("3 architecture issues found", out)
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
# Cursor -> Open VSX https listing. A cursor: scheme would be stripped by GitHub.
self.assertIn("[**Open in Cursor →**](https://open-vsx.org/extension/CodeBoarding/codeboarding)", out)
self.assertNotIn("cursor:extension", out)
self.assertNotIn("Get the extension", out) # dropped without a proxy
self.assertNotIn("Open in VS Code", out) # cursor-only repo

def test_no_proxy_default_vscode_deeplink_no_banner_at_zero(self):
def test_no_proxy_vscode_marketplace_https_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.assertIn(
"[**Open in VS Code →**](https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding)", out
)
self.assertNotIn("vscode:extension", out) # custom scheme stripped by GitHub
self.assertNotIn("Get the extension", out)
self.assertNotIn("architecture issue", out) # banner suppressed at 0 issues

def test_links_banner_and_cursor_only(self):
Expand Down
Loading