dsh-diff-approval 0.20.1 → 0.21.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.
@@ -23,7 +23,18 @@ export declare function diffPathsMatch(chipPath: string, filePath: string, works
23
23
  export declare const SIDEBAR_AUTO_COLLAPSE_PX = 1024;
24
24
  export declare function wrapInto(text: string, widthPx: number, measure: (t: string) => number, tabPx: number): string[];
25
25
  /** Full panel props composed by the sidebar footer-action slot. */
26
- export type PendingPanelProps = PropsRuntime<'sidebar.footer.action'> & InjectFace<PendingPanelFace> & PropsLocale<'diff-approval'>;
26
+ export type PendingPanelProps = PropsRuntime<'sidebar.footer.action'> & InjectFace<PendingPanelFace> & PropsLocale<'diff-approval'> & PendingPanelDockProps;
27
+ /** How the panel is hosted when it is not the footer's floating overlay: the
28
+ * right sidebar's tab renders it docked, filling the tab and portaling its
29
+ * content into `dockHost` (the element the tab body owns). */
30
+ export interface PendingPanelDockProps {
31
+ /** Docked in the sidebar tab: no badge, no overlay positioning, no composer
32
+ * offset, and no header of its own — the tab's chip carries the title, the
33
+ * mode switch, and the kit's close button. */
34
+ docked?: boolean;
35
+ /** The element the docked panel portals its content into. */
36
+ dockHost?: HTMLElement;
37
+ }
27
38
  /**
28
39
  * Open the DSH settings dialog and switch to this plugin's section. The
29
40
  * settings shell keeps its open state and the active section id as
@@ -34,6 +45,26 @@ export type PendingPanelProps = PropsRuntime<'sidebar.footer.action'> & InjectFa
34
45
  * @param sectionLabel - the nav label of this plugin's settings section.
35
46
  */
36
47
  export declare function openSettingsSection(sectionLabel: string): void;
48
+ /**
49
+ * How much of the window the app itself occupies on each side of the conversation
50
+ * — how far the floating panel has to start from an edge to leave that part
51
+ * visible.
52
+ *
53
+ * The centre column's own box is the direct answer: the header above it, an
54
+ * expanded sidebar, a collapsed rail, and a right panel hanging over the centre
55
+ * all sit outside it. The frame's grid tracks fill in the rest — they name the
56
+ * sidebar columns even before a conversation is mounted, and a right sidebar shown
57
+ * without a track of its own draws over the centre, so its own panel is measured
58
+ * instead. Relying on the frame's *resizers* alone was a bug: a collapsed sidebar
59
+ * renders no resizer, so a collapsed rail read as "nothing on that side".
60
+ * @returns each side's occupied size, 0 when that side shows nothing.
61
+ */
62
+ export declare function frameInsets(): {
63
+ top: number;
64
+ bottom: number;
65
+ left: number;
66
+ right: number;
67
+ };
37
68
  /** One file's synchronous view: diff rows and change blocks. */
38
69
  interface RowModel {
39
70
  diff: ReturnType<typeof computeWholeFileDiff>;
@@ -89,5 +120,5 @@ export declare const SplitDiff: import("react").ForwardRefExoticComponent<{
89
120
  */
90
121
  export declare function selectedPlainText(): string | undefined;
91
122
  /** Render the pending-edit review panel and its unified footer action. */
92
- export declare function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPreviewImage, onPasteReference, onUndo, onRedo, onImportVcs, onRefreshVcs, onBrowse, onAddPath, onKeepAll, onRevertAll, onAckRedoCleared, collapseSidebar, t, }: PendingPanelProps): import("react").JSX.Element;
123
+ export declare function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPreviewImage, onPasteReference, onUndo, onRedo, onImportVcs, onRefreshVcs, onBrowse, onAddPath, onKeepAll, onRevertAll, onAckRedoCleared, collapseSidebar, t, docked, dockHost, onOpenDock, closeDock, useDock, }: PendingPanelProps): import("react").JSX.Element;
93
124
  export {};
