pi-note 0.10.0 → 0.11.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/README.md CHANGED
@@ -62,8 +62,8 @@ The agent fetches current task text and saved handoff fields when you submit;
62
62
  they are not copied into the draft. The default prompt asks for a summary, not implementation.
63
63
  Existing drafts are preserved; nothing is submitted automatically.
64
64
  With a selected task it offers **Continue**, **Done**, **Switch task**, and **Settings**.
65
- Press **Tab** from the task menu or picker to open global footer settings; Tab
66
- returns to tasks without saving. Settings also lists fields found on the current
65
+ Press **Tab** from the task menu or picker to open global settings; Tab
66
+ returns to tasks, keeping saved changes. Settings also lists fields found on the current
67
67
  task, so existing values can be configured without creating another task.
68
68
  Type in the task picker to filter by text, ID, tag, or state (all words must match).
69
69
  Use ↑/↓ and Enter to select, or Esc to cancel. Rows show **●** for in progress or
@@ -109,6 +109,13 @@ and selected tasks. Run `/reload` after changing this setting. Invalid policy
109
109
  values or unreadable/malformed configuration suppress offers; interactive startup
110
110
  warns until you fix the file and reload. No project-local configuration is read.
111
111
 
112
+ In `/pi-note → Settings`, select **Task prompt** to edit the text inserted after
113
+ selection or **Continue**. **Shift+Enter** or **Ctrl+J** adds a newline; **Enter** saves it
114
+ globally, **Ctrl+C** clears the edit, and **Esc** cancels that unfinished edit.
115
+ Settings and fields save automatically after each confirmed change; no separate
116
+ Save action is needed. Leaving Settings with **Esc** or **Tab** keeps saved changes
117
+ and discards only unfinished input. Saving never changes existing editor input or submits anything.
118
+
112
119
  Task selection and **Continue** reread `handoffPrompt` each time; changing that
113
120
  field needs no `/reload`. Its value is literal text, not a prompt template; use
114
121
  `\n` in JSON strings for multiple lines. It must be a nonblank string without control
@@ -203,9 +210,12 @@ accepted. Missing/blank values do not display and do not consume the field limit
203
210
  In `/pi-note → Settings`, **Add field** adds a global display definition, not a
204
211
  task value. Select any field to edit **Link**, **Label**, **Format**, and **Width**.
205
212
  Label starts as the field name; clear it for no prefix. Link off displays plain
206
- text without a clickable link. **Save** persists the draft; cancellation leaves
207
- configuration untouched. Saves preserve unrelated settings (`handoffPrompt`,
208
- `taskOfferPolicy`) and reject configuration changes made while the dialog was open.
213
+ text without a clickable link. Toggles, field additions/removals, and edits confirmed
214
+ with **Enter** save immediately. **Esc** returns from a field to settings without
215
+ undoing saved changes. Invalid edits and failed saves stay open with an error;
216
+ failed saves leave saved values unchanged and can be retried. Saves include the task
217
+ prompt, preserve unrelated settings (such as `taskOfferPolicy`), and reject external
218
+ configuration changes made since opening settings or the last successful save.
209
219
  Concurrent saves use `pi-note.json.lock`; remove stale locks only when no save is
210
220
  running. Symlinked configuration must be edited manually; saves never replace the link.
211
221
 
@@ -221,8 +231,8 @@ the first Settings save turns this legacy list into explicit global definitions.
221
231
  Settings saves apply immediately in this Pi session. Run `/reload` after manual
222
232
  footer edits or in other running sessions; settings are also reread on session start.
223
233
  Invalid footer configuration warns and uses footer defaults; missing files silently
224
- use defaults. This does not validate or change `handoffPrompt`, which is read
225
- separately on selection/Continue as described above.
234
+ use defaults. The task prompt is also read separately on selection/Continue as
235
+ described above; prompt changes need no reload in any running session.
226
236
  This is user-level configuration, not task data; agents should not change it
227
237
  without approval. Task values still come from `pinote_update_current`.
228
238
 
