@plannotator/ui 0.52.1 → 0.53.1
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 +284 -29
- package/README.md +45 -5
- package/components/AnnotationPanel.tsx +195 -118
- package/components/AnnotationToolbar.tsx +17 -14
- package/components/AnnotationToolstrip.tsx +54 -40
- package/components/BlockRenderer.tsx +20 -14
- package/components/CodeFilePopout.tsx +132 -69
- package/components/CommentPopover.tsx +8 -349
- package/components/ComposerTokenTextarea.tsx +379 -0
- package/components/ExportModal.tsx +6 -1
- package/components/HostTitle.tsx +108 -0
- package/components/HtmlSurfaceControls.tsx +2 -1
- 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/MentionPicker.tsx +51 -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 +67 -18
- package/components/Tooltip.tsx +51 -4
- package/components/Viewer.tsx +220 -49
- 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 +183 -96
- package/components/blocks/QuestionContext.tsx +3 -0
- package/components/diagram/DiagramViewer.tsx +41 -4
- package/components/diagram/index.ts +1 -1
- package/components/html-viewer/HtmlViewer.tsx +95 -3
- package/components/html-viewer/bridge-script.asset.js +413 -7
- package/components/html-viewer/bridge-script.lite.ts +1 -1
- package/components/html-viewer/bridge-script.ts +454 -7
- 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/useMentionAutocomplete.ts +1 -1
- package/hooks/useOpenInApps.ts +88 -0
- package/hooks/useSkillReferenceAutocomplete.ts +7 -3
- package/hooks/useUpdateCheck.ts +38 -98
- package/hooks/useUpdateCheckSetting.ts +43 -0
- package/hooks/useUpdateToast.ts +101 -0
- package/hooks/useVimSelection.ts +9 -0
- package/package.json +2 -2
- package/shortcuts/code-review/markdownPreview.shortcuts.ts +18 -0
- package/shortcuts/index.ts +1 -0
- package/shortcuts/runtime.ts +10 -3
- 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/mentions.ts +34 -5
- 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
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
useCallback,
|
|
4
4
|
useEffect,
|
|
5
5
|
useImperativeHandle,
|
|
6
|
+
useLayoutEffect,
|
|
6
7
|
useMemo,
|
|
7
8
|
useRef,
|
|
8
9
|
useState,
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
isVimSelectionActionId,
|
|
15
16
|
type VimSelectionHudContext,
|
|
16
17
|
} from "../../shortcuts";
|
|
17
|
-
import type { Annotation, EditorMode, ImageAttachment, InputMethod } from "../../types";
|
|
18
|
+
import type { Annotation, EditorMode, HostDraft, ImageAttachment, InputMethod } from "../../types";
|
|
18
19
|
import { AnnotationType } from "../../types";
|
|
19
20
|
import { copyTextPreservingFocus } from "../../utils/clipboard";
|
|
20
21
|
import { elementIdentityForAskAI } from "../../utils/parser";
|
|
@@ -181,6 +182,9 @@ function parseVimBridgeCopy(value: unknown): string | null {
|
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
/** Inputs for the sandboxed raw-HTML viewer and its parent-side annotation UI. */
|
|
185
|
+
/** The fewest milliseconds between two honored `step` messages from the page. */
|
|
186
|
+
export const STEP_MESSAGE_MIN_INTERVAL_MS = 150;
|
|
187
|
+
|
|
184
188
|
export interface HtmlViewerProps {
|
|
185
189
|
rawHtml: string;
|
|
186
190
|
/** Live proxied-app mode: render `src` (no sandbox, no srcdoc) instead of
|
|
@@ -228,6 +232,20 @@ export interface HtmlViewerProps {
|
|
|
228
232
|
/** Mod+Shift+X pressed while focus lived inside the iframe: show/hide the
|
|
229
233
|
* host's floating tools over the page. The host owns that state. */
|
|
230
234
|
onToolsToggle?: () => void;
|
|
235
|
+
/** A plain `]` (1) or `[` (-1) pressed inside the page while the surface is
|
|
236
|
+
* armed (Annotate) and the page's focus is not in a text field: the host's
|
|
237
|
+
* next / previous file (the Inbox steps through a thread's attachments).
|
|
238
|
+
* The bridge forwards nothing in Interact, and the page still gets the
|
|
239
|
+
* key. Honored only while the frame holds focus, and at most once per
|
|
240
|
+
* STEP_MESSAGE_MIN_INTERVAL_MS. Absent: the message is ignored, as before. */
|
|
241
|
+
onStep?: (dir: 1 | -1) => void;
|
|
242
|
+
/** Escape pressed inside the page in Interact that the page did not use
|
|
243
|
+
* itself (no handler of the page cancelled it or stopped it, and focus was
|
|
244
|
+
* not in one of its text fields): the host may close something of its own
|
|
245
|
+
* (the Inbox leaves full screen). Never sent in Annotate, whose Escape is
|
|
246
|
+
* `onAnnotateModeExit`. Honored only while the frame holds focus, and at
|
|
247
|
+
* most once per STEP_MESSAGE_MIN_INTERVAL_MS. Absent: ignored, as before. */
|
|
248
|
+
onPageEscape?: () => void;
|
|
231
249
|
/** Opt-in Vim-style keyboard selection. Default false for compatibility. */
|
|
232
250
|
vimModeEnabled?: boolean;
|
|
233
251
|
/** Replace the iframe-local compact badge with the shared live key HUD. */
|
|
@@ -325,6 +343,27 @@ export interface HtmlViewerProps {
|
|
|
325
343
|
* warning). Meaningless on the inline path, which never shows a strip.
|
|
326
344
|
*/
|
|
327
345
|
bridgeErrorDisplay?: "banner" | "none";
|
|
346
|
+
/**
|
|
347
|
+
* Opt-in host capability: the composer handed to the host (see
|
|
348
|
+
* `HostDraft`). A text selection reports `intent: 'selection'` and a pin
|
|
349
|
+
* `intent: 'compose'` with the element's label, instead of opening the
|
|
350
|
+
* toolbar or the composer; the selection clearing reports null. With it the
|
|
351
|
+
* handle's `stepPin` moves a pinned draft to the element around it or back
|
|
352
|
+
* in. The host saves the comment and draws it through `annotations` /
|
|
353
|
+
* `applySharedAnnotations`. Ignored when `readOnly`. Absent → unchanged.
|
|
354
|
+
*/
|
|
355
|
+
onHostDraft?: (draft: HostDraft | null) => void;
|
|
356
|
+
/**
|
|
357
|
+
* Opt-in host capability for a native shell whose web view decides every
|
|
358
|
+
* navigation itself (the Inbox surface in the iPhone app). A link the
|
|
359
|
+
* person taps in Interact opens as a new window (`window.open`, the sandbox
|
|
360
|
+
* gains `allow-popups`) instead of being reported through the frame
|
|
361
|
+
* protocol (`onOpenLink` does not fire for it); the host's web view hands
|
|
362
|
+
* that window to its own delegate, which decides it. A scripted click opens
|
|
363
|
+
* nothing. In-page `#` links still scroll here, and an armed pinpoint tap
|
|
364
|
+
* still pins. Absent → unchanged.
|
|
365
|
+
*/
|
|
366
|
+
hostNavigates?: boolean;
|
|
328
367
|
}
|
|
329
368
|
|
|
330
369
|
/**
|
|
@@ -351,6 +390,8 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
351
390
|
onAnnotateModeExit,
|
|
352
391
|
onAnnotateModeToggle,
|
|
353
392
|
onToolsToggle,
|
|
393
|
+
onStep,
|
|
394
|
+
onPageEscape,
|
|
354
395
|
vimModeEnabled = false,
|
|
355
396
|
vimHudEnabled = false,
|
|
356
397
|
vimHudKeyPanelEnabled = true,
|
|
@@ -377,6 +418,8 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
377
418
|
bridgeReadyTimeoutMs = DEFAULT_BRIDGE_READY_TIMEOUT_MS,
|
|
378
419
|
onBridgeUnavailable,
|
|
379
420
|
bridgeErrorDisplay = "banner",
|
|
421
|
+
onHostDraft,
|
|
422
|
+
hostNavigates = false,
|
|
380
423
|
},
|
|
381
424
|
ref,
|
|
382
425
|
) => {
|
|
@@ -411,6 +454,15 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
411
454
|
onAnnotateModeToggleRef.current = onAnnotateModeToggle;
|
|
412
455
|
const onToolsToggleRef = useRef(onToolsToggle);
|
|
413
456
|
onToolsToggleRef.current = onToolsToggle;
|
|
457
|
+
const onStepRef = useRef(onStep);
|
|
458
|
+
onStepRef.current = onStep;
|
|
459
|
+
// The step message is honored only while the host has the surface armed.
|
|
460
|
+
const annotateModeActiveRef = useRef(annotateModeActive);
|
|
461
|
+
annotateModeActiveRef.current = annotateModeActive;
|
|
462
|
+
const lastStepAtRef = useRef(0);
|
|
463
|
+
const onPageEscapeRef = useRef(onPageEscape);
|
|
464
|
+
onPageEscapeRef.current = onPageEscape;
|
|
465
|
+
const lastPageEscapeAtRef = useRef(0);
|
|
414
466
|
|
|
415
467
|
/** Single choke point for direct-to-bridge posts: live sessions get the
|
|
416
468
|
* token + concrete targetOrigin, srcdoc keeps "*" and no token. */
|
|
@@ -613,6 +665,7 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
613
665
|
onUnanchoredChange: handleBridgeUnanchored,
|
|
614
666
|
maxAdditionalTargets,
|
|
615
667
|
scrollBehavior,
|
|
668
|
+
onHostDraft: readOnly ? undefined : onHostDraft,
|
|
616
669
|
});
|
|
617
670
|
createdIdsRef.current = hook.createdAnnotationIds;
|
|
618
671
|
|
|
@@ -682,7 +735,12 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
682
735
|
[hook.draftTargets],
|
|
683
736
|
);
|
|
684
737
|
|
|
685
|
-
|
|
738
|
+
// Registered in the commit that inserts the iframe (a layout effect), so
|
|
739
|
+
// the bridge's `ready` can never arrive before anyone listens: a passive
|
|
740
|
+
// effect can run after a fast srcdoc frame has already posted it, and a
|
|
741
|
+
// lost ready leaves the bridge unconfigured for good (seen on a Linux
|
|
742
|
+
// WebKit runner, the Inbox surface's proof).
|
|
743
|
+
useLayoutEffect(() => {
|
|
686
744
|
function handler(e: MessageEvent<unknown>) {
|
|
687
745
|
if (e.source !== iframeRef.current?.contentWindow) return;
|
|
688
746
|
// Live sessions verify origin + token before reading anything.
|
|
@@ -748,6 +806,31 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
748
806
|
onToolsToggleRef.current?.();
|
|
749
807
|
return;
|
|
750
808
|
}
|
|
809
|
+
if (isRecord(e.data) && e.data.type === `${PREFIX}step`) {
|
|
810
|
+
// The page itself can post this message, so it is honored only for
|
|
811
|
+
// a key the person pressed: the frame holds focus, the surface is
|
|
812
|
+
// armed, and at most one step per STEP_MESSAGE_MIN_INTERVAL_MS (a
|
|
813
|
+
// page cannot push itself out of view by posting in a loop).
|
|
814
|
+
const dir = e.data.dir;
|
|
815
|
+
if (!annotateModeActiveRef.current || (dir !== 1 && dir !== -1)) return;
|
|
816
|
+
if (document.activeElement !== iframeRef.current) return;
|
|
817
|
+
const now = Date.now();
|
|
818
|
+
if (now - lastStepAtRef.current < STEP_MESSAGE_MIN_INTERVAL_MS) return;
|
|
819
|
+
lastStepAtRef.current = now;
|
|
820
|
+
onStepRef.current?.(dir);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (isRecord(e.data) && e.data.type === `${PREFIX}page-escape`) {
|
|
824
|
+
// Like a step, the page could post this itself: honored only for a
|
|
825
|
+
// key pressed in the frame, in Interact, at most once per interval.
|
|
826
|
+
if (annotateModeActiveRef.current) return;
|
|
827
|
+
if (document.activeElement !== iframeRef.current) return;
|
|
828
|
+
const now = Date.now();
|
|
829
|
+
if (now - lastPageEscapeAtRef.current < STEP_MESSAGE_MIN_INTERVAL_MS) return;
|
|
830
|
+
lastPageEscapeAtRef.current = now;
|
|
831
|
+
onPageEscapeRef.current?.();
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
751
834
|
const vimCopy = parseVimBridgeCopy(e.data);
|
|
752
835
|
if (vimCopy !== null) {
|
|
753
836
|
const iframe = iframeRef.current;
|
|
@@ -916,6 +999,12 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
916
999
|
);
|
|
917
1000
|
}, [iframeReadyVersion, annotateModeActive]);
|
|
918
1001
|
|
|
1002
|
+
// A native host decides link navigations itself (`hostNavigates`).
|
|
1003
|
+
useEffect(() => {
|
|
1004
|
+
if (iframeReadyVersion === 0 || !hostNavigates) return;
|
|
1005
|
+
postToBridge({ type: `${PREFIX}set-host-navigates`, active: true });
|
|
1006
|
+
}, [iframeReadyVersion, hostNavigates]);
|
|
1007
|
+
|
|
919
1008
|
// Parent-side Esc rung: with focus outside the iframe the bridge never
|
|
920
1009
|
// sees the keydown. Any open composer/toolbar/picker still closes first —
|
|
921
1010
|
// their state is read from this render's closure, so an Esc that closed
|
|
@@ -1012,6 +1101,9 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
1012
1101
|
// Shared/draft restores respect the live page filter too.
|
|
1013
1102
|
applySharedAnnotations: (anns: Annotation[]) =>
|
|
1014
1103
|
hook.applyAnnotations(forCurrentPage(anns)),
|
|
1104
|
+
stepPin: (direction: "parent" | "child") =>
|
|
1105
|
+
postToBridge({ type: `${PREFIX}step-pin`, direction }),
|
|
1106
|
+
revealPin: () => postToBridge({ type: `${PREFIX}reveal-pin` }),
|
|
1015
1107
|
}));
|
|
1016
1108
|
|
|
1017
1109
|
const handleGlobalCommentSubmit = useCallback(
|
|
@@ -1166,7 +1258,7 @@ export const HtmlViewer = forwardRef<ViewerHandle, HtmlViewerProps>(
|
|
|
1166
1258
|
same-origin XHR) and no srcdoc. Srcdoc mode is unchanged. */}
|
|
1167
1259
|
<iframe
|
|
1168
1260
|
ref={iframeRef}
|
|
1169
|
-
{...(src ? { src } : { srcDoc: srcdoc, sandbox: "allow-scripts" })}
|
|
1261
|
+
{...(src ? { src } : { srcDoc: srcdoc, sandbox: "allow-scripts" + (hostNavigates ? " allow-popups" : "") })}
|
|
1170
1262
|
style={{
|
|
1171
1263
|
width: "100%",
|
|
1172
1264
|
height: fullViewport ? "100%" : `${iframeHeight}px`,
|