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.
Files changed (47) hide show
  1. package/README.md +55 -20
  2. package/README.zh.md +55 -21
  3. package/cordis.patch.yml +1 -1
  4. package/lib/client.js +36 -35
  5. package/lib/client.js.map +4 -4
  6. package/lib/contracts/host-endpoints.d.ts +27 -0
  7. package/lib/host/actions.d.ts +22 -2
  8. package/lib/host/core.d.ts +7 -1
  9. package/lib/host/index.d.ts +25 -15
  10. package/lib/host/index.js +419 -53
  11. package/lib/host/index.js.map +4 -4
  12. package/lib/host/parser.d.ts +37 -1
  13. package/lib/host/queries.d.ts +17 -0
  14. package/lib/host/types.d.ts +131 -1
  15. package/package.json +1 -1
  16. package/src/adapters/dsh/client-adapter.ts +120 -0
  17. package/src/adapters/dsh/types/cordis.d.ts +48 -0
  18. package/src/adapters/dsh/types/typert-protocol.d.ts +81 -0
  19. package/src/adapters/dsh/types/ui-primitives.d.ts +45 -0
  20. package/src/adapters/dsh/ui-primitives.ts +16 -0
  21. package/src/client/GitCenter.tsx +1414 -149
  22. package/src/client/GitPill.tsx +282 -67
  23. package/src/client/changes-diff.ts +63 -0
  24. package/src/client/controller.ts +34 -31
  25. package/src/client/error-text.ts +21 -0
  26. package/src/client/file-tree.ts +101 -0
  27. package/src/client/git-graph.ts +188 -0
  28. package/src/client/icons.tsx +292 -0
  29. package/src/client/index.ts +38 -134
  30. package/src/client/locales.ts +124 -0
  31. package/src/client/popup-close.ts +19 -0
  32. package/src/client/remote.ts +85 -3
  33. package/src/client/select-menu.tsx +113 -0
  34. package/src/client/side-by-side.ts +150 -0
  35. package/src/client/styles.ts +1375 -86
  36. package/src/client/time-format.ts +32 -0
  37. package/src/contracts/client-platform.ts +147 -0
  38. package/src/contracts/host-endpoints.ts +58 -0
  39. package/src/contracts/plugin-activation.ts +129 -0
  40. package/src/contracts/ui-context.tsx +28 -0
  41. package/src/contracts/ui-primitives.ts +48 -0
  42. package/src/host/actions.ts +66 -15
  43. package/src/host/core.ts +9 -2
  44. package/src/host/index.ts +60 -54
  45. package/src/host/parser.ts +155 -12
  46. package/src/host/queries.ts +289 -0
  47. 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
+ }