dsh-diff-approval 0.30.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.
@@ -125,18 +125,36 @@ export declare function rememberPlacedThreads(sessionId: string | undefined, thr
125
125
  */
126
126
  export declare function forgetPlacedThreadsNotIn(sessionId: string | undefined, listed: readonly string[]): void;
127
127
  /**
128
- * Whether the reader asked not to be asked again about removing one file.
128
+ * Whether the reader asked not to be asked again about rows leaving THIS session's list.
129
129
  * @param sessionId - the session the panel is reviewing; nothing is quiet without one.
130
- * @param fileId - the pending entry the question was about.
131
130
  * @returns true when the action should run without the question.
132
131
  */
133
- export declare function removalAskQuiet(sessionId: string | undefined, fileId: string): boolean;
132
+ export declare function removalAskQuiet(sessionId: string | undefined): boolean;
134
133
  /**
135
- * Stop asking whether one file should leave the list, for the rest of this page.
134
+ * Stop asking whether rows should leave the list, for the rest of this page: the whole session, every
135
+ * file in it.
136
136
  * @param sessionId - the session the panel is reviewing; nothing is recorded without one.
137
- * @param fileId - the pending entry the question was about.
138
137
  */
139
- export declare function quietenRemovalAsk(sessionId: string | undefined, fileId: string): void;
138
+ export declare function quietenRemovalAsk(sessionId: string | undefined): void;
139
+ /**
140
+ * Window event a dismissal dispatches, so every mounted seat hears it.
141
+ *
142
+ * The notice's chip lives in the open file's status bar, and the panel that draws it can be mounted more than
143
+ * once (the footer seat and the right sidebar's docked tab are separate mounts): the state above is the fact,
144
+ * and this event is how the OTHER mount learns about it NOW rather than at its next mount. One state, one
145
+ * event — so whichever seat is on screen cannot disagree about whether the reader has said "got it".
146
+ */
147
+ export declare const UPDATE_DISMISSED_EVENT = "diff-approval:update-dismissed";
148
+ /**
149
+ * The release whose notice the reader dismissed on this page.
150
+ * @returns the dismissed version, or `''` when none has been dismissed here.
151
+ */
152
+ export declare function dismissedUpdateVersion(): string;
153
+ /**
154
+ * Remember that the reader dismissed the notice for one version on this page, and tell the other mounts.
155
+ * @param version - the version that notice was about.
156
+ */
157
+ export declare function dismissUpdate(version: string): void;
140
158
  /**
141
159
  * Remember that a session's panel was left showing one file, and where in it.
142
160
  * @param sessionId - the session the panel was reviewing; nothing is recorded
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Path comparison for the review's own references: is the path a press named the same file as one the
3
+ * pending list holds?
4
+ *
5
+ * Its own module because three places ask it and they must not disagree — the panel's jump to a file, the
6
+ * plugin's produced-file press bridge, and the store's "does the page hold this path" reader — while the
7
+ * panel itself is a React component module that a data-layer reader should not have to import.
8
+ *
9
+ * @module dsh-diff-approval/client/path-match
10
+ */
11
+ /** Normalize a path for comparison: forward slashes, no trailing slash. */
12
+ export declare function normalizeDiffPath(p: string): string;
13
+ /**
14
+ * The directory a file path lives in, as the path was spelled.
15
+ *
16
+ * @param p - the file's own path.
17
+ * @returns the directory, or undefined when the path names no directory (empty, or a bare file name).
18
+ */
19
+ export declare function directoryOfPath(p: string | undefined): string | undefined;
20
+ /**
21
+ * The absolute path a Markdown link names, resolved the way Markdown means it: against the directory of
22
+ * the file the preview is showing.
23
+ *
24
+ * A link in a document is relative to THAT DOCUMENT — `../src/foo.ts` in `docs/a.md` is `src/foo.ts` at the
25
+ * workspace root, not `../src/foo.ts` of the page's own URL — which is why the base is the previewed file's
26
+ * own directory and not the workspace root. The href is percent-decoded, its `#line`/`?query` suffix is
27
+ * dropped, `.`/`..` are resolved, and both separators are accepted. An href that is already absolute
28
+ * (drive letter or a leading slash) needs no base.
29
+ *
30
+ * Exported so the panel's preview click and its tests share one implementation and cannot disagree about
31
+ * what a link points at.
32
+ *
33
+ * @param baseDirectory - the directory of the file being previewed, or the workspace root when that file's
34
+ * own path is unknown.
35
+ * @param href - the anchor's href, as written in the Markdown.
36
+ * @returns the absolute path, or undefined when it cannot be named (an empty href, or a relative href with
37
+ * no base to resolve against).
38
+ */
39
+ export declare function resolveMarkdownHref(baseDirectory: string | undefined, href: string): string | undefined;
40
+ /**
41
+ * Whether a produced-file chip path and a pending file path refer to the same file, tolerant of separator
42
+ * style (\\ vs /) and of a workspace-relative vs absolute form. `chipPath` is typically the harness's
43
+ * workspace-relative forward-slash path; `filePath` is the host's absolute native-separator path. Matching
44
+ * is case-insensitive so a Windows drive/segment case difference does not miss the file the user clicked.
45
+ * @param chipPath - the path a press (or a reference) named.
46
+ * @param filePath - the path the pending list holds.
47
+ * @param workspacePath - the session's workspace root, when it is known.
48
+ * @returns whether the two name one file.
49
+ */
50
+ export declare function diffPathsMatch(chipPath: string, filePath: string, workspacePath: string | undefined): boolean;
@@ -5,7 +5,7 @@
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 { CommentAnchor, CommentQuoteLine, DiffApprovalActionValue, DiffApprovalAddValue, DiffApprovalBlockRange, DiffApprovalBrowseValue, DiffApprovalBulkValue, DiffApprovalCommentAddValue, DiffApprovalCommentAskValue, DiffApprovalCommentRemoveManyValue, DiffApprovalCommentRemoveValue, 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
11
  /**
@@ -33,6 +33,18 @@ export interface CommentDraft {
33
33
  export interface DiffApprovalPort {
34
34
  /** Read one session's pending entries (plus its workspace root), oldest capture first. */
35
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>;
36
48
  /** Keep one operation. `keepListed` leaves the resolved entry in the list. */
37
49
  keep(sessionId: SessionId, id: string, keepListed?: boolean): Promise<DiffApprovalActionValue>;
38
50
  /** Revert one operation. `keepListed` leaves the resolved entry in the list. */
@@ -56,6 +68,8 @@ export interface DiffApprovalPort {
56
68
  * path must be one regular file: a directory is refused instead of being scanned.
57
69
  */
58
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>;
59
73
  /** Open one file with its default application or reveal it in the folder. */
60
74
  open(sessionId: SessionId, id: string, action: DiffApprovalOpenAction): Promise<DiffApprovalOpenValue>;
61
75
  /** Keep every pending entry of one session in a single host call (one batch). */
@@ -90,8 +104,21 @@ export interface DiffApprovalPort {
90
104
  * calls is a different thing — N requests, N writes — which is what this endpoint exists to avoid.
91
105
  */
92
106
  commentRemoveMany(sessionId: SessionId, ids: readonly string[]): Promise<DiffApprovalCommentRemoveManyValue>;
93
- /** Ask one stored comment as its own turn of the session. */
107
+ /** Asks one stored comment as its own turn of the session. */
94
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>;
95
122
  }
96
123
  /** Build the port over one generic RPC caller.
97
124
  * @param rpc - the connection's channel caller.
@@ -13,35 +13,89 @@
13
13
  * `a[href]` any other link naming a file `href` = the path
14
14
  *
15
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 this panel does not hold, a modified press (Ctrl/Cmd/Shift/Alt, or a
17
- * middle click — gestures the harness may give its own meaning later), and any press inside this
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
18
  * plugin's own surface, whose buttons carry paths too and belong to the panel that is already showing
19
- * the file.
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.
20
21
  *
21
22
  * The name of the module (and of the event it raises) dates from when the produced-file card was the
22
23
  * only press of this kind; `data-presented-files-row` is that same list under 0.1.7's name for it.
23
24
  */
25
+ import type { PendingDiffSnapshot } from './slots.ts';
24
26
  /** Window event dispatched when a pending file's press is taken over; the panel listens for it. */
25
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;
26
43
  /** Window event dispatched by the panel to open a file in the panel; PendingPanel listens for it. */
27
44
  export declare const OPEN_FILE_EVENT = "diff-approval:open-file";
28
- /** What a press tells the panel: which file, and where the press was (the menu hangs under it). */
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. */
29
47
  export interface ProducedChipMenuDetail {
30
48
  /** The path the pressed element names. */
31
49
  path: string;
32
50
  /** The pressed element's own box, in viewport coordinates. */
33
51
  x: number;
34
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;
35
75
  }
36
76
  /** What the bridge needs from the host to decide, and to report. */
37
77
  export interface ProducedChipBridge {
38
78
  /**
39
- * Whether the panel holds this file right now. Called synchronously on every press, because
40
- * the decision it answers is whether the press is prevented — there is no second chance at it.
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.
41
84
  */
42
85
  isPending: (path: string) => boolean;
43
86
  /** A pending file's press was taken over: open the menu the press asked for. */
44
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;
45
99
  }
46
100
  /**
47
101
  * Press a file's own press again — the shell's open, run the only way this plugin can run it
@@ -55,6 +109,29 @@ export interface ProducedChipBridge {
55
109
  * @returns whether an element naming it was found to press.
56
110
  */
57
111
  export declare function replayFilePress(path: string): boolean;
112
+ /**
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;
58
135
  /**
59
136
  * Start routing the presses that would open a file in the shell's own viewer.
60
137
  *
@@ -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 {};
@@ -18,12 +18,27 @@
18
18
  */
19
19
  import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
20
20
  /**
21
- * Publish the session the header entry is showing.
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.
22
27
  * @param id - the id, or `undefined` when the shell named none.
23
28
  */
24
29
  export declare function publishSessionId(id: SessionId | undefined): void;
25
30
  /** The session the header entry last showed, or `undefined` when it never named one. */
26
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;
27
42
  /**
28
43
  * The selected session a shell's session-list state names, by every field the shells have used.
29
44
  * @param state - the store state handed to the selector.
@@ -44,10 +59,35 @@ export declare function selectedSessionOf(state: unknown): SessionId | undefined
44
59
  */
45
60
  export declare function sessionIsBlank(state: unknown, id: SessionId | undefined): boolean;
46
61
  /**
47
- * The session this mount is about: the slot prop the shell composed, else the store's own selected
48
- * session, else what the header entry published (see the module doc).
49
- * @param propSessionId - `sessionId` from the composed slot props, when the shell sends one.
50
- * @param storeSelected - the store's selected session, when its shape names one.
51
- * @returns the id, or `undefined` when nothing here knows one.
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.
52
92
  */
53
- export declare function sessionIdFor(propSessionId: SessionId | undefined, storeSelected: SessionId | undefined): SessionId | undefined;
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 off while the mode is a preview (see
210
- * {@link COMMENT_MODE_PREVIEW_KEY}); only an explicit `'1'` enables it. This gates
211
- * STARTING a thread, not the threads themselves — one already written keeps rendering,
212
- * so turning the mode off never hides work or interrupts an answer on its way back.
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
- export declare const COMMENT_MODE_CHANGED_EVENT = "diff-approval:comment-mode";
218
- /** Persist the comment-mode preference. */
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 **off**: the bundled face is JetBrains Maple Mono subset and
224
- * sliced into 138 woff2 files, and a page pulls the slices its characters land
225
- * in — tens of kilobytes for a comment, up to ~120 KB for a rare character. That
226
- * is real traffic, so it is the reader's decision, not a default they discover.
227
- * Only an explicit `'1'` enables it.
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
- export declare const CODE_FONT_CHANGED_EVENT = "diff-approval:code-font";
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
  /**