dsh-diff-approval 0.20.1 → 0.22.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 -5
- package/README.zh.md +7 -5
- package/lib/client.js +3006 -1114
- package/lib/types/client/PendingPanel.d.ts +33 -2
- package/lib/types/client/boundary.d.ts +38 -0
- package/lib/types/client/chords.d.ts +49 -0
- package/lib/types/client/coverage-control.d.ts +49 -0
- package/lib/types/client/dock.d.ts +198 -0
- package/lib/types/client/header-entry.d.ts +59 -0
- package/lib/types/client/locales.d.ts +70 -4
- package/lib/types/client/panel-memory.d.ts +47 -0
- package/lib/types/client/presentation-menu.d.ts +45 -0
- package/lib/types/client/settings.d.ts +59 -1
- package/lib/types/client/slots.d.ts +19 -2
- package/package.json +1 -1
|
@@ -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,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A crash inside the panel must not take the review away for the rest of the page.
|
|
3
|
+
*
|
|
4
|
+
* The slot renderer wraps every seat in its own boundary, but a crash there
|
|
5
|
+
* *abdicates* the entry: the footer row, the header button and the docked tab
|
|
6
|
+
* disappear for the life of the page, with nothing on screen to say why and no way
|
|
7
|
+
* back but a reload. This boundary keeps a failure inside our own tree instead —
|
|
8
|
+
* the entry stays registered, it says what happened, and one press retries the
|
|
9
|
+
* render — so a broken panel can never present as "the panel is gone" or as a
|
|
10
|
+
* blank surface with no explanation.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-diff-approval/client/boundary
|
|
13
|
+
*/
|
|
14
|
+
import { Component } from 'react';
|
|
15
|
+
import type { ErrorInfo, ReactNode } from 'react';
|
|
16
|
+
import type { Translator } from './locales.ts';
|
|
17
|
+
/** Props of the boundary and of the face it shows in place of its children. */
|
|
18
|
+
export interface PanelBoundaryProps {
|
|
19
|
+
/** The panel mount to keep contained. */
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
/** The panel's translator, for the failure note's copy. */
|
|
22
|
+
t: Translator;
|
|
23
|
+
}
|
|
24
|
+
/** State: whether a child has thrown since the last retry. */
|
|
25
|
+
interface PanelBoundaryState {
|
|
26
|
+
failed: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Keep one panel mount's crash inside its own slot entry, with a visible note and
|
|
30
|
+
* a retry that re-renders it.
|
|
31
|
+
*/
|
|
32
|
+
export declare class PanelBoundary extends Component<PanelBoundaryProps, PanelBoundaryState> {
|
|
33
|
+
state: PanelBoundaryState;
|
|
34
|
+
static getDerivedStateFromError(): PanelBoundaryState;
|
|
35
|
+
componentDidCatch(error: unknown, info: ErrorInfo): void;
|
|
36
|
+
render(): ReactNode;
|
|
37
|
+
}
|
|
38
|
+
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,198 @@
|
|
|
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
|
+
/** Name a file for the panel to show, from a mount that is not the panel itself
|
|
36
|
+
* (the produced-file chip): every mounted panel instance switches to it and lands
|
|
37
|
+
* on its first change — the ask is "show me this diff", not "put me back where I
|
|
38
|
+
* was". The file is also recorded as the last one, so an instance that only
|
|
39
|
+
* appears afterwards (the docked tab) opens the same file the same way. */
|
|
40
|
+
export declare const OPEN_PANEL_FILE_EVENT = "diff-approval:panel-file";
|
|
41
|
+
/** Payload of {@link OPEN_PANEL_FILE_EVENT}. */
|
|
42
|
+
export interface PanelFileDetail {
|
|
43
|
+
/** The pending entry to show. */
|
|
44
|
+
fileId: string;
|
|
45
|
+
}
|
|
46
|
+
/** Payload of {@link PANEL_STATE_EVENT}. */
|
|
47
|
+
export interface PanelStateDetail {
|
|
48
|
+
/** Whether the floating panel is showing right now. */
|
|
49
|
+
open: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** What the footer entry needs to know about the dock. */
|
|
52
|
+
export interface DockSnapshot {
|
|
53
|
+
/** Whether this build has a right sidebar to dock into at all. */
|
|
54
|
+
readonly available: boolean;
|
|
55
|
+
/** Whether our tab is currently showing: reported by the docked tab body
|
|
56
|
+
* itself, because the sidebar's controller exposes no observable. */
|
|
57
|
+
readonly open: boolean;
|
|
58
|
+
/** Why it is unavailable, when it is: the lookup result, for the user to report. */
|
|
59
|
+
readonly reason: string | undefined;
|
|
60
|
+
}
|
|
61
|
+
/** The dock's face to the panel: an observable for the entry, the reveal the
|
|
62
|
+
* entry calls, the close the chip publishes, and the visibility the docked body
|
|
63
|
+
* reports. All exist from apply time — see {@link createDockState}. */
|
|
64
|
+
export interface DockFace {
|
|
65
|
+
readonly hooks: {
|
|
66
|
+
readonly dock: HostObservable<DockSnapshot>;
|
|
67
|
+
};
|
|
68
|
+
/** Expand the sidebar and focus our tab, opening it when it is missing; a
|
|
69
|
+
* no-op while no sidebar is attached. Throws when the sidebar is mounted but
|
|
70
|
+
* cannot act (no seat bound yet), which the panel reports. */
|
|
71
|
+
readonly open: () => void;
|
|
72
|
+
/** The docked tab body's own visibility, so the footer entry can light up. */
|
|
73
|
+
readonly setShowing: (showing: boolean) => void;
|
|
74
|
+
/** The chip's own close, so a chord can close the docked tab from anywhere —
|
|
75
|
+
* the tab is the only thing that knows how to close itself, and it is not
|
|
76
|
+
* mounted where the footer entry lives. `undefined` once the tab is gone. */
|
|
77
|
+
readonly setClose: (close: (() => void) | undefined) => void;
|
|
78
|
+
/** Close our docked tab, when one exists. */
|
|
79
|
+
readonly close: () => void;
|
|
80
|
+
}
|
|
81
|
+
/** The dock's live state, independent of when the sidebar's services attach. */
|
|
82
|
+
export interface DockState {
|
|
83
|
+
readonly face: DockFace;
|
|
84
|
+
/** Swap the live sidebar in (undefined when its services go away), with the
|
|
85
|
+
* reason to report while there is none; returns the detach function. */
|
|
86
|
+
attach(sidebar: unknown, reason?: string): () => void;
|
|
87
|
+
}
|
|
88
|
+
/** Structural view of the tab-type registry (see the module note). */
|
|
89
|
+
export interface TabRegistryFace {
|
|
90
|
+
register(definition: {
|
|
91
|
+
id: string;
|
|
92
|
+
kind: string;
|
|
93
|
+
title: (address: string) => string;
|
|
94
|
+
guide?: readonly {
|
|
95
|
+
order: number;
|
|
96
|
+
title: () => string;
|
|
97
|
+
description?: () => string;
|
|
98
|
+
}[];
|
|
99
|
+
}): () => void;
|
|
100
|
+
}
|
|
101
|
+
/** The tab seat's own tab hook, injected by the sidebar framework: the record
|
|
102
|
+
* being drawn (with the seat's verdict on whether it is on screen) and the
|
|
103
|
+
* actions that act on it. */
|
|
104
|
+
type TabInfoHook = () => {
|
|
105
|
+
readonly tab: {
|
|
106
|
+
readonly actions: {
|
|
107
|
+
close(): void;
|
|
108
|
+
};
|
|
109
|
+
/** Whether this record is actually on screen (expanded column, active tab). */
|
|
110
|
+
readonly visible: boolean;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
export interface DiffDockBodyProps extends PropsLocale<'diff-approval'> {
|
|
114
|
+
/** The seat's own tab hook (the sidebar framework injects it). */
|
|
115
|
+
readonly useTabInfo?: TabInfoHook;
|
|
116
|
+
/** This plugin's pending store hook, for the chip's count. */
|
|
117
|
+
readonly usePending?: (select: (snapshot: PendingDiffSnapshot) => unknown) => unknown;
|
|
118
|
+
/** Whether the panel renders as the docked body (set by the tab seat's face). */
|
|
119
|
+
readonly docked?: boolean;
|
|
120
|
+
/** Report this body's visibility to the dock state (see {@link DockFace}). */
|
|
121
|
+
readonly onDockShowing?: ((showing: boolean) => void) | undefined;
|
|
122
|
+
/** Publish this tab's own close, so a chord can close the docked panel. */
|
|
123
|
+
readonly onDockClose?: ((close: (() => void) | undefined) => void) | undefined;
|
|
124
|
+
[key: string]: unknown;
|
|
125
|
+
}
|
|
126
|
+
/** One live dock adapter: the face, and the teardown that unsubscribes. */
|
|
127
|
+
export interface DiffDock {
|
|
128
|
+
readonly face: DockFace;
|
|
129
|
+
dispose(): void;
|
|
130
|
+
}
|
|
131
|
+
/** The cordis pieces this adapter uses, structurally: optional service lookup and
|
|
132
|
+
* a scoped effect. Both are what the plugin sandbox always forwards — its
|
|
133
|
+
* whitelist covers `effect`/`on`/timers but NOT cordis's dynamic `inject`, and it
|
|
134
|
+
* gates *direct* service access on the plugin's declared inject list (which must
|
|
135
|
+
* not name the sidebar: the panel has to keep working where there is none). `get`
|
|
136
|
+
* is the one lookup it never gates, so discovery goes through it. */
|
|
137
|
+
export interface DockHostContext {
|
|
138
|
+
get(name: string): unknown;
|
|
139
|
+
effect(callback: () => unknown, label: string): void;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The dock's live state, created at apply time rather than when the sidebar's
|
|
143
|
+
* services appear. The footer entry is seated once, so a face captured before the
|
|
144
|
+
* sidebar attaches would freeze on "no dock" and the panel would never offer the
|
|
145
|
+
* switch — the state below is therefore always present, and `attach` swaps the
|
|
146
|
+
* live sidebar underneath it while `open` resolves the current one per call.
|
|
147
|
+
* @returns the state; `attach` reports availability and the tab's own visibility.
|
|
148
|
+
*/
|
|
149
|
+
export declare function createDockState(): DockState;
|
|
150
|
+
/**
|
|
151
|
+
* The tab body: the review panel, docked. The panel's content is one portaled
|
|
152
|
+
* block, so instead of restructuring it this hands it a host element of the
|
|
153
|
+
* sidebar's own making to portal into — the tab body owns the element it fills.
|
|
154
|
+
* @param props - the seat's runtime props, this plugin's face, and `useTabInfo`.
|
|
155
|
+
* @returns the host element and, once it exists, the docked panel inside it.
|
|
156
|
+
*/
|
|
157
|
+
/** Keeps a failure inside the tab from taking the app's tree down with it, and
|
|
158
|
+
* from leaving the pane blank: {@link PanelBoundary} says what happened and can
|
|
159
|
+
* re-render the panel without a reload. */
|
|
160
|
+
export declare function DiffDockBody(props: DiffDockBodyProps): ReactNode;
|
|
161
|
+
/**
|
|
162
|
+
* The tab chip: the mode switch, then the panel's title with the pending count —
|
|
163
|
+
* read live from the store so the chip tracks the list without re-registering the
|
|
164
|
+
* tab. The glyph leads because that is this seat's convention (the document tab
|
|
165
|
+
* puts its file-type icon there), and because the chip's tail is masked for the
|
|
166
|
+
* kit's own close button.
|
|
167
|
+
*
|
|
168
|
+
* A docked panel draws no header of its own, so this chip is where the panel is
|
|
169
|
+
* named *and* where it is told to leave: the mode switch offers the two overlay
|
|
170
|
+
* states, and its menu footer keeps this plugin's Settings one click away, which
|
|
171
|
+
* the docked panel has no gear for.
|
|
172
|
+
* @param props - the same face the body gets; the count and the switch are drawn.
|
|
173
|
+
* @returns the chip content.
|
|
174
|
+
*/
|
|
175
|
+
export declare function DiffDockTitle(props: DiffDockBodyProps): ReactNode;
|
|
176
|
+
/**
|
|
177
|
+
* Find the sidebar's services and, once they are there, attach the panel to them:
|
|
178
|
+
* register the tab type (with a guide entry, so the panel is discoverable from the
|
|
179
|
+
* sidebar's own guide page) and hand the live sidebar to `attach`. Where they are
|
|
180
|
+
* never provided — an older app, or a composition without the right sidebar —
|
|
181
|
+
* nothing is registered and nothing attaches, which is what makes the whole
|
|
182
|
+
* feature optional rather than required.
|
|
183
|
+
*
|
|
184
|
+
* Discovery is a timed optional lookup rather than cordis's dynamic `inject`,
|
|
185
|
+
* because the plugin sandbox forwards `effect`/`on`/timers but not `inject`, and
|
|
186
|
+
* the sidebar may attach after this plugin does. Timers are the window's own (the
|
|
187
|
+
* sandbox's ctx timer verbs want a `timer` declaration, and a host that withholds
|
|
188
|
+
* the verb must not take the panel down with it).
|
|
189
|
+
* @param ctx - the client context (structurally, see {@link DockHostContext}).
|
|
190
|
+
* @param copy - the tab's copy, read per call so a language change needs no
|
|
191
|
+
* re-registration.
|
|
192
|
+
* @param attach - takes the live sidebar; returns its detach.
|
|
193
|
+
*/
|
|
194
|
+
export declare function attachDiffDock(ctx: DockHostContext, copy: {
|
|
195
|
+
title: () => string;
|
|
196
|
+
guideDescription: () => string;
|
|
197
|
+
}, attach: (sidebar: unknown, reason?: string) => () => void): void;
|
|
198
|
+
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,31 @@ export declare const zh: {
|
|
|
110
121
|
'panel.importFailed': string;
|
|
111
122
|
'action.search': string;
|
|
112
123
|
'action.settings': string;
|
|
113
|
-
'action.
|
|
114
|
-
'action.
|
|
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;
|
|
147
|
+
'action.retry': string;
|
|
148
|
+
'panel.crashed': string;
|
|
116
149
|
'status.kept': string;
|
|
117
150
|
'status.reverted': string;
|
|
118
151
|
'status.missing': string;
|
|
@@ -166,6 +199,9 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
166
199
|
export declare const en: {
|
|
167
200
|
'panel.title': string;
|
|
168
201
|
'panel.empty': string;
|
|
202
|
+
'panel.dockGuide': string;
|
|
203
|
+
'panel.dockUnavailable': string;
|
|
204
|
+
'panel.dockFailed': string;
|
|
169
205
|
'panel.loading': string;
|
|
170
206
|
'panel.readFailed': string;
|
|
171
207
|
'panel.group.current': string;
|
|
@@ -215,6 +251,8 @@ export declare const en: {
|
|
|
215
251
|
'settings.diffViewDesc': string;
|
|
216
252
|
'settings.diffPreview': string;
|
|
217
253
|
'panel.keyDesc': string;
|
|
254
|
+
'panel.shortcutNone': string;
|
|
255
|
+
'panel.shortcutReset': string;
|
|
218
256
|
'panel.key.jumpUp': string;
|
|
219
257
|
'panel.key.jumpDown': string;
|
|
220
258
|
'panel.key.copyRef': string;
|
|
@@ -227,6 +265,10 @@ export declare const en: {
|
|
|
227
265
|
'panel.key.redo': string;
|
|
228
266
|
'panel.key.cycleNext': string;
|
|
229
267
|
'panel.key.cyclePrev': string;
|
|
268
|
+
'panel.key.coverLeft': string;
|
|
269
|
+
'panel.key.coverComposer': string;
|
|
270
|
+
'panel.key.coverRight': string;
|
|
271
|
+
'panel.key.coverTop': string;
|
|
230
272
|
'settings.tabLabel': string;
|
|
231
273
|
'row.create': string;
|
|
232
274
|
'row.failed': string;
|
|
@@ -245,6 +287,8 @@ export declare const en: {
|
|
|
245
287
|
'action.nextDiff': string;
|
|
246
288
|
'action.showFileList': string;
|
|
247
289
|
'action.hideFileList': string;
|
|
290
|
+
'action.fileListFloatOn': string;
|
|
291
|
+
'action.fileListFloatOff': string;
|
|
248
292
|
'action.viewSplit': string;
|
|
249
293
|
'action.viewUnified': string;
|
|
250
294
|
'action.viewPreview': string;
|
|
@@ -272,9 +316,31 @@ export declare const en: {
|
|
|
272
316
|
'panel.importFailed': string;
|
|
273
317
|
'action.search': string;
|
|
274
318
|
'action.settings': string;
|
|
275
|
-
'action.
|
|
276
|
-
'action.
|
|
319
|
+
'action.presentation': string;
|
|
320
|
+
'action.presentationCurrent': string;
|
|
321
|
+
'action.cover': string;
|
|
322
|
+
'cover.left': string;
|
|
323
|
+
'cover.right': string;
|
|
324
|
+
'cover.composer': string;
|
|
325
|
+
'cover.top': string;
|
|
326
|
+
'cover.leftDesc': string;
|
|
327
|
+
'cover.topDesc': string;
|
|
328
|
+
'cover.rightDesc': string;
|
|
329
|
+
'cover.composerDesc': string;
|
|
330
|
+
'settings.cover': string;
|
|
331
|
+
'settings.coverDesc': string;
|
|
332
|
+
'cover.on': string;
|
|
333
|
+
'cover.off': string;
|
|
334
|
+
'cover.notice': string;
|
|
335
|
+
'action.presentationFloat': string;
|
|
336
|
+
'action.presentationDock': string;
|
|
337
|
+
'action.presentationDocked': string;
|
|
277
338
|
'action.close': string;
|
|
339
|
+
'action.closeHint': string;
|
|
340
|
+
'action.closeHintEsc': string;
|
|
341
|
+
'action.summonHint': string;
|
|
342
|
+
'action.retry': string;
|
|
343
|
+
'panel.crashed': string;
|
|
278
344
|
'status.kept': string;
|
|
279
345
|
'status.reverted': string;
|
|
280
346
|
'status.missing': string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the review panel was left, for this page's lifetime.
|
|
3
|
+
*
|
|
4
|
+
* Closing the panel is not "done reviewing": reopening it should put the reader
|
|
5
|
+
* back on the file they were in, at the offset they were at. That is a fact about
|
|
6
|
+
* this visit rather than a stored preference, so it lives in module state — which
|
|
7
|
+
* also gives the two mounts that show the panel (the floating overlay and the
|
|
8
|
+
* docked tab) one shared memory, so switching presentation does not lose the
|
|
9
|
+
* place, and a reload starts clean.
|
|
10
|
+
*
|
|
11
|
+
* Keyed by session: two sessions have their own pending lists, and one session's
|
|
12
|
+
* panel must not resume another's file.
|
|
13
|
+
*
|
|
14
|
+
* @module dsh-diff-approval/client/panel-memory
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Remember that a session's panel was left showing one file, and where in it.
|
|
18
|
+
* @param sessionId - the session the panel was reviewing; nothing is recorded
|
|
19
|
+
* without one.
|
|
20
|
+
* @param view - the pending entry that was open, and the code view's scrollTop.
|
|
21
|
+
* An absent offset *forgets* any remembered place for that file, which is what
|
|
22
|
+
* a caller that is about to open it at its first change wants: a mount that only
|
|
23
|
+
* appears afterwards then lands there too, instead of on a stale offset.
|
|
24
|
+
*/
|
|
25
|
+
export declare function rememberPanelView(sessionId: string | undefined, view: {
|
|
26
|
+
fileId: string;
|
|
27
|
+
scrollTop?: number | undefined;
|
|
28
|
+
}): void;
|
|
29
|
+
/**
|
|
30
|
+
* The file a session's panel was last showing.
|
|
31
|
+
* @param sessionId - the session, if any.
|
|
32
|
+
* @returns the pending entry id, or undefined when that session's panel has never
|
|
33
|
+
* closed on a file.
|
|
34
|
+
*/
|
|
35
|
+
export declare function lastPanelFile(sessionId: string | undefined): string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* How far down one file was left, when it has been left before.
|
|
38
|
+
* @param sessionId - the session, if any.
|
|
39
|
+
* @param fileId - the pending entry id.
|
|
40
|
+
* @returns the remembered scrollTop, or undefined when there is none.
|
|
41
|
+
*/
|
|
42
|
+
export declare function panelFileOffset(sessionId: string | undefined, fileId: string): number | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Forget every session's record. A page reload does this by itself; a test calls
|
|
45
|
+
* it to start from a clean page rather than inheriting the previous case's place.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resetPanelMemory(): void;
|