package/footer-config.ts CHANGED
@@ -3,6 +3,15 @@ import { closeSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, rm
3
3
  import { join } from "node:path";
4
4
  import { randomUUID } from "node:crypto";
5
5
 
6
+ export const defaultHandoffPrompt = "Read the current Pinote task. Summarize your understanding, but don’t start work yet.";
7
+ export function parseHandoffPrompt(value: unknown): string {
8
+ if (typeof value !== "string" || !value.trim() || /[\x00-\x08\x0b-\x1f\x7f-\x9f]/u.test(value)) {
9
+ throw new Error("handoffPrompt in pi-note.json must be a nonblank string without control characters (except tabs/newlines).");
10
+ }
11
+ return value;
12
+ }
13
+ export type PinoteSettings = { footer: FooterConfig; handoffPrompt: string };
14
+
6
15
  export type FooterField = { name: string; label: string; link: boolean; format: string; width?: number };
7
16
  export type FooterConfig = { titleWidth: number; fieldWidth: number; maxFields: number; fields: FooterField[] | null };
8
17
  export const defaultFooterConfig: FooterConfig = { titleWidth: 60, fieldWidth: 60, maxFields: 4, fields: null };
@@ -80,9 +89,10 @@ export function loadFooterConfig(warn: (message: string) => void): FooterConfig
80
89
  }
81
90
 
82
91
  // Synchronous compare-and-replace: reject edits made while the settings dialog was open,
