@danypops/pi-lector 0.11.0 → 0.11.2

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.
@@ -56,16 +56,27 @@ export class ExplorerComponent implements Component {
56
56
  this.theme = theme;
57
57
  this.session = session;
58
58
  this.done = done;
59
- void this.loadDirectory(initialRelativePath);
59
+ this.pending = this.loadDirectory(initialRelativePath).catch((error: unknown) => this.reportError(error));
60
60
  }
61
61
 
62
62
  private pending: Promise<void> = Promise.resolve();
63
63
 
64
64
  invalidate(): void {}
65
65
 
66
- /** Fire-and-forget from the real TUI's own perspective (matching Component's void-returning contract) -- tests await settled() for deterministic assertions after triggering async work. */
66
+ /**
67
+ * Fire-and-forget from the real TUI's own perspective (matching Component's void-returning
68
+ * contract) -- tests await settled() for deterministic assertions after triggering async work.
69
+ * Any rejection (a daemon call failing, a stale/incompatible daemon, a genuine bug) is caught
70
+ * here rather than left to become an unhandled rejection: that crashed the whole Pi process in
71
+ * production once already, when a stale running daemon didn't yet support workspace.listDirectory.
72
+ */
67
73
  handleInput(data: string): void {
68
- this.pending = this.handleInputAsync(data);
74
+ this.pending = this.handleInputAsync(data).catch((error: unknown) => this.reportError(error));
75
+ }
76
+
77
+ private reportError(error: unknown): void {
78
+ this.statusMessage = `error: ${error instanceof Error ? error.message : String(error)}`;
79
+ this.tui.requestRender();
69
80
  }
70
81
 
71
82
  /** Resolves once every async operation triggered by the most recent handleInput() call has finished -- a test-determinism hook, not part of the Component contract. */
@@ -54,21 +54,40 @@ export class NeovimEditorComponent implements Component {
54
54
  this.done = done;
55
55
  this.extension = extname(host.filePath);
56
56
  this.state = new EditorState(content);
57
- void this.refreshHighlights();
57
+ this.refreshHighlightsSafely();
58
58
  }
59
59
 
60
60
  invalidate(): void {
61
61
  this.highlightCache = undefined;
62
62
  }
63
63
 
64
+ /**
65
+ * Fire-and-forget from the real TUI's own perspective (matching Component's void-returning
66
+ * contract). Any rejection from performAction (host.save/host.hover talking to a daemon that
67
+ * fails or restarts mid-session) is caught here rather than left to become an unhandled
68
+ * rejection that crashes the whole Pi process -- the same defect class found and fixed in
69
+ * ExplorerComponent's own constructor.
70
+ */
64
71
  handleInput(data: string): void {
65
72
  this.state.handleKey(data);
66
73
  this.scrollToKeepCursorVisible();
67
74
  const action = this.state.pendingAction;
68
- if (action) void this.performAction(action);
75
+ if (action) this.performActionSafely(action);
69
76
  this.tui.requestRender();
70
77
  }
71
78
 
79
+ private performActionSafely(action: EditorAction): void {
80
+ void this.performAction(action).catch((error: unknown) => {
81
+ this.statusMessage = `error: ${error instanceof Error ? error.message : String(error)}`;
82
+ this.tui.requestRender();
83
+ });
84
+ }
85
+
86
+ /** Highlighting is cosmetic: a failure here must never surface as a status message that stomps a real save/hover result, and must never crash the editor. */
87
+ private refreshHighlightsSafely(): void {
88
+ void this.refreshHighlights().catch(() => undefined);
89
+ }
90
+
72
91
  private async performAction(action: EditorAction): Promise<void> {
73
92
  switch (action.kind) {
74
93
  case "save":
@@ -112,6 +131,10 @@ export class NeovimEditorComponent implements Component {
112
131
  this.tui.requestRender();
113
132
  }
114
133
 
134
+ private refreshHighlightsIfStale(): void {
135
+ if (this.highlightCache?.text !== this.state.buffer.text) this.refreshHighlightsSafely();
136
+ }
137
+
115
138
  private scrollToKeepCursorVisible(): void {
116
139
  const viewportHeight = Math.max(1, this.tui.terminal.rows - 2);
117
140
  if (this.state.cursorLine < this.scrollTop) this.scrollTop = this.state.cursorLine;
@@ -119,7 +142,7 @@ export class NeovimEditorComponent implements Component {
119
142
  }
120
143
 
121
144
  render(width: number): string[] {
122
- if (this.highlightCache?.text !== this.state.buffer.text) void this.refreshHighlights();
145
+ this.refreshHighlightsIfStale();
123
146
 
124
147
  const viewportHeight = Math.max(1, this.tui.terminal.rows - 2);
125
148
  const gutterWidth = Math.max(3, String(this.state.buffer.lineCount).length) + 1;
@@ -1,5 +1,5 @@
1
1
  import type { JobSnapshot, PopulateSymbolGraphResult, WorkspaceCacheStatus } from "@danypops/lector";
2
- import { lectorClient, withWorkspace, workspaceForDirectory } from "../lector-client.ts";
2
+ import { lectorClient, withWorkspace, workspaceForProjectDirectory } from "../lector-client.ts";
3
3
 
4
4
  export interface WorkspaceCacheOperations {
5
5
  status(directory: string, maxFiles: number, maxSymbolsPerFile: number): Promise<WorkspaceCacheStatus>;
@@ -11,7 +11,7 @@ export function createWorkspaceCacheOperations(): WorkspaceCacheOperations {
11
11
  return {
12
12
  status(directory, maxFiles, maxSymbolsPerFile) {
13
13
  return withWorkspace(
14
- () => workspaceForDirectory(directory),
14
+ () => workspaceForProjectDirectory(directory),
15
15
  async ({ workspaceId }) => {
16
16
  const client = await lectorClient();
17
17
  return client.call("workspace.cacheStatus", { workspaceId, maxFiles, maxSymbolsPerFile });
@@ -20,7 +20,7 @@ export function createWorkspaceCacheOperations(): WorkspaceCacheOperations {
20
20
  },
21
21
  submit(directory, maxFiles, maxSymbolsPerFile) {
22
22
  return withWorkspace(
23
- () => workspaceForDirectory(directory),
23
+ () => workspaceForProjectDirectory(directory),
24
24
  async ({ workspaceId }) => {
25
25
  const client = await lectorClient();
26
26
  const { job } = await client.callOnce("job.submit", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-lector",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "Pi host adapter for Lector: overrides read/write/edit with a daemon-backed, hash-guarded filesystem",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -23,6 +23,7 @@
23
23
  "malevich-tui-components": "^0.19.0"
24
24
  },
25
25
  "devDependencies": {
26
+ "@danypops/pi-extension-harness": "^0.2.0",
26
27
  "@danypops/pi-tui-harness": "^0.0.1",
27
28
  "@earendil-works/pi-ai": "^0.81.1",
28
29
  "@earendil-works/pi-coding-agent": "^0.81.1",