pi-note 0.39.0 → 0.39.3

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 CHANGED
@@ -58,9 +58,11 @@ confirmation. **Clear selection** leaves its progress unchanged.
58
58
 
59
59
  **Preview** opens a scrollable, read-only document. **Insert task prompt…** reviews
60
60
  and inserts into an empty editor only; it never submits or replaces a draft.
61
- Creation, suggestion acceptance and shared changes require complete review, then
62
- a separate Cancel-first confirmation. Completion also asks explicitly and requires
63
- idle Pi. Failed or uncertain writes offer refresh/verification, never automatic replay.
61
+ Creation, menu suggestion acceptance and shared changes require complete review,
62
+ then a separate Cancel-first confirmation. Menu completion also asks explicitly.
63
+ Footer double-click directly approves suggestion acceptance or completion;
64
+ completion requires idle Pi. Failed or uncertain
65
+ writes offer refresh/verification, never automatic replay.
64
66
 
65
67
  **Settings** groups prompts/guidance, task-offer policy, footer layout and field
66
68
  definitions. Editors accept plain text or numbers, not JSON; validation retains
@@ -70,9 +72,12 @@ are saved only after review and confirmation.
70
72
  Menus use Pi's native inline components, themes and configured selection keys.
71
73
  **Escape** goes back; **Ctrl+R** refreshes; page keys scroll lists and documents.
72
74
  Small terminals show compact menus or ask you to enlarge the window before acting.
73
- Fullscreen has a clickable footer: **▤** opens Pinote; task and suggestion titles
74
- only preview, never complete or accept on double-click. Regular mode shows status;
75
- `/pinote` provides all actions. No Kitty action handler is needed.
75
+ Fullscreen has a clickable footer: **▤** opens Pinote; single-click a task title
76
+ to preview, or double-click to complete immediately without another confirmation
77
+ (requires idle Pi). Single-click a suggestion title to preview it; double-click
78
+ to accept, start and select it immediately without another confirmation.
79
+ Regular mode shows status; `/pinote` provides all actions. No Kitty action handler
80
+ is needed.
76
81
 
77
82
  Accepting a suggestion, selecting a different task, or creating and selecting one
78
83
  renames the current Kitty tab to `[tag] task title` (tag omitted when unset),
@@ -212,10 +217,18 @@ See [setup, script contract and safeguards](mcp/FIELD_EVENTS.md). Disabled by de
212
217
 
213
218
  ## Migration
214
219
 
220
+ Since pi-note 0.39.3, fullscreen suggestion-title double-click accepts immediately;
221
+ single-click still previews and menu acceptance still requires review/confirmation.
222
+ Update both client and service, then `/reload` Pi.
223
+
224
+ Since pi-note 0.39.2, fullscreen task-title double-click completes without another
225
+ confirmation; single-click still previews and menu completion still confirms.
226
+ Update the client and `/reload` Pi. The service must support footer double-click
227
+ (pi-note 0.39.1+).
228
+
215
229
  Since pi-note 0.38.0, the default HTTP extension uses task-first navigation and
216
- explicit completion/suggestion approval instead of footer-title double-click
217
- mutations. Update the client and service together, then `/reload`. The explicit
218
- local-only extension retains its existing menus.
230
+ explicit completion/suggestion approval. Update the client and service together,
231
+ then `/reload`. The explicit local-only extension retains its existing menus.
219
232
 
220
233
 
221
234
  Since pi-note 0.36.0, new-task proposals use `title` and optional `description`
