dsh-zen-remote 1.0.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 (136) hide show
  1. package/AGENTS.md +51 -0
  2. package/LICENSE +38 -0
  3. package/README.md +237 -0
  4. package/assets/home.png +0 -0
  5. package/assets/info.png +0 -0
  6. package/assets/pairing.png +0 -0
  7. package/assets/session.png +0 -0
  8. package/assets/sheet.png +0 -0
  9. package/cordis.patch.yml +17 -0
  10. package/cordis.patch.yml.example +16 -0
  11. package/docs/interface.md +200 -0
  12. package/docs/remote-access.en.md +335 -0
  13. package/docs/remote-access.md +422 -0
  14. package/dsh-push.mjs +213 -0
  15. package/lan-gate.mjs +78 -0
  16. package/lib/client.js +6615 -0
  17. package/lib/index.js +310 -0
  18. package/lib/index.js.map +1 -0
  19. package/lib/lan-gate-server.cjs +626 -0
  20. package/lib/types/client/MobileAttachButton.d.ts +25 -0
  21. package/lib/types/client/MobileAttachButton.d.ts.map +1 -0
  22. package/lib/types/client/MobileAttachChips.d.ts +32 -0
  23. package/lib/types/client/MobileAttachChips.d.ts.map +1 -0
  24. package/lib/types/client/MobileDrawerFooter.d.ts +21 -0
  25. package/lib/types/client/MobileDrawerFooter.d.ts.map +1 -0
  26. package/lib/types/client/MobileHome.d.ts +24 -0
  27. package/lib/types/client/MobileHome.d.ts.map +1 -0
  28. package/lib/types/client/MobileHomeChips.d.ts +76 -0
  29. package/lib/types/client/MobileHomeChips.d.ts.map +1 -0
  30. package/lib/types/client/MobileNavOverlay.d.ts +15 -0
  31. package/lib/types/client/MobileNavOverlay.d.ts.map +1 -0
  32. package/lib/types/client/MobileNavToggle.d.ts +18 -0
  33. package/lib/types/client/MobileNavToggle.d.ts.map +1 -0
  34. package/lib/types/client/MobileSessionHeader.d.ts +58 -0
  35. package/lib/types/client/MobileSessionHeader.d.ts.map +1 -0
  36. package/lib/types/client/MobileSessionInfo.d.ts +41 -0
  37. package/lib/types/client/MobileSessionInfo.d.ts.map +1 -0
  38. package/lib/types/client/attach-upload.d.ts +78 -0
  39. package/lib/types/client/attach-upload.d.ts.map +1 -0
  40. package/lib/types/client/chips-store.d.ts +13 -0
  41. package/lib/types/client/chips-store.d.ts.map +1 -0
  42. package/lib/types/client/debug.d.ts +9 -0
  43. package/lib/types/client/debug.d.ts.map +1 -0
  44. package/lib/types/client/effects/aionui-compat.d.ts +4 -0
  45. package/lib/types/client/effects/aionui-compat.d.ts.map +1 -0
  46. package/lib/types/client/effects/gestures.d.ts +7 -0
  47. package/lib/types/client/effects/gestures.d.ts.map +1 -0
  48. package/lib/types/client/effects/header-status.d.ts +15 -0
  49. package/lib/types/client/effects/header-status.d.ts.map +1 -0
  50. package/lib/types/client/effects/phone-chrome.d.ts +106 -0
  51. package/lib/types/client/effects/phone-chrome.d.ts.map +1 -0
  52. package/lib/types/client/effects/turn-fold.d.ts +25 -0
  53. package/lib/types/client/effects/turn-fold.d.ts.map +1 -0
  54. package/lib/types/client/index.d.ts +18 -0
  55. package/lib/types/client/index.d.ts.map +1 -0
  56. package/lib/types/client/locales.d.ts +58 -0
  57. package/lib/types/client/locales.d.ts.map +1 -0
  58. package/lib/types/client/nav-store.d.ts +73 -0
  59. package/lib/types/client/nav-store.d.ts.map +1 -0
  60. package/lib/types/client/session-dot.d.ts +12 -0
  61. package/lib/types/client/session-dot.d.ts.map +1 -0
  62. package/lib/types/client/styles/base.css.d.ts +2 -0
  63. package/lib/types/client/styles/base.css.d.ts.map +1 -0
  64. package/lib/types/client/styles/chips.css.d.ts +2 -0
  65. package/lib/types/client/styles/chips.css.d.ts.map +1 -0
  66. package/lib/types/client/styles/compat.css.d.ts +2 -0
  67. package/lib/types/client/styles/compat.css.d.ts.map +1 -0
  68. package/lib/types/client/styles/composer.css.d.ts +2 -0
  69. package/lib/types/client/styles/composer.css.d.ts.map +1 -0
  70. package/lib/types/client/styles/header.css.d.ts +2 -0
  71. package/lib/types/client/styles/header.css.d.ts.map +1 -0
  72. package/lib/types/client/styles/home.css.d.ts +2 -0
  73. package/lib/types/client/styles/home.css.d.ts.map +1 -0
  74. package/lib/types/client/styles/index.d.ts +15 -0
  75. package/lib/types/client/styles/index.d.ts.map +1 -0
  76. package/lib/types/client/styles/info.css.d.ts +2 -0
  77. package/lib/types/client/styles/info.css.d.ts.map +1 -0
  78. package/lib/types/client/styles/layout.css.d.ts +2 -0
  79. package/lib/types/client/styles/layout.css.d.ts.map +1 -0
  80. package/lib/types/client/styles/misc.css.d.ts +2 -0
  81. package/lib/types/client/styles/misc.css.d.ts.map +1 -0
  82. package/lib/types/client/styles/turn-fold.css.d.ts +2 -0
  83. package/lib/types/client/styles/turn-fold.css.d.ts.map +1 -0
  84. package/lib/types/index.d.ts +83 -0
  85. package/lib/types/index.d.ts.map +1 -0
  86. package/llms.txt +20 -0
  87. package/package.json +128 -0
  88. package/pwa/app.css +95 -0
  89. package/pwa/icons/icon-192.png +0 -0
  90. package/pwa/icons/icon-512.png +0 -0
  91. package/pwa/icons/icon-maskable-512.png +0 -0
  92. package/pwa/icons/icon-maskable.svg +1 -0
  93. package/pwa/icons/icon.svg +1 -0
  94. package/pwa/inject.js +190 -0
  95. package/pwa/manifest.json +29 -0
  96. package/pwa/offline.html +38 -0
  97. package/pwa/sw.js +176 -0
  98. package/pwa/touch-gestures.js +94 -0
  99. package/scripts/build-client.mjs +101 -0
  100. package/scripts/cdp-probe.mjs +64 -0
  101. package/scripts/check-attach-upload.mjs +99 -0
  102. package/scripts/check-sunk-viewport.mjs +33 -0
  103. package/scripts/check-upload-endpoint.mjs +168 -0
  104. package/src/client/MobileAttachButton.tsx +125 -0
  105. package/src/client/MobileAttachChips.tsx +85 -0
  106. package/src/client/MobileDrawerFooter.tsx +56 -0
  107. package/src/client/MobileHome.tsx +348 -0
  108. package/src/client/MobileHomeChips.tsx +426 -0
  109. package/src/client/MobileNavOverlay.tsx +279 -0
  110. package/src/client/MobileNavToggle.tsx +52 -0
  111. package/src/client/MobileSessionHeader.tsx +242 -0
  112. package/src/client/MobileSessionInfo.tsx +303 -0
  113. package/src/client/attach-upload.ts +140 -0
  114. package/src/client/chips-store.ts +41 -0
  115. package/src/client/debug.ts +253 -0
  116. package/src/client/effects/aionui-compat.ts +183 -0
  117. package/src/client/effects/gestures.ts +265 -0
  118. package/src/client/effects/header-status.ts +30 -0
  119. package/src/client/effects/phone-chrome.ts +265 -0
  120. package/src/client/effects/turn-fold.ts +258 -0
  121. package/src/client/index.tsx +227 -0
  122. package/src/client/locales.ts +109 -0
  123. package/src/client/nav-store.ts +78 -0
  124. package/src/client/session-dot.ts +17 -0
  125. package/src/client/styles/base.css.ts +167 -0
  126. package/src/client/styles/chips.css.ts +224 -0
  127. package/src/client/styles/compat.css.ts +681 -0
  128. package/src/client/styles/composer.css.ts +504 -0
  129. package/src/client/styles/header.css.ts +368 -0
  130. package/src/client/styles/home.css.ts +477 -0
  131. package/src/client/styles/index.ts +25 -0
  132. package/src/client/styles/info.css.ts +238 -0
  133. package/src/client/styles/layout.css.ts +501 -0
  134. package/src/client/styles/misc.css.ts +128 -0
  135. package/src/client/styles/turn-fold.css.ts +106 -0
  136. package/src/index.ts +343 -0
