@plannotator/ui 0.46.1 → 0.49.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/types.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import type { DiagramAnchor } from '@plannotator/core/diagram-anchor';
2
2
  import type { DiagramRenderKind } from '@plannotator/core/annotatable';
3
+ import type { QuestionAnswer } from '@plannotator/core/question-block';
3
4
 
4
5
  export type { DiagramAnchor } from '@plannotator/core/diagram-anchor';
6
+ export type { QuestionAnswer } from '@plannotator/core/question-block';
5
7
  export type { DiagramRenderKind } from '@plannotator/core/annotatable';
6
8
 
7
9
  /**
@@ -100,6 +102,7 @@ export interface Annotation {
100
102
  elementContext?: HtmlElementContext; // raw-HTML / live-app pinpoint: bounded agent-facing description of the primary element (never used by restore)
101
103
  htmlAdditionalTargets?: HtmlAnnotationTarget[]; // raw-HTML shift-click multi-select: extra elements this one comment covers (primary stays htmlAnchor/originalText)
102
104
  diagramAnchor?: DiagramAnchor; // a comment on a rendered diagram part (Mermaid / Graphviz fence): the part's own id, label and document source line; the highlighter skips it and the diagram overlay restores it (see @plannotator/core/diagram-anchor)
105
+ questionAnswer?: QuestionAnswer; // the reviewer's answer to a `:::question` block (id `ann-question-<key>`): the Viewer draws the answer from it, the highlighter skips it, and the export prints it in the "Answers to your questions" section instead of as a numbered comment (see @plannotator/core/question-block)
103
106
  // web-highlighter metadata for cross-element selections
104
107
  startMeta?: AnnotationTextMeta;
105
108
  endMeta?: AnnotationTextMeta;
@@ -21,6 +21,10 @@ export interface AIProviderOption {
21
21
  id: string;
22
22
  name: string;
23
23
  models?: AIProviderModel[];
24
+ /** Where `models` came from, when the server says (`/api/ai/capabilities`). */
25
+ modelsSource?: 'fallback' | 'discovered';
26
+ /** The installed CLI's version, when the server reports it. */
27
+ toolVersion?: string;
24
28
  }
25
29
 
