Skip to content

fix(extensions): open status and widget links in a new tab - #1377

Merged
agegr merged 2 commits into
agegr:mainfrom
Zhangs-11:fix/ansi-links-new-tab
Oct 29, 2026
Merged

agegr merged 2 commits into
agegr:mainfrom
Zhangs-11:fix/ansi-links-new-tab

Conversation

@Zhangs-11

Copy link
Copy Markdown
Contributor

Fixes #1374

Problem

An extension that shows a link through ctx.ui.setStatus() (or a widget / custom panel) with an OSC 8 hyperlink renders it as a plain <a href>. Clicking it navigates the Pi Web tab away from the session. On a phone or tablet there is no modifier-click to avoid that.

Reproduce on main

  1. Add a global extension, e.g. ~/.pi/agent/extensions/status-link.js:

    export default function statusLink(pi) {
      pi.on("session_start", async (_event, ctx) => {
        ctx.ui.setStatus("preview", "Preview: \x1b]8;;https://example.com/\x1b\\example.com\x1b]8;;\x1b\\");
      });
    }
  2. Open a session and send anything that starts its runtime (!true is enough).

  3. The status bar shows Preview: example.com. Its DOM is <a href="https://example.com/">example.com</a>, and clicking it replaces the Pi Web page.

Cause

components/AnsiText.tsx returns ansi_up's HTML as is, and ansi_up emits OSC 8 links without a target. Chat Markdown already opens such links with target="_blank" rel="noopener noreferrer" (components/MarkdownBody.tsx), so the two kinds of links behaved differently.

Fix

AnsiText adds target="_blank" rel="noopener noreferrer" to the anchors ansi_up produces. This covers the status bar, widgets and custom panels, which all render through AnsiText. It is safe to do on the string: ansi_up escapes HTML in the text, so <a href=" can only come from its own hyperlink output, and it only emits http/https links.

The issue suggested keeping same-host links in the current tab. I kept one rule for all of them instead, the same rule chat Markdown uses for absolute links, so a link behaves the same wherever pi shows it. Happy to change that if you prefer the host check.

Checks

  • New tests in components/AnsiText.test.mjs: an OSC 8 link gets the new-tab attributes, and link-like text stays escaped.
  • npm test (2871 passed, 0 failed), node_modules/.bin/tsc --noEmit, npm run lint.
  • Manually with the extension above in headless Chrome: after the change the link opens in a new tab and the session page stays.
  • Added one line to the extension status bar notes in docs/agents/sessions.md.

🤖 Generated with Claude Code

An OSC 8 hyperlink in an extension status, widget or custom panel rendered
as a plain anchor, so clicking a preview link navigated the Pi Web tab away
from its session. They now open in a new tab, like links in chat Markdown.

Fixes agegr#1374

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tammelin

Copy link
Copy Markdown
Contributor

Opening all links in a new tab works for my use case—thanks for the fix!

I can imagine an extension linking to another session within Pi Web, where staying in the same tab might be preferable. But that’s hypothetical, not a requirement from me, so I’m happy with the simpler approach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@agegr

agegr commented Oct 29, 2026

Copy link
Copy Markdown
Owner

Thanks. I checked ansi_up 6.0.6's source. It only emits anchors for http/https (a scheme allowlist), and it HTML-escapes both the URL and the text, so any <a href=" in its output is its own link and the string replace is safe. ansi_up never produces a relative ?session= link, and chat Markdown already opens an absolute same-instance URL in a new tab, so one rule for every extension link keeps the two consistent.

I pushed one commit that mirrors the change into demo/components/AnsiText.tsx, which is a copy of this file. tsc, lint and the AnsiText tests pass.

@agegr
agegr merged commit 6a5c02f into agegr:main Oct 29, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Open external extension-status links in a new tab

3 participants