@young1lin/dsh-ui-gitworkbench 0.1.11 → 0.1.13

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 (36) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/CHANGELOG_EN.md +28 -0
  4. package/README.md +100 -50
  5. package/README_EN.md +2 -1
  6. package/lib/client.js +6902 -6256
  7. package/package.json +1 -1
  8. package/src/client/ChangesFileTree.tsx +550 -0
  9. package/src/client/ChromeGlyph.tsx +27 -0
  10. package/src/client/CodeEditor.tsx +129 -3
  11. package/src/client/CommitHistory.tsx +1001 -0
  12. package/src/client/DiffViews.tsx +1070 -0
  13. package/src/client/GitWorkbenchPanel.module.css +15 -2513
  14. package/src/client/GitWorkbenchPanel.tsx +37 -3959
  15. package/src/client/PaneDivider.tsx +74 -0
  16. package/src/client/WorkbenchControls.tsx +1047 -0
  17. package/src/client/WorktreeGlyph.tsx +24 -0
  18. package/src/client/cm-search-theme.ts +250 -0
  19. package/src/client/diff-nav.ts +59 -0
  20. package/src/client/git-workbench-types.ts +252 -0
  21. package/src/client/locales.ts +14 -8
  22. package/src/client/row-window.ts +23 -0
  23. package/src/client/search-count.ts +125 -0
  24. package/src/client/side-rows.ts +66 -0
  25. package/src/client/styles/changes.css +505 -0
  26. package/src/client/styles/controls.css +236 -0
  27. package/src/client/styles/environment.css +89 -0
  28. package/src/client/styles/files.css +113 -0
  29. package/src/client/styles/history-filters.css +400 -0
  30. package/src/client/styles/history.css +276 -0
  31. package/src/client/styles/image.css +67 -0
  32. package/src/client/styles/operations.css +179 -0
  33. package/src/client/styles/shell.css +431 -0
  34. package/src/client/styles/themes.css +224 -0
  35. package/src/client/use-change-nav.ts +6 -5
  36. package/src/client/use-row-window.ts +55 -0
