@plannotator/ui 0.30.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/components/ActionMenu.tsx +6 -1
- package/components/AgentsTab.tsx +8 -9
- package/components/AnalysisLayerToggle.tsx +48 -0
- package/components/AnnotationPanel.tsx +62 -32
- package/components/AnnotationToolbar.tsx +28 -8
- package/components/AnnotationToolstrip.tsx +9 -0
- package/components/CommentPopover.tsx +212 -46
- package/components/ConfirmDialog.tsx +42 -28
- package/components/InlineMarkdown.tsx +3 -2
- package/components/KeyboardShortcuts.tsx +9 -0
- package/components/Landing.tsx +1 -1
- package/components/LookAndFeelAnnouncementDialog.tsx +147 -178
- package/components/MarkdownEditor/embedPicker.ts +349 -0
- package/components/MarkdownEditor.tsx +12 -0
- package/components/ModeToggle.tsx +2 -1
- package/components/PermissionModeSetup.tsx +24 -5
- package/components/PinpointOverlay.tsx +11 -6
- package/components/PlanHeaderMenu.tsx +140 -1
- package/components/SearchableSelect.tsx +2 -0
- package/components/Settings.tsx +136 -10
- package/components/SkillReferenceMenu.tsx +9 -0
- package/components/StickyHeaderLane.tsx +9 -2
- package/components/TableOfContents.tsx +9 -4
- package/components/TextShimmer.tsx +8 -5
- package/components/ThemeProvider.tsx +43 -1
- package/components/ThemeTab.tsx +52 -1
- package/components/Tooltip.tsx +3 -1
- package/components/Viewer.tsx +19 -5
- package/components/VimTargetReticle.tsx +12 -4
- package/components/ai/DocumentAIChatPanel.tsx +1 -0
- package/components/core/button.tsx +14 -6
- package/components/html-viewer/HtmlViewer.tsx +208 -39
- package/components/html-viewer/bridge-script.ts +319 -65
- package/components/html-viewer/composerYield.ts +1 -51
- package/components/html-viewer/useHtmlAnnotation.ts +146 -57
- package/components/plan-diff/PlanCleanDiffView.tsx +1 -0
- package/components/sidebar/FileBrowser.tsx +17 -5
- package/components/sidebar/SidebarContainer.tsx +124 -28
- package/components/ui/button.tsx +10 -8
- package/components/ui/dialog.tsx +35 -25
- package/config/index.ts +6 -1
- package/config/reviewView.ts +42 -9
- package/config/settings.ts +141 -0
- package/hooks/useAIProviderConfig.ts +8 -7
- package/hooks/useActiveSection.ts +6 -4
- package/hooks/useAgentJobs.ts +3 -0
- package/hooks/useAnnotationHighlighter.ts +20 -0
- package/hooks/useIsMobile.ts +37 -0
- package/hooks/useLinkedDoc.ts +7 -0
- package/hooks/useScrollViewport.ts +74 -0
- package/hooks/useViewportEnvironment.ts +350 -0
- package/package.json +3 -2
- package/shortcuts/index.ts +3 -0
- package/shortcuts/plan-review/annotationMode.shortcuts.ts +91 -0
- package/shortcuts/plan-review/documentView.shortcuts.ts +26 -0
- package/shortcuts/plan-review/htmlAnnotate.shortcuts.ts +24 -0
- package/styles.css +1 -1
- package/theme.css +229 -0
- package/types.ts +34 -0
- package/utils/annotateAgentTerminal.ts +36 -5
- package/utils/blockTargeting.ts +6 -3
- package/utils/composerYield.ts +45 -0
- package/utils/htmlChrome.ts +20 -16
- package/utils/lookAndFeelAnnouncement.ts +12 -8
- package/utils/markdownExtensions.ts +57 -0
- package/utils/parser.ts +37 -2
- package/utils/vimNavigation.ts +4 -1
- package/utils/vimScroll.ts +9 -4
- package/utils/wideMode.ts +20 -0
- package/components/PlanAIAnnouncementDialog.tsx +0 -187
- package/components/VimModeAnnouncementDialog.tsx +0 -557
- package/utils/planAIAnnouncement.ts +0 -17
- package/utils/vimModeAnnouncement.ts +0 -23
package/utils/vimScroll.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getScrollViewportRect,
|
|
3
|
+
offsetScrollViewport,
|
|
4
|
+
} from '../hooks/useScrollViewport';
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* Keep the Vim cursor clear of the HUD bands that hug the viewport edges.
|
|
3
8
|
*
|
|
@@ -131,11 +136,11 @@ export function scrollVimTargetIntoView(
|
|
|
131
136
|
return;
|
|
132
137
|
}
|
|
133
138
|
|
|
134
|
-
const viewportRect = viewport
|
|
139
|
+
const viewportRect = getScrollViewportRect(viewport);
|
|
135
140
|
const targetRect = element.getBoundingClientRect();
|
|
136
141
|
if (targetRect.height === 0 && targetRect.width === 0) return;
|
|
137
142
|
|
|
138
|
-
const margin = resolveVimScrollMargin(
|
|
143
|
+
const margin = resolveVimScrollMargin(viewportRect.height);
|
|
139
144
|
const stickyBottom = viewport
|
|
140
145
|
.querySelector<HTMLElement>('[data-sticky-actions]')
|
|
141
146
|
?.getBoundingClientRect().bottom;
|
|
@@ -153,10 +158,10 @@ export function scrollVimTargetIntoView(
|
|
|
153
158
|
: margin;
|
|
154
159
|
|
|
155
160
|
const delta = computeVimScrollDelta(
|
|
156
|
-
{ top: viewportRect.top, height:
|
|
161
|
+
{ top: viewportRect.top, height: viewportRect.height },
|
|
157
162
|
{ top: targetRect.top, bottom: targetRect.bottom },
|
|
158
163
|
{ topMargin, bottomMargin },
|
|
159
164
|
);
|
|
160
165
|
if (delta === 0) return;
|
|
161
|
-
viewport
|
|
166
|
+
offsetScrollViewport(viewport, delta);
|
|
162
167
|
}
|
package/utils/wideMode.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SidebarTab } from '@plannotator/ui/hooks/useSidebar';
|
|
2
|
+
import type { WideModeType } from '@plannotator/ui/types';
|
|
2
3
|
export type { WideModeType } from '@plannotator/ui/types';
|
|
3
4
|
|
|
4
5
|
export type WideModeLayoutSnapshot = {
|
|
@@ -26,6 +27,25 @@ export function canUseAnnotateWideMode(options: {
|
|
|
26
27
|
return !options.archiveMode && !options.isPlanDiffActive;
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
/** What the focus-mode keyboard shortcut should do on this press. */
|
|
31
|
+
export type FocusShortcutAction = 'enter-focus' | 'exit' | 'none';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Decide the next step for the focus-mode keyboard shortcut.
|
|
35
|
+
*
|
|
36
|
+
* The shortcut is a single "hide the chrome / give it back" toggle, so ANY
|
|
37
|
+
* active panel-hiding view mode restores the remembered layout — including
|
|
38
|
+
* `wide`, which the toolbar control would instead swap for `focus`. A press
|
|
39
|
+
* that could only re-hide already-hidden panels would look like a dead key.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveFocusShortcutAction(state: {
|
|
42
|
+
canUseWideMode: boolean;
|
|
43
|
+
wideModeType: WideModeType | null;
|
|
44
|
+
}): FocusShortcutAction {
|
|
45
|
+
if (state.wideModeType !== null) return 'exit';
|
|
46
|
+
return state.canUseWideMode ? 'enter-focus' : 'none';
|
|
47
|
+
}
|
|
48
|
+
|
|
29
49
|
export function resolveWideModeExitLayout(
|
|
30
50
|
snapshot: WideModeLayoutSnapshot | null,
|
|
31
51
|
options?: WideModeExitOptions,
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { createPortal } from 'react-dom';
|
|
3
|
-
import type { Origin } from '@plannotator/core/agents';
|
|
4
|
-
import { AGENT_CONFIG, getAgentAIProviderTypes, getAgentName } from '@plannotator/core/agents';
|
|
5
|
-
import { SparklesIcon } from './SparklesIcon';
|
|
6
|
-
import { getProviderMeta } from './ProviderIcons';
|
|
7
|
-
|
|
8
|
-
interface PlanAIAnnouncementProvider {
|
|
9
|
-
id: string;
|
|
10
|
-
name: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface PlanAIAnnouncementDialogProps {
|
|
14
|
-
isOpen: boolean;
|
|
15
|
-
origin?: Origin | null;
|
|
16
|
-
providerName?: string | null;
|
|
17
|
-
/** Actually-detected providers (installed + authenticated). Cards matching one of these are selectable. */
|
|
18
|
-
providers?: PlanAIAnnouncementProvider[];
|
|
19
|
-
onSelectProvider?: (providerId: string) => void;
|
|
20
|
-
onOpenAI: () => void;
|
|
21
|
-
onDismiss: () => void;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const SUPPORTED_AI_PROVIDER_TYPES = Array.from(
|
|
25
|
-
new Set(
|
|
26
|
-
(Object.keys(AGENT_CONFIG) as Origin[])
|
|
27
|
-
.flatMap(agentOrigin => getAgentAIProviderTypes(agentOrigin))
|
|
28
|
-
)
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
const UNSUPPORTED_AI_ORIGINS = (Object.keys(AGENT_CONFIG) as Origin[])
|
|
32
|
-
.filter(agentOrigin => getAgentAIProviderTypes(agentOrigin).length === 0);
|
|
33
|
-
|
|
34
|
-
export const PlanAIAnnouncementDialog: React.FC<PlanAIAnnouncementDialogProps> = ({
|
|
35
|
-
isOpen,
|
|
36
|
-
origin,
|
|
37
|
-
providerName,
|
|
38
|
-
providers = [],
|
|
39
|
-
onSelectProvider,
|
|
40
|
-
onOpenAI,
|
|
41
|
-
onDismiss,
|
|
42
|
-
}) => {
|
|
43
|
-
if (!isOpen) return null;
|
|
44
|
-
|
|
45
|
-
const agentLabel = getAgentName(origin ?? undefined);
|
|
46
|
-
const providerLabel = providerName ? getProviderMeta(providerName).label : null;
|
|
47
|
-
const unsupportedLabels = UNSUPPORTED_AI_ORIGINS.map(getAgentName).join(', ');
|
|
48
|
-
|
|
49
|
-
return createPortal(
|
|
50
|
-
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-background/90 backdrop-blur-sm p-4">
|
|
51
|
-
<div className="bg-card border border-border rounded-xl w-full max-w-xl shadow-2xl">
|
|
52
|
-
{/* Header */}
|
|
53
|
-
<div className="p-5 border-b border-border">
|
|
54
|
-
<div className="flex items-center gap-2 mb-2">
|
|
55
|
-
<div className="p-1.5 rounded-lg bg-primary/15">
|
|
56
|
-
<SparklesIcon className="w-5 h-5 text-primary" />
|
|
57
|
-
</div>
|
|
58
|
-
<h3 className="font-semibold text-base">New: Ask AI for annotated documents</h3>
|
|
59
|
-
</div>
|
|
60
|
-
<p className="text-sm text-muted-foreground">
|
|
61
|
-
Chat with plans and annotated documents directly inside Plannotator.
|
|
62
|
-
</p>
|
|
63
|
-
{providerLabel && (
|
|
64
|
-
<p className="text-xs text-muted-foreground/70 mt-1">
|
|
65
|
-
Plannotator selected {providerLabel} for {agentLabel}.
|
|
66
|
-
</p>
|
|
67
|
-
)}
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
{/* Details */}
|
|
71
|
-
<div className="p-4 space-y-4">
|
|
72
|
-
{SUPPORTED_AI_PROVIDER_TYPES.length > 0 && (
|
|
73
|
-
<div>
|
|
74
|
-
<div className="text-xs text-muted-foreground mb-2">Supported providers</div>
|
|
75
|
-
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
|
|
76
|
-
{SUPPORTED_AI_PROVIDER_TYPES.map(providerType => {
|
|
77
|
-
const meta = getProviderMeta(providerType);
|
|
78
|
-
const Icon = meta.icon;
|
|
79
|
-
const isSelected = providerType === providerName;
|
|
80
|
-
// A card is selectable only if the provider is actually detected on this machine.
|
|
81
|
-
const detected = providers.find(p => p.name === providerType) ?? null;
|
|
82
|
-
const isSelectable = Boolean(detected && onSelectProvider);
|
|
83
|
-
|
|
84
|
-
const inner = (
|
|
85
|
-
<>
|
|
86
|
-
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md bg-background/70 text-muted-foreground">
|
|
87
|
-
<Icon className="h-[18px] w-[18px]" />
|
|
88
|
-
</div>
|
|
89
|
-
<div className="min-w-0">
|
|
90
|
-
<div className="truncate text-sm font-medium">{meta.label}</div>
|
|
91
|
-
{isSelected ? (
|
|
92
|
-
<div className="text-[10px] uppercase tracking-wide text-primary">selected</div>
|
|
93
|
-
) : !detected ? (
|
|
94
|
-
<div className="text-[10px] text-muted-foreground/60">not installed</div>
|
|
95
|
-
) : null}
|
|
96
|
-
</div>
|
|
97
|
-
</>
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
if (!isSelectable) {
|
|
101
|
-
return (
|
|
102
|
-
<div
|
|
103
|
-
key={providerType}
|
|
104
|
-
className={`flex min-h-16 items-center gap-2 rounded-lg border p-2.5 ${
|
|
105
|
-
isSelected
|
|
106
|
-
? 'border-primary bg-primary/5'
|
|
107
|
-
: 'border-border bg-muted/35 opacity-60'
|
|
108
|
-
}`}
|
|
109
|
-
>
|
|
110
|
-
{inner}
|
|
111
|
-
</div>
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return (
|
|
116
|
-
<button
|
|
117
|
-
key={providerType}
|
|
118
|
-
type="button"
|
|
119
|
-
onClick={() => onSelectProvider?.(detected!.id)}
|
|
120
|
-
aria-pressed={isSelected}
|
|
121
|
-
className={`flex min-h-16 items-center gap-2 rounded-lg border p-2.5 text-left transition-colors ${
|
|
122
|
-
isSelected
|
|
123
|
-
? 'border-primary bg-primary/5'
|
|
124
|
-
: 'border-border bg-muted/35 hover:border-muted-foreground/30 hover:bg-muted/60'
|
|
125
|
-
}`}
|
|
126
|
-
>
|
|
127
|
-
{inner}
|
|
128
|
-
</button>
|
|
129
|
-
);
|
|
130
|
-
})}
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
)}
|
|
134
|
-
|
|
135
|
-
<div className="grid gap-2 sm:grid-cols-2">
|
|
136
|
-
<div className="flex items-start gap-3 p-3 rounded-lg border border-transparent bg-muted/50">
|
|
137
|
-
<SparklesIcon className="w-4 h-4 mt-0.5 text-primary flex-shrink-0" />
|
|
138
|
-
<div className="flex-1">
|
|
139
|
-
<div className="text-sm font-medium">Ask from comments</div>
|
|
140
|
-
<div className="text-xs text-muted-foreground">
|
|
141
|
-
Use Ask AI from a comment popover to chat about selected text.
|
|
142
|
-
</div>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
<div className="flex items-start gap-3 p-3 rounded-lg border border-transparent bg-muted/50">
|
|
146
|
-
<SparklesIcon className="w-4 h-4 mt-0.5 text-primary flex-shrink-0" />
|
|
147
|
-
<div className="flex-1">
|
|
148
|
-
<div className="text-sm font-medium">Open the side chat</div>
|
|
149
|
-
<div className="text-xs text-muted-foreground">
|
|
150
|
-
Use the AI button in the header for broader document questions.
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
{unsupportedLabels && (
|
|
157
|
-
<p className="text-[11px] leading-relaxed text-muted-foreground/70">
|
|
158
|
-
Not supported for Ask AI yet: {unsupportedLabels}.
|
|
159
|
-
</p>
|
|
160
|
-
)}
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
{/* Footer */}
|
|
164
|
-
<div className="p-4 border-t border-border flex justify-between items-center gap-3">
|
|
165
|
-
<p className="text-xs text-muted-foreground">
|
|
166
|
-
This notice only appears once.
|
|
167
|
-
</p>
|
|
168
|
-
<div className="flex items-center gap-2">
|
|
169
|
-
<button
|
|
170
|
-
onClick={onDismiss}
|
|
171
|
-
className="px-4 py-2 border border-border rounded-lg text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
|
172
|
-
>
|
|
173
|
-
Dismiss
|
|
174
|
-
</button>
|
|
175
|
-
<button
|
|
176
|
-
onClick={onOpenAI}
|
|
177
|
-
className="px-4 py-2 bg-primary text-primary-foreground rounded-lg text-sm font-medium hover:opacity-90 transition-opacity"
|
|
178
|
-
>
|
|
179
|
-
Open AI Chat
|
|
180
|
-
</button>
|
|
181
|
-
</div>
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
</div>,
|
|
185
|
-
document.body
|
|
186
|
-
);
|
|
187
|
-
};
|