feat: add Pi origin support to code review UI (#263)

* feat: add Pi origin support to code review UI and update banner

Pi users were seeing "OpenCode" badge and getting the wrong update
command. Adds 'pi' to origin types in review/annotate servers, shows
violet "Pi" badge in code review header, and copies the correct
`pi install` command in the update banner.

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

* feat: update Pi extension from install scripts when pi is available

The install scripts now check if `pi` is on PATH and run
`pi install npm:@plannotator/pi-extension` to update the extension.
Silent for users without Pi installed.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Ramos
2026-03-09 23:41:29 -07:00
committed by GitHub
parent 709e9ffaa1
commit b6bec6943b
7 changed files with 45 additions and 13 deletions
+10 -8
View File
@@ -35,7 +35,7 @@ interface DiffData {
files: DiffFile[]; files: DiffFile[];
rawPatch: string; rawPatch: string;
gitRef: string; gitRef: string;
origin?: 'opencode' | 'claude-code'; origin?: 'opencode' | 'claude-code' | 'pi';
diffType?: string; diffType?: string;
gitContext?: GitContext; gitContext?: GitContext;
sharingEnabled?: boolean; sharingEnabled?: boolean;
@@ -139,7 +139,7 @@ const ReviewApp: React.FC = () => {
const [copyFeedback, setCopyFeedback] = useState<string | null>(null); const [copyFeedback, setCopyFeedback] = useState<string | null>(null);
const [viewedFiles, setViewedFiles] = useState<Set<string>>(new Set()); const [viewedFiles, setViewedFiles] = useState<Set<string>>(new Set());
const [hideViewedFiles, setHideViewedFiles] = useState(false); const [hideViewedFiles, setHideViewedFiles] = useState(false);
const [origin, setOrigin] = useState<'opencode' | 'claude-code' | null>(null); const [origin, setOrigin] = useState<'opencode' | 'claude-code' | 'pi' | null>(null);
const [diffType, setDiffType] = useState<string>('uncommitted'); const [diffType, setDiffType] = useState<string>('uncommitted');
const [gitContext, setGitContext] = useState<GitContext | null>(null); const [gitContext, setGitContext] = useState<GitContext | null>(null);
const [isLoadingDiff, setIsLoadingDiff] = useState(false); const [isLoadingDiff, setIsLoadingDiff] = useState(false);
@@ -214,7 +214,7 @@ const ReviewApp: React.FC = () => {
.then((data: { .then((data: {
rawPatch: string; rawPatch: string;
gitRef: string; gitRef: string;
origin?: 'opencode' | 'claude-code'; origin?: 'opencode' | 'claude-code' | 'pi';
diffType?: string; diffType?: string;
gitContext?: GitContext; gitContext?: GitContext;
sharingEnabled?: boolean; sharingEnabled?: boolean;
@@ -615,9 +615,11 @@ const ReviewApp: React.FC = () => {
<span className={`text-[10px] px-1.5 py-0.5 rounded font-medium hidden md:inline ${ <span className={`text-[10px] px-1.5 py-0.5 rounded font-medium hidden md:inline ${
origin === 'claude-code' origin === 'claude-code'
? 'bg-orange-500/15 text-orange-400' ? 'bg-orange-500/15 text-orange-400'
: 'bg-zinc-500/20 text-zinc-400' : origin === 'pi'
? 'bg-violet-500/15 text-violet-400'
: 'bg-zinc-500/20 text-zinc-400'
}`}> }`}>
{origin === 'claude-code' ? 'Claude Code' : 'OpenCode'} {origin === 'claude-code' ? 'Claude Code' : origin === 'pi' ? 'Pi' : 'OpenCode'}
</span> </span>
)} )}
{repoInfo && ( {repoInfo && (
@@ -991,10 +993,10 @@ const ReviewApp: React.FC = () => {
title={submitted === 'approved' ? 'Changes Approved' : 'Feedback Sent'} title={submitted === 'approved' ? 'Changes Approved' : 'Feedback Sent'}
subtitle={ subtitle={
submitted === 'approved' submitted === 'approved'
? `${origin === 'claude-code' ? 'Claude Code' : 'OpenCode'} will proceed with the changes.` ? `${origin === 'claude-code' ? 'Claude Code' : origin === 'pi' ? 'Pi' : 'OpenCode'} will proceed with the changes.`
: `${origin === 'claude-code' ? 'Claude Code' : 'OpenCode'} will address your review feedback.` : `${origin === 'claude-code' ? 'Claude Code' : origin === 'pi' ? 'Pi' : 'OpenCode'} will address your review feedback.`
} }
agentLabel={origin === 'claude-code' ? 'Claude Code' : 'OpenCode'} agentLabel={origin === 'claude-code' ? 'Claude Code' : origin === 'pi' ? 'Pi' : 'OpenCode'}
/> />
{/* Update notification */} {/* Update notification */}
+1 -1
View File
@@ -31,7 +31,7 @@ export interface AnnotateServerOptions {
/** HTML content to serve for the UI */ /** HTML content to serve for the UI */
htmlContent: string; htmlContent: string;
/** Origin identifier for UI customization */ /** Origin identifier for UI customization */
origin?: "opencode" | "claude-code"; origin?: "opencode" | "claude-code" | "pi";
/** Whether URL sharing is enabled (default: true) */ /** Whether URL sharing is enabled (default: true) */
sharingEnabled?: boolean; sharingEnabled?: boolean;
/** Custom base URL for share links */ /** Custom base URL for share links */
+1 -1
View File
@@ -34,7 +34,7 @@ export interface ReviewServerOptions {
/** HTML content to serve for the UI */ /** HTML content to serve for the UI */
htmlContent: string; htmlContent: string;
/** Origin identifier for UI customization */ /** Origin identifier for UI customization */
origin?: "opencode" | "claude-code"; origin?: "opencode" | "claude-code" | "pi";
/** Current diff type being displayed */ /** Current diff type being displayed */
diffType?: DiffType; diffType?: DiffType;
/** Git context with branch info and available diff options */ /** Git context with branch info and available diff options */
+11 -3
View File
@@ -1,7 +1,8 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useUpdateCheck } from '../hooks/useUpdateCheck'; import { useUpdateCheck } from '../hooks/useUpdateCheck';
const INSTALL_COMMAND = 'curl -fsSL https://plannotator.ai/install.sh | bash'; const DEFAULT_INSTALL_COMMAND = 'curl -fsSL https://plannotator.ai/install.sh | bash';
const PI_INSTALL_COMMAND = 'pi install npm:@plannotator/pi-extension';
interface UpdateBannerProps { interface UpdateBannerProps {
origin?: 'claude-code' | 'opencode' | 'pi' | null; origin?: 'claude-code' | 'opencode' | 'pi' | null;
@@ -16,13 +17,15 @@ export const UpdateBanner: React.FC<UpdateBannerProps> = ({ origin }) => {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const previewOrigin = urlParams.get('preview-origin'); const previewOrigin = urlParams.get('preview-origin');
const effectiveOrigin = previewOrigin || origin; const effectiveOrigin = previewOrigin || origin;
const isPi = effectiveOrigin === 'pi';
const isOpenCode = effectiveOrigin === 'opencode'; const isOpenCode = effectiveOrigin === 'opencode';
const installCommand = isPi ? PI_INSTALL_COMMAND : DEFAULT_INSTALL_COMMAND;
if (!updateInfo?.updateAvailable || dismissed) return null; if (!updateInfo?.updateAvailable || dismissed) return null;
const handleCopy = async () => { const handleCopy = async () => {
try { try {
await navigator.clipboard.writeText(INSTALL_COMMAND); await navigator.clipboard.writeText(installCommand);
setCopied(true); setCopied(true);
setTimeout(() => setCopied(false), 2000); setTimeout(() => setCopied(false), 2000);
} catch (e) { } catch (e) {
@@ -76,12 +79,17 @@ export const UpdateBanner: React.FC<UpdateBannerProps> = ({ origin }) => {
You have {updateInfo.currentVersion} You have {updateInfo.currentVersion}
</p> </p>
{/* OpenCode extra instructions */} {/* Agent-specific extra instructions */}
{isOpenCode && ( {isOpenCode && (
<p className="text-xs text-muted-foreground mt-3"> <p className="text-xs text-muted-foreground mt-3">
Run the install script, then restart OpenCode. Run the install script, then restart OpenCode.
</p> </p>
)} )}
{isPi && (
<p className="text-xs text-muted-foreground mt-3">
Run the install command, then restart Pi.
</p>
)}
<div className="mt-4 flex items-center gap-2"> <div className="mt-4 flex items-center gap-2">
<button <button
+8
View File
@@ -155,6 +155,14 @@ echo }
echo Updated plugin hooks at !PLUGIN_HOOKS! echo Updated plugin hooks at !PLUGIN_HOOKS!
) )
REM Update Pi extension if pi is installed
where pi >nul 2>&1
if !ERRORLEVEL! equ 0 (
echo Updating Pi extension...
pi install npm:@plannotator/pi-extension
echo Pi extension updated.
)
REM Install /review slash command REM Install /review slash command
if defined CLAUDE_CONFIG_DIR ( if defined CLAUDE_CONFIG_DIR (
set "CLAUDE_COMMANDS_DIR=%CLAUDE_CONFIG_DIR%\commands" set "CLAUDE_COMMANDS_DIR=%CLAUDE_CONFIG_DIR%\commands"
+7
View File
@@ -110,6 +110,13 @@ if (Test-Path $pluginHooks) {
Remove-Item -Recurse -Force "$env:USERPROFILE\.cache\opencode\node_modules\@plannotator" -ErrorAction SilentlyContinue Remove-Item -Recurse -Force "$env:USERPROFILE\.cache\opencode\node_modules\@plannotator" -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force "$env:USERPROFILE\.bun\install\cache\@plannotator" -ErrorAction SilentlyContinue Remove-Item -Recurse -Force "$env:USERPROFILE\.bun\install\cache\@plannotator" -ErrorAction SilentlyContinue
# Update Pi extension if pi is installed
if (Get-Command pi -ErrorAction SilentlyContinue) {
Write-Host "Updating Pi extension..."
pi install npm:@plannotator/pi-extension
Write-Host "Pi extension updated."
}
# Install Claude Code slash command # Install Claude Code slash command
$claudeCommandsDir = if ($env:CLAUDE_CONFIG_DIR) { "$env:CLAUDE_CONFIG_DIR\commands" } else { "$env:USERPROFILE\.claude\commands" } $claudeCommandsDir = if ($env:CLAUDE_CONFIG_DIR) { "$env:CLAUDE_CONFIG_DIR\commands" } else { "$env:USERPROFILE\.claude\commands" }
New-Item -ItemType Directory -Force -Path $claudeCommandsDir | Out-Null New-Item -ItemType Directory -Force -Path $claudeCommandsDir | Out-Null
+7
View File
@@ -110,6 +110,13 @@ fi
# Clear any cached OpenCode plugin to force fresh download on next run # Clear any cached OpenCode plugin to force fresh download on next run
rm -rf "$HOME/.cache/opencode/node_modules/@plannotator" "$HOME/.bun/install/cache/@plannotator" 2>/dev/null || true rm -rf "$HOME/.cache/opencode/node_modules/@plannotator" "$HOME/.bun/install/cache/@plannotator" 2>/dev/null || true
# Update Pi extension if pi is installed
if command -v pi &>/dev/null; then
echo "Updating Pi extension..."
pi install npm:@plannotator/pi-extension
echo "Pi extension updated."
fi
# Install /review slash command # Install /review slash command
CLAUDE_COMMANDS_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/commands" CLAUDE_COMMANDS_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/commands"
mkdir -p "$CLAUDE_COMMANDS_DIR" mkdir -p "$CLAUDE_COMMANDS_DIR"