dsh-git-ui 0.0.2 → 0.1.1
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 +55 -20
- package/README.zh.md +55 -21
- package/cordis.patch.yml +1 -1
- package/lib/client.js +36 -35
- package/lib/client.js.map +4 -4
- package/lib/contracts/host-endpoints.d.ts +27 -0
- package/lib/host/actions.d.ts +22 -2
- package/lib/host/core.d.ts +7 -1
- package/lib/host/index.d.ts +25 -15
- package/lib/host/index.js +419 -53
- package/lib/host/index.js.map +4 -4
- package/lib/host/parser.d.ts +37 -1
- package/lib/host/queries.d.ts +17 -0
- package/lib/host/types.d.ts +131 -1
- package/package.json +1 -1
- package/src/adapters/dsh/client-adapter.ts +120 -0
- package/src/adapters/dsh/types/cordis.d.ts +48 -0
- package/src/adapters/dsh/types/typert-protocol.d.ts +81 -0
- package/src/adapters/dsh/types/ui-primitives.d.ts +45 -0
- package/src/adapters/dsh/ui-primitives.ts +16 -0
- package/src/client/GitCenter.tsx +1414 -149
- package/src/client/GitPill.tsx +282 -67
- package/src/client/changes-diff.ts +63 -0
- package/src/client/controller.ts +34 -31
- package/src/client/error-text.ts +21 -0
- package/src/client/file-tree.ts +101 -0
- package/src/client/git-graph.ts +188 -0
- package/src/client/icons.tsx +292 -0
- package/src/client/index.ts +38 -134
- package/src/client/locales.ts +124 -0
- package/src/client/popup-close.ts +19 -0
- package/src/client/remote.ts +85 -3
- package/src/client/select-menu.tsx +113 -0
- package/src/client/side-by-side.ts +150 -0
- package/src/client/styles.ts +1375 -86
- package/src/client/time-format.ts +32 -0
- package/src/contracts/client-platform.ts +147 -0
- package/src/contracts/host-endpoints.ts +58 -0
- package/src/contracts/plugin-activation.ts +129 -0
- package/src/contracts/ui-context.tsx +28 -0
- package/src/contracts/ui-primitives.ts +48 -0
- package/src/host/actions.ts +66 -15
- package/src/host/core.ts +9 -2
- package/src/host/index.ts +60 -54
- package/src/host/parser.ts +155 -12
- package/src/host/queries.ts +289 -0
- package/src/host/types.ts +104 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh Client 平台适配:将 Cordis Context 翻译为 `ClientPlatform` 接口。
|
|
3
|
+
*
|
|
4
|
+
* 本文件是 client 端**唯一**知道 Cordis / typert 插件生命周期的地方。
|
|
5
|
+
* dsh 升级导致插件 API 变更时,只需修改此文件。
|
|
6
|
+
*
|
|
7
|
+
* 关键适配点:
|
|
8
|
+
* - `mountRemoteAndGetService`:处理 typert Remote 挂载 + Cordis child fiber
|
|
9
|
+
* - `registerSlotEntry`:翻译 slot 注册两步模式(inject + register)
|
|
10
|
+
* - `onEvent` / `effect`:直接映射 Cordis 同名方法
|
|
11
|
+
*/
|
|
12
|
+
import type { TypertRemoteContribution } from '@deepseek-ai/dsh-typert-protocol'
|
|
13
|
+
import type { ReactNode } from 'react'
|
|
14
|
+
import type { ClientPlatform, LocaleDicts, RemoteContribution, SlotEntryDescriptor, GitInjected, GitRemoteLike } from '../../contracts/client-platform.ts'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Cordis Context 的结构化切片。
|
|
18
|
+
*
|
|
19
|
+
* 只声明本适配器实际使用的属性和方法。dsh 升级时若 API 变更,
|
|
20
|
+
* 只需更新此接口和下方的适配实现。
|
|
21
|
+
*/
|
|
22
|
+
export interface DshClientContext {
|
|
23
|
+
/** 订阅应用事件(自动在 fiber 卸载时清理)。 */
|
|
24
|
+
on(event: string, listener: (...args: never[]) => void): (() => void) | void
|
|
25
|
+
/** 注册副作用(自动在 fiber 卸载时清理)。 */
|
|
26
|
+
effect(callback: () => void | (() => void | Promise<void>), label?: string): void
|
|
27
|
+
/** Remote 服务:挂载贡献 + 访问已挂载的命名空间。 */
|
|
28
|
+
remote: {
|
|
29
|
+
$mount(contribution: TypertRemoteContribution): Promise<() => Promise<void>>
|
|
30
|
+
gitInfo: GitRemoteLike
|
|
31
|
+
}
|
|
32
|
+
/** Slot 注册表。 */
|
|
33
|
+
slots: {
|
|
34
|
+
inject(slotName: string, provider: () => (() => void) | void): void
|
|
35
|
+
register(
|
|
36
|
+
options: {
|
|
37
|
+
readonly name: string
|
|
38
|
+
readonly id: string
|
|
39
|
+
readonly order?: number
|
|
40
|
+
readonly locale?: string
|
|
41
|
+
readonly inject: (sessionId: string) => GitInjected
|
|
42
|
+
},
|
|
43
|
+
component: unknown,
|
|
44
|
+
): () => void
|
|
45
|
+
}
|
|
46
|
+
/** 国际化服务。 */
|
|
47
|
+
locale: {
|
|
48
|
+
register(namespace: string, dictionaries: LocaleDicts): void
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 将我们的 `RemoteContribution` 转换为 dsh 的 `TypertRemoteContribution`。
|
|
54
|
+
*
|
|
55
|
+
* 当前两者结构相同(字段一一对应),直接透传。若未来 typert 协议变更,
|
|
56
|
+
* 在此处做字段映射。
|
|
57
|
+
*/
|
|
58
|
+
function toTypertContribution(ours: RemoteContribution): TypertRemoteContribution {
|
|
59
|
+
return ours as TypertRemoteContribution
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 将 Cordis Context 适配为 `ClientPlatform`。
|
|
64
|
+
*
|
|
65
|
+
* 注意:`mountRemoteAndGetService` 的实现利用了 Cordis 的特性——
|
|
66
|
+
* 挂载完成后,`ctx.remote.gitInfo` 立即可用。无需 child fiber 模式
|
|
67
|
+
* (该模式是 Cordis 内部为避免 inject 死锁的优化,此处我们在 mount
|
|
68
|
+
* 完成后直接访问,效果等价)。
|
|
69
|
+
*/
|
|
70
|
+
export function adaptDshClientContext(ctx: DshClientContext): ClientPlatform {
|
|
71
|
+
return {
|
|
72
|
+
registerLocale(namespace, dicts) {
|
|
73
|
+
ctx.locale.register(namespace, dicts)
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
async mountRemoteAndGetService(contribution, namespace) {
|
|
77
|
+
// 挂载 Remote 贡献
|
|
78
|
+
await ctx.remote.$mount(toTypertContribution(contribution))
|
|
79
|
+
// 挂载完成后,命名空间服务立即可用
|
|
80
|
+
// 当前只有 'gitInfo' 命名空间,直接返回
|
|
81
|
+
if (namespace === 'gitInfo') {
|
|
82
|
+
return ctx.remote.gitInfo
|
|
83
|
+
}
|
|
84
|
+
throw new Error(`adaptDshClientContext: 未知命名空间 "${namespace}"`)
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
registerSlotEntry(options, component) {
|
|
88
|
+
// Cordis slot 注册是两步模式:inject + register
|
|
89
|
+
// inject 注册一个 provider 工厂,register 在 provider 内调用
|
|
90
|
+
let disposeRegister: (() => void) | undefined
|
|
91
|
+
ctx.slots.inject(options.name, () => {
|
|
92
|
+
disposeRegister = ctx.slots.register(
|
|
93
|
+
{
|
|
94
|
+
name: options.name,
|
|
95
|
+
id: options.id,
|
|
96
|
+
order: options.order,
|
|
97
|
+
locale: options.locale,
|
|
98
|
+
inject: options.inject,
|
|
99
|
+
},
|
|
100
|
+
component,
|
|
101
|
+
)
|
|
102
|
+
return () => {
|
|
103
|
+
disposeRegister?.()
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
// 返回释放函数:调用时注销 slot 条目
|
|
107
|
+
return () => {
|
|
108
|
+
disposeRegister?.()
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
onEvent(event, listener) {
|
|
113
|
+
return ctx.on(event, listener as (...args: never[]) => void)
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
effect(callback, label) {
|
|
117
|
+
ctx.effect(callback, label)
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type shim for `@deepseek-ai/cordis`.
|
|
3
|
+
*
|
|
4
|
+
* The npm ecosystem does not carry the full dsh package chain (`@deepseek-ai/cordis`
|
|
5
|
+
* depends on unpublished packages), so this package cannot install cordis as a
|
|
6
|
+
* dev dependency. At runtime the host dsh installation provides the real cordis
|
|
7
|
+
* through peer dependency resolution; this file only supplies the compile-time
|
|
8
|
+
* surface used by dsh-git-ui (shapes copied from the deepseek-harness vendor
|
|
9
|
+
* tree, 0.1.0-rc.x). Keep this file in sync with the subset actually used.
|
|
10
|
+
*/
|
|
11
|
+
declare module '@deepseek-ai/cordis' {
|
|
12
|
+
/** Minimal Context surface used by host/client halves. */
|
|
13
|
+
export interface Context {
|
|
14
|
+
readonly root: Context
|
|
15
|
+
/** Service registry reflection (service list for gateway SRC discovery). */
|
|
16
|
+
readonly reflect: {
|
|
17
|
+
readonly props: Record<string, { type?: string; [key: string]: unknown }>
|
|
18
|
+
provide(name: string, value: unknown, check?: unknown): void
|
|
19
|
+
}
|
|
20
|
+
/** Look up an optional service by key (undefined when not provided). */
|
|
21
|
+
get<T = unknown>(key: string): T | undefined
|
|
22
|
+
/** Declare services this plugin needs before activation. */
|
|
23
|
+
inject(keys: readonly string[], callback: (ctx: Context) => void): void
|
|
24
|
+
/** Register a side effect with auto-cleanup on fiber dispose. */
|
|
25
|
+
effect(callback: () => void | (() => void | Promise<void>), label?: string): void
|
|
26
|
+
/** Subscribe to an application event; returns an unsubscribe function. */
|
|
27
|
+
on<K extends string>(event: K, listener: (...args: never[]) => void): (() => void) | void
|
|
28
|
+
/** Start a plugin fiber in the current context. */
|
|
29
|
+
plugin(plugin: unknown, config?: unknown): Promise<unknown> & { dispose(): Promise<void> }
|
|
30
|
+
[key: string]: unknown
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Base class for services that expose a named API on `ctx`. */
|
|
34
|
+
export abstract class Service<out T = never> {
|
|
35
|
+
static readonly init: unique symbol
|
|
36
|
+
static readonly check: unique symbol
|
|
37
|
+
static readonly config: unique symbol
|
|
38
|
+
static readonly invoke: unique symbol
|
|
39
|
+
static readonly extend: unique symbol
|
|
40
|
+
static readonly tracker: unique symbol
|
|
41
|
+
static readonly resolveConfig: unique symbol
|
|
42
|
+
/** The service name this instance is registered under. */
|
|
43
|
+
public name!: string
|
|
44
|
+
/** Owning context (registered via `super(ctx, name)`). */
|
|
45
|
+
readonly ctx: Context
|
|
46
|
+
constructor(ctx: Context, name: string)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type shim for `@deepseek-ai/dsh-typert-protocol`.
|
|
3
|
+
*
|
|
4
|
+
* Same rationale as `cordis.d.ts`: the runtime implementation is provided by the
|
|
5
|
+
* host dsh installation (peer dependency); this file supplies the compile-time
|
|
6
|
+
* surface used by dsh-git-ui (shapes copied from the deepseek-harness
|
|
7
|
+
* `packages/typert/protocol` source, 0.1.0-rc.x).
|
|
8
|
+
*/
|
|
9
|
+
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
10
|
+
import type { Context, Service } from '@deepseek-ai/cordis'
|
|
11
|
+
|
|
12
|
+
/** Visible declaration that one Service participates in Typert Gateway export. */
|
|
13
|
+
export interface TypertGatewayBinding<ServiceT extends object = object> {
|
|
14
|
+
readonly service: ServiceT
|
|
15
|
+
readonly serviceKey: string
|
|
16
|
+
readonly namespace: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Cordis Service base that exposes its registered name through Typert Gateway. */
|
|
20
|
+
export abstract class TypertRemoteService<out T = never> extends Service<T> {
|
|
21
|
+
/** Visible binding consumed by the Gateway's source-mode discovery. */
|
|
22
|
+
readonly typertRemote: TypertGatewayBinding<this>
|
|
23
|
+
protected constructor(ctx: Context, serviceKey: string, options?: { namespace?: string })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Standard method decorator marking a Remote endpoint. */
|
|
27
|
+
export type RemoteMethodDecorator = <This extends object, Args extends unknown[], Result>(
|
|
28
|
+
method: (this: This, ...args: Args) => Result,
|
|
29
|
+
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
|
|
30
|
+
) => void
|
|
31
|
+
|
|
32
|
+
/** Mark one public instance method as a direct Remote invocation. */
|
|
33
|
+
export function Remote(exportName: string): RemoteMethodDecorator
|
|
34
|
+
|
|
35
|
+
/** Read Remote markers attached to a live Service by decorator initializers. */
|
|
36
|
+
export function remoteMethods(service: object): readonly {
|
|
37
|
+
readonly method: string
|
|
38
|
+
readonly exportName?: string
|
|
39
|
+
readonly invocation: { readonly kind: 'direct' } | { readonly kind: 'context'; readonly context: string }
|
|
40
|
+
}[]
|
|
41
|
+
|
|
42
|
+
/** One codec for a Remote parameter/result (strict = zod-validated). */
|
|
43
|
+
export interface TypertCodec {
|
|
44
|
+
readonly mode: 'strict'
|
|
45
|
+
readonly typeSymbol: string
|
|
46
|
+
readonly schema: { parse(value: unknown): unknown }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** One Remote method descriptor inside a contribution. */
|
|
50
|
+
export interface InvocationDescriptor {
|
|
51
|
+
readonly id: string
|
|
52
|
+
readonly service: string
|
|
53
|
+
readonly namespace: string
|
|
54
|
+
readonly method: string
|
|
55
|
+
readonly invocation: { readonly kind: 'direct' } | { readonly kind: 'context'; readonly context: string; readonly wire: string }
|
|
56
|
+
readonly parameters: readonly {
|
|
57
|
+
readonly name: string
|
|
58
|
+
readonly wire: string
|
|
59
|
+
readonly source: 'json'
|
|
60
|
+
readonly codec: TypertCodec
|
|
61
|
+
}[]
|
|
62
|
+
/**
|
|
63
|
+
* Optional caller-supplied cancellation slot: the descriptor carries an
|
|
64
|
+
* AbortSignal in the trailing business position, propagated over the
|
|
65
|
+
* carrier so an aborted call also aborts the host-side work.
|
|
66
|
+
*/
|
|
67
|
+
readonly cancellation?: { readonly parameter: 'signal' }
|
|
68
|
+
readonly result: TypertCodec
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Client-side contribution mounted via `ctx.remote.$mount`. */
|
|
72
|
+
export interface TypertRemoteContribution {
|
|
73
|
+
readonly package: string
|
|
74
|
+
readonly descriptors: readonly InvocationDescriptor[]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The `remote` service face mounted by the api-gateway client half. */
|
|
78
|
+
export interface TypertClientRemote {
|
|
79
|
+
$mount(contribution: TypertRemoteContribution): Promise<() => Promise<void>>
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type shim for `@deepseek-ai/dsh-client-ui-primitives`.
|
|
3
|
+
*
|
|
4
|
+
* Platform module (externalized in the client bundle, provided by the host
|
|
5
|
+
* loader table); this package only needs the `Modal` dialog, the `Button`
|
|
6
|
+
* atom and the transient `Toast` banner, so the surface is declared here by
|
|
7
|
+
* hand (mirrored from the ui-primitives source, 0.1.0-rc.x).
|
|
8
|
+
*/
|
|
9
|
+
declare module '@deepseek-ai/dsh-client-ui-primitives' {
|
|
10
|
+
import type { ButtonHTMLAttributes, ReactNode } from 'react'
|
|
11
|
+
|
|
12
|
+
/** Controlled full-viewport dialog over a blurred mask (Escape/mask close). */
|
|
13
|
+
export function Modal(props: {
|
|
14
|
+
open: boolean
|
|
15
|
+
onClose: () => void
|
|
16
|
+
title: string
|
|
17
|
+
closeLabel?: string
|
|
18
|
+
description?: string
|
|
19
|
+
children?: ReactNode
|
|
20
|
+
footer?: ReactNode
|
|
21
|
+
className?: string
|
|
22
|
+
contentClassName?: string
|
|
23
|
+
headless?: boolean
|
|
24
|
+
}): ReactNode
|
|
25
|
+
|
|
26
|
+
/** Token-styled button atom (native button attributes pass through). */
|
|
27
|
+
export function Button(props: {
|
|
28
|
+
variant?: 'primary' | 'ghost' | 'outline' | 'toolbar'
|
|
29
|
+
size?: 'md' | 'sm'
|
|
30
|
+
icon?: ReactNode
|
|
31
|
+
className?: string | undefined
|
|
32
|
+
children?: ReactNode
|
|
33
|
+
} & ButtonHTMLAttributes<HTMLButtonElement>): ReactNode
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Transient top-center banner: slides in, holds ~3s, fades out, then calls
|
|
37
|
+
* `onDone` so the owner can unmount it.
|
|
38
|
+
*/
|
|
39
|
+
export function Toast(props: {
|
|
40
|
+
text: string
|
|
41
|
+
icon?: ReactNode
|
|
42
|
+
anchor?: HTMLElement | null
|
|
43
|
+
onDone: () => void
|
|
44
|
+
}): ReactNode
|
|
45
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh UI 基础组件适配:将 `@deepseek-ai/dsh-client-ui-primitives` 的
|
|
3
|
+
* Modal / Button / Toast 包装为我们的 `UIPrimitives` 接口。
|
|
4
|
+
*
|
|
5
|
+
* 本文件是 client 端**唯一** import `@deepseek-ai/dsh-client-ui-primitives`
|
|
6
|
+
* 的地方。dsh 升级导致组件 API 变更时,只需修改此文件。
|
|
7
|
+
*/
|
|
8
|
+
import { Modal as DshModal, Button as DshButton, Toast as DshToast } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
9
|
+
import type { UIPrimitives } from '../../contracts/ui-primitives.ts'
|
|
10
|
+
|
|
11
|
+
/** dsh 宿主提供的 UI 基础组件实现。 */
|
|
12
|
+
export const dshUIPrimitives: UIPrimitives = {
|
|
13
|
+
Modal: DshModal,
|
|
14
|
+
Button: DshButton,
|
|
15
|
+
Toast: DshToast,
|
|
16
|
+
}
|