26
30
  export interface AIProviderSettings {
@@ -0,0 +1,59 @@
1
+ /**
2
+ * One-time notice after a background auto-update (#1634).
3
+ *
4
+ * The compiled CLI's servers attach `autoUpdateNotice` to /api/plan and
5
+ * /api/diff when the data dir records an install attempt: `updated` once the
6
+ * running binary is the new version, `failed` when the install script exited
7
+ * non-zero. The notice id is stable per attempt; the "seen" marker is a cookie,
8
+ * which is shared across the random ports sessions run on, so the toast shows
9
+ * once whichever app (plan, annotate, review) loads first.
10
+ */
11
+
12
+ import { getItem, setItem } from './storage';
13
+
14
+ export interface AutoUpdateNotice {
15
+ id: string;
16
+ kind: 'updated' | 'failed';
17
+ version: string;
18
+ releaseUrl: string;
19
+ logPath?: string;
20
+ }
21
+
22
+ const SEEN_KEY = 'plannotator-auto-update-notice-seen';
23
+
24
+ export function parseAutoUpdateNotice(value: unknown): AutoUpdateNotice | undefined {
25
+ if (!value || typeof value !== 'object') return undefined;
26
+ const v = value as Record<string, unknown>;
27
+ if (typeof v.id !== 'string' || !v.id) return undefined;
28
+ if (v.kind !== 'updated' && v.kind !== 'failed') return undefined;
29
+ if (typeof v.version !== 'string' || typeof v.releaseUrl !== 'string') return undefined;
30
+ if (!/^https:\/\/github\.com\//.test(v.releaseUrl)) return undefined;
31
+ return {
32
+ id: v.id,
33
+ kind: v.kind,
34
+ version: v.version,
35
+ releaseUrl: v.releaseUrl,
36
+ ...(typeof v.logPath === 'string' && { logPath: v.logPath }),
37
+ };
38
+ }
39
+
40
+ /**
41
+ * True the first time a given notice is claimed in this browser, false after.
42
+ * Claiming marks it seen, so a caller shows the toast only when this is true.
43
+ */
44
+ export function claimAutoUpdateNotice(notice: AutoUpdateNotice): boolean {
45
+ if (getItem(SEEN_KEY) === notice.id) return false;
46
+ setItem(SEEN_KEY, notice.id);
47
+ return true;
48
+ }
49
+
50
+ /** Title and description for the toast. */
51
+ export function describeAutoUpdateNotice(notice: AutoUpdateNotice): { title: string; description: string } {
52
+ if (notice.kind === 'updated') {
53
+ return { title: `Updated to v${notice.version}`, description: 'Plannotator updated itself in the background.' };
54
+ }
55
+ return {
56
+ title: 'Auto-update failed',
57
+ description: notice.logPath ? `See ${notice.logPath}` : `Could not install v${notice.version}.`,
58
+ };
59
+ }
@@ -126,6 +126,15 @@ export interface DecisionPlatformInput {
126
126
  mrLabel: string;
127
127
  /** The viewer authored this PR/MR: approve paths mute, never disappear. */
128
128
  selfAuthored: boolean;
129
+ /**
130
+ * #1611: whether the platform has a real request-changes review (GitHub
131
+ * does, GitLab does not). `false` makes the request-changes rows say they
132
+ * post as a comment. Absent keeps the original copy, so a host that never
133
+ * sets it renders exactly what it rendered before. When supported, a
134
+ * self-authored PR mutes Request changes too (GitHub refuses it on your own
135
+ * PR) and adds a live "Comment…" row so the empty state is never a dead end.
136
+ */
137
+ requestChangesSupported?: boolean;
129
138
  }
130
139
 
131
140
  export const DECISION_NOTE_PLACEHOLDER = 'Add a note...';
@@ -345,6 +354,11 @@ function buildPlatformSpec(input: DecisionSpecInput, platform: DecisionPlatformI
345
354
  // Frozen copy (maintainer-approved): the self-approval mute reason.
346
355
  const selfReason = `You can't approve your own ${noun} on ${platform.label}.`;
347
356
  const selfReasonShort = `You can't approve your own ${platform.mrLabel}`;
357
+ const requestChangesUnsupported = platform.requestChangesSupported === false;
358
+ // GitHub refuses REQUEST_CHANGES on your own PR, exactly like APPROVE.
359
+ const requestChangesMuted = selfAuthored && platform.requestChangesSupported === true;
360
+ const selfRequestChangesShort = `You can't request changes on your own ${platform.mrLabel}`;
361
+ const postsAsComment = `Posts as a comment; ${platform.label} has no request-changes review`;
348
362
 
349
363
  if (!input.hasFeedback) {
350
364
  return {
@@ -374,11 +388,27 @@ function buildPlatformSpec(input: DecisionSpecInput, platform: DecisionPlatformI
374
388
  id: 'request-changes',
375
389
  // Frozen copy (maintainer-approved): 'Request changes…'.
376
390
  label: 'Request changes…',
377
- subtitle: 'Overall feedback, zero line comments, via the dialog',
391
+ subtitle: requestChangesMuted
392
+ ? selfRequestChangesShort
393
+ : requestChangesUnsupported
394
+ ? postsAsComment
395
+ : 'Overall feedback, zero line comments, via the dialog',
378
396
  tone: 'primary',
379
397
  icon: 'send',
380
398
  dividerBefore: true,
399
+ ...(requestChangesMuted ? { muted: true } : {}),
381
400
  },
401
+ // With Request changes muted on your own PR, a neutral comment is the
402
+ // live path (the empty state is never a dead end).
403
+ ...(requestChangesMuted
404
+ ? [{
405
+ id: 'note-with-feedback' as const,
406
+ label: 'Comment…',
407
+ subtitle: 'Overall feedback as a neutral comment, via the dialog',
408
+ tone: 'primary' as const,
409
+ icon: 'send' as const,
410
+ }]
411
+ : []),
382
412
  ],
383
413
  };
384
414
  }
@@ -406,7 +436,11 @@ function buildPlatformSpec(input: DecisionSpecInput, platform: DecisionPlatformI
406
436
  {
407
437
  id: 'note-with-feedback',
408
438
  label: 'Post comments, then…',
409
- subtitle: 'Request changes or stay neutral, chosen in the dialog',
439
+ subtitle: requestChangesUnsupported
440
+ ? postsAsComment
441
+ : requestChangesMuted
442
+ ? `Posts as a neutral comment; you can't request changes on your own ${platform.mrLabel}`
443
+ : 'Request changes or stay neutral, chosen in the dialog',
410
444
  tone: 'primary',
411
445
  icon: 'send',
412
446
  dividerBefore: true,