@plannotator/ui 0.52.0 → 0.53.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/HANDOFF.md +317 -29
- package/README.md +35 -3
- package/components/AnnotationPanel.tsx +159 -107
- package/components/AnnotationToolbar.tsx +17 -14
- package/components/AnnotationToolstrip.tsx +9 -0
- package/components/BlockRenderer.tsx +26 -14
- package/components/CodeFilePopout.tsx +131 -68
- package/components/CommentPopover.tsx +6 -349
- package/components/ComposerTokenTextarea.tsx +379 -0
- package/components/ExportModal.tsx +6 -1
- package/components/HostTitle.tsx +108 -0
- package/components/ImageLightbox.tsx +288 -17
- package/components/ImagePeek.tsx +255 -0
- package/components/ImageResolverContext.tsx +31 -0
- package/components/InlineMarkdown.tsx +123 -15
- package/components/LocalLinkContext.tsx +49 -0
- package/components/MenuVersionSection.tsx +51 -25
- package/components/OpenInAppButton.tsx +18 -49
- package/components/PlanHeaderMenu.tsx +2 -8
- package/components/ProviderIcons.tsx +10 -4
- package/components/RenderedMarkdown.tsx +40 -4
- package/components/Settings.tsx +33 -5
- package/components/SkillReferenceMenu.tsx +28 -6
- package/components/Tooltip.tsx +51 -4
- package/components/Viewer.tsx +214 -55
- package/components/ai/AIProviderBar.tsx +11 -0
- package/components/ai/AskSessionSetupHint.tsx +12 -0
- package/components/ai/DocumentAIChatPanel.tsx +17 -5
- package/components/ai/SessionAskNotice.tsx +4 -3
- package/components/blocks/BlockquoteBlock.tsx +159 -0
- package/components/blocks/HtmlBlock.tsx +121 -9
- package/components/blocks/QuestionBlock.tsx +253 -104
- package/components/diagram/DiagramViewer.tsx +41 -4
- package/components/diagram/index.ts +1 -1
- package/components/html-viewer/HtmlViewer.tsx +73 -3
- package/components/html-viewer/bridge-script.asset.js +86 -0
- package/components/html-viewer/bridge-script.ts +86 -0
- package/components/html-viewer/useHtmlAnnotation.ts +51 -3
- package/components/icons/AgentIcons.tsx +9 -7
- package/components/plan-diff/PlanCleanDiffView.tsx +239 -36
- package/components/plan-diff/PlanDiffViewer.tsx +6 -1
- package/configure.ts +27 -1
- package/hooks/useAgentJobs.ts +8 -3
- package/hooks/useAnnotationHighlighter.ts +128 -23
- package/hooks/useExternalAnnotations.ts +6 -0
- package/hooks/useOpenInApps.ts +88 -0
- package/hooks/useUpdateCheck.ts +38 -98
- package/hooks/useUpdateCheckSetting.ts +43 -0
- package/hooks/useUpdateToast.ts +101 -0
- package/package.json +2 -2
- package/shortcuts/code-review/markdownPreview.shortcuts.ts +18 -0
- package/shortcuts/index.ts +1 -0
- package/styles.css +1 -1
- package/types.ts +48 -0
- package/utils/agentClosed.ts +26 -0
- package/utils/agentToolSetting.ts +16 -9
- package/utils/aiProvider.ts +7 -0
- package/utils/askSessionSetupHint.ts +27 -0
- package/utils/blockTargeting.ts +14 -3
- package/utils/parser.ts +65 -51
- package/utils/pasteInsert.ts +234 -0
- package/utils/planDiffEngine.ts +272 -2
- package/utils/skillCatalog.ts +15 -2
- package/utils/skillReferences.ts +50 -13
- package/utils/storage.ts +5 -0
- package/utils/textCut.ts +41 -0
- package/utils/updateNotice.ts +120 -0
package/components/Settings.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UpdateCheckSetting } from '../hooks/useUpdateCheckSetting';
|
|
1
2
|
import React, { useState, useEffect, useMemo, useRef } from 'react';
|
|
2
3
|
import { createPortal } from 'react-dom';
|
|
3
4
|
import type { AnnotateAgentTerminalSide } from '@plannotator/core/agent-terminal';
|
|
@@ -112,6 +113,10 @@ interface SettingsProps {
|
|
|
112
113
|
* `env` is PLANNOTATOR_AUTO_UPDATE when it overrides the config file.
|
|
113
114
|
* Hosts that omit it get no toggle. */
|
|
114
115
|
autoUpdateSetting?: { env?: boolean };
|
|
116
|
+
/** Present when the server reports the update-notice switch (serverConfig.updateCheck,
|
|
117
|
+
* useUpdateCheckSetting); `env` is PLANNOTATOR_UPDATE_CHECK when it overrides the
|
|
118
|
+
* config file. Hosts that omit it get no row. */
|
|
119
|
+
updateCheckSetting?: UpdateCheckSetting;
|
|
115
120
|
/** The `plannotator` agent tool switch, as the server reports it
|
|
116
121
|
* (parseAgentToolSetting of serverConfig: present only when the session's
|
|
117
122
|
* host registers the tool). Shown only together with onAgentToolChange;
|
|
@@ -1002,9 +1007,10 @@ const CommentsTab: React.FC = () => {
|
|
|
1002
1007
|
);
|
|
1003
1008
|
};
|
|
1004
1009
|
|
|
1005
|
-
export const Settings: React.FC<SettingsProps> = ({ taterMode, onTaterModeChange, onIdentityChange, origin, mode = 'plan', annotateParity = false, onUIPreferencesChange, externalOpen, onExternalClose, aiProviders = [], gitUser, autoUpdateSetting, agentToolSetting, onAgentToolChange, sinceBaseUnavailable, isCompactTouchLayout = false, onDetectObsidianVaults, agentTerminalAvailable = false, webmcpAvailable = false }) => {
|
|
1010
|
+
export const Settings: React.FC<SettingsProps> = ({ taterMode, onTaterModeChange, onIdentityChange, origin, mode = 'plan', annotateParity = false, onUIPreferencesChange, externalOpen, onExternalClose, aiProviders = [], gitUser, autoUpdateSetting, updateCheckSetting, agentToolSetting, onAgentToolChange, sinceBaseUnavailable, isCompactTouchLayout = false, onDetectObsidianVaults, agentTerminalAvailable = false, webmcpAvailable = false }) => {
|
|
1006
1011
|
const webmcpTools = useWebMcpToolsEnabled();
|
|
1007
1012
|
const autoUpdate = useConfigValue('autoUpdate');
|
|
1013
|
+
const updateChecksOn = updateCheckSetting ? updateCheckSetting.on : true;
|
|
1008
1014
|
const [showDialog, setShowDialog] = useState(false);
|
|
1009
1015
|
const settingsWasOpenRef = useRef(false);
|
|
1010
1016
|
const [themePreview, setThemePreview] = useState(false);
|
|
@@ -1441,20 +1447,42 @@ export const Settings: React.FC<SettingsProps> = ({ taterMode, onTaterModeChange
|
|
|
1441
1447
|
</div>
|
|
1442
1448
|
</div>
|
|
1443
1449
|
|
|
1450
|
+
{/* Update notices: on by default. Writes updateCheck to
|
|
1451
|
+
~/.plannotator/config.json; off makes no request to GitHub. */}
|
|
1452
|
+
{updateCheckSetting && (
|
|
1453
|
+
<>
|
|
1454
|
+
<div className="border-t border-border" />
|
|
1455
|
+
<ToggleSwitch
|
|
1456
|
+
checked={updateChecksOn}
|
|
1457
|
+
onChange={(v) => updateCheckSetting.onChange(v)}
|
|
1458
|
+
disabled={updateCheckSetting.env !== undefined}
|
|
1459
|
+
label="Tell me when a new version is out"
|
|
1460
|
+
description={
|
|
1461
|
+
updateCheckSetting.env !== undefined
|
|
1462
|
+
? 'Set by PLANNOTATOR_UPDATE_CHECK in your environment.'
|
|
1463
|
+
: 'This computer asks GitHub once a day, never your browser. Off makes no request at all.'
|
|
1464
|
+
}
|
|
1465
|
+
/>
|
|
1466
|
+
</>
|
|
1467
|
+
)}
|
|
1468
|
+
|
|
1444
1469
|
{/* Auto-update (#1634): opt-in, default off. Writes
|
|
1445
|
-
autoUpdate to ~/.plannotator/config.json.
|
|
1470
|
+
autoUpdate to ~/.plannotator/config.json. It needs
|
|
1471
|
+
update checks, so it is locked while they are off. */}
|
|
1446
1472
|
{autoUpdateSetting && (
|
|
1447
1473
|
<>
|
|
1448
1474
|
<div className="border-t border-border" />
|
|
1449
1475
|
<ToggleSwitch
|
|
1450
|
-
checked={autoUpdateSetting.env ?? autoUpdate}
|
|
1476
|
+
checked={(autoUpdateSetting.env ?? autoUpdate) && updateChecksOn}
|
|
1451
1477
|
onChange={(v) => configStore.set('autoUpdate', v)}
|
|
1452
|
-
disabled={autoUpdateSetting.env !== undefined}
|
|
1478
|
+
disabled={autoUpdateSetting.env !== undefined || !updateChecksOn}
|
|
1453
1479
|
label="Keep Plannotator up to date"
|
|
1454
1480
|
description={
|
|
1455
1481
|
autoUpdateSetting.env !== undefined
|
|
1456
1482
|
? 'Set by PLANNOTATOR_AUTO_UPDATE in your environment.'
|
|
1457
|
-
:
|
|
1483
|
+
: !updateChecksOn
|
|
1484
|
+
? 'Needs update checks (above).'
|
|
1485
|
+
: 'Installs new releases in the background, at most once a day, never while another review is open.'
|
|
1458
1486
|
}
|
|
1459
1487
|
/>
|
|
1460
1488
|
</>
|
|
@@ -182,11 +182,12 @@ export const SkillReferenceMenu: React.FC<SkillReferenceMenuProps> = ({
|
|
|
182
182
|
}`}
|
|
183
183
|
>
|
|
184
184
|
<SkillIcon />
|
|
185
|
-
|
|
186
|
-
<span className="
|
|
185
|
+
{/* data-skill-item-part: a host's styling hook (the Inbox lays a row out in two lines on a phone). */}
|
|
186
|
+
<span data-skill-item-part="name" className="shrink-0 font-semibold text-foreground">{item.name}</span>
|
|
187
|
+
<span data-skill-item-part="description" className="min-w-0 flex-1 truncate text-muted-foreground">
|
|
187
188
|
{item.description ?? ''}
|
|
188
189
|
</span>
|
|
189
|
-
<span className="shrink-0 pl-3 text-xs text-muted-foreground/80">
|
|
190
|
+
<span data-skill-item-part="root" className="shrink-0 pl-3 text-xs text-muted-foreground/80">
|
|
190
191
|
{ROOT_LABELS[item.root]}
|
|
191
192
|
</span>
|
|
192
193
|
</button>
|
|
@@ -215,6 +216,26 @@ const SkillIcon: React.FC = () => (
|
|
|
215
216
|
|
|
216
217
|
interface HumanOnlySkillNoticeProps {
|
|
217
218
|
skills: SkillCatalogEntry[];
|
|
219
|
+
/**
|
|
220
|
+
* What the instructions go with. Default: the host's (`setSkillInstructionsDestination`),
|
|
221
|
+
* else "your feedback". The Inbox says "your reply" or "your message".
|
|
222
|
+
*/
|
|
223
|
+
destination?: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Module-level seam (see packages/ui/CLAUDE.md): what every composer's
|
|
227
|
+
// human-only line says the instructions go with, unless the composer says.
|
|
228
|
+
// Plannotator's comments go with "your feedback"; the Inbox's comments on
|
|
229
|
+
// attached files ride a reply.
|
|
230
|
+
const DEFAULT_DESTINATION = 'your feedback';
|
|
231
|
+
let defaultDestination = DEFAULT_DESTINATION;
|
|
232
|
+
|
|
233
|
+
export function setSkillInstructionsDestination(destination: string): void {
|
|
234
|
+
defaultDestination = destination || DEFAULT_DESTINATION;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function resetSkillInstructionsDestination(): void {
|
|
238
|
+
defaultDestination = DEFAULT_DESTINATION;
|
|
218
239
|
}
|
|
219
240
|
|
|
220
241
|
/**
|
|
@@ -227,8 +248,9 @@ interface HumanOnlySkillNoticeProps {
|
|
|
227
248
|
* toggles), and assistive tech alike. It pairs with the dotted-underline
|
|
228
249
|
* marker the composer overlay paints on human-only tokens.
|
|
229
250
|
*/
|
|
230
|
-
export const HumanOnlySkillNotice: React.FC<HumanOnlySkillNoticeProps> = ({ skills }) => {
|
|
251
|
+
export const HumanOnlySkillNotice: React.FC<HumanOnlySkillNoticeProps> = ({ skills, destination }) => {
|
|
231
252
|
if (skills.length === 0) return null;
|
|
253
|
+
destination ??= defaultDestination;
|
|
232
254
|
const names = skills.map((s) => s.name).join(', ');
|
|
233
255
|
return (
|
|
234
256
|
<details data-skill-human-only-notice="true" className="group mt-1 px-1">
|
|
@@ -240,12 +262,12 @@ export const HumanOnlySkillNotice: React.FC<HumanOnlySkillNoticeProps> = ({ skil
|
|
|
240
262
|
{skills.length === 1 ? (
|
|
241
263
|
<>
|
|
242
264
|
<span className="font-mono">{names}</span> cannot be invoked by a model, so
|
|
243
|
-
its instructions will be included with
|
|
265
|
+
its instructions will be included with {destination}.
|
|
244
266
|
</>
|
|
245
267
|
) : (
|
|
246
268
|
<>
|
|
247
269
|
<span className="font-mono">{names}</span> cannot be invoked by a model, so
|
|
248
|
-
their instructions will be included with
|
|
270
|
+
their instructions will be included with {destination}.
|
|
249
271
|
</>
|
|
250
272
|
)}
|
|
251
273
|
</p>
|
package/components/Tooltip.tsx
CHANGED
|
@@ -44,6 +44,25 @@ interface TooltipProps {
|
|
|
44
44
|
* that use it for short button labels.
|
|
45
45
|
*/
|
|
46
46
|
wide?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Asked each time the tooltip would open, with the trigger element; `false`
|
|
49
|
+
* keeps it closed this time (e.g. open only while the trigger's text is cut
|
|
50
|
+
* off). Unset: it always opens, as before.
|
|
51
|
+
*/
|
|
52
|
+
shouldOpen?: (trigger: Element) => boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Where the popup is portaled. Unset: the document body. A trigger inside a
|
|
55
|
+
* native modal `<dialog>` needs the dialog here, or the popup is drawn
|
|
56
|
+
* under the dialog's top layer.
|
|
57
|
+
*/
|
|
58
|
+
container?: HTMLElement | null;
|
|
59
|
+
/**
|
|
60
|
+
* When true, Escape closes the tooltip without using the key up: it still
|
|
61
|
+
* reaches the page (not default-prevented, not stopped), so the page's own
|
|
62
|
+
* Escape (closing a pane) works while a tooltip is open. Unset: Base UI's
|
|
63
|
+
* default, where the Escape that closes a tooltip goes no further.
|
|
64
|
+
*/
|
|
65
|
+
escapePassesThrough?: boolean;
|
|
47
66
|
}
|
|
48
67
|
|
|
49
68
|
export const Tooltip: React.FC<TooltipProps> = ({
|
|
@@ -54,15 +73,43 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
|
|
54
73
|
delayDuration,
|
|
55
74
|
sideOffset = 8,
|
|
56
75
|
wide = false,
|
|
76
|
+
shouldOpen,
|
|
77
|
+
container,
|
|
78
|
+
escapePassesThrough = false,
|
|
57
79
|
}) => {
|
|
58
80
|
const disableHoverablePopup = React.useContext(DisableHoverablePopupContext);
|
|
81
|
+
// Controlled only for escapePassesThrough: the Escape close is canceled (so Base UI neither prevents nor stops
|
|
82
|
+
// the key) and the tooltip is closed here instead.
|
|
83
|
+
const [controlledOpen, setControlledOpen] = React.useState(false);
|
|
84
|
+
const controlled = escapePassesThrough ? { open: controlledOpen } : {};
|
|
59
85
|
return (
|
|
60
|
-
<BaseTooltip.Root
|
|
86
|
+
<BaseTooltip.Root
|
|
87
|
+
disableHoverablePopup={disableHoverablePopup}
|
|
88
|
+
{...controlled}
|
|
89
|
+
onOpenChange={
|
|
90
|
+
shouldOpen || escapePassesThrough
|
|
91
|
+
? (open, details) => {
|
|
92
|
+
if (open && shouldOpen && !(details.trigger && shouldOpen(details.trigger))) {
|
|
93
|
+
details.cancel();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!escapePassesThrough) return;
|
|
97
|
+
if (!open && details.reason === 'escape-key') {
|
|
98
|
+
details.cancel();
|
|
99
|
+
details.allowPropagation();
|
|
100
|
+
}
|
|
101
|
+
setControlledOpen(open);
|
|
102
|
+
}
|
|
103
|
+
: undefined
|
|
104
|
+
}
|
|
105
|
+
>
|
|
61
106
|
<BaseTooltip.Trigger render={children} delay={delayDuration} />
|
|
62
|
-
<BaseTooltip.Portal>
|
|
63
|
-
|
|
107
|
+
<BaseTooltip.Portal container={container ?? undefined}>
|
|
108
|
+
{/* Above the annotation toolbar (z-[100]), so a tooltip that flips toward it is never tucked under it. */}
|
|
109
|
+
<BaseTooltip.Positioner side={side} align={align} sideOffset={sideOffset} className="isolate z-[110]">
|
|
64
110
|
<BaseTooltip.Popup
|
|
65
|
-
|
|
111
|
+
data-pn-tooltip=""
|
|
112
|
+
className={`z-[110] px-2 py-1 text-xs bg-popover text-popover-foreground border border-border rounded shadow-md origin-[var(--transform-origin)] transition-[opacity,scale] duration-150 ease-out data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 ${
|
|
66
113
|
wide
|
|
67
114
|
? 'max-w-[min(260px,calc(100vw-1rem))] leading-snug whitespace-normal [overflow-wrap:anywhere]'
|
|
68
115
|
: 'whitespace-nowrap'
|