dsh-zen-remote 1.1.11 → 1.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.
- package/AGENTS.md +24 -0
- package/README.md +7 -3
- package/README.zh-CN.md +7 -3
- package/docs/interface.md +5 -2
- package/dsh-push.mjs +25 -1
- package/lan-gate.mjs +16 -1
- package/lib/client.js +684 -43
- package/lib/lan-gate-server.cjs +107 -4
- package/lib/types/client/MobileHome.d.ts +7 -2
- package/lib/types/client/MobileHome.d.ts.map +1 -1
- package/lib/types/client/MobileSessionInfo.d.ts +2 -2
- package/lib/types/client/MobileSessionInfo.d.ts.map +1 -1
- package/lib/types/client/compat/store.d.ts +62 -0
- package/lib/types/client/compat/store.d.ts.map +1 -0
- package/lib/types/client/compat/types.d.ts +84 -0
- package/lib/types/client/compat/types.d.ts.map +1 -0
- package/lib/types/client/debug.d.ts +1 -1
- package/lib/types/client/debug.d.ts.map +1 -1
- package/lib/types/client/effects/aionui-compat.d.ts +1 -1
- package/lib/types/client/effects/aionui-compat.d.ts.map +1 -1
- package/lib/types/client/effects/gestures.d.ts +1 -1
- package/lib/types/client/effects/gestures.d.ts.map +1 -1
- package/lib/types/client/effects/header-status.d.ts +1 -1
- package/lib/types/client/effects/header-status.d.ts.map +1 -1
- package/lib/types/client/effects/keyboard-avoid.d.ts +1 -1
- package/lib/types/client/effects/keyboard-avoid.d.ts.map +1 -1
- package/lib/types/client/effects/keyboard-guard.d.ts +1 -1
- package/lib/types/client/effects/keyboard-guard.d.ts.map +1 -1
- package/lib/types/client/effects/modal-back.d.ts +1 -1
- package/lib/types/client/effects/modal-back.d.ts.map +1 -1
- package/lib/types/client/effects/model-sheet-extras.d.ts +33 -0
- package/lib/types/client/effects/model-sheet-extras.d.ts.map +1 -0
- package/lib/types/client/effects/native-trigger-overlay.d.ts +1 -1
- package/lib/types/client/effects/native-trigger-overlay.d.ts.map +1 -1
- package/lib/types/client/effects/phone-chrome.d.ts +1 -1
- package/lib/types/client/effects/phone-chrome.d.ts.map +1 -1
- package/lib/types/client/effects/turn-fold.d.ts +1 -1
- package/lib/types/client/effects/turn-fold.d.ts.map +1 -1
- package/lib/types/client/effects/welcome-notice.d.ts +1 -1
- package/lib/types/client/effects/welcome-notice.d.ts.map +1 -1
- package/lib/types/client/effects/workbench-ref-close.d.ts +1 -1
- package/lib/types/client/effects/workbench-ref-close.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/nav-store.d.ts +2 -2
- package/lib/types/client/nav-store.d.ts.map +1 -1
- package/lib/types/client/session-dot.d.ts +11 -3
- package/lib/types/client/session-dot.d.ts.map +1 -1
- package/lib/types/client/styles/compat.css.d.ts +1 -1
- package/lib/types/client/styles/compat.css.d.ts.map +1 -1
- package/lib/types/client/styles/composer.css.d.ts +1 -1
- package/lib/types/client/styles/composer.css.d.ts.map +1 -1
- package/lib/types/client/styles/info.css.d.ts +1 -1
- package/lib/types/client/styles/info.css.d.ts.map +1 -1
- package/package.json +49 -26
- package/pwa/app.css +47 -7
- package/scripts/check-client-externals.mjs +48 -0
- package/scripts/check-compat-store.mjs +113 -0
- package/scripts/check-host-shadow.mjs +145 -0
- package/scripts/check-info-sheet-stacking.mjs +55 -0
- package/src/client/MobileAttachButton.tsx +1 -1
- package/src/client/MobileHome.tsx +33 -7
- package/src/client/MobileSessionInfo.tsx +6 -4
- package/src/client/chips-store.ts +1 -1
- package/src/client/compat/store.ts +205 -0
- package/src/client/compat/types.ts +115 -0
- package/src/client/debug.ts +1 -1
- package/src/client/effects/aionui-compat.ts +1 -1
- package/src/client/effects/gestures.ts +1 -1
- package/src/client/effects/header-status.ts +22 -3
- package/src/client/effects/keyboard-avoid.ts +1 -1
- package/src/client/effects/keyboard-guard.ts +1 -1
- package/src/client/effects/modal-back.ts +1 -1
- package/src/client/effects/model-sheet-extras.ts +159 -0
- package/src/client/effects/native-trigger-overlay.ts +1 -1
- package/src/client/effects/phone-chrome.ts +1 -1
- package/src/client/effects/turn-fold.ts +1 -1
- package/src/client/effects/welcome-notice.ts +1 -1
- package/src/client/effects/workbench-ref-close.ts +1 -1
- package/src/client/index.tsx +70 -6
- package/src/client/nav-store.ts +2 -2
- package/src/client/session-dot.ts +13 -4
- package/src/client/styles/compat.css.ts +43 -11
- package/src/client/styles/composer.css.ts +184 -9
- package/src/client/styles/info.css.ts +21 -0
package/lib/client.js
CHANGED
|
@@ -316,12 +316,188 @@ function MobileDrawerFooter({ useSessions, downloadSessionLog, toggleSidebar, t
|
|
|
316
316
|
}, children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.IconDownloadOutline16, { size: 14 }), (0, jsx_runtime_1.jsx)("span", { children: t('sessionLog') })] })] }));
|
|
317
317
|
}
|
|
318
318
|
};
|
|
319
|
+
__modules["compat/types.js"] = function (require, module, exports) {
|
|
320
|
+
"use strict";
|
|
321
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
322
|
+
exports.agentPresetOf = agentPresetOf;
|
|
323
|
+
exports.hasPendingInteraction = hasPendingInteraction;
|
|
324
|
+
/**
|
|
325
|
+
* 0.1.1's session row carries `agentPreset`; 0.1.2 dropped it. Reading it
|
|
326
|
+
* must not depend on which version's types are in scope in the current
|
|
327
|
+
* file, so narrow through unknown.
|
|
328
|
+
* @param row - a session row of either version.
|
|
329
|
+
* @returns the agent preset name, or undefined when absent.
|
|
330
|
+
*/
|
|
331
|
+
function agentPresetOf(row) {
|
|
332
|
+
const agentPreset = row?.agentPreset;
|
|
333
|
+
return typeof agentPreset === 'string' ? agentPreset : undefined;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* 0.1.1's session row carries `pendingInteraction` (the pending-interaction
|
|
337
|
+
* state behind the sidebar's yellow dot); 0.1.2 serves it from the
|
|
338
|
+
* uiSession service instead. This answers only "does this row carry a
|
|
339
|
+
* pending interaction".
|
|
340
|
+
* @param row - a session row of either version.
|
|
341
|
+
* @returns true when the row has a pending interaction.
|
|
342
|
+
*/
|
|
343
|
+
function hasPendingInteraction(row) {
|
|
344
|
+
const pending = row?.pendingInteraction;
|
|
345
|
+
return pending !== undefined && pending !== null;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
__modules["compat/store.js"] = function (require, module, exports) {
|
|
349
|
+
"use strict";
|
|
350
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
351
|
+
exports.createSnapshotStore = createSnapshotStore;
|
|
352
|
+
exports.defineStore = defineStore;
|
|
353
|
+
exports.workspaceTitleOf = workspaceTitleOf;
|
|
354
|
+
/**
|
|
355
|
+
* Create a snapshot store with optional whole-value localStorage persistence.
|
|
356
|
+
* The 0.1.1 `opts.flush: 'raf' | 'sync'` mode is deliberately not
|
|
357
|
+
* implemented: this plugin never used it (chips-store.ts persists
|
|
358
|
+
* synchronously and React reads via useSyncExternalStore).
|
|
359
|
+
* @param init - initial state.
|
|
360
|
+
* @param opts - optional persistence key.
|
|
361
|
+
* @returns the store.
|
|
362
|
+
*/
|
|
363
|
+
function createSnapshotStore(init, opts) {
|
|
364
|
+
let state = init;
|
|
365
|
+
const listeners = new Set();
|
|
366
|
+
const persistName = opts?.persist?.name;
|
|
367
|
+
// Rehydrate once at construction: a persisted whole value replaces the
|
|
368
|
+
// initial state wholesale. Failures (quota, private mode, corrupted JSON)
|
|
369
|
+
// only log — a broken persisted value must not take the store down, and a
|
|
370
|
+
// store that cannot persist must still work in memory.
|
|
371
|
+
if (persistName !== undefined && typeof localStorage !== 'undefined') {
|
|
372
|
+
try {
|
|
373
|
+
const raw = localStorage.getItem(persistName);
|
|
374
|
+
if (raw !== null)
|
|
375
|
+
state = JSON.parse(raw);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
console.error(`snapshot store '${persistName}' rehydration failed:`, error);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const persist = () => {
|
|
382
|
+
if (persistName === undefined || typeof localStorage === 'undefined')
|
|
383
|
+
return;
|
|
384
|
+
try {
|
|
385
|
+
localStorage.setItem(persistName, JSON.stringify(state));
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
console.error(`snapshot store '${persistName}' persistence failed:`, error);
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
const notify = () => {
|
|
392
|
+
// Iterate a snapshot of the set so a callback that unsubscribes cannot
|
|
393
|
+
// make the loop skip listeners registered after it.
|
|
394
|
+
for (const fn of [...listeners])
|
|
395
|
+
fn();
|
|
396
|
+
};
|
|
397
|
+
return {
|
|
398
|
+
getSnapshot: () => state,
|
|
399
|
+
subscribe: (fn) => {
|
|
400
|
+
listeners.add(fn);
|
|
401
|
+
return () => {
|
|
402
|
+
listeners.delete(fn);
|
|
403
|
+
};
|
|
404
|
+
},
|
|
405
|
+
update: (mutator) => {
|
|
406
|
+
// ponytail: official uses immer's structural sharing; here we copy one
|
|
407
|
+
// level shallowly (array spread / object spread) and let the mutator
|
|
408
|
+
// edit that copy before swapping it in wholesale. That only supports
|
|
409
|
+
// one-level-deep draft writes — fine while both plugin stores stay
|
|
410
|
+
// flat ({view, workspace}, Record<string, boolean>); if a state ever
|
|
411
|
+
// nests, switch this to immer instead of deepening the copy.
|
|
412
|
+
const draft = (Array.isArray(state) ? [...state] : { ...state });
|
|
413
|
+
mutator(draft);
|
|
414
|
+
state = draft;
|
|
415
|
+
notify();
|
|
416
|
+
persist();
|
|
417
|
+
},
|
|
418
|
+
set: (next) => {
|
|
419
|
+
state = next;
|
|
420
|
+
notify();
|
|
421
|
+
persist();
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Declare a store: an init lambda (fresh state per instance), an optional
|
|
427
|
+
* persistence key, and the full write set as pure draft mutators. Returns
|
|
428
|
+
* the registration currency of the slot system's store seat.
|
|
429
|
+
*
|
|
430
|
+
* The `A & ActionsDecl<T>` actions position is load-bearing: T resolves from
|
|
431
|
+
* `init` in the first inference round, and the intersection then
|
|
432
|
+
* contextually types each mutator's draft parameter, so call sites write
|
|
433
|
+
* `(draft, view: MobileView) => { … }` without annotating the draft. Keep
|
|
434
|
+
* the intersection — dropping it makes nav-store.ts fail to typecheck.
|
|
435
|
+
*
|
|
436
|
+
* Persist-key rules mirror the official defineStore: `decl.persist` alone at
|
|
437
|
+
* root scope, suffixed with `.{scopeKey}` per session scope, none when
|
|
438
|
+
* `decl.persist` is absent.
|
|
439
|
+
* @param decl - init/persist/actions declaration.
|
|
440
|
+
* @returns the store handle.
|
|
441
|
+
*/
|
|
442
|
+
function defineStore(decl) {
|
|
443
|
+
return {
|
|
444
|
+
spec: decl,
|
|
445
|
+
create(scopeKey) {
|
|
446
|
+
const persistKey = decl.persist === undefined
|
|
447
|
+
? undefined
|
|
448
|
+
: scopeKey === undefined
|
|
449
|
+
? decl.persist
|
|
450
|
+
: `${decl.persist}.${scopeKey}`;
|
|
451
|
+
const store = createSnapshotStore(decl.init(), persistKey !== undefined ? { persist: { name: persistKey } } : undefined);
|
|
452
|
+
// TS cannot verify an object that is filled key-by-key from
|
|
453
|
+
// Object.keys(decl.actions) satisfies the BakedActions mapped type, so
|
|
454
|
+
// the map is built loosely and narrowed once at the contract boundary;
|
|
455
|
+
// at runtime every declared action key is present (the declaration's
|
|
456
|
+
// own keys are the ones iterated).
|
|
457
|
+
const actions = {};
|
|
458
|
+
for (const [key, mutate] of Object.entries(decl.actions)) {
|
|
459
|
+
actions[key] = (...params) => {
|
|
460
|
+
store.update((draft) => {
|
|
461
|
+
mutate(draft, ...params);
|
|
462
|
+
});
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
actions: actions,
|
|
467
|
+
getSnapshot: () => store.getSnapshot(),
|
|
468
|
+
subscribe: (fn) => store.subscribe(fn),
|
|
469
|
+
store,
|
|
470
|
+
clearPersisted: () => {
|
|
471
|
+
if (persistKey === undefined || typeof localStorage === 'undefined')
|
|
472
|
+
return;
|
|
473
|
+
try {
|
|
474
|
+
localStorage.removeItem(persistKey);
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
// Storage gone or denied: nothing to clean up anyway.
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
};
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Last path segment of a workspace directory, for showing "which workspace
|
|
486
|
+
* is this session in" on the mobile info card. 0.1.1's one-line
|
|
487
|
+
* implementation, kept verbatim.
|
|
488
|
+
* @param cwd - a workspace path (posix or windows separators).
|
|
489
|
+
* @returns the trailing name, or '' when no non-empty segment exists.
|
|
490
|
+
*/
|
|
491
|
+
function workspaceTitleOf(cwd) {
|
|
492
|
+
return cwd.replace(/[/\\]+$/, '').split(/[/\\]/).pop() ?? '';
|
|
493
|
+
}
|
|
494
|
+
};
|
|
319
495
|
__modules["nav-store.js"] = function (require, module, exports) {
|
|
320
496
|
"use strict";
|
|
321
497
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
322
498
|
exports.SESSION_INFO_EVENT = exports.GO_HOME_EVENT = void 0;
|
|
323
499
|
exports.createNavStore = createNavStore;
|
|
324
|
-
const
|
|
500
|
+
const store_ts_1 = require("./compat/store.js");
|
|
325
501
|
/**
|
|
326
502
|
* `window` event a `conversation.session.header.*` slot (session scope)
|
|
327
503
|
* fires to move the ROOT-scope nav store back to `home` — see
|
|
@@ -362,7 +538,7 @@ exports.SESSION_INFO_EVENT = 'dsh-mobile-nav:session-info';
|
|
|
362
538
|
* one apply().
|
|
363
539
|
*/
|
|
364
540
|
function createNavStore() {
|
|
365
|
-
return (0,
|
|
541
|
+
return (0, store_ts_1.defineStore)({
|
|
366
542
|
init: () => ({ view: 'home', workspace: null }),
|
|
367
543
|
actions: {
|
|
368
544
|
/**
|
|
@@ -505,16 +681,25 @@ __modules["session-dot.js"] = function (require, module, exports) {
|
|
|
505
681
|
"use strict";
|
|
506
682
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
507
683
|
exports.dotState = dotState;
|
|
684
|
+
const types_ts_1 = require("./compat/types.js");
|
|
508
685
|
/**
|
|
509
686
|
* Status dot state of one session, matching the official sidebar semantics.
|
|
510
687
|
* Shared by the home-screen row dots (MobileHome.tsx) and the session
|
|
511
688
|
* header's running indicator (effects/header-status.ts), which reads it
|
|
512
689
|
* outside React — see that module for why.
|
|
513
|
-
*
|
|
690
|
+
*
|
|
691
|
+
* Pending interaction has two version-specific sources: `pending` is the
|
|
692
|
+
* 0.1.2 source (the uiSession service / useSessionPendingInteraction map,
|
|
693
|
+
* which this dot's caller resolves), and `hasPendingInteraction(row)` is the
|
|
694
|
+
* 0.1.1 source (the row's own field). Each version hits exactly one of the
|
|
695
|
+
* two: on 0.1.2 the row field is gone so the row check is always false, and
|
|
696
|
+
* on 0.1.1 `pending` is always false because the caller has no uiSession.
|
|
697
|
+
* @param row - session row.
|
|
698
|
+
* @param pending - whether the uiSession pending map has this row (0.1.2).
|
|
514
699
|
* @returns the dot state, or undefined when the row needs no dot.
|
|
515
700
|
*/
|
|
516
|
-
function dotState(row) {
|
|
517
|
-
if (
|
|
701
|
+
function dotState(row, pending) {
|
|
702
|
+
if (pending === true || (0, types_ts_1.hasPendingInteraction)(row))
|
|
518
703
|
return 'warning';
|
|
519
704
|
if (row.running)
|
|
520
705
|
return 'ongoing';
|
|
@@ -529,7 +714,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
529
714
|
exports.isChipEnabled = isChipEnabled;
|
|
530
715
|
exports.toggleChip = toggleChip;
|
|
531
716
|
exports.useChipsPrefs = useChipsPrefs;
|
|
532
|
-
const
|
|
717
|
+
const store_ts_1 = require("./compat/store.js");
|
|
533
718
|
const react_1 = require("react");
|
|
534
719
|
/**
|
|
535
720
|
* Plain `createSnapshotStore` (runtime contract/store.d.ts), not a
|
|
@@ -544,7 +729,7 @@ const react_1 = require("react");
|
|
|
544
729
|
* localStorage-backed identity, so re-importing the module always reaches
|
|
545
730
|
* the same store.
|
|
546
731
|
*/
|
|
547
|
-
const store = (0,
|
|
732
|
+
const store = (0, store_ts_1.createSnapshotStore)({}, { persist: { name: 'dsh-mobile-nav.chips' } });
|
|
548
733
|
/** @param prefs - current snapshot. @param id - chip id. @returns whether the chip should render (default true). */
|
|
549
734
|
function isChipEnabled(prefs, id) {
|
|
550
735
|
return prefs[id] !== false;
|
|
@@ -877,6 +1062,7 @@ exports.MobileHome = MobileHome;
|
|
|
877
1062
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
878
1063
|
const react_1 = require("react");
|
|
879
1064
|
const dsh_client_ui_primitives_1 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
1065
|
+
const types_ts_1 = require("./compat/types.js");
|
|
880
1066
|
const nav_store_ts_1 = require("./nav-store.js");
|
|
881
1067
|
const history_nav_ts_1 = require("./history-nav.js");
|
|
882
1068
|
/** The phone shell's one page-stack step: session list -> session. */
|
|
@@ -885,6 +1071,16 @@ const session_dot_ts_1 = require("./session-dot.js");
|
|
|
885
1071
|
const MobileHomeChips_tsx_1 = require("./MobileHomeChips.js");
|
|
886
1072
|
/** Phone breakpoint: below the tablet range, where the app-shell layout applies. */
|
|
887
1073
|
const PHONE_QUERY = '(max-width: 767px)';
|
|
1074
|
+
/**
|
|
1075
|
+
* Empty pending-interaction map singleton: when the 0.1.2 source is absent
|
|
1076
|
+
* the fallback hook returns THIS map, so the selector sees one stable
|
|
1077
|
+
* reference across renders — a fresh Map per render would make every
|
|
1078
|
+
* selector result a new reference and loop the list into infinite
|
|
1079
|
+
* re-renders. Module-level on purpose.
|
|
1080
|
+
*/
|
|
1081
|
+
const EMPTY_PENDING_MAP = new Map();
|
|
1082
|
+
/** 0.1.1 兜底:恒返回空 Map 的替身 hook(见组件里 useSessionPendingInteraction ?? EMPTY_PENDING_HOOK)。 */
|
|
1083
|
+
const EMPTY_PENDING_HOOK = (sel) => sel(EMPTY_PENDING_MAP);
|
|
888
1084
|
/** Swipe geometry: reveal width of the archive action / settle threshold. */
|
|
889
1085
|
const SWIPE_REVEAL = 88;
|
|
890
1086
|
const SWIPE_SETTLE = 44;
|
|
@@ -1029,19 +1225,20 @@ function relativeTime(at) {
|
|
|
1029
1225
|
/**
|
|
1030
1226
|
* Card status subline (real-device round 2 follow-up, 2026-08-17): reuses
|
|
1031
1227
|
* `dotState`'s own state read (the same ongoing/warning/done semantics the
|
|
1032
|
-
* dot already encodes) plus `agentPreset` from the same
|
|
1033
|
-
*
|
|
1228
|
+
* dot already encodes) plus `agentPreset` from the same session row — no
|
|
1229
|
+
* new data source. Returns undefined when there is neither a
|
|
1034
1230
|
* state nor a preset to show, so the caller can skip the subline entirely
|
|
1035
1231
|
* rather than render an empty row.
|
|
1036
1232
|
*/
|
|
1037
1233
|
function statusLine(row, dot, t) {
|
|
1234
|
+
const preset = (0, types_ts_1.agentPresetOf)(row);
|
|
1038
1235
|
const state = dot === 'ongoing' ? t('homeStatusOngoing')
|
|
1039
1236
|
: dot === 'warning' ? t('homeStatusWarning')
|
|
1040
1237
|
: dot === 'done' ? t('homeStatusDone')
|
|
1041
1238
|
: undefined;
|
|
1042
|
-
if (state !== undefined &&
|
|
1043
|
-
return `${state} · ${
|
|
1044
|
-
return state ??
|
|
1239
|
+
if (state !== undefined && preset !== undefined)
|
|
1240
|
+
return `${state} · ${preset}`;
|
|
1241
|
+
return state ?? preset;
|
|
1045
1242
|
}
|
|
1046
1243
|
/**
|
|
1047
1244
|
* Phone home screen: the full-screen session list that owns the first level
|
|
@@ -1052,7 +1249,7 @@ function statusLine(row, dot, t) {
|
|
|
1052
1249
|
* all navigation from the injected official actions; nothing here reads the
|
|
1053
1250
|
* official DOM.
|
|
1054
1251
|
*/
|
|
1055
|
-
function MobileHome({ useSessions, useWorkspaces, useStore, actions, openSession, startSession, downloadSessionLog, archiveSession, t, }) {
|
|
1252
|
+
function MobileHome({ useSessions, useWorkspaces, useSessionPendingInteraction, useStore, actions, openSession, startSession, downloadSessionLog, archiveSession, t, }) {
|
|
1056
1253
|
const phone = usePhone();
|
|
1057
1254
|
const iconHref = useSiteIconHref();
|
|
1058
1255
|
const [iconBroken, setIconBroken] = (0, react_1.useState)(false);
|
|
@@ -1065,6 +1262,11 @@ function MobileHome({ useSessions, useWorkspaces, useStore, actions, openSession
|
|
|
1065
1262
|
const [sheet, setSheet] = (0, react_1.useState)(null);
|
|
1066
1263
|
/** The one row whose archive action is swiped open (null = none). */
|
|
1067
1264
|
const [swipeOpen, setSwipeOpen] = (0, react_1.useState)(null);
|
|
1265
|
+
// 0.1.2 的待处理交互来源;0.1.1 没有这个标准 prop,读到 undefined 时用一个
|
|
1266
|
+
// 恒返回空 Map 的替身,保证 hook 调用次数与顺序在两版之间一致(hooks 规则:
|
|
1267
|
+
// 真实 hook 只在 0.1.2 被调用,且每次渲染同一位置无条件调用一次)。
|
|
1268
|
+
const pendingHook = useSessionPendingInteraction ?? EMPTY_PENDING_HOOK;
|
|
1269
|
+
const pending = pendingHook((m) => m);
|
|
1068
1270
|
/** Archive with the same confirm gate the session-info card uses. */
|
|
1069
1271
|
const onArchiveRow = (id) => {
|
|
1070
1272
|
if (!window.confirm(t('infoArchiveConfirm'))) {
|
|
@@ -1164,7 +1366,7 @@ function MobileHome({ useSessions, useWorkspaces, useStore, actions, openSession
|
|
|
1164
1366
|
return null;
|
|
1165
1367
|
const title = selectedWorkspace?.title ?? t('allWorkspaces');
|
|
1166
1368
|
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [view === 'session' && ((0, jsx_runtime_1.jsx)("button", { type: "button", "data-mobile-nav": "hero-back", "aria-label": t('backToList'), onClick: () => actions.show('home'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.IconChevronLeftOutline14, { size: 20 }) })), (0, jsx_runtime_1.jsxs)("div", { "data-mobile-nav": "home", "data-view": view, "aria-hidden": view === 'session', children: [(0, jsx_runtime_1.jsxs)("div", { "data-mobile-nav": "home-top", children: [iconHref !== undefined && !iconBroken && ((0, jsx_runtime_1.jsx)("img", { src: iconHref, alt: "", "aria-hidden": "true", "data-mobile-nav": "home-logo", onError: () => setIconBroken(true) })), (0, jsx_runtime_1.jsxs)("button", { type: "button", "data-mobile-nav": "ws-switch", "aria-haspopup": "menu", onClick: () => setSheet('filter'), children: [(0, jsx_runtime_1.jsx)("span", { children: title }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.IconChevronDownOutline14, { size: 14 })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", "data-mobile-nav": "home-settings", "aria-label": t('settings'), title: t('settings'), onClick: openSettings, children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.IconSettingsOutline16, { size: 18 }) })] }), (0, jsx_runtime_1.jsx)(MobileHomeChips_tsx_1.MobileHomeChips, { t: t, sessionId: sessions.current, downloadSessionLog: (id) => downloadSessionLog(id), onCustomize: () => setSheet('chips') }), (0, jsx_runtime_1.jsxs)("ul", { "data-mobile-nav": "home-list", children: [rows.map((row) => {
|
|
1167
|
-
const dot = (0, session_dot_ts_1.dotState)(row);
|
|
1369
|
+
const dot = (0, session_dot_ts_1.dotState)(row, pending.has(row.id));
|
|
1168
1370
|
const status = statusLine(row, dot, t);
|
|
1169
1371
|
const initial = row.displayTitle.trim().charAt(0).toUpperCase();
|
|
1170
1372
|
return ((0, jsx_runtime_1.jsx)(SwipeRow, { open: swipeOpen === row.id, onOpenChange: (open) => setSwipeOpen(open ? row.id : null), onArchive: () => onArchiveRow(row.id), archiveLabel: t('infoArchive'), children: (guardClick) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", "data-mobile-nav": "home-row", "data-current": row.id === sessions.current ? '' : undefined, onClick: () => guardClick(() => enter(() => openSession(row.id))), children: [(0, jsx_runtime_1.jsx)("span", { "data-mobile-nav": "home-row-avatar", "aria-hidden": "true", children: dot !== undefined ? (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.StateDot, { state: dot, size: 10 }) : initial }), (0, jsx_runtime_1.jsxs)("span", { "data-mobile-nav": "home-row-body", children: [(0, jsx_runtime_1.jsx)("span", { "data-mobile-nav": "home-row-title", children: row.displayTitle }), status !== undefined && ((0, jsx_runtime_1.jsx)("span", { "data-mobile-nav": "home-row-status", children: status }))] }), (0, jsx_runtime_1.jsx)("time", { "data-mobile-nav": "home-row-time", dateTime: new Date(row.updatedAt).toISOString(), children: relativeTime(row.updatedAt) })] })) }, row.id));
|
|
@@ -1419,7 +1621,8 @@ exports.MobileSessionInfo = MobileSessionInfo;
|
|
|
1419
1621
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
1420
1622
|
const react_1 = require("react");
|
|
1421
1623
|
const dsh_client_ui_primitives_1 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
1422
|
-
const
|
|
1624
|
+
const store_ts_1 = require("./compat/store.js");
|
|
1625
|
+
const types_ts_1 = require("./compat/types.js");
|
|
1423
1626
|
const nav_store_ts_1 = require("./nav-store.js");
|
|
1424
1627
|
const history_nav_ts_1 = require("./history-nav.js");
|
|
1425
1628
|
/** Layer id for the info sheet, so Android back closes it before leaving the session. */
|
|
@@ -1566,7 +1769,8 @@ function MobileSessionInfo({ sessionId, useSessions, useProjection, forkSession,
|
|
|
1566
1769
|
};
|
|
1567
1770
|
const cacheHit = usage === undefined ? null : cacheHitPercent(usage);
|
|
1568
1771
|
const tokensEmpty = usage === undefined || (billedInputTokens(usage) === 0 && usage.outputTokens === 0);
|
|
1569
|
-
const cwd = row?.cwd === undefined || row.cwd === '' ? undefined : (0,
|
|
1772
|
+
const cwd = row?.cwd === undefined || row.cwd === '' ? undefined : (0, store_ts_1.workspaceTitleOf)(row.cwd);
|
|
1773
|
+
const preset = (0, types_ts_1.agentPresetOf)(row);
|
|
1570
1774
|
const cells = [
|
|
1571
1775
|
{ label: t('infoStatTurns'), value: stats === undefined ? NA : String(stats.turns), sub: undefined },
|
|
1572
1776
|
{ label: t('infoStatSteps'), value: stats === undefined ? NA : String(stats.steps), sub: undefined },
|
|
@@ -1604,7 +1808,7 @@ function MobileSessionInfo({ sessionId, useSessions, useProjection, forkSession,
|
|
|
1604
1808
|
if (!tab.active)
|
|
1605
1809
|
tab.el.click();
|
|
1606
1810
|
close();
|
|
1607
|
-
}, children: tab.label }, tab.label))) })), (0, jsx_runtime_1.jsx)("button", { type: "button", "data-mobile-nav": "info-close", "aria-label": t('infoClose'), onClick: close, children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.IconCloseOutline16, { size: 16 }) })] }), (0, jsx_runtime_1.jsxs)("div", { "data-mobile-nav": "info-badges", children: [
|
|
1811
|
+
}, children: tab.label }, tab.label))) })), (0, jsx_runtime_1.jsx)("button", { type: "button", "data-mobile-nav": "info-close", "aria-label": t('infoClose'), onClick: close, children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.IconCloseOutline16, { size: 16 }) })] }), (0, jsx_runtime_1.jsxs)("div", { "data-mobile-nav": "info-badges", children: [preset !== undefined && (0, jsx_runtime_1.jsx)("span", { "data-mobile-nav": "info-badge", children: preset }), subagentCount > 0 && ((0, jsx_runtime_1.jsx)("span", { "data-mobile-nav": "info-badge", children: t('infoSubagents', { count: subagentCount }) })), jobCount > 0 && ((0, jsx_runtime_1.jsx)("span", { "data-mobile-nav": "info-badge", children: t('infoJobs', { count: jobCount }) })), (0, jsx_runtime_1.jsx)("span", { "data-mobile-nav": "info-badge-cwd", children: cwd ?? t('infoCwdFallback') })] }), (0, jsx_runtime_1.jsx)("div", { "data-mobile-nav": "info-stats", children: cells.map((cell) => ((0, jsx_runtime_1.jsxs)("div", { "data-mobile-nav": "info-stat", children: [(0, jsx_runtime_1.jsx)("span", { "data-mobile-nav": "info-stat-value", children: cell.value }), (0, jsx_runtime_1.jsx)("span", { "data-mobile-nav": "info-stat-label", children: cell.label }), cell.sub !== undefined && (0, jsx_runtime_1.jsx)("span", { "data-mobile-nav": "info-stat-sub", children: cell.sub })] }, cell.label))) }), error !== null && (0, jsx_runtime_1.jsx)("div", { "data-mobile-nav": "info-error", children: t('infoActionError', { message: error }) }), (0, jsx_runtime_1.jsxs)("div", { "data-mobile-nav": "info-actions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", "data-mobile-nav": "info-action", disabled: busy, onClick: onExport, children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.IconDownloadOutline16, { size: 16 }), (0, jsx_runtime_1.jsx)("span", { children: t('infoExport') })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", "data-mobile-nav": "info-action", disabled: busy, onClick: onRename, children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.IconEditOutline16, { size: 16 }), (0, jsx_runtime_1.jsx)("span", { children: t('infoRename') })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", "data-mobile-nav": "info-action", disabled: busy, onClick: onFork, children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.IconBranchOutline16, { size: 16 }), (0, jsx_runtime_1.jsx)("span", { children: t('infoFork') })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", "data-mobile-nav": "info-action", "data-mobile-nav-danger": "", disabled: busy, onClick: onArchive, children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.IconArchiveOutline20, { size: 20 }), (0, jsx_runtime_1.jsx)("span", { children: t('infoArchive') })] })] })] })] }));
|
|
1608
1812
|
}
|
|
1609
1813
|
};
|
|
1610
1814
|
__modules["attach-upload.js"] = function (require, module, exports) {
|
|
@@ -3470,22 +3674,39 @@ exports.COMPAT_CSS = ` /* ---------- dsh-web-ui family compatibility ----------
|
|
|
3470
3674
|
|
|
3471
3675
|
/* ---------- dsh-vision-router: composer vision toggle (2026-08-26) ----------
|
|
3472
3676
|
THIRD-PARTY COMPAT RULE — dsh-vision-router registers a Vision mode
|
|
3473
|
-
toggle into conversation.input.right (
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3677
|
+
toggle into conversation.input.right (slot id
|
|
3678
|
+
"vision-router-mode-toggle"): a bordered pill sized by INLINE styles —
|
|
3679
|
+
minHeight 28, padding 4px 8px, fontSize 12, white-space nowrap —
|
|
3680
|
+
roughly 90px of unshrinkable width.
|
|
3681
|
+
|
|
3682
|
+
Its children changed shape in 2.1.x (re-measured on the real UI
|
|
3683
|
+
2026-09-04, plugin 2.1.1). Both forms are handled:
|
|
3684
|
+
- text form (<= 2.0.x): <span>👁</span><span>label</span> plus a
|
|
3685
|
+
<span>✓</span> while active;
|
|
3686
|
+
- svg form (2.1.x, lib/client-presentation-boundary.js): a 14px eye
|
|
3687
|
+
<svg>, <span>label</span>, plus a SECOND 12px check <svg> while
|
|
3688
|
+
active.
|
|
3689
|
+
The 2.1.1 update is what the user hit: the label span was still hidden
|
|
3690
|
+
but the new eye svg was not, so it rendered NEXT TO our ::before eye —
|
|
3691
|
+
two eyes in a 28px button — and the active check svg had nothing hiding
|
|
3692
|
+
it at all, eating width the phone row does not have.
|
|
3478
3693
|
|
|
3479
3694
|
The phone composer row is nowrap by design (composer.css.ts section 1)
|
|
3480
3695
|
and the model seat is its only shrinkable item, so that pill squeezes
|
|
3481
3696
|
the model name out of the row (user report, 2026-08-26).
|
|
3482
3697
|
|
|
3483
3698
|
Phone treatment: a 28x28 round icon button in the attach button's
|
|
3484
|
-
language (same size, same neutral fill)
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3699
|
+
language (same size, same neutral fill), showing exactly ONE 16px eye
|
|
3700
|
+
and nothing else. Every span is hidden (label, and the text form's
|
|
3701
|
+
emoji and check); of the svg form's icons only the first survives, the
|
|
3702
|
+
trailing check svg is hidden by a sibling combinator so the count is
|
|
3703
|
+
never assumed. Our own ::before eye is now a FALLBACK, gated on
|
|
3704
|
+
:has(svg) so it draws only for the text form — the plugin ships its own
|
|
3705
|
+
icon from 2.1.x on, and drawing over it is what produced the double
|
|
3706
|
+
eye. Generated content rather than a DOM-injected icon, for the same
|
|
3707
|
+
survives-React-re-render reason as the auto-approve shield below: the
|
|
3708
|
+
button re-renders on every state flip, so an injected node would need
|
|
3709
|
+
an observer while ::before does not. The
|
|
3489
3710
|
accessible name is unaffected: the button ships aria-label + title with
|
|
3490
3711
|
the full enable/disable copy, and aria-pressed keeps conveying the
|
|
3491
3712
|
state; visually the plugin's own inline styles still flip border +
|
|
@@ -3528,12 +3749,27 @@ exports.COMPAT_CSS = ` /* ---------- dsh-web-ui family compatibility ----------
|
|
|
3528
3749
|
background: var(--dsw-specific-selector, rgba(127, 127, 127, .12)) !important;
|
|
3529
3750
|
box-shadow: none !important;
|
|
3530
3751
|
}
|
|
3531
|
-
/*
|
|
3752
|
+
/* Text form: emoji + label + active check are all spans — all out. */
|
|
3532
3753
|
[data-slot="conversation.composer.bar"] [data-vision-router-mode-toggle] > span {
|
|
3533
3754
|
display: none !important;
|
|
3534
3755
|
}
|
|
3756
|
+
/* Svg form (2.1.x): the first svg IS the eye, so keep it and match the
|
|
3757
|
+
row's other icons at 16px. */
|
|
3758
|
+
[data-slot="conversation.composer.bar"] [data-vision-router-mode-toggle] > svg:first-of-type {
|
|
3759
|
+
width: 16px !important;
|
|
3760
|
+
height: 16px !important;
|
|
3761
|
+
}
|
|
3762
|
+
/* ...and drop every svg after it — today that is the active-state check
|
|
3763
|
+
mark. A sibling combinator rather than :nth-child(2): the count is the
|
|
3764
|
+
plugin's business, this only says "one icon, the first one". */
|
|
3765
|
+
[data-slot="conversation.composer.bar"] [data-vision-router-mode-toggle] > svg ~ svg {
|
|
3766
|
+
display: none !important;
|
|
3767
|
+
}
|
|
3535
3768
|
/* The one svg: eye outline + iris as a currentColor mask — brand while
|
|
3536
3769
|
active, dimmed with the button by the plugin's own disabled opacity. */
|
|
3770
|
+
[data-slot="conversation.composer.bar"] [data-vision-router-mode-toggle]:has(svg)::before {
|
|
3771
|
+
content: none !important;
|
|
3772
|
+
}
|
|
3537
3773
|
[data-slot="conversation.composer.bar"] [data-vision-router-mode-toggle]::before {
|
|
3538
3774
|
content: '';
|
|
3539
3775
|
width: 16px;
|
|
@@ -4847,12 +5083,28 @@ exports.COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- */
|
|
|
4847
5083
|
}
|
|
4848
5084
|
${ROW} > [class$="_tools"] > [class$="_modes"] {
|
|
4849
5085
|
order: 3 !important;
|
|
4850
|
-
|
|
4851
|
-
|
|
5086
|
+
/* Rigid on purpose. This seat holds the permission chip, whose own
|
|
5087
|
+
wrapper (the plugin's element inside _modes) is \`flex: 0 0 auto\` and
|
|
5088
|
+
will NOT follow the seat down — so a shrinkable seat does not make the
|
|
5089
|
+
chip narrower, it just slices the seat out from under a chip that
|
|
5090
|
+
stays 44px wide, and the overflow lands on top of the model pill.
|
|
5091
|
+
Measured 2026-09-04 on DSH 0.1.2 at 390px: seat 90-120 (29.7px) with
|
|
5092
|
+
the chip still 90-134, model starting at 125 — a 9px overlap, exactly
|
|
5093
|
+
the "the two chips touch" report. With the seat rigid: seat 90-134,
|
|
5094
|
+
model 139-246, overlap 0; and at 320px the model pill shrinks to 52px
|
|
5095
|
+
and still nothing overlaps.
|
|
5096
|
+
This also restores what section 1 says out loud — the model pill is
|
|
5097
|
+
the ONLY shrinkable item in the row. \`min-width: 0\` is gone with the
|
|
5098
|
+
shrink: it only ever mattered as the companion of \`flex: 0 1\`. */
|
|
5099
|
+
flex: 0 0 auto !important;
|
|
4852
5100
|
gap: 6px !important;
|
|
4853
5101
|
}
|
|
4854
|
-
/* The
|
|
4855
|
-
the
|
|
5102
|
+
/* The elastic gap used to live here as \`margin-right: auto\`, which broke
|
|
5103
|
+
the moment a session had no model pill: a subagent session has none, so
|
|
5104
|
+
the gap vanished and the stop / send buttons bunched up in the middle of
|
|
5105
|
+
the row instead of sitting at the right edge (reported 2026-09-06). The
|
|
5106
|
+
gap belongs to the RIGHT group, not to the last item of the left one —
|
|
5107
|
+
see the two rules below the order list. */
|
|
4856
5108
|
${MODEL} {
|
|
4857
5109
|
order: 4 !important;
|
|
4858
5110
|
/* A definite flex-basis (not auto) breaks the circular sizing between
|
|
@@ -4863,7 +5115,6 @@ exports.COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- */
|
|
|
4863
5115
|
flex: 0 1 min(48vw, 200px) !important;
|
|
4864
5116
|
min-width: 0 !important;
|
|
4865
5117
|
max-width: min(48vw, 200px) !important;
|
|
4866
|
-
margin-right: auto !important;
|
|
4867
5118
|
}
|
|
4868
5119
|
/* Third-party input.right entries park next to the ring rather than
|
|
4869
5120
|
landing at order 0 (= far left) once the groups are flattened. */
|
|
@@ -4878,6 +5129,22 @@ exports.COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- */
|
|
|
4878
5129
|
${ROW} > [class$="_trailing"] > [class$="_primary"] {
|
|
4879
5130
|
order: 7 !important;
|
|
4880
5131
|
}
|
|
5132
|
+
/* The elastic gap. It sits in front of whichever of the two right-hand
|
|
5133
|
+
items comes first, so the group hugs the right edge no matter what the
|
|
5134
|
+
left side happens to contain — with a model pill, without one (subagent
|
|
5135
|
+
sessions), with or without the third-party entries at order 5.
|
|
5136
|
+
Both get \`margin-left: auto\` and the last rule takes it back off the
|
|
5137
|
+
send button whenever a ring precedes it: two auto margins would SHARE
|
|
5138
|
+
the free space and open a second gap between the ring and send. A
|
|
5139
|
+
sibling combinator answers "is there a ring before me" without assuming
|
|
5140
|
+
anything about the host's nesting. */
|
|
5141
|
+
${ROW} > [class$="_trailing"] > span[class$="_root"],
|
|
5142
|
+
${ROW} > [class$="_trailing"] > [class$="_primary"] {
|
|
5143
|
+
margin-left: auto !important;
|
|
5144
|
+
}
|
|
5145
|
+
${ROW} > [class$="_trailing"] > span[class$="_root"] ~ [class$="_primary"] {
|
|
5146
|
+
margin-left: 0 !important;
|
|
5147
|
+
}
|
|
4881
5148
|
|
|
4882
5149
|
/* --- 3. permission as icon-only pill; model as name+effort pill ---
|
|
4883
5150
|
(real-device round 2, 2026-08-17; model pill reworked 2026-08-25:
|
|
@@ -4998,6 +5265,115 @@ exports.COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- */
|
|
|
4998
5265
|
border-radius: 12px !important;
|
|
4999
5266
|
font-size: 15px !important;
|
|
5000
5267
|
}
|
|
5268
|
+
/* --- 4a. third-party composer entries live in the model sheet ---
|
|
5269
|
+
effects/model-sheet-extras.ts parks the \`conversation.input.right\`
|
|
5270
|
+
container inside the model menu while that menu is open and puts it back
|
|
5271
|
+
when it closes; these rules are the two halves of that trip.
|
|
5272
|
+
|
|
5273
|
+
In the row: hidden, but ONLY once the effect has marked the container as
|
|
5274
|
+
one it manages. Keying the hide off the marker rather than off "not
|
|
5275
|
+
currently parked" is what makes the failure direction safe — if the
|
|
5276
|
+
effect declines to act (nothing in the slot but the vision toggle, so
|
|
5277
|
+
nothing worth moving) or never runs at all, the marker is absent, this
|
|
5278
|
+
rule does not match, and the controls stay exactly where the host put
|
|
5279
|
+
them. The inverse spelling hid them in the row whenever they were not in
|
|
5280
|
+
the sheet, which in those same cases left them reachable from neither
|
|
5281
|
+
place (caught in review, 2026-09-06).
|
|
5282
|
+
Hiding by ancestor rather than by a display rule on the container itself
|
|
5283
|
+
is what lets the parked copy style itself freely — same element, two
|
|
5284
|
+
homes, two rule sets. \`!important\` because the slot container carries an
|
|
5285
|
+
inline \`display: contents\`. */
|
|
5286
|
+
${ROW} > [class$="_trailing"] > [data-slot="conversation.input.right"][data-zen-sheet-extras] {
|
|
5287
|
+
display: none !important;
|
|
5288
|
+
}
|
|
5289
|
+
/* Parked in the sheet: a column of full-width rows in the sheet's own
|
|
5290
|
+
language, so a relocated control reads as a sibling of 模型 and 推理等级
|
|
5291
|
+
rather than a chip that wandered in. */
|
|
5292
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] {
|
|
5293
|
+
display: flex !important;
|
|
5294
|
+
flex-direction: column !important;
|
|
5295
|
+
gap: 2px !important;
|
|
5296
|
+
width: 100% !important;
|
|
5297
|
+
}
|
|
5298
|
+
/* Undo the row's compaction on every parked control: back to full width and
|
|
5299
|
+
the 48px the sheet's own cells use. \`> * > *\` reaches both shapes without
|
|
5300
|
+
naming either plugin — subscriptions wraps its trigger in a positioning
|
|
5301
|
+
div, vision-router puts its button straight in the slot. */
|
|
5302
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] > *,
|
|
5303
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] > * > button {
|
|
5304
|
+
width: 100% !important;
|
|
5305
|
+
max-width: none !important;
|
|
5306
|
+
min-width: 0 !important;
|
|
5307
|
+
justify-content: flex-start !important;
|
|
5308
|
+
}
|
|
5309
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] button {
|
|
5310
|
+
box-sizing: border-box !important;
|
|
5311
|
+
min-height: 48px !important;
|
|
5312
|
+
height: auto !important;
|
|
5313
|
+
padding: 0 12px !important;
|
|
5314
|
+
gap: 10px !important;
|
|
5315
|
+
border-radius: 12px !important;
|
|
5316
|
+
border: none !important;
|
|
5317
|
+
background: transparent !important;
|
|
5318
|
+
font-size: 15px !important;
|
|
5319
|
+
/* Both plugins center their chip label, which is right for a pill in the
|
|
5320
|
+
row and wrong for a full-width row: the sheet's own cells start their
|
|
5321
|
+
label at the left edge. justify-content covers the flex triggers,
|
|
5322
|
+
text-align the ones that are not flex containers. */
|
|
5323
|
+
text-align: left !important;
|
|
5324
|
+
justify-content: flex-start !important;
|
|
5325
|
+
}
|
|
5326
|
+
/* Order comes from the row (compat.css.ts puts the vision toggle at 5 so it
|
|
5327
|
+
parks beside the context ring), and that rule still matches in here — the
|
|
5328
|
+
sheet is a DOM descendant of the composer bar even though it paints as a
|
|
5329
|
+
fixed layer. Reset it so the sheet follows DOM order instead of
|
|
5330
|
+
inheriting a decision that was about a different layout. */
|
|
5331
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] > * {
|
|
5332
|
+
order: 0 !important;
|
|
5333
|
+
}
|
|
5334
|
+
/* The vision toggle is squeezed to a 28px icon in the row (compat.css.ts);
|
|
5335
|
+
in the sheet it is a row, so its label comes back and the icon leads. */
|
|
5336
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] [data-vision-router-mode-toggle] > span {
|
|
5337
|
+
display: inline !important;
|
|
5338
|
+
}
|
|
5339
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] [data-vision-router-mode-toggle] > svg:first-of-type {
|
|
5340
|
+
width: 18px !important;
|
|
5341
|
+
height: 18px !important;
|
|
5342
|
+
}
|
|
5343
|
+
/* The speed chip opens a menu of its own, anchored \`bottom: 100%\` to its
|
|
5344
|
+
trigger. Inside the sheet — which is \`overflow: hidden\` so its rounded
|
|
5345
|
+
top clips its content — that menu would be cut off. Same escape section 4
|
|
5346
|
+
uses for the host's two menus: position: fixed leaves the clip behind
|
|
5347
|
+
(the sheet sets \`transform: none\`, so nothing between here and the
|
|
5348
|
+
viewport captures fixed positioning) and it lands as its own sheet on top
|
|
5349
|
+
of this one. */
|
|
5350
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] [role="menu"] {
|
|
5351
|
+
position: fixed !important;
|
|
5352
|
+
left: 0 !important;
|
|
5353
|
+
right: 0 !important;
|
|
5354
|
+
bottom: 0 !important;
|
|
5355
|
+
top: auto !important;
|
|
5356
|
+
margin: 0 !important;
|
|
5357
|
+
width: 100% !important;
|
|
5358
|
+
min-width: 0 !important;
|
|
5359
|
+
max-width: none !important;
|
|
5360
|
+
max-height: min(70dvh, 520px) !important;
|
|
5361
|
+
box-sizing: border-box !important;
|
|
5362
|
+
border-radius: 16px 16px 0 0 !important;
|
|
5363
|
+
border-bottom: none !important;
|
|
5364
|
+
padding: 8px 8px calc(8px + var(--mnav-sab)) !important;
|
|
5365
|
+
/* Above the model sheet it sits on (60, section 4), below the session-info
|
|
5366
|
+
sheet's 70 (info.css.ts) — it is a child of the model sheet, not a peer
|
|
5367
|
+
of the page's own layers. */
|
|
5368
|
+
z-index: 61 !important;
|
|
5369
|
+
box-shadow: 0 -8px 32px rgba(0, 0, 0, .18) !important;
|
|
5370
|
+
}
|
|
5371
|
+
${MODEL} > [class$="_menu"] > [data-zen-sheet-extras] [role="menu"] [role="menuitemradio"] {
|
|
5372
|
+
min-height: 48px !important;
|
|
5373
|
+
border-radius: 12px !important;
|
|
5374
|
+
font-size: 15px !important;
|
|
5375
|
+
}
|
|
5376
|
+
|
|
5001
5377
|
/* --- 4b. the official scroll-to-bottom button must not poke through the
|
|
5002
5378
|
sheet (real-device follow-up, 2026-08-17) ---
|
|
5003
5379
|
ChatView's own "jump to latest" button (aria-label t("chat.toBottom"))
|
|
@@ -5032,6 +5408,17 @@ exports.COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- */
|
|
|
5032
5408
|
[class$="_composerSeat"] {
|
|
5033
5409
|
--dsh-composer-text-max-height: 124px;
|
|
5034
5410
|
}
|
|
5411
|
+
/* Pull the text in from the card's left border. Upstream insets the editing
|
|
5412
|
+
host by 16px while the control row below it starts 8px in (row padding,
|
|
5413
|
+
section 1), so the placeholder sat noticeably further out than the
|
|
5414
|
+
buttons under it and the field read as over-padded on a phone (reported
|
|
5415
|
+
2026-09-06). 12px squares it with the 12px of clear space on the right,
|
|
5416
|
+
giving the text a symmetric box, and closes most of the gap with the
|
|
5417
|
+
button row. Anchored on the editing host's own contract attribute rather
|
|
5418
|
+
than a hashed class. */
|
|
5419
|
+
[data-slot="conversation.composer.bar"] [data-composer-input] {
|
|
5420
|
+
padding-left: 12px !important;
|
|
5421
|
+
}
|
|
5035
5422
|
|
|
5036
5423
|
/* --- 6. no divider above OR below the message list ---
|
|
5037
5424
|
Instead of a rule the messages butt against, they fade near both edges.
|
|
@@ -5123,7 +5510,13 @@ exports.COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- */
|
|
|
5123
5510
|
composer. Anchored on the official data-testid, a stable contract
|
|
5124
5511
|
marker rather than a hashed class name. */
|
|
5125
5512
|
[data-slot="conversation.input.dock"] > [data-testid="todo-panel"] {
|
|
5126
|
-
|
|
5513
|
+
/* A whole line to itself. It shared line 0 with the pills until the goal
|
|
5514
|
+
bar turned up (2026-09-06): two full-width things on one nowrap line
|
|
5515
|
+
meant the to-do header shrank to a stub and the goal bar ran off the
|
|
5516
|
+
right edge. Both are status strips you read at a glance, so they stack
|
|
5517
|
+
— see the line policy in 7c. */
|
|
5518
|
+
flex: 1 0 100% !important;
|
|
5519
|
+
order: 1 !important;
|
|
5127
5520
|
min-width: 0 !important;
|
|
5128
5521
|
max-width: none !important;
|
|
5129
5522
|
min-height: 0 !important;
|
|
@@ -5150,7 +5543,7 @@ exports.COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- */
|
|
|
5150
5543
|
display: flex !important;
|
|
5151
5544
|
flex: 1 0 100% !important;
|
|
5152
5545
|
flex-wrap: wrap !important;
|
|
5153
|
-
order:
|
|
5546
|
+
order: 4 !important;
|
|
5154
5547
|
align-items: center !important;
|
|
5155
5548
|
gap: 6px !important;
|
|
5156
5549
|
max-width: none !important;
|
|
@@ -5185,12 +5578,12 @@ exports.COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- */
|
|
|
5185
5578
|
sits closest to the input it will be sent from. The rail only wraps
|
|
5186
5579
|
while one of the two full-width entries exists, so a session with
|
|
5187
5580
|
neither keeps the original single scrolling chip line. */
|
|
5188
|
-
[data-slot="conversation.input.dock"]:has(> [data-queue-dock], > [data-mobile-nav="attach-chips"]) {
|
|
5581
|
+
[data-slot="conversation.input.dock"]:has(> [data-queue-dock], > [data-mobile-nav="attach-chips"], > [data-testid="todo-panel"], > [data-goal-bar]) {
|
|
5189
5582
|
flex-wrap: wrap !important;
|
|
5190
5583
|
}
|
|
5191
5584
|
[data-slot="conversation.input.dock"] > [data-queue-dock] {
|
|
5192
5585
|
flex: 1 0 100% !important;
|
|
5193
|
-
order:
|
|
5586
|
+
order: 3 !important;
|
|
5194
5587
|
max-width: none !important;
|
|
5195
5588
|
min-height: 0 !important;
|
|
5196
5589
|
max-height: none !important;
|
|
@@ -5201,6 +5594,24 @@ exports.COMPOSER_CSS = `/* ---------- phone composer (< 768px) ---------- */
|
|
|
5201
5594
|
font-size: 13px !important;
|
|
5202
5595
|
line-height: normal !important;
|
|
5203
5596
|
}
|
|
5597
|
+
/* dsh-client-ui-goal's status strip (its own \`data-goal-bar\` marker; it
|
|
5598
|
+
registers into this dock at order 10). Fourth thing here that is not a
|
|
5599
|
+
pill: a row carrying the objective text plus pause / edit / delete
|
|
5600
|
+
buttons, so the 26px cage clipped its buttons off the right edge and the
|
|
5601
|
+
nowrap line pushed what was left off screen (reported 2026-09-06). Same
|
|
5602
|
+
treatment the queue gets — a line of its own, official geometry back. */
|
|
5603
|
+
[data-slot="conversation.input.dock"] > [data-goal-bar] {
|
|
5604
|
+
flex: 1 0 100% !important;
|
|
5605
|
+
order: 2 !important;
|
|
5606
|
+
max-width: none !important;
|
|
5607
|
+
min-height: 0 !important;
|
|
5608
|
+
max-height: none !important;
|
|
5609
|
+
border-radius: 12px !important;
|
|
5610
|
+
overflow: visible !important;
|
|
5611
|
+
font-size: 13px !important;
|
|
5612
|
+
line-height: normal !important;
|
|
5613
|
+
}
|
|
5614
|
+
|
|
5204
5615
|
/* The panel is the dock's only child; upstream rounds its top corners only
|
|
5205
5616
|
and drops the bottom border, because there it is half-hidden behind the
|
|
5206
5617
|
card. On its own line it is a free-standing strip, so close it up. */
|
|
@@ -5466,6 +5877,27 @@ exports.INFO_CSS = `/* ---------- session-info sheet (< 768px) ---------- */
|
|
|
5466
5877
|
display: block !important;
|
|
5467
5878
|
}
|
|
5468
5879
|
|
|
5880
|
+
/* The sheet renders inside the header, and \`[data-phase] header\` is a
|
|
5881
|
+
stacking context of its own (position: relative + z-index: 2, set in
|
|
5882
|
+
header.css.ts so the header's fade strip paints over the scroller). A
|
|
5883
|
+
child's z-index is scoped to that context, so the z:70 below competes not
|
|
5884
|
+
with the page but with the header's own 2 — and the host's composer seat
|
|
5885
|
+
(.wSkVaW_composerSeat, z:7, from dsh-client-ui-conversation) outranks
|
|
5886
|
+
it. Measured live on DSH 0.1.2, 2026-09-04: with the sheet open, the
|
|
5887
|
+
topmost element over the composer band was the composer's own trigger
|
|
5888
|
+
button, i.e. the input row painted THROUGH the card.
|
|
5889
|
+
Promote the header itself for exactly as long as the sheet is mounted —
|
|
5890
|
+
MobileSessionInfo returns null when closed (\`if (!open) return null\`),
|
|
5891
|
+
so the header drops back to 2 on close (verified: 70 while open, 2
|
|
5892
|
+
after). Descendant :has(), never \`>\`: the utilities slot wrapper is
|
|
5893
|
+
\`display: contents\` and the host owns the depth (AGENTS.md — do not
|
|
5894
|
+
encode host DOM depth in a selector).
|
|
5895
|
+
Not raising the seat's own z-index instead: it belongs to the host and
|
|
5896
|
+
is shared with the scroll-to-bottom button and the composer sheets. */
|
|
5897
|
+
[data-phase] header:has([data-mobile-nav="info-layer"]) {
|
|
5898
|
+
z-index: 70;
|
|
5899
|
+
}
|
|
5900
|
+
|
|
5469
5901
|
[data-mobile-nav="info-layer"] {
|
|
5470
5902
|
position: fixed;
|
|
5471
5903
|
inset: 0;
|
|
@@ -7677,14 +8109,35 @@ function installHeaderStatusDot(ctx) {
|
|
|
7677
8109
|
return;
|
|
7678
8110
|
const { current, byId } = ctx.sessions.list.getSnapshot();
|
|
7679
8111
|
const row = current === undefined ? undefined : byId[current];
|
|
7680
|
-
|
|
8112
|
+
// 0.1.2 的待处理交互来源,每次现查:本插件的 inject 不含 uiSession(加了
|
|
8113
|
+
// 0.1.1 就不激活),apply 此刻服务未必注册好;每次算 dot 时现查,注册顺序
|
|
8114
|
+
// 就不再是问题。0.1.1 上没有这个服务,这里恒 undefined,黄点只由行字段决定。
|
|
8115
|
+
const uiSession = ctx.get('uiSession');
|
|
8116
|
+
const pending = current === undefined ? false
|
|
8117
|
+
: uiSession?.pendingInteractions.getSnapshot().has(current) === true;
|
|
8118
|
+
const state = row === undefined ? undefined : (0, session_dot_ts_1.dotState)(row, pending);
|
|
7681
8119
|
if (state === undefined)
|
|
7682
8120
|
frame.removeAttribute('data-mobile-nav-dot');
|
|
7683
8121
|
else
|
|
7684
8122
|
frame.setAttribute('data-mobile-nav-dot', state);
|
|
7685
8123
|
};
|
|
7686
8124
|
apply();
|
|
7687
|
-
|
|
8125
|
+
// Two sources can push the dot now (session list + the 0.1.2 pending
|
|
8126
|
+
// map); probe uiSession once more right here and subscribe to whichever
|
|
8127
|
+
// exist. 0.1.1 only ever has the first. Degradation note: if uiSession
|
|
8128
|
+
// has not registered by this moment (this plugin ran before it), only
|
|
8129
|
+
// the list subscription is attached — the dot stays correct because
|
|
8130
|
+
// every later list change re-runs apply(), whose fresh probe then sees
|
|
8131
|
+
// the service; a pending-only change before any list change would be
|
|
8132
|
+
// missed until then.
|
|
8133
|
+
const unsubscribes = [ctx.sessions.list.subscribe(apply)];
|
|
8134
|
+
const pendingSource = ctx.get('uiSession')?.pendingInteractions;
|
|
8135
|
+
if (pendingSource !== undefined)
|
|
8136
|
+
unsubscribes.push(pendingSource.subscribe(apply));
|
|
8137
|
+
return () => {
|
|
8138
|
+
for (const off of unsubscribes)
|
|
8139
|
+
off();
|
|
8140
|
+
};
|
|
7688
8141
|
}, 'dsh-mobile-nav: header status dot');
|
|
7689
8142
|
}
|
|
7690
8143
|
};
|
|
@@ -8049,6 +8502,166 @@ function installModalBack(ctx) {
|
|
|
8049
8502
|
}, 'dsh-zen-remote: modal back gesture');
|
|
8050
8503
|
}
|
|
8051
8504
|
};
|
|
8505
|
+
__modules["effects/model-sheet-extras.js"] = function (require, module, exports) {
|
|
8506
|
+
"use strict";
|
|
8507
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8508
|
+
exports.installModelSheetExtras = installModelSheetExtras;
|
|
8509
|
+
/** Phone breakpoint — same query every phone-only effect in this plugin uses. */
|
|
8510
|
+
const PHONE_QUERY = '(max-width: 767px)';
|
|
8511
|
+
/** The model pill itself. Absent in a subagent session — see `sync`. */
|
|
8512
|
+
const MODEL_SEAT_SELECTOR = '[data-slot="conversation.input.model"]';
|
|
8513
|
+
/** The model pill's own popup, which section 4 of composer.css.ts turns into a bottom sheet. */
|
|
8514
|
+
const MENU_SELECTOR = '[data-slot="conversation.input.model"] [class$="_menu"]';
|
|
8515
|
+
/**
|
|
8516
|
+
* The third-party composer controls this effect relocates. Both register into
|
|
8517
|
+
* `conversation.input.right`; moving the slot container rather than its
|
|
8518
|
+
* children keeps them together and leaves exactly one node to put back.
|
|
8519
|
+
*/
|
|
8520
|
+
const EXTRAS_SELECTOR = '[data-slot="conversation.input.right"]';
|
|
8521
|
+
/**
|
|
8522
|
+
* dsh-vision-router's own mount marker. Used here only to answer "is there
|
|
8523
|
+
* anything in this slot BESIDES the vision toggle" — see `worthMoving`.
|
|
8524
|
+
*/
|
|
8525
|
+
const VISION_SELECTOR = '[data-vision-router-mode-toggle]';
|
|
8526
|
+
/**
|
|
8527
|
+
* Set on the container while this effect manages it, in the row and in the
|
|
8528
|
+
* sheet alike. It is the styling hook for both halves in composer.css.ts, and
|
|
8529
|
+
* it is deliberately what the row's `display: none` keys off: without the
|
|
8530
|
+
* marker the controls stay exactly where the host put them, so a version of
|
|
8531
|
+
* this file that declines to act — or never runs at all — cannot leave them
|
|
8532
|
+
* hidden in the row and absent from the sheet, i.e. unreachable.
|
|
8533
|
+
*/
|
|
8534
|
+
const MANAGED = 'data-zen-sheet-extras';
|
|
8535
|
+
/**
|
|
8536
|
+
* Is the slot holding anything that actually costs the row width?
|
|
8537
|
+
*
|
|
8538
|
+
* The row is one nowrap flex line whose only elastic item is the model pill
|
|
8539
|
+
* (composer.css.ts section 1), so entries here are paid for out of the model
|
|
8540
|
+
* name. But that is only a problem once something WIDE lands in the slot —
|
|
8541
|
+
* today dsh-plugin-subscriptions' speed chip, which renders "速度 · 标准" and
|
|
8542
|
+
* eats ~70px. dsh-vision-router's toggle alone is a 28px icon that the row
|
|
8543
|
+
* accommodates fine, and moving it on its own would cost a tap (it becomes
|
|
8544
|
+
* two: open the sheet, then toggle) to buy width nobody needed.
|
|
8545
|
+
*
|
|
8546
|
+
* So: relocate only when the slot holds an entry that is not the vision
|
|
8547
|
+
* toggle. With no such entry — subscriptions not installed, or installed but
|
|
8548
|
+
* not contributing on this model — nothing moves and the row keeps its one
|
|
8549
|
+
* icon, which is the behaviour asked for (user review, 2026-09-06).
|
|
8550
|
+
*
|
|
8551
|
+
* Why this rather than sniffing for subscriptions by name: the slot system
|
|
8552
|
+
* adds no per-registration marker (measured — entries render straight into the
|
|
8553
|
+
* container) and subscriptions marks nothing of its own, so naming it would
|
|
8554
|
+
* mean adding a marker to our fork of that plugin and anchoring on it. That
|
|
8555
|
+
* marker dies the day the fork is dropped, and the feature would go quiet
|
|
8556
|
+
* with nothing to catch it. The width question is the real question anyway,
|
|
8557
|
+
* and this asks it directly.
|
|
8558
|
+
*/
|
|
8559
|
+
function worthMoving(extras) {
|
|
8560
|
+
return [...extras.children].some(child => !child.matches(VISION_SELECTOR));
|
|
8561
|
+
}
|
|
8562
|
+
/**
|
|
8563
|
+
* Phone: park the composer row's third-party controls inside the model sheet.
|
|
8564
|
+
*
|
|
8565
|
+
* With the speed chip and the vision toggle both present on a GPT model the
|
|
8566
|
+
* row runs out of width. Both are model-scoped settings, so the model sheet —
|
|
8567
|
+
* which already holds 模型 and 推理等级 — is where they belong (user request,
|
|
8568
|
+
* 2026-09-06).
|
|
8569
|
+
*
|
|
8570
|
+
* Why move the real controls instead of drawing our own rows that drive them:
|
|
8571
|
+
* the same reason native-trigger-overlay.ts moves the real trigger. A stand-in
|
|
8572
|
+
* has to script the original, which is a road this plugin does not go down —
|
|
8573
|
+
* beyond the trusted-input wall that effect documents, our own rows would have
|
|
8574
|
+
* to mirror every piece of state (speed tier, disabled, aria-pressed, whether
|
|
8575
|
+
* a vision twin exists for the current model) and would rot on the next
|
|
8576
|
+
* upstream restyle. Moving the node keeps one source of truth: their React
|
|
8577
|
+
* roots go on owning and updating these elements, we only change where they
|
|
8578
|
+
* hang.
|
|
8579
|
+
*
|
|
8580
|
+
* Measured 2026-09-06 (DSH 0.1.2, 390px) before writing this — the two things
|
|
8581
|
+
* that would have killed the approach both hold: a relocated control survives
|
|
8582
|
+
* host re-renders (typing into the composer re-renders the row and does not
|
|
8583
|
+
* yank it back) and stays hit-testable at its new position.
|
|
8584
|
+
*
|
|
8585
|
+
* Restoring on close is not cosmetic. The sheet unmounts when it closes and
|
|
8586
|
+
* anything still inside goes with it, so the controls would vanish from the
|
|
8587
|
+
* page while their React roots kept updating detached nodes. The observer
|
|
8588
|
+
* therefore watches the menu leaving too, and the parked node is held as a
|
|
8589
|
+
* reference rather than looked up — once the menu is detached a document
|
|
8590
|
+
* query can no longer find it.
|
|
8591
|
+
*/
|
|
8592
|
+
function installModelSheetExtras(ctx) {
|
|
8593
|
+
ctx.effect(() => {
|
|
8594
|
+
const phone = window.matchMedia(PHONE_QUERY);
|
|
8595
|
+
/** The container while parked in the sheet, plus where to put it back. */
|
|
8596
|
+
let parked = null;
|
|
8597
|
+
const park = (node, menu) => {
|
|
8598
|
+
if (parked !== null || node.parentElement === null)
|
|
8599
|
+
return;
|
|
8600
|
+
parked = { node, parent: node.parentElement, next: node.nextSibling };
|
|
8601
|
+
menu.appendChild(node);
|
|
8602
|
+
};
|
|
8603
|
+
const unpark = () => {
|
|
8604
|
+
if (parked === null)
|
|
8605
|
+
return;
|
|
8606
|
+
const { node, parent, next } = parked;
|
|
8607
|
+
parked = null;
|
|
8608
|
+
// The menu may already be detached, having taken the container with it.
|
|
8609
|
+
// The node itself is still live either way; re-home it only if its old
|
|
8610
|
+
// parent is still on the page (when the whole composer went away, the
|
|
8611
|
+
// owning React roots rebuild the slot on the next mount).
|
|
8612
|
+
if (parent.isConnected)
|
|
8613
|
+
parent.insertBefore(node, next);
|
|
8614
|
+
};
|
|
8615
|
+
/** Hand the container back to the host, marker and all. */
|
|
8616
|
+
const release = (node) => {
|
|
8617
|
+
unpark();
|
|
8618
|
+
node?.removeAttribute(MANAGED);
|
|
8619
|
+
};
|
|
8620
|
+
const sync = () => {
|
|
8621
|
+
const extras = document.querySelector(EXTRAS_SELECTOR);
|
|
8622
|
+
if (extras === null) {
|
|
8623
|
+
unpark();
|
|
8624
|
+
return;
|
|
8625
|
+
}
|
|
8626
|
+
// No model pill, no sheet to park into — a subagent session has none.
|
|
8627
|
+
// Claiming the slot there would hide the controls from the row with
|
|
8628
|
+
// nowhere to show them instead, which is the unreachable state the
|
|
8629
|
+
// marker is meant to prevent.
|
|
8630
|
+
const seat = document.querySelector(MODEL_SEAT_SELECTOR);
|
|
8631
|
+
if (!phone.matches || seat === null || !worthMoving(extras)) {
|
|
8632
|
+
release(extras);
|
|
8633
|
+
return;
|
|
8634
|
+
}
|
|
8635
|
+
extras.setAttribute(MANAGED, '');
|
|
8636
|
+
const menu = document.querySelector(MENU_SELECTOR);
|
|
8637
|
+
if (menu === null)
|
|
8638
|
+
unpark();
|
|
8639
|
+
else
|
|
8640
|
+
park(extras, menu);
|
|
8641
|
+
};
|
|
8642
|
+
// Synchronous in the observer callback, never behind rAF: the callback is
|
|
8643
|
+
// a microtask and runs before this frame paints, so the controls are in
|
|
8644
|
+
// the sheet the first time it is drawn. A rAF would land a frame late and
|
|
8645
|
+
// show one frame of the sheet without them (AGENTS.md's "晚一帧" rule).
|
|
8646
|
+
// Body-level childList is the same shape modal-back.ts uses; aria-expanded
|
|
8647
|
+
// is watched too so a close that reuses the node still re-syncs.
|
|
8648
|
+
const observer = new MutationObserver(sync);
|
|
8649
|
+
observer.observe(document.body, {
|
|
8650
|
+
childList: true,
|
|
8651
|
+
subtree: true,
|
|
8652
|
+
attributes: true,
|
|
8653
|
+
attributeFilter: ['aria-expanded'],
|
|
8654
|
+
});
|
|
8655
|
+
phone.addEventListener('change', sync);
|
|
8656
|
+
sync();
|
|
8657
|
+
return () => {
|
|
8658
|
+
observer.disconnect();
|
|
8659
|
+
phone.removeEventListener('change', sync);
|
|
8660
|
+
release(document.querySelector(EXTRAS_SELECTOR));
|
|
8661
|
+
};
|
|
8662
|
+
}, 'dsh-mobile-nav: model sheet extras');
|
|
8663
|
+
}
|
|
8664
|
+
};
|
|
8052
8665
|
__modules["effects/native-trigger-overlay.js"] = function (require, module, exports) {
|
|
8053
8666
|
"use strict";
|
|
8054
8667
|
/**
|
|
@@ -8727,6 +9340,7 @@ const header_status_ts_1 = require("./effects/header-status.js");
|
|
|
8727
9340
|
const gestures_ts_1 = require("./effects/gestures.js");
|
|
8728
9341
|
const turn_fold_ts_1 = require("./effects/turn-fold.js");
|
|
8729
9342
|
const modal_back_ts_1 = require("./effects/modal-back.js");
|
|
9343
|
+
const model_sheet_extras_ts_1 = require("./effects/model-sheet-extras.js");
|
|
8730
9344
|
const native_trigger_overlay_ts_1 = require("./effects/native-trigger-overlay.js");
|
|
8731
9345
|
const welcome_notice_ts_1 = require("./effects/welcome-notice.js");
|
|
8732
9346
|
const keyboard_guard_ts_1 = require("./effects/keyboard-guard.js");
|
|
@@ -8779,6 +9393,9 @@ function apply(ctx) {
|
|
|
8779
9393
|
(0, turn_fold_ts_1.installTurnFold)(ctx);
|
|
8780
9394
|
(0, modal_back_ts_1.installModalBack)(ctx);
|
|
8781
9395
|
(0, native_trigger_overlay_ts_1.installNativeTriggerOverlay)(ctx);
|
|
9396
|
+
// Third-party composer entries (speed chip, vision toggle) move into the
|
|
9397
|
+
// model sheet — the row has no width to spare and both are model settings.
|
|
9398
|
+
(0, model_sheet_extras_ts_1.installModelSheetExtras)(ctx);
|
|
8782
9399
|
// "内测声明" first-run notice: keep it visible (CSS-hiding it leaked the
|
|
8783
9400
|
// dialog's #root inert lock — see effects/welcome-notice.ts) and offer a
|
|
8784
9401
|
// per-browser "不再弹出" opt-out instead.
|
|
@@ -8845,7 +9462,13 @@ function apply(ctx) {
|
|
|
8845
9462
|
forkSession: (id) => ctx.sessions.fork({ sessionId: id }),
|
|
8846
9463
|
openSession: (id) => ctx.sessions.open(id),
|
|
8847
9464
|
renameSession: (id, title) => ctx.sessions.binding(id)?.session.rename(title),
|
|
8848
|
-
|
|
9465
|
+
// 0.1.2 的界面级归档在 uiWorkspace(顺带清当前选中);懒查——回调是用户
|
|
9466
|
+
// 点击才跑,那时服务必已注册。本插件的 inject 不包含 uiWorkspace(0.1.1
|
|
9467
|
+
// 没有它),所以启动时不能查一次就用:可能早于它注册。
|
|
9468
|
+
archiveSession: (id) => {
|
|
9469
|
+
const ui = ctx.get('uiWorkspace');
|
|
9470
|
+
return ui === undefined ? ctx.workspaces.archiveSession(id) : ui.archiveSession(id);
|
|
9471
|
+
},
|
|
8849
9472
|
downloadSessionLog: (id) => ctx.sessionLogDownload.download(id),
|
|
8850
9473
|
}),
|
|
8851
9474
|
}, MobileSessionInfo_tsx_1.MobileSessionInfo));
|
|
@@ -8893,11 +9516,26 @@ function apply(ctx) {
|
|
|
8893
9516
|
store: nav,
|
|
8894
9517
|
inject: () => ({
|
|
8895
9518
|
openSession: (id) => ctx.sessions.open(id),
|
|
8896
|
-
|
|
9519
|
+
// uiWorkspace 懒查:回调是用户点击才跑,服务那时必已注册(inject 不含
|
|
9520
|
+
// uiWorkspace,启动时可能先于它注册,不能启动查一次就用)。0.1.1 上恒
|
|
9521
|
+
// undefined,退回 ctx.workspaces.startSession——0.1.1 专用分支,0.1.2 的
|
|
9522
|
+
// 类型里该方法已不存在,故对类型做 LegacyWorkspacesLike cast,运行时走不到。
|
|
9523
|
+
startSession: (workspaceId) => {
|
|
9524
|
+
const ui = ctx.get('uiWorkspace');
|
|
9525
|
+
return ui === undefined
|
|
9526
|
+
? ctx.workspaces.startSession(workspaceId)
|
|
9527
|
+
: ui.startSession(workspaceId);
|
|
9528
|
+
},
|
|
8897
9529
|
// S5 session-log chip — the same service call MobileDrawerFooter uses.
|
|
8898
9530
|
downloadSessionLog: (id) => ctx.sessionLogDownload.download(id),
|
|
8899
|
-
// Row swipe action — the same binding MobileSessionInfo archives with
|
|
8900
|
-
|
|
9531
|
+
// Row swipe action — the same binding MobileSessionInfo archives with;
|
|
9532
|
+
// on 0.1.2 the uiWorkspace variant also clears the current selection
|
|
9533
|
+
// when the archived session is the one selected. Same lazy lookup as
|
|
9534
|
+
// the session-info sheet's archive binding above.
|
|
9535
|
+
archiveSession: (id) => {
|
|
9536
|
+
const ui = ctx.get('uiWorkspace');
|
|
9537
|
+
return ui === undefined ? ctx.workspaces.archiveSession(id) : ui.archiveSession(id);
|
|
9538
|
+
},
|
|
8901
9539
|
}),
|
|
8902
9540
|
}, MobileHome_tsx_1.MobileHome));
|
|
8903
9541
|
// Session log download, relocated from the session header to the drawer
|
|
@@ -8917,6 +9555,9 @@ function apply(ctx) {
|
|
|
8917
9555
|
order: 5,
|
|
8918
9556
|
locale: locales_ts_1.NS,
|
|
8919
9557
|
inject: () => ({
|
|
9558
|
+
// The footer slot contract hands the id over as a loose string;
|
|
9559
|
+
// download() wants the branded SessionId — pure type-layer narrowing
|
|
9560
|
+
// (runtime value is a session id either way, same as MobileHome.tsx).
|
|
8920
9561
|
downloadSessionLog: (sessionId) => ctx.sessionLogDownload.download(sessionId),
|
|
8921
9562
|
toggleSidebar: () => ctx.layout.toggleSidebar(),
|
|
8922
9563
|
}),
|