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
7 changes: 4 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions packages/editor/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { useActiveSection } from '@plannotator/ui/hooks/useActiveSection';
import { storage } from '@plannotator/ui/utils/storage';
import { configStore } from '@plannotator/ui/config';
import { CompletionOverlay } from '@plannotator/ui/components/CompletionOverlay';
import { UpdateBanner } from '@plannotator/ui/components/UpdateBanner';
import { useUpdateCheck } from '@plannotator/ui/hooks/useUpdateCheck';
import { PlanAIAnnouncementDialog } from '@plannotator/ui/components/PlanAIAnnouncementDialog';
import { getObsidianSettings, getEffectiveVaultPath, isObsidianConfigured, CUSTOM_PATH_SENTINEL } from '@plannotator/ui/utils/obsidian';
import { getBearSettings } from '@plannotator/ui/utils/bear';
Expand Down Expand Up @@ -153,6 +153,7 @@ const App: React.FC = () => {
const [origin, setOrigin] = useState<Origin | null>(null);
const [gitUser, setGitUser] = useState<string | undefined>();
const [isWSL, setIsWSL] = useState(false);
const updateInfo = useUpdateCheck();
const [globalAttachments, setGlobalAttachments] = useState<ImageAttachment[]>([]);
const [annotateMode, setAnnotateMode] = useState(false);
const [gate, setGate] = useState(false);
Expand Down Expand Up @@ -1993,6 +1994,8 @@ const App: React.FC = () => {
onSaveToBear={handleSaveToBear}
onSaveToOctarine={handleSaveToOctarine}
appVersion={typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.0.0'}
updateInfo={updateInfo}
isWSL={isWSL}
agentInstructionsEnabled={isApiMode && !archive.archiveMode && !annotateMode && !goalSetupMode}
obsidianConfigured={isObsidianConfigured()}
bearConfigured={getBearSettings().enabled}
Expand Down Expand Up @@ -2519,9 +2522,6 @@ const App: React.FC = () => {
agentLabel={agentName}
/>

{/* Update notification */}
<UpdateBanner origin={origin} isWSL={isWSL} />

<PlanAIAnnouncementDialog
isOpen={shouldShowPlanAIAnnouncement}
origin={origin}
Expand Down
8 changes: 8 additions & 0 deletions packages/editor/components/AppHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import type { Origin } from '@plannotator/shared/agents';
import type { Agent } from '@plannotator/ui/hooks/useAgents';
import type { UpdateInfo } from '@plannotator/ui/hooks/useUpdateCheck';
import { FeedbackButton, ApproveButton, ExitButton } from '@plannotator/ui/components/ToolbarButtons';
import { ApproveDropdown } from '@plannotator/ui/components/ApproveDropdown';
import { Settings } from '@plannotator/ui/components/Settings';
Expand Down Expand Up @@ -76,6 +77,8 @@ interface AppHeaderProps {

// PlanHeaderMenu config
appVersion: string;
updateInfo?: UpdateInfo | null;
isWSL?: boolean;
agentInstructionsEnabled: boolean;
obsidianConfigured: boolean;
bearConfigured: boolean;
Expand Down Expand Up @@ -138,6 +141,8 @@ export const AppHeader = React.memo<AppHeaderProps>(({
onSaveToBear,
onSaveToOctarine,
appVersion,
updateInfo,
isWSL,
agentInstructionsEnabled,
obsidianConfigured,
bearConfigured,
Expand Down Expand Up @@ -321,6 +326,9 @@ export const AppHeader = React.memo<AppHeaderProps>(({

<PlanHeaderMenu
appVersion={appVersion}
updateInfo={updateInfo}
origin={origin}
isWSL={isWSL}
onOpenSettings={onOpenSettings}
onOpenExport={onOpenExport}
onCopyAgentInstructions={onCopyAgentInstructions}
Expand Down
9 changes: 5 additions & 4 deletions packages/review-editor/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ConfirmDialog } from '@plannotator/ui/components/ConfirmDialog';
import { Settings } from '@plannotator/ui/components/Settings';
import { FeedbackButton, ApproveButton, ExitButton } from '@plannotator/ui/components/ToolbarButtons';
import { AgentReviewActions } from './components/AgentReviewActions';
import { UpdateBanner } from '@plannotator/ui/components/UpdateBanner';
import { useUpdateCheck } from '@plannotator/ui/hooks/useUpdateCheck';
import { storage } from '@plannotator/ui/utils/storage';
import { CompletionOverlay } from '@plannotator/ui/components/CompletionOverlay';
import { GitHubIcon } from '@plannotator/ui/components/GitHubIcon';
Expand Down Expand Up @@ -256,6 +256,7 @@ const ReviewApp: React.FC = () => {
const mrNumberLabel = prMetadata ? getMRNumberLabel(prMetadata) : '';
const displayRepo = prMetadata ? getDisplayRepo(prMetadata) : '';
const appVersion = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.0.0';
const updateInfo = useUpdateCheck();

const identity = useConfigValue('displayName');

Expand Down Expand Up @@ -2079,6 +2080,9 @@ const ReviewApp: React.FC = () => {
isFileTreeOpen={isFileTreeOpen}
isSidebarOpen={reviewSidebar.isOpen}
appVersion={appVersion}
updateInfo={updateInfo}
origin={origin}
isWSL={isWSL}
/>

<div className="w-px h-5 bg-border/50 mx-1 hidden md:block" />
Expand Down Expand Up @@ -2467,9 +2471,6 @@ const ReviewApp: React.FC = () => {
agentLabel={getAgentName(origin)}
/>

{/* Update notification */}
<UpdateBanner origin={origin} isWSL={isWSL} />

{/* GitHub general comment dialog */}
<ReviewSubmissionDialog
isOpen={!!platformCommentDialog}
Expand Down
56 changes: 26 additions & 30 deletions packages/review-editor/components/ReviewHeaderMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import {
ActionMenuSectionLabel,
} from '@plannotator/ui/components/ActionMenu';
import { useTheme } from '@plannotator/ui/components/ThemeProvider';
import { MenuVersionSection } from '@plannotator/ui/components/MenuVersionSection';
import { modKey } from '@plannotator/ui/utils/platform';
import type { UpdateInfo } from '@plannotator/ui/hooks/useUpdateCheck';
import type { Origin } from '@plannotator/shared/agents';

interface ReviewHeaderMenuProps {
onOpenSettings: () => void;
Expand All @@ -16,6 +19,9 @@ interface ReviewHeaderMenuProps {
isFileTreeOpen: boolean;
isSidebarOpen: boolean;
appVersion: string;
updateInfo?: UpdateInfo | null;
origin?: Origin | null;
isWSL?: boolean;
}

export const ReviewHeaderMenu: React.FC<ReviewHeaderMenuProps> = ({
Expand All @@ -26,18 +32,26 @@ export const ReviewHeaderMenu: React.FC<ReviewHeaderMenuProps> = ({
isFileTreeOpen,
isSidebarOpen,
appVersion,
updateInfo,
origin,
isWSL = false,
}) => {
const { theme, resolvedMode, setTheme } = useTheme();
const activeTheme = useMemo<'light' | 'dark'>(() => {
return theme === 'system' ? resolvedMode : theme;
}, [resolvedMode, theme]);

const showUpdateDot = !!updateInfo?.updateAvailable && !updateInfo.dismissed;

return (
<ActionMenu
renderTrigger={({ isOpen, toggleMenu }) => (
<button
onClick={toggleMenu}
className={`flex items-center gap-1.5 p-1.5 md:px-2.5 md:py-1 rounded-md text-xs font-medium transition-colors ${
onClick={() => {
if (!isOpen && showUpdateDot) updateInfo?.dismiss();
toggleMenu();
}}
className={`relative flex items-center gap-1.5 p-1.5 md:px-2.5 md:py-1 rounded-md text-xs font-medium transition-colors ${
isOpen
? 'bg-muted text-foreground'
: 'text-muted-foreground hover:text-foreground hover:bg-muted'
Expand All @@ -48,6 +62,9 @@ export const ReviewHeaderMenu: React.FC<ReviewHeaderMenuProps> = ({
>
{isOpen ? <CloseIcon /> : <MenuIcon />}
<span className="hidden md:inline">Options</span>
{showUpdateDot && (
<span className="absolute top-0.5 right-0.5 md:-top-0.5 md:-right-0.5 w-2 h-2 rounded-full bg-primary ring-2 ring-background" />
)}
</button>
)}
>
Expand Down Expand Up @@ -118,34 +135,13 @@ export const ReviewHeaderMenu: React.FC<ReviewHeaderMenuProps> = ({

<ActionMenuDivider />

<div className="px-3 py-2 space-y-2">
<div className="flex items-center justify-between gap-3">
<ActionMenuSectionLabel>Plannotator</ActionMenuSectionLabel>
<span className="text-[10px] font-mono text-muted-foreground/70">
v{appVersion}
</span>
</div>
<div className="flex flex-col items-start gap-1 text-[11px]">
<a
href="https://github.com/backnotprop/plannotator/releases"
target="_blank"
rel="noopener noreferrer"
onClick={closeMenu}
className="text-muted-foreground hover:text-foreground transition-colors"
>
Release notes
</a>
<a
href="https://github.com/backnotprop/plannotator"
target="_blank"
rel="noopener noreferrer"
onClick={closeMenu}
className="text-muted-foreground hover:text-foreground transition-colors"
>
Project repo
</a>
</div>
</div>
<MenuVersionSection
appVersion={appVersion}
updateInfo={updateInfo}
origin={origin}
isWSL={isWSL}
closeMenu={closeMenu}
/>
</>
)}
</ActionMenu>
Expand Down
38 changes: 38 additions & 0 deletions packages/ui/components/BorderTrail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { motion, type Transition } from 'motion/react';

export interface BorderTrailProps {
className?: string;
size?: number;
transition?: Transition;
style?: React.CSSProperties;
}

export function BorderTrail({
className,
size = 60,
transition,
style,
}: BorderTrailProps) {
const defaultTransition: Transition = {
repeat: Infinity,
duration: 5,
ease: 'linear',
};

return (
<div className="pointer-events-none absolute inset-0 rounded-[inherit] border border-transparent [mask-clip:padding-box,border-box] [mask-composite:intersect] [mask-image:linear-gradient(transparent,transparent),linear-gradient(#000,#000)]">
<motion.div
className={['absolute aspect-square bg-zinc-500', className].filter(Boolean).join(' ')}
style={{
width: size,
offsetPath: `rect(0 auto auto 0 round ${size}px)`,
...style,
}}
animate={{
offsetDistance: ['0%', '100%'],
}}
transition={transition ?? defaultTransition}
/>
</div>
);
}
91 changes: 91 additions & 0 deletions packages/ui/components/MenuVersionSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React, { useState } from 'react';
import { TextShimmer } from './TextShimmer';
import type { UpdateInfo } from '../hooks/useUpdateCheck';
import type { Origin } from '@plannotator/shared/agents';
import { isWindows } from '../utils/platform';

const PI_INSTALL_COMMAND = 'pi install npm:@plannotator/pi-extension';

function getInstallCommand(origin?: Origin | null, isWSL = false): string {
if (origin === 'pi') return PI_INSTALL_COMMAND;
return isWindows && !isWSL
? 'powershell -c "irm https://plannotator.ai/install.ps1 | iex"'
: 'curl -fsSL https://plannotator.ai/install.sh | bash';
}

interface MenuVersionSectionProps {
appVersion: string;
updateInfo?: UpdateInfo | null;
origin?: Origin | null;
isWSL: boolean;
closeMenu: () => void;
}

export const MenuVersionSection: React.FC<MenuVersionSectionProps> = ({
appVersion,
updateInfo,
origin,
isWSL,
closeMenu,
}) => {
const [copied, setCopied] = useState(false);
const hasUpdate = !!updateInfo?.updateAvailable;

const handleCopy = async () => {
try {
await navigator.clipboard.writeText(getInstallCommand(origin, isWSL));
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (e) {
console.error('Failed to copy:', e);
}
};

return (
<div className="px-3 py-2 space-y-2">
<div className="flex items-center justify-between gap-3">
<a
href="https://github.com/backnotprop/plannotator"
target="_blank"
rel="noopener noreferrer"
onClick={closeMenu}
className="text-[10px] font-semibold tracking-wide text-muted-foreground hover:text-foreground transition-colors"
>
Plannotator
</a>
<span className="text-[10px] font-mono text-muted-foreground/70">
v{appVersion}
</span>
</div>
<div className="flex flex-col items-start gap-1 text-[11px]">
<span className="flex items-center gap-1.5">
<a
href={hasUpdate ? updateInfo!.releaseUrl : 'https://github.com/backnotprop/plannotator/releases'}
target="_blank"
rel="noopener noreferrer"
onClick={closeMenu}
className="text-muted-foreground hover:text-foreground transition-colors"
>
Release notes
</a>
{hasUpdate && (
<>
<span className="text-muted-foreground/40">·</span>
<TextShimmer className="text-[10px] font-medium" duration={2.5} spread={1.5}>
New update available!
</TextShimmer>
</>
)}
</span>
{hasUpdate && (
<button
onClick={handleCopy}
className="text-emerald-500 hover:text-emerald-400 transition-colors"
>
{copied ? 'Copied!' : 'Copy update command'}
</button>
)}
</div>
</div>
);
};
Loading
Loading