@@ -0,0 +1,49 @@
1
+ /**
2
+ * How a keybinding is spelled in a tooltip.
3
+ *
4
+ * Every hint reads the binding at render time rather than baking in a default, so
5
+ * a rebind in Settings shows up everywhere the chord is advertised. That is also
6
+ * why the strings live here: the panel's header, its diff toolbars, and the
7
+ * coverage popover all advertise chords, and they must spell them the same way.
8
+ *
9
+ * @module dsh-diff-approval/client/chords
10
+ */
11
+ import type { Translator } from './locales.ts';
12
+ /**
13
+ * One stored chord as a hint renders it: modifiers as written, arrow keys as
14
+ * glyphs (`Ctrl+ArrowUp` → `Ctrl+↑`).
15
+ * @param chord - the stored chord.
16
+ * @returns the hint text; `''` for an unbound action.
17
+ */
18
+ export declare function chordHint(chord: string): string;
19
+ /**
20
+ * One action's chord as a hint.
21
+ * @param action - the keybinding action id (see `DEFAULT_KEYBINDINGS`).
22
+ * @returns the hint text; `''` when the action has no chord.
23
+ */
24
+ export declare function chordLabel(action: string): string;
25
+ /**
26
+ * A tooltip label with the action's configured chord appended. The hint states
27
+ * the binding the user actually has — including one rebound in Settings, or none
28
+ * at all, which simply adds nothing.
29
+ * @param label - the translated action label.
30
+ * @param action - the keybinding action id (see `DEFAULT_KEYBINDINGS`).
31
+ * @returns the label, with ` (chord)` appended when one is configured.
32
+ */
33
+ export declare function withChord(label: string, action: string): string;
34
+ /**
35
+ * How the panel's close button names its two ways out: Escape, and the
36
+ * quick-summon chord when the user still has one bound. Unbinding the chord
37
+ * leaves Escape as the only answer, so the hint then says just that.
38
+ * @param t - the panel's translator.
39
+ * @returns the tooltip label.
40
+ */
41
+ export declare function closeHint(t: Translator): string;
42
+ /**
43
+ * How an entry button names what it opens: the pending-changes panel, plus the
44
+ * chord that does the same. With the chord unbound the plain label is the whole
45
+ * hint — never an empty parenthetical.
46
+ * @param t - the panel's translator.
47
+ * @returns the tooltip label.
48
+ */
49
+ export declare function summonHint(t: Translator): string;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The floating panel's coverage control: one header button, and a popover of
3
+ * three glyph buttons — cover the left sidebar, the composer below, the right
4
+ * sidebar. All three on is what the panel used to call "fullscreen".
5
+ *
6
+ * Each glyph is the same rounded rectangle with one band highlighted, drawn to
7
+ * the product's icon conventions (16px grid, ~1.4px ink, `currentColor`): the
8
+ * band is filled while that side is covered and hollow while it is not, so the
9
+ * shape says *which* edge and the fill says whether it is on. The popover is a
10
+ * row rather than the kit's vertical menu because these are three independent
11
+ * switches, not a list of choices.
12
+ *
13
+ * @module dsh-diff-approval/client/coverage-control
14
+ */
15
+ import type { ReactElement, ReactNode } from 'react';
16
+ import type { DiffApprovalCover } from './settings.ts';
17
+ import type { Translator } from './locales.ts';
18
+ /** One coverage switch: its key, and the keybinding action whose chord the
19
+ * button advertises. */
20
+ type CoverKey = keyof DiffApprovalCover;
21
+ export interface CoverageControlProps {
22
+ t: Translator;
23
+ cover: DiffApprovalCover;
24
+ onToggle: (key: CoverKey) => void;
25
+ }
26
+ /** How long the chord's on-screen echo stays up, matching its CSS animation. */
27
+ export declare const COVER_NOTICE_MS = 1250;
28
+ /**
29
+ * The chord's echo: the same row of glyphs the popover shows, centred on screen,
30
+ * shown the moment a coverage chord flips a switch and fading out after a second.
31
+ * It is a report, not a control — nothing in it is pressable — so a keyboard flip
32
+ * is as visible as a click, without a pointer target appearing under the mouse.
33
+ * @param props - copy, the coverage after the flip, and which edge just changed.
34
+ * @returns the notice.
35
+ */
36
+ export declare function CoverageNotice({ t, cover, changed }: {
37
+ t: Translator;
38
+ cover: DiffApprovalCover;
39
+ changed: CoverKey;
40
+ }): ReactNode;
41
+ /**
42
+ * The trigger and its popover. Marked with `data-diff-approval-cover`, and each
43
+ * switch with `data-diff-approval-cover-<key>`, so the two halves are addressable
44
+ * apart.
45
+ * @param props - copy, the current coverage, and what a switch does.
46
+ * @returns the header control.
47
+ */
48
+ export declare function CoverageControl({ t, cover, onToggle }: CoverageControlProps): ReactElement;
49
+ export {};
@@ -0,0 +1,177 @@
1
+ /**
2
+ * The app's right sidebar (the docking surface) as a host for the review panel.
3
+ *
4
+ * The sidebar is an extensible tab system: a tab type registers its static face
5
+ * with the `sidebarRightTabs` service, its body in the keyed `sidebar.right.pane.tab`
6
+ * seat under the same `id`, and is opened by `kind` through `sidebarRight`. The
7
+ * harness documents this as the path a *package outside the product* takes (its
8
+ * own document preview ships that way), so nothing here imports the sidebar's
9
+ * package as a runtime value: the shapes below are the structural minimum, which
10
+ * is exactly what lets this plugin keep loading on releases that have no right
11
+ * sidebar at all — the type is simply never registered there.
12
+ *
13
+ * @module dsh-diff-approval/client/dock
14
+ */
15
+ import type { ReactNode } from 'react';
16
+ import type { HostObservable, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
17
+ import type { PendingDiffSnapshot } from './slots.ts';
18
+ /** The tab type's `kind`: what `openTab` names. */
19
+ export declare const DIFF_DOCK_KIND = "diff-approval";
20
+ /** The registration's `id`: the key its body and title register under. The
21
+ * registry wants a name unique across implementations, and the package name is
22
+ * the natural value. */
23
+ export declare const DIFF_DOCK_ID = "dsh-diff-approval";
24
+ /** Window event a docked panel dispatches to move the panel back to the overlay.
25
+ * The docked tab and the footer entry are separate mounts, so the ask travels as
26
+ * an event — the same bridge the produced-file buttons use. */
27
+ export declare const SHOW_PANEL_EVENT = "diff-approval:show-panel";
28
+ /** Ask the panel to toggle: what the header entry's button does. It runs the same
29
+ * action as the footer badge, so the two entries can never disagree about what a
30
+ * press means — including for a docked panel, whose tab the chip still closes. */
31
+ export declare const TOGGLE_PANEL_EVENT = "diff-approval:toggle-panel";
32
+ /** The floating panel's own visibility, published on every change so a second
33
+ * entry can light up while the panel is open (`detail.open`). */
34
+ export declare const PANEL_STATE_EVENT = "diff-approval:panel-state";
35
+ /** Payload of {@link PANEL_STATE_EVENT}. */
36
+ export interface PanelStateDetail {
37
+ /** Whether the floating panel is showing right now. */
38
+ open: boolean;
39
+ }
40
+ /** What the footer entry needs to know about the dock. */
41
+ export interface DockSnapshot {
42
+ /** Whether this build has a right sidebar to dock into at all. */
43
+ readonly available: boolean;
44
+ /** Whether our tab is currently showing: reported by the docked tab body
45
+ * itself, because the sidebar's controller exposes no observable. */
46
+ readonly open: boolean;
47
+ /** Why it is unavailable, when it is: the lookup result, for the user to report. */
48
+ readonly reason: string | undefined;
49
+ }
50
+ /** The dock's face to the panel: an observable for the entry, the reveal the
51
+ * entry calls, the close the chip publishes, and the visibility the docked body
52
+ * reports. All exist from apply time — see {@link createDockState}. */
53
+ export interface DockFace {
54
+ readonly hooks: {
55
+ readonly dock: HostObservable<DockSnapshot>;
56
+ };
57
+ /** Expand the sidebar and focus our tab, opening it when it is missing; a
58
+ * no-op while no sidebar is attached. Throws when the sidebar is mounted but
59
+ * cannot act (no seat bound yet), which the panel reports. */
60
+ readonly open: () => void;
61
+ /** The docked tab body's own visibility, so the footer entry can light up. */
62
+ readonly setShowing: (showing: boolean) => void;
63
+ /** The chip's own close, so a chord can close the docked tab from anywhere —
64
+ * the tab is the only thing that knows how to close itself, and it is not
65
+ * mounted where the footer entry lives. `undefined` once the tab is gone. */
66
+ readonly setClose: (close: (() => void) | undefined) => void;
67
+ /** Close our docked tab, when one exists. */
68
+ readonly close: () => void;
69
+ }
70
+ /** The dock's live state, independent of when the sidebar's services attach. */
71
+ export interface DockState {
72
+ readonly face: DockFace;
73
+ /** Swap the live sidebar in (undefined when its services go away), with the
74
+ * reason to report while there is none; returns the detach function. */
75
+ attach(sidebar: unknown, reason?: string): () => void;
76
+ }
77
+ /** Structural view of the tab-type registry (see the module note). */
78
+ export interface TabRegistryFace {
79
+ register(definition: {
80
+ id: string;
81
+ kind: string;
82
+ title: (address: string) => string;
83
+ guide?: readonly {
84
+ order: number;
85
+ title: () => string;
86
+ description?: () => string;
87
+ }[];
88
+ }): () => void;
89
+ }
90
+ /** The tab seat's own tab hook, injected by the sidebar framework: the record
91
+ * being drawn (with the seat's verdict on whether it is on screen) and the
92
+ * actions that act on it. */
93
+ type TabInfoHook = () => {
94
+ readonly tab: {
95
+ readonly actions: {
96
+ close(): void;
97
+ };
98
+ /** Whether this record is actually on screen (expanded column, active tab). */
99
+ readonly visible: boolean;
100
+ };
101
+ };
102
+ export interface DiffDockBodyProps extends PropsLocale<'diff-approval'> {
103
+ /** The seat's own tab hook (the sidebar framework injects it). */
104
+ readonly useTabInfo?: TabInfoHook;
105
+ /** This plugin's pending store hook, for the chip's count. */
106
+ readonly usePending?: (select: (snapshot: PendingDiffSnapshot) => unknown) => unknown;
107
+ /** Whether the panel renders as the docked body (set by the tab seat's face). */
108
+ readonly docked?: boolean;
109
+ /** Report this body's visibility to the dock state (see {@link DockFace}). */
110
+ readonly onDockShowing?: ((showing: boolean) => void) | undefined;
111
+ /** Publish this tab's own close, so a chord can close the docked panel. */
112
+ readonly onDockClose?: ((close: (() => void) | undefined) => void) | undefined;
113
+ [key: string]: unknown;
114
+ }
115
+ /** One live dock adapter: the face, and the teardown that unsubscribes. */
116
+ export interface DiffDock {
117
+ readonly face: DockFace;
118
+ dispose(): void;
119
+ }
120
+ /** The cordis pieces this adapter uses, structurally: optional service lookup and
121
+ * a scoped effect. Both are what the plugin sandbox always forwards — its
122
+ * whitelist covers `effect`/`on`/timers but NOT cordis's dynamic `inject`, and it
123
+ * gates *direct* service access on the plugin's declared inject list (which must
124
+ * not name the sidebar: the panel has to keep working where there is none). `get`
125
+ * is the one lookup it never gates, so discovery goes through it. */
126
+ export interface DockHostContext {
127
+ get(name: string): unknown;
128
+ effect(callback: () => unknown, label: string): void;
129
+ }
130
+ /**
131
+ * The dock's live state, created at apply time rather than when the sidebar's
132
+ * services appear. The footer entry is seated once, so a face captured before the
133
+ * sidebar attaches would freeze on "no dock" and the panel would never offer the
134
+ * switch — the state below is therefore always present, and `attach` swaps the
135
+ * live sidebar underneath it while `open` resolves the current one per call.
136
+ * @returns the state; `attach` reports availability and the tab's own visibility.
137
+ */
138
+ export declare function createDockState(): DockState;
139
+ export declare function DiffDockBody(props: DiffDockBodyProps): ReactNode;
140
+ /**
141
+ * The tab chip: the mode switch, then the panel's title with the pending count —
142
+ * read live from the store so the chip tracks the list without re-registering the
143
+ * tab. The glyph leads because that is this seat's convention (the document tab
144
+ * puts its file-type icon there), and because the chip's tail is masked for the
145
+ * kit's own close button.
146
+ *
147
+ * A docked panel draws no header of its own, so this chip is where the panel is
148
+ * named *and* where it is told to leave: the mode switch offers the two overlay
149
+ * states, and its menu footer keeps this plugin's Settings one click away, which
150
+ * the docked panel has no gear for.
151
+ * @param props - the same face the body gets; the count and the switch are drawn.
152
+ * @returns the chip content.
153
+ */
154
+ export declare function DiffDockTitle(props: DiffDockBodyProps): ReactNode;
155
+ /**
156
+ * Find the sidebar's services and, once they are there, attach the panel to them:
157
+ * register the tab type (with a guide entry, so the panel is discoverable from the
158
+ * sidebar's own guide page) and hand the live sidebar to `attach`. Where they are
159
+ * never provided — an older app, or a composition without the right sidebar —
160
+ * nothing is registered and nothing attaches, which is what makes the whole
161
+ * feature optional rather than required.
162
+ *
163
+ * Discovery is a timed optional lookup rather than cordis's dynamic `inject`,
164
+ * because the plugin sandbox forwards `effect`/`on`/timers but not `inject`, and
165
+ * the sidebar may attach after this plugin does. Timers are the window's own (the
166
+ * sandbox's ctx timer verbs want a `timer` declaration, and a host that withholds
167
+ * the verb must not take the panel down with it).
168
+ * @param ctx - the client context (structurally, see {@link DockHostContext}).
169
+ * @param copy - the tab's copy, read per call so a language change needs no
170
+ * re-registration.
171
+ * @param attach - takes the live sidebar; returns its detach.
172
+ */
173
+ export declare function attachDiffDock(ctx: DockHostContext, copy: {
174
+ title: () => string;
175
+ guideDescription: () => string;
176
+ }, attach: (sidebar: unknown, reason?: string) => () => void): void;
177
+ export {};
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The pending-changes entry in the Session header.
3
+ *
4
+ * The sidebar footer's badge is only where the sidebar is: with the sidebar
5
+ * collapsed to its rail, or hidden on a narrow window, the review has no entry.
6
+ * The Session header's right-aligned utilities hold the app's own per-session
7
+ * controls — "open in app" and the more-actions button the session-log export
8
+ * lives behind — and are visible whatever the sidebar does. This seats the same
9
+ * action there: one compact button carrying the pending count.
10
+ *
11
+ * The header entry is a *second mount* of one action, not a second panel: the
12
+ * click travels to the mount that owns the open state ({@link TOGGLE_PANEL_EVENT})
13
+ * and the visibility comes back ({@link PANEL_STATE_EVENT}), because the two sit
14
+ * in different slot trees and neither owns the other. That is also why a press is
15
+ * exactly the footer badge's press: a docked panel's tab is revealed, and still
16
+ * closed by the chip on its own tab rather than from here.
17
+ *
18
+ * @module dsh-diff-approval/client/header-entry
19
+ */
20
+ import type { ReactNode } from 'react';
21
+ import type { HostObservable, InjectFace, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
22
+ import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
23
+ import type { PendingDiffSnapshot } from './slots.ts';
24
+ import type { DockSnapshot } from './dock.tsx';
25
+ /** What the header entry reads from this plugin's own face: the two observables.
26
+ * Everything else it needs (the button's own click) goes through the window
27
+ * events, so the entry never holds a copy of the panel's state. */
28
+ export interface HeaderEntryFace {
29
+ hooks: {
30
+ /** Live pending-diff snapshot: the count on the button. */
31
+ pending: HostObservable<PendingDiffSnapshot>;
32
+ /** The dock's state, absent in a build without a right sidebar. */
33
+ dock?: HostObservable<DockSnapshot>;
34
+ };
35
+ }
36
+ /** The framework session-list seats this entry reads, as far as it reads them.
37
+ * Both are optional so the entry still renders where a host provides neither. */
38
+ export interface HeaderEntrySeat {
39
+ /** The selected session and its rows; the entry is inert without one. */
40
+ useSessions?: ((select: (state: HeaderEntrySessions) => boolean) => boolean) | undefined;
41
+ }
42
+ /** The slice of the session list this entry reads. */
43
+ export interface HeaderEntrySessions {
44
+ /** The session the app is showing. */
45
+ current?: SessionId | undefined;
46
+ /** Known sessions by id, for the blank-session check. */
47
+ byId: Record<string, {
48
+ blank?: boolean;
49
+ } | undefined>;
50
+ }
51
+ /** Full props of the header entry: the injected face's hooks bound as
52
+ * `usePending`/`useDock`, the framework seats, and the locale `t`. */
53
+ export type DiffApprovalHeaderEntryProps = InjectFace<HeaderEntryFace> & HeaderEntrySeat & PropsLocale<'diff-approval'>;
54
+ /**
55
+ * Render the Session header's pending-changes button.
56
+ * @param props - the bound hooks, the session seat, and the translator.
57
+ * @returns the icon button, with the pending count while there is one.
58
+ */
59
+ export declare function DiffApprovalHeaderEntry({ usePending, useSessions, useDock, t }: DiffApprovalHeaderEntryProps): ReactNode;
@@ -4,6 +4,9 @@ export declare const NS = "diff-approval";
4
4
  export declare const zh: {
5
5
  'panel.title': string;
6
6
  'panel.empty': string;
7
+ 'panel.dockGuide': string;
8
+ 'panel.dockUnavailable': string;
9
+ 'panel.dockFailed': string;
7
10
  'panel.loading': string;
8
11
  'panel.readFailed': string;
9
12
  'panel.group.current': string;
@@ -53,6 +56,8 @@ export declare const zh: {
53
56
  'settings.diffViewDesc': string;
54
57
  'settings.diffPreview': string;
55
58
  'panel.keyDesc': string;
59
+ 'panel.shortcutNone': string;
60
+ 'panel.shortcutReset': string;
56
61
  'panel.key.jumpUp': string;
57
62
  'panel.key.jumpDown': string;
58
63
  'panel.key.copyRef': string;
@@ -65,6 +70,10 @@ export declare const zh: {
65
70
  'panel.key.redo': string;
66
71
  'panel.key.cycleNext': string;
67
72
  'panel.key.cyclePrev': string;
73
+ 'panel.key.coverLeft': string;
74
+ 'panel.key.coverComposer': string;
75
+ 'panel.key.coverRight': string;
76
+ 'panel.key.coverTop': string;
68
77
  'settings.tabLabel': string;
69
78
  'row.create': string;
70
79
  'row.failed': string;
@@ -83,6 +92,8 @@ export declare const zh: {
83
92
  'action.nextDiff': string;
84
93
  'action.showFileList': string;
85
94
  'action.hideFileList': string;
95
+ 'action.fileListFloatOn': string;
96
+ 'action.fileListFloatOff': string;
86
97
  'action.viewSplit': string;
87
98
  'action.viewUnified': string;
88
99
  'action.viewPreview': string;
@@ -110,9 +121,29 @@ export declare const zh: {
110
121
  'panel.importFailed': string;
111
122
  'action.search': string;
112
123
  'action.settings': string;
113
- 'action.expand': string;
114
- 'action.exitFullscreen': string;
124
+ 'action.presentation': string;
125
+ 'action.presentationCurrent': string;
126
+ 'action.cover': string;
127
+ 'cover.left': string;
128
+ 'cover.right': string;
129
+ 'cover.composer': string;
130
+ 'cover.top': string;
131
+ 'cover.leftDesc': string;
132
+ 'cover.topDesc': string;
133
+ 'cover.rightDesc': string;
134
+ 'cover.composerDesc': string;
135
+ 'settings.cover': string;
136
+ 'settings.coverDesc': string;
137
+ 'cover.on': string;
138
+ 'cover.off': string;
139
+ 'cover.notice': string;
140
+ 'action.presentationFloat': string;
141
+ 'action.presentationDock': string;
142
+ 'action.presentationDocked': string;
115
143
  'action.close': string;
144
+ 'action.closeHint': string;
145
+ 'action.closeHintEsc': string;
146
+ 'action.summonHint': string;
116
147
  'status.kept': string;
117
148
  'status.reverted': string;
118
149
  'status.missing': string;
@@ -166,6 +197,9 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
166
197
  export declare const en: {
167
198
  'panel.title': string;
168
199
  'panel.empty': string;
200
+ 'panel.dockGuide': string;
201
+ 'panel.dockUnavailable': string;
202
+ 'panel.dockFailed': string;
169
203
  'panel.loading': string;
170
204
  'panel.readFailed': string;
171
205
  'panel.group.current': string;
@@ -215,6 +249,8 @@ export declare const en: {
215
249
  'settings.diffViewDesc': string;
216
250
  'settings.diffPreview': string;
217
251
  'panel.keyDesc': string;
252
+ 'panel.shortcutNone': string;
253
+ 'panel.shortcutReset': string;
218
254
  'panel.key.jumpUp': string;
219
255
  'panel.key.jumpDown': string;
220
256
  'panel.key.copyRef': string;
@@ -227,6 +263,10 @@ export declare const en: {
227
263
  'panel.key.redo': string;
228
264
  'panel.key.cycleNext': string;
229
265
  'panel.key.cyclePrev': string;
266
+ 'panel.key.coverLeft': string;
267
+ 'panel.key.coverComposer': string;
268
+ 'panel.key.coverRight': string;
269
+ 'panel.key.coverTop': string;
230
270
  'settings.tabLabel': string;
231
271
  'row.create': string;
232
272
  'row.failed': string;
@@ -245,6 +285,8 @@ export declare const en: {
245
285
  'action.nextDiff': string;
246
286
  'action.showFileList': string;
247
287
  'action.hideFileList': string;
288
+ 'action.fileListFloatOn': string;
289
+ 'action.fileListFloatOff': string;
248
290
  'action.viewSplit': string;
249
291
  'action.viewUnified': string;
250
292
  'action.viewPreview': string;
@@ -272,9 +314,29 @@ export declare const en: {
272
314
  'panel.importFailed': string;
273
315
  'action.search': string;
274
316
  'action.settings': string;
275
- 'action.expand': string;
276
- 'action.exitFullscreen': string;
317
+ 'action.presentation': string;
318
+ 'action.presentationCurrent': string;
319
+ 'action.cover': string;
320
+ 'cover.left': string;
321
+ 'cover.right': string;
322
+ 'cover.composer': string;
323
+ 'cover.top': string;
324
+ 'cover.leftDesc': string;
325
+ 'cover.topDesc': string;
326
+ 'cover.rightDesc': string;
327
+ 'cover.composerDesc': string;
328
+ 'settings.cover': string;
329
+ 'settings.coverDesc': string;
330
+ 'cover.on': string;
331
+ 'cover.off': string;
332
+ 'cover.notice': string;
333
+ 'action.presentationFloat': string;
334
+ 'action.presentationDock': string;
335
+ 'action.presentationDocked': string;
277
336
  'action.close': string;
337
+ 'action.closeHint': string;
338
+ 'action.closeHintEsc': string;
339
+ 'action.summonHint': string;
278
340
  'status.kept': string;
279
341
  'status.reverted': string;
280
342
  'status.missing': string;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The mode switch: where the panel shows — floating over the app, or docked as a
3
+ * tab in the app's right sidebar. (What the floating panel *covers* is the
4
+ * coverage control's business; see the panel's own header.)
5
+ *
6
+ * One implementation, two hosts. The floating panel draws it in its own header
7
+ * beside the gear. A *docked* panel draws no header at all — the tab is the
8
+ * frame, its chip already carries the title and a close button — so the chip
9
+ * (`sidebar.right.pane.tab.title`, which is this plugin's own component) hosts
10
+ * the same control instead: the seat's convention is a glyph *before* the title
11
+ * text, and the chip's tail is masked for the kit's own close button.
12
+ *
13
+ * @module dsh-diff-approval/client/presentation-menu
14
+ */
15
+ import type { ReactNode } from 'react';
16
+ import type { MenuEntry } from '@deepseek-ai/dsh-client-ui-primitives';
17
+ import type { DiffApprovalPresentation } from './settings.ts';
18
+ import type { Translator } from './locales.ts';
19
+ /**
20
+ * The mode switch's mark: the state it currently shows, and the same glyph each
21
+ * menu row leads with. The dock mark is the icon the app's own right sidebar uses
22
+ * (`IconPanelLeftOutline16`, mirrored — see ui-sidebar-right's ExpandButton), so
23
+ * "dock" points at the same affordance the column does.
24
+ * @param props - the state, and the drawn size in px.
25
+ * @returns the glyph.
26
+ */
27
+ export declare function PresentationIcon({ state, size }: {
28
+ state: DiffApprovalPresentation;
29
+ size: number;
30
+ }): ReactNode;
31
+ export interface PresentationMenuProps {
32
+ t: Translator;
33
+ /** Where the panel is now: the checked row, and the mark on the trigger. */
34
+ current: DiffApprovalPresentation;
35
+ onChoose: (next: DiffApprovalPresentation) => void;
36
+ /** Rows pinned below a hairline, for a host with no other way to reach them
37
+ * (the chip carries this plugin's Settings; a docked panel has no gear). */
38
+ footer?: readonly MenuEntry[];
39
+ /** Called for a `footer` row's id, which is not a presentation. */
40
+ onFooter?: (id: string) => void;
41
+ /** The chip's variant: no room for the chevron, and a box the chip's 28px
42
+ * height can hold. */
43
+ compact?: boolean;
44
+ }
45
+ export declare function PresentationMenu({ t, current, onChoose, footer, onFooter, compact }: PresentationMenuProps): ReactNode;
@@ -1,4 +1,24 @@
1
1
  /** Client preferences for the review panel, persisted in localStorage. */
2
+ /** Where the review panel shows: floating over the app, or docked as a tab in the
3
+ * app's right sidebar. What the floating panel covers is a separate setting —
4
+ * see {@link DiffApprovalCover}. */
5
+ export type DiffApprovalPresentation = 'float' | 'dock';
6
+ /**
7
+ * What the floating panel covers: the app's header above the conversation, its
8
+ * left sidebar, its right sidebar, and the composer below. All four on is the
9
+ * presentation the panel used to call "fullscreen" — the panel filling the
10
+ * window; both sidebars and the header only is the plain floating panel, which
11
+ * leaves the input usable.
12
+ */
13
+ export interface DiffApprovalCover {
14
+ readonly top: boolean;
15
+ readonly left: boolean;
16
+ readonly right: boolean;
17
+ readonly composer: boolean;
18
+ }
19
+ /** The floating panel's default coverage: the header and both sidebars, not the
20
+ * composer. */
21
+ export declare const FLOAT_COVER_DEFAULT: DiffApprovalCover;
2
22
  /** Default lead rows above a jumped-to diff block (kept small and bounded). */
3
23
  export declare const NAV_LEAD_ROWS_DEFAULT = 2;
4
24
  export declare const NAV_LEAD_ROWS_MIN = 0;
@@ -61,6 +81,42 @@ export declare function setSearchCaseSensitive(value: boolean): void;
61
81
  export declare function searchWholeWord(): boolean;
62
82
  /** Persist the search whole-word preference. */
63
83
  export declare function setSearchWholeWord(value: boolean): void;
84
+ /**
85
+ * Where the review panel shows, as the user last had it. Two states, both
86
+ * remembered so the footer entry can restore the one they were using:
87
+ *
88
+ * - `float` — the panel floating over the app, covering what
89
+ * {@link panelCover} says (and the composer only when that says so);
90
+ * - `dock` — the panel docked in the app's right sidebar, as its own tab.
91
+ *
92
+ * A stored `fullscreen` — the third state before coverage became three switches —
93
+ * reads as `float`, with {@link panelCover} supplying the all-on coverage it
94
+ * meant.
95
+ * @returns the remembered presentation; `float` when unset or unrecognized.
96
+ */
97
+ export declare function panelPresentation(): DiffApprovalPresentation;
98
+ /** Persist the panel's presentation (see {@link panelPresentation}). */
99
+ export declare function setPanelPresentation(value: DiffApprovalPresentation): void;
100
+ /**
101
+ * What the floating panel covers (see {@link DiffApprovalCover}). Defaults to
102
+ * both sidebars and not the composer; a stored `fullscreen` presentation from
103
+ * before the switches means all three.
104
+ * @returns the remembered coverage.
105
+ */
106
+ export declare function panelCover(): DiffApprovalCover;
107
+ /** Window event: the floating panel's coverage changed (see {@link setPanelCover}). */
108
+ export declare const COVER_CHANGED_EVENT = "diff-approval:cover";
109
+ /** Persist what the floating panel covers. */
110
+ export declare function setPanelCover(value: DiffApprovalCover): void;
111
+ /**
112
+ * Whether the file list always folds into its floating card, whatever width the
113
+ * panel has. Defaults to off, where the width decides: the list sits beside the
114
+ * diff while there is room for both. Only an explicit `'1'` forces the fold.
115
+ * @returns whether the file list is always folded.
116
+ */
117
+ export declare function fileListFloat(): boolean;
118
+ /** Persist the always-fold preference for the file list. */
119
+ export declare function setFileListFloat(value: boolean): void;
64
120
  /**
65
121
  * Whether lines wrap (auto-wrap) in the diff for one highlight language.
66
122
  * Defaults to off; only an explicit `'1'` enables it. Stored per language, so
@@ -171,7 +227,9 @@ export declare function quickSummonKey(): string;
171
227
  /** Persist the quick-summon chord. */
172
228
  export declare function setQuickSummonKey(value: string): void;
173
229
  /** Default chord for each configurable action (every supported key except the
174
- * panel's own ESC-to-close, which is intentionally not remapped). */
230
+ * panel's own ESC-to-close, which is intentionally not remapped). The coverage
231
+ * switches are keyed like the edges they toggle, in the order the panel lists
232
+ * them: left, top, right, bottom. */
175
233
  export declare const DEFAULT_KEYBINDINGS: Record<string, string>;
176
234
  /** The currently configured chord for one action; falls back to its default. */
177
235
  export declare function keybindingOf(action: string): string;