@@ -262,9 +262,9 @@ function view(data, scoped = false, secondary = false, features = {}) {
262
262
  : [{ text: `${icons.note} `, width: 3 }];
263
263
  if (features.footerNativeActions) {
264
264
  if (task)
265
- footer.push({ text: `${task.tag ? `[${task.tag}] ` : ''}${task.text.split('\n')[0].slice(0, 2000)}`, action: 'preview', width: config.config.titleWidth });
265
+ footer.push({ text: `${task.tag ? `[${task.tag}] ` : ''}${task.text.split('\n')[0].slice(0, 2000)}`, action: 'preview', ...(features.footerDoubleActions ? { doubleAction: 'done' } : {}), width: config.config.titleWidth });
266
266
  else if (state.proposal)
267
- footer.push({ text: `? ${suggestion.tag ? `[${suggestion.tag}] ` : ''}${suggestion.text.split('\n')[0].slice(0, 2000)}`, action: 'proposal:preview', ...(features.footerStyles ? { style: { italic: true, color: 'accent' } } : {}), width: config.config.titleWidth });
267
+ footer.push({ text: `? ${suggestion.tag ? `[${suggestion.tag}] ` : ''}${suggestion.text.split('\n')[0].slice(0, 2000)}`, action: 'proposal:preview', ...(features.footerDoubleActions ? { doubleAction: 'proposal:accept' } : {}), ...(features.footerStyles ? { style: { italic: true, color: 'accent' } } : {}), width: config.config.titleWidth });
268
268
  else
269
269
  footer.push({ text: 'Pinote · No task', width: config.config.titleWidth });
270
270
  }
package/mcp/core.ts CHANGED
@@ -228,8 +228,8 @@ function view(data: Snapshot, scoped = false, secondary = false, features: { foo
228
228
  ? [{ text: `${icons['open-app']} `, openApp: true, width: 3 }]
229
229
  : [{ text: `${icons.note} `, width: 3 }];
230
230
  if (features.footerNativeActions) {
231
- if (task) footer.push({ text: `${task.tag ? `[${task.tag}] ` : ''}${task.text.split('\n')[0].slice(0, 2000)}`, action: 'preview', width: config.config.titleWidth });
232
- else if (state.proposal) footer.push({ text: `? ${suggestion!.tag ? `[${suggestion!.tag}] ` : ''}${suggestion!.text.split('\n')[0].slice(0, 2000)}`, action: 'proposal:preview', ...(features.footerStyles ? { style: { italic: true, color: 'accent' } as const } : {}), width: config.config.titleWidth });
231
+ if (task) footer.push({ text: `${task.tag ? `[${task.tag}] ` : ''}${task.text.split('\n')[0].slice(0, 2000)}`, action: 'preview', ...(features.footerDoubleActions ? { doubleAction: 'done' } : {}), width: config.config.titleWidth });
232
+ else if (state.proposal) footer.push({ text: `? ${suggestion!.tag ? `[${suggestion!.tag}] ` : ''}${suggestion!.text.split('\n')[0].slice(0, 2000)}`, action: 'proposal:preview', ...(features.footerDoubleActions ? { doubleAction: 'proposal:accept' } : {}), ...(features.footerStyles ? { style: { italic: true, color: 'accent' } as const } : {}), width: config.config.titleWidth });
233
233
  else footer.push({ text: 'Pinote · No task', width: config.config.titleWidth });
234
234
  } else if (task) {
235
235
  footer.push({ text: `${icons.check} `, action: 'done', width: 3 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-note",
3
- "version": "0.39.0",
3
+ "version": "0.39.3",
4
4
  "description": "Persistent pinote tasks, Markdown handoffs, and task status for Pi",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/remote/ui.ts CHANGED
@@ -43,7 +43,9 @@ export class RemoteUI {
43
43
  if (this.opening || this.acting) return false;
44
44
  if (item.openApp) { await this.open(ctx); return true; }
45
45
  const control = view.controls.find((c) => c.id === action && c.kind === 'action');
46
- return control ? this.dispatch(ctx, view, control) : false;
46
+ // Deliberate footer double-click approves completion or suggestion acceptance.
47
+ const approved = (action === 'done' || action === 'proposal:accept') && item.doubleAction === action;
48
+ return control ? this.dispatch(ctx, view, control, undefined, approved) : false;
47
49
  }, this.repaint);
48
50
  }, { placement: 'belowEditor' });
49
51
  this.render();
@@ -65,7 +67,7 @@ export class RemoteUI {
65
67
  }) : 'Pinote · unavailable (/pinote to reconnect)');
66
68
  this.repaint();
67
69
  }
68
- async dispatch(ctx: ExtensionContext, view: NativeView, control: NativeControl, value?: NativeControl['value']): Promise<boolean> {
70
+ async dispatch(ctx: ExtensionContext, view: NativeView, control: NativeControl, value?: NativeControl['value'], footerConfirmed = false): Promise<boolean> {
69
71
  if (ctx.mode !== 'tui') throw new Error('Pinote UI actions require TUI mode');
70
72
  if (this.acting) return false;
71
73
  if (control.id === 'done' && !ctx.isIdle()) {
@@ -75,14 +77,14 @@ export class RemoteUI {
75
77
  const current = this.session.current(), signal = this.session.signal;
76
78
  try {
77
79
  if (typeof value === 'string') editorText(value);
78
- if (control.id === 'done') {
80
+ if (control.id === 'done' && !footerConfirmed) {
79
81
  const exits = typeof view.state.selectedId === 'number' && this.exitOnComplete(view.state.selectedId);
80
82
  const accept = exits ? 'Complete task and exit' : 'Complete task';
81
83
  const summary = truncateToWidth(line(view.body ?? ''), 100);
82
84
  if (await ctx.ui.select(`Pinote / ${accept} #${view.state.selectedId}\n${summary}\n${exits ? 'Closes this dedicated Pi session.' : 'Clears selection; keeps this conversation and editor.'}`,
83
85
  ['Cancel', accept], { signal }) !== accept) return false;
84
86
  }
85
- if (control.confirm) {
87
+ if (control.confirm && !(footerConfirmed && control.id === 'proposal:accept')) {
86
88
  const details = control.kind === 'action' ? control.confirm : `${control.confirm}\n\n${control.id === 'add' || control.id === 'add-field' ? '' : `Before:\n${disclosure(control.value)}\n\n`}After:\n${disclosure(value)}`;
87
89
  const accept = control.id === 'add' ? 'Create, start and select' : control.id === 'proposal:accept' ? 'Accept suggestion' :
88
90
  control.id === 'remove-field' ? 'Remove display field' : control.id === 'add-field' ? 'Add field' : 'Save change';