@@ -0,0 +1,236 @@
1
+ /* ============================ one button vocabulary ============================
2
+ *
3
+ * Every clickable control in the drawer is one of two shapes, and which one it
4
+ * gets is decided by WHERE IT LIVES, never by what it does: drawer-level chrome
5
+ * (header actions, ref chips, menu triggers) is a PILL, and anything inside a
6
+ * panel or pane is a ROUNDED RECT. They differ in shape and nothing else — same
7
+ * height, same type size, same hover, same focus ring, same transition.
8
+ *
9
+ * Grouping the selectors is what enforces that. Each class used to spell out
10
+ * its own padding, radius and font-size, and the same "small secondary button"
11
+ * had drifted to four heights and three radii across one screen.
12
+ */
13
+ .btn, .miniBtn, .treeIcon, .commitCopy, .scopeBtn, .refButton, .funnelButton, .funnelPreset, .layoutButton {
14
+ display: inline-flex; align-items: center; justify-content: center; gap: 6px;
15
+ box-sizing: border-box;
16
+ flex: none;
17
+ height: var(--gs-h-control);
18
+ padding: var(--gs-pad-control);
19
+ border: 1px solid var(--gs-border);
20
+ border-radius: var(--gs-r-control);
21
+ background: transparent;
22
+ color: var(--gs-fg-dim);
23
+ font-family: inherit;
24
+ font-size: var(--gs-t-ui);
25
+ line-height: 1;
26
+ white-space: nowrap;
27
+ cursor: pointer;
28
+ transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
29
+ }
30
+ .btn:hover, .miniBtn:hover, .treeIcon:hover, .commitCopy:hover,
31
+ .scopeBtn:hover, .refButton:hover, .funnelButton:hover, .funnelPreset:hover, .layoutButton:hover {
32
+ background: var(--gs-raise);
33
+ color: var(--gs-fg);
34
+ border-color: var(--gs-fg-fainter);
35
+ }
36
+ .btn:focus-visible, .miniBtn:focus-visible, .treeIcon:focus-visible,
37
+ .commitCopy:focus-visible, .scopeBtn:focus-visible, .refButton:focus-visible,
38
+ .funnelPreset:focus-visible, .layoutButton:focus-visible {
39
+ outline: 2px solid var(--gs-accent);
40
+ outline-offset: 1px;
41
+ }
42
+
43
+ /* Pills are for drawer-level chrome: the header's actions, the ref chips, and
44
+ anything that opens a menu — dsh spells its own dropdown triggers
45
+ ("Code mode ⌄", "glm-5.3 ⌄") as pills, so a trigger that is not one reads as
46
+ foreign. Controls that live INSIDE a panel or pane stay 8px rects. */
47
+ .btn, .refButton { border-radius: var(--gs-r-pill); }
48
+
49
+ /* Pane tools give their vertical space back to the diff; the date presets sit
50
+ inside a 320px popover, so they take the compact height too. */
51
+ .treeIcon, .commitCopy, .funnelPreset, .layoutButton {
52
+ height: var(--gs-h-compact);
53
+ padding: var(--gs-pad-compact);
54
+ font-size: var(--gs-t-dense);
55
+ }
56
+ /* Expand / collapse all are the tree's own chevron at its two angles: the
57
+ button shows the state it puts every row into. Icon-only because at the 170px
58
+ the pane drags down to, two more text buttons cannot share a row with the
59
+ count — and these two are the pair a tree header always carries. */
60
+ .treeIcon { width: var(--gs-h-compact); padding: 0; }
61
+ /* The arrangement switch: two square icon buttons at the end of the commit
62
+ list's head, side by side with no gap so they read as one control. Icon-only
63
+ for the same reason the tree's are — the head already carries a title, a
64
+ funnel and a search box, and at the 190px the pane drags down to there is no
65
+ room for two more words. */
66
+ /* Pushed to the far end of History's toolbar row, away from the ref picker:
67
+ one changes WHAT is listed, the other only where it is drawn. */
68
+ .layoutSwitch { display: inline-flex; flex: none; gap: 2px; margin-left: auto; }
69
+ .layoutButton { width: var(--gs-h-compact); padding: 0; }
70
+ /* Qualified, like `.treeIconOn`: the shared vocabulary sets `color` at the
71
+ same specificity a few rules above. */
72
+ .layoutButton.layoutButtonOn { color: var(--gs-accent); border-color: var(--gs-accent); }
73
+ .layoutGlyph { display: block; }
74
+ /* Qualified, like `.funnelButtonActive`: this sits inside the shared button
75
+ vocabulary, which sets `color` at the same specificity a few lines above. */
76
+ .treeIcon.treeIconOn { color: var(--gs-accent); border-color: var(--gs-accent); }
77
+ .treeIconGlyph { display: block; font-size: var(--gs-t-meta); line-height: 1; }
78
+ .treeIconDown { transform: rotate(90deg); }
79
+
80
+ /* The scope toggle splits its row in two equal halves, so it opts out of the
81
+ vocabulary's `flex: none`. Declared here rather than beside `.scopeRow`
82
+ because it has to win against the grouped rule above by source order. */
83
+ .scopeBtn { flex: 1 1 0; }
84
+
85
+ /* The single "this one is selected" idiom: accent text, in an accent tint,
86
+ inside an accent border. Everything that can be the chosen one is here — a
87
+ tree leaf, a commit, a ref, a palette, a segment, a funnel tab, a date
88
+ bound, a preset — because the drawer had reached three spellings of one
89
+ meaning (this chip, a filled panel, a left accent bar) and showed two of
90
+ them at once whenever the Changes tree sat beside the history.
91
+
92
+ A modifier applied as `${css.base} ${css.mod}` carries the SAME specificity
93
+ as its base, so source order decides. Every base named here is declared
94
+ above this rule; `.fbRow.fbRowActive` is the one exception and is written
95
+ qualified, because `files.css` is imported after this file. */
96
+ .scopeBtnActive, .miniBtnPrimary, .segmentActive, .refRowActive, .paletteRowActive,
97
+ .fileActive, .commitActive, .funnelTabActive, .funnelBoundBtnActive, .funnelPresetActive,
98
+ .fbRow.fbRowActive {
99
+ border-color: var(--gs-accent-border);
100
+ background: var(--gs-accent-bg);
101
+ color: var(--gs-accent);
102
+ font-weight: 600;
103
+ }
104
+
105
+ /* Icon-only chrome: square, so four of them read as a control cluster rather
106
+ than four pills of drifting width. Height, border, radius and hover still
107
+ come from the vocabulary — only the box changes shape. */
108
+ .btnIcon { width: var(--gs-h-control); padding: 0; }
109
+ .btnIcon svg { display: block; }
110
+ /* Close is the one destructive control in the row, and every window on the
111
+ machine says so with red on hover. Quiet until then: a permanently red X in
112
+ a header would read as an error state. */
113
+ .btnClose:hover:not(:disabled) {
114
+ border-color: var(--gs-del); background: var(--gs-del-bg); color: var(--gs-del);
115
+ }
116
+
117
+ /* Sync actions that have something to report.
118
+ *
119
+ * These three variants set COLOUR ONLY — height, padding, radius and type size
120
+ * still come from the vocabulary above, so a loud Push is the same object as a
121
+ * quiet one and not a second button that happens to look similar.
122
+ *
123
+ * Fetch deliberately has no variant. It writes nothing, so it has no state to
124
+ * announce, and leaving one of the three permanently quiet is what makes the
125
+ * other two register as signal rather than decoration. */
126
+ .btnBehind {
127
+ border-color: var(--gs-warn); background: var(--gs-warn-bg); color: var(--gs-warn);
128
+ font-weight: 600;
129
+ }
130
+ .btnBehind:hover:not(:disabled) { background: var(--gs-warn-bg); color: var(--gs-warn); border-color: var(--gs-warn); }
131
+ .btnAhead {
132
+ border-color: var(--gs-add); background: var(--gs-add-bg); color: var(--gs-add);
133
+ font-weight: 600;
134
+ }
135
+ .btnAhead:hover:not(:disabled) { background: var(--gs-add-bg); color: var(--gs-add); border-color: var(--gs-add); }
136
+ /* Publishing a branch that exists nowhere else is the one moment this bar has a
137
+ primary action, so it gets the solid fill Commit uses — and only then. */
138
+ .btnPrimary {
139
+ border-color: var(--gs-accent); background: var(--gs-accent); color: var(--gs-on-accent);
140
+ font-weight: 600;
141
+ }
142
+ .btnPrimary:hover:not(:disabled) { background: var(--gs-accent); color: var(--gs-on-accent); border-color: var(--gs-accent); }
143
+
144
+ /* The count riding inside Pull / Push. Reads as part of the label rather than a
145
+ badge stuck on it: same colour, tabular, just set apart by a hairline. */
146
+ .btnCount {
147
+ margin-left: 1px; padding-left: 6px;
148
+ border-left: 1px solid currentColor;
149
+ font-variant-numeric: tabular-nums;
150
+ opacity: 0.85;
151
+ }
152
+
153
+ /* Pull and its strategy are one control. The picker is Pull's argument, not a
154
+ third action, so the two are welded: shared border, one outer radius. */
155
+ .pullGroup { display: inline-flex; flex: none; align-items: center; }
156
+ .pullGroup > .refPicker > .refButton {
157
+ border-top-right-radius: 0; border-bottom-right-radius: 0;
158
+ border-right-color: transparent;
159
+ }
160
+ .pullGroup > .btn {
161
+ margin-left: -1px;
162
+ border-top-left-radius: 0; border-bottom-left-radius: 0;
163
+ }
164
+
165
+ /* Scrollbars inside the drawer.
166
+ Listing the scrolling panes by name is what let the file tree keep the
167
+ platform's own wide light scrollbar after that pane was renamed, so this
168
+ matches every descendant instead: nothing to keep in step as panes change.
169
+ `scrollbar-*` inherit, which covers Firefox from the same declaration. */
170
+ .drawer { scrollbar-width: thin; scrollbar-color: var(--gs-border) transparent; }
171
+ .drawer ::-webkit-scrollbar { width: 10px; height: 10px; }
172
+ .drawer ::-webkit-scrollbar-track, .drawer ::-webkit-scrollbar-corner { background: transparent; }
173
+ .drawer ::-webkit-scrollbar-thumb {
174
+ /* Painted inside a transparent border so the thumb reads as a floating pill
175
+ rather than a bar welded to the pane edge. */
176
+ background: var(--gs-border);
177
+ background-clip: padding-box;
178
+ border: 3px solid transparent;
179
+ border-radius: var(--gs-r-pill);
180
+ }
181
+ .drawer ::-webkit-scrollbar-thumb:hover { background: var(--gs-fg-faint); background-clip: padding-box; }
182
+
183
+ /* Overflow counter for a commit carrying more refs than the row can show. */
184
+ .commitRefMore {
185
+ flex: none;
186
+ padding: 0 5px;
187
+ border-radius: var(--gs-r-pill);
188
+ background: var(--gs-neutral-bg);
189
+ color: var(--gs-fg-dim);
190
+ font-size: var(--gs-t-meta); line-height: 15px;
191
+ cursor: default;
192
+ }
193
+
194
+ /* ---- the roll-back confirmation ----
195
+ *
196
+ * Scoped to the drawer rather than the viewport: `.drawer` is the positioned
197
+ * ancestor, so the scrim dims the thing the question is about and leaves the
198
+ * session behind it alone. It is also the only modal surface in this plugin —
199
+ * everything else the drawer does can be done again.
200
+ */
201
+ .confirmScrim {
202
+ /* `position`, `inset` and the stacking rank live with the drawer's other
203
+ child ranks — see `.drawer > .confirmScrim`. */
204
+ display: flex; align-items: center; justify-content: center;
205
+ padding: 24px;
206
+ background: var(--gs-backdrop);
207
+ animation: gsFade 120ms ease-out;
208
+ }
209
+ .confirmBox {
210
+ display: flex; flex-direction: column; gap: 10px;
211
+ box-sizing: border-box;
212
+ width: min(440px, 100%);
213
+ padding: 18px;
214
+ border: 1px solid var(--gs-border); border-radius: var(--gs-r-drawer);
215
+ background: var(--gs-bg);
216
+ box-shadow: 0 18px 48px var(--gs-shadow);
217
+ }
218
+ .confirmTitle { color: var(--gs-fg); font-size: var(--gs-t-ui); font-weight: 600; }
219
+ /* The body carries a path, which is the one string here that can be long and
220
+ has no sensible break points of its own. */
221
+ .confirmBody {
222
+ color: var(--gs-fg-dim);
223
+ font-size: var(--gs-t-dense); line-height: 1.55;
224
+ overflow-wrap: anywhere;
225
+ }
226
+ /* Cancel first in the DOM so it takes focus and the Tab order starts on the
227
+ harmless answer; `row-reverse` puts the destructive one where the eye ends. */
228
+ .confirmActions { display: flex; flex-direction: row-reverse; gap: 8px; margin-top: 2px; }
229
+ /* Qualified rather than a bare modifier: written this way it out-specifies
230
+ `.btn` wherever either rule sits, which is the failure the drawer already
231
+ shipped once. */
232
+ .btn.btnDanger {
233
+ border-color: var(--gs-del); background: var(--gs-del); color: var(--gs-on-accent);
234
+ font-weight: 600;
235
+ }
236
+ .btn.btnDanger:hover:not(:disabled) { border-color: var(--gs-del); background: var(--gs-del); color: var(--gs-on-accent); }
@@ -0,0 +1,89 @@
1
+ /* Change inspector.
2
+ *
3
+ * The session-header card lives in dsh chrome and keeps dsh's own --dsw-* tokens,
4
+ * so it is themed by the app and needs nothing here. The DRAWER paints its own
5
+ * surface — a diff needs an add/delete/word-level/syntax palette dsh does not
6
+ * define — so it carries a self-contained palette instead.
7
+ *
8
+ * Every drawer colour resolves through a --gs-* token. The palettes below are the
9
+ * only place a literal colour appears; each theme sets the same token names, so a
10
+ * theme is a palette swap and nothing else. `data-gs-theme` on the overlay picks
11
+ * one, and the panel computes it from the theme setting (`system` follows dsh's
12
+ * `body[data-ds-dark-theme]`; light/dark pin the drawer).
13
+ *
14
+ * One exception is deliberate: the mode picker's own light/dark chips are fixed
15
+ * white and near-black, because those buttons name colours rather than wear them.
16
+ *
17
+ * `data-gs-part` attributes on the overlay, card, header, tabs and three panes are
18
+ * the stable hooks user CSS targets — class names are hashed by CSS Modules and
19
+ * change on every build, so they are not addressable from outside.
20
+ */
21
+
22
+ /* ---- environment card (lives in dsh chrome — keep dsw tokens) ---- */
23
+ .card {
24
+ display: inline-flex;
25
+ align-items: center;
26
+ gap: 7px;
27
+ min-height: 28px;
28
+ padding: 3px 10px;
29
+ border: 1px solid var(--dsw-alias-border-l2);
30
+ border-radius: 8px;
31
+ background: transparent;
32
+ color: var(--dsw-alias-label-secondary);
33
+ font-size: 12px;
34
+ line-height: 18px;
35
+ font-variant-numeric: tabular-nums;
36
+ cursor: pointer;
37
+ transition: background 120ms ease, border-color 120ms ease;
38
+ }
39
+ .card:hover, .card:focus-visible {
40
+ background: var(--dsw-alias-interactive-bg-hover);
41
+ border-color: var(--dsw-alias-border-l3);
42
+ }
43
+ .cardBranch {
44
+ display: inline-flex;
45
+ align-items: center;
46
+ gap: 4px;
47
+ max-width: 220px;
48
+ overflow: hidden;
49
+ color: var(--dsw-alias-label-primary);
50
+ font-weight: 550;
51
+ }
52
+ .cardBranchName { min-width: 0; overflow: hidden; }
53
+ .cardGlyph { flex: none; opacity: 0.7; }
54
+ .cardDetached {
55
+ padding: 0 6px;
56
+ border-radius: 999px;
57
+ background: var(--dsw-alias-state-warning-bg, rgba(187, 128, 9, 0.15));
58
+ color: var(--dsw-alias-state-warning-primary, #d29922);
59
+ font-size: 10px;
60
+ line-height: 16px;
61
+ letter-spacing: 0.02em;
62
+ }
63
+ /* Bound-worktree badge: the session is attached to an agent worktree. */
64
+ .cardWt {
65
+ display: inline-flex;
66
+ align-items: center;
67
+ gap: 3px;
68
+ max-width: 150px;
69
+ overflow: hidden;
70
+ padding: 0 6px;
71
+ border-radius: 999px;
72
+ background: var(--dsw-alias-state-business-bg, rgba(56, 139, 253, 0.16));
73
+ color: var(--dsw-alias-state-business-primary, #58a6ff);
74
+ font-size: 10px;
75
+ line-height: 16px;
76
+ letter-spacing: 0.02em;
77
+ white-space: nowrap;
78
+ text-overflow: ellipsis;
79
+ }
80
+ .cardAhead, .cardBehind {
81
+ color: var(--dsw-alias-label-tertiary);
82
+ font-size: 11px;
83
+ }
84
+ .cardAhead { color: var(--dsw-alias-state-success-primary); }
85
+ .cardBehind { color: var(--dsw-alias-state-attention-primary, #d29922); }
86
+ .cardSep { width: 1px; height: 14px; background: var(--dsw-alias-border-l2); }
87
+ .cardAdded { color: var(--dsw-alias-state-success-primary); }
88
+ .cardDeleted { color: var(--dsw-alias-state-error-primary); }
89
+ .cardFiles { color: var(--dsw-alias-label-tertiary); }
@@ -0,0 +1,113 @@
1
+ /* Files tab: the repository browser's tree, and the pane that opens a file.
2
+ Modifier rules stay BELOW the base rule they narrow — a modifier declared
3
+ first loses the cascade, which the drawer-chrome test checks for. */
4
+ .fbTree {
5
+ flex: none;
6
+ min-width: var(--gs-min-tree);
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: 4px;
10
+ padding: 6px;
11
+ overflow: auto;
12
+ background: var(--gs-surface-2);
13
+ }
14
+ .fbSearch {
15
+ flex: none;
16
+ width: 100%;
17
+ box-sizing: border-box;
18
+ padding: 4px 6px;
19
+ font: inherit;
20
+ font-size: var(--gs-t-meta);
21
+ color: var(--gs-fg);
22
+ background: var(--gs-surface);
23
+ border: 1px solid var(--gs-border);
24
+ border-radius: var(--gs-r-control);
25
+ }
26
+ .fbSearch:focus { outline: none; border-color: var(--gs-accent); }
27
+ .fbNote { flex: none; font-size: var(--gs-t-meta); color: var(--gs-fg-dim); padding: 2px 4px; }
28
+ .fbList { list-style: none; margin: 0; padding: 0; }
29
+ .fbRow {
30
+ display: flex;
31
+ align-items: center;
32
+ gap: 4px;
33
+ width: 100%;
34
+ padding: 2px 6px 2px 4px;
35
+ font: inherit;
36
+ font-size: var(--gs-t-dense);
37
+ color: var(--gs-fg-dim);
38
+ text-align: left;
39
+ background: none;
40
+ border: 0;
41
+ border-radius: var(--gs-r-control);
42
+ cursor: pointer;
43
+ white-space: nowrap;
44
+ overflow: hidden;
45
+ text-overflow: ellipsis;
46
+ }
47
+ .fbRow:hover { background: var(--gs-raise); }
48
+ /* .fbRowActive: see the shared selected-state rule in controls.css. It is
49
+ written there qualified, because this file is imported after that one and a
50
+ bare modifier declared above its base loses the cascade. */
51
+ .fbDirName { overflow: hidden; text-overflow: ellipsis; }
52
+ .fbFileName { overflow: hidden; text-overflow: ellipsis; }
53
+ .fbMain {
54
+ flex: 1 1 0;
55
+ min-width: var(--gs-min-diff);
56
+ display: flex;
57
+ flex-direction: column;
58
+ min-height: 0;
59
+ background: var(--gs-surface);
60
+ }
61
+ .fbHeader {
62
+ flex: none;
63
+ display: flex;
64
+ align-items: flex-start;
65
+ gap: 8px;
66
+ padding: 4px 8px 0;
67
+ border-bottom: 1px solid var(--gs-border);
68
+ }
69
+ .fbPath {
70
+ font-size: var(--gs-t-meta);
71
+ color: var(--gs-fg-dim);
72
+ overflow: hidden;
73
+ text-overflow: ellipsis;
74
+ white-space: nowrap;
75
+ padding: 4px 0;
76
+ }
77
+ /* The editor scrolls inside this, so the header and the notices above stay
78
+ put — the same reason the side pane's toolbar is pinned. */
79
+ .fbBody { flex: 1 1 0; min-height: 0; overflow: auto; padding: 4px 8px; }
80
+
81
+ /* The commit behind the picked blame line. A strip rather than a floating
82
+ popup: it never covers the code it is about, and it survives a scroll. */
83
+ .fbCommit {
84
+ flex: none;
85
+ display: flex;
86
+ align-items: baseline;
87
+ gap: 8px;
88
+ padding: 4px 8px;
89
+ font-size: var(--gs-t-meta);
90
+ color: var(--gs-fg-dim);
91
+ background: var(--gs-surface-2);
92
+ border-bottom: 1px solid var(--gs-border);
93
+ }
94
+ .fbCommitLine { flex: none; color: var(--gs-fg-faint); font-variant-numeric: tabular-nums; }
95
+ .fbCommitWho { flex: none; color: var(--gs-fg); font-weight: 600; }
96
+ .fbCommitWhen { flex: none; font-variant-numeric: tabular-nums; }
97
+ .fbCommitHash { flex: none; font-family: inherit; color: var(--gs-fg-faint); }
98
+ .fbCommitWhat {
99
+ flex: 1 1 auto;
100
+ min-width: 0;
101
+ overflow: hidden;
102
+ text-overflow: ellipsis;
103
+ white-space: nowrap;
104
+ color: var(--gs-fg);
105
+ }
106
+ .fbCommitHint { color: var(--gs-fg-faint); }
107
+ .fbMore {
108
+ display: block;
109
+ padding: 2px 6px 4px 4px;
110
+ font-size: var(--gs-t-meta);
111
+ color: var(--gs-fg-faint);
112
+ font-style: italic;
113
+ }