dsh-mobile 0.4.6 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,7 +45,7 @@ window.__ModuleLoader__.load({
45
45
  }
46
46
  /** Preserve a readable conversation width before turning a requested right track into a docked column. */
47
47
  function resolveMobileRightbarLayout(viewportWidth, sidebarOpen, track, fullscreen) {
48
- const overlayWidth = Math.min(viewportWidth * .94, RIGHTBAR_MAX_WIDTH_PX);
48
+ const overlayWidth = isWideViewportLayout(viewportWidth) ? Math.min(viewportWidth * .94, RIGHTBAR_MAX_WIDTH_PX) : viewportWidth;
49
49
  if (!track || fullscreen || !isWideViewportLayout(viewportWidth) && sidebarOpen) return Object.freeze({
50
50
  docked: false,
51
51
  width: overlayWidth
@@ -67,6 +67,55 @@ window.__ModuleLoader__.load({
67
67
  return !wideViewport && sidebarOpen || detailsModalOpen;
68
68
  }
69
69
  /**
70
+ * Query matching devices whose primary pointer cannot hover: the phones and
71
+ * tablets where focusing an editor raises a soft keyboard the user did not ask
72
+ * for. A desktop keeps stock behaviour because it has a real keyboard already.
73
+ */
74
+ const TOUCH_PRIMARY_QUERY = "(hover: none), (pointer: coarse)";
75
+ /** The stock composer wrapper, which owns everything the phone adapter edits. */
76
+ const COMPOSER_CARD_SELECTOR = "[data-composer-card]";
77
+ /** The composer's editing host, whichever contenteditable flavour it boots with. */
78
+ const COMPOSER_EDITOR_SELECTOR = `${COMPOSER_CARD_SELECTOR} [contenteditable="true"],${COMPOSER_CARD_SELECTOR} [contenteditable="plaintext-only"]`;
79
+ /** The composer's Add trigger, whose menu must open without summoning the IME. */
80
+ const COMPOSER_COMMAND_TRIGGER_SELECTOR = `${COMPOSER_CARD_SELECTOR} button[aria-haspopup="listbox"]`;
81
+ /**
82
+ * Whether a focus target belongs to the composer, which owns its own keyboard
83
+ * decision and must never be blurred by a suppression window.
84
+ * @param target - the focus target, already narrowed to an element.
85
+ * @returns whether the target sits inside the composer card.
86
+ */
87
+ function isComposerOwnedFocus(target) {
88
+ return target !== null && target.closest(COMPOSER_CARD_SELECTOR) !== null;
89
+ }
90
+ /**
91
+ * Whether a click opens a different Session, rather than its menu or current row.
92
+ * @param target - the click target in the workspace sidebar.
93
+ * @returns whether the Session selection can change.
94
+ */
95
+ function isSessionRowNavigation(target) {
96
+ if (target === null) return false;
97
+ const row = target.closest("[role=\"treeitem\"][aria-selected]");
98
+ if (row === null || row.getAttribute("aria-selected") === "true") return false;
99
+ const action = target.closest("button,[role=\"button\"]");
100
+ return action === null || action === row;
101
+ }
102
+ /**
103
+ * Decide how one pointer event changes the composer's soft keyboard.
104
+ *
105
+ * The Add button's own mousedown calls `keepDraftFocus`, which focuses the draft
106
+ * on purpose so the caret survives the menu; on a phone that focus is what
107
+ * raises the keyboard. `inputmode="none"` withholds the IME without moving focus
108
+ * or the caret, so the menu, the caret, and the draft all keep stock behaviour.
109
+ * @param target - the pointer target, already narrowed to an element.
110
+ * @param touchPrimary - whether the device's primary pointer cannot hover.
111
+ * @returns `withhold` for the Add trigger, `restore` for any other tap, and
112
+ * `ignore` where no soft keyboard exists to withhold.
113
+ */
114
+ function resolveComposerImePolicy(target, touchPrimary) {
115
+ if (!touchPrimary || target === null) return "ignore";
116
+ return target.closest(COMPOSER_COMMAND_TRIGGER_SELECTOR) === null ? "restore" : "withhold";
117
+ }
118
+ /**
70
119
  * Close the details drawer from its scrim, collapsing the DSH 0.1.5 right
71
120
  * Sidebar first when that optional service owns the visible content.
72
121
  */
@@ -101,6 +150,19 @@ window.__ModuleLoader__.load({
101
150
  };
102
151
  };
103
152
  getSnapshot = () => this.snapshot;
153
+ /**
154
+ * The panel store on the layout service itself (`ctx.layout.panelInfo`),
155
+ * mirroring the official LayoutController of DSH 0.1.7-rc.2+. The root hook
156
+ * wired in `apply` feeds `usePanelInfo` rendering; rc.2 client plugins read
157
+ * this service member directly. Without it `dsh-client-ui-plugin-manager`
158
+ * fails activation on the dedicated mobile frontend ("web boot: 1 entry did
159
+ * not activate") and `dsh-client-ui-open-in-app` throws the first time its
160
+ * shortcut target runs.
161
+ */
162
+ panelInfo = {
163
+ getSnapshot: () => this.getSnapshot().panelInfo,
164
+ subscribe: (listener) => this.subscribe(listener)
165
+ };
104
166
  toggleSidebar() {
105
167
  this.update({ sidebarOpen: !this.snapshot.sidebarOpen });
106
168
  }
@@ -226,6 +288,7 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
226
288
  .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)}
227
289
  .dshm-details[data-open=true]{transform:translateX(0)}
228
290
  .dshm-details[data-open=false]{pointer-events:none;visibility:hidden;transition:transform 190ms cubic-bezier(.22,1,.36,1),visibility 0s linear 190ms}
291
+ @media(max-width:899px){.dshm-details{width:100%;max-width:100%;box-shadow:none}.dshm-details [data-sidebar-right-toggle],.dshm-details [data-sidebar-right-mode]{min-width:48px;min-height:48px}}
229
292
  .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}
230
293
  .dshm-scrim[data-open=true]{opacity:1;pointer-events:auto}
231
294
  .dshm-overlay{position:fixed;z-index:90;inset:0;pointer-events:none}.dshm-overlay>*{pointer-events:auto}
@@ -267,6 +330,16 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
267
330
  function MobileAppFrame(props) {
268
331
  const state = (0, react.useSyncExternalStore)(props.controller.subscribe, props.controller.getSnapshot);
269
332
  const suppressKeyboardUntil = (0, react.useRef)(0);
333
+ const suppressComposerUntil = (0, react.useRef)(0);
334
+ const navigationIme = (0, react.useRef)(/* @__PURE__ */ new Map());
335
+ const restoreNavigationIme = () => {
336
+ for (const [editor, previous] of navigationIme.current) {
337
+ if (editor.getAttribute("inputmode") !== "none") continue;
338
+ if (previous === null) editor.removeAttribute("inputmode");
339
+ else editor.setAttribute("inputmode", previous);
340
+ }
341
+ navigationIme.current.clear();
342
+ };
270
343
  const [viewportWidth, setViewportWidth] = (0, react.useState)(window.innerWidth);
271
344
  (0, react.useEffect)(() => {
272
345
  let frame;
@@ -286,10 +359,11 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
286
359
  const wideViewport = isWideViewportLayout(viewportWidth);
287
360
  const rightbar = resolveMobileRightbarLayout(viewportWidth, state.sidebarOpen, state.rightbarTrack, state.rightbarFullscreen);
288
361
  const rightbarDocked = state.detailsOpen && rightbar.docked;
362
+ const rightbarFullWidth = state.detailsOpen && !rightbarDocked && !wideViewport;
289
363
  const [documentLanguage, setDocumentLanguage] = (0, react.useState)(document.documentElement.lang);
290
364
  const language = resolveMobileLayoutLanguage(documentLanguage, navigator.languages.length > 0 ? navigator.languages : [navigator.language]);
291
365
  const messages = MOBILE_LAYOUT_MESSAGES[language];
292
- const scrimOpen = isMobileScrimOpen(state.sidebarOpen, state.detailsOpen && !rightbarDocked, wideViewport);
366
+ const scrimOpen = !rightbarFullWidth && isMobileScrimOpen(state.sidebarOpen, state.detailsOpen && !rightbarDocked, wideViewport);
293
367
  const activeSessionId = props.useSessions((session) => {
294
368
  const current = session.current;
295
369
  return current !== void 0 && session.byId[current]?.blank === false ? current : void 0;
@@ -319,10 +393,17 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
319
393
  };
320
394
  }, [sessionTitle]);
321
395
  (0, react.useEffect)(() => {
396
+ const elementTarget = (target) => target instanceof Element ? target : null;
322
397
  const suppressAutofocus = (event) => {
398
+ const target = elementTarget(event.target);
399
+ if (target === null) return;
400
+ if (!(target instanceof HTMLElement && (target.matches("input,textarea") || target.isContentEditable))) return;
401
+ if (isComposerOwnedFocus(target)) {
402
+ if (performance.now() < suppressComposerUntil.current) target.blur();
403
+ return;
404
+ }
323
405
  if (performance.now() >= suppressKeyboardUntil.current) return;
324
- const target = event.target;
325
- if (target instanceof HTMLElement && (target.matches("input,textarea") || target.isContentEditable)) target.blur();
406
+ target.blur();
326
407
  };
327
408
  const suppressBranchAutofocus = (event) => {
328
409
  if (!(event.target instanceof Element)) return;
@@ -334,29 +415,45 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
334
415
  if (active instanceof HTMLElement && (active.matches("input,textarea") || active.isContentEditable)) active.blur();
335
416
  }, 0);
336
417
  };
337
- const suppressCommandAutofocus = (event) => {
338
- if (!(event.target instanceof Element)) return;
339
- if (event.target.closest("button[aria-haspopup=\"listbox\"]") === null) return;
340
- suppressKeyboardUntil.current = performance.now() + 700;
341
- event.preventDefault();
342
- event.stopPropagation();
343
- window.setTimeout(() => {
344
- const active = document.activeElement;
345
- if (active instanceof HTMLElement && (active.matches("input,textarea") || active.isContentEditable)) active.blur();
346
- }, 0);
418
+ const applyComposerImePolicy = (event) => {
419
+ const target = elementTarget(event.target);
420
+ if (isComposerOwnedFocus(target)) {
421
+ suppressComposerUntil.current = 0;
422
+ restoreNavigationIme();
423
+ }
424
+ const policy = resolveComposerImePolicy(target, window.matchMedia(TOUCH_PRIMARY_QUERY).matches);
425
+ if (policy === "ignore") return;
426
+ for (const editor of document.querySelectorAll(COMPOSER_EDITOR_SELECTOR)) if (policy === "withhold") editor.setAttribute("inputmode", "none");
427
+ else if (editor.getAttribute("inputmode") === "none") editor.removeAttribute("inputmode");
347
428
  };
348
429
  document.addEventListener("focusin", suppressAutofocus, true);
349
430
  document.addEventListener("click", suppressBranchAutofocus, true);
350
- document.addEventListener("mousedown", suppressCommandAutofocus, true);
431
+ document.addEventListener("pointerdown", applyComposerImePolicy, true);
351
432
  return () => {
352
433
  document.removeEventListener("focusin", suppressAutofocus, true);
353
434
  document.removeEventListener("click", suppressBranchAutofocus, true);
354
- document.removeEventListener("mousedown", suppressCommandAutofocus, true);
435
+ document.removeEventListener("pointerdown", applyComposerImePolicy, true);
436
+ restoreNavigationIme();
437
+ for (const editor of document.querySelectorAll(`${COMPOSER_CARD_SELECTOR} [inputmode="none"]`)) editor.removeAttribute("inputmode");
355
438
  };
356
439
  }, []);
357
440
  const closeDrawerAfterSessionAction = (event) => {
358
- if (viewportIsWide()) return;
359
441
  if (!(event.target instanceof Element)) return;
442
+ if (isSessionRowNavigation(event.target) && window.matchMedia("(hover: none), (pointer: coarse)").matches) {
443
+ const deadline = performance.now() + 500;
444
+ suppressComposerUntil.current = deadline;
445
+ for (const editor of document.querySelectorAll(COMPOSER_EDITOR_SELECTOR)) {
446
+ if (!navigationIme.current.has(editor)) navigationIme.current.set(editor, editor.getAttribute("inputmode"));
447
+ editor.setAttribute("inputmode", "none");
448
+ if (document.activeElement === editor) editor.blur();
449
+ }
450
+ window.setTimeout(() => {
451
+ if (suppressComposerUntil.current !== deadline) return;
452
+ suppressComposerUntil.current = 0;
453
+ restoreNavigationIme();
454
+ }, 500);
455
+ }
456
+ if (viewportIsWide()) return;
360
457
  const row = event.target.closest("[role=\"treeitem\"][aria-selected]");
361
458
  const action = event.target.closest("button,[role=\"button\"]");
362
459
  const startsSession = action?.matches("button[class*=\"_newSession\"],button[class*=\"_brand\"]") || /新建会话|新会话|new session|new conversation|nuova sessione|nuova conversazione/i.test(action?.getAttribute("aria-label") ?? "");
@@ -377,6 +474,10 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
377
474
  }, (0, react.createElement)("main", {
378
475
  className: "dshm-main",
379
476
  "data-dsh-mobile-session": activeSessionId,
477
+ ...rightbarFullWidth ? {
478
+ inert: "",
479
+ "aria-hidden": true
480
+ } : {},
380
481
  "data-pane": "conversation"
381
482
  }, props.renderSlot("main", {}, {
382
483
  entryKey: state.panelInfo.activePanelId ?? "conversation",
@@ -433,10 +534,7 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
433
534
  document.head.append(style);
434
535
  const disposeMainPanelRegistry = controller.bindMainPanelRegistry((id) => ctx.slots.entries("main").some((entry) => entry.options.key === id));
435
536
  const disposeService = ctx.reflect.provide("layout", controller);
436
- const disposePanelInfo = typeof ctx.slots.provideRoot === "function" ? ctx.slots.provideRoot({ hooks: { panelInfo: {
437
- getSnapshot: () => controller.getSnapshot().panelInfo,
438
- subscribe: (listener) => controller.subscribe(listener)
439
- } } }) : () => {};
537
+ const disposePanelInfo = typeof ctx.slots.provideRoot === "function" ? ctx.slots.provideRoot({ hooks: { panelInfo: controller.panelInfo } }) : () => {};
440
538
  const disposeRoot = ctx.slots.register({
441
539
  name: "root",
442
540
  children: {
@@ -506,13 +604,17 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
506
604
  //#endregion
507
605
  exports.MOBILE_LAYOUT_MESSAGES = MOBILE_LAYOUT_MESSAGES;
508
606
  exports.MOBILE_LAYOUT_STYLES = MOBILE_LAYOUT_STYLES;
607
+ exports.TOUCH_PRIMARY_QUERY = TOUCH_PRIMARY_QUERY;
509
608
  exports.WIDE_LAYOUT_MIN_WIDTH_PX = WIDE_LAYOUT_MIN_WIDTH_PX;
510
609
  exports.apply = apply;
511
610
  exports.closeDetailsFromScrim = closeDetailsFromScrim;
512
611
  exports.inject = inject;
612
+ exports.isComposerOwnedFocus = isComposerOwnedFocus;
513
613
  exports.isMobileScrimOpen = isMobileScrimOpen;
614
+ exports.isSessionRowNavigation = isSessionRowNavigation;
514
615
  exports.isSidebarRightControl = isSidebarRightControl;
515
616
  exports.isWideViewportLayout = isWideViewportLayout;
617
+ exports.resolveComposerImePolicy = resolveComposerImePolicy;
516
618
  exports.resolveMobileLayoutLanguage = resolveMobileLayoutLanguage;
517
619
  exports.resolveMobileRightbarLayout = resolveMobileRightbarLayout;
518
620
  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: (\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 rightbarTrack: boolean\n readonly rightbarFullscreen: 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\nconst RIGHTBAR_MIN_WIDTH_PX = 300\nconst RIGHTBAR_MAX_WIDTH_PX = 460\nconst RIGHTBAR_CENTER_MIN_WIDTH_PX = 400\nconst RIGHTBAR_DEFAULT_RATIO = 0.45\nconst SIDEBAR_EXPANDED_WIDTH_PX = 340\nconst SIDEBAR_COLLAPSED_WIDTH_PX = 56\n\nexport function isWideViewportLayout(viewportWidth: number): boolean {\n return viewportWidth >= WIDE_LAYOUT_MIN_WIDTH_PX\n}\n\n/** Resolved right-panel presentation for the dedicated responsive layout. */\nexport interface MobileRightbarLayout {\n readonly docked: boolean\n readonly width: number\n}\n\n/** Preserve a readable conversation width before turning a requested right track into a docked column. */\nexport function resolveMobileRightbarLayout(\n viewportWidth: number,\n sidebarOpen: boolean,\n track: boolean,\n fullscreen: boolean,\n): MobileRightbarLayout {\n const overlayWidth = Math.min(viewportWidth * 0.94, RIGHTBAR_MAX_WIDTH_PX)\n if (!track || fullscreen || (!isWideViewportLayout(viewportWidth) && sidebarOpen)) {\n return Object.freeze({ docked: false, width: overlayWidth })\n }\n const sidebarWidth = isWideViewportLayout(viewportWidth) && sidebarOpen\n ? SIDEBAR_EXPANDED_WIDTH_PX\n : SIDEBAR_COLLAPSED_WIDTH_PX\n const available = Math.floor(viewportWidth - sidebarWidth - RIGHTBAR_CENTER_MIN_WIDTH_PX)\n if (available < RIGHTBAR_MIN_WIDTH_PX) return Object.freeze({ docked: false, width: overlayWidth })\n const preferred = Math.min(\n RIGHTBAR_MAX_WIDTH_PX,\n Math.max(RIGHTBAR_MIN_WIDTH_PX, Math.round(viewportWidth * RIGHTBAR_DEFAULT_RATIO)),\n )\n return Object.freeze({ docked: true, width: Math.min(preferred, available) })\n}\n\n/** Whether an overlay drawer needs the modal scrim without dimming a docked desktop panel. */\nexport function isMobileScrimOpen(\n sidebarOpen: boolean,\n detailsModalOpen: boolean,\n wideViewport: boolean,\n): boolean {\n return (!wideViewport && sidebarOpen) || detailsModalOpen\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 rightbarTrack: false,\n rightbarFullscreen: 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, rightbarTrack: false, rightbarFullscreen: 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 /** Preserve the native seat's distinction between a docked track and fullscreen. */\n openRightbar(track = true, fullscreen = false): void {\n this.update({ detailsOpen: true, rightbarTrack: track, rightbarFullscreen: fullscreen })\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.rightbarTrack === this.snapshot.rightbarTrack\n && snapshot.rightbarFullscreen === this.snapshot.rightbarFullscreen\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;position:relative;min-width:0;min-height:0;margin-right:0;overflow:hidden;transition:margin-right var(--ds-transition-duration-slow,190ms) var(--ds-ease-in-out,ease)}\n.dshm-shell[data-rightbar-docked=true] .dshm-main{margin-right:var(--dshm-rightbar-width)}\n.dshm-shell[data-rightbar-docked=true] .dshm-details{position:absolute;width:var(--dshm-rightbar-width);box-shadow:none;padding-top:0}\n/* Draw above the native absolute panel without consuming its width or\n intercepting controls; only docked mode needs this column separator. */\n.dshm-shell[data-rightbar-docked=true] .dshm-details::after{content:\"\";position:absolute;inset:0 auto 0 0;width:.5px;background:var(--dsw-alias-border-l4);z-index:11;pointer-events:none}\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/* display:none also suppresses fixed descendants installed by the phone adapter. */\n.dshm-drawer[data-right-modal=true]{display:none!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@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],.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.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>*,.dshm-main{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 [viewportWidth, setViewportWidth] = useState(window.innerWidth)\n useEffect(() => {\n let frame: number | undefined\n const resize = (): void => {\n if (frame !== undefined) return\n frame = window.requestAnimationFrame(() => {\n frame = undefined\n setViewportWidth(window.innerWidth)\n })\n }\n window.addEventListener('resize', resize)\n return () => {\n window.removeEventListener('resize', resize)\n if (frame !== undefined) window.cancelAnimationFrame(frame)\n }\n }, [])\n const wideViewport = isWideViewportLayout(viewportWidth)\n const rightbar = resolveMobileRightbarLayout(\n viewportWidth,\n state.sidebarOpen,\n state.rightbarTrack,\n state.rightbarFullscreen,\n )\n const rightbarDocked = state.detailsOpen && rightbar.docked\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 && !rightbarDocked, 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 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', {\n className: 'dshm-shell', lang: language,\n 'data-rightbar-docked': rightbarDocked,\n style: { '--dshm-rightbar-width': `${rightbar.width}px` },\n },\n createElement('main', {\n className: 'dshm-main',\n 'data-dsh-mobile-session': activeSessionId,\n // Legacy dsh-web community plugins use semantic pane attributes as their\n // compatibility path when the stock CSS-module column names are absent.\n 'data-pane': 'conversation',\n },\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-pane': 'sidebar',\n 'data-open': state.sidebarOpen,\n // The phone adapter promotes the whale toggle to a fixed high layer.\n // Remove that entire navigation surface while the right modal owns focus.\n 'data-right-modal': state.detailsOpen && !rightbarDocked,\n ...(state.detailsOpen && !rightbarDocked ? { inert: '', 'aria-hidden': true } : {}),\n // Community rows collapse their label when an ancestor carries this marker.\n ...(state.sidebarOpen ? {} : { 'data-sidebar-collapsed': '' }),\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: rightbar.width,\n viewportWidth,\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;;;;ECuED,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;EACxC,MAAM,wBAAwB;EAC9B,MAAM,wBAAwB;EAC9B,MAAM,+BAA+B;EACrC,MAAM,yBAAyB;EAC/B,MAAM,4BAA4B;EAClC,MAAM,6BAA6B;EAEnC,SAAgB,qBAAqB,eAAgC;GACnE,OAAO,iBAAA;EACT;;EASA,SAAgB,4BACd,eACA,aACA,OACA,YACsB;GACtB,MAAM,eAAe,KAAK,IAAI,gBAAgB,KAAM,qBAAqB;GACzE,IAAI,CAAC,SAAS,cAAe,CAAC,qBAAqB,aAAa,KAAK,aACnE,OAAO,OAAO,OAAO;IAAE,QAAQ;IAAO,OAAO;GAAa,CAAC;GAE7D,MAAM,eAAe,qBAAqB,aAAa,KAAK,cACxD,4BACA;GACJ,MAAM,YAAY,KAAK,MAAM,gBAAgB,eAAe,4BAA4B;GACxF,IAAI,YAAY,uBAAuB,OAAO,OAAO,OAAO;IAAE,QAAQ;IAAO,OAAO;GAAa,CAAC;GAClG,MAAM,YAAY,KAAK,IACrB,uBACA,KAAK,IAAI,uBAAuB,KAAK,MAAM,gBAAgB,sBAAsB,CAAC,CACpF;GACA,OAAO,OAAO,OAAO;IAAE,QAAQ;IAAM,OAAO,KAAK,IAAI,WAAW,SAAS;GAAE,CAAC;EAC9E;;EAGA,SAAgB,kBACd,aACA,kBACA,cACS;GACT,OAAQ,CAAC,gBAAgB,eAAgB;EAC3C;;;;;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,eAAe;IACf,oBAAoB;IACpB,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;KAAE,aAAa;KAAO,eAAe;KAAO,oBAAoB;IAAM,CAAC;GACrF;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;;GAGA,aAAa,QAAQ,MAAM,aAAa,OAAa;IACnD,KAAK,OAAO;KAAE,aAAa;KAAM,eAAe;KAAO,oBAAoB;IAAW,CAAC;GACzF;;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,kBAAkB,KAAK,SAAS,iBACzC,SAAS,uBAAuB,KAAK,SAAS,sBAC9C,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4DpC,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,eAAe,qBAAA,GAAoBC,MAAAA,SAAAA,CAAS,OAAO,UAAU;GACpE,CAAA,GAAA,MAAA,UAAA,OAAgB;IACd,IAAI;IACJ,MAAM,eAAqB;KACzB,IAAI,UAAU,KAAA,GAAW;KACzB,QAAQ,OAAO,4BAA4B;MACzC,QAAQ,KAAA;MACR,iBAAiB,OAAO,UAAU;KACpC,CAAC;IACH;IACA,OAAO,iBAAiB,UAAU,MAAM;IACxC,aAAa;KACX,OAAO,oBAAoB,UAAU,MAAM;KAC3C,IAAI,UAAU,KAAA,GAAW,OAAO,qBAAqB,KAAK;IAC5D;GACF,GAAG,CAAC,CAAC;GACL,MAAM,eAAe,qBAAqB,aAAa;GACvD,MAAM,WAAW,4BACf,eACA,MAAM,aACN,MAAM,eACN,MAAM,kBACR;GACA,MAAM,iBAAiB,MAAM,eAAe,SAAS;GACrD,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,eAAe,CAAC,gBAAgB,YAAY;GACzG,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,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;IAC1B,WAAW;IAAc,MAAM;IAC/B,wBAAwB;IACxB,OAAO,EAAE,yBAAyB,GAAG,SAAS,MAAM,IAAI;GAC1D,IAAA,GACEA,MAAAA,cAAAA,CAAc,QAAQ;IACpB,WAAW;IACX,2BAA2B;IAG3B,aAAa;GACf,GACE,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;IACb,aAAa,MAAM;IAGnB,oBAAoB,MAAM,eAAe,CAAC;IAC1C,GAAI,MAAM,eAAe,CAAC,iBAAiB;KAAE,OAAO;KAAI,eAAe;IAAK,IAAI,CAAC;IAEjF,GAAI,MAAM,cAAc,CAAC,IAAI,EAAE,0BAA0B,GAAG;IAC5D,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,SAAS;IAChB;IAQA,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"}
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 rightbarTrack: boolean\n readonly rightbarFullscreen: 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\nconst RIGHTBAR_MIN_WIDTH_PX = 300\nconst RIGHTBAR_MAX_WIDTH_PX = 460\nconst RIGHTBAR_CENTER_MIN_WIDTH_PX = 400\nconst RIGHTBAR_DEFAULT_RATIO = 0.45\nconst SIDEBAR_EXPANDED_WIDTH_PX = 340\nconst SIDEBAR_COLLAPSED_WIDTH_PX = 56\n\nexport function isWideViewportLayout(viewportWidth: number): boolean {\n return viewportWidth >= WIDE_LAYOUT_MIN_WIDTH_PX\n}\n\n/** Resolved right-panel presentation for the dedicated responsive layout. */\nexport interface MobileRightbarLayout {\n readonly docked: boolean\n readonly width: number\n}\n\n/** Preserve a readable conversation width before turning a requested right track into a docked column. */\nexport function resolveMobileRightbarLayout(\n viewportWidth: number,\n sidebarOpen: boolean,\n track: boolean,\n fullscreen: boolean,\n): MobileRightbarLayout {\n const overlayWidth = isWideViewportLayout(viewportWidth)\n ? Math.min(viewportWidth * 0.94, RIGHTBAR_MAX_WIDTH_PX)\n : viewportWidth\n if (!track || fullscreen || (!isWideViewportLayout(viewportWidth) && sidebarOpen)) {\n return Object.freeze({ docked: false, width: overlayWidth })\n }\n const sidebarWidth = isWideViewportLayout(viewportWidth) && sidebarOpen\n ? SIDEBAR_EXPANDED_WIDTH_PX\n : SIDEBAR_COLLAPSED_WIDTH_PX\n const available = Math.floor(viewportWidth - sidebarWidth - RIGHTBAR_CENTER_MIN_WIDTH_PX)\n if (available < RIGHTBAR_MIN_WIDTH_PX) return Object.freeze({ docked: false, width: overlayWidth })\n const preferred = Math.min(\n RIGHTBAR_MAX_WIDTH_PX,\n Math.max(RIGHTBAR_MIN_WIDTH_PX, Math.round(viewportWidth * RIGHTBAR_DEFAULT_RATIO)),\n )\n return Object.freeze({ docked: true, width: Math.min(preferred, available) })\n}\n\n/** Whether an overlay drawer needs the modal scrim without dimming a docked desktop panel. */\nexport function isMobileScrimOpen(\n sidebarOpen: boolean,\n detailsModalOpen: boolean,\n wideViewport: boolean,\n): boolean {\n return (!wideViewport && sidebarOpen) || detailsModalOpen\n}\n\n/**\n * Query matching devices whose primary pointer cannot hover: the phones and\n * tablets where focusing an editor raises a soft keyboard the user did not ask\n * for. A desktop keeps stock behaviour because it has a real keyboard already.\n */\nexport const TOUCH_PRIMARY_QUERY = '(hover: none), (pointer: coarse)'\n\n/** The stock composer wrapper, which owns everything the phone adapter edits. */\nconst COMPOSER_CARD_SELECTOR = '[data-composer-card]'\n\n/** The composer's editing host, whichever contenteditable flavour it boots with. */\nconst COMPOSER_EDITOR_SELECTOR = `${COMPOSER_CARD_SELECTOR} [contenteditable=\"true\"],${COMPOSER_CARD_SELECTOR} [contenteditable=\"plaintext-only\"]`\n\n/** The composer's Add trigger, whose menu must open without summoning the IME. */\nconst COMPOSER_COMMAND_TRIGGER_SELECTOR = `${COMPOSER_CARD_SELECTOR} button[aria-haspopup=\"listbox\"]`\n\n/**\n * Whether a focus target belongs to the composer, which owns its own keyboard\n * decision and must never be blurred by a suppression window.\n * @param target - the focus target, already narrowed to an element.\n * @returns whether the target sits inside the composer card.\n */\nexport function isComposerOwnedFocus(target: Element | null): boolean {\n return target !== null && target.closest(COMPOSER_CARD_SELECTOR) !== null\n}\n\n/**\n * Whether a click opens a different Session, rather than its menu or current row.\n * @param target - the click target in the workspace sidebar.\n * @returns whether the Session selection can change.\n */\nexport function isSessionRowNavigation(target: Element | null): boolean {\n if (target === null) return false\n const row = target.closest('[role=\"treeitem\"][aria-selected]')\n if (row === null || row.getAttribute('aria-selected') === 'true') return false\n const action = target.closest('button,[role=\"button\"]')\n return action === null || action === row\n}\n\n/**\n * Decide how one pointer event changes the composer's soft keyboard.\n *\n * The Add button's own mousedown calls `keepDraftFocus`, which focuses the draft\n * on purpose so the caret survives the menu; on a phone that focus is what\n * raises the keyboard. `inputmode=\"none\"` withholds the IME without moving focus\n * or the caret, so the menu, the caret, and the draft all keep stock behaviour.\n * @param target - the pointer target, already narrowed to an element.\n * @param touchPrimary - whether the device's primary pointer cannot hover.\n * @returns `withhold` for the Add trigger, `restore` for any other tap, and\n * `ignore` where no soft keyboard exists to withhold.\n */\nexport function resolveComposerImePolicy(\n target: Element | null,\n touchPrimary: boolean,\n): 'ignore' | 'restore' | 'withhold' {\n if (!touchPrimary || target === null) return 'ignore'\n return target.closest(COMPOSER_COMMAND_TRIGGER_SELECTOR) === null ? 'restore' : 'withhold'\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 rightbarTrack: false,\n rightbarFullscreen: 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 /**\n * The panel store on the layout service itself (`ctx.layout.panelInfo`),\n * mirroring the official LayoutController of DSH 0.1.7-rc.2+. The root hook\n * wired in `apply` feeds `usePanelInfo` rendering; rc.2 client plugins read\n * this service member directly. Without it `dsh-client-ui-plugin-manager`\n * fails activation on the dedicated mobile frontend (\"web boot: 1 entry did\n * not activate\") and `dsh-client-ui-open-in-app` throws the first time its\n * shortcut target runs.\n */\n readonly panelInfo = {\n getSnapshot: (): PanelInfoSnapshot => this.getSnapshot().panelInfo,\n subscribe: (listener: () => void): (() => void) => this.subscribe(listener),\n }\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, rightbarTrack: false, rightbarFullscreen: 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 /** Preserve the native seat's distinction between a docked track and fullscreen. */\n openRightbar(track = true, fullscreen = false): void {\n this.update({ detailsOpen: true, rightbarTrack: track, rightbarFullscreen: fullscreen })\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.rightbarTrack === this.snapshot.rightbarTrack\n && snapshot.rightbarFullscreen === this.snapshot.rightbarFullscreen\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;position:relative;min-width:0;min-height:0;margin-right:0;overflow:hidden;transition:margin-right var(--ds-transition-duration-slow,190ms) var(--ds-ease-in-out,ease)}\n.dshm-shell[data-rightbar-docked=true] .dshm-main{margin-right:var(--dshm-rightbar-width)}\n.dshm-shell[data-rightbar-docked=true] .dshm-details{position:absolute;width:var(--dshm-rightbar-width);box-shadow:none;padding-top:0}\n/* Draw above the native absolute panel without consuming its width or\n intercepting controls; only docked mode needs this column separator. */\n.dshm-shell[data-rightbar-docked=true] .dshm-details::after{content:\"\";position:absolute;inset:0 auto 0 0;width:.5px;background:var(--dsw-alias-border-l4);z-index:11;pointer-events:none}\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/* display:none also suppresses fixed descendants installed by the phone adapter. */\n.dshm-drawer[data-right-modal=true]{display:none!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@media(max-width:899px){.dshm-details{width:100%;max-width:100%;box-shadow:none}.dshm-details [data-sidebar-right-toggle],.dshm-details [data-sidebar-right-mode]{min-width:48px;min-height:48px}}\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@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],.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.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>*,.dshm-main{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 suppressComposerUntil = useRef(0)\n const navigationIme = useRef(new Map<HTMLElement, string | null>())\n const restoreNavigationIme = (): void => {\n for (const [editor, previous] of navigationIme.current) {\n if (editor.getAttribute('inputmode') !== 'none') continue\n if (previous === null) editor.removeAttribute('inputmode')\n else editor.setAttribute('inputmode', previous)\n }\n navigationIme.current.clear()\n }\n const [viewportWidth, setViewportWidth] = useState(window.innerWidth)\n useEffect(() => {\n let frame: number | undefined\n const resize = (): void => {\n if (frame !== undefined) return\n frame = window.requestAnimationFrame(() => {\n frame = undefined\n setViewportWidth(window.innerWidth)\n })\n }\n window.addEventListener('resize', resize)\n return () => {\n window.removeEventListener('resize', resize)\n if (frame !== undefined) window.cancelAnimationFrame(frame)\n }\n }, [])\n const wideViewport = isWideViewportLayout(viewportWidth)\n const rightbar = resolveMobileRightbarLayout(\n viewportWidth,\n state.sidebarOpen,\n state.rightbarTrack,\n state.rightbarFullscreen,\n )\n const rightbarDocked = state.detailsOpen && rightbar.docked\n const rightbarFullWidth = state.detailsOpen && !rightbarDocked && !wideViewport\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 = !rightbarFullWidth && isMobileScrimOpen(state.sidebarOpen, state.detailsOpen && !rightbarDocked, 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 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 elementTarget = (target: EventTarget | null): Element | null =>\n target instanceof Element ? target : null\n const suppressAutofocus = (event: FocusEvent): void => {\n const target = elementTarget(event.target)\n if (target === null) return\n const editable = target instanceof HTMLElement && (target.matches('input,textarea') || target.isContentEditable)\n if (!editable) return\n if (isComposerOwnedFocus(target)) {\n if (performance.now() < suppressComposerUntil.current) target.blur()\n return\n }\n if (performance.now() >= suppressKeyboardUntil.current) return\n 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 // The native composer deliberately preserves editor focus when its Add menu\n // opens, and on a phone that focus summons the soft keyboard. Withholding the\n // IME leaves the focus, the caret, and the draft exactly as stock left them;\n // any other tap restores the keyboard.\n const applyComposerImePolicy = (event: PointerEvent): void => {\n const target = elementTarget(event.target)\n // A deliberate tap in the composer ends the navigation-only suppression\n // before focus can reach the editor. The Add trigger then reclaims its\n // existing inputmode=\"none\" policy below.\n if (isComposerOwnedFocus(target)) {\n suppressComposerUntil.current = 0\n restoreNavigationIme()\n }\n const policy = resolveComposerImePolicy(\n target,\n window.matchMedia(TOUCH_PRIMARY_QUERY).matches,\n )\n if (policy === 'ignore') return\n for (const editor of document.querySelectorAll<HTMLElement>(COMPOSER_EDITOR_SELECTOR)) {\n if (policy === 'withhold') editor.setAttribute('inputmode', 'none')\n else if (editor.getAttribute('inputmode') === 'none') editor.removeAttribute('inputmode')\n }\n }\n document.addEventListener('focusin', suppressAutofocus, true)\n document.addEventListener('click', suppressBranchAutofocus, true)\n // A finger tap never fires mousedown on Android, and this has to land before\n // the trigger's own focus, so it runs on capture for pointerdown.\n document.addEventListener('pointerdown', applyComposerImePolicy, true)\n return () => {\n document.removeEventListener('focusin', suppressAutofocus, true)\n document.removeEventListener('click', suppressBranchAutofocus, true)\n document.removeEventListener('pointerdown', applyComposerImePolicy, true)\n restoreNavigationIme()\n // A withheld editor outliving the surface would strand the composer without\n // a keyboard for the rest of the session.\n for (const editor of document.querySelectorAll<HTMLElement>(`${COMPOSER_CARD_SELECTOR} [inputmode=\"none\"]`)) {\n editor.removeAttribute('inputmode')\n }\n }\n }, [])\n\n // Session navigation on touch withholds upstream composer autofocus; only a\n // narrow overlay drawer closes after the row's own click handler completes.\n const closeDrawerAfterSessionAction = (event: { readonly target: EventTarget | null }): void => {\n if (!(event.target instanceof Element)) return\n if (isSessionRowNavigation(event.target) && window.matchMedia(TOUCH_PRIMARY_QUERY).matches) {\n const deadline = performance.now() + 500\n suppressComposerUntil.current = deadline\n for (const editor of document.querySelectorAll<HTMLElement>(COMPOSER_EDITOR_SELECTOR)) {\n if (!navigationIme.current.has(editor)) navigationIme.current.set(editor, editor.getAttribute('inputmode'))\n editor.setAttribute('inputmode', 'none')\n if (document.activeElement === editor) editor.blur()\n }\n window.setTimeout(() => {\n if (suppressComposerUntil.current !== deadline) return\n suppressComposerUntil.current = 0\n restoreNavigationIme()\n }, 500)\n }\n if (viewportIsWide()) 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', {\n className: 'dshm-shell', lang: language,\n 'data-rightbar-docked': rightbarDocked,\n style: { '--dshm-rightbar-width': `${rightbar.width}px` },\n },\n createElement('main', {\n className: 'dshm-main',\n 'data-dsh-mobile-session': activeSessionId,\n ...(rightbarFullWidth ? { inert: '', 'aria-hidden': true } : {}),\n // Legacy dsh-web community plugins use semantic pane attributes as their\n // compatibility path when the stock CSS-module column names are absent.\n 'data-pane': 'conversation',\n },\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-pane': 'sidebar',\n 'data-open': state.sidebarOpen,\n // The phone adapter promotes the whale toggle to a fixed high layer.\n // Remove that entire navigation surface while the right modal owns focus.\n 'data-right-modal': state.detailsOpen && !rightbarDocked,\n ...(state.detailsOpen && !rightbarDocked ? { inert: '', 'aria-hidden': true } : {}),\n // Community rows collapse their label when an ancestor carries this marker.\n ...(state.sidebarOpen ? {} : { 'data-sidebar-collapsed': '' }),\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: rightbar.width,\n viewportWidth,\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: controller.panelInfo } })\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":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mobile",
3
- "version": "0.4.6",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "DeepSeek Harness 移动端适配与安全访问插件,支持局域网、远程连接、Android App 和手机浏览器。",
6
6
  "type": "module",
@@ -151,18 +151,18 @@
151
151
  "@deepseek-ai/cordis": "4.0.4",
152
152
  "@deepseek-ai/cordis-plugin-include": "1.0.9",
153
153
  "@deepseek-ai/cordis-plugin-loader": "1.0.5",
154
- "@deepseek-ai/dsh-agent": "0.1.7-rc.1",
155
- "@deepseek-ai/dsh-attachment": "0.1.7-rc.1",
156
- "@deepseek-ai/dsh-brand": "0.1.7-rc.1",
157
- "@deepseek-ai/dsh-client-connection": "0.1.7-rc.1",
158
- "@deepseek-ai/dsh-commands": "0.1.7-rc.1",
159
- "@deepseek-ai/dsh-host-webserver": "0.1.7-rc.1",
160
- "@deepseek-ai/dsh-invariants": "0.1.7-rc.1",
161
- "@deepseek-ai/dsh-llm": "0.1.7-rc.1",
162
- "@deepseek-ai/dsh-scope": "0.1.7-rc.1",
163
- "@deepseek-ai/dsh-session": "0.1.7-rc.1",
164
- "@deepseek-ai/dsh-timeout": "0.1.7-rc.1",
165
- "@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.1",
154
+ "@deepseek-ai/dsh-agent": "0.1.7-rc.2",
155
+ "@deepseek-ai/dsh-attachment": "0.1.7-rc.2",
156
+ "@deepseek-ai/dsh-brand": "0.1.7-rc.2",
157
+ "@deepseek-ai/dsh-client-connection": "0.1.7-rc.2",
158
+ "@deepseek-ai/dsh-commands": "0.1.7-rc.2",
159
+ "@deepseek-ai/dsh-host-webserver": "0.1.7-rc.2",
160
+ "@deepseek-ai/dsh-invariants": "0.1.7-rc.2",
161
+ "@deepseek-ai/dsh-llm": "0.1.7-rc.2",
162
+ "@deepseek-ai/dsh-scope": "0.1.7-rc.2",
163
+ "@deepseek-ai/dsh-session": "0.1.7-rc.2",
164
+ "@deepseek-ai/dsh-timeout": "0.1.7-rc.2",
165
+ "@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.2",
166
166
  "@types/js-yaml": "^4.0.9",
167
167
  "@types/node": "^26.5.1",
168
168
  "@types/qrcode": "^1.5.5",