dsh-style-tweaks 0.1.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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +114 -0
  3. package/README.md +113 -0
  4. package/cordis.patch.yml +12 -0
  5. package/lib/client.js +2379 -0
  6. package/lib/client.js.map +1 -0
  7. package/lib/config.js +90 -0
  8. package/lib/config.js.map +1 -0
  9. package/lib/index.js +28 -0
  10. package/lib/index.js.map +1 -0
  11. package/lib/types/client/conversation-width.d.ts +81 -0
  12. package/lib/types/client/conversation-width.d.ts.map +1 -0
  13. package/lib/types/client/index.d.ts +104 -0
  14. package/lib/types/client/index.d.ts.map +1 -0
  15. package/lib/types/client/tweak-config.d.ts +55 -0
  16. package/lib/types/client/tweak-config.d.ts.map +1 -0
  17. package/lib/types/client/tweak-types.d.ts +32 -0
  18. package/lib/types/client/tweak-types.d.ts.map +1 -0
  19. package/lib/types/client/tweaks/code-block-flush-top.d.ts +91 -0
  20. package/lib/types/client/tweaks/code-block-flush-top.d.ts.map +1 -0
  21. package/lib/types/client/tweaks/locate-current-session.d.ts +62 -0
  22. package/lib/types/client/tweaks/locate-current-session.d.ts.map +1 -0
  23. package/lib/types/client/tweaks/project-running-indicator.d.ts +61 -0
  24. package/lib/types/client/tweaks/project-running-indicator.d.ts.map +1 -0
  25. package/lib/types/client/tweaks/registry.d.ts +22 -0
  26. package/lib/types/client/tweaks/registry.d.ts.map +1 -0
  27. package/lib/types/client/tweaks/settings-nav-scroll.d.ts +168 -0
  28. package/lib/types/client/tweaks/settings-nav-scroll.d.ts.map +1 -0
  29. package/lib/types/client/tweaks/stable-table.d.ts +3 -0
  30. package/lib/types/client/tweaks/stable-table.d.ts.map +1 -0
  31. package/lib/types/client/tweaks/stable-turn-rail.d.ts +53 -0
  32. package/lib/types/client/tweaks/stable-turn-rail.d.ts.map +1 -0
  33. package/lib/types/config.d.ts +149 -0
  34. package/lib/types/config.d.ts.map +1 -0
  35. package/lib/types/index.d.ts +19 -0
  36. package/lib/types/index.d.ts.map +1 -0
  37. package/lib/types/web.d.ts +55 -0
  38. package/lib/types/web.d.ts.map +1 -0
  39. package/lib/web.js +169 -0
  40. package/lib/web.js.map +1 -0
  41. package/package.json +111 -0