83
- // preserve unrelated keys (including handoffPrompt), and never leave partial JSON behind.
84
- export function saveFooterConfig(config: FooterConfig, expectedRaw: string | null): FooterConfig {
92
+ // preserve unrelated keys, and never leave partial JSON behind.
93
+ export function saveFooterConfig(config: FooterConfig, expectedRaw: string | null, handoffPrompt?: string) {
85
94
  const checked = parseFooterConfig({ footer: config });
95
+ const prompt = handoffPrompt === undefined ? {} : { handoffPrompt: parseHandoffPrompt(handoffPrompt) };
86
96
  const directory = getAgentDir();
87
97
  mkdirSync(directory, { recursive: true });
88
98
  const path = join(directory, "pi-note.json");
@@ -91,15 +101,17 @@ export function saveFooterConfig(config: FooterConfig, expectedRaw: string | nul
91
101
  try { descriptor = openSync(lock, "wx", 0o600); }
92
102
  catch { throw new Error("Pinote settings are locked. Retry after another save finishes; remove a stale pi-note.json.lock only when no save is running."); }
93
103
  const temp = join(directory, `.pi-note-${randomUUID()}.tmp`);
104
+ let raw: string;
94
105
  try {
95
106
  if (readRaw() !== expectedRaw) throw new Error("Pi-note configuration changed while settings were open. Reopen settings before saving.");
96
107
  if (lstatSync(path, { throwIfNoEntry: false })?.isSymbolicLink()) throw new Error("pi-note.json is a symlink. Edit its target manually rather than replacing the link.");
97
108
  const root = expectedRaw === null ? {} : JSON.parse(expectedRaw);
98
109
  if (!object(root)) throw new Error("Invalid pi-note configuration; expected an object.");
99
- writeFileSync(temp, `${JSON.stringify({ ...root, footer: checked }, null, 2)}\n`, { flag: "wx", mode: 0o600 });
110
+ raw = `${JSON.stringify({ ...root, ...prompt, footer: checked }, null, 2)}\n`;
111
+ writeFileSync(temp, raw, { flag: "wx", mode: 0o600 });
100
112
  renameSync(temp, path);
101
113
  } finally { rmSync(temp, { force: true }); closeSync(descriptor); rmSync(lock, { force: true }); }
102
- return checked;
114
+ return { config: checked, raw };
103
115
  }
104
116
 
105
117
  export function effectiveFooterFields(config: FooterConfig, notes: Record<string, string> = {}): FooterField[] {
@@ -1,6 +1,6 @@
1
1
  import type { Theme } from "@earendil-works/pi-coding-agent";
2
- import { Input, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
3
- import { defaultFooterField, parseFooterConfig, type FooterConfig, type FooterField } from "./footer-config.ts";
2
+ import { Editor, Input, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
3
+ import { defaultFooterField, parseFooterConfig, parseHandoffPrompt, type PinoteSettings, type FooterConfig, type FooterField } from "./footer-config.ts";
4
4
 
5
5
  type Matches = (data: string, action: string) => boolean;
6
6
  type Row = { label: string; action: () => void };
@@ -38,10 +38,14 @@ export class TaskMenu {
38
38
 
39
39
  export class FooterSettings {
40
40
  private draft: FooterConfig;
41
+ private prompt: string;
42
+ private editor?: Editor;
43
+ private createEditor: () => Editor;
41
44
  private knownFields: string[];
42
45
  private theme: Theme;
43
46
  private matches: Matches;
44
- private done: (result: FooterConfig | "tasks" | undefined) => void;
47
+ private done: (result: "tasks" | undefined) => void;
48
+ private save: (settings: PinoteSettings) => void;
45
49
  private requestRender: () => void;
46
50
  private index = 0;
47
51
  private field?: FooterField;
@@ -49,15 +53,23 @@ export class FooterSettings {
49
53
  private error = "";
50
54
  private hasFocus = false;
51
55
 
52
- constructor(config: FooterConfig, knownFields: string[], theme: Theme, matches: Matches,
53
- done: (result: FooterConfig | "tasks" | undefined) => void, requestRender: () => void) {
54
- this.draft = structuredClone(config);
56
+ constructor(config: PinoteSettings, knownFields: string[], theme: Theme, matches: Matches,
57
+ done: (result: "tasks" | undefined) => void, requestRender: () => void, createEditor: () => Editor,
58
+ save: (settings: PinoteSettings) => void) {
59
+ this.draft = structuredClone(config.footer);
60
+ this.prompt = config.handoffPrompt;
61
+ this.createEditor = createEditor;
62
+ this.save = save;
55
63
  this.draft.fields ??= [];
56
64
  this.knownFields = [...new Set(knownFields)].filter((name) => name !== "Bar");
57
65
  this.theme = theme; this.matches = matches; this.done = done; this.requestRender = requestRender;
58
66
  }
59
67
  get focused() { return this.hasFocus; }
60
- set focused(value: boolean) { this.hasFocus = value; if (this.input) this.input.widget.focused = value; }
68
+ set focused(value: boolean) {
69
+ this.hasFocus = value;
70
+ if (this.input) this.input.widget.focused = value;
71
+ if (this.editor) this.editor.focused = value;
72
+ }
61
73
 
62
74
  private ask(heading: string, value: string, apply: (value: string) => void) {
63
75
  const widget = new Input();
@@ -67,6 +79,20 @@ export class FooterSettings {
67
79
  private validate(field: FooterField): FooterField {
68
80
  return parseFooterConfig({ footer: { fields: [field] } }).fields![0];
69
81
  }
82
+ private persist(footer = this.draft, handoffPrompt = this.prompt) {
83
+ const settings = { footer: parseFooterConfig({ footer }), handoffPrompt: parseHandoffPrompt(handoffPrompt) };
84
+ this.save(structuredClone(settings));
85
+ this.draft = settings.footer;
86
+ this.prompt = settings.handoffPrompt;
87
+ }
88
+ private persistField(field: FooterField) {
89
+ const candidate = this.validate(field);
90
+ const fields = this.draft.fields!.filter((entry) => entry.name !== candidate.name);
91
+ const index = this.draft.fields!.findIndex((entry) => entry.name === candidate.name);
92
+ fields.splice(index < 0 ? fields.length : index, 0, candidate);
93
+ this.persist({ ...this.draft, fields });
94
+ this.field = candidate;
95
+ }
70
96
  private edit(name: string) {
71
97
  this.field = structuredClone(this.draft.fields!.find((field) => field.name === name) ?? defaultFooterField(name));
72
98
  this.index = 0; this.error = "";
@@ -77,56 +103,66 @@ export class FooterSettings {
77
103
  action: () => this.ask(key, String(this.draft[key]), (value) => {
78
104
  const candidate = { ...this.draft, [key]: Number(value) };
79
105
  if (!value.trim()) throw new Error("Enter a number.");
80
- this.draft = parseFooterConfig({ footer: candidate });
106
+ this.persist(candidate);
81
107
  }),
82
108
  }));
109
+ rows.push({ label: `Task prompt: ${display(this.prompt)}`, action: () => {
110
+ this.editor = this.createEditor();
111
+ this.editor.disableSubmit = true;
112
+ this.editor.setText(this.prompt);
113
+ this.editor.focused = this.hasFocus;
114
+ } });
83
115
  const configured = this.draft.fields!.map((field) => field.name);
84
116
  for (const name of [...configured, ...this.knownFields.filter((name) => !configured.includes(name))]) {
85
117
  rows.push({ label: `Field: ${display(name)}${configured.includes(name) ? "" : " (not configured)"}`, action: () => this.edit(name) });
86
118
  }
87
119
  rows.push({ label: "+ Add field", action: () => this.ask("Field name", "", (value) => {
88
120
  const field = this.validate(defaultFooterField(value));
89
- this.edit(field.name);
90
- }) }, { label: "Save settings", action: () => this.done(parseFooterConfig({ footer: this.draft })) },
121
+ if (this.draft.fields!.some((entry) => entry.name === field.name)) this.edit(field.name);
122
+ else {
123
+ this.persistField(field);
124
+ this.index = 0;
125
+ }
126
+ }) },
91
127
  { label: "Return to tasks", action: () => this.done("tasks") });
92
128
  return rows;
93
129
  }
94
130
  private fieldRows(): Row[] {
95
131
  const field = this.field!;
96
132
  const rows: Row[] = [
97
- { label: `Link: ${field.link ? "on" : "off (plain text)"}`, action: () => { field.link = !field.link; } },
133
+ { label: `Link: ${field.link ? "on" : "off (plain text)"}`, action: () => this.persistField({ ...field, link: !field.link }) },
98
134
  { label: `Label: ${display(field.label) || "(none)"}`, action: () => this.ask("Label (empty = no prefix)", field.label, (value) => {
99
- this.field = this.validate({ ...field, label: value });
135
+ this.persistField({ ...field, label: value });
100
136
  }) },
101
137
  { label: `Format: ${display(field.format) || "(empty)"}`, action: () => this.ask("Format: <value>, <url>, <number>", field.format, (value) => {
102
- this.field = this.validate({ ...field, format: value });
138
+ this.persistField({ ...field, format: value });
103
139
  }) },
104
140
  { label: `Width: ${field.width ?? `default (${this.draft.fieldWidth})`}`, action: () => this.ask("Width (empty = default)", field.width === undefined ? "" : String(field.width), (value) => {
105
141
  const candidate = { ...field };
106
142
  if (value.trim()) candidate.width = Number(value); else delete candidate.width;
107
- this.field = this.validate(candidate);
143
+ this.persistField(candidate);
108
144
  }) },
109
- { label: "Save field", action: () => {
110
- const candidate = this.validate(field);
111
- const fields = this.draft.fields!.filter((entry) => entry.name !== candidate.name);
112
- const index = this.draft.fields!.findIndex((entry) => entry.name === candidate.name);
113
- fields.splice(index < 0 ? fields.length : index, 0, candidate);
114
- this.draft = parseFooterConfig({ footer: { ...this.draft, fields } });
115
- this.field = undefined; this.index = 0;
116
- } },
117
145
  ];
118
146
  if (this.draft.fields!.some((entry) => entry.name === field.name)) rows.push({ label: "Remove field", action: () => {
119
- this.draft.fields = this.draft.fields!.filter((entry) => entry.name !== field.name);
147
+ this.persist({ ...this.draft, fields: this.draft.fields!.filter((entry) => entry.name !== field.name) });
120
148
  this.field = undefined; this.index = 0;
121
149
  } });
122
- rows.push({ label: "Cancel field", action: () => { this.field = undefined; this.index = 0; } });
150
+ rows.push({ label: "Back to settings", action: () => { this.field = undefined; this.index = 0; } });
123
151
  return rows;
124
152
  }
125
153
  handleInput(data: string) {
126
154
  this.error = "";
127
155
  try {
128
156
  if (isTab(data)) this.done("tasks");
129
- else if (this.input) {
157
+ else if (this.editor) {
158
+ if (matchesKey(data, "ctrl+c")) this.editor.setText("");
159
+ else if (this.matches(data, "tui.select.cancel")) this.editor = undefined;
160
+ else if (this.matches(data, "tui.input.newLine")) this.editor.handleInput(data);
161
+ else if (this.matches(data, "tui.select.confirm")) {
162
+ this.persist(this.draft, this.editor.getExpandedText());
163
+ this.editor = undefined;
164
+ } else this.editor.handleInput(data);
165
+ } else if (this.input) {
130
166
  if (this.matches(data, "tui.select.cancel")) this.input = undefined;
131
167
  else if (this.matches(data, "tui.select.confirm")) {
132
168
  const input = this.input;
@@ -147,9 +183,12 @@ export class FooterSettings {
147
183
  }
148
184
  render(width: number) {
149
185
  const lines = [this.theme.fg("accent", "Pinote — Global Settings"), this.theme.fg("dim", "Tasks (Tab) · Settings · Fields")];
150
- if (this.input) {
186
+ if (this.editor) {
187
+ lines.push(this.theme.fg("accent", "Task prompt"), ...this.editor.render(width),
188
+ this.theme.fg("dim", "Enter save · Shift+Enter/Ctrl+J newline · Ctrl+C clear · Esc cancel edit"));
189
+ } else if (this.input) {
151
190
  lines.push(this.theme.fg("accent", this.input.heading), ...this.input.widget.render(width),
152
- this.theme.fg("dim", "Enter apply to draft · Esc cancel"));
191
+ this.theme.fg("dim", "Enter save · Esc cancel edit"));
153
192
  } else {
154
193
  if (this.field) lines.push(this.theme.fg("accent", `Field: ${display(this.field.name)}`));
155
194
  const rows = this.field ? this.fieldRows() : this.rootRows();
@@ -158,13 +197,13 @@ export class FooterSettings {
158
197
  lines.push(...rows.slice(start, start + 8).map((row, offset) => offset + start === this.index
159
198
  ? this.theme.fg("accent", `> ${row.label}`) : ` ${row.label}`));
160
199
  if (rows.length > 8) lines.push(this.theme.fg("dim", `${this.index + 1}/${rows.length}`));
161
- lines.push(this.theme.fg("dim", "↑↓ navigate · Enter edit · Esc cancel"));
200
+ lines.push(this.theme.fg("dim", "↑↓ navigate · Enter edit · Esc back"));
162
201
  }
163
202
  if (this.error) lines.push(this.theme.fg("error", this.error));
164
- lines.push(this.theme.fg("dim", "Tab: tasks, discard draft · Save settings: persist globally"));
203
+ lines.push(this.theme.fg("dim", "Changes save automatically · Tab: tasks"));
165
204
  return lines.map((line) => truncateToWidth(line, width));
166
205
  }
167
- invalidate() { this.input?.widget.invalidate(); }
206
+ invalidate() { this.input?.widget.invalidate(); this.editor?.invalidate(); }
168
207
  }
169
208
 
170
209
  export function withSettingsTab<T extends { handleInput?(data: string): void; render(width: number): string[]; invalidate(): void; focused?: boolean }>(
package/index.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { getAgentDir, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { truncateToWidth, type Keybinding } from "@earendil-works/pi-tui";
2
+ import { Editor, truncateToWidth, type Keybinding } from "@earendil-works/pi-tui";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { stripVTControlCharacters } from "node:util";
6
6
  import { Type } from "typebox";
7
7
  import { createPRWatcher } from "./pr-watch.ts";
8
- import { defaultFooterConfig, effectiveFooterFields, loadFooterConfig, readFooterDocument, saveFooterConfig, type FooterConfig } from "./footer-config.ts";
8
+ import { defaultFooterConfig, defaultHandoffPrompt, effectiveFooterFields, loadFooterConfig, parseHandoffPrompt, readFooterDocument, saveFooterConfig, type FooterConfig, type PinoteSettings } from "./footer-config.ts";
9
9
  import { FooterSettings, TaskMenu, withSettingsTab } from "./footer-settings.ts";
10
10
  import { TaskPicker, taskState, taskTag } from "./task-picker.ts";
11
11
  import { bundledCLIVersion, cliMenu, detectCLI, setupCLI, setupHint, versionAtLeast, type CLIAction } from "./setup.ts";
@@ -40,7 +40,6 @@ const clean = (value: string) => stripVTControlCharacters(value).replace(/[\x00-
40
40
  const firstLine = (task: Summary) => clean(task.text.split("\n", 1)[0]).replace(/\s+/gu, " ").trim();
41
41
  const title = (task: Summary) => truncateToWidth(`#${task.id} ${firstLine(task)}`, 60);
42
42
 
43
- const defaultHandoffPrompt = "Read the current Pinote task. Summarize your understanding, but don’t start work yet.";
44
43
  function readConfig(): Record<string, unknown> {
45
44
  const path = join(getAgentDir(), "pi-note.json");
46
45
  let raw: string;
@@ -77,12 +76,7 @@ function creationGuidance(): { guidelines: string[]; error?: string } {
77
76
 
78
77
  function handoffPrompt(): string {
79
78
  const config = readConfig();
80
- const path = join(getAgentDir(), "pi-note.json");
81
- if (!("handoffPrompt" in config)) return defaultHandoffPrompt;
82
- if (!text(config.handoffPrompt) || !config.handoffPrompt.trim()) {
83
- throw new Error(`handoffPrompt in ${path} must be a nonblank string without control characters (except tabs/newlines).`);
84
- }
85
- return config.handoffPrompt;
79
+ return "handoffPrompt" in config ? parseHandoffPrompt(config.handoffPrompt) : defaultHandoffPrompt;
86
80
  }
87
81
 
88
82
  function parse(raw: string): unknown {
@@ -360,7 +354,7 @@ export default function (pi: ExtensionAPI) {
360
354
  registerInstallCommand("pi-note-upgrade");
361
355
 
362
356
  pi.registerCommand("pi-note", {
363
- description: "Continue, complete, switch tasks, or edit global footer settings (Tab)",
357
+ description: "Continue, complete, switch tasks, or edit global settings (Tab)",
364
358
  handler: async (args, ctx) => {
365
359
  if (!ctx.hasUI || ctx.mode !== "tui") return;
366
360
  if (args.trim()) { ctx.ui.notify("Usage: /pi-note", "warning"); return; }
@@ -381,15 +375,30 @@ export default function (pi: ExtensionAPI) {
381
375
  while (canAct()) {
382
376
  if (settings) {
383
377
  const document = readFooterDocument();
384
- const config = { ...document.config, fields: effectiveFooterFields(document.config, current?.agent_notes) };
385
- const edited = await ctx.ui.custom<FooterConfig | "tasks" | undefined>((tui, theme, kb, done) =>
378
+ const root = document.raw === null ? {} : JSON.parse(document.raw);
379
+ const config: PinoteSettings = {
380
+ footer: { ...document.config, fields: effectiveFooterFields(document.config, current?.agent_notes) },
381
+ handoffPrompt: "handoffPrompt" in root ? parseHandoffPrompt(root.handoffPrompt) : defaultHandoffPrompt,
382
+ };
383
+ let expectedRaw = document.raw;
384
+ const edited = await ctx.ui.custom<"tasks" | undefined>((tui, theme, kb, done) =>
386
385
  new FooterSettings(config, Object.keys(current?.agent_notes ?? {}).filter((name) => name !== "Bar"),
387
- theme, (data, action) => kb.matches(data, action as Keybinding), done, () => tui.requestRender()));
386
+ theme, (data, action) => kb.matches(data, action as Keybinding), done, () => tui.requestRender(),
387
+ () => new Editor(tui, {
388
+ borderColor: (line) => theme.fg("accent", line),
389
+ selectList: { selectedPrefix: (line) => theme.fg("accent", line), selectedText: (line) => theme.fg("accent", line),
390
+ description: (line) => theme.fg("muted", line), scrollInfo: (line) => theme.fg("dim", line),
391
+ noMatch: (line) => theme.fg("warning", line) },
392
+ }), (updated) => {
393
+ if (!canAct()) throw new Error("This session changed. Reopen settings before editing.");
394
+ const saved = saveFooterConfig(updated.footer, expectedRaw, updated.handoffPrompt);
395
+ expectedRaw = saved.raw;
396
+ footerConfig = saved.config;
397
+ void refresh(ctx);
398
+ }));
388
399
  if (!canAct() || edited === undefined) return;
389
- if (edited === "tasks") { settings = false; continue; }
390
- footerConfig = saveFooterConfig(edited, document.raw);
391
- ctx.ui.notify("Global Pinote footer settings saved.", "info");
392
- return;
400
+ settings = false;
401
+ continue;
393
402
  }
394
403
  const action = current
395
404
  ? await ctx.ui.custom<string | undefined>((tui, theme, kb, done) =>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-note",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "Persistent pinote tasks, Markdown handoffs, and task status for Pi",
5
5
  "license": "MIT",
6
6
  "type": "module",