pi-note 0.22.1 → 0.23.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.
@@ -27,7 +27,7 @@ const Action = z.object({ method: z.literal('native-ui/action'), params: z.objec
27
27
  uri: z.literal(URI), revision: z.string().min(1).max(256), action: z.string().min(1).max(256),
28
28
  value: z.union([z.string().max(20_000), z.number().finite(), z.boolean(), z.null()]).optional(), _meta: z.record(z.unknown()).optional(),
29
29
  }) });
30
- const server = new Server({ name: 'pinote', version: '0.22.1' }, { capabilities: { tools: {}, resources: {}, experimental: { 'native-ui-v1': {} } } });
30
+ const server = new Server({ name: 'pinote', version: '0.23.0' }, { capabilities: { tools: {}, resources: {}, experimental: { 'native-ui-v1': {} } } });
31
31
  // Serialize complete read/check/write cycles even when a host sends parallel calls.
32
32
  let queue = Promise.resolve();
33
33
  function serial(run) {
@@ -137,7 +137,13 @@ function staleAction() {
137
137
  function secondaryActions(meta) {
138
138
  return z.object({ footerSecondaryActions: z.literal(true) }).safeParse(meta?.['native-ui/features']).success;
139
139
  }
140
- function view(data, scoped = false, secondary = false) {
140
+ function footerFeatures(meta) {
141
+ return {
142
+ footerStyles: z.object({ footerStyles: z.literal(true) }).safeParse(meta?.['native-ui/features']).success,
143
+ footerDoubleActions: z.object({ footerDoubleActions: z.literal(true) }).safeParse(meta?.['native-ui/features']).success,
144
+ };
145
+ }
146
+ function view(data, scoped = false, secondary = false, features = {}) {
141
147
  const { tasks, task, config, revision } = data;
142
148
  const state = Context.parse({ conversationId: 'outgoing-state', state: { ...data.state, selectedId: task?.id ?? null } }).state;
143
149
  const controls = [];
@@ -196,11 +202,11 @@ function view(data, scoped = false, secondary = false) {
196
202
  const footer = [{ text: `${icons.note} `, width: 3 }];
197
203
  if (task) {
198
204
  footer.push({ text: `${icons.check} `, action: 'done', width: 3 });
199
- footer.push({ text: `${task.tag ? `[${task.tag}] ` : ''}${task.text.split('\n')[0].slice(0, 2000)}`, action: 'preview', width: config.config.titleWidth });
205
+ 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 });
200
206
  }
201
207
  else if (state.proposal) {
202
208
  footer.push({ text: `${icons.add} `, action: 'proposal:accept', ...(secondary ? { secondaryAction: 'proposal:dismiss' } : {}), width: 3 });
203
- footer.push({ text: `${state.proposal.tag ? `[${state.proposal.tag}] ` : ''}${state.proposal.text.split('\n')[0].slice(0, 2000)}`, width: config.config.titleWidth });
209
+ footer.push({ text: `${state.proposal.tag ? `[${state.proposal.tag}] ` : ''}${state.proposal.text.split('\n')[0].slice(0, 2000)}`, ...(features.footerDoubleActions ? { doubleAction: 'proposal:accept' } : {}), ...(features.footerStyles ? { style: { italic: true, color: 'accent' } } : {}), width: config.config.titleWidth });
204
210
  }
205
211
  else
206
212
  footer.push({ text: 'Pinote · no task', width: config.config.titleWidth });
@@ -256,7 +262,7 @@ server.setRequestHandler(ReadResourceRequestSchema, (request, extra) => serial(a
256
262
  if (request.params.uri !== URI)
257
263
  throw new Error('Unknown resource');
258
264
  const data = await snapshot(context(request.params._meta).state, extra.signal);
259
- return { contents: [{ uri: URI, mimeType: MIME, text: JSON.stringify(view(data, scopedActions(request.params._meta), secondaryActions(request.params._meta))) }] };
265
+ return { contents: [{ uri: URI, mimeType: MIME, text: JSON.stringify(view(data, scopedActions(request.params._meta), secondaryActions(request.params._meta), footerFeatures(request.params._meta))) }] };
260
266
  }));
261
267
  server.setRequestHandler(Action, (request, extra) => serial(async () => {
262
268
  const state = context(request.params._meta).state;
@@ -354,7 +360,7 @@ server.setRequestHandler(Action, (request, extra) => serial(async () => {
354
360
  extra.signal.throwIfAborted();
355
361
  saveFooterConfig(cfg, before.config.raw, handoff, newSession, policy);
356
362
  }
357
- const result = { view: view(await snapshot(next, extra.signal), scoped, secondaryActions(request.params._meta)), ...(draft ? { draft } : {}), ...(preview ? { preview } : {}) };
363
+ const result = { view: view(await snapshot(next, extra.signal), scoped, secondaryActions(request.params._meta), footerFeatures(request.params._meta)), ...(draft ? { draft } : {}), ...(preview ? { preview } : {}) };
358
364
  await server.notification({ method: 'notifications/native-ui/changed', params: { uri: URI } });
359
365
  return result;
360
366
  }));
package/mcp/README.md CHANGED
@@ -55,8 +55,13 @@ normal personal data. No desktop notifications are sent.
55
55
  edits are rejected and unrelated settings are preserved. Invalid configuration
56
56
  fails closed; repair it manually rather than replacing it with defaults.
57
57
  - **Footer:** selected task plus configured fields; safe HTTP(S) links are clickable.
58
- The title previews the task; **✔** completes it and clears the selection without
59
- resetting the conversation or editor. Suggestions show only **✚**: left-click to
58
+ Single-click the active title to preview; double-click it to complete. **✔** also
59
+ completes and clears the selection without resetting the conversation or editor.
60
+ Suggested titles use italic accent text; double-click to accept/start, then the
61
+ title returns to normal text. Acceptance cannot reuse the same click capability
62
+ to complete. Styling/double-click require gateway 0.28.0+ and double-click needs
63
+ its updated Kitty mouse mapping (Ctrl+Shift+double-click in fullscreen).
64
+ Older hosts retain plain titles and icon actions. Suggestions show **✚**: left-click to
60
65
  create/start/select, right-click to dismiss. Right-click requires gateway 0.22.0+
61
66
  and its Kitty mouse mapping; **Dismiss suggestion** in the app menu remains the
62
67
  keyboard/older-host fallback. These use the gateway's generic footer actions, not a Pinote
package/mcp/server.ts CHANGED
@@ -32,7 +32,7 @@ const Action = z.object({ method: z.literal('native-ui/action'), params: z.objec
32
32
  uri: z.literal(URI), revision: z.string().min(1).max(256), action: z.string().min(1).max(256),
33
33
  value: z.union([z.string().max(20_000), z.number().finite(), z.boolean(), z.null()]).optional(), _meta: z.record(z.unknown()).optional(),
34
34
  }) });
35
- const server = new Server({ name: 'pinote', version: '0.22.1' }, { capabilities: { tools: {}, resources: {}, experimental: { 'native-ui-v1': {} } } });
35
+ const server = new Server({ name: 'pinote', version: '0.23.0' }, { capabilities: { tools: {}, resources: {}, experimental: { 'native-ui-v1': {} } } });
36
36
  // Serialize complete read/check/write cycles even when a host sends parallel calls.
37
37
  let queue = Promise.resolve();
38
38
  function serial<T>(run: () => Promise<T>): Promise<T> {
@@ -118,7 +118,13 @@ function staleAction(): never {
118
118
  function secondaryActions(meta: Record<string, unknown> | undefined): boolean {
119
119
  return z.object({ footerSecondaryActions: z.literal(true) }).safeParse(meta?.['native-ui/features']).success;
120
120
  }
121
- function view(data: Snapshot, scoped = false, secondary = false) {
121
+ function footerFeatures(meta: Record<string, unknown> | undefined) {
122
+ return {
123
+ footerStyles: z.object({ footerStyles: z.literal(true) }).safeParse(meta?.['native-ui/features']).success,
124
+ footerDoubleActions: z.object({ footerDoubleActions: z.literal(true) }).safeParse(meta?.['native-ui/features']).success,
125
+ };
126
+ }
127
+ function view(data: Snapshot, scoped = false, secondary = false, features: { footerStyles?: boolean; footerDoubleActions?: boolean } = {}) {
122
128
  const { tasks, task, config, revision } = data;
123
129
  const state = Context.parse({ conversationId: 'outgoing-state', state: { ...data.state, selectedId: task?.id ?? null } }).state;
124
130
  const controls: Control[] = [];
@@ -164,13 +170,13 @@ function view(data: Snapshot, scoped = false, secondary = false) {
164
170
  for (const candidate of matches.slice(0, 200)) action(`select:${candidate.id}`, `${candidate.tag ? `[${candidate.tag}] ` : ''}${candidate.text.split('\n')[0]}`, { description: `#${candidate.id} · ${candidate.state}`, confirm: 'Select and start this task for this conversation?' });
165
171
  if (matches.length > 200) body += '\nShowing the first 200 matches; use Filter tasks to narrow the list.';
166
172
  }
167
- const footer: { text: string; url?: string; action?: string; secondaryAction?: string; width?: number }[] = [{ text: `${icons.note} `, width: 3 }];
173
+ const footer: { text: string; url?: string; action?: string; secondaryAction?: string; doubleAction?: string; style?: { italic: boolean; color: 'accent' }; width?: number }[] = [{ text: `${icons.note} `, width: 3 }];
168
174
  if (task) {
169
175
  footer.push({ text: `${icons.check} `, action: 'done', width: 3 });
170
- footer.push({ text: `${task.tag ? `[${task.tag}] ` : ''}${task.text.split('\n')[0].slice(0, 2000)}`, action: 'preview', width: config.config.titleWidth });
176
+ 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 });
171
177
  } else if (state.proposal) {
172
178
  footer.push({ text: `${icons.add} `, action: 'proposal:accept', ...(secondary ? { secondaryAction: 'proposal:dismiss' } : {}), width: 3 });
173
- footer.push({ text: `${state.proposal.tag ? `[${state.proposal.tag}] ` : ''}${state.proposal.text.split('\n')[0].slice(0, 2000)}`, width: config.config.titleWidth });
179
+ footer.push({ text: `${state.proposal.tag ? `[${state.proposal.tag}] ` : ''}${state.proposal.text.split('\n')[0].slice(0, 2000)}`, ...(features.footerDoubleActions ? { doubleAction: 'proposal:accept' } : {}), ...(features.footerStyles ? { style: { italic: true, color: 'accent' } as const } : {}), width: config.config.titleWidth });
174
180
  } else footer.push({ text: 'Pinote · no task', width: config.config.titleWidth });
175
181
  const titleParts = footer.length;
176
182
  if (task) {
@@ -211,7 +217,7 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [
211
217
  server.setRequestHandler(ReadResourceRequestSchema, (request, extra) => serial(async () => {
212
218
  if (request.params.uri !== URI) throw new Error('Unknown resource');
213
219
  const data = await snapshot(context(request.params._meta).state, extra.signal);
214
- return { contents: [{ uri: URI, mimeType: MIME, text: JSON.stringify(view(data, scopedActions(request.params._meta), secondaryActions(request.params._meta))) }] };
220
+ return { contents: [{ uri: URI, mimeType: MIME, text: JSON.stringify(view(data, scopedActions(request.params._meta), secondaryActions(request.params._meta), footerFeatures(request.params._meta))) }] };
215
221
  }));
216
222
  server.setRequestHandler(Action, (request, extra) => serial(async () => {
217
223
  const state = context(request.params._meta).state;
@@ -275,7 +281,7 @@ server.setRequestHandler(Action, (request, extra) => serial(async () => {
275
281
  extra.signal.throwIfAborted();
276
282
  saveFooterConfig(cfg, before.config.raw, handoff, newSession, policy);
277
283
  }
278
- const result = { view: view(await snapshot(next, extra.signal), scoped, secondaryActions(request.params._meta)), ...(draft ? { draft } : {}), ...(preview ? { preview } : {}) };
284
+ const result = { view: view(await snapshot(next, extra.signal), scoped, secondaryActions(request.params._meta), footerFeatures(request.params._meta)), ...(draft ? { draft } : {}), ...(preview ? { preview } : {}) };
279
285
  await server.notification({ method: 'notifications/native-ui/changed', params: { uri: URI } });
280
286
  return result;
281
287
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-note",
3
- "version": "0.22.1",
3
+ "version": "0.23.0",
4
4
  "description": "Persistent pinote tasks, Markdown handoffs, and task status for Pi",
5
5
  "license": "MIT",
6
6
  "type": "module",