@@ -0,0 +1,91 @@
1
+ /**
2
+ * DSH-style-tweaks — code-block-flush-top tweak.
3
+ *
4
+ * Removes the 16 px of top whitespace above and inside every highlighted
5
+ * code block so the highlighted box sits flush with the preceding
6
+ * paragraph, list item, or heading *and* the first line of code sits flush
7
+ * with the top edge of the box. The right, bottom, and left sides keep
8
+ * 16 px so a code block still breathes inside its parent column and the
9
+ * code itself still has lateral breathing room.
10
+ *
11
+ * ## Symptom
12
+ *
13
+ * DSH renders every markdown code fence through `CodeBlock`
14
+ * (`packages/client/ui-primitives/src/markdown/CodeBlock.tsx:60`), which
15
+ * wraps the highlighted `<pre>` in a banner + `<pre>` pair:
16
+ *
17
+ * <div className={clsx(css.block, 'md-code-block', className)}>
18
+ * <div className={css.bannerWrap}> ... language / copy button ... </div>
19
+ * <pre className="shiki css-variables"> ... highlighted code ... </pre>
20
+ * </div>
21
+ *
22
+ * Two 16 px gaps stack vertically to create the "blank space above the
23
+ * code" the user sees:
24
+ *
25
+ * 1. The wrapper `.block` carries `margin: 16px 0` (DSH's
26
+ * `CodeBlock.module.css:13`), so the highlighted code is offset 16 px
27
+ * from preceding content (paragraph, list item, heading, ...).
28
+ * 2. The inner `<pre>` carries `padding: 16px` (CodeBlock.module.css:73),
29
+ * so the first line of code sits 16 px below the banner / box edge.
30
+ *
31
+ * Chrome DevTools exposes these on two different elements — clicking on the
32
+ * `<pre class="shiki css-variables">` shows `padding: 16px`, clicking on
33
+ * the `<div class="_block_xxxxx md-code-block">` wrapper shows
34
+ * `margin: 16px`. Both need to go for the code to sit flush.
35
+ *
36
+ * ## Fix
37
+ *
38
+ * Two rules on the stable `.md-code-block` hook (the same hand-written class
39
+ * `stable-table.ts` uses for `.md-table-wide`):
40
+ *
41
+ * - `.md-code-block { margin-top: 0 !important }` collapses the outer
42
+ * gap above the box. Only `margin-top` is touched: writing a full
43
+ * `margin: 0 16px 16px 16px` would add 16 px of left/right margin to
44
+ * the wrapper, shrinking the visible code box by 32 px (a regression
45
+ * reported by the user as "the box shrinks by a ring"). The wrapper's
46
+ * horizontal sizing is owned by the conversation column — there is no
47
+ * left/right margin on `.block` to preserve.
48
+ * - `.md-code-block pre { padding: 0 16px 16px 16px !important }`
49
+ * collapses the inner gap above the code. The streaming arm of
50
+ * `CodeBlock.tsx:130` mounts `<pre>` as a direct child of the wrapper;
51
+ * the settled arm (`CodeBlock.tsx:166`) wraps it in an anonymous
52
+ * `<div>` for the `dangerouslySetInnerHTML` sink. A descendant selector
53
+ * matches both arms identically.
54
+ *
55
+ * ## Specificity
56
+ *
57
+ * DSH's rules in play:
58
+ *
59
+ * .block { margin: 16px 0 } (0,0,1,0)
60
+ * .block:not(:last-child) { margin-bottom: 11px } (0,0,2,0)
61
+ * .block :where(pre) { padding: 16px } (0,0,2,0)
62
+ *
63
+ * A plain `.md-code-block { ... !important }` is (0,0,1,0) and would lose
64
+ * to `.block:not(:last-child)` on the trailing-edge specificity tiebreak
65
+ * (the :not() pseudo-class adds one to specificity). Doubling the class —
66
+ * .md-code-block.md-code-block — reaches (0,0,2,0) and ties; !important
67
+ * then takes precedence on importance, leaving us in control of all four
68
+ * edges.
69
+ *
70
+ * The inner-pre selector uses .md-code-block.md-code-block pre, which is
71
+ * (0,0,2,1) — that strictly beats DSH's .block :where(pre) (0,0,2,0) on
72
+ * total specificity. The !important is belt-and-suspenders, but the
73
+ * specificity win alone is enough.
74
+ *
75
+ * ## Scope safety
76
+ *
77
+ * `.md-code-block` is set in exactly one place in DSH's source
78
+ * (`CodeBlock.tsx:170`); DSH reserves it for plugins/extensions so future
79
+ * code does not collide. The empty-fence branch (`renderCode` in
80
+ * `packages/client/ui-primitives/src/markdown/render.tsx:312-321`) renders a
81
+ * bare `<pre>` without `.md-code-block`, so empty fences are untouched —
82
+ * they have no banner/wrapper anyway.
83
+ *
84
+ * Streaming and settled arms of `CodeBlock` (`CodeBlock.tsx:130` streaming
85
+ * `<pre>`, `:166` settled `<div dangerouslySetInnerHTML>`) share the same
86
+ * wrapper class, so both arms are affected uniformly by the descendant
87
+ * selector.
88
+ */
89
+ export declare const CODE_BLOCK_FLUSH_TOP_CSS_ID = "cst-code-block-flush-top";
90
+ export declare function injectCodeBlockFlushTopStyles(): () => void;
91
+ //# sourceMappingURL=code-block-flush-top.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-block-flush-top.d.ts","sourceRoot":"","sources":["../../../../src/client/tweaks/code-block-flush-top.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AA6CH,eAAO,MAAM,2BAA2B,6BAA6B,CAAA;AAErE,wBAAgB,6BAA6B,IAAI,MAAM,IAAI,CAU1D"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * dsh-style-tweaks — locate-current-session tweak.
3
+ *
4
+ * Adds a "locate current session" button to the left of the native search
5
+ * button in the sidebar's "工作区" (workspaces) section header. Clicking it:
6
+ *
7
+ * 1. Finds the currently-selected session row in the sidebar tree.
8
+ * 2. Locates its enclosing workspace group via the stable
9
+ * `[class*="groupSection"]` ancestor hook.
10
+ * 3. If the parent workspace row is collapsed (`aria-expanded="false"`),
11
+ * clicks it to expand.
12
+ * 4. Scrolls the session row to the center of the sidebar's visible area
13
+ * so the user can confirm "this is where I am".
14
+ *
15
+ * ## Why DOM patching
16
+ *
17
+ * DSH's `sidebar.workspaces` slot is a single owner (the workspace browser)
18
+ * and exposes no public sub-slot for the section header row. There is also
19
+ * no plugin-callable service that scrolls a sidebar row into view. The
20
+ * plugin therefore decorates the DOM from outside, exactly the way
21
+ * `project-running-indicator.ts` decorates project header rows.
22
+ *
23
+ * Stable anchors (CSS-Modules hashes rotate per build):
24
+ * • Native search button: localized `aria-label` resolved through the
25
+ * locale service (`workspace` ns, key `search.sessions.aria`), with a
26
+ * structural fallback on the `searchButton` class fragment.
27
+ * • Breadcrumb: same strategy (`conversation` ns, key
28
+ * `session.hierarchy`; fallback `crumbs` fragment).
29
+ * • Selected session row: `[role="treeitem"][aria-selected="true"]`
30
+ * (ARIA `aria-selected` is part of the WAI-ARIA tree pattern, DSH
31
+ * uses it to mark the active row.)
32
+ * • Workspace group container: closest element whose class attribute
33
+ * contains the substring `groupSection` (a semantic fragment DSH
34
+ * keeps even when its hashed prefix changes).
35
+ * • Workspace header row: `[role="treeitem"][aria-expanded]` inside the
36
+ * same group container.
37
+ *
38
+ * ## Lifecycle
39
+ *
40
+ * `setupLocateCurrentSession` returns a disposer that removes the button
41
+ * and disconnects the observer. The caller in `index.tsx` mounts/unmounts
42
+ * it on every settings change, exactly like the other JS-level tweaks —
43
+ * disabling the toggle in Settings causes the button to disappear with no
44
+ * page refresh.
45
+ *
46
+ * @module dsh-style-tweaks/client/tweaks/locate-current-session
47
+ */
48
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
49
+ /**
50
+ * Install the locate-current-session stylesheet into <head>. Idempotent: a
51
+ * second call returns no-op (the existing <style> is reused). The caller
52
+ * keeps the disposer alive while the tweak is enabled and disposes it when
53
+ * the user disables the tweak.
54
+ */
55
+ export declare function injectLocateCurrentSessionStyles(): () => void;
56
+ /**
57
+ * Setup the live tweak. Returns a disposer that removes the button and
58
+ * the observer. Safe to call when the search button never appears — the
59
+ * tweak then stays inert until the next observer tick finds one.
60
+ */
61
+ export declare function setupLocateCurrentSession(ctx: ClientContext): () => void;
62
+ //# sourceMappingURL=locate-current-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locate-current-session.d.ts","sourceRoot":"","sources":["../../../../src/client/tweaks/locate-current-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AA+KnE;;;;;GAKG;AACH,wBAAgB,gCAAgC,IAAI,MAAM,IAAI,CAU7D;AAmYD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,IAAI,CAwFxE"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * dsh-style-tweaks — project-running-indicator tweak.
3
+ *
4
+ * The first non-pure-CSS tweak: it renders the *app's own* `StateDot`
5
+ * (the animated pixel-chase shown left of a running conversation's title,
6
+ * `ui-primitives/src/StateDot.tsx`) on the right side of every project
7
+ * directory header row in the sidebar, so a running conversation stays
8
+ * visible even when its group is collapsed.
9
+ *
10
+ * ## Why DOM patching
11
+ *
12
+ * DSH's project header (`ProjectRowItem`, `ui-workspace/src/client/rows/
13
+ * Rows.tsx:112`) exposes no plugin slot, and the `sidebar.workspaces` seat
14
+ * is already occupied by the stock browser — so the header row can only be
15
+ * decorated from outside, via a MutationObserver. Consistent with this
16
+ * repo's selector philosophy, the engine anchors exclusively on hand-written
17
+ * DOM facts that survive CSS-Modules hashing:
18
+ *
19
+ * - group header rows are the only `role="treeitem"` elements carrying
20
+ * `aria-expanded` (session rows and flat-list rows never have it);
21
+ * - the row's title text is the only `:scope > span > span` descendant
22
+ * with non-empty text (folder/chevron slots hold bare SVGs, the action
23
+ * buttons' labels live on `aria-label`, not text).
24
+ *
25
+ * ## Data source
26
+ *
27
+ * Running state is read from the app's own stores, not inferred from the
28
+ * DOM (a collapsed group hides its child rows, so the DOM cannot answer):
29
+ *
30
+ * - `ctx.get('sessions').list` — snapshot store of `SessionSummary`s
31
+ * (`byId[id].running`);
32
+ * - `ctx.get('workspaces').list` — `WorkspaceSnapshot.items`, each
33
+ * `WorkspaceView.sessionIds` mapping sessions to directories.
34
+ *
35
+ * Membership is computed from the workspace arrays (mirroring
36
+ * `deriveGroups` in `ui-workspace/src/client/tree.ts`): archived and blank
37
+ * sessions are skipped, sessions not in any workspace count toward the
38
+ * "Ungrouped" bucket. A running subagent marks its top-level ancestor
39
+ * session busy (same lineage walk as `indexSubagentDescendants`), which is
40
+ * what the title's own dot shows.
41
+ *
42
+ * Header rows are matched to workspaces by title text; a header whose label
43
+ * matches no workspace title is the Ungrouped bucket. This avoids depending
44
+ * on DSH's locale copy for the bucket label (and tolerates renames for
45
+ * free, since the title text *is* the match key).
46
+ *
47
+ * ## Rendering
48
+ *
49
+ * The dot is the real `StateDot` imported from
50
+ * `@deepseek-ai/dsh-client-ui-primitives`. That package is in the client
51
+ * shell's `PLATFORM_MODULES` baseline, so at runtime the import resolves to
52
+ * the app's shared instance — its CSS module (and the global
53
+ * `dsh-state-dot-chase` keyframes) and theme tokens are already loaded, and
54
+ * the animation is pixel-identical to the one on session titles. The dot is
55
+ * mounted with a `react-dom/client` root per decorated row and unmounted
56
+ * whenever the group goes idle, the row leaves the DOM, or the tweak is
57
+ * disabled.
58
+ */
59
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
60
+ export declare function setupProjectRunningIndicator(ctx: ClientContext): () => void;
61
+ //# sourceMappingURL=project-running-indicator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-running-indicator.d.ts","sourceRoot":"","sources":["../../../../src/client/tweaks/project-running-indicator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AASH,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAkKnE,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,IAAI,CA8G3E"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * dsh-style-tweaks — tweak registry.
3
+ *
4
+ * Central catalogue of all CSS tweaks the plugin can inject. Each tweak is
5
+ * described once here; the client iterates this list to (a) render Settings
6
+ * panel toggles and (b) mount / unmount the corresponding styles live.
7
+ */
8
+ export interface TweakDescriptor {
9
+ /** Unique id, e.g. "stable-table". Doubles as the DOM data-tweak id. */
10
+ readonly id: string;
11
+ /** Settings document field name, e.g. "stableTable". */
12
+ readonly settingKey: string;
13
+ /** Default value when the field is absent from the settings document. */
14
+ readonly defaultEnabled: boolean;
15
+ /** i18n key for the toggle label in the Settings panel. */
16
+ readonly titleKey: string;
17
+ /** i18n key for the one-line description shown next to the toggle. */
18
+ readonly descriptionKey: string;
19
+ }
20
+ /** All tweaks, in display order. Add new entries here. */
21
+ export declare const TWEAKS: readonly TweakDescriptor[];
22
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../../src/client/tweaks/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,wDAAwD;IACxD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC,2DAA2D;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,sEAAsE;IACtE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;CAChC;AAED,0DAA0D;AAC1D,eAAO,MAAM,MAAM,EAAE,SAAS,eAAe,EA2C5C,CAAA"}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * dsh-style-tweaks — settings-nav-scroll tweak.
3
+ *
4
+ * Makes the settings dialog's LEFT nav column scrollable once its section
5
+ * list outgrows the fixed-height panel, instead of silently clipping the
6
+ * entries at the bottom. Without this, a user with several plugin-registered
7
+ * sections cannot reach the last items at all — the panel hides them and
8
+ * nothing scrolls. The scrollbar itself is an overlay-style thin strip:
9
+ * parked in the rail's spare padding so it never squeezes the menu, and
10
+ * visible only while the list is actually being scrolled (macOS-style),
11
+ * driven by a scroll listener rather than `:hover`.
12
+ *
13
+ * ## Symptom
14
+ *
15
+ * The settings dialog is DSH's `SettingsRoot`
16
+ * (`packages/client/ui-settings-general/src/client/SettingsRoot.tsx`):
17
+ *
18
+ * <div className={css.panel} role="dialog" aria-modal="true"> ← 800px, overflow: hidden
19
+ * <nav className={css.nav}> ← 188px rail
20
+ * <div className={css.navTitle}>…</div>
21
+ * <div className={css.navList}> ← the section buttons
22
+ * <button className={css.navCell}>…</button> × N
23
+ * </div>
24
+ * </nav>
25
+ * <div className={css.content}> … </div>
26
+ * </div>
27
+ *
28
+ * The panel has ONE height for every section (`SettingsRoot.module.css`:
29
+ * `height: min(800px, calc(100vh - 48px)); overflow: hidden`) so it does not
30
+ * resize under the pointer between nav clicks, and its own comment states
31
+ * "whatever does not fit scrolls in `.options`" — the right content column
32
+ * got `overflow-y: auto`, but the LEFT nav never received the same
33
+ * treatment. `.navList` is a plain flex column (`gap: 4px`, default
34
+ * `flex: 0 1 auto`, no `min-height: 0`, no overflow), and its `min-height:
35
+ * auto` forbids shrinking below the content, so the list simply overflows
36
+ * `.nav` and the `.panel`'s `overflow: hidden` clips whatever spills past
37
+ * the bottom edge. With enough plugin sections (this plugin alone adds one),
38
+ * the last nav entries become unreachable.
39
+ *
40
+ * ## Fix — part 1 (CSS): make the list the scroll container
41
+ *
42
+ * One rule turning the nav list into the scroll container of the nav rail's
43
+ * leftover height:
44
+ *
45
+ * flex: 1 — the list owns the column height below the title
46
+ * (previously implicit; nav is a flex column whose
47
+ * only other child is the title row).
48
+ * min-height: 0 — the load-bearing line: overrides the flex item's
49
+ * `min-height: auto`, allowing the list to be shorter
50
+ * than its content so overflow becomes possible.
51
+ * overflow-y: auto — scroll only when entries exceed the height; a short
52
+ * list looks exactly like stock DSH.
53
+ * overscroll-behavior: contain — at the list's scroll end the chain stops
54
+ * here instead of scrolling some page behind the modal.
55
+ * padding-bottom: 12px — breathing room after the last entry when fully
56
+ * scrolled (`.nav` has no bottom padding by design);
57
+ * invisible when the list does not scroll.
58
+ * margin-inline-end: -12px — parks the bar OUT of the content column,
59
+ * 8px clear of the cells (see part 2).
60
+ *
61
+ * The title row stays fixed and the panel keeps its size — no layout shift
62
+ * anywhere else.
63
+ *
64
+ * ## Fix — part 2 (CSS): the bar parks in the rail's padding gutter, clear of the cells
65
+ *
66
+ * A classic scrollbar consumes its width from the element's content box —
67
+ * the first shipped version narrowed the cells 164 → 156px the moment the
68
+ * bar appeared, and the bar sat there permanently while the list overflowed.
69
+ * Native Chromium scrollbars have no CSS "overlay" mode, but the classic
70
+ * bar can be parked out of the content column:
71
+ *
72
+ * margin-inline-end: -12px — the list's border box stretches the rail's
73
+ * full 12px right padding into itself: 8px gap + 4px
74
+ * bar strip. The thumb therefore sits 8px clear of the
75
+ * entries (values user-tuned in the console as W=4 /
76
+ * GAP=8, then solidified into the tweak) and floats
77
+ * over the rail's padding area like an overlay bar,
78
+ * flush with the gutter edge before the content column
79
+ * boundary.
80
+ *
81
+ * [class*="_navCell"] { max-width: 164px } — the park widens the list's
82
+ * content box whenever the bar is ABSENT (no overflow →
83
+ * content 176px), which made the hover highlight paint
84
+ * 12px wider than stock in that state. Capping the cells
85
+ * at DSH's stock width (188 nav − 2×12 padding) pins
86
+ * them to 164px in EVERY state — bar present, bar
87
+ * absent, hovering, scrolling — so the rail's geometry
88
+ * never differs from stock by a pixel. If DSH ever
89
+ * widens the rail, the cap only over-narrows cosmetically;
90
+ * if it narrows, stretch wins below the cap and nothing
91
+ * changes.
92
+ *
93
+ * ::-webkit-scrollbar { width: 4px } — thinner than DSH's global 8px
94
+ * skin; colour/radius of the visible thumb come from
95
+ * DSH's global thumb rules (l2 tokens inherited from
96
+ * the panel; the 4px radius auto-clamps to 2px on this
97
+ * width), so only geometry lives here.
98
+ *
99
+ * ## Fix — part 3 (JS): show only while scrolling
100
+ *
101
+ * The idle bar must be invisible, and the first attempt used the classic
102
+ * hover-reveal (`:not(:hover)::-webkit-scrollbar-thumb → transparent`).
103
+ * Chromium does not reliably repaint custom scrollbar pseudo-elements when
104
+ * only the HOST's `:hover` state changes — the thumb kept its last painted
105
+ * style until an unrelated invalidation; in practice the bar appeared when
106
+ * clicking a nav cell (React re-rendered the list → repaint) instead of
107
+ * while scrolling, which is exactly backwards.
108
+ *
109
+ * So the show condition is driven by real activity: `scroll` events on the
110
+ * list toggle a `cst-nav-scroll-show` class on the list itself, and ~800ms
111
+ * after the last scroll event it is removed (macOS overlay-bar timing).
112
+ * Class changes restyle the element itself, so the scrollbar repaints
113
+ * reliably — no dependence on host pseudo-state. Wheel-scrolling requires
114
+ * the pointer on the list, thumb drags fire scroll, and programmatic
115
+ * scrolls (keyboard focus moving through the cells) count as activity too;
116
+ * idle hovering shows nothing, matching "hide when not scrolling".
117
+ *
118
+ * The JS surface is deliberately small: a MutationObserver keeps the
119
+ * scroll listener attached to whichever element currently matches the
120
+ * selector (the dialog unmounts when closed and remounts when opened),
121
+ * one passive `scroll` listener per attached list, one idle timer.
122
+ *
123
+ * ## Selector strategy
124
+ *
125
+ * DSH hashes CSS Modules class names with the `[hash]_[local]` pattern
126
+ * (`packages/client/tsdown.client.ts`: `cssModules: { pattern:
127
+ * '[hash]_[local]' }`), so the compiled class is `<hash>_navList` — the hash
128
+ * is unpredictable across DSH versions, the local name is not. We match the
129
+ * fragment with `[class*="_navList"]` and pin it structurally to the one
130
+ * place that renders it: the modal panel's nav rail
131
+ * (`div[role="dialog"][aria-modal="true"] > nav > …`). Both anchors are
132
+ * redundant on their own; together they survive either DSH reshuffling the
133
+ * dialog structure or renaming the local class.
134
+ *
135
+ * `navList` is defined in exactly one CSS module in DSH
136
+ * (`ui-settings-general/src/client/SettingsRoot.module.css`), so the
137
+ * fragment cannot collide with other dialogs (Modal primitive, lightbox,
138
+ * onboarding steps, …) even before the structural pin.
139
+ *
140
+ * ## Specificity
141
+ *
142
+ * DSH's `.navList { display: flex; flex-direction: column; gap: 4px }` is
143
+ * (0,0,1,0). The base rule is (0,3,2) — three attribute selectors plus two
144
+ * element selectors — and DSH sets none of the base properties we touch.
145
+ * The idle-thumb rule (0,3,2) beats DSH's unscoped global thumb skin
146
+ * (0,0,1); the show rule adds a class (0,4,2) and its hover variant (0,5,2)
147
+ * restores DSH's idle/hover token pair while shown. No `!important`
148
+ * anywhere.
149
+ *
150
+ * ## Scrollbar skin
151
+ *
152
+ * The visible thumb reads DSH's themed scrollbar tokens: the panel rebinds
153
+ * `--dsh-scrollbar-thumb{,-hover}` to the l2 elevation tokens (declared on
154
+ * `.panel` so they inherit into any scrolling descendant); we reference the
155
+ * same custom properties so the nav bar matches the right column's look,
156
+ * only thinner.
157
+ */
158
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
159
+ export declare const SETTINGS_NAV_SCROLL_CSS_ID = "cst-settings-nav-scroll";
160
+ /**
161
+ * Mount the scroll driver: keep the scroll listener on whichever element
162
+ * currently matches the nav-list selector, reveal the thumb while it
163
+ * scrolls, fade it out after the idle delay.
164
+ * @param _ctx - client context (unused; signature parity with JS tweaks).
165
+ * @returns the disposer removing styles, listeners, and observers.
166
+ */
167
+ export declare function setupSettingsNavScroll(_ctx: ClientContext): () => void;
168
+ //# sourceMappingURL=settings-nav-scroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-nav-scroll.d.ts","sourceRoot":"","sources":["../../../../src/client/tweaks/settings-nav-scroll.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4JG;AAEH,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAkDnE,eAAO,MAAM,0BAA0B,4BAA4B,CAAA;AA+BnE;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,IAAI,CA8EtE"}
@@ -0,0 +1,3 @@
1
+ export declare const STABLE_TABLE_CSS_ID = "cst-stable-table";
2
+ export declare function injectStableTableStyles(): () => void;
3
+ //# sourceMappingURL=stable-table.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stable-table.d.ts","sourceRoot":"","sources":["../../../../src/client/tweaks/stable-table.ts"],"names":[],"mappings":"AAoDA,eAAO,MAAM,mBAAmB,qBAAqB,CAAA;AAErD,wBAAgB,uBAAuB,IAAI,MAAM,IAAI,CAUpD"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * dsh-style-tweaks — stable-turn-rail tweak.
3
+ *
4
+ * Fixes the visual jump of the turn-navigation rail when the reader scrolls
5
+ * the chat upward into the system-prompt area at the very top of the
6
+ * conversation column.
7
+ *
8
+ * ## Symptom
9
+ *
10
+ * The rail (right-side Turn navigator) sits inside the chat scroll container
11
+ * as a `position: sticky; top: 0; height: 0` slot. Its parent has
12
+ * `padding-top: 16px`. With `top: 0` sticky behaviour, the slot's natural
13
+ * flow position is `padding-top` below the scrollport's visible top, so:
14
+ *
15
+ * • When the chat is scrolled far enough that the slot would naturally
16
+ * be above the scrollport top (scrollTop > 16px), sticky activates and
17
+ * pins the slot at scrollport top:0. Rail sits at the expected height.
18
+ * • When the chat is scrolled all the way up (scrollTop ≤ 16px), the slot
19
+ * falls back to its natural position — which is 16px below scrollport
20
+ * top. The rail shifts DOWN by 16px.
21
+ *
22
+ * After clicking the first Turn marker, the chat lands such that the first
23
+ * message sits near the reading line but the chat can still scroll up into
24
+ * the system prompt above it. Scrolling that last bit causes the rail to
25
+ * visibly drop by 16px.
26
+ *
27
+ * ## Fix
28
+ *
29
+ * Move the 16px of top padding from the scroll container down into the
30
+ * chat column (`[data-chat-flow]`). Net effect:
31
+ *
32
+ * • Container's `padding-top` becomes 0, so the sticky slot's natural
33
+ * position is at scrollport top:0 even when scrollTop = 0.
34
+ * • Column picks up `padding-top: 16px`, so the first message still
35
+ * starts 16px below the scrollport top — identical visual layout to
36
+ * before, only the source of those 16px has moved.
37
+ *
38
+ * Selectors use stable DSH markers only:
39
+ *
40
+ * • `[data-chat-flow]` — the chat column (stable attribute, hardcoded in
41
+ * ChatView.tsx).
42
+ * • `:has(> * > nav[aria-label="Turn navigation"], > * > nav[aria-label="轮次导航"])`
43
+ * — the chat scroll container, identified as the only element that has
44
+ * the rail nav as a grandchild and the chat column as a direct child.
45
+ * The two aria-label values cover DSH's shipped en / zh dictionaries
46
+ * (TurnNavigator.tsx → `t('chat.turnNavigation.label')`).
47
+ *
48
+ * No CSS-module class names are referenced, so the fix stays correct across
49
+ * DSH builds that re-hash module class names.
50
+ */
51
+ export declare const STABLE_TURN_RAIL_CSS_ID = "cst-stable-turn-rail";
52
+ export declare function injectStableTurnRailStyles(): () => void;
53
+ //# sourceMappingURL=stable-turn-rail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stable-turn-rail.d.ts","sourceRoot":"","sources":["../../../../src/client/tweaks/stable-turn-rail.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAkBH,eAAO,MAAM,uBAAuB,yBAAyB,CAAA;AAE7D,wBAAgB,0BAA0B,IAAI,MAAM,IAAI,CAUvD"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * dsh-style-tweaks — configuration.
3
+ *
4
+ * Owns the `style-tweaks` settings namespace. Two feature
5
+ * areas:
6
+ * 1. Column-width control (ported from dsh-dialog-width): a px input
7
+ * (600–1600) with presets, a plugin-vs-native toggle, and side margin.
8
+ * 2. Opt-in CSS tweaks (stable-table layout on hover, with more added
9
+ * over time). Each tweak is a boolean field.
10
+ * @module dsh-style-tweaks/config
11
+ */
12
+ import type Schema from '@deepseek-ai/schemastery';
13
+ /** Settings document namespace owned by this plugin. */
14
+ export declare const STYLE_TWEAKS_SETTINGS_NAMESPACE = "style-tweaks";
15
+ /** Raw user-facing configuration (partial inputs receive schema defaults). */
16
+ export interface StyleTweaksConfig {
17
+ /**
18
+ * Conversation column width in px. The plugin's own width input / preset
19
+ * row reads & writes this field when "plugin width control" is on (see
20
+ * `usePluginWidth`); when off, the field still mirrors the user's chosen
21
+ * px value to the shared storage slot so flipping the switch never loses
22
+ * it. Clamped to [600, 1600] on read.
23
+ */
24
+ dialogWidth?: number;
25
+ /**
26
+ * Whether the plugin's own column-width input owns the conversation width
27
+ * axis. On (default): the plugin writes `--dsh-chat-user-width` directly
28
+ * and hides the native 40 px hover handles. Off: the native drag handles
29
+ * own the axis (clamp(680, col×0.64, 920)) and the plugin's width input
30
+ * mirrors its value to the shared localStorage so flipping the switch back
31
+ * on restores the same px.
32
+ */
33
+ usePluginWidth?: boolean;
34
+ /**
35
+ * Whitespace in px kept on each side of the conversation area. The width
36
+ * axis is clamped to min(dialogWidth, liveColumn − 2 × sideMargin), so the
37
+ * content narrows — together with the composer card — when the sidebar
38
+ * opens or the window shrinks, never hugging the edges. Minimum 32 px.
39
+ */
40
+ sideMargin?: number;
41
+ /**
42
+ * Lock markdown table layout on hover so the table does not reflow and push
43
+ * surrounding content around. Fixes the "text jumps when I hover a table"
44
+ * layout-shift bug reported against the stock conversation view.
45
+ */
46
+ stableTable?: boolean;
47
+ /**
48
+ * Keep the turn-navigation rail at a stable position when scrolling up
49
+ * past the first message into the system-prompt area. Without this fix
50
+ * the rail drops by 16px (the chat scroll container's `padding-top`)
51
+ * because the rail slot is `position: sticky; top: 0` inside that
52
+ * padded container.
53
+ */
54
+ stableTurnRail?: boolean;
55
+ /**
56
+ * Remove the 16 px gap above highlighted code blocks so the highlighted
57
+ * box sits flush with the preceding paragraph, list item, or heading.
58
+ * The wrapper (`.md-code-block` in `CodeBlock.module.css`) ships with
59
+ * `margin: 16px 0`; this tweak overrides only the top edge.
60
+ */
61
+ codeBlockFlushTop?: boolean;
62
+ /**
63
+ * Show the conversation title's animated running dot on the right side of
64
+ * every project directory header row in the sidebar, so a running
65
+ * conversation stays visible even when its group is collapsed. The dot is
66
+ * DSH's own `StateDot` (shared instance) driven by the app's
67
+ * sessions/workspaces stores — pendingInteraction (amber) needs the
68
+ * ui-session service face and is out of scope; only ongoing shows.
69
+ */
70
+ projectRunningIndicator?: boolean;
71
+ /**
72
+ * Add a "locate" button next to the sidebar search box; clicking it
73
+ * expands the current session's workspace and scrolls the session into
74
+ * view. Client-side toggle only: the browser bundle reads this field and
75
+ * mounts / unmounts the button live.
76
+ */
77
+ locateCurrentSession?: boolean;
78
+ /**
79
+ * Let the settings dialog's left nav column scroll once its section list
80
+ * outgrows the fixed-height panel, instead of the panel's
81
+ * `overflow: hidden` clipping the bottom entries (unreachable when enough
82
+ * plugins register settings sections).
83
+ */
84
+ settingsNavScroll?: boolean;
85
+ }
86
+ /** Dialog width in px. 600 is the chat-column minimum, 1600 the soft cap. */
87
+ export declare const MIN_DIALOG_WIDTH = 600;
88
+ export declare const MAX_DIALOG_WIDTH = 1600;
89
+ /** 748 matches the stock DSH column. */
90
+ export declare const DEFAULT_DIALOG_WIDTH = 748;
91
+ /**
92
+ * Plugin width control defaults to ON: existing settings documents never had
93
+ * this field, so the default must match what users saw before the native
94
+ * handles shipped — the plugin owning the column.
95
+ */
96
+ export declare const DEFAULT_USE_PLUGIN_WIDTH = true;
97
+ /** Default side margin in px — 50 gives a comfortable gap on each side. */
98
+ export declare const DEFAULT_SIDE_MARGIN = 50;
99
+ /** Minimum side margin in px — below 32 the gap becomes too tight. */
100
+ export declare const MIN_SIDE_MARGIN = 32;
101
+ /**
102
+ * localStorage slot the native WidthHandle reads/writes; kept here so a
103
+ * future rename of the host key only needs touching one place. We mirror
104
+ * the plugin's chosen px value here too so toggling plugin-width off
105
+ * surfaces the user's last choice in the native handle.
106
+ */
107
+ export declare const CONVERSATION_WIDTH_STORAGE_KEY = "dsh.conversation.contentWidth";
108
+ /** Default: every shipped tweak is on. */
109
+ export declare const DEFAULT_STABLE_TABLE = true;
110
+ /** Default: every shipped tweak is on. */
111
+ export declare const DEFAULT_STABLE_TURN_RAIL = true;
112
+ /** Default: every shipped tweak is on. */
113
+ export declare const DEFAULT_CODE_BLOCK_FLUSH_TOP = true;
114
+ /** Default: every shipped tweak is on. */
115
+ export declare const DEFAULT_PROJECT_RUNNING_INDICATOR = true;
116
+ /** Default: every shipped tweak is on. */
117
+ export declare const DEFAULT_LOCATE_CURRENT_SESSION = true;
118
+ /** Default: every shipped tweak is on. */
119
+ export declare const DEFAULT_SETTINGS_NAV_SCROLL = true;
120
+ /** Configuration schema with documented defaults. */
121
+ export declare const Config: Schema<StyleTweaksConfig>;
122
+ /** Configuration after static validation, with every default materialized. */
123
+ export interface ResolvedStyleTweaksConfig {
124
+ /** Dialog width in px (748 = the stock DSH column). */
125
+ dialogWidth: number;
126
+ /** Whether the plugin's width control owns the column (vs. native handles). */
127
+ usePluginWidth: boolean;
128
+ /** Side margin in px applied to both sides of the conversation column. */
129
+ sideMargin: number;
130
+ /** Whether the stable-table tweak is enabled. */
131
+ stableTable: boolean;
132
+ /** Whether the stable-turn-rail tweak is enabled. */
133
+ stableTurnRail: boolean;
134
+ /** Whether the code-block-flush-top tweak is enabled. */
135
+ codeBlockFlushTop: boolean;
136
+ /** Whether the project-running-indicator tweak is enabled. */
137
+ projectRunningIndicator: boolean;
138
+ /** Whether the locate-current-session tweak is enabled. */
139
+ locateCurrentSession: boolean;
140
+ /** Whether the settings-nav-scroll tweak is enabled. */
141
+ settingsNavScroll: boolean;
142
+ }
143
+ /** Resolve a partial config into a fully defaulted value. */
144
+ export declare function resolveConfig(config?: StyleTweaksConfig): ResolvedStyleTweaksConfig;
145
+ /** Normalize a dialog width value (legacy strings included) to px. */
146
+ export declare function resolveDialogWidth(value: number | undefined): number;
147
+ /** Normalize a side-margin value (legacy strings included) to px. */
148
+ export declare function resolveSideMargin(value: number | undefined): number;
149
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,MAAM,MAAM,0BAA0B,CAAA;AAElD,wDAAwD;AACxD,eAAO,MAAM,+BAA+B,iBAAiB,CAAA;AAE7D,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAEhC;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IAGnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;;;;;;OAOG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAGD,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,MAAM,CAAA;AACnC,eAAO,MAAM,gBAAgB,OAAO,CAAA;AACpC,wCAAwC;AACxC,eAAO,MAAM,oBAAoB,MAAM,CAAA;AACvC;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAA;AAC5C,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,KAAK,CAAA;AACrC,sEAAsE;AACtE,eAAO,MAAM,eAAe,KAAK,CAAA;AACjC;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,kCAAkC,CAAA;AAG7E,0CAA0C;AAC1C,eAAO,MAAM,oBAAoB,OAAO,CAAA;AACxC,0CAA0C;AAC1C,eAAO,MAAM,wBAAwB,OAAO,CAAA;AAC5C,0CAA0C;AAC1C,eAAO,MAAM,4BAA4B,OAAO,CAAA;AAChD,0CAA0C;AAC1C,eAAO,MAAM,iCAAiC,OAAO,CAAA;AACrD,0CAA0C;AAC1C,eAAO,MAAM,8BAA8B,OAAO,CAAA;AAClD,0CAA0C;AAC1C,eAAO,MAAM,2BAA2B,OAAO,CAAA;AAE/C,qDAAqD;AACrD,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,iBAAiB,CAU3C,CAAA;AAEF,8EAA8E;AAC9E,MAAM,WAAW,yBAAyB;IACxC,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAA;IACnB,+EAA+E;IAC/E,cAAc,EAAE,OAAO,CAAA;IACvB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAA;IAClB,iDAAiD;IACjD,WAAW,EAAE,OAAO,CAAA;IACpB,qDAAqD;IACrD,cAAc,EAAE,OAAO,CAAA;IACvB,yDAAyD;IACzD,iBAAiB,EAAE,OAAO,CAAA;IAC1B,8DAA8D;IAC9D,uBAAuB,EAAE,OAAO,CAAA;IAChC,2DAA2D;IAC3D,oBAAoB,EAAE,OAAO,CAAA;IAC7B,wDAAwD;IACxD,iBAAiB,EAAE,OAAO,CAAA;CAC3B;AAED,6DAA6D;AAC7D,wBAAgB,aAAa,CAAC,MAAM,GAAE,iBAAsB,GAAG,yBAAyB,CAYvF;AAED,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAKpE;AAED,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGnE"}