@benz-ai-x/dsh-md-preview 0.2.6 → 0.3.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/lib/index.js
CHANGED
|
@@ -146,24 +146,7 @@ let MdPreviewService = (() => {
|
|
|
146
146
|
* @throws RemoteError with a stable MdPreview failure code.
|
|
147
147
|
*/
|
|
148
148
|
async read(sessionId, path, signal) {
|
|
149
|
-
|
|
150
|
-
if (!isAllowedExtension(path, this.config.allowedExtensions)) throw failure("md-preview/unsupported-extension", `mdPreview/read refuses non-previewable path "${path}"`);
|
|
151
|
-
const session = this.ctx.sessions.get(sessionId);
|
|
152
|
-
if (session === void 0) throw failure("md-preview/unknown-session", `mdPreview/read cannot resolve session "${sessionId}"`);
|
|
153
|
-
const cwd = session.header.cwd;
|
|
154
|
-
if (cwd === void 0) throw failure("md-preview/no-workspace", `mdPreview/read session "${sessionId}" has no working directory`);
|
|
155
|
-
const root = await this.resolveWorkspacePath(cwd, signal);
|
|
156
|
-
let target;
|
|
157
|
-
try {
|
|
158
|
-
target = await this.resolveWorkspacePath(path, signal, cwd);
|
|
159
|
-
} catch (error) {
|
|
160
|
-
if (signal.aborted) throw error;
|
|
161
|
-
throw failure("md-preview/not-found", `mdPreview/read cannot resolve path "${path}"`);
|
|
162
|
-
}
|
|
163
|
-
if (!this.ctx.fs.contains(root, target)) throw failure("md-preview/forbidden", "mdPreview/read refuses paths outside the session workspace");
|
|
164
|
-
const info = await this.ctx.fs.stat(target, signal);
|
|
165
|
-
if (info === void 0) throw failure("md-preview/not-found", `mdPreview/read cannot find "${path}"`);
|
|
166
|
-
if (info.type !== "file") throw failure("md-preview/unsupported-extension", `mdPreview/read target "${path}" is not a regular file`);
|
|
149
|
+
const { target, info } = await this.resolveWorkspaceTarget(sessionId, path, signal, "read");
|
|
167
150
|
if (info.size !== void 0 && info.size > this.config.maxBytes) throw failure("md-preview/too-large", `mdPreview/read refuses "${path}" above the configured byte cap`);
|
|
168
151
|
let content;
|
|
169
152
|
try {
|
|
@@ -192,24 +175,7 @@ let MdPreviewService = (() => {
|
|
|
192
175
|
* @throws RemoteError with a stable MdPreview failure code.
|
|
193
176
|
*/
|
|
194
177
|
async write(sessionId, path, content, fingerprint, force, signal) {
|
|
195
|
-
|
|
196
|
-
if (!isAllowedExtension(path, this.config.allowedExtensions)) throw failure("md-preview/unsupported-extension", `mdPreview/write refuses non-previewable path "${path}"`);
|
|
197
|
-
const session = this.ctx.sessions.get(sessionId);
|
|
198
|
-
if (session === void 0) throw failure("md-preview/unknown-session", `mdPreview/write cannot resolve session "${sessionId}"`);
|
|
199
|
-
const cwd = session.header.cwd;
|
|
200
|
-
if (cwd === void 0) throw failure("md-preview/no-workspace", `mdPreview/write session "${sessionId}" has no working directory`);
|
|
201
|
-
const root = await this.resolveWorkspacePath(cwd, signal);
|
|
202
|
-
let target;
|
|
203
|
-
try {
|
|
204
|
-
target = await this.resolveWorkspacePath(path, signal, cwd);
|
|
205
|
-
} catch (error) {
|
|
206
|
-
if (signal.aborted) throw error;
|
|
207
|
-
throw failure("md-preview/not-found", `mdPreview/write cannot resolve path "${path}"`);
|
|
208
|
-
}
|
|
209
|
-
if (!this.ctx.fs.contains(root, target)) throw failure("md-preview/forbidden", "mdPreview/write refuses paths outside the session workspace");
|
|
210
|
-
const info = await this.ctx.fs.stat(target, signal);
|
|
211
|
-
if (info === void 0) throw failure("md-preview/not-found", `mdPreview/write cannot find "${path}"`);
|
|
212
|
-
if (info.type !== "file") throw failure("md-preview/unsupported-extension", `mdPreview/write target "${path}" is not a regular file`);
|
|
178
|
+
const { target, cwd } = await this.resolveWorkspaceTarget(sessionId, path, signal, "write");
|
|
213
179
|
if (content.length > this.config.maxBytes) throw failure("md-preview/too-large", `mdPreview/write refuses "${path}" above the configured byte cap`);
|
|
214
180
|
if (fingerprint === void 0 && !force) throw failure("md-preview/bad-request", "mdPreview/write requires a fingerprint or force");
|
|
215
181
|
const expected = fingerprint === void 0 ? void 0 : {
|
|
@@ -237,12 +203,45 @@ let MdPreviewService = (() => {
|
|
|
237
203
|
fingerprint: outcome.version
|
|
238
204
|
};
|
|
239
205
|
}
|
|
240
|
-
/**
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
206
|
+
/**
|
|
207
|
+
* The shared authority preamble of the remote methods: resolve a path to a
|
|
208
|
+
* live, contained, regular workspace file, or reject it with the stable
|
|
209
|
+
* failure code. One home for the check order and the aborted-rethrow
|
|
210
|
+
* convention; `op` only names the calling method in diagnostics.
|
|
211
|
+
* @param sessionId - owning session; its header cwd roots the resolution.
|
|
212
|
+
* @param path - path as it appeared in the conversation.
|
|
213
|
+
* @param signal - caller cancellation carried through every fs call.
|
|
214
|
+
* @param op - calling method name for diagnostic messages.
|
|
215
|
+
* @returns the resolved target, its stat info, and the workspace cwd.
|
|
216
|
+
* @throws RemoteError with a stable MdPreview failure code.
|
|
217
|
+
*/
|
|
218
|
+
async resolveWorkspaceTarget(sessionId, path, signal, op) {
|
|
219
|
+
if (path.trim().length === 0) throw failure("md-preview/bad-request", `mdPreview/${op} requires a non-empty path`);
|
|
220
|
+
if (!isAllowedExtension(path, this.config.allowedExtensions)) throw failure("md-preview/unsupported-extension", `mdPreview/${op} refuses non-previewable path "${path}"`);
|
|
221
|
+
const session = this.ctx.sessions.get(sessionId);
|
|
222
|
+
if (session === void 0) throw failure("md-preview/unknown-session", `mdPreview/${op} cannot resolve session "${sessionId}"`);
|
|
223
|
+
const cwd = session.header.cwd;
|
|
224
|
+
if (cwd === void 0) throw failure("md-preview/no-workspace", `mdPreview/${op} session "${sessionId}" has no working directory`);
|
|
225
|
+
const root = await this.ctx.fs.resolve(cwd, { signal });
|
|
226
|
+
let target;
|
|
227
|
+
try {
|
|
228
|
+
target = await this.ctx.fs.resolve(path, {
|
|
229
|
+
cwd,
|
|
230
|
+
signal
|
|
231
|
+
});
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (signal.aborted) throw error;
|
|
234
|
+
throw failure("md-preview/not-found", `mdPreview/${op} cannot resolve path "${path}"`);
|
|
235
|
+
}
|
|
236
|
+
if (!this.ctx.fs.contains(root, target)) throw failure("md-preview/forbidden", `mdPreview/${op} refuses paths outside the session workspace`);
|
|
237
|
+
const info = await this.ctx.fs.stat(target, signal);
|
|
238
|
+
if (info === void 0) throw failure("md-preview/not-found", `mdPreview/${op} cannot find "${path}"`);
|
|
239
|
+
if (info.type !== "file") throw failure("md-preview/unsupported-extension", `mdPreview/${op} target "${path}" is not a regular file`);
|
|
240
|
+
return {
|
|
241
|
+
target,
|
|
242
|
+
info,
|
|
243
|
+
cwd
|
|
244
|
+
};
|
|
246
245
|
}
|
|
247
246
|
};
|
|
248
247
|
})();
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
2
2
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
|
-
import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
|
|
4
3
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
5
4
|
import type { MdPreviewFile, MdPreviewWriteResult } from '../protocol.ts';
|
|
6
5
|
import type { MdPreviewState } from './preview-state.ts';
|
|
@@ -13,15 +12,15 @@ export interface PreviewOverlayInjected {
|
|
|
13
12
|
/** Dismiss the panel and drop the target. */
|
|
14
13
|
close(): void;
|
|
15
14
|
/** One bounded read; the transport carries the AbortSignal. */
|
|
16
|
-
read(sessionId: SessionId, path: string, signal: AbortSignal): Promise<RemoteResult<MdPreviewFile>>;
|
|
15
|
+
read(sessionId: SessionId, path: string, signal: AbortSignal): Promise<import('@deepseek-ai/dsh-typert-protocol').RemoteResult<MdPreviewFile>>;
|
|
17
16
|
/** One guarded write; `fingerprint` comes from the backing read. */
|
|
18
|
-
write(sessionId: SessionId, path: string, content: string, fingerprint: string | undefined, force: boolean, signal: AbortSignal): Promise<RemoteResult<MdPreviewWriteResult>>;
|
|
17
|
+
write(sessionId: SessionId, path: string, content: string, fingerprint: string | undefined, force: boolean, signal: AbortSignal): Promise<import('@deepseek-ai/dsh-typert-protocol').RemoteResult<MdPreviewWriteResult>>;
|
|
19
18
|
}
|
|
20
19
|
/** Full composed panel props. */
|
|
21
20
|
export type PreviewOverlayProps = PropsRuntime<'shell.overlay'> & InjectFace<PreviewOverlayInjected> & PropsLocale<'md-preview'>;
|
|
22
21
|
/**
|
|
23
22
|
* Render the preview panel for the current target.
|
|
24
|
-
* @param props - target hook, read
|
|
23
|
+
* @param props - target hook, read/write RPCs, dismissal, and the locale seat.
|
|
25
24
|
* @returns the docked panel, or null while closed.
|
|
26
25
|
*/
|
|
27
26
|
export declare function PreviewOverlay({ usePreviewTarget, close, read, write, t }: PreviewOverlayProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The PreviewSession machine: the pure state algebra of one preview target's
|
|
3
|
+
* lifecycle — content read, edit session, guarded save, prompts. The React
|
|
4
|
+
* adapter lives beside the panel (use-preview-session.ts); effects and RPC
|
|
5
|
+
* never enter here. `READ_STARTED` is the single reset point: a new read
|
|
6
|
+
* begins only when the previous document's whole session is over.
|
|
7
|
+
*/
|
|
8
|
+
import type { MdPreviewFile, MdPreviewWriteResult } from '../protocol.ts';
|
|
9
|
+
/** Content lifecycle of one preview target. */
|
|
10
|
+
export type PreviewSessionContent = {
|
|
11
|
+
readonly state: 'loading';
|
|
12
|
+
} | {
|
|
13
|
+
readonly state: 'ready';
|
|
14
|
+
readonly file: MdPreviewFile;
|
|
15
|
+
} | {
|
|
16
|
+
readonly state: 'failed';
|
|
17
|
+
readonly code: string;
|
|
18
|
+
readonly message: string;
|
|
19
|
+
};
|
|
20
|
+
/** Whole machine state for the panel's current preview target. */
|
|
21
|
+
export interface PreviewSessionState {
|
|
22
|
+
/** Read lifecycle of the current target. */
|
|
23
|
+
readonly content: PreviewSessionContent;
|
|
24
|
+
/** Panel face: rendering the document or editing a draft of it. */
|
|
25
|
+
readonly face: 'view' | 'edit';
|
|
26
|
+
/** The edit session's draft; meaningful only while `face` is edit. */
|
|
27
|
+
readonly draft: string;
|
|
28
|
+
/** A save is in flight. */
|
|
29
|
+
readonly saving: boolean;
|
|
30
|
+
/** The last save met a changed file; the conflict bar is up. */
|
|
31
|
+
readonly conflicted: boolean;
|
|
32
|
+
/** The last save failed for a non-conflict reason; the error bar is up. */
|
|
33
|
+
readonly saveError: {
|
|
34
|
+
readonly code: string;
|
|
35
|
+
readonly message: string;
|
|
36
|
+
} | null;
|
|
37
|
+
/** The unsaved guard is asking before a close. */
|
|
38
|
+
readonly unsavedPrompt: boolean;
|
|
39
|
+
/** The saved toast is showing. */
|
|
40
|
+
readonly toast: boolean;
|
|
41
|
+
/** The session asked the shell to close the panel; the adapter acts once. */
|
|
42
|
+
readonly closeRequested: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** One machine transition. */
|
|
45
|
+
export type PreviewSessionAction = {
|
|
46
|
+
readonly type: 'READ_STARTED';
|
|
47
|
+
} | {
|
|
48
|
+
readonly type: 'RETRY_READ';
|
|
49
|
+
} | {
|
|
50
|
+
readonly type: 'READ_RESOLVED';
|
|
51
|
+
readonly file: MdPreviewFile;
|
|
52
|
+
} | {
|
|
53
|
+
readonly type: 'READ_FAILED';
|
|
54
|
+
readonly code: string;
|
|
55
|
+
readonly message: string;
|
|
56
|
+
} | {
|
|
57
|
+
readonly type: 'ENTER_EDIT';
|
|
58
|
+
} | {
|
|
59
|
+
readonly type: 'EDIT';
|
|
60
|
+
readonly draft: string;
|
|
61
|
+
} | {
|
|
62
|
+
readonly type: 'SAVE_STARTED';
|
|
63
|
+
} | {
|
|
64
|
+
readonly type: 'SAVE_RESOLVED';
|
|
65
|
+
readonly result: MdPreviewWriteResult;
|
|
66
|
+
} | {
|
|
67
|
+
readonly type: 'SAVE_CONFLICT';
|
|
68
|
+
} | {
|
|
69
|
+
readonly type: 'SAVE_FAILED';
|
|
70
|
+
readonly code: string;
|
|
71
|
+
readonly message: string;
|
|
72
|
+
} | {
|
|
73
|
+
readonly type: 'CANCEL_EDIT';
|
|
74
|
+
} | {
|
|
75
|
+
readonly type: 'REQUEST_CLOSE';
|
|
76
|
+
} | {
|
|
77
|
+
readonly type: 'DISCARD';
|
|
78
|
+
} | {
|
|
79
|
+
readonly type: 'KEEP_EDITING';
|
|
80
|
+
} | {
|
|
81
|
+
readonly type: 'TOAST_EXPIRED';
|
|
82
|
+
};
|
|
83
|
+
/** The pristine state every session starts (and resets) from. */
|
|
84
|
+
export declare function initialPreviewSession(): PreviewSessionState;
|
|
85
|
+
/** Whether the draft differs from the loaded document. */
|
|
86
|
+
export declare function isDirty(state: PreviewSessionState): boolean;
|
|
87
|
+
/** The save-enable policy: something to save (dirty or conflicted), no save in flight. */
|
|
88
|
+
export declare function canSave(state: PreviewSessionState): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Apply one transition.
|
|
91
|
+
* @param state - current machine state.
|
|
92
|
+
* @param action - the transition.
|
|
93
|
+
* @returns the next state (transitions are total; guards decline no-ops).
|
|
94
|
+
*/
|
|
95
|
+
export declare function transition(state: PreviewSessionState, action: PreviewSessionAction): PreviewSessionState;
|
|
96
|
+
//# sourceMappingURL=preview-session.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
|
|
2
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
3
|
+
import type { MdPreviewFile, MdPreviewWriteResult } from '../protocol.ts';
|
|
4
|
+
import type { MdPreviewTarget } from './preview-state.ts';
|
|
5
|
+
import { initialPreviewSession } from './preview-session.ts';
|
|
6
|
+
/** The adapter's inputs: the RPCs and dismissal from the plugin's apply world. */
|
|
7
|
+
export interface PanelDocumentSessionDeps {
|
|
8
|
+
readonly read: (sessionId: SessionId, path: string, signal: AbortSignal) => Promise<RemoteResult<MdPreviewFile>>;
|
|
9
|
+
readonly write: (sessionId: SessionId, path: string, content: string, fingerprint: string | undefined, force: boolean, signal: AbortSignal) => Promise<RemoteResult<MdPreviewWriteResult>>;
|
|
10
|
+
readonly close: () => void;
|
|
11
|
+
}
|
|
12
|
+
/** The panel-facing surface: machine state plus intent actions. */
|
|
13
|
+
export interface PanelDocumentSession {
|
|
14
|
+
readonly state: ReturnType<typeof initialPreviewSession>;
|
|
15
|
+
readonly canSave: boolean;
|
|
16
|
+
readonly dirty: boolean;
|
|
17
|
+
readonly actions: {
|
|
18
|
+
enterEdit(): void;
|
|
19
|
+
edit(draft: string): void;
|
|
20
|
+
save(force: boolean): void;
|
|
21
|
+
cancelEdit(): void;
|
|
22
|
+
reload(): void;
|
|
23
|
+
retryRead(): void;
|
|
24
|
+
requestClose(): void;
|
|
25
|
+
discard(): void;
|
|
26
|
+
keepEditing(): void;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Run one PreviewSession for the current preview target.
|
|
31
|
+
* @param deps - read/write RPCs and the panel dismissal.
|
|
32
|
+
* @param target - the current preview target; null while the panel is closed.
|
|
33
|
+
* @returns machine state with derived flags and intent actions.
|
|
34
|
+
*/
|
|
35
|
+
export declare function usePanelDocumentSession(deps: PanelDocumentSessionDeps, target: MdPreviewTarget | null): PanelDocumentSession;
|
|
36
|
+
//# sourceMappingURL=use-preview-session.d.ts.map
|
package/lib/types/remote.d.ts
CHANGED
|
@@ -50,7 +50,18 @@ export declare class MdPreviewService extends TypertRemoteService {
|
|
|
50
50
|
* @throws RemoteError with a stable MdPreview failure code.
|
|
51
51
|
*/
|
|
52
52
|
write(sessionId: SessionId, path: string, content: string, fingerprint: string | undefined, force: boolean, signal: AbortSignal): Promise<MdPreviewWriteResult>;
|
|
53
|
-
/**
|
|
54
|
-
|
|
53
|
+
/**
|
|
54
|
+
* The shared authority preamble of the remote methods: resolve a path to a
|
|
55
|
+
* live, contained, regular workspace file, or reject it with the stable
|
|
56
|
+
* failure code. One home for the check order and the aborted-rethrow
|
|
57
|
+
* convention; `op` only names the calling method in diagnostics.
|
|
58
|
+
* @param sessionId - owning session; its header cwd roots the resolution.
|
|
59
|
+
* @param path - path as it appeared in the conversation.
|
|
60
|
+
* @param signal - caller cancellation carried through every fs call.
|
|
61
|
+
* @param op - calling method name for diagnostic messages.
|
|
62
|
+
* @returns the resolved target, its stat info, and the workspace cwd.
|
|
63
|
+
* @throws RemoteError with a stable MdPreview failure code.
|
|
64
|
+
*/
|
|
65
|
+
private resolveWorkspaceTarget;
|
|
55
66
|
}
|
|
56
67
|
//# sourceMappingURL=remote.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@benz-ai-x/dsh-md-preview",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "DSH markdown preview: clicking a markdown document in the conversation opens a rendered preview panel docked to the right of the chat.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|