dsh-mobile 0.3.14 → 0.3.15

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.
@@ -22,6 +22,12 @@ window.__ModuleLoader__.load({
22
22
  });
23
23
  //#endregion
24
24
  //#region src/mobile-layout.ts
25
+ /** Whether an optional service value supports the public right-Sidebar controls. */
26
+ function isSidebarRightControl(value) {
27
+ if (typeof value !== "object" || value === null) return false;
28
+ const candidate = value;
29
+ return typeof candidate.isExpanded === "function" && typeof candidate.toggleExpanded === "function";
30
+ }
25
31
  /**
26
32
  * Viewport width at which the dedicated layout treats the sidebar as a
27
33
  * persistent desktop panel instead of an overlay drawer. Narrow screens
@@ -31,6 +37,18 @@ window.__ModuleLoader__.load({
31
37
  function isWideViewportLayout(viewportWidth) {
32
38
  return viewportWidth >= 900;
33
39
  }
40
+ /** Whether the narrow layout needs the modal scrim for either side panel. */
41
+ function isMobileScrimOpen(sidebarOpen, detailsOpen, wideViewport) {
42
+ return !wideViewport && (sidebarOpen || detailsOpen);
43
+ }
44
+ /**
45
+ * Close the details drawer from its scrim, collapsing the DSH 0.1.5 right
46
+ * Sidebar first when that optional service owns the visible content.
47
+ */
48
+ function closeDetailsFromScrim(sidebarRight, closeDetails) {
49
+ if (isSidebarRightControl(sidebarRight) && sidebarRight.isExpanded()) sidebarRight.toggleExpanded();
50
+ closeDetails();
51
+ }
34
52
  /** Reads the live viewport; unknown environments (SSR, tests) stay narrow. */
