@plannotator/ui 0.39.0 → 0.40.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/utils/parser.ts CHANGED
@@ -1131,6 +1131,9 @@ export interface ElementContextExportOptions {
1131
1131
  /** Emit the live-app route line. The grouped export already prints a
1132
1132
  * `## Page:` heading, so it passes false; a single copied entry passes true. */
1133
1133
  includeRoute?: boolean;
1134
+ /** Print the identity lines WITHOUT the fenced outline, for model turns where
1135
+ * the 600-char outline is the expensive part. Default true. */
1136
+ includeOutline?: boolean;
1134
1137
  }
1135
1138
 
1136
1139
  /** The agent-facing element block for a raw-HTML / live-app pinpoint: a
@@ -1141,8 +1144,9 @@ export interface ElementContextExportOptions {
1141
1144
  export const elementContextExportBlock = (ann: any, opts: ElementContextExportOptions = {}): string => {
1142
1145
  const context = ann?.elementContext;
1143
1146
  if (!context || typeof context !== 'object' || typeof context.tag !== 'string') return '';
1147
+ const includeOutline = opts.includeOutline ?? true;
1144
1148
  let block = '';
1145
- if (typeof context.outline === 'string' && context.outline.trim()) {
1149
+ if (includeOutline && typeof context.outline === 'string' && context.outline.trim()) {
1146
1150
  // Fence at 4 backticks; the boundary already defuses 3+ runs inside the
1147
1151
  // outline, and a 4-run here cannot be closed by anything the page wrote.
1148
1152
  const outline = context.outline.replace(/`{3,}/g, "'''").trim();
@@ -1205,11 +1209,18 @@ const additionalTargetsExportBlock = (ann: any): string => {
1205
1209
  // Element identity for the agent, one line per extra target: the
1206
1210
  // selector and the path (a full context block per target would swamp
1207
1211
  // the comment; the rest of the context stays persisted, not exported).
1212
+ //
1213
+ // Gated on the target carrying element context, which is what makes the
1214
+ // element-context work additive in the literal sense it claims: a target
1215
+ // captured before it existed (or restored from an older draft) has an
1216
+ // anchor but no context, and exports byte-identically to before.
1208
1217
  const locators: string[] = [];
1209
- const selector = target?.anchor?.selector;
1210
- if (typeof selector === 'string' && selector) locators.push(`\`${safeInline(selector, 300)}\``);
1211
- const path = target?.context?.path;
1212
- if (typeof path === 'string' && path) locators.push(`\`${safeInline(path, 512)}\``);
1218
+ if (target?.context && typeof target.context === 'object') {
1219
+ const selector = target?.anchor?.selector;
1220
+ if (typeof selector === 'string' && selector) locators.push(`\`${safeInline(selector, 300)}\``);
1221
+ const path = target?.context?.path;
1222
+ if (typeof path === 'string' && path) locators.push(`\`${safeInline(path, 512)}\``);
1223
+ }
1213
1224
  block += `- ${label}"${clipped}"${locators.length ? ` — ${locators.join(' · ')}` : ''}\n`;
1214
1225
  });
1215
1226
  return block;
@@ -1241,7 +1252,11 @@ export const exportAnnotationEntry = (ann: any, opts: ElementContextExportOption
1241
1252
  output += `${commentHeadingLine(ann)}\n> ${ann?.text ?? ''}\n`;
1242
1253
  }
1243
1254
  }
1244
- output += elementContextExportBlock(ann, opts);
1255
+ const resolvedOpts: ElementContextExportOptions = {
1256
+ includeRoute: opts.includeRoute ?? true,
1257
+ ...(opts.includeOutline !== undefined ? { includeOutline: opts.includeOutline } : {}),
1258
+ };
1259
+ output += elementContextExportBlock(ann, resolvedOpts);
1245
1260
  output += additionalTargetsExportBlock(ann);
1246
1261
  if (Array.isArray(ann?.images) && ann.images.length > 0) {
1247
1262
  output += `**Attached images:**\n`;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * One-time gate for the terminal-tools announcement (Plannotator TUI and
3
+ * Herdr Annotate). Cookie-backed like the other announcement gates, so a
4
+ * dismissal survives Plannotator's random localhost ports, and shared by the
5
+ * plan editor, the annotate surfaces and the code review editor: dismissing it
6
+ * anywhere retires it everywhere.
7
+ */
8
+
9
+ import { storage } from './storage';
10
+
11
+ const STORAGE_KEY = 'plannotator-announce-tui-herdr-seen';
12
+ // Bump to re-announce after a meaningful revision.
13
+ const CURRENT_VERSION = '1';
14
+
15
+ export function needsTerminalToolsAnnouncement(): boolean {
16
+ return storage.getItem(STORAGE_KEY) !== CURRENT_VERSION;
17
+ }
18
+
19
+ export function markTerminalToolsAnnouncementSeen(): void {
20
+ storage.setItem(STORAGE_KEY, CURRENT_VERSION);
21
+ }
22
+
23
+ export interface TerminalToolsAnnouncementGateState {
24
+ /**
25
+ * Latched at mount from needsTerminalToolsAnnouncement(). Latched rather than
26
+ * read per render so a dismissal cannot unmount the dialog before its own
27
+ * click handler finishes.
28
+ */
29
+ readonly announcementPending: boolean;
30
+ /** The app has not finished loading its initial payload. */
31
+ readonly isLoading: boolean;
32
+ /**
33
+ * The session has no author to address: archive browsing, a read-only shared
34
+ * plan, and any session with no Plannotator server behind it (the share
35
+ * portal's root and its demo plan included — those are not "shared
36
+ * sessions", so the host must fold that in). Telling a viewer about a CLI
37
+ * they did not open is noise, and the cookie is deliberately NOT consumed,
38
+ * so the next authoring session still shows it.
39
+ */
40
+ readonly readOnlySession: boolean;
41
+ /**
42
+ * Plannotator's compact touch shell. The panel is desktop-shaped (install
43
+ * commands to copy, four outbound links) and a phone is not where anyone
44
+ * installs a terminal tool. Also deferred rather than consumed.
45
+ */
46
+ readonly compact: boolean;
47
+ /**
48
+ * Any other first-run dialog is on screen. The chain dialogs never stack.
49
+ */
50
+ readonly otherFirstRunDialogVisible: boolean;
51
+ }
52
+
53
+ /**
54
+ * Chain gate for the announcement. It is LAST in each app's first-run dialog
55
+ * chain, after every dialog that asks the user to decide something (code
56
+ * review: guide intro, look-and-feel, review setup, edit mode, token hover
57
+ * cards; plan and annotate: look-and-feel, goal setup, permission mode).
58
+ *
59
+ * Last rather than first because none of those dialogs consume this cookie:
60
+ * a session that is busy asking questions defers the announcement to the next
61
+ * load instead of burning it. That also puts it in front of the right reader.
62
+ * Someone opening Plannotator for the first time is still learning this app;
63
+ * the people who should hear that it now runs in a terminal are the ones who
64
+ * already answered every setup question, and they see it on their next load.
65
+ */
66
+ export function terminalToolsAnnouncementCanShow(
67
+ state: TerminalToolsAnnouncementGateState,
68
+ ): boolean {
69
+ return (
70
+ state.announcementPending &&
71
+ !state.isLoading &&
72
+ !state.readOnlySession &&
73
+ !state.compact &&
74
+ !state.otherFirstRunDialogVisible
75
+ );
76
+ }