@@ -0,0 +1,17 @@
1
+ import type { SessionSummary } from '@deepseek-ai/dsh-client-runtime/client'
2
+ import type { StateDotState } from '@deepseek-ai/dsh-client-ui-primitives'
3
+
4
+ /**
5
+ * Status dot state of one session, matching the official sidebar semantics.
6
+ * Shared by the home-screen row dots (MobileHome.tsx) and the session
7
+ * header's running indicator (effects/header-status.ts), which reads it
8
+ * outside React — see that module for why.
9
+ * @param row - session summary.
10
+ * @returns the dot state, or undefined when the row needs no dot.
11
+ */
12
+ export function dotState(row: SessionSummary): StateDotState | undefined {
13
+ if (row.pendingInteraction !== undefined) return 'warning'
14
+ if (row.running) return 'ongoing'
15
+ if (row.completed === true) return 'done'
16
+ return undefined
17
+ }
@@ -0,0 +1,167 @@
1
+ // base — split from src/client/mobile.css.ts (2026-08-16), order preserved.
2
+ // Do not reorder: styles/index.ts concatenates in this exact order.
3
+
4
+ export const BASE_CSS = `
5
+ /* ---------- safe-area variables (S2.1, 2026-08-17) ----------
6
+ Every safe-area use in this stylesheet reads --mnav-sat / --mnav-sab
7
+ instead of env() directly. Same computed value by default, but the
8
+ indirection gives one place to override: ?mobile-nav-inset=54 (client/
9
+ debug.ts) writes a fake inset onto the root element, so a desktop CDP
10
+ run can regress notch layout — env(safe-area-inset-*) is hard 0 in every
11
+ desktop browser, which is exactly why the header and the workbench
12
+ panel shipped broken to a real iPhone. */
13
+ :root {
14
+ --mnav-sat: env(safe-area-inset-top, 0px);
15
+ --mnav-sab: env(safe-area-inset-bottom, 0px);
16
+ }
17
+
18
+ /* ---------- base control styles (rendered at any width, hidden where unused) ---------- */
19
+
20
+ [data-mobile-nav="toggle"],
21
+ [data-mobile-nav="files"] {
22
+ display: inline-flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ width: 28px;
26
+ height: 28px;
27
+ flex: none;
28
+ padding: 0;
29
+ border: none;
30
+ border-radius: 50%;
31
+ background: transparent;
32
+ color: var(--dsw-alias-label-secondary, inherit);
33
+ cursor: pointer;
34
+ -webkit-tap-highlight-color: transparent;
35
+ }
36
+ [data-mobile-nav="toggle"]:hover,
37
+ [data-mobile-nav="files"]:hover {
38
+ background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
39
+ }
40
+ [data-mobile-nav="toggle"]:focus-visible,
41
+ [data-mobile-nav="files"]:focus-visible {
42
+ outline: 2px solid var(--dsw-alias-state-business-primary, #4f6ef7);
43
+ outline-offset: 1px;
44
+ }
45
+
46
+ /* Drawer footer actions: the relocated Session log download plus the Files
47
+ action that opens the dsh-web-ui explorer sheet. */
48
+ [data-mobile-nav="drawer-actions"] {
49
+ display: inline-flex;
50
+ align-items: center;
51
+ gap: 8px;
52
+ }
53
+ [data-mobile-nav="session-log"],
54
+ [data-mobile-nav="explorer"] {
55
+ display: inline-flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ gap: 6px;
59
+ height: 34px;
60
+ padding: 0 12px;
61
+ border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12));
62
+ border-radius: 12px;
63
+ background: transparent;
64
+ color: var(--dsw-alias-label-primary, inherit);
65
+ font-family: inherit;
66
+ font-size: 13px;
67
+ line-height: 20px;
68
+ cursor: pointer;
69
+ -webkit-tap-highlight-color: transparent;
70
+ }
71
+ [data-mobile-nav="session-log"]:hover:not(:disabled),
72
+ [data-mobile-nav="explorer"]:hover {
73
+ background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
74
+ }
75
+ [data-mobile-nav="session-log"]:disabled {
76
+ color: var(--dsw-alias-label-dimmed, rgba(0, 0, 0, .35));
77
+ cursor: default;
78
+ }
79
+
80
+ /* Floating fallback button (hero / blank phases without a session header).
81
+ The top clears the camera band below the status bar; when the client has
82
+ set viewport-fit=cover the safe-area inset moves it below the notch too. */
83
+ [data-mobile-nav="fab"] {
84
+ position: absolute;
85
+ top: calc(var(--mnav-sat) + 72px);
86
+ left: 10px;
87
+ z-index: 21;
88
+ display: inline-flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ width: 38px;
92
+ height: 38px;
93
+ padding: 0;
94
+ border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12));
95
+ border-radius: 50%;
96
+ background: var(--dsw-alias-button-floating-fill, #ffffff);
97
+ color: var(--dsw-alias-label-primary, inherit);
98
+ cursor: pointer;
99
+ box-shadow: 0 2px 12px rgba(0, 0, 0, .18);
100
+ -webkit-tap-highlight-color: transparent;
101
+ }
102
+ [data-mobile-nav="fab"]:hover {
103
+ background: var(--dsw-alias-button-floating-hover, rgba(0, 0, 0, .08));
104
+ }
105
+ [data-mobile-nav="fab"]:focus-visible {
106
+ outline: 2px solid var(--dsw-alias-state-business-primary, #4f6ef7);
107
+ outline-offset: 2px;
108
+ }
109
+
110
+ /* Dimmed backdrop under the open drawer; above every column, below the drawer. */
111
+ [data-mobile-nav="backdrop"] {
112
+ position: absolute;
113
+ inset: 0;
114
+ z-index: 30;
115
+ background: rgba(0, 0, 0, .45);
116
+ cursor: pointer;
117
+ animation: dsh-mobile-nav-fade .2s var(--ds-ease-in-out, ease-in-out);
118
+ -webkit-tap-highlight-color: transparent;
119
+ }
120
+ @keyframes dsh-mobile-nav-fade {
121
+ from { opacity: 0; }
122
+ to { opacity: 1; }
123
+ }
124
+ /* Settings sheet entrance: the official dialog mounts with no animation at
125
+ all, so it snaps in. Fade + slight rise/scale reads as a proper sheet. */
126
+ @keyframes dsh-mobile-nav-sheet-in {
127
+ from {
128
+ opacity: 0;
129
+ transform: translateY(14px) scale(.98);
130
+ }
131
+ to {
132
+ opacity: 1;
133
+ transform: none;
134
+ }
135
+ }
136
+ /* Preview sheet rise: the aionui preview column opens as a bottom sheet. */
137
+ @keyframes dsh-mobile-nav-sheet-up {
138
+ from {
139
+ opacity: 0;
140
+ transform: translateY(28px);
141
+ }
142
+ to {
143
+ opacity: 1;
144
+ transform: none;
145
+ }
146
+ }
147
+
148
+ /* ---------- beta-notice suppression (ALL widths — deliberate exception) ----
149
+ User-directed (2026-08-17): hide DSH's beta/preview notices everywhere,
150
+ including desktop. This is the ONE place this plugin intentionally breaks
151
+ its own ">=1024px strict no-op" rule — do not "fix" it back.
152
+ Selector notes:
153
+ - The hero "预览版" badge only carries a hashed class; per repo convention
154
+ the stable part is the suffix (_previewBadge), matched with [class$=].
155
+ - The first-run "内测声明" modal is hidden via its ARIA label. The label is
156
+ LOCALIZED — zh is covered; if the UI runs in another language the dialog
157
+ shows (harmless, dismiss by hand). Hiding targets the presentation
158
+ wrapper via :has() so the modal's backdrop cannot linger invisibly and
159
+ block taps; the app never requires acknowledging this dialog to work. */
160
+ [class$="_previewBadge"] {
161
+ display: none !important;
162
+ }
163
+ [role="presentation"]:has(> [role="dialog"][aria-label="内测声明"]),
164
+ [role="dialog"][aria-label="内测声明"] {
165
+ display: none !important;
166
+ }
167
+ `
@@ -0,0 +1,224 @@
1
+ // chips — S5 (2026-08-17): the home-screen plugin-entry chips row, its
2
+ // customize sheet, the settings gear button, and the settings/usage-stats
3
+ // dialog portal fix. Appended LAST in index.ts (after home.css.ts) so its
4
+ // `display` overrides win any tie against the phone shell's blanket
5
+ // `[data-mobile-nav="frame"] > :first-child { display: none }` rule — though
6
+ // the extra `:has()` clause already outranks it on specificity alone.
7
+
8
+ export const CHIPS_CSS = `/* ---------- chips row + settings entry (S5) ---------- */
9
+
10
+ @media (max-width: 767px) {
11
+ /* --- settings dialog / usage-stats panel / scheduled-tasks dialog portal fix ---
12
+ dsh-client-ui-settings-general's SettingsRoot (trigger button +
13
+ position:fixed;z-index:1000 modal), dsh-usage-stats' sidebar-footer
14
+ badge (position:fixed panel), and @opendsh/dsh-plugin-scheduled-tasks'
15
+ "定时任务" trigger (position:fixed;z-index:120 \`.dshst-overlay\`,
16
+ verified against 0.2.0 — real-device report 2026-08-17: chip click
17
+ produced no visible window) all mount as PLAIN CHILDREN of the sidebar
18
+ tree — not a React portal to document.body — inside
19
+ sidebar.settings / sidebar.footer.action
20
+ (dsh-client-ui-sidebar's SidebarRoot: footArea > settingsArea /
21
+ footerActions). home.css.ts sets the sidebar ROOT
22
+ (\`[data-mobile-nav="frame"] > :first-child\`) to display:none on the
23
+ phone breakpoint (the app shell replaced the drawer) — and display:none
24
+ removes the WHOLE subtree from the render tree, including
25
+ position:fixed descendants, which cannot escape it the way they escape
26
+ a merely-offscreen or opacity:0 ancestor. A \`.click()\` on the (still
27
+ DOM-present) hidden trigger still flips its component-local \`open\`
28
+ state and mounts the aria-modal dialog (synthetic dispatch bypasses
29
+ hit-testing — S2.1 precedent), but nothing paints without this fix.
30
+
31
+ Un-hiding the WHOLE sidebar would surface the session tree / new-session
32
+ button underneath the dialog, so this is scoped to exactly the sidebar
33
+ root plus its OWN direct children that are not on the path to
34
+ footArea: display:contents on the root removes its own box (no
35
+ duplicate page background/padding, no grid-column reflow) while
36
+ letting footArea/footerActions/settingsArea resume their OWN official
37
+ (non-none) display — logoRow / newSession / regionArea are hidden
38
+ explicitly instead. :has() matches against the DOM tree regardless of
39
+ the scope element's own computed display, so this reads correctly even
40
+ though the whole subtree started at display:none (same precedent as
41
+ every other :has()-gated rule in this stylesheet, e.g. the settings
42
+ sheet adaptation below, which depends on exactly this becoming
43
+ visible).
44
+
45
+ \`:first-child\` is a layout-column WRAPPER
46
+ (\`pI_x6G_sidebarCol\`, dsh-client-ui-layout's AppFrame grid cell), not
47
+ SidebarRoot's own div — SidebarRoot (\`hHd-Xa_root\`) sits one more empty
48
+ unstyled div below it (measured 2026-08-17: sidebarCol > div (no
49
+ class) > div.hHd-Xa_root > logoRow/newSession/regionArea/footArea).
50
+ display:contents on sidebarCol lets that whole chain resume its own
51
+ official (non-none) display with no other rule needed, but the
52
+ "hide the other three" selectors below must therefore be DESCENDANT
53
+ (unscoped depth), not direct-child \`>\`, or they silently match
54
+ nothing — confirmed live: an earlier \`>\`-only version left
55
+ logoRow/newSession/regionArea at \`display: flex\`, which is only
56
+ hidden from view by being fully underneath the usage-stats panel's
57
+ opaque body, not by this rule. */
58
+ [data-mobile-nav="frame"] > :first-child:has([aria-modal="true"], [data-usage-stats-panel], .dshst-overlay) {
59
+ display: contents !important;
60
+ }
61
+ [data-mobile-nav="frame"] > :first-child:has([aria-modal="true"], [data-usage-stats-panel], .dshst-overlay) [class$="_logoRow"],
62
+ [data-mobile-nav="frame"] > :first-child:has([aria-modal="true"], [data-usage-stats-panel], .dshst-overlay) [class$="_newSession"],
63
+ [data-mobile-nav="frame"] > :first-child:has([aria-modal="true"], [data-usage-stats-panel], .dshst-overlay) [class$="_regionArea"] {
64
+ display: none !important;
65
+ }
66
+
67
+ /* Home-top settings gear: 44pt touch target, pushed to the row's end by
68
+ its own auto margin (home-top itself stays a plain flex row). */
69
+ [data-mobile-nav="home-settings"] {
70
+ flex: none;
71
+ display: inline-flex;
72
+ align-items: center;
73
+ justify-content: center;
74
+ width: 44px;
75
+ height: 44px;
76
+ margin-left: auto;
77
+ padding: 0;
78
+ border: none;
79
+ background: transparent;
80
+ color: var(--dsw-alias-label-secondary, inherit);
81
+ cursor: pointer;
82
+ touch-action: manipulation;
83
+ -webkit-tap-highlight-color: transparent;
84
+ }
85
+ [data-mobile-nav="home-settings"]:active {
86
+ background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
87
+ border-radius: 50%;
88
+ }
89
+
90
+ /* Plugin-entry chips row: one horizontally-scrolling line of 34px pills
91
+ between the workspace title and the session list (prototype spec). A
92
+ thin scrollbar is the overflow affordance (AGENTS.md: an early
93
+ no-scrollbar version silently cut off the last tab with no way to
94
+ discover more content). */
95
+ [data-mobile-nav="chip-row"] {
96
+ flex: 0 0 auto;
97
+ display: flex;
98
+ align-items: center;
99
+ gap: 8px;
100
+ padding: 0 16px 10px;
101
+ overflow-x: auto;
102
+ overscroll-behavior: contain;
103
+ -webkit-overflow-scrolling: touch;
104
+ scrollbar-width: thin;
105
+ }
106
+ [data-mobile-nav="chip-row"]::-webkit-scrollbar {
107
+ height: 2px;
108
+ }
109
+ [data-mobile-nav="chip-row"]::-webkit-scrollbar-thumb {
110
+ background: var(--dsw-alias-border-l2, rgba(0, 0, 0, .16));
111
+ border-radius: 2px;
112
+ }
113
+ [data-mobile-nav="chip"],
114
+ [data-mobile-nav="chip-more"] {
115
+ flex: none;
116
+ display: inline-flex;
117
+ align-items: center;
118
+ gap: 6px;
119
+ height: 34px;
120
+ padding: 0 14px;
121
+ border: none;
122
+ border-radius: 17px;
123
+ background: var(--dsw-alias-bg-base, #ffffff);
124
+ color: var(--dsw-alias-label-primary, inherit);
125
+ font-family: inherit;
126
+ font-size: 13px;
127
+ font-weight: 500;
128
+ white-space: nowrap;
129
+ cursor: pointer;
130
+ touch-action: manipulation;
131
+ -webkit-tap-highlight-color: transparent;
132
+ box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
133
+ }
134
+ [data-mobile-nav="chip-more"] {
135
+ padding: 0 9px;
136
+ }
137
+ [data-mobile-nav="chip"]:active,
138
+ [data-mobile-nav="chip-more"]:active {
139
+ background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
140
+ }
141
+
142
+ /* Harvested chip icon (S5.1): the cloned <svg> keeps whatever width/height
143
+ the source plugin gave it (18px for the one verified live example) —
144
+ force it down to the same 16px box every hand-registered chip icon
145
+ uses, CSS wins over the presentational SVG attributes. */
146
+ [data-mobile-nav="chip-harvest-icon"] {
147
+ display: inline-flex;
148
+ flex: none;
149
+ }
150
+ [data-mobile-nav="chip-harvest-icon"] svg {
151
+ display: block;
152
+ width: 16px;
153
+ height: 16px;
154
+ }
155
+
156
+ /* Customize sheet: toggle rows inside the SAME home-sheet-layer/mask/sheet
157
+ chrome the workspace switcher declares in home.css.ts — S6's
158
+ drag-to-close and mask-click-close bind to
159
+ \`[data-mobile-nav="home-sheet"]\` generically (effects/gestures.ts), so
160
+ this second use of the marker needs no new gesture wiring. */
161
+ [data-mobile-nav="chip-toggle-row"] {
162
+ display: flex;
163
+ align-items: center;
164
+ gap: 10px;
165
+ width: 100%;
166
+ /* Real-device fix: without border-box this row's used width is
167
+ 100% (of the sheet's own already-padded content box) PLUS the
168
+ 12px+12px padding below, overflowing the sheet by 24px and
169
+ shoving the flex:none toggle off its right edge (2026-08-17
170
+ report: "开关跑到屏幕外"). home-sheet-item next door in
171
+ home.css.ts has the identical width:100%+padding shape and the
172
+ same latent bug, just with no trailing element to visibly clip. */
173
+ box-sizing: border-box;
174
+ min-height: 48px;
175
+ padding: 0 12px;
176
+ color: inherit;
177
+ }
178
+ [data-mobile-nav="chip-toggle-label"] {
179
+ flex: 1 1 auto;
180
+ min-width: 0;
181
+ font-size: 15px;
182
+ overflow: hidden;
183
+ text-overflow: ellipsis;
184
+ white-space: nowrap;
185
+ }
186
+ [data-mobile-nav="chip-toggle"] {
187
+ flex: none;
188
+ position: relative;
189
+ width: 42px;
190
+ height: 26px;
191
+ border: none;
192
+ border-radius: 13px;
193
+ background: var(--dsw-alias-border-l2, rgba(0, 0, 0, .16));
194
+ cursor: pointer;
195
+ touch-action: manipulation;
196
+ -webkit-tap-highlight-color: transparent;
197
+ transition: background .15s var(--ds-ease-in-out, ease-in-out);
198
+ }
199
+ [data-mobile-nav="chip-toggle"]::after {
200
+ content: '';
201
+ position: absolute;
202
+ top: 2px;
203
+ left: 2px;
204
+ width: 22px;
205
+ height: 22px;
206
+ border-radius: 50%;
207
+ background: #ffffff;
208
+ box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
209
+ transition: transform .15s var(--ds-ease-in-out, ease-in-out);
210
+ }
211
+ [data-mobile-nav="chip-toggle"][aria-checked="true"] {
212
+ background: var(--dsw-alias-button-primary-fill, #1a1a1a);
213
+ }
214
+ [data-mobile-nav="chip-toggle"][aria-checked="true"]::after {
215
+ transform: translateX(16px);
216
+ }
217
+ @media (prefers-reduced-motion: reduce) {
218
+ [data-mobile-nav="chip-toggle"],
219
+ [data-mobile-nav="chip-toggle"]::after {
220
+ transition: none !important;
221
+ }
222
+ }
223
+ }
224
+ `