35
53
  function viewportIsWide() {
36
54
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
@@ -43,9 +61,12 @@ window.__ModuleLoader__.load({
43
61
  var MobileLayoutController = class {
44
62
  snapshot = Object.freeze({
45
63
  sidebarOpen: viewportIsWide(),
46
- detailsOpen: false
64
+ detailsOpen: false,
65
+ panelInfo: Object.freeze({ activePanelId: null })
47
66
  });
48
67
  listeners = /* @__PURE__ */ new Set();
68
+ hasMainPanel;
69
+ navigation = new AbortController();
49
70
  subscribe = (listener) => {
50
71
  this.listeners.add(listener);
51
72
  return () => {
@@ -65,12 +86,64 @@ window.__ModuleLoader__.load({
65
86
  closeSidebar() {
66
87
  this.update({ sidebarOpen: false });
67
88
  }
89
+ /**
90
+ * Invalidate the previous navigation and hand back a fresh signal. The
91
+ * workspace controller wraps this around "open a workspace" and "fork a
92
+ * session" (dsh-client-ui-workspace) — the path behind the sidebar's
93
+ * new-session button. Without it startSession throws inside a
94
+ * `.catch(reason => console.warn('new session failed:', reason))`, so the
95
+ * button silently did nothing.
96
+ */
97
+ beginNavigation() {
98
+ this.navigation.abort();
99
+ this.navigation = new AbortController();
100
+ return this.navigation.signal;
101
+ }
102
+ /** Invalidate pending navigations when this layout unloads. */
103
+ dispose() {
104
+ this.navigation.abort();
105
+ }
106
+ /** Bind panel validation to the live slot registry owned by this application. */
107
+ bindMainPanelRegistry(hasMainPanel) {
108
+ this.hasMainPanel = hasMainPanel;
109
+ return () => {
110
+ if (this.hasMainPanel === hasMainPanel) this.hasMainPanel = void 0;
111
+ };
112
+ }
113
+ /**
114
+ * Show the right panel. The official controller tracks a persistent column
115
+ * here; this layout always renders the panel as the details overlay, so both
116
+ * the track and fullscreen requests open the same drawer.
117
+ */
118
+ openRightbar(_track, _fullscreen) {
119
+ this.openDetails();
120
+ }
121
+ /** Hide the right panel. */
122
+ closeRightbar() {
123
+ this.closeDetails();
124
+ }
125
+ /**
126
+ * Select the main panel rendered in the center column; null means the
127
+ * conversation. Mirrors the official LayoutController so the `panelInfo`
128
+ * root hook stays truthful.
129
+ */
130
+ selectPanel(panelId) {
131
+ if (panelId !== null && this.hasMainPanel?.(panelId) !== true) throw new Error(`layout.selectPanel: main panel "${panelId}" is not registered`);
132
+ this.navigation.abort();
133
+ if (this.snapshot.panelInfo.activePanelId === panelId) return;
134
+ this.update({ panelInfo: Object.freeze({ activePanelId: panelId }) });
135
+ }
136
+ /** Drop a selected panel once no main-slot entry declares it. */
137
+ retainMainPanels(panelIds) {
138
+ const active = this.snapshot.panelInfo.activePanelId;
139
+ if (active !== null && !panelIds.includes(active)) this.update({ panelInfo: Object.freeze({ activePanelId: null }) });
140
+ }
68
141
  update(next) {
69
142
  const snapshot = Object.freeze({
70
143
  ...this.snapshot,
71
144
  ...next
72
145
  });
73
- if (snapshot.sidebarOpen === this.snapshot.sidebarOpen && snapshot.detailsOpen === this.snapshot.detailsOpen) return;
146
+ if (snapshot.sidebarOpen === this.snapshot.sidebarOpen && snapshot.detailsOpen === this.snapshot.detailsOpen && snapshot.panelInfo === this.snapshot.panelInfo) return;
74
147
  this.snapshot = snapshot;
75
148
  for (const listener of this.listeners) listener();
76
149
  }
@@ -160,6 +233,7 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
160
233
  const [documentLanguage, setDocumentLanguage] = (0, react.useState)(document.documentElement.lang);
161
234
  const language = resolveMobileLayoutLanguage(documentLanguage, navigator.languages.length > 0 ? navigator.languages : [navigator.language]);
162
235
  const messages = MOBILE_LAYOUT_MESSAGES[language];
236
+ const scrimOpen = isMobileScrimOpen(state.sidebarOpen, state.detailsOpen, wideViewport);
163
237
  const activeSessionId = props.useSessions((session) => {
164
238
  const current = session.current;
165
239
  return current !== void 0 && session.byId[current]?.blank === false ? current : void 0;
@@ -257,14 +331,19 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
257
331
  }, (0, react.createElement)("main", {
258
332
  className: "dshm-main",
259
333
  "data-dsh-mobile-session": activeSessionId
260
- }, props.renderSlot("conversation", {})), (0, react.createElement)("button", {
334
+ }, props.renderSlot("main", {}, {
335
+ entryKey: state.panelInfo.activePanelId ?? "conversation",
336
+ fallback: props.renderSlot("conversation", {})
337
+ })), (0, react.createElement)("button", {
261
338
  "aria-label": messages.closePanels,
262
339
  className: "dshm-scrim",
263
- "data-open": state.detailsOpen || state.sidebarOpen && !wideViewport,
340
+ "data-open": scrimOpen,
341
+ "aria-hidden": !scrimOpen,
264
342
  onClick: () => {
265
- state.detailsOpen ? props.controller.closeDetails() : props.controller.closeSidebar();
343
+ if (!scrimOpen) return;
344
+ state.detailsOpen ? props.requestDetailsClose() : props.controller.closeSidebar();
266
345
  },
267
- tabIndex: state.sidebarOpen || state.detailsOpen ? 0 : -1,
346
+ tabIndex: scrimOpen ? 0 : -1,
268
347
  type: "button"
269
348
  }), (0, react.createElement)("aside", {
270
349
  "aria-label": messages.workspaceNavigation,
@@ -279,11 +358,11 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
279
358
  className: "dshm-details",
280
359
  "data-open": state.detailsOpen,
281
360
  ...state.detailsOpen ? {} : { inert: "" }
282
- }, hasSession ? [props.renderSlot("details", {}), props.renderSlot("rightbar", {
361
+ }, [hasSession ? props.renderSlot("details", {}) : void 0, props.renderSlot("rightbar", {
283
362
  width: Math.min(window.innerWidth * .94, 460),
284
363
  viewportWidth: window.innerWidth,
285
- canShow: window.innerWidth >= 720
286
- })] : void 0), (0, react.createElement)("div", {
364
+ canShow: true
365
+ })]), (0, react.createElement)("div", {
287
366
  className: "dshm-overlay",
288
367
  "data-shell-overlay": true
289
368
  }, props.renderSlot("shell.overlay", {})));
@@ -298,7 +377,12 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
298
377
  style.dataset.plugin = "dsh-mobile-layout";
299
378
  style.textContent = MOBILE_LAYOUT_STYLES;
300
379
  document.head.append(style);
380
+ const disposeMainPanelRegistry = controller.bindMainPanelRegistry((id) => ctx.slots.entries("main").some((entry) => entry.options.key === id));
301
381
  const disposeService = ctx.reflect.provide("layout", controller);
382
+ const disposePanelInfo = typeof ctx.slots.provideRoot === "function" ? ctx.slots.provideRoot({ hooks: { panelInfo: {
383
+ getSnapshot: () => controller.getSnapshot().panelInfo,
384
+ subscribe: (listener) => controller.subscribe(listener)
385
+ } } }) : () => {};
302
386
  const disposeRoot = ctx.slots.register({
303
387
  name: "root",
304
388
  children: {
@@ -310,9 +394,13 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
310
394
  kind: "single",
311
395
  scope: "session-maybe"
312
396
  },
397
+ main: {
398
+ kind: "keyed",
399
+ scope: "root"
400
+ },
313
401
  rightbar: {
314
402
  kind: "single",
315
- scope: "session"
403
+ scope: "root"
316
404
  },
317
405
  details: {
318
406
  kind: "single",
@@ -325,11 +413,25 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
325
413
  }
326
414
  }, (props) => (0, react.createElement)(MobileAppFrame, {
327
415
  ...props,
328
- controller
416
+ controller,
417
+ requestDetailsClose: () => {
418
+ closeDetailsFromScrim(ctx.get("sidebarRight"), () => {
419
+ controller.closeDetails();
420
+ });
421
+ }
329
422
  }));
423
+ const retainMainPanels = () => {
424
+ controller.retainMainPanels(ctx.slots.entries("main").flatMap((entry) => entry.options.key === void 0 ? [] : [entry.options.key]));
425
+ };
426
+ const disposePanels = ctx.slots.subscribe("main", retainMainPanels);
427
+ retainMainPanels();
330
428
  return () => {
429
+ controller.dispose();
430
+ disposePanels();
331
431
  disposeRoot();
432
+ disposePanelInfo();
332
433
  disposeService();
434
+ disposeMainPanelRegistry();
333
435
  style.remove();
334
436
  };
335
437
  }, "dsh-mobile: dedicated root layout");
@@ -352,7 +454,10 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
352
454
  exports.MOBILE_LAYOUT_STYLES = MOBILE_LAYOUT_STYLES;
353
455
  exports.WIDE_LAYOUT_MIN_WIDTH_PX = WIDE_LAYOUT_MIN_WIDTH_PX;
354
456
  exports.apply = apply;
457
+ exports.closeDetailsFromScrim = closeDetailsFromScrim;
355
458
  exports.inject = inject;
459
+ exports.isMobileScrimOpen = isMobileScrimOpen;
460
+ exports.isSidebarRightControl = isSidebarRightControl;
356
461
  exports.isWideViewportLayout = isWideViewportLayout;
357
462
  exports.resolveMobileLayoutLanguage = resolveMobileLayoutLanguage;
358
463
  return module.exports;
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-layout.js","names":["useSyncExternalStore","useRef","useState","createElement"],"sources":["../src/mobile-layout-messages.ts","../src/mobile-layout.ts"],"sourcesContent":["export type MobileLayoutLanguage = 'it' | 'en' | 'zh'\n\nexport const MOBILE_LAYOUT_MESSAGES = Object.freeze({\n it: Object.freeze({ closePanels: 'Chiudi pannelli', workspaceNavigation: 'Navigazione area di lavoro e sessioni' }),\n en: Object.freeze({ closePanels: 'Close panels', workspaceNavigation: 'Workspace and session navigation' }),\n zh: Object.freeze({ closePanels: '关闭浮层', workspaceNavigation: '工作区与会话导航' }),\n})\n","import { createElement, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport type { ReactNode } from 'react'\nimport { MOBILE_LAYOUT_MESSAGES, type MobileLayoutLanguage } from './mobile-layout-messages.js'\n\nexport { MOBILE_LAYOUT_MESSAGES } from './mobile-layout-messages.js'\nexport type { MobileLayoutLanguage } from './mobile-layout-messages.js'\n\ninterface SessionState {\n readonly current?: string\n readonly byId: Readonly<Record<string, { readonly blank?: boolean; readonly title?: string } | undefined>>\n}\n\ninterface MobileRootProps {\n readonly renderSlot: (name: string, owner: Record<string, unknown>) => ReactNode\n readonly useSessions: <T>(selector: (state: SessionState) => T) => T\n}\n\ninterface MobileClientContext {\n readonly effect: (effect: () => void | (() => void), label?: string) => void\n readonly on: (event: string, listener: (value: ThemeSnapshot) => void) => () => void\n readonly reflect: { provide: (name: string, value: unknown) => () => void | Promise<void> }\n readonly slots: {\n register: (options: Record<string, unknown>, component: (props: MobileRootProps) => ReactNode) => () => void\n }\n readonly theme: { getTheme: () => ThemeSnapshot }\n}\n\ninterface ThemeSnapshot {\n readonly active: {\n readonly colorScheme: 'dark' | 'light'\n readonly tokens: Readonly<Record<string, string>>\n }\n}\n\ninterface LayoutSnapshot {\n readonly sidebarOpen: boolean\n readonly detailsOpen: boolean\n}\n\n/**\n * Viewport width at which the dedicated layout treats the sidebar as a\n * persistent desktop panel instead of an overlay drawer. Narrow screens\n * keep the overlay behavior byte-for-byte.\n */\nexport const WIDE_LAYOUT_MIN_WIDTH_PX = 900\n\nexport function isWideViewportLayout(viewportWidth: number): boolean {\n return viewportWidth >= WIDE_LAYOUT_MIN_WIDTH_PX\n}\n\n/** Reads the live viewport; unknown environments (SSR, tests) stay narrow. */\nfunction viewportIsWide(): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false\n return window.matchMedia(`(min-width: ${WIDE_LAYOUT_MIN_WIDTH_PX}px)`).matches\n}\n\n/** Resolve the supported language used by the dedicated mobile layout. */\nexport function resolveMobileLayoutLanguage(\n documentLanguage: string,\n browserLanguages: readonly string[],\n): MobileLayoutLanguage {\n return [documentLanguage, ...browserLanguages]\n .map(value => value.trim().toLowerCase().split(/[-_]/u)[0])\n .find((value): value is MobileLayoutLanguage => value === 'it' || value === 'en' || value === 'zh') ?? 'en'\n}\n\nclass MobileLayoutController {\n // Wide viewports start with the persistent sidebar open; applying the\n // layout again (reconnect, refocus) reuses the module singleton below,\n // so an explicit user collapse is never reset.\n private snapshot: LayoutSnapshot = Object.freeze({ sidebarOpen: viewportIsWide(), detailsOpen: false })\n private readonly listeners = new Set<() => void>()\n\n readonly subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n readonly getSnapshot = (): LayoutSnapshot => this.snapshot\n\n toggleSidebar(): void {\n this.update({ sidebarOpen: !this.snapshot.sidebarOpen })\n }\n\n openDetails(): void {\n this.update({ detailsOpen: true })\n }\n\n closeDetails(): void {\n this.update({ detailsOpen: false })\n }\n\n closeSidebar(): void {\n this.update({ sidebarOpen: false })\n }\n\n private update(next: Partial<LayoutSnapshot>): void {\n const snapshot = Object.freeze({ ...this.snapshot, ...next })\n if (snapshot.sidebarOpen === this.snapshot.sidebarOpen && snapshot.detailsOpen === this.snapshot.detailsOpen) return\n this.snapshot = snapshot\n for (const listener of this.listeners) listener()\n }\n}\n\nclass ThemePresenter {\n private appliedTokens: string[] = []\n private readonly meta = document.createElement('meta')\n\n constructor() {\n this.meta.name = 'theme-color'\n }\n\n apply(snapshot: ThemeSnapshot): void {\n const scheme = snapshot.active.colorScheme\n document.documentElement.style.colorScheme = scheme\n document.body.toggleAttribute('data-ds-dark-theme', scheme === 'dark')\n for (const name of this.appliedTokens) document.body.style.removeProperty(name)\n this.appliedTokens = []\n for (const [name, value] of Object.entries(snapshot.active.tokens)) {\n document.body.style.setProperty(name, value)\n this.appliedTokens.push(name)\n }\n this.meta.content = getComputedStyle(document.body).backgroundColor\n if (!this.meta.isConnected) document.head.append(this.meta)\n }\n\n dispose(): void {\n document.documentElement.style.removeProperty('color-scheme')\n document.body.removeAttribute('data-ds-dark-theme')\n for (const name of this.appliedTokens) document.body.style.removeProperty(name)\n this.meta.remove()\n }\n}\n\nexport const MOBILE_LAYOUT_STYLES = `\nhtml,body,#root{width:100%;height:100%;overflow:hidden}\n.dshm-shell{position:relative;display:grid;width:100%;height:100dvh;min-width:0;overflow:hidden;background:var(--dsw-alias-bg-base,#fff)}\n.dshm-main{grid-area:1/1;min-width:0;min-height:0;overflow:hidden}\n.dshm-main>*,.dshm-main>*>*{min-width:0}\n.dshm-drawer{position:fixed;z-index:70;inset:0 auto 0 0;box-sizing:border-box;width:56px;max-width:100%;padding-top:env(safe-area-inset-top);overflow:hidden;background:var(--dsw-alias-bg-layer-1,#f8fafc);box-shadow:none;will-change:width;transition:width 240ms cubic-bezier(.22,1,.36,1),box-shadow 240ms ease}\n.dshm-drawer[data-open=true]{width:min(88vw,340px);box-shadow:18px 0 46px rgb(15 23 42 / 18%)}\n.dshm-drawer[data-open=false]{pointer-events:auto;visibility:visible}\n.dshm-drawer>*{width:100%!important;height:100%!important;transform:translateX(-6px);opacity:.94;transition:transform 220ms cubic-bezier(.22,1,.36,1),opacity 160ms ease-out}\n.dshm-drawer[data-open=true]>*{transform:translateX(0);opacity:1}\n.dshm-drawer[data-open=false]>*{width:56px!important}\n.dshm-details{position:fixed;z-index:80;inset:0 0 0 auto;box-sizing:border-box;width:min(94vw,460px);max-width:100%;padding-top:env(safe-area-inset-top);overflow:hidden;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:-18px 0 46px rgb(15 23 42 / 18%);transform:translateX(104%);transition:transform 190ms cubic-bezier(.22,1,.36,1)}\n.dshm-details[data-open=true]{transform:translateX(0)}\n.dshm-details[data-open=false]{pointer-events:none;visibility:hidden;transition:transform 190ms cubic-bezier(.22,1,.36,1),visibility 0s linear 190ms}\n.dshm-scrim{position:fixed;z-index:65;inset:0;border:0;background:rgb(15 23 42 / 40%);opacity:0;pointer-events:none;transition:opacity 180ms ease-out}\n.dshm-scrim[data-open=true]{opacity:1;pointer-events:auto}\n.dshm-overlay{position:fixed;z-index:90;inset:0;pointer-events:none}.dshm-overlay>*{pointer-events:auto}\n.dshm-shell header{min-width:0;padding-left:52px}\n.dshm-shell textarea{font-size:16px}\n.dshm-shell table{display:block;max-width:100%;overflow-x:auto}\n.dshm-shell pre{max-width:100%;overflow-x:auto}\n.dshm-shell img,.dshm-shell video,.dshm-shell canvas,.dshm-shell svg{max-width:100%}\n.dshm-shell [data-disclosure-row]{min-width:0;max-width:100%}\n.dshm-shell [data-disclosure-row]>*{min-width:0;overflow-wrap:anywhere}\n.dshm-shell [data-context-fields]>*{min-width:0}\n.dshm-shell [class*=\"_body\"]{max-width:100%;overflow-wrap:anywhere}\n.dshm-shell [data-question-key],.dshm-shell [data-plan-review-key]{box-sizing:border-box;width:100%;height:auto!important;min-width:0;flex:none!important;align-self:flex-end;padding:6px max(10px,env(safe-area-inset-left)) max(10px,env(safe-area-inset-bottom)) max(10px,env(safe-area-inset-right))!important}\n.dshm-shell [data-question-key]>section,.dshm-shell [data-plan-review-key]>section{width:100%;height:auto!important;min-height:0!important;max-width:none!important;max-height:min(68dvh,520px)!important;border-radius:16px!important}\n.dshm-shell [data-question-scroll],.dshm-shell [data-plan-review-scroll]{flex:0 1 auto!important;min-height:0!important;max-height:min(42dvh,360px)!important;overscroll-behavior:contain;scroll-padding-bottom:12px}\n@media(max-width:420px){.dshm-shell [data-context-fields]>*{display:grid;grid-template-columns:1fr!important;gap:4px}.dshm-shell [class*=\"_ioSection\"]{grid-template-columns:1fr!important}}\n@media(max-width:600px){\n.dshm-shell [data-question-key]>section>header{display:flex!important;visibility:visible!important;flex:none!important;gap:8px!important;padding:12px 8px 4px 14px!important}\n.dshm-shell [data-question-key]>section>header h2{min-width:0;overflow-wrap:anywhere;font-size:16px!important;line-height:22px!important}\n.dshm-shell [data-question-key]>section>header button{min-width:40px;min-height:40px}\n.dshm-shell [data-question-key] [role=radio],.dshm-shell [data-question-key] [role=checkbox]{min-height:48px!important;touch-action:manipulation}\n.dshm-shell [data-question-key]>section>footer{display:grid!important;grid-template-columns:auto minmax(0,1fr);align-items:center!important;flex:none!important;gap:6px 8px!important;margin-top:4px!important;padding:6px 10px 10px!important}\n.dshm-shell [data-question-key]>section>footer>:last-child{grid-column:1/-1;display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr));width:100%;gap:8px!important}\n.dshm-shell [data-question-key]>section>footer>:last-child button{width:100%;min-height:44px}\n.dshm-shell [data-plan-review-key]>section>div:last-child{display:grid!important;grid-template-columns:1fr;gap:8px!important;padding:8px 12px 10px!important}\n.dshm-shell [data-plan-review-key]>section>div:last-child>div:last-child{display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr));width:100%;gap:8px!important}\n.dshm-shell [data-plan-review-key]>section>div:last-child>div:last-child>button:first-child{grid-column:1/-1}\n.dshm-shell [data-plan-review-key]>section>div:last-child button{width:100%;min-height:44px}\n}\n@media(min-width:900px){\n.dshm-shell{grid-template-columns:auto minmax(0,1fr)}\n.dshm-main{grid-area:1/2}\n.dshm-drawer{position:static;grid-area:1/1;box-shadow:none}\n.dshm-drawer[data-open=true]{width:340px;box-shadow:none}\n.dshm-drawer[data-open=false]{width:56px}\n}\n@media(prefers-reduced-motion:reduce){.dshm-drawer,.dshm-details,.dshm-scrim,.dshm-drawer>*{transition:none!important}}\n`\n\nfunction MobileAppFrame(props: MobileRootProps & { readonly controller: MobileLayoutController }): ReactNode {\n const state = useSyncExternalStore(props.controller.subscribe, props.controller.getSnapshot)\n const suppressKeyboardUntil = useRef(0)\n const [wideViewport, setWideViewport] = useState(viewportIsWide)\n const [documentLanguage, setDocumentLanguage] = useState(document.documentElement.lang)\n const browserLanguages = navigator.languages.length > 0 ? navigator.languages : [navigator.language]\n const language = resolveMobileLayoutLanguage(documentLanguage, browserLanguages)\n const messages = MOBILE_LAYOUT_MESSAGES[language]\n const activeSessionId = props.useSessions(session => {\n const current = session.current\n return current !== undefined && session.byId[current]?.blank === false ? current : undefined\n })\n const hasSession = activeSessionId !== undefined\n\n useEffect(() => {\n if (typeof window.matchMedia !== 'function') return\n const query = window.matchMedia(`(min-width: ${WIDE_LAYOUT_MIN_WIDTH_PX}px)`)\n const syncViewport = (): void => { setWideViewport(query.matches) }\n syncViewport()\n query.addEventListener('change', syncViewport)\n return () => { query.removeEventListener('change', syncViewport) }\n }, [])\n\n useEffect(() => {\n const observer = new MutationObserver(() => { setDocumentLanguage(document.documentElement.lang) })\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['lang'] })\n return () => { observer.disconnect() }\n }, [])\n\n useEffect(() => {\n if (!hasSession) props.controller.closeDetails()\n }, [hasSession, props.controller])\n\n // Mirror the stock layout: project the current session title into the\n // browser tab, restoring the product title when no session is selected.\n const sessionTitle = props.useSessions(session =>\n session.current === undefined ? undefined : session.byId[session.current]?.title,\n )\n useEffect(() => {\n const productTitle = document.title\n if (sessionTitle !== undefined) document.title = `${sessionTitle} — ${productTitle}`\n return () => { document.title = productTitle }\n }, [sessionTitle])\n\n useEffect(() => {\n const suppressAutofocus = (event: FocusEvent): void => {\n if (performance.now() >= suppressKeyboardUntil.current) return\n const target = event.target\n if (target instanceof HTMLElement && (target.matches('input,textarea') || target.isContentEditable)) target.blur()\n }\n const suppressBranchAutofocus = (event: MouseEvent): void => {\n if (!(event.target instanceof Element)) return\n const branch = event.target.closest('button[aria-label*=\"分支\"],button[aria-label*=\"Branch\"],button[aria-label*=\"branch\"],button[aria-label*=\"Ramo\"],button[aria-label*=\"ramo\"]')\n if (branch === null || branch.hasAttribute('disabled') || branch.getAttribute('aria-disabled') === 'true') return\n suppressKeyboardUntil.current = performance.now() + 700\n window.setTimeout(() => {\n const active = document.activeElement\n if (active instanceof HTMLElement && (active.matches('input,textarea') || active.isContentEditable)) active.blur()\n }, 0)\n }\n const suppressCommandAutofocus = (event: MouseEvent): void => {\n if (!(event.target instanceof Element)) return\n const commandButton = event.target.closest('button[aria-haspopup=\"listbox\"]')\n if (commandButton === null) return\n // The native composer deliberately preserves editor focus on mousedown;\n // mobile command menus should open without summoning the soft keyboard.\n suppressKeyboardUntil.current = performance.now() + 700\n event.preventDefault()\n event.stopPropagation()\n window.setTimeout(() => {\n const active = document.activeElement\n if (active instanceof HTMLElement && (active.matches('input,textarea') || active.isContentEditable)) active.blur()\n }, 0)\n }\n document.addEventListener('focusin', suppressAutofocus, true)\n document.addEventListener('click', suppressBranchAutofocus, true)\n document.addEventListener('mousedown', suppressCommandAutofocus, true)\n return () => {\n document.removeEventListener('focusin', suppressAutofocus, true)\n document.removeEventListener('click', suppressBranchAutofocus, true)\n document.removeEventListener('mousedown', suppressCommandAutofocus, true)\n }\n }, [])\n\n // A persistent wide sidebar stays put: selecting a session only dismisses\n // the narrow overlay drawer (and its soft-keyboard suppression).\n const closeDrawerAfterSessionAction = (event: { readonly target: EventTarget | null }): void => {\n if (viewportIsWide()) return\n if (!(event.target instanceof Element)) return\n const row = event.target.closest<HTMLElement>('[role=\"treeitem\"][aria-selected]')\n const action = event.target.closest('button,[role=\"button\"]')\n const startsSession = action?.matches('button[class*=\"_newSession\"],button[class*=\"_brand\"]')\n || /新建会话|新会话|new session|new conversation|nuova sessione|nuova conversazione/i.test(action?.getAttribute('aria-label') ?? '')\n if (row === null && !startsSession) return\n if (row !== null && action !== null && action !== row) return\n suppressKeyboardUntil.current = performance.now() + 500\n // Let the session row finish its own click handler before unmounting the drawer.\n window.setTimeout(() => {\n props.controller.closeSidebar()\n const active = document.activeElement\n if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement) active.blur()\n }, 0)\n }\n\n return createElement('div', { className: 'dshm-shell', lang: language },\n createElement('main', { className: 'dshm-main', 'data-dsh-mobile-session': activeSessionId }, props.renderSlot('conversation', {})),\n createElement('button', {\n 'aria-label': messages.closePanels,\n className: 'dshm-scrim',\n // A persistent wide sidebar needs no dimming; the scrim only covers\n // the narrow overlay drawer and the details panel.\n 'data-open': state.detailsOpen || (state.sidebarOpen && !wideViewport),\n onClick: () => { state.detailsOpen ? props.controller.closeDetails() : props.controller.closeSidebar() },\n tabIndex: state.sidebarOpen || state.detailsOpen ? 0 : -1,\n type: 'button',\n }),\n createElement('aside', {\n 'aria-label': messages.workspaceNavigation,\n className: 'dshm-drawer',\n 'data-open': state.sidebarOpen,\n onClickCapture: closeDrawerAfterSessionAction,\n }, props.renderSlot('sidebar', {\n collapsed: !state.sidebarOpen,\n width: state.sidebarOpen ? 340 : 56,\n })),\n createElement('aside', {\n 'aria-hidden': !state.detailsOpen,\n className: 'dshm-details',\n 'data-open': state.detailsOpen,\n ...(state.detailsOpen ? {} : { inert: '' }),\n }, hasSession ? [\n // Dual right-seat rendering across harness generations: 0.1.3-alpha.2\n // and earlier expose the `details` seat (empty owner share); the master\n // layout line renamed it `rightbar` with resolved column geometry.\n // Each host mounts content for exactly one of the two names, so the\n // other slot renders empty without error.\n props.renderSlot('details', {}),\n props.renderSlot('rightbar', {\n width: Math.min(window.innerWidth * 0.94, 460),\n viewportWidth: window.innerWidth,\n // Right track needs ~300px next to a ~400px center; below that the\n // column cannot show and narrow phones see the drawer overlay instead.\n canShow: window.innerWidth >= 720,\n }),\n ] : undefined),\n createElement('div', { className: 'dshm-overlay', 'data-shell-overlay': true }, props.renderSlot('shell.overlay', {})),\n )\n}\n\n// One controller per document: re-applying the layout (reconnect, tab\n// refocus) must not reset the user's explicit sidebar toggle.\nlet sharedController: MobileLayoutController | undefined\n\n/** Replace the desktop layout module on the authenticated mobile surface. */\nexport function apply(ctx: MobileClientContext): void {\n sharedController ??= new MobileLayoutController()\n const controller = sharedController\n ctx.effect(() => {\n const style = document.createElement('style')\n style.dataset.plugin = 'dsh-mobile-layout'\n style.textContent = MOBILE_LAYOUT_STYLES\n document.head.append(style)\n const disposeService = ctx.reflect.provide('layout', controller)\n const disposeRoot = ctx.slots.register({\n name: 'root',\n children: {\n sidebar: { kind: 'single', scope: 'root' },\n conversation: { kind: 'single', scope: 'session-maybe' },\n rightbar: { kind: 'single', scope: 'session' },\n details: { kind: 'single', scope: 'session' },\n 'shell.overlay': { kind: 'list', scope: 'root' },\n },\n }, props => createElement(MobileAppFrame, { ...props, controller }))\n return () => {\n disposeRoot()\n void disposeService()\n style.remove()\n }\n }, 'dsh-mobile: dedicated root layout')\n\n ctx.effect(() => {\n const presenter = new ThemePresenter()\n presenter.apply(ctx.theme.getTheme())\n const off = ctx.on('theme/change', snapshot => { presenter.apply(snapshot) })\n return () => { off(); presenter.dispose() }\n }, 'dsh-mobile: theme presenter')\n}\n\n/** Preserve the official layout module's dependency ordering. */\nexport const inject: readonly string[] = ['slots', 'theme']\n"],"mappings":"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"}
1
+ {"version":3,"file":"mobile-layout.js","names":["useSyncExternalStore","useRef","useState","createElement"],"sources":["../src/mobile-layout-messages.ts","../src/mobile-layout.ts"],"sourcesContent":["export type MobileLayoutLanguage = 'it' | 'en' | 'zh'\n\nexport const MOBILE_LAYOUT_MESSAGES = Object.freeze({\n it: Object.freeze({ closePanels: 'Chiudi pannelli', workspaceNavigation: 'Navigazione area di lavoro e sessioni' }),\n en: Object.freeze({ closePanels: 'Close panels', workspaceNavigation: 'Workspace and session navigation' }),\n zh: Object.freeze({ closePanels: '关闭浮层', workspaceNavigation: '工作区与会话导航' }),\n})\n","import { createElement, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport type { ReactNode } from 'react'\nimport { MOBILE_LAYOUT_MESSAGES, type MobileLayoutLanguage } from './mobile-layout-messages.js'\n\nexport { MOBILE_LAYOUT_MESSAGES } from './mobile-layout-messages.js'\nexport type { MobileLayoutLanguage } from './mobile-layout-messages.js'\n\ninterface SessionState {\n readonly current?: string\n readonly byId: Readonly<Record<string, { readonly blank?: boolean; readonly title?: string } | undefined>>\n}\n\ninterface MobileRootProps {\n readonly renderSlot: (\n name: string,\n owner: Record<string, unknown>,\n options?: { readonly entryKey?: string | null; readonly fallback?: ReactNode },\n ) => ReactNode\n readonly useSessions: <T>(selector: (state: SessionState) => T) => T\n}\n\n/** Root contribution carrying one observable hook into the standard kit. */\ninterface RootHookContribution {\n readonly hooks: Readonly<Record<string, {\n readonly getSnapshot: () => unknown\n readonly subscribe: (listener: () => void) => () => void\n }>>\n}\n\ninterface MobileClientContext {\n readonly effect: (effect: () => void | (() => void), label?: string) => void\n readonly get: (name: string) => unknown\n readonly on: (event: string, listener: (value: ThemeSnapshot) => void) => () => void\n readonly reflect: { provide: (name: string, value: unknown) => () => void | Promise<void> }\n readonly slots: {\n register: (options: Record<string, unknown>, component: (props: MobileRootProps) => ReactNode) => () => void\n entries: (name: string) => readonly { readonly options: { readonly key?: string } }[]\n subscribe: (name: string, listener: () => void) => () => void\n /**\n * Publish a root standard hook. The official layout owns `panelInfo`, so\n * replacing that module means replacing the owner too.\n */\n provideRoot?: (contribution: RootHookContribution) => () => void\n }\n readonly theme: { getTheme: () => ThemeSnapshot }\n}\n\ninterface ThemeSnapshot {\n readonly active: {\n readonly colorScheme: 'dark' | 'light'\n readonly tokens: Readonly<Record<string, string>>\n }\n}\n\ninterface LayoutSnapshot {\n readonly sidebarOpen: boolean\n readonly detailsOpen: boolean\n readonly panelInfo: PanelInfoSnapshot\n}\n\n/**\n * Panel selection as the standard `usePanelInfo` hook reports it: a null\n * `activePanelId` means the conversation owns the center column.\n */\ninterface PanelInfoSnapshot {\n readonly activePanelId: string | null\n}\n\n/** Optional DSH 0.1.5 right-Sidebar controls used by a Mobile close gesture. */\nexport interface SidebarRightControl {\n isExpanded(): boolean\n toggleExpanded(): void\n}\n\n/** Whether an optional service value supports the public right-Sidebar controls. */\nexport function isSidebarRightControl(value: unknown): value is SidebarRightControl {\n if (typeof value !== 'object' || value === null) return false\n const candidate = value as Partial<SidebarRightControl>\n return typeof candidate.isExpanded === 'function' && typeof candidate.toggleExpanded === 'function'\n}\n\n/**\n * Viewport width at which the dedicated layout treats the sidebar as a\n * persistent desktop panel instead of an overlay drawer. Narrow screens\n * keep the overlay behavior byte-for-byte.\n */\nexport const WIDE_LAYOUT_MIN_WIDTH_PX = 900\n\nexport function isWideViewportLayout(viewportWidth: number): boolean {\n return viewportWidth >= WIDE_LAYOUT_MIN_WIDTH_PX\n}\n\n/** Whether the narrow layout needs the modal scrim for either side panel. */\nexport function isMobileScrimOpen(\n sidebarOpen: boolean,\n detailsOpen: boolean,\n wideViewport: boolean,\n): boolean {\n return !wideViewport && (sidebarOpen || detailsOpen)\n}\n\n/**\n * Close the details drawer from its scrim, collapsing the DSH 0.1.5 right\n * Sidebar first when that optional service owns the visible content.\n */\nexport function closeDetailsFromScrim(sidebarRight: unknown, closeDetails: () => void): void {\n if (isSidebarRightControl(sidebarRight) && sidebarRight.isExpanded()) {\n sidebarRight.toggleExpanded()\n }\n closeDetails()\n}\n\n/** Reads the live viewport; unknown environments (SSR, tests) stay narrow. */\nfunction viewportIsWide(): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false\n return window.matchMedia(`(min-width: ${WIDE_LAYOUT_MIN_WIDTH_PX}px)`).matches\n}\n\n/** Resolve the supported language used by the dedicated mobile layout. */\nexport function resolveMobileLayoutLanguage(\n documentLanguage: string,\n browserLanguages: readonly string[],\n): MobileLayoutLanguage {\n return [documentLanguage, ...browserLanguages]\n .map(value => value.trim().toLowerCase().split(/[-_]/u)[0])\n .find((value): value is MobileLayoutLanguage => value === 'it' || value === 'en' || value === 'zh') ?? 'en'\n}\n\nclass MobileLayoutController {\n // Wide viewports start with the persistent sidebar open; applying the\n // layout again (reconnect, refocus) reuses the module singleton below,\n // so an explicit user collapse is never reset.\n private snapshot: LayoutSnapshot = Object.freeze({\n sidebarOpen: viewportIsWide(),\n detailsOpen: false,\n panelInfo: Object.freeze({ activePanelId: null }),\n })\n private readonly listeners = new Set<() => void>()\n private hasMainPanel: ((id: string) => boolean) | undefined\n // Pending workspace/session navigation, cancelled whenever a newer one\n // starts. Mirrors the official LayoutController's navigation controller.\n private navigation = new AbortController()\n\n readonly subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n readonly getSnapshot = (): LayoutSnapshot => this.snapshot\n\n toggleSidebar(): void {\n this.update({ sidebarOpen: !this.snapshot.sidebarOpen })\n }\n\n openDetails(): void {\n this.update({ detailsOpen: true })\n }\n\n closeDetails(): void {\n this.update({ detailsOpen: false })\n }\n\n closeSidebar(): void {\n this.update({ sidebarOpen: false })\n }\n\n /**\n * Invalidate the previous navigation and hand back a fresh signal. The\n * workspace controller wraps this around \"open a workspace\" and \"fork a\n * session\" (dsh-client-ui-workspace) — the path behind the sidebar's\n * new-session button. Without it startSession throws inside a\n * `.catch(reason => console.warn('new session failed:', reason))`, so the\n * button silently did nothing.\n */\n beginNavigation(): AbortSignal {\n this.navigation.abort()\n this.navigation = new AbortController()\n return this.navigation.signal\n }\n\n /** Invalidate pending navigations when this layout unloads. */\n dispose(): void {\n this.navigation.abort()\n }\n\n /** Bind panel validation to the live slot registry owned by this application. */\n bindMainPanelRegistry(hasMainPanel: (id: string) => boolean): () => void {\n this.hasMainPanel = hasMainPanel\n return () => {\n if (this.hasMainPanel === hasMainPanel) this.hasMainPanel = undefined\n }\n }\n\n /**\n * Show the right panel. The official controller tracks a persistent column\n * here; this layout always renders the panel as the details overlay, so both\n * the track and fullscreen requests open the same drawer.\n */\n openRightbar(_track?: boolean, _fullscreen?: boolean): void {\n this.openDetails()\n }\n\n /** Hide the right panel. */\n closeRightbar(): void {\n this.closeDetails()\n }\n\n /**\n * Select the main panel rendered in the center column; null means the\n * conversation. Mirrors the official LayoutController so the `panelInfo`\n * root hook stays truthful.\n */\n selectPanel(panelId: string | null): void {\n if (panelId !== null && this.hasMainPanel?.(panelId) !== true) {\n throw new Error(`layout.selectPanel: main panel \"${panelId}\" is not registered`)\n }\n this.navigation.abort()\n if (this.snapshot.panelInfo.activePanelId === panelId) return\n this.update({ panelInfo: Object.freeze({ activePanelId: panelId }) })\n }\n\n /** Drop a selected panel once no main-slot entry declares it. */\n retainMainPanels(panelIds: readonly string[]): void {\n const active = this.snapshot.panelInfo.activePanelId\n if (active !== null && !panelIds.includes(active)) {\n this.update({ panelInfo: Object.freeze({ activePanelId: null }) })\n }\n }\n\n private update(next: Partial<LayoutSnapshot>): void {\n const snapshot = Object.freeze({ ...this.snapshot, ...next })\n if (\n snapshot.sidebarOpen === this.snapshot.sidebarOpen\n && snapshot.detailsOpen === this.snapshot.detailsOpen\n && snapshot.panelInfo === this.snapshot.panelInfo\n ) return\n this.snapshot = snapshot\n for (const listener of this.listeners) listener()\n }\n}\n\nclass ThemePresenter {\n private appliedTokens: string[] = []\n private readonly meta = document.createElement('meta')\n\n constructor() {\n this.meta.name = 'theme-color'\n }\n\n apply(snapshot: ThemeSnapshot): void {\n const scheme = snapshot.active.colorScheme\n document.documentElement.style.colorScheme = scheme\n document.body.toggleAttribute('data-ds-dark-theme', scheme === 'dark')\n for (const name of this.appliedTokens) document.body.style.removeProperty(name)\n this.appliedTokens = []\n for (const [name, value] of Object.entries(snapshot.active.tokens)) {\n document.body.style.setProperty(name, value)\n this.appliedTokens.push(name)\n }\n this.meta.content = getComputedStyle(document.body).backgroundColor\n if (!this.meta.isConnected) document.head.append(this.meta)\n }\n\n dispose(): void {\n document.documentElement.style.removeProperty('color-scheme')\n document.body.removeAttribute('data-ds-dark-theme')\n for (const name of this.appliedTokens) document.body.style.removeProperty(name)\n this.meta.remove()\n }\n}\n\nexport const MOBILE_LAYOUT_STYLES = `\nhtml,body,#root{width:100%;height:100%;overflow:hidden}\n.dshm-shell{position:relative;display:grid;width:100%;height:100dvh;min-width:0;overflow:hidden;background:var(--dsw-alias-bg-base,#fff)}\n.dshm-main{grid-area:1/1;min-width:0;min-height:0;overflow:hidden}\n.dshm-main>*,.dshm-main>*>*{min-width:0}\n.dshm-drawer{position:fixed;z-index:70;inset:0 auto 0 0;box-sizing:border-box;width:56px;max-width:100%;padding-top:env(safe-area-inset-top);overflow:hidden;background:var(--dsw-alias-bg-layer-1,#f8fafc);box-shadow:none;will-change:width;transition:width 240ms cubic-bezier(.22,1,.36,1),box-shadow 240ms ease}\n.dshm-drawer[data-open=true]{width:min(88vw,340px);box-shadow:18px 0 46px rgb(15 23 42 / 18%)}\n.dshm-drawer[data-open=false]{pointer-events:auto;visibility:visible}\n.dshm-drawer>*{width:100%!important;height:100%!important;transform:translateX(-6px);opacity:.94;transition:transform 220ms cubic-bezier(.22,1,.36,1),opacity 160ms ease-out}\n.dshm-drawer[data-open=true]>*{transform:translateX(0);opacity:1}\n.dshm-drawer[data-open=false]>*{width:56px!important}\n.dshm-details{position:fixed;z-index:80;inset:0 0 0 auto;box-sizing:border-box;width:min(94vw,460px);max-width:100%;padding-top:env(safe-area-inset-top);overflow:hidden;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:-18px 0 46px rgb(15 23 42 / 18%);transform:translateX(104%);transition:transform 190ms cubic-bezier(.22,1,.36,1)}\n.dshm-details[data-open=true]{transform:translateX(0)}\n.dshm-details[data-open=false]{pointer-events:none;visibility:hidden;transition:transform 190ms cubic-bezier(.22,1,.36,1),visibility 0s linear 190ms}\n.dshm-scrim{position:fixed;z-index:65;inset:0;border:0;background:rgb(15 23 42 / 40%);opacity:0;pointer-events:none;transition:opacity 180ms ease-out}\n.dshm-scrim[data-open=true]{opacity:1;pointer-events:auto}\n.dshm-overlay{position:fixed;z-index:90;inset:0;pointer-events:none}.dshm-overlay>*{pointer-events:auto}\n.dshm-shell header{min-width:0;padding-left:52px}\n.dshm-shell textarea{font-size:16px}\n.dshm-shell table{display:block;max-width:100%;overflow-x:auto}\n.dshm-shell pre{max-width:100%;overflow-x:auto}\n.dshm-shell img,.dshm-shell video,.dshm-shell canvas,.dshm-shell svg{max-width:100%}\n.dshm-shell [data-disclosure-row]{min-width:0;max-width:100%}\n.dshm-shell [data-disclosure-row]>*{min-width:0;overflow-wrap:anywhere}\n.dshm-shell [data-context-fields]>*{min-width:0}\n.dshm-shell [class*=\"_body\"]{max-width:100%;overflow-wrap:anywhere}\n.dshm-shell [data-question-key],.dshm-shell [data-plan-review-key]{box-sizing:border-box;width:100%;height:auto!important;min-width:0;flex:none!important;align-self:flex-end;padding:6px max(10px,env(safe-area-inset-left)) max(10px,env(safe-area-inset-bottom)) max(10px,env(safe-area-inset-right))!important}\n.dshm-shell [data-question-key]>section,.dshm-shell [data-plan-review-key]>section{width:100%;height:auto!important;min-height:0!important;max-width:none!important;max-height:min(68dvh,520px)!important;border-radius:16px!important}\n.dshm-shell [data-question-scroll],.dshm-shell [data-plan-review-scroll]{flex:0 1 auto!important;min-height:0!important;max-height:min(42dvh,360px)!important;overscroll-behavior:contain;scroll-padding-bottom:12px}\n@media(max-width:420px){.dshm-shell [data-context-fields]>*{display:grid;grid-template-columns:1fr!important;gap:4px}.dshm-shell [class*=\"_ioSection\"]{grid-template-columns:1fr!important}}\n@media(max-width:600px){\n.dshm-shell [data-question-key]>section>header{display:flex!important;visibility:visible!important;flex:none!important;gap:8px!important;padding:12px 8px 4px 14px!important}\n.dshm-shell [data-question-key]>section>header h2{min-width:0;overflow-wrap:anywhere;font-size:16px!important;line-height:22px!important}\n.dshm-shell [data-question-key]>section>header button{min-width:40px;min-height:40px}\n.dshm-shell [data-question-key] [role=radio],.dshm-shell [data-question-key] [role=checkbox]{min-height:48px!important;touch-action:manipulation}\n.dshm-shell [data-question-key]>section>footer{display:grid!important;grid-template-columns:auto minmax(0,1fr);align-items:center!important;flex:none!important;gap:6px 8px!important;margin-top:4px!important;padding:6px 10px 10px!important}\n.dshm-shell [data-question-key]>section>footer>:last-child{grid-column:1/-1;display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr));width:100%;gap:8px!important}\n.dshm-shell [data-question-key]>section>footer>:last-child button{width:100%;min-height:44px}\n.dshm-shell [data-plan-review-key]>section>div:last-child{display:grid!important;grid-template-columns:1fr;gap:8px!important;padding:8px 12px 10px!important}\n.dshm-shell [data-plan-review-key]>section>div:last-child>div:last-child{display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr));width:100%;gap:8px!important}\n.dshm-shell [data-plan-review-key]>section>div:last-child>div:last-child>button:first-child{grid-column:1/-1}\n.dshm-shell [data-plan-review-key]>section>div:last-child button{width:100%;min-height:44px}\n}\n@media(min-width:900px){\n.dshm-shell{grid-template-columns:auto minmax(0,1fr)}\n.dshm-main{grid-area:1/2}\n.dshm-drawer{position:static;grid-area:1/1;box-shadow:none}\n.dshm-drawer[data-open=true]{width:340px;box-shadow:none}\n.dshm-drawer[data-open=false]{width:56px}\n}\n@media(prefers-reduced-motion:reduce){.dshm-drawer,.dshm-details,.dshm-scrim,.dshm-drawer>*{transition:none!important}}\n`\n\nfunction MobileAppFrame(props: MobileRootProps & {\n readonly controller: MobileLayoutController\n readonly requestDetailsClose: () => void\n}): ReactNode {\n const state = useSyncExternalStore(props.controller.subscribe, props.controller.getSnapshot)\n const suppressKeyboardUntil = useRef(0)\n const [wideViewport, setWideViewport] = useState(viewportIsWide)\n const [documentLanguage, setDocumentLanguage] = useState(document.documentElement.lang)\n const browserLanguages = navigator.languages.length > 0 ? navigator.languages : [navigator.language]\n const language = resolveMobileLayoutLanguage(documentLanguage, browserLanguages)\n const messages = MOBILE_LAYOUT_MESSAGES[language]\n const scrimOpen = isMobileScrimOpen(state.sidebarOpen, state.detailsOpen, wideViewport)\n const activeSessionId = props.useSessions(session => {\n const current = session.current\n return current !== undefined && session.byId[current]?.blank === false ? current : undefined\n })\n const hasSession = activeSessionId !== undefined\n\n useEffect(() => {\n if (typeof window.matchMedia !== 'function') return\n const query = window.matchMedia(`(min-width: ${WIDE_LAYOUT_MIN_WIDTH_PX}px)`)\n const syncViewport = (): void => { setWideViewport(query.matches) }\n syncViewport()\n query.addEventListener('change', syncViewport)\n return () => { query.removeEventListener('change', syncViewport) }\n }, [])\n\n useEffect(() => {\n const observer = new MutationObserver(() => { setDocumentLanguage(document.documentElement.lang) })\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['lang'] })\n return () => { observer.disconnect() }\n }, [])\n\n useEffect(() => {\n if (!hasSession) props.controller.closeDetails()\n }, [hasSession, props.controller])\n\n // Mirror the stock layout: project the current session title into the\n // browser tab, restoring the product title when no session is selected.\n const sessionTitle = props.useSessions(session =>\n session.current === undefined ? undefined : session.byId[session.current]?.title,\n )\n useEffect(() => {\n const productTitle = document.title\n if (sessionTitle !== undefined) document.title = `${sessionTitle} — ${productTitle}`\n return () => { document.title = productTitle }\n }, [sessionTitle])\n\n useEffect(() => {\n const suppressAutofocus = (event: FocusEvent): void => {\n if (performance.now() >= suppressKeyboardUntil.current) return\n const target = event.target\n if (target instanceof HTMLElement && (target.matches('input,textarea') || target.isContentEditable)) target.blur()\n }\n const suppressBranchAutofocus = (event: MouseEvent): void => {\n if (!(event.target instanceof Element)) return\n const branch = event.target.closest('button[aria-label*=\"分支\"],button[aria-label*=\"Branch\"],button[aria-label*=\"branch\"],button[aria-label*=\"Ramo\"],button[aria-label*=\"ramo\"]')\n if (branch === null || branch.hasAttribute('disabled') || branch.getAttribute('aria-disabled') === 'true') return\n suppressKeyboardUntil.current = performance.now() + 700\n window.setTimeout(() => {\n const active = document.activeElement\n if (active instanceof HTMLElement && (active.matches('input,textarea') || active.isContentEditable)) active.blur()\n }, 0)\n }\n const suppressCommandAutofocus = (event: MouseEvent): void => {\n if (!(event.target instanceof Element)) return\n const commandButton = event.target.closest('button[aria-haspopup=\"listbox\"]')\n if (commandButton === null) return\n // The native composer deliberately preserves editor focus on mousedown;\n // mobile command menus should open without summoning the soft keyboard.\n suppressKeyboardUntil.current = performance.now() + 700\n event.preventDefault()\n event.stopPropagation()\n window.setTimeout(() => {\n const active = document.activeElement\n if (active instanceof HTMLElement && (active.matches('input,textarea') || active.isContentEditable)) active.blur()\n }, 0)\n }\n document.addEventListener('focusin', suppressAutofocus, true)\n document.addEventListener('click', suppressBranchAutofocus, true)\n document.addEventListener('mousedown', suppressCommandAutofocus, true)\n return () => {\n document.removeEventListener('focusin', suppressAutofocus, true)\n document.removeEventListener('click', suppressBranchAutofocus, true)\n document.removeEventListener('mousedown', suppressCommandAutofocus, true)\n }\n }, [])\n\n // A persistent wide sidebar stays put: selecting a session only dismisses\n // the narrow overlay drawer (and its soft-keyboard suppression).\n const closeDrawerAfterSessionAction = (event: { readonly target: EventTarget | null }): void => {\n if (viewportIsWide()) return\n if (!(event.target instanceof Element)) return\n const row = event.target.closest<HTMLElement>('[role=\"treeitem\"][aria-selected]')\n const action = event.target.closest('button,[role=\"button\"]')\n const startsSession = action?.matches('button[class*=\"_newSession\"],button[class*=\"_brand\"]')\n || /新建会话|新会话|new session|new conversation|nuova sessione|nuova conversazione/i.test(action?.getAttribute('aria-label') ?? '')\n if (row === null && !startsSession) return\n if (row !== null && action !== null && action !== row) return\n suppressKeyboardUntil.current = performance.now() + 500\n // Let the session row finish its own click handler before unmounting the drawer.\n window.setTimeout(() => {\n props.controller.closeSidebar()\n const active = document.activeElement\n if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement) active.blur()\n }, 0)\n }\n\n return createElement('div', { className: 'dshm-shell', lang: language },\n createElement('main', { className: 'dshm-main', 'data-dsh-mobile-session': activeSessionId },\n props.renderSlot('main', {}, {\n entryKey: state.panelInfo.activePanelId ?? 'conversation',\n fallback: props.renderSlot('conversation', {}),\n })),\n createElement('button', {\n 'aria-label': messages.closePanels,\n className: 'dshm-scrim',\n // A persistent wide sidebar needs no dimming; the scrim only covers\n // the narrow overlay drawer and the details panel.\n 'data-open': scrimOpen,\n 'aria-hidden': !scrimOpen,\n onClick: () => {\n if (!scrimOpen) return\n state.detailsOpen ? props.requestDetailsClose() : props.controller.closeSidebar()\n },\n tabIndex: scrimOpen ? 0 : -1,\n type: 'button',\n }),\n createElement('aside', {\n 'aria-label': messages.workspaceNavigation,\n className: 'dshm-drawer',\n 'data-open': state.sidebarOpen,\n onClickCapture: closeDrawerAfterSessionAction,\n }, props.renderSlot('sidebar', {\n collapsed: !state.sidebarOpen,\n width: state.sidebarOpen ? 340 : 56,\n })),\n createElement('aside', {\n 'aria-hidden': !state.detailsOpen,\n className: 'dshm-details',\n 'data-open': state.detailsOpen,\n ...(state.detailsOpen ? {} : { inert: '' }),\n }, [\n // DSH 0.1.3 and earlier bind details to a Session. Newer DSH owns the\n // Session decision inside its root-scoped rightbar occupant, so that\n // occupant must stay mounted even while no Session is selected.\n hasSession ? props.renderSlot('details', {}) : undefined,\n props.renderSlot('rightbar', {\n width: Math.min(window.innerWidth * 0.94, 460),\n viewportWidth: window.innerWidth,\n // The official frame reports whether a persistent column still fits\n // (normal.rightbar > 0); this layout renders the right panel in the\n // details overlay, which needs no column space. Reporting false would\n // make RightbarSeat force the surface closed immediately after opening\n // it (ui-sidebar-right: `shown && !fullscreen && !canShow` ->\n // actions.setExpanded(false)), so the panel could never stay open on a\n // phone-width viewport.\n canShow: true,\n }),\n ]),\n createElement('div', { className: 'dshm-overlay', 'data-shell-overlay': true }, props.renderSlot('shell.overlay', {})),\n )\n}\n\n// One controller per document: re-applying the layout (reconnect, tab\n// refocus) must not reset the user's explicit sidebar toggle.\nlet sharedController: MobileLayoutController | undefined\n\n/** Replace the desktop layout module on the authenticated mobile surface. */\nexport function apply(ctx: MobileClientContext): void {\n sharedController ??= new MobileLayoutController()\n const controller = sharedController\n ctx.effect(() => {\n const style = document.createElement('style')\n style.dataset.plugin = 'dsh-mobile-layout'\n style.textContent = MOBILE_LAYOUT_STYLES\n document.head.append(style)\n const disposeMainPanelRegistry = controller.bindMainPanelRegistry(id =>\n ctx.slots.entries('main').some(entry => entry.options.key === id))\n const disposeService = ctx.reflect.provide('layout', controller)\n // The official layout owns the root `panelInfo` hook; replacing the layout\n // module means replacing that owner. Without it every registration that\n // declares the standard `usePanelInfo` prop fails assembly\n // (\"strict standard hook 'panelInfo' has no source\") and renders as a dead\n // cell — the workspace sidebar's session list lives exactly there.\n const disposePanelInfo = typeof ctx.slots.provideRoot === 'function'\n ? ctx.slots.provideRoot({ hooks: { panelInfo: {\n getSnapshot: () => controller.getSnapshot().panelInfo,\n subscribe: (listener: () => void) => controller.subscribe(listener),\n } } })\n : () => {}\n const disposeRoot = ctx.slots.register({\n name: 'root',\n children: {\n sidebar: { kind: 'single', scope: 'root' },\n conversation: { kind: 'single', scope: 'session-maybe' },\n // The conversation panel moved into this keyed slot in DSH 0.1.5;\n // without the declaration the center column stays blank.\n main: { kind: 'keyed', scope: 'root' },\n rightbar: { kind: 'single', scope: 'root' },\n details: { kind: 'single', scope: 'session' },\n 'shell.overlay': { kind: 'list', scope: 'root' },\n },\n }, props => createElement(MobileAppFrame, {\n ...props,\n controller,\n // sidebarRight is registered after the layout it depends on, so resolve\n // the optional service at gesture time rather than caching it at boot.\n requestDetailsClose: () => {\n closeDetailsFromScrim(ctx.get('sidebarRight'), () => { controller.closeDetails() })\n },\n }))\n const retainMainPanels = (): void => {\n controller.retainMainPanels(ctx.slots.entries('main').flatMap(entry =>\n entry.options.key === undefined ? [] : [entry.options.key]))\n }\n const disposePanels = ctx.slots.subscribe('main', retainMainPanels)\n retainMainPanels()\n return () => {\n controller.dispose()\n disposePanels()\n disposeRoot()\n disposePanelInfo()\n void disposeService()\n disposeMainPanelRegistry()\n style.remove()\n }\n }, 'dsh-mobile: dedicated root layout')\n\n ctx.effect(() => {\n const presenter = new ThemePresenter()\n presenter.apply(ctx.theme.getTheme())\n const off = ctx.on('theme/change', snapshot => { presenter.apply(snapshot) })\n return () => { off(); presenter.dispose() }\n }, 'dsh-mobile: theme presenter')\n}\n\n/** Preserve the official layout module's dependency ordering. */\nexport const inject: readonly string[] = ['slots', 'theme']\n"],"mappings":";;;;;;;;EAEA,MAAa,yBAAyB,OAAO,OAAO;GAClD,IAAI,OAAO,OAAO;IAAE,aAAa;IAAmB,qBAAqB;GAAwC,CAAC;GAClH,IAAI,OAAO,OAAO;IAAE,aAAa;IAAgB,qBAAqB;GAAmC,CAAC;GAC1G,IAAI,OAAO,OAAO;IAAE,aAAa;IAAQ,qBAAqB;GAAW,CAAC;EAC5E,CAAC;;;;ECqED,SAAgB,sBAAsB,OAA8C;GAClF,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;GACxD,MAAM,YAAY;GAClB,OAAO,OAAO,UAAU,eAAe,cAAc,OAAO,UAAU,mBAAmB;EAC3F;;;;;;EAOA,MAAa,2BAA2B;EAExC,SAAgB,qBAAqB,eAAgC;GACnE,OAAO,iBAAA;EACT;;EAGA,SAAgB,kBACd,aACA,aACA,cACS;GACT,OAAO,CAAC,iBAAiB,eAAe;EAC1C;;;;;EAMA,SAAgB,sBAAsB,cAAuB,cAAgC;GAC3F,IAAI,sBAAsB,YAAY,KAAK,aAAa,WAAW,GACjE,aAAa,eAAe;GAE9B,aAAa;EACf;;EAGA,SAAS,iBAA0B;GACjC,IAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY,OAAO;GACrF,OAAO,OAAO,WAAW,oBAA4C,CAAC,CAAC;EACzE;;EAGA,SAAgB,4BACd,kBACA,kBACsB;GACtB,OAAO,CAAC,kBAAkB,GAAG,gBAAgB,CAAC,CAC3C,KAAI,UAAS,MAAM,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,CAC1D,MAAM,UAAyC,UAAU,QAAQ,UAAU,QAAQ,UAAU,IAAI,KAAK;EAC3G;EAEA,IAAM,yBAAN,MAA6B;GAI3B,WAAmC,OAAO,OAAO;IAC/C,aAAa,eAAe;IAC5B,aAAa;IACb,WAAW,OAAO,OAAO,EAAE,eAAe,KAAK,CAAC;GAClD,CAAC;GACD,4BAA6B,IAAI,IAAgB;GACjD;GAGA,aAAqB,IAAI,gBAAgB;GAEzC,aAAsB,aAAuC;IAC3D,KAAK,UAAU,IAAI,QAAQ;IAC3B,aAAa;KAAE,KAAK,UAAU,OAAO,QAAQ;IAAE;GACjD;GAEA,oBAA6C,KAAK;GAElD,gBAAsB;IACpB,KAAK,OAAO,EAAE,aAAa,CAAC,KAAK,SAAS,YAAY,CAAC;GACzD;GAEA,cAAoB;IAClB,KAAK,OAAO,EAAE,aAAa,KAAK,CAAC;GACnC;GAEA,eAAqB;IACnB,KAAK,OAAO,EAAE,aAAa,MAAM,CAAC;GACpC;GAEA,eAAqB;IACnB,KAAK,OAAO,EAAE,aAAa,MAAM,CAAC;GACpC;;;;;;;;;GAUA,kBAA+B;IAC7B,KAAK,WAAW,MAAM;IACtB,KAAK,aAAa,IAAI,gBAAgB;IACtC,OAAO,KAAK,WAAW;GACzB;;GAGA,UAAgB;IACd,KAAK,WAAW,MAAM;GACxB;;GAGA,sBAAsB,cAAmD;IACvE,KAAK,eAAe;IACpB,aAAa;KACX,IAAI,KAAK,iBAAiB,cAAc,KAAK,eAAe,KAAA;IAC9D;GACF;;;;;;GAOA,aAAa,QAAkB,aAA6B;IAC1D,KAAK,YAAY;GACnB;;GAGA,gBAAsB;IACpB,KAAK,aAAa;GACpB;;;;;;GAOA,YAAY,SAA8B;IACxC,IAAI,YAAY,QAAQ,KAAK,eAAe,OAAO,MAAM,MACvD,MAAM,IAAI,MAAM,mCAAmC,QAAQ,oBAAoB;IAEjF,KAAK,WAAW,MAAM;IACtB,IAAI,KAAK,SAAS,UAAU,kBAAkB,SAAS;IACvD,KAAK,OAAO,EAAE,WAAW,OAAO,OAAO,EAAE,eAAe,QAAQ,CAAC,EAAE,CAAC;GACtE;;GAGA,iBAAiB,UAAmC;IAClD,MAAM,SAAS,KAAK,SAAS,UAAU;IACvC,IAAI,WAAW,QAAQ,CAAC,SAAS,SAAS,MAAM,GAC9C,KAAK,OAAO,EAAE,WAAW,OAAO,OAAO,EAAE,eAAe,KAAK,CAAC,EAAE,CAAC;GAErE;GAEA,OAAe,MAAqC;IAClD,MAAM,WAAW,OAAO,OAAO;KAAE,GAAG,KAAK;KAAU,GAAG;IAAK,CAAC;IAC5D,IACE,SAAS,gBAAgB,KAAK,SAAS,eACpC,SAAS,gBAAgB,KAAK,SAAS,eACvC,SAAS,cAAc,KAAK,SAAS,WACxC;IACF,KAAK,WAAW;IAChB,KAAK,MAAM,YAAY,KAAK,WAAW,SAAS;GAClD;EACF;EAEA,IAAM,iBAAN,MAAqB;GACnB,gBAAkC,CAAC;GACnC,OAAwB,SAAS,cAAc,MAAM;GAErD,cAAc;IACZ,KAAK,KAAK,OAAO;GACnB;GAEA,MAAM,UAA+B;IACnC,MAAM,SAAS,SAAS,OAAO;IAC/B,SAAS,gBAAgB,MAAM,cAAc;IAC7C,SAAS,KAAK,gBAAgB,sBAAsB,WAAW,MAAM;IACrE,KAAK,MAAM,QAAQ,KAAK,eAAe,SAAS,KAAK,MAAM,eAAe,IAAI;IAC9E,KAAK,gBAAgB,CAAC;IACtB,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,SAAS,OAAO,MAAM,GAAG;KAClE,SAAS,KAAK,MAAM,YAAY,MAAM,KAAK;KAC3C,KAAK,cAAc,KAAK,IAAI;IAC9B;IACA,KAAK,KAAK,UAAU,iBAAiB,SAAS,IAAI,CAAC,CAAC;IACpD,IAAI,CAAC,KAAK,KAAK,aAAa,SAAS,KAAK,OAAO,KAAK,IAAI;GAC5D;GAEA,UAAgB;IACd,SAAS,gBAAgB,MAAM,eAAe,cAAc;IAC5D,SAAS,KAAK,gBAAgB,oBAAoB;IAClD,KAAK,MAAM,QAAQ,KAAK,eAAe,SAAS,KAAK,MAAM,eAAe,IAAI;IAC9E,KAAK,KAAK,OAAO;GACnB;EACF;EAEA,MAAa,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqDpC,SAAS,eAAe,OAGV;GACZ,MAAM,SAAA,GAAQA,MAAAA,qBAAAA,CAAqB,MAAM,WAAW,WAAW,MAAM,WAAW,WAAW;GAC3F,MAAM,yBAAA,GAAwBC,MAAAA,OAAAA,CAAO,CAAC;GACtC,MAAM,CAAC,cAAc,oBAAA,GAAmBC,MAAAA,SAAAA,CAAS,cAAc;GAC/D,MAAM,CAAC,kBAAkB,wBAAA,GAAuBA,MAAAA,SAAAA,CAAS,SAAS,gBAAgB,IAAI;GAEtF,MAAM,WAAW,4BAA4B,kBADpB,UAAU,UAAU,SAAS,IAAI,UAAU,YAAY,CAAC,UAAU,QAAQ,CACpB;GAC/E,MAAM,WAAW,uBAAuB;GACxC,MAAM,YAAY,kBAAkB,MAAM,aAAa,MAAM,aAAa,YAAY;GACtF,MAAM,kBAAkB,MAAM,aAAY,YAAW;IACnD,MAAM,UAAU,QAAQ;IACxB,OAAO,YAAY,KAAA,KAAa,QAAQ,KAAK,QAAQ,EAAE,UAAU,QAAQ,UAAU,KAAA;GACrF,CAAC;GACD,MAAM,aAAa,oBAAoB,KAAA;GAEvC,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,IAAI,OAAO,OAAO,eAAe,YAAY;IAC7C,MAAM,QAAQ,OAAO,WAAW,oBAA4C;IAC5E,MAAM,qBAA2B;KAAE,gBAAgB,MAAM,OAAO;IAAE;IAClE,aAAa;IACb,MAAM,iBAAiB,UAAU,YAAY;IAC7C,aAAa;KAAE,MAAM,oBAAoB,UAAU,YAAY;IAAE;GACnE,GAAG,CAAC,CAAC;GAEL,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,MAAM,WAAW,IAAI,uBAAuB;KAAE,oBAAoB,SAAS,gBAAgB,IAAI;IAAE,CAAC;IAClG,SAAS,QAAQ,SAAS,iBAAiB;KAAE,YAAY;KAAM,iBAAiB,CAAC,MAAM;IAAE,CAAC;IAC1F,aAAa;KAAE,SAAS,WAAW;IAAE;GACvC,GAAG,CAAC,CAAC;GAEL,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,IAAI,CAAC,YAAY,MAAM,WAAW,aAAa;GACjD,GAAG,CAAC,YAAY,MAAM,UAAU,CAAC;GAIjC,MAAM,eAAe,MAAM,aAAY,YACrC,QAAQ,YAAY,KAAA,IAAY,KAAA,IAAY,QAAQ,KAAK,QAAQ,QAAQ,EAAE,KAC7E;GACA,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,MAAM,eAAe,SAAS;IAC9B,IAAI,iBAAiB,KAAA,GAAW,SAAS,QAAQ,GAAG,aAAa,KAAK;IACtE,aAAa;KAAE,SAAS,QAAQ;IAAa;GAC/C,GAAG,CAAC,YAAY,CAAC;GAEjB,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,MAAM,qBAAqB,UAA4B;KACrD,IAAI,YAAY,IAAI,KAAK,sBAAsB,SAAS;KACxD,MAAM,SAAS,MAAM;KACrB,IAAI,kBAAkB,gBAAgB,OAAO,QAAQ,gBAAgB,KAAK,OAAO,oBAAoB,OAAO,KAAK;IACnH;IACA,MAAM,2BAA2B,UAA4B;KAC3D,IAAI,EAAE,MAAM,kBAAkB,UAAU;KACxC,MAAM,SAAS,MAAM,OAAO,QAAQ,oJAA0I;KAC9K,IAAI,WAAW,QAAQ,OAAO,aAAa,UAAU,KAAK,OAAO,aAAa,eAAe,MAAM,QAAQ;KAC3G,sBAAsB,UAAU,YAAY,IAAI,IAAI;KACpD,OAAO,iBAAiB;MACtB,MAAM,SAAS,SAAS;MACxB,IAAI,kBAAkB,gBAAgB,OAAO,QAAQ,gBAAgB,KAAK,OAAO,oBAAoB,OAAO,KAAK;KACnH,GAAG,CAAC;IACN;IACA,MAAM,4BAA4B,UAA4B;KAC5D,IAAI,EAAE,MAAM,kBAAkB,UAAU;KAExC,IADsB,MAAM,OAAO,QAAQ,mCAC3B,MAAM,MAAM;KAG5B,sBAAsB,UAAU,YAAY,IAAI,IAAI;KACpD,MAAM,eAAe;KACrB,MAAM,gBAAgB;KACtB,OAAO,iBAAiB;MACtB,MAAM,SAAS,SAAS;MACxB,IAAI,kBAAkB,gBAAgB,OAAO,QAAQ,gBAAgB,KAAK,OAAO,oBAAoB,OAAO,KAAK;KACnH,GAAG,CAAC;IACN;IACA,SAAS,iBAAiB,WAAW,mBAAmB,IAAI;IAC5D,SAAS,iBAAiB,SAAS,yBAAyB,IAAI;IAChE,SAAS,iBAAiB,aAAa,0BAA0B,IAAI;IACrE,aAAa;KACX,SAAS,oBAAoB,WAAW,mBAAmB,IAAI;KAC/D,SAAS,oBAAoB,SAAS,yBAAyB,IAAI;KACnE,SAAS,oBAAoB,aAAa,0BAA0B,IAAI;IAC1E;GACF,GAAG,CAAC,CAAC;GAIL,MAAM,iCAAiC,UAAyD;IAC9F,IAAI,eAAe,GAAG;IACtB,IAAI,EAAE,MAAM,kBAAkB,UAAU;IACxC,MAAM,MAAM,MAAM,OAAO,QAAqB,oCAAkC;IAChF,MAAM,SAAS,MAAM,OAAO,QAAQ,0BAAwB;IAC5D,MAAM,gBAAgB,QAAQ,QAAQ,0DAAsD,KACvF,4EAA4E,KAAK,QAAQ,aAAa,YAAY,KAAK,EAAE;IAC9H,IAAI,QAAQ,QAAQ,CAAC,eAAe;IACpC,IAAI,QAAQ,QAAQ,WAAW,QAAQ,WAAW,KAAK;IACvD,sBAAsB,UAAU,YAAY,IAAI,IAAI;IAEpD,OAAO,iBAAiB;KACtB,MAAM,WAAW,aAAa;KAC9B,MAAM,SAAS,SAAS;KACxB,IAAI,kBAAkB,oBAAoB,kBAAkB,qBAAqB,OAAO,KAAK;IAC/F,GAAG,CAAC;GACN;GAEA,QAAA,GAAOC,MAAAA,cAAAA,CAAc,OAAO;IAAE,WAAW;IAAc,MAAM;GAAS,IAAA,GACpEA,MAAAA,cAAAA,CAAc,QAAQ;IAAE,WAAW;IAAa,2BAA2B;GAAgB,GACzF,MAAM,WAAW,QAAQ,CAAC,GAAG;IAC3B,UAAU,MAAM,UAAU,iBAAiB;IAC3C,UAAU,MAAM,WAAW,gBAAgB,CAAC,CAAC;GAC/C,CAAC,CAAC,IAAA,GACJA,MAAAA,cAAAA,CAAc,UAAU;IACtB,cAAc,SAAS;IACvB,WAAW;IAGX,aAAa;IACb,eAAe,CAAC;IAChB,eAAe;KACb,IAAI,CAAC,WAAW;KAChB,MAAM,cAAc,MAAM,oBAAoB,IAAI,MAAM,WAAW,aAAa;IAClF;IACA,UAAU,YAAY,IAAI;IAC1B,MAAM;GACR,CAAC,IAAA,GACDA,MAAAA,cAAAA,CAAc,SAAS;IACrB,cAAc,SAAS;IACvB,WAAW;IACX,aAAa,MAAM;IACnB,gBAAgB;GAClB,GAAG,MAAM,WAAW,WAAW;IAC7B,WAAW,CAAC,MAAM;IAClB,OAAO,MAAM,cAAc,MAAM;GACnC,CAAC,CAAC,IAAA,GACFA,MAAAA,cAAAA,CAAc,SAAS;IACrB,eAAe,CAAC,MAAM;IACtB,WAAW;IACX,aAAa,MAAM;IACnB,GAAI,MAAM,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG;GAC3C,GAAG,CAID,aAAa,MAAM,WAAW,WAAW,CAAC,CAAC,IAAI,KAAA,GAC/C,MAAM,WAAW,YAAY;IAC3B,OAAO,KAAK,IAAI,OAAO,aAAa,KAAM,GAAG;IAC7C,eAAe,OAAO;IAQtB,SAAS;GACX,CAAC,CACH,CAAC,IAAA,GACDA,MAAAA,cAAAA,CAAc,OAAO;IAAE,WAAW;IAAgB,sBAAsB;GAAK,GAAG,MAAM,WAAW,iBAAiB,CAAC,CAAC,CAAC,CACvH;EACF;EAIA,IAAI;;EAGJ,SAAgB,MAAM,KAAgC;GACpD,qBAAqB,IAAI,uBAAuB;GAChD,MAAM,aAAa;GACnB,IAAI,aAAa;IACf,MAAM,QAAQ,SAAS,cAAc,OAAO;IAC5C,MAAM,QAAQ,SAAS;IACvB,MAAM,cAAc;IACpB,SAAS,KAAK,OAAO,KAAK;IAC1B,MAAM,2BAA2B,WAAW,uBAAsB,OAChE,IAAI,MAAM,QAAQ,MAAM,CAAC,CAAC,MAAK,UAAS,MAAM,QAAQ,QAAQ,EAAE,CAAC;IACnE,MAAM,iBAAiB,IAAI,QAAQ,QAAQ,UAAU,UAAU;IAM/D,MAAM,mBAAmB,OAAO,IAAI,MAAM,gBAAgB,aACtD,IAAI,MAAM,YAAY,EAAE,OAAO,EAAE,WAAW;KAC1C,mBAAmB,WAAW,YAAY,CAAC,CAAC;KAC5C,YAAY,aAAyB,WAAW,UAAU,QAAQ;IACpE,EAAE,EAAE,CAAC,UACC,CAAC;IACX,MAAM,cAAc,IAAI,MAAM,SAAS;KACrC,MAAM;KACN,UAAU;MACR,SAAS;OAAE,MAAM;OAAU,OAAO;MAAO;MACzC,cAAc;OAAE,MAAM;OAAU,OAAO;MAAgB;MAGvD,MAAM;OAAE,MAAM;OAAS,OAAO;MAAO;MACrC,UAAU;OAAE,MAAM;OAAU,OAAO;MAAO;MAC1C,SAAS;OAAE,MAAM;OAAU,OAAO;MAAU;MAC5C,iBAAiB;OAAE,MAAM;OAAQ,OAAO;MAAO;KACjD;IACF,IAAG,WAAA,GAASA,MAAAA,cAAAA,CAAc,gBAAgB;KACxC,GAAG;KACH;KAGA,2BAA2B;MACzB,sBAAsB,IAAI,IAAI,cAAc,SAAS;OAAE,WAAW,aAAa;MAAE,CAAC;KACpF;IACF,CAAC,CAAC;IACF,MAAM,yBAA+B;KACnC,WAAW,iBAAiB,IAAI,MAAM,QAAQ,MAAM,CAAC,CAAC,SAAQ,UAC5D,MAAM,QAAQ,QAAQ,KAAA,IAAY,CAAC,IAAI,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC;IAC/D;IACA,MAAM,gBAAgB,IAAI,MAAM,UAAU,QAAQ,gBAAgB;IAClE,iBAAiB;IACjB,aAAa;KACX,WAAW,QAAQ;KACnB,cAAc;KACd,YAAY;KACZ,iBAAiB;KACjB,eAAoB;KACpB,yBAAyB;KACzB,MAAM,OAAO;IACf;GACF,GAAG,mCAAmC;GAEtC,IAAI,aAAa;IACf,MAAM,YAAY,IAAI,eAAe;IACrC,UAAU,MAAM,IAAI,MAAM,SAAS,CAAC;IACpC,MAAM,MAAM,IAAI,GAAG,iBAAgB,aAAY;KAAE,UAAU,MAAM,QAAQ;IAAE,CAAC;IAC5E,aAAa;KAAE,IAAI;KAAG,UAAU,QAAQ;IAAE;GAC5C,GAAG,6BAA6B;EAClC;;EAGA,MAAa,SAA4B,CAAC,SAAS,OAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mobile",
3
- "version": "0.3.14",
3
+ "version": "0.3.15",
4
4
  "private": false,
5
5
  "description": "DeepSeek Harness 移动端适配与安全访问插件,支持局域网、远程连接、Android App 和手机浏览器。",
6
6
  "type": "module",
@@ -31,6 +31,7 @@
31
31
  "README.md",
32
32
  "README.en.md",
33
33
  "CHANGELOG.md",
34
+ "docs/DSH_0.1.5_LAN.md",
34
35
  "docs/SELF_HOSTED_FRP.md",
35
36
  "LICENSE",
36
37
  "FUNNEL_THIRD_PARTY_LICENSES.txt",
@@ -100,10 +101,10 @@
100
101
  "license": "Apache-2.0",
101
102
  "peerDependencies": {
102
103
  "@deepseek-ai/cordis": "^4.0.1",
103
- "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.5 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.3-0",
104
- "@deepseek-ai/dsh-commands": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.3-0",
105
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.5 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.3-0",
106
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.3-0",
104
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.5 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.3-0 || ^0.1.5-0",
105
+ "@deepseek-ai/dsh-commands": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.3-0 || ^0.1.5-0",
106
+ "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.5 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.3-0 || ^0.1.5-0",
107
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.3-0 || ^0.1.5-0",
107
108
  "react": "^18.2.0"
108
109
  },
109
110
  "peerDependenciesMeta": {
@@ -133,17 +134,18 @@
133
134
  "selfsigned": "^5.5.0"
134
135
  },
135
136
  "devDependencies": {
136
- "@deepseek-ai/cordis": "4.0.1",
137
- "@deepseek-ai/cordis-plugin-include": "1.0.6",
138
- "@deepseek-ai/cordis-plugin-loader": "1.0.2",
139
- "@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
140
- "@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.2",
141
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
137
+ "@deepseek-ai/cordis": "4.0.2",
138
+ "@deepseek-ai/cordis-plugin-include": "1.0.7",
139
+ "@deepseek-ai/cordis-plugin-loader": "1.0.3",
140
+ "@deepseek-ai/dsh-client-connection": "0.1.5-rc.2",
141
+ "@deepseek-ai/dsh-commands": "0.1.5-rc.2",
142
+ "@deepseek-ai/dsh-host-webserver": "0.1.5-rc.2",
143
+ "@deepseek-ai/dsh-llm": "0.1.5-rc.2",
142
144
  "@types/js-yaml": "^4.0.9",
143
145
  "@types/node": "^24.3.0",
144
146
  "@types/qrcode": "^1.5.5",
145
147
  "@types/react": "~18.3.1",
146
- "js-yaml": "^4.3.1",
148
+ "js-yaml": "^4.3.2",
147
149
  "react": "^18.2.0",
148
150
  "tsdown": "^0.22.14",
149
151
  "typescript": "^6.0.3",