dsh-diff-approval 0.29.1 → 0.31.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/README.md +7 -4
- package/README.zh.md +7 -4
- package/assets/fonts/README.md +10 -8
- package/assets/fonts/manifest.json +1 -1
- package/lib/client.js +5161 -1972
- package/lib/index.js +4331 -442
- package/lib/types/annotate-skill.d.ts +34 -0
- package/lib/types/annotate-tool.d.ts +227 -0
- package/lib/types/annotate.d.ts +186 -0
- package/lib/types/atomic-write.d.ts +77 -0
- package/lib/types/client/PendingPanel.d.ts +92 -24
- package/lib/types/client/category-color.d.ts +62 -0
- package/lib/types/client/chords.d.ts +70 -17
- package/lib/types/client/discussion.d.ts +76 -16
- package/lib/types/client/dock.d.ts +8 -8
- package/lib/types/client/dsh-icons.d.ts +98 -0
- package/lib/types/client/header-entry.d.ts +27 -11
- package/lib/types/client/ids.d.ts +18 -0
- package/lib/types/client/locales.d.ts +88 -36
- package/lib/types/client/panel-memory.d.ts +118 -35
- package/lib/types/client/path-match.d.ts +50 -0
- package/lib/types/client/port.d.ts +77 -1
- package/lib/types/client/presentation-menu.d.ts +1 -1
- package/lib/types/client/produced-diff.d.ts +136 -22
- package/lib/types/client/reference.d.ts +0 -2
- package/lib/types/client/remap-sync.d.ts +18 -1
- package/lib/types/client/session-seat.d.ts +93 -0
- package/lib/types/client/settings.d.ts +40 -14
- package/lib/types/client/slots.d.ts +184 -12
- package/lib/types/client/store.d.ts +85 -2
- package/lib/types/comment-ask.d.ts +236 -0
- package/lib/types/comment-lines.d.ts +131 -0
- package/lib/types/comments.d.ts +465 -0
- package/lib/types/index.d.ts +7 -2
- package/lib/types/pending.d.ts +53 -4
- package/lib/types/types.d.ts +356 -4
- package/lib/types/vcs.d.ts +53 -26
- package/lib/types/version.d.ts +162 -0
- package/package.json +7 -2
- package/lib/types/client/chat-bridge.d.ts +0 -70
|
@@ -5,13 +5,46 @@
|
|
|
5
5
|
* @module dsh-diff-approval/client/port
|
|
6
6
|
*/
|
|
7
7
|
import type { ClientConnectionRpc, SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
8
|
-
import type { DiffApprovalActionValue, DiffApprovalAddValue, DiffApprovalBlockRange, DiffApprovalBrowseValue, DiffApprovalBulkValue, DiffApprovalListValue, DiffApprovalOpenAction, DiffApprovalOpenValue, DiffApprovalPreviewImageValue, DiffApprovalRefreshValue, VcsImportValue } from '../types.ts';
|
|
8
|
+
import type { CommentAnchor, CommentQuoteLine, DiffApprovalActionValue, DiffApprovalAddValue, DiffApprovalBlockRange, DiffApprovalBrowseValue, DiffApprovalBulkValue, DiffApprovalCommentAddValue, DiffApprovalCommentAskValue, DiffApprovalCommentRemoveManyValue, DiffApprovalCommentRemoveValue, DiffApprovalListCountValue, DiffApprovalListValue, DiffApprovalOpenAction, DiffApprovalOpenValue, DiffApprovalPreviewImageValue, DiffApprovalRefreshValue, DiffApprovalUpdateValue, VcsImportValue } from '../types.ts';
|
|
9
9
|
/** The channel the host half registers and this port calls. */
|
|
10
10
|
export declare const DIFF_APPROVAL_CHANNEL = "/diff-approval";
|
|
11
|
+
/**
|
|
12
|
+
* One annotation as the panel hands it over: what the reader wrote and where they
|
|
13
|
+
* wrote it. The display path is not part of it — the entry a comment hangs off is the
|
|
14
|
+
* host's authority on what the file is called.
|
|
15
|
+
*/
|
|
16
|
+
export interface CommentDraft {
|
|
17
|
+
/** Caller-minted id, so a retried write lands on the same comment. */
|
|
18
|
+
id: string;
|
|
19
|
+
/** The listed entry the annotation hangs off. */
|
|
20
|
+
entryId: string;
|
|
21
|
+
/** The lines it was made on, as new-file line numbers. */
|
|
22
|
+
anchor: CommentAnchor;
|
|
23
|
+
/** The anchored lines as they read then (the re-anchor fingerprint). */
|
|
24
|
+
quote: string;
|
|
25
|
+
/** `quote` with one line of context on each side, as it read then. */
|
|
26
|
+
quoteContext?: string | undefined;
|
|
27
|
+
/** The gutter numbers of `quote`'s lines, in the same order. */
|
|
28
|
+
quoteLines?: CommentQuoteLine[] | undefined;
|
|
29
|
+
/** What the reader wrote. */
|
|
30
|
+
text: string;
|
|
31
|
+
}
|
|
11
32
|
/** This package's business verbs over the review channel. */
|
|
12
33
|
export interface DiffApprovalPort {
|
|
13
34
|
/** Read one session's pending entries (plus its workspace root), oldest capture first. */
|
|
14
35
|
list(sessionId: SessionId): Promise<DiffApprovalListValue>;
|
|
36
|
+
/**
|
|
37
|
+
* Ask HOW MANY rows a full `list` read would carry for this session, reading no file content.
|
|
38
|
+
*
|
|
39
|
+
* This is the badge's own question: the full read ships every visible entry's whole old/new text
|
|
40
|
+
* (megabytes on a long-lived session), while the count costs one stat per visible row on the host and
|
|
41
|
+
* puts a single number on the wire. It is the SAME visibility rule as `list` on the host's side.
|
|
42
|
+
*
|
|
43
|
+
* A host that predates the endpoint throws instead — its handler answers `unknown endpoint
|
|
44
|
+
* "list-count"` — and the caller is expected to fall back to the full read (see
|
|
45
|
+
* `PendingDiffStore.refreshCount`), which is exactly the behaviour a page had before this verb existed.
|
|
46
|
+
*/
|
|
47
|
+
listCount(sessionId: SessionId): Promise<DiffApprovalListCountValue>;
|
|
15
48
|
/** Keep one operation. `keepListed` leaves the resolved entry in the list. */
|
|
16
49
|
keep(sessionId: SessionId, id: string, keepListed?: boolean): Promise<DiffApprovalActionValue>;
|
|
17
50
|
/** Revert one operation. `keepListed` leaves the resolved entry in the list. */
|
|
@@ -35,14 +68,57 @@ export interface DiffApprovalPort {
|
|
|
35
68
|
* path must be one regular file: a directory is refused instead of being scanned.
|
|
36
69
|
*/
|
|
37
70
|
addPath(sessionId: SessionId, path: string, includeUnchanged: boolean, exact?: boolean): Promise<DiffApprovalAddValue>;
|
|
71
|
+
/** Tell the host the reader is looking at a file: the row's unseen dot goes out. */
|
|
72
|
+
markSeen(sessionId: SessionId, id: string): Promise<void>;
|
|
38
73
|
/** Open one file with its default application or reveal it in the folder. */
|
|
39
74
|
open(sessionId: SessionId, id: string, action: DiffApprovalOpenAction): Promise<DiffApprovalOpenValue>;
|
|
40
75
|
/** Keep every pending entry of one session in a single host call (one batch). */
|
|
41
76
|
keepAll(sessionId: SessionId): Promise<DiffApprovalBulkValue>;
|
|
42
77
|
/** Revert every pending entry of one session in a single host call (one batch). */
|
|
43
78
|
revertAll(sessionId: SessionId): Promise<DiffApprovalBulkValue>;
|
|
79
|
+
/**
|
|
80
|
+
* Keep a PICK of entries in one host call: one request, one durable write and one undo step — the
|
|
81
|
+
* host records the whole pick as a single batch, so one Ctrl+Z takes that one decision back instead
|
|
82
|
+
* of peeling it apart file by file. `keepListed` says whether the resolved files stay listed.
|
|
83
|
+
*
|
|
84
|
+
* There is a `revertMany` beside it, and the pair is NOT symmetric: a keep writes nothing, while a
|
|
85
|
+
* revert writes the baseline over each file — and DELETES the ones the agent created, which the host
|
|
86
|
+
* cannot undo. The panel asks before sending a pick whose revert would delete anything.
|
|
87
|
+
*/
|
|
88
|
+
keepMany(sessionId: SessionId, ids: readonly string[], keepListed: boolean | undefined): Promise<DiffApprovalBulkValue>;
|
|
89
|
+
/** Revert a PICK of entries in one host call: the same one request, one write, one undo step. The undo
|
|
90
|
+
* restores the entries the host could snapshot — see `keepMany` for the deletes it cannot. */
|
|
91
|
+
revertMany(sessionId: SessionId, ids: readonly string[], keepListed: boolean | undefined): Promise<DiffApprovalBulkValue>;
|
|
44
92
|
/** Read one workspace image and inline it as a base64 data URI (for the Markdown preview). */
|
|
45
93
|
previewImage(sessionId: SessionId, path: string): Promise<DiffApprovalPreviewImageValue>;
|
|
94
|
+
/**
|
|
95
|
+
* Write one annotation down. The host refuses a comment whose entry is not in the
|
|
96
|
+
* session's list, and the refusal is reported rather than retried.
|
|
97
|
+
*/
|
|
98
|
+
commentAdd(sessionId: SessionId, comment: CommentDraft): Promise<DiffApprovalCommentAddValue>;
|
|
99
|
+
/** Drop one annotation (what "a comment dies with its entry" does by hand). */
|
|
100
|
+
commentRemove(sessionId: SessionId, id: string): Promise<DiffApprovalCommentRemoveValue>;
|
|
101
|
+
/**
|
|
102
|
+
* Drop several annotations in one host call: one request, one write of the session's comment file,
|
|
103
|
+
* and one read afterwards (`store.commentRemoveMany`). A batch that is a loop of `commentRemove`
|
|
104
|
+
* calls is a different thing — N requests, N writes — which is what this endpoint exists to avoid.
|
|
105
|
+
*/
|
|
106
|
+
commentRemoveMany(sessionId: SessionId, ids: readonly string[]): Promise<DiffApprovalCommentRemoveManyValue>;
|
|
107
|
+
/** Asks one stored comment as its own turn of the session. */
|
|
108
|
+
commentAsk(sessionId: SessionId, id: string, prompt: string, text: string): Promise<DiffApprovalCommentAskValue>;
|
|
109
|
+
/** Tell the host the reader is looking at a comment: the card's unseen dot goes out. */
|
|
110
|
+
commentSeen(sessionId: SessionId, id: string): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Ask whether a newer release of this plugin is published, and what changed since the
|
|
113
|
+
* installed one.
|
|
114
|
+
*
|
|
115
|
+
* No session is named: the answer is about the PACKAGE, not about a review. The host owns
|
|
116
|
+
* the whole question — it reads its own `package.json`, reaches the registry (a browser
|
|
117
|
+
* may not: no CORS header there) and decides `newer` — so a caller only has to render the
|
|
118
|
+
* answer. A host that predates the endpoint answers `unknown endpoint "update-check"`, and
|
|
119
|
+
* the caller is expected to ignore that failure exactly as it ignores a network one.
|
|
120
|
+
*/
|
|
121
|
+
checkUpdate(): Promise<DiffApprovalUpdateValue>;
|
|
46
122
|
}
|
|
47
123
|
/** Build the port over one generic RPC caller.
|
|
48
124
|
* @param rpc - the connection's channel caller.
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* @module dsh-diff-approval/client/presentation-menu
|
|
14
14
|
*/
|
|
15
15
|
import type { ReactNode } from 'react';
|
|
16
|
-
import type { MenuEntry } from '
|
|
16
|
+
import type { MenuEntry } from './dsh-icons.ts';
|
|
17
17
|
import type { DiffApprovalPresentation } from './settings.ts';
|
|
18
18
|
import type { Translator } from './locales.ts';
|
|
19
19
|
/**
|
|
@@ -1,27 +1,141 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* The harness
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
2
|
+
* Route a file press's own click.
|
|
3
|
+
*
|
|
4
|
+
* The harness draws several presses that answer by opening a file in a viewer of its OWN, and this
|
|
5
|
+
* module takes over exactly those — and only for a file the review panel is already holding — so the
|
|
6
|
+
* reader gets both ways of opening it instead of one fixed behaviour:
|
|
7
|
+
*
|
|
8
|
+
* `[data-produced-files-row] button` 0.1.5's produced-file card `title` = the path
|
|
9
|
+
* `[data-presented-files-row] button` 0.1.7's presented-file card `title` = the path
|
|
10
|
+
* `[data-changed-files] button` 0.1.7's changed-files row `aria-describedby` = the path
|
|
11
|
+
* `button[title]` a message's file LINK (0.1.7) `title` = the path
|
|
12
|
+
* `button[data-ref-chip]` a `@file` chip in a message `title` = the token
|
|
13
|
+
* `a[href]` any other link naming a file `href` = the path
|
|
14
|
+
*
|
|
15
|
+
* Everything else is left entirely alone: a link to a URL, an in-page anchor, a chip naming something
|
|
16
|
+
* that is not a file, a file LINK whose file this panel does not hold, a modified press (Ctrl/Cmd/Shift/
|
|
17
|
+
* Alt, or a middle click — gestures the harness may give its own meaning later), and any press inside this
|
|
18
|
+
* plugin's own surface, whose buttons carry paths too and belong to the panel that is already showing
|
|
19
|
+
* the file. A file-list ROW is the one press taken over whether or not the panel holds its file (see
|
|
20
|
+
* `ROW_SELECTOR`); "we do not hold it" then decides what the menu offers, not whether it appears.
|
|
21
|
+
*
|
|
22
|
+
* The name of the module (and of the event it raises) dates from when the produced-file card was the
|
|
23
|
+
* only press of this kind; `data-presented-files-row` is that same list under 0.1.7's name for it.
|
|
16
24
|
*/
|
|
17
|
-
|
|
25
|
+
import type { PendingDiffSnapshot } from './slots.ts';
|
|
26
|
+
/** Window event dispatched when a pending file's press is taken over; the panel listens for it. */
|
|
27
|
+
export declare const CHIP_MENU_EVENT = "diff-approval:chip-menu";
|
|
28
|
+
/**
|
|
29
|
+
* Whether the page HOLDS this path — the `held` label a press carries into the menu.
|
|
30
|
+
*
|
|
31
|
+
* It is a LABEL now, not a gate: every file-list row's press becomes the menu regardless, and `held` only
|
|
32
|
+
* decides whether "在审批面板中查看" opens the row directly or adds it first (see `PendingPanel`). It is
|
|
33
|
+
* answered from the views the page is holding, i.e. from the last FULL read of each session: while every
|
|
34
|
+
* surface is shut nothing re-reads the list, so a row edited since then is labelled not-held. That is
|
|
35
|
+
* acceptable rather than hidden: the add answers the truth (a path already listed comes back `duplicate` and
|
|
36
|
+
* selects the same entry), so the item still opens the file — one request later. What the label must not do
|
|
37
|
+
* is claim a row exists that does not: `false` errs towards adding, never towards acting on nothing.
|
|
38
|
+
* @param views - the views this page is holding, one per session it has read (see `PendingDiffStore.views`).
|
|
39
|
+
* @param path - the path a press named.
|
|
40
|
+
* @returns whether any held view holds it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function panelHolds(views: readonly PendingDiffSnapshot[], path: string): boolean;
|
|
43
|
+
/** Window event dispatched by the panel to open a file in the panel; PendingPanel listens for it. */
|
|
18
44
|
export declare const OPEN_FILE_EVENT = "diff-approval:open-file";
|
|
45
|
+
/** What a press tells the panel: which file, where the press was (the menu hangs under it), and whether
|
|
46
|
+
* the review list already holds that file. */
|
|
47
|
+
export interface ProducedChipMenuDetail {
|
|
48
|
+
/** The path the pressed element names. */
|
|
49
|
+
path: string;
|
|
50
|
+
/** The pressed element's own box, in viewport coordinates. */
|
|
51
|
+
x: number;
|
|
52
|
+
y: number;
|
|
53
|
+
/**
|
|
54
|
+
* Whether the review list holds this path right now.
|
|
55
|
+
*
|
|
56
|
+
* It decides what the menu's items DO, never whether the menu appears: a row press always becomes the
|
|
57
|
+
* menu, and "not held" is what makes "在审批面板中查看" add the path first (see `PendingPanel`). It is
|
|
58
|
+
* also what a future item that NEEDS a listed entry has to gate on — pressing keep or revert for a file
|
|
59
|
+
* the host is not holding would simply fail.
|
|
60
|
+
*/
|
|
61
|
+
held: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* The session the press belongs to, when its own surface says so.
|
|
64
|
+
*
|
|
65
|
+
* WITHOUT THIS the panel can only ask the session IT is showing to add the path, and the host resolves that
|
|
66
|
+
* path against that session's workspace: a press in session A's transcript while the panel is still bound
|
|
67
|
+
* to session B answers `outside` ("路径不在当前工作区内") — measured, and it was the reader's bug: the first
|
|
68
|
+
* press refused and the second worked, because by then the panel had followed the shell's selection.
|
|
69
|
+
*
|
|
70
|
+
* Read from the ANCESTORS of the pressed element only (see `sessionOfPress`), and `undefined` when none of
|
|
71
|
+
* them names a session. That is the honest half: the panel then asks its own session and, if the host
|
|
72
|
+
* refuses, says so with the wording that names what was checked rather than blaming the path.
|
|
73
|
+
*/
|
|
74
|
+
sessionId?: string | undefined;
|
|
75
|
+
}
|
|
76
|
+
/** What the bridge needs from the host to decide, and to report. */
|
|
77
|
+
export interface ProducedChipBridge {
|
|
78
|
+
/**
|
|
79
|
+
* Whether the review list holds this path right now.
|
|
80
|
+
*
|
|
81
|
+
* A LABEL, not a gate: it rides every press this bridge takes over (a file-list row and a message's file
|
|
82
|
+
* link alike) and decides only what the menu's items DO — see `held` above. It is called synchronously on
|
|
83
|
+
* every press because that is when the menu is raised, and there is no second chance at a press.
|
|
84
|
+
*/
|
|
85
|
+
isPending: (path: string) => boolean;
|
|
86
|
+
/** A pending file's press was taken over: open the menu the press asked for. */
|
|
87
|
+
onMenu: (detail: ProducedChipMenuDetail) => void;
|
|
88
|
+
/**
|
|
89
|
+
* Whether this plugin's file menu is MOUNTED at all — the reader's setting, read at press time.
|
|
90
|
+
*
|
|
91
|
+
* `false` leaves every press exactly as DSH has it: no menu, no `preventDefault`, not even a path lookup,
|
|
92
|
+
* so the plugin's presence is indistinguishable from it never having loaded. It is read per press rather
|
|
93
|
+
* than latched, because the setting can be switched while the page is open.
|
|
94
|
+
*
|
|
95
|
+
* ABSENT means enabled: a host, or a test, that offers no setting keeps the behaviour this bridge has
|
|
96
|
+
* always had.
|
|
97
|
+
*/
|
|
98
|
+
enabled?: (() => boolean) | undefined;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Press a file's own press again — the shell's open, run the only way this plugin can run it
|
|
102
|
+
* faithfully, with the bridge standing down for that press (see the file's own doc).
|
|
103
|
+
*
|
|
104
|
+
* The menu's first row must open the file exactly the way the shell does, and what that is is the
|
|
105
|
+
* shell's business, not this plugin's: pressing the same element again keeps every part of it —
|
|
106
|
+
* the viewer it opens, the line a `#L24` suffix asked for, whatever a future shell adds there.
|
|
107
|
+
*
|
|
108
|
+
* @param path - the file the press names.
|
|
109
|
+
* @returns whether an element naming it was found to press.
|
|
110
|
+
*/
|
|
111
|
+
export declare function replayFilePress(path: string): boolean;
|
|
19
112
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
113
|
+
* The session a press belongs to, read from the pressed element's OWN surface.
|
|
114
|
+
*
|
|
115
|
+
* Ancestors only, and the NEAREST ancestor that names a session wins — whichever spelling it uses:
|
|
116
|
+
*
|
|
117
|
+
* `data-row-key="session:<id>"` a session-LIST item, measured by this repo's e2e harness. Right for a
|
|
118
|
+
* press raised inside the sidebar, and NOT an ancestor of a transcript
|
|
119
|
+
* chip (measured on the live shell: exactly one such element in the whole
|
|
120
|
+
* document, and it does not contain the chip).
|
|
121
|
+
* `data-conversation-session` the conversation BODY, measured on the live shell — this is the one that
|
|
122
|
+
* covers the reader's press, a chip inside a message.
|
|
123
|
+
* `data-session-id` unmeasured alternative spelling, kept as a last resort.
|
|
124
|
+
*
|
|
125
|
+
* Walking the chain rather than asking three separate `closest` queries is what makes "nearest wins" true
|
|
126
|
+
* across spellings: a chip inside a session row that ALSO nests a conversation body takes the nearer
|
|
127
|
+
* body's session. A document-wide search is still deliberately NOT done — an identity hit may be a SIBLING,
|
|
128
|
+
* an `href` or an `aria-controls`, and guessing which session a press belongs to would be worse than not
|
|
129
|
+
* knowing: a wrong guess would ask a THIRD session to add the file, which this can never do.
|
|
130
|
+
*
|
|
131
|
+
* @param press - the element the press landed on.
|
|
132
|
+
* @returns the session id, or undefined when no ancestor names one.
|
|
133
|
+
*/
|
|
134
|
+
export declare function sessionOfPress(press: Element): string | undefined;
|
|
135
|
+
/**
|
|
136
|
+
* Start routing the presses that would open a file in the shell's own viewer.
|
|
137
|
+
*
|
|
138
|
+
* @param bridge - the host's decision (is the file pending) and where to report a press that is.
|
|
139
|
+
* @returns a cleanup that stops routing.
|
|
26
140
|
*/
|
|
27
|
-
export declare function
|
|
141
|
+
export declare function startProducedChipMenu(bridge: ProducedChipBridge): () => void;
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
* the panel.
|
|
6
6
|
* @module dsh-diff-approval/client/reference
|
|
7
7
|
*/
|
|
8
|
-
/** The marker that replaces a reference's line number when its lines are gone. */
|
|
9
|
-
export declare const LINE_MISSING_LABEL = "LINE_MISSING";
|
|
10
8
|
/**
|
|
11
9
|
* Last path segment of a file path, any separator style.
|
|
12
10
|
* @param path - the path to shorten.
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* `reference.ts`.
|
|
7
7
|
* @module dsh-diff-approval/client/remap-sync
|
|
8
8
|
*/
|
|
9
|
+
import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
9
10
|
import type { PendingDiffStore } from './store.ts';
|
|
10
11
|
/** One queued message's minimal content block; text blocks carry `text`, and every other kind passes through verbatim. */
|
|
11
12
|
interface QueueBlock {
|
|
@@ -28,6 +29,22 @@ export interface ReferenceRemapOpts {
|
|
|
28
29
|
readQueue: () => readonly QueueMessage[];
|
|
29
30
|
/** Apply an edit mutation to one queued message's full content. */
|
|
30
31
|
writeQueue: (itemId: string, content: readonly QueueBlock[]) => void;
|
|
32
|
+
/**
|
|
33
|
+
* The session whose list this sync follows and whose view supplies the workspace path, read per
|
|
34
|
+
* observation.
|
|
35
|
+
*
|
|
36
|
+
* The pending list is per session now, and the draft and the queue this sync rewrites belong to the
|
|
37
|
+
* session the page is SHOWING (see `shownSessionId`): observing another session's files would remap one
|
|
38
|
+
* session's references inside another session's composer. It must not be a pointer to whichever mount
|
|
39
|
+
* refreshed last either — with a dock tab on one session and the header on another, alternating polls
|
|
40
|
+
* moved that pointer about once a second and dropped these baselines with it, so a file that really
|
|
41
|
+
* changed was only ever re-seeded and never remapped. The baselines are still dropped when this answers
|
|
42
|
+
* a DIFFERENT session, so a session switch seeds fresh baselines instead of reading as "file changed".
|
|
43
|
+
*
|
|
44
|
+
* Absent, the page-wide view is observed instead — the same fallback a reader with no session gets
|
|
45
|
+
* (`viewFor(undefined)`, which answers the newest read).
|
|
46
|
+
*/
|
|
47
|
+
sessionId?: (() => SessionId | undefined) | undefined;
|
|
31
48
|
}
|
|
32
49
|
/**
|
|
33
50
|
* Subscribe to the pending store and remap references as each file's content
|
|
@@ -39,6 +56,6 @@ export interface ReferenceRemapOpts {
|
|
|
39
56
|
*/
|
|
40
57
|
export declare function attachReferenceRemap(opts: ReferenceRemapOpts): {
|
|
41
58
|
unsubscribe: () => void;
|
|
42
|
-
remapFile: (path: string, oldText: string, newText: string) => void;
|
|
59
|
+
remapFile: (sessionId: SessionId, path: string, oldText: string, newText: string) => void;
|
|
43
60
|
};
|
|
44
61
|
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which session the review is about, as every shell of this plugin has offered it.
|
|
3
|
+
*
|
|
4
|
+
* 0.1.7 changed how a client learns the selected session. Its session-list store carries
|
|
5
|
+
* `{ ids, byId, phase, projectionsBySession }` — there is no `current` any more — and the shell's own
|
|
6
|
+
* components take the id from their composed SLOT PROPS instead (`const { sessionId, useSessions } =
|
|
7
|
+
* props`, then `useSessions(s => s.byId[sessionId]?.blank)`; measured in `dsh-client-ui-conversation`
|
|
8
|
+
* and `dsh-client-ui-agent-preset`). Older shells put it in the store as `state.current`.
|
|
9
|
+
*
|
|
10
|
+
* The seats differ, and that is the whole reason this module exists: the Session HEADER seat is
|
|
11
|
+
* session-scoped and is handed `sessionId`; the sidebar FOOTER seat — where this panel's own badge
|
|
12
|
+
* lives — is rendered `renderSlot('sidebar.footer.action', { wide })` and is handed nothing else. So
|
|
13
|
+
* the header publishes what it is showing, the footer reads it, and the store is the third fallback
|
|
14
|
+
* (`current` on the shells that have it). Every reader here is total: an unknown shape answers
|
|
15
|
+
* "nothing known" rather than throwing, and no field that is absent is taken for a value.
|
|
16
|
+
*
|
|
17
|
+
* @module dsh-diff-approval/client/session-seat
|
|
18
|
+
*/
|
|
19
|
+
import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
20
|
+
/**
|
|
21
|
+
* Publish the session the header entry is showing. Call it from an EFFECT, never during render.
|
|
22
|
+
*
|
|
23
|
+
* Writing this during the header's render was the cross-talk's other half: the value a render read
|
|
24
|
+
* depended on whether another component had already rendered, so two mounts could disagree about which
|
|
25
|
+
* session was showing — and, in concurrent rendering, a discarded render could publish a session that
|
|
26
|
+
* was never committed. From an effect the value only ever describes a committed render.
|
|
27
|
+
* @param id - the id, or `undefined` when the shell named none.
|
|
28
|
+
*/
|
|
29
|
+
export declare function publishSessionId(id: SessionId | undefined): void;
|
|
30
|
+
/** The session the header entry last showed, or `undefined` when it never named one. */
|
|
31
|
+
export declare function publishedSessionId(): SessionId | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The published session as a React value: the root-scoped mounts (the sidebar footer's badge) have no
|
|
34
|
+
* session prop, so this is how they follow the one the Session header is showing.
|
|
35
|
+
*
|
|
36
|
+
* It subscribes rather than reading the module global at render time, because the write now happens in
|
|
37
|
+
* an effect: a reader that only read the global would keep whatever the value was on its first render
|
|
38
|
+
* and never notice the reader switching sessions.
|
|
39
|
+
* @returns the session the header entry is showing, or `undefined`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function usePublishedSessionId(): SessionId | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* The selected session a shell's session-list state names, by every field the shells have used.
|
|
44
|
+
* @param state - the store state handed to the selector.
|
|
45
|
+
* @returns the id, or `undefined` when this shape names none.
|
|
46
|
+
*/
|
|
47
|
+
export declare function selectedSessionOf(state: unknown): SessionId | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Whether one session row is BLANK — a freshly created session with nothing in it yet, which has
|
|
50
|
+
* nothing to review and leaves this plugin's entry inert exactly like no session at all.
|
|
51
|
+
*
|
|
52
|
+
* `byId[id].blank` is the row's own flag and has kept its name across the shells; a store that
|
|
53
|
+
* carries `blank` on the STATE (with the id beside it) is read too. An absent flag is not blank: a
|
|
54
|
+
* shape this plugin does not recognise must not disable the panel.
|
|
55
|
+
*
|
|
56
|
+
* @param state - the store state handed to the selector.
|
|
57
|
+
* @param id - the session the answer is about; `undefined` answers false.
|
|
58
|
+
* @returns true only when the shell says that session is blank.
|
|
59
|
+
*/
|
|
60
|
+
export declare function sessionIsBlank(state: unknown, id: SessionId | undefined): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Whether a seat has NO reviewable session: it cannot name one at all, or the shell says the one it names
|
|
63
|
+
* is blank (a brand-new session with nothing said in it yet).
|
|
64
|
+
*
|
|
65
|
+
* The two halves are ONE question and must be asked of the SAME id — the id the seat would read its view
|
|
66
|
+
* for. Splitting them is what this helper exists to stop: a seat that drew its view for the published
|
|
67
|
+
* session while testing `sessionIsBlank` against a different (absent) id stayed enabled on a blank session
|
|
68
|
+
* and drew the previous session's list — from another workspace as readily as from its own, because nothing
|
|
69
|
+
* in this client compares workspaces.
|
|
70
|
+
*
|
|
71
|
+
* @param state - the session-list state handed to the selector.
|
|
72
|
+
* @param id - the session this seat would read its view for.
|
|
73
|
+
* @returns true when there is nothing to review.
|
|
74
|
+
*/
|
|
75
|
+
export declare function unreviewableSession(state: unknown, id: SessionId | undefined): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* The session the PAGE is showing, resolved the way a whole-page reader has to resolve it: the
|
|
78
|
+
* session-list store's own selection first, else what the Session header entry published.
|
|
79
|
+
*
|
|
80
|
+
* `readSessionsSelection` is supplied by the plugin body (it owns the `sessions` service), and may
|
|
81
|
+
* return `undefined` for a shell whose state names none — 0.1.7's store carries no selected session and
|
|
82
|
+
* hands the id to its own session-scoped seats instead, which is why the published id is read here too
|
|
83
|
+
* (`publishSessionId` runs from the header entry's effect). This is a plain read for non-React code: the
|
|
84
|
+
* component that publishes it reads the SUBSCRIBED value ({@link usePublishedSessionId}).
|
|
85
|
+
*
|
|
86
|
+
* It is deliberately NOT every session anyone has read: the page-wide readers here (the reference remap,
|
|
87
|
+
* which rewrites the visible composer's draft) mean the session the reader is looking at, and taking the
|
|
88
|
+
* newest session some other mount polled would address a composer that is not on screen.
|
|
89
|
+
*
|
|
90
|
+
* @param readSessionsSelection - the store's own selected session, when its shape names one.
|
|
91
|
+
* @returns the id, or `undefined` when nothing on this page names a session.
|
|
92
|
+
*/
|
|
93
|
+
export declare function shownSessionId(readSessionsSelection: () => SessionId | undefined): SessionId | undefined;
|
|
@@ -204,32 +204,58 @@ export declare function setSplitMode(value: boolean): void;
|
|
|
204
204
|
export declare function mdPreviewEnabled(): boolean;
|
|
205
205
|
/** Persist the Markdown-preview default preference. */
|
|
206
206
|
export declare function setMdPreviewEnabled(value: boolean): void;
|
|
207
|
+
/**
|
|
208
|
+
* Whether this plugin MOUNTS its own file menu at all — the press bridge that takes over a file press in
|
|
209
|
+
* the shell's lists, a file link in a message, and a relative link in the Markdown preview.
|
|
210
|
+
*
|
|
211
|
+
* Default ON, and the default is the interesting half: only an explicit `'0'` turns it off, so a reader who
|
|
212
|
+
* never touches the setting keeps the menu they have always had (and a storage that cannot be read — a
|
|
213
|
+
* privacy mode, a cleared profile — leaves the plugin working rather than silently disabled).
|
|
214
|
+
*
|
|
215
|
+
* Off means the bridge does not take the press over AT ALL: no menu, and the shell's own behaviour runs
|
|
216
|
+
* exactly as if this plugin were not installed. That includes this plugin's own preview, where a relative
|
|
217
|
+
* link therefore goes back to the browser's own navigation — which is the honest consequence of asking this
|
|
218
|
+
* plugin to keep its hands off, not a bug to special-case.
|
|
219
|
+
*
|
|
220
|
+
* @returns whether the file menu is enabled.
|
|
221
|
+
*/
|
|
222
|
+
export declare function chipMenuEnabled(): boolean;
|
|
223
|
+
/** Persist the file-menu preference (see {@link chipMenuEnabled}). */
|
|
224
|
+
export declare function setChipMenuEnabled(value: boolean): void;
|
|
207
225
|
/**
|
|
208
226
|
* Whether commenting on a diff range is offered: the selection frame's comment button,
|
|
209
|
-
* and the chord that stands for it. Defaults to
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
227
|
+
* and the chord that stands for it. **Defaults to ON** — comment mode is the mode now,
|
|
228
|
+
* not a preview — so only an explicit `'0'` turns it off (the `chipMenuEnabled` idiom a
|
|
229
|
+
* few lines up), and an absent field means on for every profile that predates the flip.
|
|
230
|
+
* This gates STARTING a thread, not the threads themselves — one already written keeps
|
|
231
|
+
* rendering, so turning the mode off never hides work or interrupts an answer on its way
|
|
232
|
+
* back.
|
|
213
233
|
* @returns whether comment mode is on.
|
|
214
234
|
*/
|
|
215
235
|
export declare function commentModeEnabled(): boolean;
|
|
216
|
-
/** Window event: comment mode was toggled (see {@link setCommentModeEnabled}).
|
|
217
|
-
|
|
218
|
-
|
|
236
|
+
/** Window event: comment mode was toggled (see {@link setCommentModeEnabled}). A DIFFERENT string from
|
|
237
|
+
* {@link COMMENT_MODE_KEY} on purpose: a key literal and an event literal that read the same make every
|
|
238
|
+
* grep a guess (the hazard this file already names for the file-menu key at the top). */
|
|
239
|
+
export declare const COMMENT_MODE_CHANGED_EVENT = "diff-approval:comment-mode-changed";
|
|
240
|
+
/** Persist the comment-mode preference (`'1'` on, `'0'` off — see {@link commentModeEnabled}). */
|
|
219
241
|
export declare function setCommentModeEnabled(value: boolean): void;
|
|
220
242
|
/**
|
|
221
243
|
* Whether the diff's code uses the font this plugin bundles.
|
|
222
244
|
*
|
|
223
|
-
* Defaults to
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* is
|
|
227
|
-
*
|
|
245
|
+
* **Defaults to ON**, like comment mode: absent means on, and only an explicit `'0'` turns
|
|
246
|
+
* it off. The old field's answer is deliberately NOT carried over (see the constant): a
|
|
247
|
+
* reader who had turned the bundled face off before this change comes back with it on, which
|
|
248
|
+
* is the one behaviour the reader asked for. The cost is real and worth naming where the
|
|
249
|
+
* switch is: the face is a JetBrains Maple Mono subset sliced into 138 woff2 files, and a
|
|
250
|
+
* page pulls only the slices its characters land in — tens of kilobytes for a comment, up to
|
|
251
|
+
* ~120 KB for a rare character — downloaded on demand rather than shipped.
|
|
228
252
|
* @returns whether the bundled code font is enabled.
|
|
229
253
|
*/
|
|
230
254
|
export declare function codeFontEnabled(): boolean;
|
|
231
|
-
/** Window event: the bundled code font was turned on or off (see {@link setCodeFontEnabled}).
|
|
232
|
-
|
|
255
|
+
/** Window event: the bundled code font was turned on or off (see {@link setCodeFontEnabled}). A DIFFERENT
|
|
256
|
+
* string from {@link CODE_FONT_KEY} on purpose — they used to be the same literal, which made a grep for
|
|
257
|
+
* either one a guess (the hazard this file names for the file-menu key at the top). */
|
|
258
|
+
export declare const CODE_FONT_CHANGED_EVENT = "diff-approval:code-font-changed";
|
|
233
259
|
/** Persist the bundled-code-font preference. */
|
|
234
260
|
export declare function setCodeFontEnabled(value: boolean): void;
|
|
235
261
|
/**
|